МИНИСТЕРСТВО ОБРАЗОВАНИЯ РЕСПУБЛИКИ БАШКОРТОСТАН
Государственное бюджетное профессиональное образовательное учреждение
Уфимский колледж радиоэлектроники, телекоммуникаций и безопасности	

	УТВЕРЖДАЮ
Зам. директора 
_____________ Л.Р. Туктарова
«30» августа 2019 г.	

СБОРНИК МЕТОДИЧЕСКИХ УКАЗАНИЙ

ДЛЯ СТУДЕНТОВ ПО ВЫПОЛНЕНИЮ

ПРАКТИЧЕСКИХ РАБОТ

МЕЖДИСЦИПЛИНАРНЫЙ КУРС
 «СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ»

специальность 09.02.07 «Информационные системы и программирование»

	СОГЛАСОВАНО
Зав. кафедрой
____________ М.Е. Бронштейн
РАЗРАБОТАЛ:
Преподаватель
_____________ М.А.Кашина	
Уфа 2019 г.

СОДЕРЖАНИЕ

	Стр.	
Предисловие	5	
Практическая работа № 1-2 «Использование символьных строк при разработке программного кода»	7	
Практическая работа № 3-4 «Обработка строк с помощью специальных директив»	13	
Практическая работа № 5-6 «Использование связных списков»	21	
Практическая работа № 7-8 «Использование структур»	33	
Практическая работа № 9-10 «Вывод информации на экран в графическом режиме»	39	
Практическая работа № 11-12 «Представление в памяти двухмерных массивов и матриц»	42	
Практическая работа № 13-14 «Представление в памяти многомерных массивов и матриц»	48	
Практическая работа № 15-16 «Получение навыков в определении конфигурации основных характеристик ЭВМ»	51	
Практическая работа № 17-18 «Изучение функций системного таймера»	59	
Практическая работа № 19-20 «Загрузочная запись»	66	
Практическая работа№21-22 «Управление проектами»	72	
Практическая работа № 23-24 «Изучение организации и принципов работы клавиатуры»	79	
Практическая работа № 25-26 «Изучение особенностей функционирования видеосистемы	87	
Практическая работа № 27-28 Изучение принципов управления программами в MS DOS	93	
Практическая работа №29-30 «Использование драйверов DOS»	101	
Практическая работа №31-32 «Изучение и распределение памяти в MS DOS»	111	
Практическая работа №33-35 «Использование динамических библиотек для создания приложений»	121	

ПРЕДИСЛОВИЕ

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

Правила выполнения практических работ
	1. Студент должен прийти на практическое занятие подготовленным к его выполнению.
	2. После проведения практической работы студент должен представить отчет о проделанной работе.
	3. Отчет о проделанной работе следует выполнять в журнале практических работ на листах формата А4 с одной стороны листа.
Оценку по практической работе студент получает, если:
- студентом работа выполнена в полном объеме;
- студент может пояснить выполнение любого этапа работы;
- отчет выполнен в соответствии с требованиями к выполнению работы;
- студент отвечает на контрольные вопросы на удовлетворительную оценку и выше.
	Зачет по выполнению практических работ студент получает при условии выполнения всех предусмотренных программой работ после сдачи журнала с отчетами по работам и оценкам.

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

Обеспеченность занятия (средства обучения):
1.	Учебно-методическая литература:
- Архитектура и проектирование программных систем : монография / С.В. Назаров. — 2-е изд., перераб. и доп. — М. : ИНФРА-М, 2017. — 374 с. — (Научная мысль). — www.dx.doi.org/ 10.12737/18292.
- Проектирование информационных систем: Учебное пособие / Н.Н. Заботина. - М.: НИЦ ИНФРА-М, 2014. - 331 с.: 60x90 1/16 + ( Доп. мат. znanium.com). - (Высшее образование: Бакалавриат). (переплет) ISBN 978-5-16-004509-2, 100 экз.
- Архитектура ЭВМ и вычислительных систем : учебник / Н.В. Максимов, Т.Л. Партыка, И.И. Попов. — 5-е изд., перераб. и доп. — М. : ФОРУМ : ИНФРА-М, 2017. — 511 с. — (Среднее профессиональное образование).

2.	Справочная литература:
•	справочник по С++
3.	Технические средства обучения:
•	персональный компьютер.
4.	Программное обеспечение: VisiolStudio
5.	Отчет по выполнению практичсеких работ.

Порядок выполнения отчета по практической работе
1.	Ознакомиться с теоретическим материалом по практической работе.
2.	Записать краткий конспект теоретической части.
3.	Выполнить предложенное задание согласно варианту по списку группы.
4.	Продемонстрировать результаты выполнения предложенных заданий преподавателю.
5.	Записать алгоритм в виде блок-схемы, псевдокода и кода программы в отчет.
6.	Ответить на контрольные вопросы.
7.	Записать выводы о проделанной работе.

Практическая работа № 1-2 
«Использование символьных строк при разработке программного кода»
Цель работы: закрепление практических навыков при работе с функциями и указателями языка Си, обеспечение функциональной модульности.
Образовательные результаты:
Студент должен 
уметь: 
     - осуществлять разработку кода программного модуля на современных языках программирования;
  - создавать программу по разработанному алгоритму как отдельный модуль. 
знать: 
          -основные этапы разработки программного обеспечения;
   - основные принципы технологии структурного и объектно-ориентированного   программирования.

Краткие теоретические и учебно-методические материалы 
Символьная строка в языке C++ представляется в памяти как массив символов, в конце которого находится байт с кодом 0 - признак конца строки. Строку, как и любой другой массив можно обрабатывать либо традиционным методом - как массив, с использованием операции индексации, либо через указатели, с использованием операций адресной арифметики. При работе со строкой как с массивом нужно иметь в виду, что длина строки заранее неизвестна, так что циклы должны быть организованы не со счетчиком, а до появления признака конца строки.
Передача параметров может осуществляться по значению и по ссылке. Переменные, в которых сохраняются параметры, передаваемые функции, также являются локальными для этой функции. Эти переменные создаются при вызове функции и в них копируются значения, передаваемые функции в качестве параметров. Эти переменные можно изменять, но все изменения этих переменных будут "забыты" после выхода из функции. 
Задача. Составить программу, которая во вводим с клавиатуры слове меняет местами первую и последнюю буквы. Новое слово необходимо вывести на экран.
Входные данные: строка s.
Выходные данные: преобразованная строка s.
Для работы со строками в C++ существует специальный класс string. Для его работы в начале программы необходимо подключить заголовочный файл string: #include <string>.
Для работы со строкой воспользуемся функцией size, которая возвращает число элементов в строке.

В переменной целого типа len будем хранить число элементов в строке.

Перепишем строку s в строку s1. Так как символы в строке нумеруются с 0, то нулевому символу строки s присвоим значение (len - 1) - го символа строки s1, а (len - 1) -му символу строки присвоим нулевой символ строки s1.

Остается только вывести строку s на экран.

Текст программы:
#include "stdafx.h"
#include <string>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{string s,s1;
cin>>s;
int len;
len=s.size();
s1=s;
s[0]=s1[len-1];
s[len-1]=s1[0];
cout<<s;
           system("pause");
Указатель — это переменная, которая содержит адрес некоторого объекта в памяти компьютера. Простейшая операция над указателями — это операция &, что означает "взять адрес". Существует еще одна операция над указателями. Она обозначается символом звездочка *. Смысл этой операции таков: "значение, расположенное по указанному адресу". Хотя знак звездочка * соответствует обычной операции умножения, но никак нельзя перепутать эти две операции. Ведь арифметическая операция умножения имеет два операнда. Иначе говоря, при умножении должны быть указаны, как данные, два числа, участвующие в умножении. Поэтому и говорят, что умножение — это бинарная операция. Операция * над указателями, в отличие от арифметического умножения, — это унарная операция. То есть, другими словами, она использует всего один операнд (одно данное). 
Пример: Написать программу Функция - substr(s,n,l). Назначение - выделение из строки s подстроки, начиная с позиции n, длиной l. 
Определим состав параметров функции: 
            int substr (src, dest, num, len); где	src -	строка, с которой выбираются символы; dest -	строка, в которую записываются символы; num -	номер первого символа в строке src, с которого начинается подстрока (нумерация символов ведется с 0); len - длина выходной строки. Возможные возвращаемые значения функции установим: 1 (задание параметров правильное) и 0 (задание не правильное). Эти значения при обращениях к функции можно будет интерпретировать как "истина" или "ложь". 
Обозначим через Lsrc длину строки src. Тогда возможны такие варианты при задании параметров: 
num+len <= Lsrc - полностью правильное задание; 
num+len > Lsrc; num < Lsrc - правильное задание, но длина выходной строки буде меньше, чем len; 
num >= Lsrc - неправильное задание, выходная строка будет пустой; 
num < 0 или len <= 0 - неправильное задание, выходная строка будет пустой. 
Программа состоит из одного программного модуля - файл LAB1.C. В состав модуля входят три функции - main, substr_mas и subs_ptr. Общих переменных в программе нет. Макроконстантой N определена максимальная длина строки - 80. 
Функция main является главной функцией программы, она предназначена для ввода исходных данных, вызова других функций и вывода результатов. В функции определены переменные: 
ss и dd - входная и выходная строки соответственно; 
n - номер символа, с которого должна начинаться выходная строка; 
l - длина выходной строки. 
Функция запрашивает и вводит значение входной строки, номера символа и длины. Далее функция вызывает функцию substr_mas, передавая ей как параметры введенные значения. Если функция substr_mas возвращает 1, выводится на экран входная и выходная строки, если 0 - выводится сообщение об ошибке и входная строка. Потом входная строка делается пустой и то же самое выполняется для функции substr_ptr. 
Функция substr_mas выполняет поставленное задание методом массивов. Ее параметры: - src и dest - входная и выходная строки соответственно, представленные в виде массивов неопределенного размера; num и len. Внутренние переменные i и j используются как индексы в массивах. 
Если признак конца в первых num символах не найден, выполняется цикл, в котором индекс входного массива начинает меняться от 1, а индекс выходного - от 0. В каждой итерации этого цикла один элемент входного массива пересылается в выходной. Если пересланный элемент является признаком конца строки, то функция немедленно заканчивается, возвращая 1. Если в цикле не встретится конец строки, цикл завершится после len итераций. В этом случае в конец выходной строки записывается признак конца и функция возвращает 1. 
Если признак конца не найден, выполняется цикл, подобный первому циклу while, но по счетчику len. В каждой итерации этого цикла символ, на который показывает src переписывается по адресу, задаваемому dest, после чего оба указателя увеличиваются на 1. Цикл закончится, когда будет переписано len символов или встретится признак конца строки. В любом варианте завершения цикла по текущему адресу, который содержится в указателе dest, записывается признак конца строки, и функция завершается, возвращая 1. 
Код программы
#include <stdio.h>
#define N 80
int substr_mas(char src[N],char dest[N],int num,int len){
int i, j;
/* проверка случая 4 */
if ( (num<0)||(len<=0) ) {
dest[0]=0;  return 0;}
/* выход на num-ый символ */
for (i=0; i<=num; i++)

/* проверка случая 3 */
if ( src[i]=='\0') {
dest[0]=0;  return 0;}
/* перезапись символов */
for (i--, j=0; j<len; j++, i++) {
dest[j]=src[i];

/* проверка случая 2 */
if ( dest[j]=='\0') return 1;}
/* запись признака конца в выходную строку */
dest[j]='\0';
return 1;}

int substr_ptr(char *src, char *dest, int num, int len) {
/* проверка случая 4 */
if ( (num<0)||(len<=0) ) return dest[0]=0;
/* выход на num-ый символ или на конец строки */
while ( num-- && *src++ );

/* проверка случая 3 */
if ( !num ) return dest[0]=0;
/* перезапись символов */
while ( len-- && *src )  *dest++=*src++;
/* запись признака конца в выходную строку */
*dest=0;
return 1;}
main()
{ char ss[N], dd[N];
int n, l;
clrscr();
printf("Вводите строку:\n");
gets(ss);
printf("начало=");
scanf("%d",&n);
printf("длина=");
scanf("%d",&l);
printf("Массивы:\n");
if (substr_mas(ss,dd,n,l))  printf(">>%s<<\n>>%s<<\n",ss,dd);
else printf("Ошибка! >>%s<<\n",dd);
dd[0]='\0';
printf("Адресная арифметика:\n");
if (substr_ptr(ss,dd,n,l))  printf(">>%s<<\n>>%s<<\n",ss,dd);
else printf("Ошибка! >>%s<<\n",dd);
getch();}

Задания для практической работы:
Создать функцию для обработки символьных строк. За образец брать библиотечные функции обработки строк языка Cи, но не применять их в своей функции. Предусмотреть обработку ошибок в задании параметров и особые случаи. Разработать два варианта заданной функции - используя традиционную обработку массивов и используя адресную арифметику. 
1.	Составить программу, которая во вводимой с клавиатуры строке удаляет n элементов, начиная с k-го элемента. Где n и k — вводимые с клавиатуры числа целого типа. Преобразованную строку вывести на экран.
2.	Ввести с клавиатуры строку. После каждого символа ‘a’ вставить пробел. Преобразованную строку вывести на экран.
3.	Даны две строки. Проверить, совпадают ли они, и вывести соответствующее сообщение.
4.	Удалить из строки все символы ‘+’. Преобразованную строку вывести на экран.
5.	Ввести с клавиатуры. Подсчитать, сколько раз среди символов данной строки встречается символ ‘+’ и сколько раз символ ‘—’.
6.	Составить программу, которая во вводимой с клавиатуры строке заменяет в ней все восклицательные знаки точками.
7.	Выяснить, верно ли, что среди символов строки имеются все буквы, входящие в слово ‘father’.
8.	Дана строка s. Удалить из данной строки последовательность символов букв вида ‘no’.
9.	Дана строка s, среди символов которой есть восклицательный знак. Сформировать новую строку, содержащую все символы до первого появления восклицательного знака в исходной строке.
10.	Дана строка s. Преобразовать строку, удалив из нее все запятые.
11.	Даны две строки. Распечатать строку, в которой содержится большее количество символов.
12.	Дана строка s. Подсчитать количество символов ‘x’ в строке.
13.	Проверить, встречается ли в строке s подстрока s1.
14.	Дана строка s. Преобразовать строку, заменив все символы ‘=’ на символ ‘!’
15. Составить программу, которая во вводимой с клавиатуры строке заменяет в ней каждую точку многоточием (т.е. тремя точками).
16	Функция - Copies(s,s1,n). Назначение - копирование строки s в строку s1 n раз
17	Функция - Words(s). Назначение - подсчет слов в строке s
18	Функция - Concat(s1,s2). Назначение - конкатенация строк s1 и s2 
19	Функция - Parse(s,t). Назначение - разделение строки s на две части: до первого вхождения символа t и после него
20	Функция - Center(s1,s2,l). Назначение - центрирование - размещение строки s1 в середине строки s2 длиной l
21	Функция - Delete(s,n,l). Назначение - удаление из строки s подстроки, начиная с позиции n, длиной l 
22	Функция - Left(s,l). Назначение - выравнивание строки s по левому краю до длины l.
23	Функция - Right(s,l) Назначение - выравнивание строки s по правому краю до длины l.
24	Функция - Insert(s,s1,n). Назначение - вставка в строку s подстроки s1, начиная с позиции n (аналогичная библиотечная функция есть в Pascal).
25	Функция - Reverse(s). Назначение - изменение порядка символов в строке s на противоположный.
26	Функция - Pos(s,s1). Назначение - поиск первого вхождения подстроки s1 в строку s (аналогичная функция C - strstr).
27. Функция - LastPos(s,s1). Назначение - поиск последнего вхождения подстроки s1 в строку s.
28	Функция - WordIndex(s,n). Назначение - определение позиции начала в строке s слова с номером n.
29	Функция - WordLength(s,n). Назначение - определение длины слова с номером n в строке s.
15	Функция - SubWord(s,n,l). Назначение - выделение из строки s l слов, начиная со слова с номером n.
30	Функция - WordCmp(s1,s2). Назначение - сравнение строк (с игнорированием множественных пробелов). 
Контрольные вопросы
1. Как осуществляется передача аргументов по ссылке?
2. Перечислите операции над строками и приведите примеры их прототипов.
3. Что такое разыменовывание указателя?
4. За что отвечает функция getchar()? 
5. Как осуществляется объявление символьных строк на языке Си?

Практическая работа 3-4 
«Обработка строк с помощью специальных директив»
Цель работы – изучить особенности работы с директивами препроцессора в С++.
Образовательные результаты:
Студент должен 
уметь: 
     - осуществлять разработку кода программного модуля на современных языках программирования;
  - создавать программу по разработанному алгоритму как отдельный модуль. 
знать: 
          -основные этапы разработки программного обеспечения;
  - основные принципы технологии структурного и объектно-ориентированного   программирования.

Краткие теоретические и учебно-методические материалы
Препроцессор — это компьютерная программа, принимающая данные на входе и выдающая данные, предназначенные для входа другой программы
(например, компилятора). О данных на выходе препроцессора говорят, что они находятся в препроцессированной форме, пригодной для обработки последующими программами (компилятор). Результат и вид обработки зависят от вида препроцессора; так, некоторые препроцессоры могут только выполнить простую текстовую подстановку, другие способны по возможностям сравниться с языками программирования. Наиболее частый случай использования препроцессора - обработка исходного кода перед передачей его на следующий шаг компиляции. Языки программирования C/C++ и система компьютерной вёрстки TeX используют препроцессоры, значительно расширяющие их возможности.
В некоторых языках программирования этап компиляции и трансляции получили название «препроцессинга».
Макрокоманда, макроопределение или макрос - программный алгоритм действий, записанный пользователем. Часто макросы применяют для
выполнения рутинных действий.
Препроцессорная обработка (макрообработка) — это преобразование текста путем замены препроцессорных переменных их значениями и выполнения препроцессорных операторов (директив препроцессора).
В общем случае препроцессорные средства включают:
определение препроцессорных переменных и присвоенных им
значений;
средства управления просмотром преобразуемого текста;
правила подстановки значений макропеременных.
Основные директивы препроцессора
#include — вставляет текст из указанного файла
#define — задаёт макроопределение (макрос) или символическую константу
#undef — отменяет предыдущее определение
#if — осуществляет условную компиляцию при истинности константного выражения
#ifdef — осуществляет условную компиляцию при определённости символической константы
#ifndef — осуществляет условную компиляцию при неопределённости символической константы
#else — ветка условной компиляции при ложности выражения
#elif — ветка условной компиляции, образуемая слиянием else и if
#endif — конец ветки условной компиляции
#line — препроцессор изменяет номер текущей строки и имя компилируемого файла
#error — выдача диагностического сообщения
#pragma — действие, зависящее от конкретной реализации компилятора.

Директива #include
Директива #include позволяет включать в текст программы указанный файл. Если файл является стандартной библиотекой и находится в папке компилятора, он заключается в угловые скобки <>.
Если файл находится в текущем каталоге проекта, он указывается в кавычках "". Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь. 
#include <stdio.h>
#include "func.c"
Директива #define
Директива #define позволяет вводить в текст программы константы и макроопределения.
Общая форма записи
#define Идентификатор Замена
Поля Идентификатор и Замена разделяются одним или несколькими пробелами.
Директива #define указывает компилятору, что нужно подставить строку, определенную аргументом Замена, вместо каждого аргумента Идентификатор в исходном файле. Идентификатор не заменяется, если он находится в комментарии, в строке или как часть более длинного идентификатора.
#include <stdio.h>
#define A 3
int main()
{
  printf("%d + %d = %d", A, A, A+A); // 3 + 3 = 6
  getchar();
  return 0;
}
В зависимости от значения константы компилятор присваивает ей тот или иной тип. С помощью суффиксов можно переопределить тип константы:
•	U или u представляет целую константу в беззнаковой форме (unsigned);
•	F (или f) позволяет описать вещественную константу типа float;
•	L (или l) позволяет выделить целой константе 8 байт (long int);
•	L (или l) позволяет описать вещественную константу типа long double
#define A 280U   // unsigned int
#define B 280LU  // unsigned long int
#define C 280    // int (long int)
#define D 280L   // long int
#define K 28.0   // double
#define L 28.0F  // float
#define M 28.0L  // long double
Вторая форма синтаксиса определяет макрос, подобный функции, с параметрами. Эта форма допускает использование необязательного списка параметров, которые должны находиться в скобках. После определения макроса каждое последующее вхождение
идентификатор(аргумент1, ..., агрументn)
замещается версией аргумента замена, в которой вместо формальных аргументов подставлены фактические аргументы.

Пример на Си: Вычисление синуса угла
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.14159265
#define SIN(x) sin(PI*x/180)
int main()
{
  int c;
  system("chcp 1251");
  system("cls");
  printf("Введите угол в градусах: ");
  scanf("%d", &c);
  printf("sin(%d)=%lf", c, SIN(c));
  getchar(); getchar();
  return 0;
}
Результат выполнения

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

Однако при использовании таких макроопределений следует соблюдать осторожность, например
#include <stdio.h>
#define sum(A,B) A+B
int main()
{
  int a, b, c, d;
  a = 3; b = 5;
  c = (a + b) * 2; // c = (a + b)*2
  d = sum(a, b) * 2; // d = a + b*2;
  printf(" a = %d\n b = %d\n", a, b);
  printf(" c = %d \n d = %d \n", c, d);
  getchar();
  return 0;
}
Результат выполнения:
По умолчанию текст макроопределения должен размещаться на одной строке. Если требуется перенести текст макроопределения на новую строку, то в конце текущей строки ставится символ "обратный слеш" — \.
#include <stdio.h>
#define sum(A,B) A + \
                 B
int main()
{
  int a, b, c, d;
  a = 3; b = 5;
  c = (a + b) * 2; // c = (a + b)*2
  d = sum(a, b) * 2; // d = a + b*2;
  printf(" a = %d\n b = %d\n", a, b);
  printf(" c = %d \n d = %d \n", c, d);
  getchar();
  return 0;
}

Кроме того, директива #define позволяет замещать часть идентификатора. Для указания замещаемой части используется ##.
#include <stdio.h>
#define SUM(x,y) (a##x + a##y)
int main()
{
  int a1 = 5, a2 = 3;
  printf("%d", SUM(1, 2)); // (a1 + a2)
  getchar();
  return 0;
}
Результат выполнения:

Условная компиляция
Директивы #if или #ifdef/#ifndef вместе с директивами #elif, #else и #endif управляют компиляцией частей исходного файла.
Если указанное выражение после #if имеет ненулевое значение, в записи преобразования сохраняется группа строк, следующая сразу за директивой #if. Синтаксис условной директивы следующий:

#if константное выражение
   группа операций
#elif константное выражение
   группа операций
#else
   группа операций
#endif
Отличие директив  #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define.

У каждой директивы #if в исходном файле должна быть соответствующая закрывающая директива #endif. Между директивами #if и #endif может располагаться любое количество директив #elif, однако допускается не более одной директивы #else. Директива #else, если присутствует, должна быть последней перед директивой #endif.
Пример
#include <stdio.h>
#include <stdlib.h>
#define P 2
int main()
{
  system("chcp 1251");
  system("cls");
#if P==1
  printf("Выполняется ветка 1");
#elif P==2
  printf("Выполняется ветка 2, P=%d", P);
#else
  printf("Выполняется другая ветка, P=%d", P);
#endif
  getchar();
  return 0;
}
Результат выполнения

Задание для практической работы 
Разработать пошаговый алгоритм решения задачи. Выполнить программную реализацию задачи согласно варианту. В программе обязательно предусмотреть вывод информации об исполнителе работы (ФИО), номере варианта, выбранном уровне сложности и задании. Предусмотреть возможность повторного запуска программы (запрос о желании выйти из программы, или продолжить работу). Отслеживать все особые случаи входных данных.
Ключевые моменты программы обязательно должны содержать комментарии.
1.	Используя, один и тот же идентификатор объявить цифровой и символьный массивы. Для цифрового массива определить количество повторений каждого числа. Для символьного массива найти позицию и значение символа по заданному коду символа. Использовать условную компиляцию.
2.Проверить вводимые с клавиатуры числа на чётность и положительность. Оформить в виде макросов определение чётности, положительности, вывод диагностических уведомлений. С помощью условной компиляции разделить процессы определения чётности и положительности чисел.
3.Используя макросы __FILE__, __LINE__, вывести информационные сообщения. Сформировать произвольное сообщение о программной ошибке.
4.Используя один и тот же идентификатор объявить цифровой и символьный массивы. Для цифрового массива найти значение максимального элемента. Для символьного массива найти количество слов (слова разделяются пробелами). Использовать условную компиляцию.
5.Задать макрос вычисления n-ого члена геометрической прогрессии по введённым пользователем первому члену и знаменателю.
6.Задать макрос вычисления периметра эллипса по известным радиусам a и b.
7.Используя условную компиляцию вычислить значение переменной.
Функции max, min и возведение в степень выполнить в виде макросов.
8.Используя условную компиляцию вычислить значение переменной.
Функции max, min, взятие модуля и возведение в степень выполнить в виде
макросов.
9.Продемонстрировать особенности применения опции comment
директивы #pragma.
10.Используя условную компиляцию вычислить значение переменной.
Функции max, min, взятие модуля и возведение в степень выполнить в виде
макросов.
11.Используя условную компиляцию вычислить значение переменной.
Функции max, min, взятие модуля, возведение в степень, извлечение корня выполнить в виде макросов.
12. Используя, один и тот же идентификатор объявить цифровой и символьный массивы. Для цифрового массива найти значение минимального 11.

элемента. Для символьного массива определить наиболее часто встречаемый
символ. Использовать условную компиляцию.
13.Используя условную компиляцию вычислить значение переменной.
Функции max, min, возведение в степень, извлечение корня выполнить в виде
макросов. Условие выбора задать самостоятельно.
14.Используя условную компиляцию вычислить значение переменной.
Условие выбора задать самостоятельно.
15.Используя макросы __DATE__, __TIME__ вывести информационные сообщения. Сформировать произвольное сообщение о программной ошибке.
16.Продемонстрировать особенности использования директивы #line в
обоих режимах.
17.Используя условную компиляцию вычислить значение переменной.
Функции max, min, взятие модуля, возведение в степень выполнить в виде
макросов. Условие выбора задать самостоятельно.
18.Используя условную компиляцию вычислить значение переменной.
Функции max, min, возведение в степень выполнить в виде макросов. Условие
выбора задать самостоятельно.
19.Продемонстрировать особенности применения опций startup и exit
директивы #pragma.12
20. Проверить вводимые с клавиатуры числа на нечётность и положительность. Оформить в виде макросов определение нечётности,
положительности, вывод диагностических уведомлений. С помощью условной компиляции разделить процессы определения нечётности и положительности чисел.
21. Проверить вводимые с клавиатуры числа на чётность и отрицательность. Оформить в виде макросов определение чётности,
отрицательности, вывод диагностических уведомлений. С помощью условной компиляции разделить процессы определения чётности и отрицательности чисел.
22.Решить квадратное уравнение, используя макросы для задания формулы дискриминанта, корней уравнения и выведения ответа на экран.
23.Продемонстрировать особенности использования директивы #error.
24.Задать макрос вычисления площади круга по введённому пользователем радиусу. Исследовать макрос при использовании постфиксных и префиксных операций.
25.Задано два натуральных числа n, m (n, m ≤ 100), если условие выполняется найти среднее арифметическое чисел, иначе остановить компиляцию при помощи директивы #error.
26.Продемонстрировать работу операции ##.
27.Требуется найти наименьшее натуральное число Q такое, что произведение его цифр равно заданному числу N. Произведение вычислять при помощи макросов с параметрами.
28.Заданы первый и второй элементы арифметической прогрессии.
Найти n-й элемент прогрессии, при вычислениях использовать макросы с
передачей параметров.13+
29.Заданы два числа m и n, которые являются числителем и знаменателем некоторой дроби. Произвести сокращение этой дроби, если возможно используя макросы при вычислениях.
30.Задать макрос вычисления объема эллипсоида по известным радиусам a,b и c.

КОНТРОЛЬНЫЕ ВОПРОСЫ
1.Назначение макросов?
2.Что такое макроподстановка?
3.Что такое именованная константа?
4.В чём состоит сущность условной компиляции?
5.Привести пример использования макрокоманд обработки переменного числа параметров.

Практическая работа № 5-6 
 «Использование структур и связных списков»
Цель работы: закрепление практических навыков в работе со структурами и связными списками языка C++, обеспечении функциональной модульности. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования.

Краткие теоретические и учебно-методические материалы 
Список членов структуры носит название шаблона (template). Структура на самом деле не определяет никаких переменных. Члены структуры сами по себе не являются переменными, они представляют собой компоненты одной или нескольких переменных. Такие переменные называются структурными переменными и должны быть определены как имеющие тип соответствующей структуры. Шаблон определяет эти компоненты и говорит компилятору, сколько памяти следует зарезервировать для каждой структурной переменной. 
Ключевое слово struct указывает компилятору на то, что он имеет дело со структурой, а тип записи определяет шаблон, к которому относится переменная. Следом за типом записи идет имя переменной, которое будет использоваться в программе.
Если до начала работы с данными невозможно определить, сколько памяти потребуется для хранения, память выделяется по мере необходимости отдельными блоками, связанными друг с другом с помощью указателей. Такой способ организации данных называется динамическими данными, поскольку их размер изменяется во время выполнения программы.  Динамические данные могут занимать несмежные участки оперативной памяти.
Элемент динамического данного, часто называемый узлом, представляет собой структуру, содержащую по крайней мере два поля: для хранения данных (информационная часть узла) и для указателей (адресная часть узла). Полей данных и указателей может быть несколько.  Поля данных могут быть любого типа. Описание простейшего элемента (узла) выглядит следующим образом:
struct Node				
{ Data d;		       // информационная часть узла 
                                     // тип данных Data должен быть определен ранее
   Node    *next;	       // адресная часть узла 
};					
 Самый простой способ связать элементы – сделать так, чтобы каждый элемент содержал адрес следующего. Такие динамические данные называются односвязным списком.
Над списками можно выполнять следующие операции:
•	добавление элемента в конец списка;
•	удаление элемента из списка;
•	начальное формирование списка;
•	просмотр (вывод) списка.

Пример: для заданной прикладной области разработать описание объектов этой области. Разработать процедуры, реализующие базовые операции над этими объектами, в том числе: 
текстовый ввод-вывод (консольный и файловый); 
присваивание; 
задание константных значений; 
сравнение (не менее 2-х типов). 
Процедуры и описания данных должны составлять отдельный модуль (модуль типа данных). 
Подготовить на магнитном носителе файл исходных данных, содержащих не менее 10 значений конкретных объектов. 
Используя процедуры и описания модуля типа данных, разработать программу, обеспечивающую ввод исходных данных из первого файла данных в память и хранение их в памяти в виде связного списка, сортировку списка по алфавитному и по числовому параметру. 
Для каждой области перечислены параметры объекта. Среди параметров обязательно есть ключевое алфавитное поле (например, фамилия), которое идентифицирует объект, у каждого объекта имеется также одно или несколько числовых полей, по которым вероятны обращения к объекту. Набор характеристик может быть расширен и усложнен по усмотрению исполнителя. 
"База данных" в оперативной памяти представляется в виде однонаправленного линейного списка. Структура элемента списка содержит четыре поля: 
  struct _emlp{
    char name[25]; /* Ф.И.О. */
    int  grade; /* Должность */
    int  hight; /* Звание */
    struct _emlp *next; /* Указатель на следующий элемент */
    };
Для сокращения записи определяем текст struct _emlp как _emlp: 
            #define emlp struct _emlp
Программное изделие выполняется в виде одного программного модуля, файла LAB2.C, в котором размещаются данные, функция main и вспомогательные функции. 
Общими переменными программы являются: 
  struct _emlp *emlph=NULL; /* указатель на начало списка */
  char fname[]="D_STRUCT.DA1"; /* имя файла для хранения списка */
Функция main В функции main введена дополнительная структура, которая описывает меню. Ее полями являются: 
op - номер элемента меню; 
*(opf) - адрес соответствующей функции обработки. 
Основой функции main является цикл for в котором и осуществляется работа с базой данных. Сначала вертикально выводятся пункты меню, затем пользователю предлагается ввести код операции,которую он хочет проделать над базой. После чего производится вызов соответствующей функции обработки. 
Рассмотрим подробнее эту операцию.В цикле перебираются все возможные значения номера меню (их всего 10) и идет сравнение ранее введенного номера с текущим,если они совпали, то по адресу, записанному в поле *(opf) элемента массива структуры меню с текущим номером,вызывается функция обработки: 
            for (i=0; i<10;i++){
           if (opcode==m[i].op) {
           if (m[i].opf()==1) eoj=1;
           break;
           }
По завершению работы функции производится выход из внутреннего цикла for и вся последовательность действий повторяется заново. Выход из внешнего цикла и соответственно из программы осуществляется при выборе последнего пункта меню с помощью функции exit. 
/* Для сокращения записи типа структуры введем следующую константу */
#define emlp struct _emlp   /* Функция печати списка */
int f_print();                /* Функция ввода списка */
int f_input();               /* Добавление элемента в список */
int f_add();                /* Уничтожение элемента списка */
int f_delete();             /* Изменение значения полей элемента списка */
int f_change() ;           /* Функция сортировки списка */
int f_sort();              /* Функция сохранения списка на диске */
int f_save();             /* Перезапись списка из файла в динамическую память */
int f_restore();
#include  <stdio.h>
#include <alloc.h>
/* Описание структуры */
emlp{
   char name[25]; /* Ф.И.О. */
   int  grade; /* Должность */
   int  hight; /* Звание */
   emlp *next; /* Указатель на следующий элемент */
   };
emlp *emlph=NULL; /* Начало списка */
char fname[]="D_STRUCT.DA1"; /* Файл для хранения списка */
main() {
   char eoj; /* Флаг окончания работы */
  int i; /* Вспомогательная переменная */
/* Структура меню */
struct {
      int op; /* Номер операции */
      (*opf)(); /* Функция обработки */
      } m[9] = {
        {'1',f_print},{'2',f_input},{'3',f_add},
        {'4',f_delete},{'5',f_change},{'6',f_sort},
        {'7',f_save},{'8',f_restore},{'0',}
        };
 int  opcode; /* Код операции */
 for ( ; ; ) {  /* Пока не конец работы */
    clrscr();           /* Очистка экрана */
    printf("1. Print\n");  /* Вывод пунктов меню на экран */
    printf("2. Input\n");
    printf("3. Add\n");
    printf("4. Delete\n");
    printf("5. Change\n");
    printf("6. Sort\n");
    printf("7. Save\n");
    printf("8. Restore\n");
    printf("0. Quit\n");
    printf("Enter operation code > "); /* Запрос на ввод номера
                                          пункта для выполнения */
    opcode=getche(); /* Ввод номера пункта */
    putchar('\n');
    if (opcode!='0') {        /* выход из программы,
                                   если выбран QUIT */
       printf("Press any key...");
       getch();
       exit(0);
       }
    for (i=0; i<10;i++){  /* Запуск соответствующей функции
                             обработки */
      if (opcode==m[i].op) {
         if (m[i].opf()==1) exit(0);
         break;
         }
      }
}
/**************** Функция вывода списка на экран ****************/
f_print() {
emlp *a; /* Указатель на структуру */
int i, j;
        /* Если списка нет в памяти,то вывод соотвтствуюшего
           сообщения */
        /* Иначе - вывод всего списка на экран */
    if (emlph==NULL) printf("List empty\n");
    else {
      for (a=emlph,i=1,j=1; a!=NULL; a=a->next,j++,i++) {
        printf("#%-2d %-10s  %-4d  %-4d\n",
          i,a->name, a->grade,a->hight);
        if (j==20){
          printf("Press any key for continue...\n");
          getch();
          j=1;
          }
        }
      printf("======= end of list ========\n");
    }
 return 0;
}
/*********** Функция ввода элементов списка **********************/
f_input() {
int cc;
  printf("Enter name=* for end of stream\n");
  /* Конец ввода - при вводе '*' вместо имени */
  while (!(cc=f_add())); /* Вызов функции добавления */
  return cc;
}
/************* Добавление элемента в список *********************/

int f_add() {
emlp *a, *b;
char ss[40];
int i=1;
/* Если список существует,осуществляем вставку элемента */
   if (emlph!=NULL)
       for (i++,a=emlph; a->next!=NULL; a=a->next,i++);
   /* Приглашение к вводу */
   printf("Line #%d. Enter: name grade hight >",i);
   scanf("%s",ss);
   if (ss[0]=='*') return 2;
   /* Выделение памяти под новый элемент */
   b=(emlp *)malloc(sizeof(emlp));
   strcpy(b->name,ss);
   scanf("%d %d",&(b->grade),&(b->hight));
   b->next=NULL;
   /* Элемент вставляется после головы списка или в начало,
      если список пустой */
   if (emlph==NULL) emlph=b;
   else a->next=b;
   return 0;
}
/************ Функция сохранения списка на диске *****************/
f_save() {
 FILE *dat;
 emlp *a;
 dat=fopen(fname,"w"); /* Открытие файла на запись */
 /* Запись в файл осуществляется  полями */
 for (a=emlph; a!=NULL; a=a->next)
 fprintf(dat,"%s %d %d\n",a->name,a->grade,a->hight);
 /* В конце файла - спецкод '***' */
 fprintf(dat,"***\n");
 fclose(dat);        /* Закрытие файла */
 return 0;
}
/****** Перезапись списка из файла в динамическую память ********/
f_restore() {
 FILE *dat;
 char ss[40];
 emlp *a, *b;
 /* Открытие файла для чтения,если файл не найден-вывод
    соответствующего сообщения */
 if ((dat=fopen(fname,"r"))==NULL) {
       printf("File not found : %s\n",fname);
       return 1;
         }
     else {
       emlph=NULL;
         do {
         /* Чтение из файла по полям пока не дошли до
            спецкода '* '*/
         fscanf(dat,"%s",ss);
         if (ss[0]!='*') {
         /* Выделение памяти под новый элемент */
         b=(emlp *)malloc(sizeof(emlp));
         if (emlph==NULL) emlph=b;
           else a->next=b;
         strcpy(b->name,ss);
         fscanf(dat,"%d %d\n",&(b->grade),&(b->hight));
         b->next=NULL;
         a=b;
           }
         } while (ss[0]!='*');
         fclose(dat);  /* Закрытие файла */
       }
    return 0;
}
/*************** Функция сортировки списка ***********************/
f_sort() {
 int n;
emlp *a, *b, *c;
/* Если список пустой или в нем один элемент,
   то выход из функции */
 if ((emlph==NULL)||(emlph->next==NULL)) return 0;
 /* Сортировка списка методом "пузырька" */
    for (n=1; n; ) {
         n=0;
       for (a=emlph, b=emlph->next; b!=NULL; )
          if (strcmp(a->name,b->name)>0) {
             a->next=b->next; b->next=a;
             if (a==emlph) emlph=b;
             else c->next=b;
             c=b; b=a->next;
             n=1;
             }
          else {
            c=a; a=b; b=b->next;
            }
       }
      return 0;
}
/************ Ввод номера элемента *******************************/

int get_ln () {
 int ln;
   printf("Enter line number >");
   do {
    /* Ввод номера элемента и проверка его(если он меньше единицы-
       выдается сообщение об ошибке */
     scanf("%d",&ln);
     if (ln<1) {
       printf("Illegial line number. Try again >");
       ln=0;
       }
     } while (!ln);
   return ln;
}
/************* Уничтожение элемента списка ***********************/
f_delete () {
 int ln;
 emlp *a, *b;
    /* Если списка нет в памяти,то вывод соотвтствуюшего
     сообщения */
   if (emlph==NULL) printf("List empty\n");
   /* Иначе-ввод номера элемента с помощью функции GET_LN */
    else {
        ln=get_ln()-1;
        if (!ln) {
        /*  Если номер корректен - переприсваивание указателей
            и освобождение памяти */
        a=emlph; emlph=a->next; free(a);
        }
        else {
        /* Иначе- ??????? */
        for(ln--, a=emlph; ln&&(a!=NULL); a=a->next,ln--);
        if (a!=NULL)
          if ((b=a->next)!=NULL) {
             a->next=b->next; free(b);
             }
          }
      }
  return 0;
}
/********** Изменение значения полей элемента списка *************/
f_change() {
 char ss[40];
 int ln;
 emlp *a;
  ln=get_ln()-1; /* Ввод номера элемента */
  for (a=emlph; ln && a!=NULL; ln--, a=a->next);
  if (ln) return 0;
  /* Вывод старых и ввод новых значений */
  /* Запись новых значений в список */
  printf("Old name  = %s   New name >",a->name);
  gets(ss);
  gets(ss);
  if (*ss) strcpy(a->name,ss);
  printf("Old grade = %d   New grade >",a->grade);
  gets(ss);
  if (*ss) sscanf(ss,"%d",&(a->grade));
  printf("Old hight = %d   New hight >",a->hight);
  gets(ss);
  if (*ss) sscanf(ss,"%d",&(a->hight));
  return 0;
}
Задания для практической работы:
Для заданной прикладной области разработать описание объектов этой области. Разработать процедуры, реализующие базовые операции над этими объектами, в том числе: 
-текстовый ввод-вывод (консольный и файловый); 
-присваивание; 
-задание константных значений; 
-сравнение (не менее 2-х типов). 
Процедуры и описания данных должны составлять отдельный модуль (модуль типа данных). Используя процедуры и описания модуля типа данных, разработать программу, обеспечивающую ввод исходных данных из первого файла данных в память и хранение их в памяти в виде связного списка, сортировку списка по алфавитному и по числовому параметру. 
Для каждой области перечислены параметры объекта. Среди параметров обязательно есть ключевое алфавитное поле (например, фамилия), которое идентифицирует объект, у каждого объекта имеется также одно или несколько числовых полей, по которым вероятны обращения к объекту.
1	Отдел кадров	фамилия сотрудника, имя, отчество, должность, стаж работы, оклад
2	Красная книга	вид животного, род, семейство, место обитания, численность популяции
3	Производство	обозначение изделия, группа к которой оно относится, год выпуска, объем выпуска, расход металла
4	Персональные ЭВМ	фирма-изготовитель, тип процессора, тактовая частота, емкость ОЗУ, емкость жесткого диска
5	Библиотека	автор книги, название, год издания, код УДК, цена, количество в библиотеке
6	Спутники планет	название, название планеты-хозяина, год открытия, диаметр, период обращения
7	Радиодетали	обозначение, тип, номинал, количество на схеме, обозначение возможного заменителя
8	Текстовые редакторы	наименование, фирма-изготовитель, количество окон, количество шрифтов, стоимость
9	Телефонная станция	номер абонента, фамилия, адрес, наличие блокиратора, задолженность
10	Быт студентов	фамилия студента, имя, отчество, факультет, размер стипендии, число членов семьи
11	Спортивные соревнования	фамилия спортсмена, имя, команда, вид спорта, зачетный результат, штрафные очки
12	Соревнование факультетов по успеваемости	факультет,количество студентов, средний балл по факультету, число отличников, число двоечников
13	С/х работы	фамилия студента, имя, отчество, факультет, вид работ, заработок
14	Сельхозработы	наименование с/х предприятия, вид собственности, число работающих, основной вид продукции, прибыль
15	Сведения о семье	фамилия студента, имя, отчество, факультет, специальность отца, специальность матери, количество братьев и сестер
16	Скотоводство	вид животных, количество особей в стаде в возрасте до 1 года, количество особей 1 - 3 лет, свыше 3 лет, смертность в каждой группе, рождаемость
17	Микросхемы памяти	обозначение, разрядность, емкость, время доступа, количество на схеме, стоимость
18	Описание изображения	тип фигуры (квадрат, окружность и т.п.), координаты на плоскости, числовые характеристики (длина строрны, радиус и т.п.).
19	Лесное хозяйство	наименование зеленого массива, площадь, основная порода, средний возраст, плотность деревьев на кв.км
20	Городской транспорт	вид транспорта, номер маршрута, начальная остановка, конечная остановка, время в пути
21	Университет	ФИО и должность преподавателя, названиепредмета, количество часов, тип контроля
22	Оптовая база	название товара, количество на складе, стоимость единицы, название поставщика, срок поставки
23	Сеть магазинов	номер, название, адрес, телефон магазина, ФИО, адрес, капитал владельцев магазина.
24	Авторемонтные мастерские	номер, марка, мощность и цвет автомобиля, ФИО и квалификация механика, тип работ
25	Зоопарк 	вид животного, кличка, возраст, категория редкости, вес, суточный рацион мяса, овощей, молока
26	Договорная деятельность организации	шифр договора, наименование организации, наименование контрагента сроки выполнения, сумма договора, вид договора.
27	Поликлиника	ФИО и дата рождения пациента, ФИО, должность и специализация лечащего врача, диагноз
28	Домоуправление	номер квартиры, общая площадь, полезная площадь, количество комнат, фамилия квартиросъемщика, количество членов семьи, количество детей в семье, есть ли задолженность по квартплате
29	Аэропорт	номер рейса, пункт назначения, день рейса, тип самолета, время вылета, время в пути, является ли маршрут международным, 
30	Шахматы	ФИО спортсмена, дата рождения, страна, спортивный разряд, участвовал ли в борьбе за звание чемпиона мира, рейтинг.

Контрольные вопросы
1.	Что такое структура?
2.	Как задается структурированная переменная?
3.	Что такое список?
4.	Какие операции над списками можно выполнить?
5.	Какой список называется линейным?

Практическая работа № 7-8 
 «Использование структур»
Цель работы: получение практических навыков в использовании структур и динамических объектов в языке С++, создание модульных программ и обеспечение инкапсуляции.
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования.

Краткие теоретические и учебно-методические материалы 
Структуры 
Структура – это объединенное в единое целое множество поименованных элементов данных. Элементы структуры (поля) могут быть различного типа, они все должны иметь различные имена.  
struct Date  	//определение структуры 
{ int day; int month; int year; 
}; 
Date birthday;  	//переменная типа Date 
Для переменных одного и того же структурного типа определена операция присваивания. 
При этом происходит поэлементное копирование.  
Доступ к элементам структур обеспечивается с помощью уточненных имен: 
имя_структуры.имя_элемента 
 
//присваивание значений полям переменной birthday birthday.day=11; birthday.month=3; birthday.year=1993; 
Date Data; 
// присваивание значения переменной birthday переменной Data Data=birthday; 

Из элементов структурного типа можно организовывать массивы также как из элементов стандартных типов. 
 Date mas[15]; //массив структур 
 
//ввод значений массива for(int i=0;i<15;i++) 
{ cout<<”\nEnter day:”;cin>>mas[i].day; cout<<”\nEnter month:”;cin>>mas[i].month; cout<<”\nEnter year:”;cin>>mas[i].year; } 

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

выходной

 

поток

 

буфер

 

буфер

 

входной

 

поток

 

программа

 

 
При работе с потоком можно: 
•	Открывать и закрывать потоки (связывать указатели на поток с конкретными файлами);  вводить и выводить строку, символ, форматированные данные, порцию данных произвольной длины; 
•	анализировать ошибки ввода-вывода и достижения конца файла; 
•	управлять буферизацией потока и размером буфера; 
•	получать и устанавливать указатель текущей позиции в файле. 
Функции библиотеки ввода-вывода находятся в заголовочном файле <stdio.h>. 
Прежде чем начать работать с потоком, его надо инициировать, т. е. открыть. При этом поток связывается со структурой предопределенного типа FILE, определение которой находится в библиотечном файле <stdio.h>. В структуре находится указатель на буфер, указатель на текущую позицию файла и т. п. При открытии потока, возвращается указатель на поток, т. е. на объект типа FILE. 
#include <stdio.h>; 
. . . . . . . .  
FILE *fp; 
. . . . . . . . . . .. fp= fopen( ”t.txt”, ”r”); 
где fopen(<имя_файла>,<режим_открытия>) - функция для инициализации файла. Существуют следующие режимы для открытия файла: 
Режим 	Описание режима открытия файла 	
r 	Файл открывается для чтения, если файл не существует , то выдается ошибка при исполнении программы. 	
w 	Файл открывается для записи, если файл не существует, то он будет создан, если файл уже существует, то вся информация из него стирается. 	
a 	Файл открывается для добавления, если файл не существует, то он будет создан, если существует, то информация из него  не стирается, можно выполнять запись в конец файла 	
r+ 	Файл открывается для чтения и записи, изменить размер файла нельзя, если файл не существует , то выдается ошибка при исполнении программы. 	
w+ 	Файл открывается для чтения и записи, если файл не существует, то он будет создан, если файл уже существует, то вся информация из него стирается. 	
a+ 	Файл открывается для чтения и записи, если файл не существует, то он будет создан, если существует, то информация из него  не стирается, можно выполнять запись в конец файла 	
Поток можно открыть в текстовом (t) или двоичном (b) режиме. По умолчанию используется текстовый режим. В явном виде режим указывается следующим образом:   ”r+b”или ”rb” - двоичный (бинарный) режим; 
•	”r+t” или ”rt” – текстовый режим. 
В файле stdio.h определена константа EOF, которая сообщает об окончании файла (отрицательное целое число). 
При открытии потока могут возникать следующие ошибки: 
•	файл, связанный с потоком не найден (при чтении из файла); 
•	диск заполнен (при записи); 
•	диск защищен от записи (при записи) и т. п. 
В этих случаях указатель на поток приобретет значение NULL (0). Указатель на поток, отличный от аварийного не равен 0. 
Для вывода об ошибке при открытии потока используется стандартная библиотечная функция из файла <stdio.h>  void perror (const char*s); 
 if ((fp=fopen(”t.txt”, ”w”)==NULL) 
{ 
// выводит строку символов с сообщением об ошибке perror(”\nошибка при открытии файла”);  exit(0); 
} 

После работы с файлом, его надо закрыть 
fclose(<указатель_на_поток>); 
Когда программа начинает выполняться, автоматически открываются несколько потоков, из которых основными являются: 
•	стандартный поток ввода (stdin); 
•	стандартный поток вывода (stdout); 
•	стандартный поток вывода об ошибках (stderr). 
По умолчанию stdin ставится в соответствие клавиатура, а потокам stdout и stderr - монитор. Для ввода-вывода с помощью стандартных потоков используются функции: 
•	getchar()/putchar() – ввод-вывод отдельного символа; 
•	gets()/puts() – ввод-вывод строки; 
•	scanf()/printf() – форматированный ввод/вывод. 
Аналогично работе со стандартными потоками выполняется ввод-вывод в потоки, связанные с файлами. 
Для символьного ввода-вывода используются функции: 
•	int fgetc(FILE*fp), где fp – указатель на поток, из которого выполняется считывание. Функция возвращает очередной символ в форме int из потока fp. Если символ не может быть прочитан, то возвращается значение EOF.  
•	int fputc(int c, FILE*fp), где fp – указатель на поток, в который выполняется запись, c – переменная типа int, в которой содержится записываемый в поток символ. Функция возвращает записанный в поток fp символ в форме int . Если символ не может быть записан, то возвращается значение EOF.  
Для построчного ввода-вывода используются следующие функции: 
•	char* fgets(char* s,int n,FILE* f), где char*s – адрес, по которому размещаются считанные байты, int n – количество считанных байтов, FILE* f – указатель на файл, из которого производится считывание. 
Прием байтов заканчивается после передачи n-1 байтов или при получении управляющего символа ‘\n’. Управляющий символ тоже передается в принимающую строку. Строка в любом случае заканчивается ‘\0’. При успешном завершении работы функция возвращает указатель на прочитанную строку, при неуспешном – 0. 
•	int puts(char* s, FILE* f), где char*s – адрес, из которого берутся записываемые в файл байты, FILE* f – указатель на файл, в который производится запись. 
Символ конца строки (‘\0’) в файл не записывается. Функция возвращает EOF, если при записи в файл произошла ошибка, при успешной записи возвращает неотрицательное число. Для блокового ввода-вывода используются функции: 
•	int fread(void*ptr,int size, int n,  FILE*f), где void*ptr – указатель на область памяти, в которой размещаются считанные из файла данные, int size – размер одного считываемого элемента, int n – количество считываемых элементов, FILE*f – указатель на файл, из которого производится считывание. 
В случае успешного считывания функция возвращает количество считанных элементов, иначе – EOF. 
•	int fwrite(void*ptr,int size, int n,  FILE*f), где void*ptr – указатель на область памяти, в которой размещаются считанные из файла данные, int size – размер одного записываемого элемента, int n – количество записываемых элементов, FILE*f – указатель на файл, в который производится запись. 
В случае успешной записи функция возвращает количество записанных элементов, иначе – EOF. 
В некоторых случаях информацию удобно записывать в файл без преобразования, т. е. в символьном виде пригодном для непосредственного отображения на экран. Для этого можно использовать функции форматированного ввода-вывода: 
•	int fprintf(FILE *f, const char*fmt,. . .) , где FILE*f – указатель на файл, в который производится запись, const char*fmt – форматная строка, . . . – список переменных, которые записываются в файл. 
Функция возвращает число записанных символов.  
•	int fscanf(FILE *f, const char*fmt, par1,par2, . . .) , где FILE*f – указатель на файл, из которого производится чтение, const char*fmt – форматная 
строка, par1,par2,. . . – список переменных, в которые заносится информация из файла. 
Функция возвращает число переменных, которым присвоено значение. 
Средства прямого доступа дают возможность перемещать указатель текущей позиции в потоке на нужный байт. Для этого используется функция  
•	int fseek(FILE *f, long off, int org), где FILE *f - – указатель на файл, long off – позиция смещения, int org – начало отсчета. 
Смещение задается выражение или переменной и может быть отрицательным, т. е. 
возможно перемещение как в прямом, так и в обратном направлениях. Начало отсчета задается одной из определенных в файле <stdio.h> констант: 
SEEK_SET ==0 – начало файла; 
SEEK_CUR==1 – текущая позиция; 
SEEK_END ==2 – конец файла. 
Функция возвращает 0, если перемещение в потоке выполнено успешно, иначе возвращает ненулевое значение. 
1.	Используя ввод-вывод в стиле С создать файл и записать в него структурированные данные, оформить в виде функции.  
2.	Написать функцию, которая выводит содержимое созданного файла на экран (синтаксис С). 
3.	Написать функцию, которая позволяет удалить из файла данные (синтаксис С). 
4.	Написать функцию, которая позволяет добавить в файл данные (синтаксис С). 
5.	Выполнить поиск в файле согласно варианту. 
6.	Вывести содержимое измененного файла на экран (синтаксис С). 
7.	Используя ввод-вывод в стиле С++ создать файл и записать в него структурированные данные.  
8.	Вывести созданный файл на экран (синтаксис С++). 
9.	Написать функцию, которая позволяет удалить из файла данные (синтаксис С++). 
10.	Написать функцию, которая позволяет добавить в файл данные (синтаксис С++). 
11.	Вывести содержимое измененного файла на экран (синтаксис С++). 
12.	Выполнить поиск в файле согласно варианту. 
13.	Считать значение строки с клавиатуры. 
14.	Записать строку в файл (синтаксис С), организовать с помощью функций. 
15.	Обработать строку согласно варианту, Для обработки строк использовать стандартные функции из библиотечного файла <string.h>. 
16.	Вывести измененную строку на экран, организовать с помощью функций 
17.	Записать измененную строку в файл (синтаксис С).  
18.	Выполнить поиск в файле согласно варианту. 
19.	Вывести содержимое измененного файла на экран (синтаксис С). 
Используя ввод-вывод в стиле С++ создать файл и записать в него структурированные данные.
20.	Вывести созданный файл на экран (синтаксис С++). 

Контрольные вопросы
1.Зачем нужны структуры?
2. Объявление и определение структур
3. Доступ к членам структур
4.Инициализация структур
5. C++11/14: Инициализация нестатических членов структур

Практическая работа № 9-10
«Вывод информации на экран в графическом режиме»
Цель работы: целью данной парктической работы является знакомство с основными
принципами отображения графической информации на экране дисплея.
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования.

Краткие теоретические и учебно-методические материалы 
Для хорошего оформления диалога пользователя с компьютером (программой) необходима развитая система функций управления работой экрана. Пакет функций управления экраном делится на две части. Первая поддерживает текстовый режим (text mode) работы. В текстовом режиме экран монитора условно разбивается на отдельные участки, чаще всего на 25 строк по 80 символов (знакомест). В каждое знакоместо может быть выведен один из 256 заранее заданных символов. Вторая часть обеспечивает работу экрана в графическом режиме (graphics mode). Он предназначен для вывода на экран графиков, диаграмм, рисунков и т.д. В этом режиме экран монитора представляет собой множество точек, каждая из которых может быть темной или светлой на монохромных мониторах или одного из нескольких цветов – на цветных. Количество точек по горизонтали и вертикали называется разрешающей способностью монитора в данном режиме.
      Рассмотрим графический режим работы экрана, который поддерживает
пакет прикладных программ Borland C++ (версия 3.1). Предполагается, что на
компьютере      установлен    один   из   графических   адаптеров    и    монитор,
соответствующий этому адаптеру. 
                              Инициализация графики
      В состав графического пакета входят:
       • заголовочный файл graphics.h,
       • библиотечный файл graphics.lib,
       • драйверы графических устройств (*.bgi),
       • шрифты (*.chr).
      Управление экраном в графическом режиме производится с помощью набора функций, прототипы которых находятся в заголовочном файле graphics.h.
Для работы в графическом режиме файл graphics.h должен быть подключен с помощью директивы #include препроцессора языка Cи ко всем модулям, использующим графические подпрограммы
      #include <graphics.h>
      Прежде чем использовать графические функции, необходимо переключить видеоадаптер в графический режим (по умолчанию он находится в текстовом режиме). Для инициализации графики предназначена функция initgraph().
Ее прототип –
      void initgraph(int *driver, int *mode, char *path);
где int *driver – тип подключаемого драйвера, int *mode – режим работы под-
ключенного драйвера, char *path – местоположение драйвера.
      Функция initgraph() считывает в память указанный драйвер, устанавливает видеорежим, соответствующий аргументу mode, и определяет маршрут к директории, в которой находится файл *.bgi (драйвер). Если маршрут не указан, то предполагается, что этот файл расположен в текущей директории.
      При использовании initgraph() можно указать или конкретный драйвер (например, egavga.bgi), или задать автоматическое определение (детектирование) типа видеоадаптера и выбора соответствующего драйвера уже во время выполнения.
Задания на практическую работу

                                        Вариант 1
       Напечатать первые N строк «треугольника Паскаля» в следующем виде:
                                         1
                                        1 1
                                       1 2 1
                                     1 3 3 1
                                    1 4 6 4 1
                                         …
В этом треугольнике крайние числа равны 1, а каждое внутреннее – сумме двух чисел над ним.
                                        Вариант 2
       Нарисовать график и напечатать таблицу значений функции y = sin(x) на участке [0, 3] с шагом 0.1. Значение x печатать с одной цифрой в дробной части, значения y – с пятью.
                                        Вариант 3
       Нарисовать график и напечатать таблицу значений функции y = tg(x) на участке [0, 3] с шагом 0.1. Значение x печатать с одной цифрой в дробной части, значения y – в экспоненциальной форме.

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

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

                                    Вариант 8
       Разработать простейший графический редактор, позволяющий рисовать на экране произвольный прямоугольник с возможностью выбора цвета и толщины линии. Редактор оформить в виде функции.
                                    Вариант 9
       Осуществить движение графического объекта типа «машинки» слева направо по экрану. Колеса «машинки» должны вращаться. Для отображения «машинки» использовать функцию.
                                    Вариант 10
       Вывести на экран правильный N-угольник. Число вершин N, цвет и толщина линии задаются пользователем в диалоговом режиме.
Контрольные вопросы

       1.     Как перейти из текстового режима в графический?
       2.     Какие заголовочные файлы необходимо подключить к программе
              для работы с графикой?
       3.     Какая функция используется для «закрытия» графического режима?
       4.     Для чего используется макрос DETECT?
       5.     С какой части экрана начинается отсчет координат?

Практическая работа № 11-12 
 «Представление в памяти массивов и матриц»
Цель работы: получение практических навыков в использовании массивов и динамических объектов в языке С++, создание модульных программ и обеспечение инкапсуляции.
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования.

Краткие теоретические и учебно-методические материалы 
Переменные с двумя и более индексами служат для описания многомерных массивов. Двумерный массив состоит из элементов, образующих прямоугольную таблицу. В этом случае первый индекс обозначает номер строки, а второй индекс – номер столбца таблицы, на пересечении которых и расположен данный элемент. 
Экономное использование памяти предусматривает, что для тех элементов матрицы, в которых наверняка содержатся нули, память выделяться не будет. Поскольку при этом нарушается двумерная структура матрицы, она может быть представлена в памяти как одномерный массив, но при обращении к элементам матрицы пользователь имеет возможность обращаться к элементу по двум индексам. 
Программное изделие должно быть отдельным модулем, в котором должны размещаться как данные (матрица и вспомогательная информация), так и функции, которые обеспечивают доступ. Внешний доступ к программам и данным модуля возможен только через вызов функций чтения и записи элементов матрицы. Доступные извне элементы программного модуля должны быть описаны в отдельном файле, который может включаться в программу пользователя оператором препроцессора.
Преобразование 2-компонентного адреса элемента матрицы, которую задает пользователь, в 1-компонентную должно выполняться отдельной функцией (так называемой, функцией линеаризации), вызов которой возможен только из функций модуля. Возможны три метода преобразования адреса: при создании матрицы для нее создается также и дескриптор D[N] - отдельный массив, каждый элемент которого соответствует одной строке матрицы; дескриптор заполняется значениями, подобранными так, чтобы: n = D[x] + y, где x, y - координаты пользователя (строка, столбец), n - линейная координата; линейная координата подсчитывается методом итерации як сумма полезных длин всех строк, предшествующих строке x, и к ней прибавляется смещение y-го полезного элемента относительно начала строки; для преобразования подбирается единое арифметическое выражение, которой реализует функцию: n = f(x,y). 
Первый вариант обеспечивает быстрейший доступ к элементу матрицы, ибо требует наименьших расчетов при каждом доступе, но плата за это - дополнительные затраты памяти на дескриптор. Второй вариант - наихудший по всем показателям, ибо каждый доступ требует выполнения оператора цикла, а это и медленно, и занимает память. Третий вариант может быть компромиссом, он не требует дополнительной памяти и работает быстрее, чем второй. Но выражение для линеаризации тут будет сложнее, чем первом варианте, следовательно, и вычисляться будет медленнее. 

Пример: написать программу, где матрица содержит нули ниже главной диагонали; индексация начинается с 0. 
Введем обозначения переменных:
int NN - размерность матрицы; 
int SIZE - количество ненулевых элементов в матрице; 
int *m_addr - адрес сжатой матрицы в памяти, начальное значение этой переменной - NULL - признак того, что память не выделена; 
int L2_RESULT - общий флаг ошибки, если после выполнения любой функции он равен -1, то произошла ошибка. 
Переменные SIZE и m_addr описаны вне функций с квалификатором static, это означает, что вони доступны для всех функций в этом модуле, но недоступны для внешних модулей. Переменная L2_RESULT также описана вне всех функций, не без явного квалификатора. Эта переменная доступна не только для этого модуля, но и для всех внешних модулей, если она в них буде описана с квалификатором extern. 
Функция creat_matr предназначена для выделения в динамической памяти места для размещения сжатой матрицы. Прототип функции: 
            int creat_matr ( int N ).
Функция сохраняет значение параметра в собственной статической переменной и подсчитывает необходимый размер памяти для размещения ненулевых элементов матрицы. Для выделения памяти используется библиотечная функция C malloc. Функция возвращает -1, если при выделении произошла ошибка, или 0, если выделение прошло нормально. При этом переменной L2_RESULT также присваивается значение 0 или -1. 
Функция close_matr предназначена для освобождения памяти при завершении работы с матрицей, Прототип функции: 
            int close_matr ( void );
Функция возвращает 0 при успешном освобождении, -1 - при попытке освободить невыделенную память. Если адрес матрицы в памяти имеет значения NULL, это признак того, что память не выделялась, тогда функция возвращает -1, иначе - освобождает память при помощи библиотечной функции free и записывает адрес матрицы - NULL. Соответственно функция также устанавливает глобальный признак ошибки - L2_RESULT. 
Функция read_matr предназначена для чтения элемента матрицы. Прототип функции:  int read_matr(int x, int y); где x и y - координаты (строка и столбец). Функция возвращает значение соответствующего элемента матрицы. Если после выполнения функции значение переменной L2_RESULT -1, то это указывает на ошибку при обращении. Проверка корректности задания координат выполняется обращением к функции ch_coord, если эта последняя возвращает ненулевое значение, выполнение read_matr на этом и заканчивается. Если же координаты заданы верно, то проверяется попадание заданного элемента в нулевой или ненулевой участок. Элемент находится в нулевом участке, если для него номер строки больше, чем номер столбца. Если элемент в нулевом участке, функция просто возвращает 0, иначе - вызывает функцию линеаризации lin и использует значение, которое возвращает lin, как индекс в массиве m_addr, по которому и выбирает то значения, которое возвращается. 
Функция write_matr предназначена для записи элемента в матрицу. Прототип функции: int write_matr(int x, int y, int value); где x и y - координаты (строка и столбец), value - то значение, которое нужно записать. Функция возвращает значение параметра value, или 0 - если была попытка записи в нулевой участок. Если после выполнения функции значение переменной L2_RESULT -1, то это указывает на ошибку при обращении. 
Выполнение функции подобно функции read_matr с тем отличием, что, если координаты указывают на ненулевой участок, то функция записывает value в массив m_addr. 
Функция ch_coord предназначена для проверки корректности задания координат. Эта функция описана как static и поэтому может вызываться только из этого же модуля. Прототип функции static char ch_coord(int x, int y); где x и y - координаты (строка и столбец). Функция возвращает 0, если координаты верные, -1 - если неверные. Соответственно, функция также устанавливает значение глобальной переменной L2_RESULT. 
Выполнение функции собственно состоит из проверки трех условий: адрес матрицы не должен быть NULL, т.е., матрица должна уже находиться в памяти; ни одна из координат не может быть меньше 0; ни одна из координат не может быть больше NN. Если хотя бы одно из этих условий не выполняется, функция устанавливает признак ошибки. 
Функция lin предназначена для преобразования двумерных координат в индекс в одномерном массиве. Эта функция описана как static и поэтому может вызываться только из этого же модуля. Прототип функции: static int lin(int x, int y); где x и y - координаты (строка и столбец). Функция возвращает координату в массиве m_addr. 
Выражение, значение которого вычисляет и возвращает функция, подобрано вот из каких соображений. Пусть мы имеет такую матрицу, как показано ниже, и нам нужно найти линейную координату элемента, обозначенного буквой A с координатами (x,y): 
     x x x x x x
     0 x x x x x
     0 0 x x A x
     0 0 0 x x x
     0 0 0 0 x x
     0 0 0 0 0 x
Координату элемента можно определить как: 
n = SIZE - sizeX +offY,
где SIZE - общее количество элементов в матрице (см. creat_matr), 
SIZE = NN * (NN - 1) / 2 + NN;
sizeX - количество ненулевых элементов, которые содержатся в строке x и ниже, 
sizeX = (NN - x) * (NN - x - 1) / 2 + (NN - x);
offY - смещение нужного элемента от начала строки x, offY = y - x.
При использовании массива как параметра функции, в функцию передается указатель на его первый элемент, т. е. массив всегда передается по адресу. При этом теряется информация о количестве элементов в массиве, поэтому размерность массива следует передавать как отдельный параметр. 
void print(int a[100],int n) //вывод массива на печать 
{ 
for(int i=0;i<n;i++) 
cout<<a[i]<<" "; 
cout<<"\n"; 

Пример программы

Задания для практической работы:
1.	Произвести следующую обработку 15 целых чисел: найти количество
отрицательных чисел, количество нулевых и подсчитать сумму положительных
чисел.
2. Произвести следующую обработку 15 целых чисел: найти количество
четных чисел, а нечетные числа, входящие в диапазон [1..11] возвести в квадрат.
3. Произвести следующую обработку 15 вещественных чисел: найти
количество отрицательных чисел, а числа, входящие в диапазон [0..10] возвести в
квадрат.
4. Произвести следующую обработку 10 вещественных чисел: найти
количество чисел, больших или равных 1,5, и подсчитать сумму отрицательных
чисел.
5. Произвести следующую обработку 10 вещественных чисел: найти
количество чисел, равных нулю, и найти сумму чисел, входящих в диапазон [-
15..15].
6. Произвести следующую обработку 15 целых чисел: найти количество
отрицательных чисел и подсчитать разность положительных чисел.
7. Произвести следующую обработку 15 вещественных чисел: найти среднее
арифметическое положительных чисел и подсчитать количество чисел, входящих в
диапазон [-15..5].
8. Произвести следующую обработку 10 целых чисел: найти количество
отрицательных чисел и подсчитать сумму положительных чисел, делящихся без
остатка на 3.
9. Произвести следующую обработку 10 целых чисел: найти количество
отрицательных чисел, а числа, входящие в диапазон [0..10], умножить на 10.
10. Произвести следующую обработку 10 целых чисел: найти количество
отрицательных чисел, а числа, входящие в диапазон [0..10], умножить на 3.
31
11. Произвести следующую обработку 15 вещественных чисел: найти среднее
арифметическое отрицательных чисел и подсчитать количество чисел, входящих в
диапазон [0..5].
12. Произвести следующую обработку 15 вещественных чисел: найти среднее
арифметическое нечетных чисел и подсчитать сумму чисел, входящих в диапазон [-
15..5].
13. Произвести следующую обработку 10 вещественных чисел: найти
количество чисел, равных нулю, и найти синус чисел, входящих в диапазон [-15..15].
14. Произвести следующую обработку 10 целых чисел: подсчитать сумму
положительных чисел и определить номера отрицательных чисел.
15. Произвести следующую обработку 15 вещественных чисел: найти
количество отрицательных чисел и номера нулевых чисел.
16. Произвести следующую обработку 12 целых чисел: подсчитать количество
чисел, делящихся без остатка на 5, и сумму чисел, входящих в диапазон [-5..5].
17. Произвести следующую обработку 10 вещественных чисел: подсчитать
количество чисел, отличающихся от числа 3 не более чем на 0.5, и сумму
отрицательных чисел.
18. Произвести следующую обработку 15 целых чисел: подсчитать количество
нулевых чисел и вычислить квадраты чисел, входящих в диапазон [-5..5].
19. Произвести следующую обработку 12 целых чисел: подсчитать количество
нечетных чисел и сумму отрицательных чисел.
20. Произвести следующую обработку 15 вещественных чисел: подсчитать
количество чисел, отличающихся от заданного не более чем на 0.5, и сумму
положительных чисел.
21. Произвести следующую обработку 10 вещественных чисел: найти
количество отрицательных чисел, находящихся в диапазоне от -5 до 5 и подсчитать
сумму положительных чисел.
22. Произвести следующую обработку 15 целых чисел: найти количество
чисел, входящих в диапазон [1..11] и каждое число возвести в квадрат.
23. Произвести следующую обработку 15 вещественных чисел: найти
количество чисел, равных 0, а числа, входящие в диапазон [-1..1] возвести в куб.
24. Произвести следующую обработку 10 вещественных чисел: найти
количество чисел, больших или равных 1,5, и подсчитать сумму отрицательных
чисел, входящих в диапазон [-1..0].
25. Произвести следующую обработку 10 вещественных чисел: найти
количество чисел
Для разряженной матрицы целых чисел в соответствии с индивидуальным заданием создать модуль доступа к ней, в котором обеспечить экономию памяти при размещении данных: 
26	все нулевые элементы размещены в левой части матрицы
27 	все нулевые элементы размещены в правой части матрицы
28 	все нулевые элементы размещены выше главной диагонали
29 	все нулевые элементы размещены в верхней части матрицы
30	все нулевые элементы размещены в нижней части матрицы

Контрольные вопросы
1. Для чего предназначена функция lin?
2. Какова максимальная размерность массива? Привести пример.
3. Для чего необходим указатель на массив?
4. Что такое разыменовывание указателя?
5. За что отвечает класс static?

Практическая работа №13-14
«Представление в памяти многомерных массивов и матриц».
Цель работы: получение практических навыков в работе с двумерными массивами.
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Двумерные и многомерные массивы создаются указанием количеств каждого
измерения в квадратных скобках.
Пример:
int a[3][5]; // двумерный массив из 3 строк и 5 столбцов.
const m=10; n=20;
float mass[ n][m ]; // двумерный массив из 10 строк и 20 столбцов.
В памяти многомерные массивы располагаются в последовательных ячейках
построчно. Доступ к элементу многомерного массива происходит с указанием всех его
индексов, например, mass[i][j] – элемент двумерного массива, стоящий в i-й строке и в
j-м столбце.
Пример: Заполнить двумерный массив случайными числами и найти в каждой его
строке сумму положительных элементов.
#include<stdlib.h>
#include<time.h>
#include<iostream.h>
void main( ){
const n = 8, m = 3;
int a[n][m];
for (int i = 0; i < n; i ++)
{ for (int j = 0;j < n; j ++)
{
a[i][j] = random(10)-5;
}
}
int S[n]; //массив сумм
for(int i= 0; i < n ; i ++)
{
S[i]=0;
for (int j = 0; j < n; j ++)
{
if (a[i][j] > 0) S[i] + = a[i] [j]; //накопление сумм
}
cout << ” S[ ” << i << ” ]= ” << S [i] ;
}
}
164
Варианты заданий
Вариант 1. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти среднее геометрическое положительных элементов матрицы.
Вариант 2. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры матрицу на экран в виде таблицы. Найти среднее геометрическое отрицательных элементов матрицы.
Вариант 3. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти произведение элементов, по модулю меньших 1 матрицы.
Вариант 4. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти произведение элементов по модулю больших 1 матрицы.
Вариант 5. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти количество положительных элементов матрицы.
Вариант 6. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти количество нулевых элементов матрицы.
Вариант 7. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти среднее арифметическое положительных элементов матрицы.
Вариант 8. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти сумму положительных элементов матрицы.
Вариант 9. Дан двумерный массив A(n×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти среднее арифметическое положительных элементов матрицы.
Вариант 10. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти среднее арифметическое отрицательных элементов матрицы.
Вариант 11. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти сумму отрицательных элементов матрицы.
Вариант 12. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти сумму и количество положительных элементов матрицы.
Вариант 13. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти количество элементов больших 5 матрицы.
Вариант 14. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти номер последнего положительного элемента матрицы.
Вариант 15. Дан двумерный массив A(n ×m). Ввести данные с клавиатуры. Вывести матрицу на экран в виде таблицы. Найти произведение и количество 165 положительных элементов матрицы.
Вариант 16. Дан двумерный массив A(n ×m). Элементы матрицы сгенерировать случайными числами. Найти сумму положительных элементов в 1-ой строке матрицы.
Вариант 17. Дан двумерный массив A(n×m). Элементы матрицы сгенерировать случайными числами. Найти среднее арифметическое положительных элементов в 5-ой строке матрицы.
Вариант 18. Дан двумерный массив A(n ×m). Элементы матрицы сгенерировать случайными числами. Найти среднее арифметическое отрицательных элементов в последней строке матрицы.
Вариант 19 Дан двумерный массив A(n ×m). Элементы матрицы сгенерировать случайными числами. Найти сумму отрицательных элементов в 1-ом столбце матрицы.
Вариант 20. Дан двумерный массив A(n ×m). Элементы матрицы сгенерировать случайными числами. Найти сумму и количество положительных элементов в 3-ем
столбце матрицы.
Контрольные вопросы
1. Что такое двумерный массив?
2. Сколько индексов требуется для обращения к элементу двумерного массива и что определяет каждый из индексов?
3.Как хранятся в памяти ЭВМ двумерные массивы?
4.Какими способами может быть описан двумерный массив?
5.Как производится ввод элементов двумерного массива?
6.Как производится вывод элементов двумерного массива?

Практическая работа № 15-16 
 «Получение навыков в определении конфигурации основных характеристик ЭВМ»
Цель работы: получение практических навыков в определении конфигурации основных характеристик ЭВМ.
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
В программе, предназначенной для тиражирования, очень важной характеристикой является переносимость. Поскольку ПЭВМ разных пользователей могут иметь существенные отличия в конфигурации оборудования, важно, чтобы программа при ее запуске могла "произвести рекогносцировку" - проанализировать состав оборудования и выбрать ту ветвь своего алгоритма, которая обеспечивает функционирование на данном составе, или выдать отказ, если конфигурация не соответствует спецификациям.
К действиям по рекогносцировке относятся: 
•	определение типа ПЭВМ; 
•	определение конфигурации подключенных внешних устройств; 
•	определение объема ОЗУ; 
•	определение версии DOS (хотя последняя проверка и не связана с оборудованием, логически ее место здесь). 
Состав оборудования определяется положением переключателей на специальных колодках. Состояние этих переключателей может быть прочитано из программы обращением к порту 0x60. Но необходимости в этом нет - эту операцию выполняет BIOS при инициализации системы. На основании этой информации BIOS формирует так называемый список оборудования - 2-байтное слово по адресу 0040:0010. Прочитать это слово можно либо обратившись по указанному адресу, либо обратившись к BIOS через прерывание 0x11.
Пример:   	
Программа использует, так называемый, список оборудования - 2-байтное слово в области данных BIOS по адресу 0040:0010. Назначение разрядов списка оборудования такое: 
Биты	Содержимое
0	установлен в 1, если есть НГМД (см. разряды 6, 7) 
1	установлен в 1, если есть сопроцессор
2,3	число 16-Кбайтних блоков ОЗУ на системной плате
4,5	код видеоадаптера: 11 - MDA, 10 - CGA, 80 колонок,
01 - CGA, 40 колонок, 00 - другой
6,7	число НГМД-1 (если в разряде 0 единица)
8	0, если есть канал ПДП 
9,10,11	число последовательных портов RS-232
12	1, если есть джойстик 
13	1, если есть последовательный принтер
14,15	число параллельных принтеров
Программа состоит только из основной функции main(). Выделения фрагментов программы в отдельные процедуры не требуется, потому что нет таких операций, которые во время работы программы выполняются многократно. 
Переменные, применяемые в программе: 
type_PC - байт типа компьютера, записанный в ПЗУ BIOS по адресу FF00:0FFE; 
a, b - переменные для определения объема extended-памяти ПЭВМ, a - младший байт, b - старший байт; 
konf_b - 2-байтное слово из области данных BIOS, которое содержит список оборудования; 
type - массив символьных строк, представляющих типы компьютера; 
typ1A - массив байт, содержащий коды типов дисплеев; 
types1A[] - массив строк, содержащий названия типов дисплеев; 
j - вспомогательная переменная, которая используется для идентификации типа дисплея; 
seg - сегмент, в котором размещено дополнительное ПЗУ; 
mark - маркер ПЗУ; 
bufVGA[64] - буфер данных VGA, из которого (при наличии VGA) ми выбираем объем видеопамяти; 
rr и sr - переменные, которые используются для задания значения регистров общего назначения и сегментных регистров, соответственно, при вызове прерывания. 
Алгоритм основной программы может быть разбито на 5 частей. 
Часть 1 предназначена для определения типа компьютера. Для этого прочитаем байт, записанный в ПЗУ BIOS по адресу FF00:0FFE. В зависимости от значения этого байта сделаем вывод о типе ПЭВМ. Так, например, компьютеру типа AT соответствует код 0xFC. 
Часть 2 предназначена для определения конфигурации ПЭВМ. Для этого прочитаем из области данных BIOS список оборудования. Для определения количества дисководов (если бит 0 установлен в 1) необходимо выделить биты 6 и 7 (маска 00C0h) и сместить их вправо на 6 разрядов, а потом добавить 1. Для определения количества 16-Кбайтних блоков ОЗУ на системной плате необходимо выделить биты 2 и 3 с помощью маски 000Ch, сместить вправо на 2 разряды и добавить 1. Для определения количества последовательных портов RS-232 выделить с помощью маски 0Eh биты 9-11 и сместить вправо на 9 разрядов. Для определения наличия математического сопроцессора - проверить установку бита 1 маской 0002h. Для определения наличия джойстика - бита 12 с помощью маски 1000h. Определить количество параллельных принтеров можно, выделив биты 14 и 15 маской C000h и сместив их вправо на 14 разрядов. Поскольку список оборудования содержит недостаточно информации про дисплейный адаптер, то для уточнения типа адаптера выполним дополнительные действия. Видеоадаптер обслуживается прерыванием BIOS 10h. Для новых типов адаптеров список его функций расширяется. Эти новые функции и используются для определения типу адаптера. Функция 1Ah доступна только при наличии расширения BIOS, ориентированного на обслуживание VGA. В этом случае функция возвращает в регистре AL код 1Ah - свою "визитную карточку", а в BL - код активного видеоадаптера. В случае, если функция 1Ah поддерживается, обратимся еще к функции 1Bh - последняя заполняет 70-байтний блок информации про состояние, из которого мы выбираемо объем видеопамяти. 
Если 1Ah не поддерживается, это означает, что VGA у нас нет, в этом случае можно обратиться к функции 12h - получение информации про EGA. При наличии расширения, ориентированного на EGA, эта функция изменяет содержимое BL (перед обращением он должен быть 10h) на 0 (цветной режим) или на 1 (монохромный режим) а в BH возвращает объем видеопамяти. Если же ни 1Ah, ни 12 не поддерживаются, то список оборудования BIOS содержит достаточную информацию про видеоадаптер и, выделивши биты 4, 5 мы можем сделать окончательный вывод про тип адаптера, который у нас есть. 
В третьей части программы определим объем оперативной памяти, наличие и объем extended-памяти. Объем оперативной памяти для AT может быть прочитан из регистров 15h (младший байт) и 16h (старший байт) CMOS-памяти или из области памяти BIOS по адресу 0040:0013 (2-байтное слово). Кроме того, в ПЭВМ может быть еще и дополнительная (expanded) память свыше 1 Мбайту. Ее объем можно получит из регистров 17h (младший байт) и 18h (старший байт) CMOS-памяти. Для чтения регистра CMOS-памяти необходимо видать в порт 70h байт номера регистра, а потом из порта 71h прочитать байт содержимого этого регистра. 
В следующей части программы определим наличие и объем дополнительных ПЗУ. В адресному пространстве от C000:0000 по F600:0000 размещаются расширения ПЗУ (эта память не обязательно присутствует в ПЭВМ). Для определения наличия дополнительного ПЗУ будем читать первое слово из каждых 2 Кбайт, начиная с адреса C000:0000 в поисках маркера расширения ПЗУ: 55AAh. Если такой маркер найден, то следующий байт содержит длину модуля ПЗУ. 
В заключительной части программы определим версию DOS, установленную на ПЭВМ. Для этого воспользуемся функцией DOS 30h, которая возвращает в регистре AL старшее число номера версии, а в регистре AH - младшее число. 
/* Подключение стандартных заголовков */
#include <dos.h>
#include <conio.h>
#include <stdio.h>
/*--------------------------------------------------------*/
void main()
{
 unsigned char type_PC, /* Тип компьютера              */
               a,b;     /* Переменные для определения  */
                        /* характеристик памяти ПЭВМ   */
 unsigned int  konf_b;  /* Байт конфигурации из BIOS   */
 char *type[]={"AT","PCjr","XT","IBM PC","unknown"};
 unsigned char typ1A[]={0,1,2,4,5,6,7,8,10,11,12,0xff};
 char *types1A[]={"нема дисплею","MDA, моно","CGA, цв.",
                  "EGA, цв.","EGA, моно","PGA, цв.",
                  "VGA, моно, анал.","VGA, кол., анал.",
                  "MCGA, кол., цифр.","MCGA, моно, анал."
                  "MCGA, кол., анал.","неизвестный тип",
                  "непредусмотренный код"};
 unsigned int j;       /* Вспомогательная переменная */
 unsigned int seg;     /* Сегмент ПЗУ                */
 unsigned int mark=0xAA55;   /* Маркер ПЗУ           */
 unsigned char bufVGA[64];       /* Буфер данных VGA */
 union REGS rr;
 struct SREGS sr;

 textbackground(0);
 clrscr();
 textattr(0x0a);
 cprintf("Практическая работа N5");
 cprintf("\nПроверка состава оборудования");

 /* Определения типа компьютера */
 type_PC=peekb(0xF000,0xFFFE);
 if( (type_PC-=0xFC)>4)
        type_PC=4;
 textattr(0x0b);
 cprintf("\nТип компьютера: ");
 textattr(0x0f);
 cprintf("%s\n\r",type[type_PC]);

 /* Конфигурация*/
 konf_b=peek(0x40,0x10); /* Чтение байта оборудования */
                         /* из памяти BIOS            */
 textattr(0x0b);
 cprintf("Конфигурация:\n\r");

 /* Количество дисководов */
 textattr(0x0e);
 cprintf("   Дисководов ГМД:           ");
 textattr(0x0f);
 if(konf_b&0x0001)
        cprintf("%d\n\r",((konf_b&0x00C0)>>6)+1);
 else
   cprintf("нет\n\r");
 textattr(0x0e);
 cprintf("   Математич. сопроцессор:   ");
 textattr(0x0f);
 if(konf_b&0x0002)
   cprintf("есть\n\r");
 else
   cprintf("нет\n\r");
 textattr(0x0e);
 cprintf("   Тип дисплейного адаптера: ");
 textattr(0x0f);

 /* Определение активного адаптера */
 /* Предположим наличие VGA */
 rr.h.ah=0x1a;
 rr.h.al=0;
 int86(0x10,&rr,&rr);
 if(rr.h.al==0x1a)  /* Поддерживается функция 1Ah */
 {                  /* прерывания 10h             */
  for(j=0;j<12;j++)
   if(rr.h.bl==typ1A[j])
    break;
  cprintf("%s",types1A[j]);
  if(j>0 && j<12)
  {
   rr.h.ah=0x1b;
   rr.x.bx=0;
   sr.es=FP_SEG(bufVGA);
   rr.x.di=FP_OFF(bufVGA);
   int86x(0x10,&rr,&rr,&sr);
   cprintf(", %d Кбайт\n\r",((int)bufVGA[49]+1)*64);
  }
  else
        cprintf("\n\r");
 }
 else
 {
  /* Предположим наличие EGA */
  rr.h.ah=0x12;
  rr.h.bl=0x10;
  int86(0x10,&rr,&rr);
  if(rr.h.bl!=0x10) /* Поддерживается функция 12h */
  {                 /* прерывания 10h */
   cprintf("EGA");
   if(rr.h.bh)
    cprintf(" моно");
   else
    cprintf(" кол.");
   cprintf(", %d Кбайт\n\r",((int)rr.h.bl+1)*64);
  }
  else
  {
   /* CGA или MDA */
   switch(konf_b&0x0030)
   {
    case 0:    cprintf("EGA/VGA\n\r");break;
    case 0x10: cprintf("CGA,40\n\r");break;
    case 0x20: cprintf("CGA,80\n\r");break;
    case 0x30: cprintf("MDA");break;
   }
  }
 }

 /* Блоки ОЗУ на системной плате */
 textattr(0x0e);
 cprintf("\n\r   Первичный блок памяти:    ");
 textattr(0x0f);
 switch (konf_b&0x000C)
 {
  case 0:cprintf("16 Кбайт\n\r");break;
  case 4:cprintf("32 Кбайт\n\r");break;
  case 8:cprintf("48 Кбайт\n\r");break;
  case 12:cprintf("64 Кбайт или больше\n\r");break;
 }

 /* Количество последовательных портов RS-232 */
 textattr(0x0e);

 cprintf("   Портов RS232:             ");
 textattr(0x0f);
 cprintf("%d\n\r",(konf_b&0x0E00)>>9);

 /* Наличие джойстика */
 textattr(0x0e);
 cprintf("   Джойстик:                 ");
 textattr(0x0f);
 if(konf_b&0x1000 )
  cprintf("есть\n\r");
 else
  cprintf("нет\n\r");

 /* Количество параллельних принтеров */
 textattr(0x0e);
 cprintf("   Принтеров:                ");
 textattr(0x0f);
 cprintf("%d\n\n\r",(konf_b&0xC000)>>14);

 /* Объем оперативной памяти */

 textattr(0x0e);
 cprintf("Объем оперативной памяти: ");
 textattr(0x0f);
 cprintf("%d Кбайт\n\r",peek(0x40,0x13));
 textattr(0x0e);

 /* Наличие и объем extended-памяти */
 outportb(0x70,0x17);
 a=inport(0x71);
 outportb(0x70,0x18);
 b=inport(0x71);
 cprintf("Объем extended-памяти:    ");
 textattr(0x0f);
 cprintf("%d  Кбайт\n\n\r",(b<<8)|a);

 /* Наличие дополнительных ПЗУ */
 for( seg=0xC000;seg<0xFFB0;seg+=0x40)
 /* Просмотр памяти от C000:0 с шагом 2 К */
  if(peek(seg,0)==mark)   /* Маркер найден */
  {
   textattr(0x0a);
   cprintf("Адрес ПЗУ =");
   textattr(0x0f);
   cprintf(" %04x",seg);
   textattr(0x0a);
   cprintf(". Длина модуля = ");
   textattr(0x0f);
   cprintf("%d",512*peekb(seg,2));
   textattr(0x0a);
   cprintf(" байт\n\r",peekb(seg,2));
  }

 /* Определение версии операцийной системы */
 rr.h.ah=0x30;
 intdos(&rr,&rr);
 textattr(0x0c);
 cprintf("\n\rВерсия MS-DOS ");
 textattr(0x0f);
 cprintf("%d.%d\n\r",rr.h.al,rr.h.ah);
 textattr(0x0a);
 gotoxy(30,24);
 cprintf("Нажмите любую клавишу");
 textattr(0x07);
 getch();
 clrscr();
}
В процессе работы программы на экран была выведена такая информация: 
Тип компьютера: AT
Конфигурация:
Дисководов ГМД:           2
Математич. сопроцессор:   есть
Тип дисплейного адаптера: VGA, кол., анал., 256 Кбайт
Первичный блок памяти:    16 Кбайт
Портов RS232:             2
Джойстик:                 нет
Принтеров:                1
Объем оперативной памяти: 639 Кбайт
Объем extended-памяти:    384  Кбайт
Адрес ПЗУ = c000. Длина модуля = 24576 байт
Версия MS-DOS 6.20
Задания для практической работы:
Для компьютера на своем рабочем месте определить: 
тип компьютера; 
конфигурацию оборудования; 
объем оперативной памяти; 
наличие и объем расширенной памяти; 
наличие дополнительных ПЗУ; 
версию операционной системы. 

Контрольные вопросы:
1.	Что такое рекогносцировка?
2.	Какие действия относят к рекогносцировке?
3.	В чем отличия классов ЭВМ XT и AT?
4.	Что входит в список оборудования?
5.	Как определить объем ОЗУ?

Практическая работа № 17-18 
 «Изучение функций системного таймера»
Цель работы: изучение функций системного таймера и закрепление практических навыков работы с ним. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
Микросхема таймера генерирует импульсы частоты 1193180 гц. Эта последовательность импульсов поступает на три канала таймера. В каждом канале есть свой счетчик, работающий как делитель частоты, максимальное число которое может быть записано в счетчике (коэффициент деления) - 655357. Счетчики каналов таймера независимы друг от друга и доступны для программиста. Назначение каналов таймера следующее: 
•	канал 0 используется для системной службы времени. При инициализации системы BIOS записывает в счетчик этого канала максимально возможное число, таким образом, импульсы на выходе делителя выдаются с частотой около 18.2 гц (период этой частоты - около 55 мсек - программисты иногда называют "тиком" таймера). Выход делителя этого канала поступает на контроллер прерываний и вызывает прерывание с номером 8; 
•	выход канала 1 используется схемами регенерации памяти, поэтому программисты с этим каналом не работают; 
•	выход канала 2 поступает на динамик ПЭВМ, этот канал используется для генерации звука. 
Каждый канал может работать в одном из 6 режимов, но программисты, как правило, используют его в режиме 3 (генератор меандра). 
Программирование канала таймера представляет собой запись числа в счетчик канала. Имеется один управляющий порт - 0x43 для всех каналов и по одному порту данных для каждого канала - 0x40, 0x41, 0x42. При программировании следует записать в порт 0x43 управляющий байт, который обычно имеет вид:  x x 1 1 0 1 1 0, где xx - номер канала таймера, а затем послать в порт данных выбранного канала сначала младший, а затем старший байт счетчика. 

Пример: 	 
Дана функция - y=50*(sin(x/10)+cos(x/8))+R+150; 
R - в диапазоне 0 - 10; 
частота - 36.4 Гц. 
Программа состоит из основной программы и трех функций. 
void *readvect(int in) - функция читает вектор прерывания с номером in и возвращает его значение. 
void writevect (int in, void *h) - функция устанавливает новый вектор прерывания in на новый обработчик этого прерывания по адресу h. 
void interrupt newtime() - процедура нового обработчика прерывания таймера. 
В этой программе применяются две константы: 
TIMEINT=8 - номер прерывания таймера; 
NN=100 - максимальное число показаний АЦП. 
Переменные, глобальные для всей программы: 
y - массив показаний АЦП; 
ny - текущий индекс в массиве показаний; 
yc - текущее значение функции; 
kf - счетчик вызовов oldtime (oldtime вызывается каждые второй раз); 
rr и sr - переменные, которые используются для задания значений регистров общего назначения и сегментных регистров, соответственно при вызове прерывания. 
Переменные главной программы: 
oldtic - старый коэффициент деления; 
newtic - новый коэффициент деления (применяется для увеличения частоты вызова прерываний таймера); 
x - аргумент заданной функции F(x); 
dd - тип графического адаптера; 
m - режим графики; 
errorcode - код результата инициализации графики. 
Главная программа постоянно вычисляет значения заданной функции F(x) при переменном аргументе, что имитирует непрерывный сигнал, а обработчик прерывания 8 имитирует преобразователь с постоянным шагом дискретизации по времени. Перед началом работы канал 0 таймера программируется на частоту в 2 рази большую обычной (записью в порт 43h управляющего байта 00110110b=36h, а потом посылкой в порт 40h нового значения коэффициента деления), таким образом, "частота дискретизации" составляет около 36.4 Гц. При поступлении следующего прерывания запоминается текущее значение функции F(x), старый обработчик прерывания oldtime вызывается не при кожному прерывании, а лишь один раз из двух (переменная kf - счетчик по модулю 2), когда oldtime не вызывается, наш обработчик сам сбрасывает контроллер прерываний посылкой значения 20h в порт 20h. После набора 100 "показаний АЦП" восстанавливается старый вектор обработчика таймера, а результат аналого-цифрового преобразование выводится на терминал в графическом режиме в виде решетчатой функции. 
Функция readvect() читает вектор заданного прерывания. Для чтения вектора применяется функция 35h DOS (прерывания 21h): 
Вход:	AH = 35h;
AL = номер вектора прерывания.
Выход:	ES:BX = адрес программы обработки прерывания.
Функция writevect() устанавливает новый вектор прерывания по заданному адресу. Для записи вектора применяется функция 25h DOS: 
Вход:	AH = 25h; 
AL = номер вектора прерывания;
DS:BX = 4-байтный адрес нового обработчика прерывания.
* Подключение стандартных заголовков */
#include <dos.h>
#include <math.h>
#include <stdlib.h>
#include <graphics.h>
#include <time.h>
#include <conio.h>
#define TIMEINT 8           /* Прерывание таймера   */
#define NN 100 /* Максимальное количество показаний */

void interrupt (*oldtime)();  /* Новый обpаботчик   */
                             /* пpеpываний таймеpа  */

void interrupt  newtime();   /* Старый обpаботчик   */
                             /* пpеpываний таймеpа  */
static int y[NN];  /* Накопитель показаний          */
static int ny;     /* Индекс в массиве y            */
static int yc;     /* Текущее значение              */
static int kf;     /* Счетчик вызовов oldtime       */
union REGS rr;     /* Запись нового вектора         */
struct SREGS sr;
void *readvect(int in);     /* Получение старого вектора */
void writevect(int in, void *h);/* Запись нового вектора */
/*--------------------------------------------------------*/
void main()
{
  unsigned oldtic=65535u;  /* Старый коэфф. деления      */
  unsigned newtic=32768u;  /* Новый коэфф. деления       */
  int dd,                  /* Графический драйвер        */
      m,                   /* Графический режим          */
      errorcode;           /* Код ошибки                 */
  double x;                /* Аргумент функций sin и cos */
  textbackground(0);
  clrscr();
  textattr(0x0a);
  cprintf("              Практическая  работа N6              ");
  cprintf("\n                Управление таймером               ");
  textattr(0x8e);
  gotoxy(35,12);
  cprintf("Please wait");
  /* Программирование канала 0 */
  outportb(0x43,0x36);                   /* Управляющий байт */
  outportb(0x40,newtic&0x00ff); /* Младший байт счетчика */
  outportb(0x40,newtic>>8);   /* Старший байт счетчика */
  ny=-1;            /* Признак того, что АЦП еще не началось */
  kf=15;
  /* Подключение к вектору */
  oldtime=readvect(TIMEINT);
  writevect(TIMEINT,newtime);
  /* Запуск "непрерывного процесса" */
  randomize();
  for (x=ny=0; ny<NN; x+=1)
    yc=(int)(50*(sin(x/10)+cos(x/8))+random(11)+150);
  /* Восстановление вектора */
  writevect(TIMEINT,oldtime);
  /* Восстановление канала 0 */
  outportb(0x43,0x36);                   /* Управляющий байт */
  outportb(0x40,oldtic&0x00ff); /* Младший байт счетчика */
  outportb(0x40,oldtic>>8);   /* Старший байт счетчика */
  /* Вывод запомненных результатов */
  dd=3; /* EGA, 16 цветов */
  m=1;  /* Режим 640*350 */
  initgraph(&dd,&m,"");
  /* проверка результата инициализации */
  errorcode = graphresult();
  if (errorcode != grOk)       /* ошибка графического режима */
  {
   printf("Graphics error: %s\n", grapherrormsg(errorcode));
   printf("Press any key to halt:");
   getch();
   exit(1);                         /* аварийное завершение */}
  setcolor(10);
  settextstyle(0,0,2);
  outtextxy(15,10,"Результати аналого-цифрового преобразования:");
  setcolor(9);
  rectangle(15,40,624,330);
  setcolor(11);
  for(ny=0; ny<NN; ny++)
  {
   circle(22+ny*6,330-y[ny]*1,2);
   line(22+ny*6,330,22+ny*6,330-y[ny]*1);}
  setcolor(12);
  settextstyle(0,0,1);
  outtextxy(260,340,"Нажмите любую клавишу ...");
  getch();
  closegraph();
}

/* Новый обpаботчик пpеpиваний таймеpа */
void interrupt newtime()
{
  if (--kf<0) {
    /* Виклик oldtime - на 2-й раз */
    (*oldtime)();
    kf=1;}
  else /* иначе - сброс контроллера */
    outportb(0x20,0x20);
  if ((ny>=0)                       /* Если АЦП началось, */
    &&(ny<NN))  /* и NN показаний еще не набрано, */
      y[ny++]=yc;        /* запоминание очередного показания *}
/* Получение старого вектора */
void *readvect(int in)
{
  rr.h.ah=0x35; rr.h.al=in;
  intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/* Запись нового вектора */
void writevect(int in, void *h)
{
  rr.h.ah=0x25;
  rr.h.al=in;
  sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h);
  intdosx(&rr,&rr,&sr);
}
Результат работы представляется в графическом режиме в виде решетчатой функции на экране терминала. 

Задания для практической работы:
Таблица 1 – варианты индивидуальных заданий
№ варианта	R	y=F(x)	Частота (ГЦ)	
1	0 - 10	y=((100-x)%100)*0.5+R+100	72.7	
2	0 - 10	y=((77-x)%200)*0.1+R+150	145.5	
3	0 - 10	y=40*(cos(x/10)+cos(x/6))+R+100	36.4	
4	0 - 2	y=50*(cos(x/10)+R*cos(x/5))+150	54.5	
5	0 - 2	y=50*sin(cos(x/10)+R)+150	90.9	
6	0 - 1	y=0.5*(x%100)+100*sin(x/5)*R+150	72.7	
7	0 - 1	y=0.5*(x%100)+50*sin(x/50)*(R+1)+150	145.5	
8	0 - 1	y=50*sin(x/(10+R))+150	36.4	
9	0 - 0.2	y=50*(fabs(sin(x/10))+R)+150	54.5	
10	0 - 10	y=10*-1N*(x/100)+R+150	90.9	
11	0 - 10	y=-1N*50*sin(x/10)+150+R	72.7	
12	0 - 10	y=0.5*-1N*(x%200)*sin(x/10)+150+R	145.5	
13	0 - 10	y=0.5*-1N*(x%200)+50*sin(x/10)+150+R	36.4	
14	0 - 10	y=50*(sin(x/64)+sin(x/32)+
+0.1*sin(x/4)+0.1*sin(x/2))+R+150	54.5	
15	0 - 10	y=50*(sin(x/64)+sin(x/32)+ +sin(x/2))+R+150	90.9	
16	0 - 10	y=0.5*(x%100+-1N*x%200)+150+R	72.7	
17	0 - 10	y=0.5*(-1N*x%100+x%200)+R+150	145.5	
18	0 - 0.1	y=0.5*(-1N*x%100+200*R*sin(x/10))+150	36.4	
19	0 - 0.3	y=50*(fabs(sin(x/10))+R)+150	55.5	
20	0 - 10	y=10*-1N*(x/100)+R+150	93.9	
21	0 - 10	y=-1N*50*sin(x/10)+50+R	75.7	
22	0 - 10	y=0.5*-1N*(x%200)*sin(x/10)+150+R	145.5	
23	0 - 10	y=0.5*-1N*(x%200)+50*sin(x/10)+150+R	36.4	
24	0 - 10	y=50*(sin(x/64)+sin(x/32)+
+0.1*sin(x/4)+0.1*sin(x/2))+R+150	54.5	
25	0 - 10	y=50*(sin(x/64)+sin(x/32)+ +sin(x/2))+R+150	90.9	
26	0 - 10	y=0.5*(x%100+-1N*x%200)+50+R	75.7	
27	0 - 10	y=0.5*(-1N*x%100+x%200)+R+50	135.5	
28	0 - 0.3	y=0.5*(-1N*x%100+200*R*sin(x/10))+150	39.4	
29	0 - 10	y=((77-x)%200)*0.1+R+150	145.5	
30	0 - 10	y=50*(cos(x/10)+cos(x/6))+R+100	39.4	
31	0 - 2	y=50*(cos(x/10)+R*cos(x/5))+150	58.5	
32	0 - 2	y=50*sin(cos(x/10)+R)+150	91.9	
33	0 - 1	y=0.5*(x%100)+100*sin(x/5)*R+5	75.7	
34	0 - 1	y=0.5*(x%100)+50*sin(x/50)*(R+1)+50	155.5	
35	0 - 1	y=50*sin(x/(10+R))+50	39.4	
Построить модель аналого-цифрового преобразователя (АЦП), которая работает в реальном времени. Процесс, который дискретизуется, моделируется программой (программным блоком), который выполняет циклическое вычисление функции y=F(x), где x - номер итерации. Преобразователь моделируется программой, которая выполняет с заданной частотой (в реальном времени) прерывание процесса, считывание и запоминание текущего значения функции. Запомнить не меньше 80 значений функции. Обеспечить наглядное представление результатов работы "АЦП". 
Примечание: R - случайное вещественное число из последовательности, равномерно распределенной в указанном интервале. 

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

Практическая работа № 19-20
 «Получение навыков в работе с главной загрузочной записью»
Цель работы: получение практических навыков в работе с главной загрузочной записью жесткого диска. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов

Краткие теоретические и учебно-методические материалы 
Жесткий диск представляет собой два или более дисков, посаженных на одну ось. Все головки чтения/записи здесь, как и для дискеты образуют жесткий блок, то есть перемещаются все вместе. Для жесткого диска вполне органичным является понятие цилиндра - совокупности всех дорожек, расположенных на разных поверхностях и равноудаленных от оси вращения. С точки зрения адресации понятия "дорожка" и "цилиндр" являются синонимами. Сектор является минимальной адресуемой единицей при обращениях к внешней памяти. Адрес сектора на внешней памяти состоит из трех составляющих: 
•	номер дорожки (нумерация дорожек начинается с 0); 
•	номер головки (нумерация головок начинается с 0); 
•	номер сектора на дорожке (нумерация секторов начинается с 1).
В DOS для чтения/записи секторов служат прерывания (прерывания, а не функции DOS!) 0x25 (чтение) и 0x26 (запись). Обычный формат обращения к этим прерываниям  следующий: 
AL	- номер логического диска (0 - A, 1 - B, 2 - C, etc.);	
CX	- количество секторов, которое нужно прочитать /записать; DX - логический номер сектора;	
DS:BX	- адрес области оперативной памяти, с которой происходит обмен.	
На выходе, как и для прерывания 0x13, устанавливается флаг переноса, а регистр AH содержит код ошибки при наличии таковой. Заметим, что дисковый адрес задается здесь не физический, а логический - номер сектора относительно начала логического диска. Нумерация секторов внутри логического диска начинается с 0, и номер логического сектора может быть вычислен как:
logs = ( (t * ns) + h) * nh + s-1;    (1),
где 
•	t, h, s - физический адрес (дорожка, головка, сектор); 
•	ns - количество секторов на дорожке, 
•	nh - количество головок чтения/записи.
Для получения абсолютного номера сектора надо к вычисленному значению прибавить еще некоторую величину s0 - абсолютный номер сектора начала логического диска. 
Некоторые области диска содержат системную информацию, используемую DOS при работе с данным диском. К таким областям относятся: 
•	Главная Загрузочная Запись (MBR - Master Boot Record) - только для жесткого диска; 
•	Загрузочный Сектор логического диска (Boot-сектор); 
•	Таблица Размещения Файлов (FAT - File Allocation Table); 
•	Корневой Каталог (Root Directiry). 

Пример:
Прочитать и выполнить форматный вывод на экран Главной Загрузочной Записи жесткого диска на своем рабочем месте. 
Программа состоит из основной программы main(), которая реализует все действия для чтения главной загрузочной записи. 
Переменные в основной программе: 
x, y - экpанные кооpдинаты; 
head - номеp головки (0); 
Sect_Trk - номеp доpожки и сектоpа (0,1); 
ndrive=0 - номеp логического диска; 
EndList - указатель на подпись. 
Кроме того, в программе есть такие структуры: 
Структура элемента раздела: 
  struct Part {
    byte ActFlag;     /* признак активного раздела */
                /* физический адрес начала раздела */
      byte Begin_Hd;                  /* # головки */
      word Begin_SecTrk;   /* # сектора та дорожки */
    byte SysCode;                   /* код системы */
                 /* физический адрес конца раздела */
      byte End_Hd;                    /* # головки */
      word End_SecTrk;      /* # сектора и дорожки */
    dword RelSec;             /* # сектора початку */
    dword Size;             /* количество секторов */
    };
Стpуктуpа главной загрузочной записи. 
  struct MBR {
    char LoadCode[0x1be];     /* пpогpамма загрузки */
    struct Part rt[4];       /* 4 элемента pазделов */
    word EndFlag;                    /* подпись MBR */
    };
Начальный адрес для чтения задается: 0,0,1. При помощи прерывания 0x13 программа считывает сектор по заданному адресу, далее происходит поэлементный анализ таблицы разделов - пока не встретится признак конца таблицы или раздел нулевого размера. Значения полей элемента таблицы выводятся на экран. Манипуляции, которые описываются макросами TRK и SECT, обеспечивают распаковку номера дорожки и сектора. Если в поле SysCode содержится признак расширенного раздела, то устанавливается новый дисковый адрес, считывается новый сектор и анализируется новая таблица. 
/* Стандартные заголовки */
#include <dos.h>
#include <conio.h>

/* Типы данных */

#define byte unsigned char
#define word unsigned int
#define dword unsigned long

void read_MBR(void);    /* Чтение MBR */
/* Получение из упакованного SecTrk # сектора */
#define SECT(x) x&0x3f
/* Получение из упакованного SecTrk # дорожки */
#define TRK(x) (x>>8)|((x<<2)&0x300)

  /* структура элемента раздела */
  struct Part {
    byte ActFlag;          /* признак активного раздела */
                     /* физический адрес начала раздела */
    byte Begin_Hd;                         /* # головки */
    word Begin_SecTrk;           /* # сектора и дорожки */
    byte SysCode;                        /* код системы */
                      /* физический адрес конца раздела */
    byte End_Hd;                           /* # головки */
    word End_SecTrk;             /* # сектора и дорожки */
    dword RelSec;                  /* # сектора початку */
    dword Size;                  /* количество секторов */
    };
  /* стpуктуpа главной загрузочной записи */
  struct MBR {
    char LoadCode[0x1be];   /* пpогpамма загрузки */
    struct Part rt[4];      /* 4 эл-та pазделов */
    word EndFlag;           /* подпись MBR */
    } mbr;
  /* дополнительные переменные */
  int x=10,y;      /* экpанные кооpдинаты */
  byte head=0;     /* номеp головки (0) */
  word Sect_Trk=1; /* номеp доpожки и сектоpа (0,1) */
  int ndrive=0;    /* номеp логического диска */
  word *EndList;   /* указатель на подпись */
  union REGS rr;
  struct SREGS sr;
  word i;
/*--------------------------------------------------------*/
main()
{
  textbackground(0);
  clrscr();
  textattr(0x0a);
  cprintf("              Практическая работа ");
  gotoxy(1,2);
  cprintf("            Главная загрузочная запись");
  textattr(12);
  gotoxy(30,4);
  cprintf("Разделы жесткого диска:\n");
  gotoxy(1,6);
  textattr(11);
  cprintf("Лог.диск -----> \n\r");
  cprintf("Признак ------> \n\r");
  cprintf("Код системы --> \n\r");
  cprintf("Начало: гол.--> \n\r");
  cprintf("        дор.--> \n\r");
  cprintf("        сект.-> \n\r");
  cprintf("Конец:  гол.--> \n\r");
  cprintf("        дор. -> \n\r");
  cprintf("        сект.-> \n\r");
  cprintf("Нач.сектор ---> \n\r");
  cprintf("Размер -------> \n\r");
  textcolor(11);
NEXT:
  read_MBR();
  for (EndList=(word *)&mbr.rt[(i=0)];
      (*EndList!=0xaa55)&&(mbr.rt[i].Size>0L);
      EndList=(word *)&mbr.rt[++i])
  {
    /* кооpдинаты куpсоpа */
    y=6;
    x+=7;
    gotoxy(x,y++);
    if (mbr.rt[i].SysCode==5)
     {textattr(13);
      cprintf("Ext    ");
     }
    else
     textattr(12);
    cprintf("%-7c",'C'+ndrive++);
    gotoxy(x,y++);  textattr(14);
    cprintf("%02xH       ",mbr.rt[i].ActFlag);
    gotoxy(x,y++);  textattr(15);
    cprintf("%-7d",mbr.rt[i].SysCode);
    gotoxy(x,y++);  textattr(14);
    cprintf("%-7d",mbr.rt[i].Begin_Hd);
    gotoxy(x,y++);  textattr(15);
    cprintf("%-7u",TRK(mbr.rt[i].Begin_SecTrk));
    gotoxy(x,y++);  textattr(14);
    cprintf("%-7u",SECT(mbr.rt[i].Begin_SecTrk));
    gotoxy(x,y++);  textattr(15);
    cprintf("%-7d",mbr.rt[i].End_Hd);
    gotoxy(x,y++);  textattr(14);
    cprintf("%-7u",TRK(mbr.rt[i].End_SecTrk));
    gotoxy(x,y++);  textattr(15);
    cprintf("%-7u",SECT(mbr.rt[i].End_SecTrk));
    gotoxy(x,y++);  textattr(14);
    cprintf("%-7lu",mbr.rt[i].RelSec);
    gotoxy(x,y++);  textattr(15);
    cprintf("%-7lu",mbr.rt[i].Size);
    if (mbr.rt[i].SysCode==5)
    {
      /* если код системы 5, pаздел содержит свою таблицу
        pазделов; определяется ее дисковый адpес,
        и новая таблица считывается в память */
      head=mbr.rt[i].Begin_Hd;
      Sect_Trk=mbr.rt[i].Begin_SecTrk;
      goto NEXT;
    }
  }
  gotoxy(x,y++);
  textattr(10+128);
  gotoxy(29,18);

  cprintf("Нажмите любую клавишу...");
  getch();
}
/*---------------------- MBR------------------------*/
void read_MBR(void)
{
    rr.h.ah=2;           /* Чтение */
    rr.h.al=1;           /* Секторов 1 */
    rr.h.dl=0x80;        /* Жесткий диск */
    rr.h.dh=head;        /* Головка */
    rr.x.cx=Sect_Trk;    /* Дорожка, сектор */
    sr.es=FP_SEG(&mbr);  /* Адрес буфера в ОП */
    rr.x.bx=FP_OFF(&mbr);
    int86x(0x13,&rr,&rr,&sr);
    /* Проверка ошибок чтения */
    if (rr.x.cflag)
    {
      printf("Ошибка чтения: %x.  ",rr.h.ah);
      printf("Нажмите любую клавишу...\n\7");
      getch();
      exit();
    }
}
В процессе работы программы на экран выводится информация такого вида: 
                                           Разделы жесткого диска:
Лог.диск -----> C      Ext    E      Ext    G
Признак ------> 80H    00H    00H    00H    00H
Код системы --> 1      5      4      5      0
Начало: гол.--> 1      0      1      0      1
дор.--> 0      121    121    724    724
сект.-> 1      1      1      1      1
Конец:  гол.--> 4      4      4      4      4
дор. -> 120    975    723    975    975
сект.-> 17     17     17     17     17
Нач.сектор ---> 17     10285  17     51255  17	
Размер -------> 10268  72675  51238  21420  21403

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

Контрольные вопросы:
1.	Что такое сектор жесткого диска?
2.	Из чего состоит адрес сектора?
3.	Как считается нумерация секторов диска?
4.	Для чего необходим Boot-сектор?
5.	Для чего необходимо прерывание 0х13?

Практическая работа № 21-22
«Управление программами»
Цель работы: получение практических навыков по управлению программой и программными ресурсами.
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
Аналогом понятия внутреннего файла в языке СИ является понятие потока. Потоку в СИ не ставится в соответствие тип. Поток – это байтовая последовательность, передаваемая в процессе ввода-вывода.
Поток должен быть связан с каким-либо внешним устройством или файлом на диске.
Основные отличия файлов в СИ состоят в следующем: здесь отсутствует понятие типа файла и, следовательно, фиксированной структуры записи файла. Любой файл рассматривается как байтовая последовательность:
байт 0	байт 1	байт 2	…	EOF	
EOF – стандартная константа, обозначающая признак конца файла.
Существуют стандартные потоки и потоки, объявляемые в программе. Последние обычно связываются с файлами на диске, создаваемыми программистами. Стандартные потоки назначаются и открываются системой автоматически. С началом работы любой программы открываются 5 стандартных потоков, из которых основными являются следующие:
•	stdin – поток стандартного ввода (обычно связан с клавиатурой);
•	stdout – поток стандартного вывода (обычно связан с дисплеем);
•	stderr – вывод сообщений об ошибках (связан с дисплеем).
Поток для работы с дисковым файлом должен быть открыт в программе.
Работа с дисковым файлом начинается с объявления указателя на поток. Формат такого объявления:
FILE *имя_указателя;
Например, FILE *fp;
Слово FILE является стандартным именем структурного типа, объявленного в заголовочном файле stdio.h. В структуре FILE содержится информация, с помощью которой ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке и т.д.
Следующий шаг – открытие потока, которое производится с помощью стандартной функции fopen(). Эта функция возвращает конкретное значение для указателя на поток и поэтому ее значение присваивается объявленному ранее указателю. Соответствующий оператор имеет формат:
имя_указателя=fopen(имя_файла, режим_открытия);
Параметры функции fopen() являются строками, которые могут быть как константами, так и указателями на символьные массивы. Например,
fp=fopen(“test.dat”,”r”);
Здесь test.dat – это имя физического файла в текущем каталоге диска, с котрым теперь будет связан поток с указателем fp. Параметр режима r означает, что файл открыт для чтения.
Существуют следующие режимы открытия файла и соответсвующие им парметры:
Параметр Режим
r открыть для чтения
w создать для записи
a открыть для добавления
r+ открыть для чтения и записи
w+ создать для чтения и записи
a+ открыть для добавления или
создать для чтения и записи
Открытие уже существующего файла для записи ведет к потере прежней информации в нем. Если такой файл еще не существовал, то он создастся. Открывать для чтения можно только существующий файл.
Поток может быть открыт либо для текстового, либо для двоичного режима обмена.
Смысл понятия текстового файла: это последовательность символов, которая делится на строки специальными кодами – возврат каретки (код 13) и перевод строки (код 10). Если файл открыт в текстовом режиме, то при чтении из такого файла комбинация символов «возврат каретки – перевод строки» преобразуется в один символ \n – переход к новой строке.
При записи в файл осуществляется обратное преобразование. При работе с двоичным файлом никаких преобразований символов не происходит, т.е. информация переносится без всяких изменений.
Указанные выше параметры режимов открывают текстовые файлы. Если требуется указать на двоичный файл, то к параметру добавляется буква b. Например, rb, wb или r+b. В некоторых компиляторах текстовый режим обмена обозначается буквой t, т.е. записывается a+t или rt.
Если при открытии потока по какой-либо причине возникла ошибка, то функция fopen() возвращает значение константы NULL. Эта константа также определена в файле stdio.h. ошибка может возникнуть из-за отсутствия открываемого файла на диске, нехватки места в динамической памяти и т.п. Поэтому желательно контролировать правильность прохождения процедуры открытия файла:
FILE *fp;
If (fp=fopen(“test.dat”,”r”)==NULL
{ puts(“Не могу открыть файл\n”);
return; }
В случае ошибки программа завершит выполнение с закрытием всех ранее открытых файлов.
Закрытие файла осуществляет функция fclose(), прототип которой имеет вид:
int fclose(FILE *fptr);
Здесь fptr обозначает формальное имя указателя на закрываемый поток. Функция возвращает ноль, если операция закрытия прошла успешно. Другая величина означает ошибку.
Запись и чтение символов
Запись символов в поток производится функцией putc() с прототипом
int putc(int ch, FILE *fptr);
Если операция прошла успешно, то возвращается записанный символ. В случае ошибки возвращается константа EOF.
Считывание символа из потока, открытого для чтения, производится функцией gets() с прототипом
int gets(FILE *fptr);
Функция возвращает значение считываемого из файла символа. Если достигнут конец файла, то возвращается значение EOF. Это происходит лишь в результате чтения кода EOF.
Функция gets() возвращает значение типа int. То же самое можно сказать и про аргумент ch в описании функции puts(). Используется же в обоих случаях только младший байт. Поэтому обмен при обращении может происходить и с переменными типа char.
Пример 1. Составить программу записи в файл символьной последовательности, вводимой с клавиатуры. Пусть признаком завершения ввода будет символ *.
//Запись символов в файл
# include <stdio.h>
void main()
{ FILE *fp; char c;
if (( fp=fopen(“test.dat”,”w”))==NULL)
{puts(“Не могу открыть файл! \n”); return;}
puts(“Вводите символы. Признак конца – *”);
while ((c=getchar())!=’*’) putc(c,fp);
fclose(fp); }
В результате на диске (в каталоге, определяемом системой) будет создан файл с именем test.dat, который заполнится вводимыми символами. Символ * в файл не запишется.
Пример 2. Файл требуется последовательно прочитать и содержимое вывести на экран.
//Чтение символов из файла
# include <stdio.h>
# include <conio.h>
void main()
{ FILE *fp; char c;
clrscr();
if (( fp=fopen(“test.dat”,”r”))==NULL)
{puts(“Не могу открыть файл! \n”); return;}
while ((c=getc(fp))!=EOF) putchar(c);
fclose(fp);
}
Запись и чтение целых чисел
Запись целых чисел в поток без преобразования их в символьную форму производится функцией putw() с прототипом
int putw(int, FILE *fptr);
Если операция прошла успешно, то возвращается записанное число. В случае ошибки возвращается константа EOF.
Считывание целого числа из потока, открытого для чтения, производится функцией getw() с прототипом
int getw(FILE *fptr);
Функция возвращает значение считываемого из файла числа. Если прочитан конец файла, то возвращается значение EOF.
Пример 3. Составить программу записи в файл последовательности целых чисел, вводимых с клавиатуры, чтения их из файла и вывода на экран. Признаком конца ввода будет число 9999.
//Запись и чтение целых чисел
# include <stdio.h>
# include <conio.h>
void main()
{ FILE *fp; int x;
clrscr();
// Открытие потока для записи
if (( fp=fopen(“test.dat”,”w”))==NULL)
{puts(“Не могу открыть файл для записи! \n”); return;}
puts(“Вводите числа. Признак конца – 9999”);
scanf(“%d”,&x);
while (x!=9999)
{putw(x,fp); scanf(“%d”,&x);}
fclose(fp); // Закрытие потока в режиме записи
// Открытие потока для чтения
if (( fp=fopen(“test.dat”,”r”))==NULL)
{puts(“Не могу открыть файл для чтения! \n”); return;}
while ((x=getw(fp))!=EOF) printf(“\n%d”,x);
fclose(fp); }
После завершения ввода чисел в файл его необходимо закрыть. При этом происходит сброс накопленных в буфере значений на диск. Только после этого можно открывать файл для чтения. Указатель устанавливается на начало потока, и дальнейшее чтение будет происходить от начала до конца файла.
Задание к выполнению парктической работе
1. Набрать и выполнить пример.
2. Переделать программу, используя функции для создания, добавления и просмотра данных.
Создать программу, в которой реализованы создание, добавление и просмотр файла, содержащего информацию о фамилии и среднем балле студентов. Процесс добавления информации заканчивается при нажатии точки.
#include <stdio.h>
#include <stdlib.h>
struct Sved {
char Fam[30];
double S_Bal;
} zap,zapt;
char Spis[]="c:\\work\\Sp.dat";
FILE *F_zap;
FILE* Open_file(char*, char*);
void main (void)
{
int i, j, kodR, size = sizeof(Sved), kod_read;
while(1) {
puts("Создать – 1\n Добавить– 3\nПросмотреть– 2\nВыход – 0");
scanf("%d",&kodR);
switch(kodR) {
case 1:
case 3:
if(kodR==1) F_zap = Open_file (Spis,"w+");
else F_zap = Open_file (Spis,"a+");
while(2) {
puts("\n Fam (. – end) ");
scanf("%s",zap.Fam);
if((zap.Fam[0])=='.') break;
puts("\n Ball: ");
scanf("%lf",&zap.S_Bal);
fwrite(&zap,size,1,F_zap);
}
fclose(F_zap);
break;
case 2: F_zap = Open_file (Spis,"r+"); int nom=1;
while(2) {
if(!fread(&zap,size, 1, F_zap)) break;
printf(" %2d: %20s %5.2lf\n",
nom++, zap.Fam, zap.S_Bal);
}
fclose(F_zap);
break;
case 0: return; // exit(0);
} // Закрывает switch()
} // Закрывает while()
}
// Функция обработки ошибочной ситуации при открытии файла
FILE* Open_file(char *file, char *kod)
{
FILE *f;
if(!(f = fopen(file, kod))) {
puts("Open File Error!");
exit(1);
}
return f;
}
Контрольные вопросы
1. Дайте определение объединения.
2. Какое основное применение объединения?
3. Как осуществляется доступ к отдельным элементам объединения?
4. Дайте определение перечисления.
5. Какое основное применение переменных типа перечисления?

Практическая работа 23-24
 «Изучение организации и принципов работы клавиатуры»
Цель работы: изучение организации и принципов работы клавиатуры и закрепление практических навыков управления ею, а также практических навыков создания собственных программ обработки прерываний. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
Работа клавиатуры организована на базе собственного микропроцессора. При нажатии или отпускании любой клавиши генерируется код (скан-код), который записывается в собственную память (аппаратный буфер) клавиатуры. При этом в центральный процессор выдается прерывание 9, сигнализирующее о появлении очередного скан-кода. Программа обработки этого прерывания может теперь прочитать код из клавиатуры. Если она этого не сделает, то сканкоды будут накапливаться в аппаратном буфере и могут быть прочитаны позднее. Чтение скан-кода производится из однобайтного порта 0x60. При выполнении только чтения скан-код не удаляется из аппаратного буфера, и при следующем обращении к порту 0x60 будет прочитан тот же скан-код. 
Для того, чтобы удалить скан-код из аппаратного буфера, необходимо послать в клавиатуру сигнал подтверждения: на короткое время выставить единицу в старшем разряде однобайтного порта 0 x61 (не разрушая при этом остальных разрядов этого порта). Каждая клавиша имеет собственный уникальный скан-код, в том числе и на 101-клавишной клавиатуре AT, где некоторые клавиши дублируются - например, левая и правая клавиши Shift имеют разные скан-коды. Распределение значений скан-кодов примерно соответствует расположению клавиш на панели клавиатуры. Скан-коды не совпадают с кодировкой внутри машинного представления символов, для которой применяется код ASCII. Преобразование скан-кодов в коды ASCII происходит программными путем.
Пример. 
Разработать программу обработки прерывания от клавиатуры, которая должна: 
распознавать нажатие "горячей" комбинации клавиш и реагировать на него звуковым сигналом; при первом нажатии "горячей" комбинации переходить в режим блокировки ввода заданной клавиши, при втором - отменять этот режим; системная обработка всех других клавиш нарушаться не должна. Комбинация клавиш LeftCtrl+RightShift+F3; блокирование ввода клавиши 3. 
Программа состоит из основной программы и трех функций. 
void *readvect(int in) - функция читает вектор прерывания с номером in и возвращает его значение. 
void writevect (int in, void *h) - функция устанавливает новый вектор прерывания in на новый обработчик этого прерывания по адресу h. 
void interrupt new9() - процедура нового обработчика прерывания 9h. 
Глобальные переменные программы: old9 - адрес старого обработчика прерывания 9h; F3_code - скан-код клавиши "F3", которая входит в комбинацию "горячих" клавиш; key3_code - скан-код клавиши "3", которая будет блокироваться/разблокироваться при каждом нажатии "горячей" комбинации клавиш; f - флаг, который при каждом нажатии "горячей" комбинации клавиш переключается из состояния 0 в 1 или из 1 в 0 (состояние 1 означает, что клавиша "3" заблокирована); rr и sr - переменные, которые используются для задания значений регистров общего назначения и сегментных регистров соответственно при вызове прерывания. 
В главной программе использует символьный массив string для проверки работы программы. Переменные процедуры обработки прерывания 9h: 
c - переменная, которая используется для подтверждения приема из клавиатуры, в случае, если была нажата клавиша "3", а флаг f показывал, что эта клавиша заблокирована; 
x, y - переменные, которые используются для сохранения координат курсора на экране в момент вызова процедуры обработки прерывания; 
byte17 - байт флага состояния клавиатуры в области данных BIOS по адресу 0040:0017; 
byte18 - байт флага состояния клавиатуры в области данных BIOS по адресу 0040:0018; 
mask - маска, которая используется для определения нажатия клавиши левый Shift (в этом случае бит 1 в byte17 установлен в 1); 
mask17 - маска, которая используется для определения нажатия клавиши Сtrl (в этом случае бит 2 в byte17 установлен в 1); 
mask18 - маска, которая используется для определения нажатия клавиши левый Сtrl (в этом случае бит 0 в byte18 установлен в 1); 
Главная программа выполняет такие действия: 
Запоминает адрес старого обработчика прерывания 9h, вызывая функцию readvect(in) с параметром in=9. Записывает в таблицу векторов прерываний адрес нового обработчика прерывания с помощью функции writevect(). Вводом строки символов дает возможность проверить работу программы и ее реакцию на нажатие "горячей" комбинации клавиш и блокирование/разблокирование ввода клавиши "3". В конце работы восстанавливает в таблице векторов прерываний адрес старого обработчика. 
Для решения задачи процедура обработки прерывания от клавиатуры new9() должна действовать по такому алгоритму: 
Прочитать флаги состояния клавиатуры (статус клавиш-переключателей), которые находятся в области данных BIOS (два байта по адресам 0040:0017 и 0040:0018). 
Выделить бит 1 в флаге по адресу 0040:0017 (если он равен 1, то нажата клавиша левый Shift). 
Выделить бит 2 в этом же флаге (если он равен 1, то нажата левый или правый Ctrl). 
Выделить бит 0 в флаге состояния клавиатуры по адресу 0040:0018 (если он равен 1, то нажата клавиша левый Ctrl). 
Из порта 60h прочитать скан-код нажатой клавиши. 
Если нажата комбинация клавиш левый Shift, правый Ctrl (нажата клавиша Ctrl, но это не правый Ctrl)и клавиша F3, то выполнить п.7. Иначе - перейти к п.8. 
Сигнализировать о нажатии "горячей" комбинации клавиш звуковым сигналом, переключить значение флага блокирования ввода клавиши "3" на обратное и вызвать старый обработчик прерывания от клавиатуры. 
Прочитав байт из порта 60h, определить, нажата ли клавиша 3" и если, кроме этого, еще и флаг блокирования указывает на то, что она заблокирована (f=1), то выполнить п.п. 9 и 10, иначе - вызвать старый обработчик прерывания. 
Послать подтверждение приема в клавиатуру. Для этого в порт 61h на короткое время выставить "1" по шине старшего разряда. 
Сбросить контроллер прерываний, посылая код 20h в порт 20h. 
Функция readvect() читает вектор заданного прерывания. Для чтения вектора используется функция 35h DOS (прерывания 21h): 
Вход:	AH = 35h;
AL = номер вектора прерывания.
Выход:	ES:BX = адрес программы обработки прерывания
Функция writevect() устанавливает новый вектор прерывания на заданный адрес. Для записи вектора используется функция 25h DOS: 
Вход:	AL = номер вектора прерывания;
DS:BX = 4-байтный адрес нового обработчика прерывания.
/* Подключение стандартных заголовков */
#include <dos.h>

void interrupt (*old9)(); /* Старый обработчик прерывания 9h */
void interrupt new9();    /* Новый обработчик прерывания 9h  */
void *readvect (int in);  /* Чтение вектора                  */
void writevect (int in,void *h); /* Запись вектора           */

unsigned char F3_code=61; /* scan-code "F3"                  */
unsigned char key3_code=4;/* scan-code "3"                   */
char f=0;                 /* Флаг                            */
union REGS rr;
struct SREGS sr;
/*--------------------------------------------------------*/
void main()
{
  char string[80]; /* Буфер для ввода текста */
  textbackground(0);
  clrscr();
  textattr(0x0a);
  cprintf("---------------");
  cprintf("              Практическая работа               ");
  cprintf("---------------");
  cprintf("---------------");
  cprintf("              Управление клавиатурой              ");
  cprintf("---------------");
  old9=readvect(9);
  writevect(9,new9);
  textattr(0x0c);
  cprintf("\n\n\r\"горячая\" комбинация: ");
  textattr(0x0a);
  cprintf("Left Shift, Right Ctrl, F3\n\r");
  textattr(0x0b);
  cprintf("Клавиша, которая блокируется: ");
  textattr(0x0f);
  cprintf("3");
  textattr(0x07);
  cprintf("\r\nВводите строку символов>");
  scanf("%s",string);
  writevect(9,old9);
}
/*--------------------------------------------------------*/
/* Чтение вектора */
void *readvect(int in)
{
  rr.h.ah=0x35;
  rr.h.al=in;
  intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/*--------------------------------------------------------*/
/* Запись вектора */
void writevect(int in,void *h)
{
  rr.h.ah=0x25;
  rr.h.al=in;
  sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h);
  intdosx(&rr,&rr,&sr);
}
/*--------------------------------------------------------*/
/* Новый обработчик 9-го прерыванмя */
void interrupt new9()
{
  unsigned char c,x,y;
  unsigned char byte17,byte18;
  unsigned char mask=0x02;
  unsigned char mask17=0x04;
  unsigned char mask18=0x01;
  byte17=peekb(0x40,0x17);
  byte18=peekb(0x40,0x18);
  if((inportb(0x60)==F3_code)&&(byte17&mask)&&
                        (byte17&mask17)&&(!(byte18&mask18)))
  {
   cputs("\7");
   x=wherex();
   y=wherey();
   gotoxy(55,3);
   textattr(0x1e);
   if(f==0)
   {
    f=1;
    cprintf("Клавиша \"3\" заблокирована ");
   }
   else
   {
    f=0;
    cprintf("Клавиша \"3\" разблокирована");
   }
   gotoxy(x,y);
   textattr(0x07);
   (*old9)();
  }
  if( (f==1) && (inportb(0x60)==key3_code) )
  {
   c=inportb(0x61);
   outportb(0x61,c|0x80);
   outportb(0x61,c);
   outportb(0x20,0x20);
  }
  else
   (*old9)();
}
Во время программы при первом нажатии комбинации клавиш LeftCtrl+RightShift+F3 программа переходит в режим блокирования вводу клавиши 3, при втором - отменяет этот режим. 

Задания для практической работы:
Разработать программу обработки прерывания от клавиатуры, которая должна: 
распознавать нажатие "горячей" комбинации клавиш и реагировать на него звуковым сигналом; 
при первом нажатии "горячей" комбинации переходить в режим блокировки ввода заданной клавиши, при втором - отменять этот режим; 
системная обработка всех других клавиш нарушаться не должна.
1. комбинация клавиш LeftCtrl+RightShift+F3; блокирование ввода клавиши 3. 
2. комбинация клавиш LeftAlt+RightShift+F1; блокирование ввода клавиши 2.
3. комбинация клавиш LeftCtrl+RightShift+F7; блокирование ввода клавиши 4.
4. комбинация клавиш LeftShift+RightAlt+F9; блокирование ввода клавиши 1.
5. комбинация клавиш LeftAlt+RightShift+F2; блокирование ввода клавиши 5.
6. комбинация клавиш LeftAlt+RightCtrl+F4; блокирование ввода клавиши 7.
7. комбинация клавиш LeftCtrl+RightShift+F7; блокирование ввода клавиши 8.
8. комбинация клавиш LeftAlt+RightShift+F9; блокирование ввода клавиши 9.
9. комбинация клавиш LeftAlt+RightShift+F8; блокирование ввода клавиши 6.
10. комбинация клавиш LeftCtrl+RightAlt+F6; блокирование ввода клавиши 0.
11. комбинация клавиш LeftAlt+RightShift+F1; блокирование ввода клавиши q.
12. комбинация клавиш LeftCtrl+RightShift+F12; блокирование ввода клавиши g
13. комбинация клавиш LeftAlt+Rightv+F11; блокирование ввода клавиши wh
14. комбинация клавиш LeftCtrl+RightShift+F10; блокирование ввода клавиши e
15. комбинация клавиш LeftAlt+RightShift+F12; блокирование ввода клавиши r
16. комбинация клавиш LeftCtrl+RightShift+F2; блокирование ввода клавиши g
17. комбинация клавиш LeftAlt+RightShift+F1; блокирование ввода клавиши t
18. комбинация клавиш LeftAlt+RightCtrl+F3; блокирование ввода клавиши y
19. комбинация клавиш LeftCtrl+RightAlt+F4; блокирование ввода клавиши u
20. комбинация клавиш LeftAlt+RightShift+F5; блокирование ввода клавиши i
21. комбинация клавиш LeftCtrl+RightAlt+F8; блокирование ввода клавиши o
22. комбинация клавиш LeftAlt+RightCtrl+F6; блокирование ввода клавиши p
23. комбинация клавиш LeftAlt+RightShift+F5; блокирование ввода клавиши l
24. комбинация клавиш LeftShift+RightAlt+F9; блокирование ввода клавиши k
25. комбинация клавиш LeftAlt+RightShift+F1; блокирование ввода клавиши j
26. комбинация клавиш LeftAlt+RightCtrl+F3; блокирование ввода клавиши h
27. комбинация клавиш LeftAlt+RightShift+F2; блокирование ввода клавиши d
28. комбинация клавиш LeftCtrl+RightAlt+F12; блокирование ввода клавиши s
29. комбинация клавиш LeftCtrl+RightShift+F11; блокирование ввода клавиши z
30. комбинация клавиш LeftAlt+RightCtrl+F9; блокирование ввода клавиши x
31. комбинация клавиш LeftAlt+RightShift+F10; блокирование ввода клавиши c
32. комбинация клавиш LeftAlt+RightCtrl+F8; блокирование ввода клавиши v
33. комбинация клавиш LeftAlt+RightShift+F5; блокирование ввода клавиши b
34. комбинация клавиш LeftShift+RightAlt+F4; блокирование ввода клавиши n
35. комбинация клавиш LeftAlt+RightShift+F1; блокирование ввода клавиши m.

Контрольные вопросы:
1.	Опишите функции стандартного ввода DOS связанные с клавиатурой.
2.	Что такое скан-коды?
3.	Каким образом происходит чтение скан-кодов?
4.	Для чего служат однобайтные ASCII-коды?
5.	Для чего служат расширенные ASCII-коды?

Практическая работа № 25-26
 «Изучение особенностей функционирования видеосистемы»
Цель работы: изучение особенностей функционирования видеосистемы в текстовом режиме и получение практических навыков работы с видеомонитором в этом режиме. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
В абсолютном большинстве применений ПЭВМ комплектуются видеоадаптером одного из четырех основных типов: 
•	MDA (Monochrome Display Adapter - монохромный дисплейный адаптер) - текстовый режим: 80 колонок x 25 строк, разрешающая способность 720 x 348 точек, матрица символа 7 x 9, черно-белый; 
•	CGA (Color Graphics Adapter - цветной графический адаптер) - текстовый режим: 80 колонок x 25 строк, матрица символа 8 x 8, 16 цветов; графический режим: 640 x 200 точек - монохромный или 320 x 200 точек - 4 цвета из 16-цветовой палитры; 
•	EGA (Enhanched Graphics Adapter - улучшенный графический адаптер) - текстовый режим: 80 колонок x 25 строк, матрица символа 8 x 14, 16 цветов из 64-цветовой палитры; графический режим: 640 x 350 точек, 16 цветов из 64-цветовой палитры; 
•	VGA (Video Graphics Array - видеографический массив) - текстовый режим: 80 колонок x 25 строк, матрица символа 8 x 14, 16 цветов из 4096-цветовой палитры; графический режим: 640 x 480 точек при 16 цветах из 4096 и 320 x 200 при 256 цветах. 
Видеоадаптер обслуживается прерыванием BIOS 0x10. Начиная с модели EGA обработчик этого прерывания располагается не в основном ПЗУ BIOS, а в расширении ПЗУ по адресу C000:0000. Это прерывание и в исходном своем варианте имело много функций, а для новых типов адаптеров список его функций расширяется. 
Видеоадаптер может функционировать в одном из нескольких режимов, отличающихся возможностями текст/графика и разрешающей способностью. 
В текстовом режиме видеопамять для CGA, EGA, VGA начинается с адреса B800:0000 (для MDA - B000:0000). Видеопамять физически расположена на плате адаптера, но ее адреса входят в общее адресное пространство ПЭВМ и доступны программисту для чтения и для записи. Каждое знакоместо экрана в видеопамяти представлено двумя байтами, первый из которых содержит ASCIIZ-код символа, а второй - цветовой атрибут в формате: H R G B h r g b, где h, r, g, b - соответственно яркостная, красная, зеленая, синяя составляющие цвета символа; R, G, B - красная, зеленая, синяя составляющие цвета фона; H - атрибут мигания (или яркостная составляющая цвета фона). Таким образом, комбинация hrgb определяет номер одного из 16 возможных цветов отображения символов. 
Пример: 
Применяя прямую запись в видеопамять получить на экране оригинальный, желательно динамический видеоэффект. 
Программа состоит из основной функции main() и семи вспомогательных функций. 
byte GetSym(x1,y1) - функция читает символ с заданной позиции экрана дисплея. 
byte GetAtr(x1,y1) - функция читает атрибут символа с заданной позиции экрана дисплея. 
void PutSym(x1,y1,sym) - функция выводит на экран дисплея символ в заданную позицию (x1,y1). 
void PutAtr(x1,y1,atr) - функция меняет на экране дисплея атрибут символа в заданной позиции (x1,y1). 
void Invert(x1,y1) - функция инвертирует участок на экране размером (10х5), координаты (x1,y1) задают один из участков на экране. 
void Change(x,y) - функция обменивает содержимое текущего участка с содержимым левого верхнего участка на экране. Координаты (x,y) задают положение текущего участка. 
void RandText(void) - функция псевдослучайным образом перетасовывает все участки на экране. 
Переменные, глобальные для всей программы: 
xk - координата X текущего участка; 
yk - координата Y текущего участка; 
Координаты участка задаются в пределах: X - [0..7], Y - [0..4] . 
Основная функция main() проверяет, был ли в командной строке дополнительный параметр. Если нет, программа не очищает старый экран. Если какой-нибудь параметр был, то экран очищается и выводится инструкция по управлению программой. Далее в основной программе выполняется бесконечный цикл, в котором обрабатываются коды нажатых клавиш и, в зависимости от них, вызываются вспомогательные функции. Выход из цикла - по клавише Esc. 
Функции GetSym(x1,y1), GetAtr(x1,y1) читают непосредственно из видеопамяти дисплея символ и атрибут соответственно. 
Функции PutSym(x1,y1,sym), PutAtr(x1,y1,atr) выводят непосредственно в видеопамять дисплея символ и атрибут соответственно. Во всех этих четырех функциях координаты задаются в квадрате 79х24 символов (нумерация начинается с нуля). 
Функция Invert(x1,y1) использует функции GetAtr и PutAtr для инверсии прямоугольника экрана размером 10х5 по маске 0x7f, при этом независимо выполняется инверсия фона и цвета символа. Функция Change(x,y) обменивает содержимое текущего участка с содержимым левого верхнего участка путем последовательного побайтного обмену атрибутов и символов. Она использует функции GetSym, GetAtr, PutSym, PutAtr. 
Функция RandText(void) - псевдослучайным образом перетасовывает все участки на экране, при этом она в цикле увеличивает на единицу локальные в данной функции координаты текущего участка xk, yk и обращается к функции Change. Таким образом достигается эффект перемешивания. Функция работает, пока не будет нажата любая клавиша. 
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
/*-----------------------Константы----------------------- */
#define VSEG 0xb800       /* Сегментный адрес видеопамяти */
#define byte unsigned char
#define word unsigned int
#define Esc 27
#define Spase 32
#define Enter 13
#define Up 0x48
#define Down 0x50
#define Left 0x4b
#define Right 0x4d
#define Home 0x47
int xk,yk;
/*--------------Чтение символа из видеопамяти-------------*/
byte GetSym(x1,y1)
int x1,y1;
{
 return(peekb(VSEG,y1*160+x1*2));
}
/*-------------Чтение атрибута из видеопамяти-------------*/
byte GetAtr(x1,y1)
int x1,y1;
{
 return(peekb(VSEG,y1*160+x1*2+1));
}
/*-------------Запись символа в видеопамять--------------*/
void PutSym(x1,y1,sym)
int x1,y1;
byte sym;
{
 pokeb(VSEG,y1*160+x1*2,sym);
}
/*-------------Запись атрибута в видеопамять--------------*/
void PutAtr(x1,y1,atr)
int x1,y1;
byte atr;
{
 pokeb(VSEG,y1*160+x1*2+1,atr);
}
/*------------Инверсия квадрата на экране-----------------*/
void Invert(x1,y1)
int x1,y1;
{
 byte b;
 int i,j;
 for (j=0;j<5;j++)
  for (i=0;i<10;i++)
   {
    b=GetAtr(x1*10+i,y1*5+j);
    PutAtr(x1*10+i,y1*5+j,(b^0x7f));
   }
}
/*-------Замена текущего квадрата на левый верхний--------*/
void Change(x,y)
byte x,y;
{
 int i,j;
 byte ba,bs;
 if ((x!=0)||(y!=0))
  for (j=0;j<5;j++)
   for (i=0;i<10;i++)
    {
     bs=GetSym(x*10+i,y*5+j);
     ba=GetAtr(x*10+i,y*5+j);
     PutSym(x*10+i,y*5+j,GetSym(i,j));
     PutAtr(x*10+i,y*5+j,GetAtr(i,j));
     PutSym(i,j,bs);
     PutAtr(i,j,ba);
    }
}
/*------Перемешивание квадратов до нажатия клавиши------*/
void RandText(void)
{
 Invert(xk,yk);
 xk=5;
 yk=1;
 while(!kbhit())
 {
  Change(xk,yk);
  xk++;
  if (xk>7) xk=0;
  yk++;
  if (yk>4) yk=0;
 }
 Invert(xk,yk);
}
/*----------------Начало основной программы---------------*/
main(int argn,char *argc[])
{
int i;
xk=0;
yk=0;
if (argn>1){}
else            /* Если параметров нет, вывод инструкции */
{
textattr(10);
clrscr();
cprintf("---------------");
cprintf("              Практическая работа N7              ");
cprintf("---------------");
cprintf("---------------");
cprintf("            Управление видеоадаптером.            ");
cprintf("---------------");
textattr(15);
gotoxy(23,4);cprintf("Демонстрация работы с видеопамятью.");
textattr(12);
gotoxy(30,6);cprintf("<< М О З А И К А >>");
textattr(14);
gotoxy(30,8);cprintf("Клавиши управления:");
gotoxy(7,10);cprintf("< Left, Right, Up, Down> - ");

             cprintf("управление выделенним квадратом.");
gotoxy(7,11);cprintf("<Spase Bar> - Обмен содержимым ");
             cprintf("между выделенным квадратом");
gotoxy(7,12);cprintf("           и левым верхним");
             cprintf(" квадратом.");
gotoxy(7,13);cprintf("<Enter> - перемешивание квадратов");
             cprintf(" до нажатия любой клавиши.");
gotoxy(7,14);cprintf("<Esc> - вихiд.");
textattr(11);
gotoxy(28,16);cprintf("З А Д А Ч А   И Г Р И :");
gotoxy(14,17);cprintf("Собрать при помощи клавиш ");
              cprintf("управления начальный экран.");
textattr(12);
gotoxy(27,19);cprintf("Ж е л а е м  у с п е х а !");
textattr(7);
gotoxy(1,21);cprintf("Примечание: При запуске с ");
              cprintf("параметром <->");
gotoxy(13,22);cprintf("начальным экраном для игри ");
              cprintf("является текущий.");
}
Invert(xk,yk);
 for(i=0;i==0;)
  switch(getch())
  { /* Обработка нажатых клавиш */
   case Esc:  i++; break;
   case Enter:RandText();break;
   case Spase:Invert(xk,yk);
              Change(xk,yk);
              Invert(xk,yk);
              break;
   case 0:
      switch (getch()) {
         case  Left:Invert(xk,yk);
                    xk--;
                    if(xk<0) xk=7;
                    Invert(xk,yk);
                    break;
         case Right:Invert(xk,yk);
                    xk++;
                    if(xk>7) xk=0;
                    Invert(xk,yk);
                    break;
         case    Up:Invert(xk,yk);
                    yk--;
                    if(yk<0) yk=4;
                    Invert(xk,yk);
                    break;
         case  Down:Invert(xk,yk);
                    yk++;
                    if(yk>4) yk=0;
                    Invert(xk,yk);
                    break;
          }
  }
Invert(xk,yk);
}

Задания для практической работы:
Применяя прямую запись в видеопамять получить на экране оригинальный, желательно динамический видеоэффект. Возможны такие варианты видеоэффектов: 
"теннисный мячик" - шарик, который летает по экрану и отражается от рамок и границ экраны; 
"сухой лист" - опадание букв с экрана; 
"жук-пожиратель" - фигурка, которая перемещается по экрану по случайной траектории и "съедает" буквы; 
"удав" - то же, что и "жук", но к тому же он увеличивается в размерах, по мере "поедания" букв.
Контрольные вопросы:
    1. Назовите и опишите основные типы видеоадаптеров.
    2. Что такое разрешающая способность?
    3. Назовите характеристики видеопамяти.
    4. Как считается адрес видеопамяти.
    5. Расшифруйте HRGBhrgb?

Практическая работа № 27-28
 «Изучение принципов управления программами в MS DOS»
Цель работы: изучение принципов управления программами в MS DOS и приобретение практических навыков работы с префиксом программного сегмента и его полями. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
Для программы, вызванной на выполнение, DOS выделяет блок памяти, называемый программным сегментом. Программный сегмент всегда начинается на границе параграфа. В начале программного сегмента DOS строит PSP (Program Segment Prefix - Префикс Программного Сегмента), который занимает 256 байт.
Директива ORG 100h, которой часто начинаются Ассемблерные программы, как раз учитывает смещение начала программы относительно начала программного сегмента, равное длине PSP. При передаче программе управления сегментный адрес PSP находится в регистрах DS и ES.
При загрузке программы DOS, кроме программного сегмента, создает для нее еще и сегмент окружения (environment). Сегмент окружения содержит ASCIIZ-строки, задающие значения некоторых глобальных переменных, эти значения могут устанавливаться командой DOS SET, они доступны командным файлам и - через PSP - программам. Набор строк окружения заканчивается пустой ASCIIZ-строкой (нулем). В DOS 3.0 и выше за ним следует еще 2-байтное число строк вызова (обычно 1) и далее - строка (или строки) вызова программы. Обычно в первую (до строк вызова) часть порождаемой программы копируется содержимое окружения программы-родителя. Программа имеет доступ к своему сегменту окружения через поле env_seg PSP, содержащее сегментный адрес окружения.
Поле JFT (Job File Table - Таблица Файлов Задачи) представляет собой массив из 20 однобайтных элементов. При открытии программой файла DOS формирует для него блок-описатель в системной таблице файлов и помещает ссылку на него (его номер) в свободный элемент JFT. Дескриптор файла, возвращаемый программе DOS при открытии файла, является номером элемента в JFT. При запуске программы первые пять элементов создаваемой для нее JFT содержат ссылки на системные файлы, остальные свободны - содержат код 0xFF.
COM-файл - это обязательно программа размером не более 64 Кбайт. Все логические сегменты этой программы (код, данные, стек) помещаются в одном физическом сегменте памяти. COM-файл содержит двоичный код-образ программы, абсолютно идентичный тому образу, который программа будет иметь в оперативной памяти. При загрузке COM-программы система размещает ее сразу вслед за ее PSP (поэтому COM-программа должна начинаться директивой ORG 100h), во все сегментные регистры записывает адрес PSP, и содержимое этих регистров не меняется в ходе выполнения программы.
EXE-программа может иметь любой размер и состоять из нескольких сегментов. Содержимое сегментных регистров меняется в ходе выполнения - в них, как правило, заносятся адреса логических сегментов программы. Поскольку во время трансляции и компоновки не известно, какие абсолютные значения будут иметь эти адреса при размещении программы в памяти, в коде программы эти адреса представляются в виде смещений сегментов относительно начала программы с тем, чтобы при загрузке выполнить настройку обращений к началам сегментов на конкретные адреса памяти. Именно в связи с необходимостью настройки EXE-файл имеет более сложный формат. В начале файла расположен заголовок, который состоит из форматированной части и таблицы перемещений, далее следует сам образ программы.
Пример: 
Разработать программу, производящую форматный вывод на печать своего префикса программного сегмента. 
Программа состоит из основной программы и двух функций: 
void get_DOS_version_h(void) - функция, возвращающая в глобальной переменной dos_ver старшее число номера версии DOS. 
void addr_PSP (void) - функция, получающая сегментный адрес префикса программного сегмента программы и возвращающая его в глобальной переменной pid. 
Переменные, глобальные для всей программы: 
p_psp - указатель на структуру struct PSP, 
pid - сегментный адрес PSP; 
dos_ver - старшее число номера версии DOS; 
i - вспомогательная переменная, используемая для просмотра таблицы файлов задачи (JFT), которая представляет собой массив из 20 элементов (хотя возможно, что их число отлично от 20, поэтому размер массива определим из поля JFT_size); 
l - переменная, используемая для вывода содержимого сегмента окружения DOS и определения числа строк вызова (для версии DOS 3.0 и выше); 
s - переменная, которая вначале используется как указатель на таблицу файлов задачи, затем на строки сегмента окружения и строки вызова; 
rr - переменная, которая используется для задания значений регистров общего назначения при вызове прерывания. 
Данная программа производит распечатку основных полей своего PSP. Для этого префикс программного сегмента представим в виде следующей структуры: 
       struct psp
       {        /* ФОРМАТ PSP */
         byte ret_op[2];       /* команда INT 20h                */
         word end_of_mem;      /* вершина доступной памяти       */
         byte reserved1;
         byte old_call_dos[5]; /* старый вызов DOS               */
         void *term_ptr;       /* адрес завершения               */
         void *ctrlbrk_ptr;    /* адрес обработчика Ctrl+Break   */
         void *criterr_ptr;    /* адрес обработчика крит.ошибок  */
         word father_psp;      /* PID родителя                   */
         byte JFT[20];         /* таблица файлов программы       */
         word env_seg;         /* адрес окружения                */
         void *stack_ptr;      /* адрес стека                    */
         word JFT_size;        /* размер таблицы файлов          */
         byte *JFT_ptr;        /* адрес таблицы файлов           */
         byte reserved2[24];
         byte new_call_dos[3]; /* новый вызов DOS                */
         } *p_psp;
Поле ret_op используется для возможного завершения программы по команде RET 0, поле old_call_dos, содержит команду вызова диспетчера функций DOS. Обращение к этому полю в программе может использоваться вместо команды INT 21h, но в современных версиях DOS для этих целей лучше обращаться к полю new_call_dos. 
Поле end_of_mem содержит сегментный адрес конца доступной памяти в системе. В три поля: term_ptr, ctrlbrk_ptr, criterr_ptr DOS при загрузке программы копирует содержимое векторов прерываний: 22h, 23h, 24, представляющее собой адреса обработчиков: завершения программы, комбинации клавиш Ctrl+Break, критической ошибки - соответственно. Предполагается, что программа может свободно перенаправить эти векторы на собственные обработчики соответствующих ситуаций, но от забот по восстановлению векторов программа избавляется, так как при ее завершении DOS сама восстанавливает векторы из соответствующих полей PSP завершаемой программы. Для аналогичных целей предназначено и поле stack_ptr - в нем сохраняется (а при завершении - из него восстанавливается) адрес стека, использовавшегося до вызова программы. Поле, именуемое father_psp, содержит сегментный адрес PSP родителя - программы, запустившей данную программу, обычно родителем является COMMAND.COM. 
При загрузке программы DOS, кроме программного сегмента, создает для нее еще и сегмент окружения. Сегмент окружения содержит ASCIIZ-строки, задающие значения некоторых глобальных переменных, эти значения могут устанавливаться командой DOS SET, они доступны командным файлам и - через PSP - программам. Набор строк окружения заканчивается пустой ASCIIZ-строкой (нулем). В DOS 3.0 и выше за ним следует еще 2-байтное число строк вызова (обычно 1) и далее - строка (или строки) вызова программы. Обычно в первую (до строк вызова) часть порождаемой программы копируется содержимое окружения программы-родителя. Программа имеет доступ к своему сегменту окружения через поле env_seg PSP, содержащее сегментный адрес окружения. 
	Поле JFT (Job File Table - Таблица Файлов Задачи) представляет собой массив из 20 однобайтных элементов. При открытии программой файла DOS формирует для него блок-описатль в системной таблице файлов и помещает ссылку на него (его номер) в свободный элемент JFT. Дескриптор файла, возвращаемый программе DOS при открытии файла, является номером элемента в JFT. При запуске программы первые пять элементов создаваемой для нее JFT содержат ссылки на системные файлы, остальные свободны. При обработке JFT DOS использует не прямое обращение к полю JFT PSP, а косвенное - через поле JFT_ptr, а в качастве ограничителя размера JFT - не константу 20, а значение поля JFT_size PSP. 
После всего, сказанного выше, не составляет труда написать программу, осуществляющую форматный вывод своего префикса программного сегмента. Для в начале необходимо определить версию DOS (с помощью функции get_DOS_version_h() и получить адрес PSP (с помощью функции addr_PSP()). 
Функция get_DOS_version_h() определяет старшее число номера версии DOS, используя для этого функцию DOS 30h (прерывание 21h), которая возвращает в регистре AL старшее число номера версии, а в регистре AH - младшее число. Нас интересует только значение регистра AL. 
Функция addr_PSP() возвращает сегментный адрес PSP путем использования функции DOS 62h: 
         	Вход:	 AH = 62h
         	Выход:	 BX = сегментный адрес PSP текущего процесса. 
/* Подключение стандартных заголовков */
#include <dos.h>
#include <conio.h>

/* Типы данных */
#define byte unsigned char
#define word unsigned int

/* Описание функций */
void get_DOS_version_h(void);  /* Определение версии DOS  */
void addr_PSP (void);          /* Получение адреса PSP    */
struct psp
{        /* ФОРМАТ PSP */
  byte ret_op[2];       /* команда INT 20h                */
  word end_of_mem;      /* вершина доступной памяти       */
  byte reserved1;
  byte old_call_dos[5]; /* старый вызов DOS               */
  void *term_ptr;       /* адрес завершения               */
  void *ctrlbrk_ptr;    /* адрес обработчика Ctrl+Break   */
  void *criterr_ptr;    /* адрес обработчика крит.ошибок  */
  word father_psp;      /* PID родителя                   */
  byte JFT[20];         /* таблица файлов программы       */
  word env_seg;         /* адрес окружения                */
  void *stack_ptr;      /* адрес стека                    */
  word JFT_size;        /* размер таблицы файлов          */
  byte *JFT_ptr;        /* адрес таблицы файлов           */
  byte reserved2[24];
  byte new_call_dos[3]; /* новый вызов DOS                */
  } *p_psp;

word pid;               /* сегм.адрес PSP                 */
int dos_ver,            /* версия DOS                     */
    i, l, j;
char *s;
union REGS rr;

main()
{
  textbackground(0);
  clrscr();
  textattr(0x0a);
  cprintf("---------------");
  cprintf("             Практическая  работа N10             ");
  cprintf("---------------");
  cprintf("---------------");
  cprintf("              Управление программами              ");
  cprintf("---------------");
  textcolor(11);
  get_DOS_version_h();
  addr_PSP();
  /* распечатка PSP */
  cprintf("\n\n            Адрес PID = %04X\n\n\r",pid);
  p_psp=(struct psp *)MK_FP(pid,0);
  textcolor(10);
  cprintf("Команды:\n\r");
  cprintf("--------\n\r");
  textcolor(14);
  cprintf("     Завершение - int 20h:");
  textcolor(12);
  cprintf("  %02X %02X\n\r",p_psp->ret_op[0],p_psp->ret_op[1]);
  textcolor(14);
  cprintf("     Старый вызов DOS:      ");
  textcolor(12);
  for (i=0;i<5;cprintf("%02X ",p_psp->old_call_dos[i++]));
  textcolor(14);
  cprintf("\n\r     Новый вызов DOS:       ");
  textcolor(12);
  for(i=0;i<3;cprintf("%02X ",p_psp->new_call_dos[i++]));
  textcolor(10);
  cprintf("\n\n\rАдреса:\n\r");
  cprintf("-------\n\r");
  textcolor(14);
  cprintf("    Конец памяти:             ");
  textcolor(12);
  cprintf("%04X:0000\n\r",p_psp->end_of_mem);
  textcolor(14);
  cprintf("    Обработчик завершения:    ");
  textcolor(12);
  cprintf("%Fp\n\r",p_psp->term_ptr);
  textcolor(14);
  cprintf("    Обработчик Ctrl+Break:    ");
  textcolor(12);
  cprintf("%Fp\n\r",p_psp->ctrlbrk_ptr);
  textcolor(14);
  cprintf("    Обработчик критич.ошибки: ");
  textcolor(12);
  cprintf("%Fp\n\r",p_psp->criterr_ptr);
  textcolor(14);
  cprintf("    Стек:                     ");
  textcolor(12);
  cprintf("%Fp\n\n\r",p_psp->stack_ptr);
  textcolor(14);
  cprintf("\n\rРодитель: ");
  textcolor(12);
  cprintf("%04X  ",p_psp->father_psp);
  textcolor(0x8b);
  cprintf("\n\n\rНажмите любую клавишу ...\n\r\7");
  getch();
  clrscr();
  textattr(0x0a);
  cprintf("---------------");
  cprintf("             Практическая  работа N19-20             ");
  cprintf("---------------");
  cprintf("---------------");
  cprintf("              Управление программами              ");
  cprintf("---------------");
  /* Распечатка таблицы файлов */
  s=p_psp->JFT_ptr;
  textcolor(10);
  cprintf("\n\n\rТаблица файлов: ");
  textcolor(12);
  cprintf("%Fp (%d) ",s,p_psp->JFT_size);
  textcolor(11);
  if (s==(byte *)p_psp+0x18)
    cprintf(" -  в этом же PSP");
  cprintf("\n\r");
  for (i=0; ++i<=p_psp->JFT_size; cprintf("%d ",*(s++)));
  textcolor(10);
  cprintf("\n\n\rОкружение DOS: ");
  textcolor(12);
  cprintf("%04X\n\r",p_psp->env_seg);
  s=(char *)MK_FP(p_psp->env_seg,0);
  textcolor(11);
  while(l=strlen(s))
  {
    cprintf("      %s\n\r",s);
    s+=l+1;
  }
  if (dos_ver>2)
  {
    /* для DOS 3.0 и дальше можно получить строку вызова */
    s++;
    l=*((int *)s);
    textcolor(10);
    cprintf("\n\rЧисло строк вызова: ");
    textcolor(12);
    cprintf("%d\n\r",l);
    s+=2;
    textcolor(11);
    for(i=0; i<l; i++)
    {
      cprintf("%s\n\r",s);
      s+=strlen(s)+1;
    }
  }
  textattr(0x8b);
  cprintf("\n\n\n\n\rНажмите любую клавишу ...\7");
  textattr(0x07);
  cprintf("\n\r");
  getch();
  clrscr();
}

/* Определение версии DOS */
void get_DOS_version_h(void)
{
  rr.h.ah=0x30;
  intdos(&rr,&rr);
  dos_ver=rr.h.al;
}

/* Получение адреса PSP */
void addr_PSP (void)
{
  rr.h.ah=0x62;
  intdos(&rr,&rr);
  pid=rr.x.bx;
}
В процессе работы программы на экран быдет выведена следующая информация: 
--------------------------------------------------------------------------------
---------------             Практическая  работа N19-20             ---------------
---------------              Управление программами              ---------------
            Адрес PID = 0BA0
Команды:
--------
     Завершение - int 20h:  CD 20
     Старый вызов DOS:      9A F0 FE 1D F0
     Новый вызов DOS:       CD 21 CB

Адреса:
    Конец памяти:             9FC0:0000
    Обработчик завершения:    0AFA:02B1
    Обработчик Ctrl+Break:    0AFA:014A
    Обработчик критич.ошибки: 0AFA:0155
    Стек:                     0E04:0F94
Родитель: 0AFA
Таблица файлов: 0BA0:0018 (20)  -  в этом же PSP
1 1 1 0 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
Окружение DOS: 0A1E
      CONFIG=STD
      COMSPEC=C:\DOS\COMMAND.COM
      PROMPT=$p$g
      PATH=D:\WIN;C:\;C:\DOS;C:\ARH;C:\NC;C:\BAT;D:\TP;D:\TP7;D:\BC\BIN
      TEMP=d:\~TMP
Число строк вызова: 1
D:\TC\TC_LAB10.EXE

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

Контрольные вопросы:
1.	Что такое программный сегмент?
2.	За что отвечает формат PSP?
3.	Что представляет собой поле JFT?
4.	Что такое СОМ-файл?
5. Что такое поле? Приведите примеры.

Практическая работа № 29-30
 «Использование драйверов DOS»
Цель работы: просмотреть список драйверов, которые в данный момент загружены в конкретной ПЭВМ. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
Драйверы устройств - это специальные программы, которые дополняют систему ввода-вывода MS DOS и обеспечивают обслуживание новых устройств или нестандартное использование имеющихся устройств. Например, с помощью драйверов возможна работа с "электронным диском", т.е. частью памяти компьютера, которая используется аналогично жесткому диску. Драйверы загружаются в память ПК при загрузке ОС, их имена указываются в специальном файле CONFIG.SYS. При загрузке ОС также выполняется командный файл AUTOEXEC.BAT. В файле AUTOEXEC.BAT указываются программы и команды, выполняемые при каждом запуске компьютера. 
Диалог пользователя с MS DOS осуществляется в форме команд. Каждая команда пользователя означает, что MS DOS должна выполнить то или иное действие, например, напечатать файл или выдать на экран оглавление каталога. Команда MS DOS состоит из имени команды и, возможно, параметров, разделенных пробелами. Имя команды MS DOS и параметры могут набираться как прописными, так и строчными латинскими буквами. Ввод каждой команды заканчивается нажатием клавиши [ENTER].
Команды MS DOS бывают двух типов: внутренние и внешние. Внутренние команды выполняются самим командным процессором MS DOS (программой COMMAND.COM). Всего таких команд около 30 (27) .Вот некоторые из них:
CD – Сменить текущий каталог; CLS – Очистить экран монитора; COPY – Копирование файлов; DATE	 – Получить или изменить текущую дату; DEL – Удаление файлов; DIR – Выдать список файлов в каталоге; REN – Изменить имя файла; RD – Удалить каталог; TIME  – Получить или изменить текущее время; TYPE – Просмотр файла; VER – Выдать номер версии MS DOS.
В состав DOS входит драйвер ANSI.SYS, обеспечивающий расширенные средства управления консолью. Чтобы иметь доступ к средствам, предоставляемым этим драйвером, в файл CONFIG.SYS должна быть включена команда: DEVICE=ANSI.SYS Функции драйвера ANSI.SYS вызываются путем вывода в стандартный вывод специально форматированных последовательностей символов. Эти последовательности начинаются символом с кодом 27 (0x1B, 8-ричный код -33), отсюда их название - Esc-последовательности. Второй символ Esc-последовательности "[" - код 91 (0x5B). Последующие символы варьируются. Для вызова ANSI-функций программа может использовать функции символьного вывода DOS или соответствующие средства языков высокого уровня, которые выдают символы на консоль.

Пример:
Определить драйверы, резидентные в памяти компьютера на своем рабочем месте. 
Программа состоит из основной программы main() и девяти вспомогательных функций. 
Функции GetSym, GetAtr, PutSym, 
PutAtr описаны в практической работе N13-14. 
void clrbuf(void) - функция очищает буфер клавиатуры. 
void MemW(x,y,xr,yr) - функция запоминает содержимое области экрана. 
void RmmW(x,y,xr,yr) - функция восстанавливает сохраненную область экрана функцией MemW на экране. 
void ClrW(x1,y1,x2,y2,s,a) - функция очищает прямоугольную область на экране заданным символом и атрибутом. 
void FlDrv(void) - функция выводит в окно подробное описание атрибутов драйвера. 
Переменные глобальные для всей программы: 
xk,yk - координаты курсора; 
cc - переменная для хранения кода нажатой клавиши; 
bm[rx][ry][2] - буфер для запоминания области на экране. 
В программе описана следующая структура заголовка драйвера: 
  struct DR_HEAD {
      struct dr_head *next;     /* адрес следующего */
      word attr;                /* атрибуты */
      word strat_off, intr_off; /* смещения секций */
      char name[8];             /* имя */
  };
Основная программа main() осуществляет просмотор списка драйверов и выводит на экран информацию о них при помощи вспомогательных функций. Функция clrbuf(void) читает символы из буфера клавиатуры, пока он не опустеет. Функция MemW(x,y,xr,yr) использует для обращения к видеопамяти функции GetSym, GetAtr. x, y - координаты левого верхнего угла прямоугольной области экрана. 8xr, yr - размеры области по горизонтали и вертикали соответственно. Функция RmmW(x,y,xr,yr) использует для обращения к видеопамяти функции PutSym, PutAtr. x, y - координаты левого верхнего угла прямоугольной области экрана. xr, yr - размеры области по горизонтали и вертикали соответственно. 
Функция ClrW(x1,y1,x2,y2,s,a) - использует для обращения к видеопамяти функции PutSym, PutAtr. x1, y1, x2, y2 - координаты прямоугольной области. s, a - символ и атрибут соответственно для заполнения области экрана. Функция FlDrv(void) рассматривает побитно байт атрибута драйвера и может выдавать информацию в соответствии со значением атрибута. 
Слово атрибутов драйвера в старшем разряде содержит признак символьного (1) или блочного (0) устройства. Интерпретация остальных разрядов слова атрибутов существенно разная для символьных и блочных устройств. Для символьных устройств слово атрибутов имеет формат: 
         1 e b 0 p 0 0 0  0 l 0 0 c n o i, а для блочных устройств: 
         0 e m 0 p 0 0 0  0 l 0 0 0 0 s 0,
где 	i - драйвер консоли ввода;
 	o - драйвер консоли вывода; 
 	n - драйвер нулевого (пустого) устройства; 
 	c - драйвер часов; 
 	l - драйвер поддерживает функции, введенные в DOS 3.2; 
 	p - драйвер выполняет операции Open/Close;
 	b - драйвер неIBM-овского блочного устройства; 
 	e - драйвер поддерживает функции IOCTL; 
 	s - драйвер поддерживает 32-битную адресацию сектора; 
 	m - драйвер определяет тип диска проверкой 1-го байта FAT.
По правилам DOS кодовая часть драйвера состоит из двух секций, называемых секцией стратегии и секцией обработки прерываний. Поля strat_off и intr_off заголовка содержат смещения этих секций от начала драйвера. Поле name для символьного устройства содержит имя устройства (возможно, дополненное пробелами), а для блочного - первый байт этого поля содержит количество устройств, поддерживаемых драйвером. 
При помощи поля next, заполняемого системой при загрузке драйвера, все драйверы (системные и устанавливаемые) связываются в список. Начало этого списка - заголовок драйвера NUL в CVT. Значение FFFFh в части смещения поля next является признаком конца списка (такой же признак конца используется и в списках, образуемых другими управляющими блоками). Главная программа примера отслеживает этот список. 

#include <dos.h>
#include <conio.h>
/*-----------------------Константы----------------------- */
#define byte unsigned char
#define word unsigned int
#define ATR(x,z) if(drv->attr&x){cprintf("     %s\n\r",z);y++;}
#define DA(x,y) (struct DR_HEAD *)MK_FP(x,y);
#define VSEG 0xb800       /* Сегментный адрес видеопамяти */
#define byte unsigned char
#define word unsigned int
#define Enter 13
#define rx 50
#define ry 13

int xk,yk;
word cc;
byte bm[rx][ry][2];
8struct DR_HEAD { /* заголовок драйвера */
   struct DR_HEAD *next;
   word attr, strat_addr, intr_addr;
   char name[8];
   } *drv;  /* адрес текущего драйвера */
struct DR_HEAD *clock, *con;  /* Адреса CLOCK$ и CON */
union REGS rr;
struct SREGS sr;
int i, y, y1;
/*---------------------------------------------------*/
void clrbuf(void)
{
 while(kbhit()) getch();
}
/*--------------Чтение символа из видеопамяти-------------*/
byte GetSym(x1,y1)
int x1,y1;
{
 return(peekb(VSEG,y1*160+x1*2));
}
/*-------------Чтение атрибута из видеопамяти-------------*/
byte GetAtr(x1,y1)
int x1,y1;
{
 return(peekb(VSEG,y1*160+x1*2+1));
}
/*-------------Запись символа в видеопамять---------------*/
void PutSym(x1,y1,sym)
int x1,y1;
byte sym;
{
 pokeb(VSEG,y1*160+x1*2,sym);
}
/*-------------Запись атрибута в видеопамять--------------*/
void PutAtr(x1,y1,atr)
8int x1,y1;
byte atr;
{
 pokeb(VSEG,y1*160+x1*2+1,atr);
}
/*--------------------------------------------------------*/
void MemW(x,y,xr,yr)
int x,y,xr,yr;
{
 int i,j;
 for (j=y;j<(y+yr);j++)
  for (i=x;i<(x+xr);i++)
   {
    bm[i][j][0]=GetSym(i,j);
    bm[i][j][1]=GetAtr(i,j);
   }
}
/*--------------------------------------------------------*/
void RmmW(x,y,xr,yr)
int x,y,xr,yr;
{
 int i,j;
 for (j=y;j<(y+yr);j++)
  for (i=x;i<(x+xr);i++)
   {
    PutSym(i,j,bm[i][j][0]);
    PutAtr(i,j,bm[i][j][1]);
   }
}
/*--------------------------------------------------------*/
void ClrW(x1,y1,x2,y2,s,a)
int x1,y1,x2,y2;
byte s,a;
{
 int i,j;
 for (j=y1;j<y2;j++)
  for (i=x1;i<x2;i++)
   {
    if (s!=0) PutSym(i,j,s);
    PutAtr(i,j,a);
   }
}
/*--------------------------------------------------------*/
void FlDrv(void)
{
 int xm,ym;
 xm=wherex();
 ym=wherey();
 MemW(0,1,rx,ry);
 ClrW(1,2,rx,ry,0,7);
 ClrW(0,1,rx-2,ry-2,32,16);
 gotoxy(1,2);
 textattr(15+16*3);
 cprintf("***         Атрибуты драйвера %5u          ***",cc);
 gotoxy(1,3);
 textattr(15+16);
 if (drv==clock) cprintf("     активный CLOCK$");
 if (drv==con) cprintf("     активный CON");
 gotoxy(1,4);
 textattr(14+16);
 if (drv->attr&0x8000)
 {
  ATR(1,"консоль ввода")
  ATR(2,"консоль вывода")
  ATR(4,"нулевое устройство")
  ATR(8,"CLOCK$ (часы)")
  ATR(0x2000,"поддерживает OUB")
 }
 else
 {
  ATR(2,"32-байтный адрес сектора")
  ATR(0x2000,"читает media FAT")
 }
 ATR(0x40,"поддерживает функции DOS 3.2")
 ATR(0x800,"поддерживает Open/Close")
 ATR(0x4000,"поддерживает IOCTL")
 gotoxy(1,12);
 textattr(15+16*3+128);
 cprintf("***          Нажмите любую клавишу...        ***");
 if (getch()==0) getch();
 RmmW(0,1,rx,ry);
 gotoxy(xm,ym);
}
/*--------------------------------------------------------*/
main() {
  int c;
  /* получение адреса CVT */
  rr.h.ah=0x52; intdosx(&rr,&rr,&sr);
  /* адрес драйвера часов */
  clock=DA(peek(sr.es,rr.x.bx+10),peek(sr.es,rr.x.bx+8));
  /* адрес драйвера консоли */
  con=DA(peek(sr.es,rr.x.bx+14),peek(sr.es,rr.x.bx+12));
  /* адрес NUL-драйвера */
  textattr(10);
  clrscr();
  cprintf("---------------");
  cprintf("              Практическая работа N21-22             ");
  cprintf("---------------");
  cprintf("---------------");
  cprintf("             Просмотр списка драйверов.           ");
  cprintf("---------------");
  drv=DA(sr.es,rr.x.bx+34);
  textattr(7);
  gotoxy(8,3);
  cprintf("Нажимайте <SpaceBar> ,а для рассмотрения атрибутов",
           "драйвера <Enter>.");
  textattr(12);
  gotoxy(28,4);
  cc=1;
  cprintf("Список драйверов устройств :\n\r");
  while(FP_OFF(drv)!=0xffff) {
    textattr(15);
    cprintf("%5u",cc);
    textattr(14);
    cprintf("  Адрес - %Fp    ",drv);
    textattr(11);
    cprintf(" атрибуты - %04X   ",drv->attr);
    textattr(13);
    if (drv->attr&0x8000)
      for (i=0;i<8; cprintf("%c",drv->name[i++]));
    else cprintf("блочный - %d",drv->name[0]);
      cprintf("\n\r"); y=0;
    while ((c=getch())==Enter)FlDrv();
    if (c==0) getch();
    drv=drv->next; /* адрес след.драйвера */
    cc++;
  }
  textattr(12+128);
  cprintf("Конец списка.     Нажмите любую клавишу...\n");
  getch();
  textattr(7);
}
В процессе работы программы на экран быдет выведена следующая информация о распределении памяти: 
--------------------------------------------------------------------------------
---------------              Практическая работа N21-22             ---------------
---------------             Просмотр списка драйверов.           ---------------
       Нажимайте <SpaceBar> ,а для рассмотрения атрибутов драйвера <Enter>.
                           Список драйверов устройств :
    1  Адрес - 011C:0048     атрибуты - 8004   NUL
    2  Адрес - 02A6:0000     атрибуты - 8000   SETVERXX
    3  Адрес - 025D:0000     атрибуты - A000   XMSXXXX0
    4  Адрес - 0070:0023     атрибуты - 8013   CON
    5  Адрес - 0070:0035     атрибуты - 8000   AUX
    6  Адрес - 0070:0047     атрибуты - A0C0   PRN
    7  Адрес - 0070:0059     атрибуты - 8008   CLOCK$
    8  Адрес - 0070:006B     атрибуты - 08C2   блочный - 4
    9  Адрес - 0070:007B     атрибуты - 8000   COM1
   10  Адрес - 0070:008D     атрибуты - A0C0   LPT1
   11  Адрес - 0070:009F     атрибуты - A0C0   LPT2
   12  Адрес - 0070:00B8     атрибуты - A0C0   LPT3
   13  Адрес - 0070:00CA     атрибуты - 8000   COM2
   14  Адрес - 0070:00DC     атрибуты - 8000   COM3
   15  Адрес - 0070:00EE     атрибуты - 8000   COM4
Конец списка.     Нажмите любую клавишу...

--------------------------------------------------------------------------------

При нажатии клавиши  Enter открывается окно  в котором подробно расписываются атрибуты драйвера.  Например, атрибуты драйвера 8:
------------------------------------------------
------------------------------------------------
----------- Атрибуты драйвера     8 ------------
------------------------------------------------
---- 32-байтный адрес сектора ------------------ 
---- поддерживает функции DOS 3.2 -------------- 
---- поддерживает Open/Close ------------------- 

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

Контрольные вопросы:
1. Что такое драйвер?
2. Какие команды MS DOS существуют?
3. Что обеспечивает драйвер ANSI.SYS?
4. Опишите функции драйвера ANSI.SYS?
5. Что такое виртуальный драйвер?

Практическая работа № 31-32
 «Изучение и распределение памяти в MS DOS»
Цель работы: изучение распределения памяти в операционной системе MS-DOS и получение практических навыков работы с блоками управления памятью. 
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки итестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
по теме практической работы 
После загpузки опеpационной системы оставшаяся часть памяти свободна и pаспpеделяется DOS пpикладным пpогpаммам. Память pаспpеделяется блоками. Размеp блока может быть пpоизвольным, но обязательно кpатен pазмеpу паpагpафа (16 байт). Вообще пpи pаспpеделении памяти объем памяти измеpяется в паpагpафах, и все блоки памяти выpавниваются по гpанице паpагpафа. Блоки создаются и уничтожаются динамически, по меpе поступления запросов на их выделение / освобождение. Каждый блок памяти пpедваpяется Упpавляющим Блоком Памяти (MCB - Memory Control Block). MCB имеет фиксиpованный pазмеp 1 паpагpаф и фоpмат.
Для каждого MCB выводится на экран: 
•	сегментный адрес MCB; 
•	программный иденитфикатор "владельца" блока памяти; 
•	размер блока памяти в параграфах; 
•	класс блока; 
•	символьный идентификатор владельца (только DOS 4.0 и дальше); 
•	строка вызова владельца (DOS 3.0 и выше).
Сегментный адрес, PID владельца и размер блока получаются из MCB очевидным образом. Класс блока определяется по следующим правилам. Если PID владельца блока нулевой, блок является свободным (класс Free). Для занятых блоков класс уточняется. Если PID (сегментный адрес PSP) владельца содержит адрес, лежащий до вершины распределяемой памяти memtop (обычно это число 8), то блок получает класс Dos. Для блоков, не принадлежащих DOS, прежде всего проверяется сегментный адрес владельца. 
Если этот адрес является адресом сегмента, следующего за текущим MCB, это означает, что блок памяти содержит программный сегмент и получает класс Pgm. В противном случае программа "заглядывает" в PSP владельца. Со смещением 0x2C в PSP содержится адрес сегмента окружения; если этот адрес является адресом сегмента, следующего за текущим MCB, то блок получает класс Env. Если класс блока не удается определить ни одним из вышеописанных способов, считаем, что блок содержит данные и помечаем его классом Data.
При обычной работе MS DOS сама занимается распределением памяти и предоставляет пользователю три функции выделения/освобождения памяти: 
•	функция 0x48 - выделение блока памяти (на входе: BX - требуемый размер блока в параграфах, на выходе: AX - сегментный адрес выделенного блока; если памяти для удовлетворения запроса не хватает, BX содержит размер наибольшего свободного блока); 
•	функция 0x49 - освобождение блока (на входе ES - сегментный адрес освобождаемого блока); 
•	функция 0x4A - изменение размера ранее выделенного блока (на входе ES - сегментный адрес, BX - требуемый размер).
Пример: 
Вывести на экран карту распределяемой памяти на своем рабочем месте. 
Структура программы Программа состоит из основной программы и двух функций. 
void get_DOS_ver_h(void) - функция, возвращающая в глобальной переменной dos старшее число номера версии DOS. 
void print_head(void) - функция, которая выводит заголовок практической работы. 
Переменные, глобальные для всей программы: 
cmcb - адрес текущего управляющего блока памяти (MCB); 
emcb - адрес управляющего блока памяти в котором содержится сегмент окружения программы; 
memtop - сегментный адрес начала цепочки управляющих блоков; 
csem - сегментный адрес текущего управляющего блока; 
othersegm - сегментный адрес блока окружения хозяина текущего блока памяти; 
fathersegm - сегментный адрес родителя для программы - хозяина текущего блока; 
0envstr - адрес строки окружения для программы-хозяина текущего блока памяти (используется для определения имени программы из строки вызова); 
envlen - длина очередной строки окружения; 
dos - старшее число номера версии DOS; 
rr и sr - переменные, которые используются для задания значений регистров общего назначения и сегментных регистров, соответственно, при вызове прерывания; 
i - вспомагательная переменная, используемая для получения имени хозяина блока (для версии DOS 4.0 и старше); 
n - число выведенных на экран строк информации о блоках (после вывода информации о каждых 20-ти блоках программа приостанавливает свою работу и ожидает нажатия на любую клавишу); 
s - указатель на имя хозяина блока (если используется версия DOS не ниже 4.0). 
В начале выполнения программы определим номер версии DOS, установленной на данной ПЭВМ. Затем с помощью функции 52h DOS (прерывание 21h) определим адрес системных управляющих блоков: 
     	Вход:   	AH = 52h
     	Выход:  	ES:BX - адрес 1-го блока параметров диска. Вычтя из этого адреса 2, получим адрес того слова памяти в котором DOS хранит сегментный адрес первого управляющего блока памяти. 
Каждый блок памяти пpедваpяется Упpавляющим Блоком Памяти (MCB - Memory Control Block). MCB имеет фиксиpованный pазмеp 1 паpагpаф и фоpмат, описываемый следующей стpуктуpой: 
       struct MCB {
         byte type;        /* тип */
         word owner;       /* владелец */
         word size;        /* размер */
         byte reserved[3]; /* не используется */
         char pgmname[8];  /* имя (только DOS 4.0 и выше) */
         };
Поле type содеpжит код, показывающий, является ли этот MCB последним (код буквы Z - 5Ah.) или непоследним (код буквы M - 4Dh). Поле owner содеpжит PID (сегментный адpес пpефикса пpогpаммного сегмента) пpогpаммы, котоpой данный блок памяти пpинадлежит. Если значение этого поля нулевое, то блок свободен. Поле size содеpжит pазмеp блока памяти в паpагpафах (в это число не включен 1 паpагpаф, занимаемый самим MCB). Следующие 3 байта (поле reserved) заpезеpвиpованы во всех веpсиях. Поле pgmname заpезеpвиpовано (не используется) в веpсиях DOS ниже 4.0. Начиная с веpсии 4.0, в MCB, пpедваpяющем пpогpаммный сегмент, здесь записано имя (без pасшиpения) пpогpаммы, находящейся в этом сегменте (если длина имени меньше 8 символов, оно заканчивается нулевым байтом). 
Все MCB увязаны в цепочку. Получив при помощи функции DOS 52h сегментный адрес начала цепочки MCB csegm, движемся по цепочке. Переход к следующему блоку производится прибавлением к адресу текущего MCB его поля size и еще 1. Перебор заканчивается при достижении MCB со значением 'Z' в поле type. 
Для каждого MCB выводится на экран: 
сегментный адрес MCB; 
программный иденитфикатор "владельца" блока памяти; 
размер блока памяти в параграфах; 
класс блока (см.ниже); 
символьный идентификатор владельца (только DOS 4.0 и старше); 
строка вызова владельца (DOS 3.0 и выше). 
Сегментный адрес, PID владельца и размер блока получаются из MCB. Класс блока определяется по следующим правилам. Если PID владельца блока нулевой, блок является свободным (класс Free). Для занятых блоков класс уточняется. Если PID (сегментный адрес PSP) владельца содержит адрес, лежащий до вершины распределяемой памяти memtop (обычно это число 8), то блок получает класс Dos. Для блоков, не принадлежащих DOS, прежде всего проверяется сегментный адрес владельца. Если этот адрес является адресом сегмента, следующего за текущим MCB, это означает, что блок памяти содержит программный сегмент и получает класс Pgm. В противном случае программа "заглядывает" в PSP владельца. Со смещением 2Ch в PSP содержится адрес сегмента окружения; если этот адрес является адресом сегмента, следующего за текущим MCB, то блок получает класс Env. Если класс блока не удается определить ни одним из вышеописанных способов, считаем, что блок содержит данные и помечаем его классом Data. 
Для DOS 4.0 и выше определяем символьный идентификатор владельца, для этого "заглядываем" в MCB того блока, на который указывает поле owner текущего блока, и выводим его поле pgmname. 
Только DOS 4.0 позволяют получить идентификатор командного процессора DOS вышеописанным образом. Однако, в ранних версиях DOS можно опознать блок памяти, принадлежащий командному процессору вот каким способом. В PSP владельца со смещением 16h находится сегментный адрес "родителя" - программы, запустившей данную программу. Для программ, запущенных из командной строки DOS, родителем является COMMAND.COM. Родителем же COMMAND.COM является он сам. По этому признаку (сам себе родитель) он и может быть опознан. 
Если же программа не является программой DOS, командным процессором или резидентной, загруженной по INSTALL, для нее должна сохраняться строка вызова. Строка вызова находится в сегменте окружения. Программа получает адрес начала блока окружения, пропускает в нем все строки до пустой, после пустой строки пропускает еще 2 байта и получает адрес строки вызова. Из строки вызова мы и определяем имя программы, которой принадлежит текущий блок. 
Функция get_DOS_ver_h() определяет старшее число номера версии DOS, используя для этого функцию 30h DOS (прерывание 21h), которая возвращает в регистре AL старшее число номера версии, а в регистре AH - младшее число. Нас интересует только значение регистра AL. 
Функция print_head() выводит заголовок практической работы (номер практической работы, тема и т.д.). 
/* Типы данных */
#define byte unsigned char
#define word unsigned int

/* Подключение стандартных заголовков */
#include <dos.h>
#include <string.h>

void get_DOS_ver_h(void);  /* получить номер версии DOS   */
void print_head(void);     /* вывод заголовка лаб. работы */

struct MCB { /* блок упpавления памятью */
  char type; /* тип */
  word owner,/* владелец */
       size; /* размер */
  byte reserved[3]; /* зарезервировано */
  char pgmname[8];  /* имя программы, находящейся в данном блоке
                              ( для  DOS 4.0 и выше )         */
   };

struct MCB *cmcb;  /* адpес текущего MCB */
struct MCB *emcb;  /* адpес MCB сpеды */
word memtop;       /* сегм.адрес начала памяти */
word csegm;        /* сегментный адpес текущего MCB */
word othersegm;    /* сегм.адрес другого MCB */
word fathersegm;   /* сегм.адрес родителя */
byte *envstr;      /* адpес стpоки окружения */
int envlen;        /* длина очередной строки окружения */
int envsize;       /* размер блока окружения */
byte dos;          /* номер версии DOS */
union REGS rr;
struct SREGS sr;
int i,n;
char *s;

void main()
{
  print_head();
  n=0; /* Число выведенных на экран блоков */
  get_DOS_ver_h();
  /* получить адрес системных блоков */
  rr.h.ah=0x52;
  intdosx(&rr,&rr,&sr);
  /* получить адрес начала цепочки */
  memtop=csegm=peek(sr.es,rr.x.bx-2);
  do {
    cmcb=(struct MCB *)MK_FP(csegm,0);
    textattr(14);
    cprintf("Addr=%04X:0000  ",csegm);
    textattr(13);
    cprintf("PID=%04X  ",cmcb->owner);
    textattr(11);
    cprintf("Size=%-6u ",cmcb->size*16);
    n++;/* Счетчик выведенных блоков увеличить на 1 */
    if (cmcb->owner==0)
    {
     textattr(15);
     cprintf(" Free"); /* блок свободен */
    }
    else {  /* блок занят */
      /* блок принадлежит DOS ? */
     if (cmcb->owner<memtop)
     {
      textattr(13);
      cprintf(" Dos ");
     }
     else {  /* блок не принадлежит DOS */
        /* если PID хозяина указывает на текущий блок,
        то это программный сегмент */
       if (csegm==cmcb->owner-1)
       {
         textattr(10);
         cprintf(" Pgm   ");}
       else {
          /* адpес блока сpеды для хозяина этого блока памя-
          ти находится в PSP хозяина со смещением 0x2C */
         othersegm=peek(cmcb->owner,0x2c);
          /* адpес родителя для программы-хозяина этого бло-
          ка находится в PSP хозяина со смещением 0x16 */
         fathersegm=peek(cmcb->owner,0x16);
          /* если на текущий блок указывает адрес окружения
            хозяина, то это блок окружения */
         if (csegm==othersegm-1)
         {
          textattr(11);
          cprintf(" Env   ");
         }
          /* иначе - это блок данных */
         else
         {
          textattr(14);
          cprintf(" Data  ");
         }
       }
        /* если хозяин сам себе родитель, то это COMMAND */
          if (cmcb->owner==fathersegm)
          {
            textattr(13);
            cprintf("COMMAND.COM");
          }
          else { /* для другой программы узнаем ее имя */
           textattr(15);
           if (dos>3) {
            emcb=(struct MCB *)MK_FP(cmcb->owner-1,0);
            for (i=0,s=emcb->pgmname; i<8; i++)
            {
             if (*s>0)
               printf("%c",*(s++));
             else
              printf(" ");
            }
            printf(" ");
           }
           if (dos>2)
           {
            /* для DOS 3.0 и выше имя - из строки вызова */
            emcb=(struct MCB *)MK_FP(othersegm-1,0);
            envsize=emcb->size*16; /*размер окружения */
            envstr=(char *)MK_FP(othersegm,0);
            do
             {
              /* пропуск строк окружения до пустой строки */
              envlen=strlen(envstr)+1;
              envstr+=envlen; envsize-=envlen;
             }
            while ((envlen>1)&&(envsize>0));
            envstr+=2;
            envsize-=2; /* 2 байта - кол.строк */
            /* envstr - указатель на строку вызова */
                if (envsize>0)
                {
                 textattr(11);
                 cprintf("%s",envstr);}}}}}
    printf("\n");
    csegm+=(cmcb->size+1); /* переход к следующему блоку */
    if(n==20)
    { /* Весь экран занят информацией о блоках */
     textattr(0x0a);
     gotoxy(30,24);
     cprintf("Нажмите любую клавишу");
     getch();
     n=0;
     print_head();}}
  while (cmcb->type!='Z');  /* выход по последн.блоку */
  textattr(0x0a);
  gotoxy(20,24);
  cprintf("Нажмите любую клавишу для возврата в DOS.");
  textattr(0x07);
  getch();
  clrscr();}
/* получить номер версии DOS */
void get_DOS_ver_h(void)
{
  rr.h.ah=0x30;
  intdos(&rr,&rr);
  dos=rr.h.al;}
/* вывод заголовка лаб. работы */
void print_head(void)
{
  textbackground(0);
  clrscr();
  textattr(0x0a);
  cprintf("---------------");
  cprintf("              Практическая работа N23-24              ");
  cprintf("---------------");
  cprintf("---------------");
  cprintf("                 Управление памятью               ");
  cprintf("---------------");
  gotoxy(28,3);
  textattr(13);
  cprintf("Карта распределяемой памяти.\n\r");}
В процессе работы программы на экран быдет выведена следующая информация о распределении памяти: 
---------------                 Управление памятью               ---------------
                           Карта распределяемой памяти.
Addr=025B:0000  PID=0008  Size=9130    Dos
Addr=0496:0000  PID=0008  Size=64      Dos
Addr=049B:0000  PID=04A0  Size=48      Data  COMMAND.COM
Addr=049F:0000  PID=04A0  Size=2640    Pgm   COMMAND.COM
Addr=0545:0000  PID=04A0  Size=64      Data  COMMAND.COM
Addr=054A:0000  PID=04A0  Size=513     Env   COMMAND.COM
Addr=056B:0000  PID=0008  Size=32      Dos
Addr=056E:0000  PID=06CB  Size=160     Env   NC       C:\NC\NC.EXE
Addr=0579:0000  PID=057A  Size=5376    Pgm   KEYRUS   C:\NC\NC.EXE
Addr=06CA:0000  PID=06CB  Size=13152   Pgm   NC       C:\NC\NC.EXE
Addr=0A01:0000  PID=0AFA  Size=160     Data  COMMAND.COM
Addr=0A0C:0000  PID=0AFA  Size=256     Env   COMMAND.COM
Addr=0A1D:0000  PID=0BA0  Size=160     Env   TC_LAB12 D:\TC\TC_LAB12.EXE
Addr=0A28:0000  PID=0000  Size=2464    Free
Addr=0AC3:0000  PID=0ACF  Size=160     Env   COPY_ECR D:\TC\COPY_ECR.COM
Addr=0ACE:0000  PID=0ACF  Size=672     Pgm   COPY_ECR D:\TC\COPY_ECR.COM
Addr=0AF9:0000  PID=0AFA  Size=2640    Pgm   COMMAND  D:\TC\COPY_ECR.COM
Addr=0B9F:0000  PID=0BA0  Size=17408   Pgm   TC_LAB12 D:\TC\COPY_ECR.COM
Addr=0FE0:0000  PID=0000  Size=65008   Free
Задания для практической работы:
Вывести на экран карту распределяемой памяти на своем рабочем месте. 
Контрольные вопросы:
1.	Что такое параграф?                       
2.	Каково назначение MCB?                                      
3.	По каким правилам определяется класс блока?
4.	Что такое сегмент окружения? 
5.	 Каков размер блока?

Практическая работа 33-35
«Использование динамических библиотек для создания приложений»
Образовательные результаты:
Студент должен 
уметь: 
- осуществлять разработку кода программного модуля на современных языках программирования;
- создавать программу по разработанному алгоритму как отдельный модуль;
-  выполнять отладку и тестирование программы на уровне модуля
знать: 
- основные этапы разработки программного обеспечения;
- основные принципы технологии структурного и объектно-ориентированного программирования;
- основные принципы отладки и тестирования программных продуктов.

Краткие теоретические и учебно-методические материалы 
Для создания динамической библиотеки в Delphi имеется специальный шаблон. Его значок DLL Wizard расположен на странице New окна создания нового проекта. В отличие от проекта обычного приложения, проект DLL состоит всего из одного исходного файла. Впоследствии к нему можно добавлять отдельные модули и формы.
Для определения типа проекта используется ключевое слово library(вместо program в обычном проекте). При компиляции такого проекта динамической библиотеки создается файл с расширением dll.
Как и в любом другом проекте, в проекте динамической библиотеки можно использовать иные модули. Это могут быть просто модули с исходным кодом и модули форм. При этом динамическая библиотека может экспортировать функции, описанные не только в главном файле, но и в присоединенных модулях.
Блок begin..endназывается блоком инициализации библиотеки и предназначен для размещения кода, который автоматически выполняется при загрузке DLL.
Между секцией usesи блоком инициализации можно располагать исходный код функций динамической библиотеки и их объявления. При этом можно использовать любые конструкции языка Object Pascal, а также применять формы и компоненты.
Примечание
При создании динамических библиотек очень удобно использовать группы проектов. В группу помещается проект приложения и проект (проекты) необходимой для его работы динамической библиотеки (библиотек). Для переключения между проектами удобно использовать Диспетчер проектов (команда Project Manager из меню View). Его можно поместить в окно Редактора кода.
Еще один способ удобной работы с проектами динамических библиотек заключается в задании для DLL вызывающей программы. Это делается в диалоге команды Parameters из меню Run. Вызывающее приложение задается в группе Host Application. В результате после компиляции динамической библиотеки вызывается использующее ее приложение.
Для того чтобы приложения могли применять функции динамической библиотеки, необходимо, во-первых, экспортировать их из DLL; во-вторых, объявить функции в самом приложении как внешние. Ниже рассматриваются способы решения этих задач.
Экспорт из dll
Для создания перечня экспортируемых из динамической библиотеки процедур и функций используется ключевое слово exports. При этом можно указывать как функции, описанные в главном файле DLL, так и функции из присоединенных модулей.
В качестве примера рассмотрим исходный код динамической библиотеки DataCheck, простейшие функции которой проверяют введенную строку перед конвертацией на соответствие одному из типов данных.
library DataChek;
uses
Windows, SysUtils, Classes, Messages, Forms, Dialogs, StdCtrls, ComCtrls;
function ValidDate(AText: String): Integer;
begin
try
Result := 0;
StrToDate(AText);
except
on E:EConvertError do
Result := -1;
end;
end;
function ValidTime(AText: String): Integer;
begin
try
Result := 0;
StrToTime(AText);
except
on E:EConvertError do
Result := -1;
end;
end;
function Validlnt(AText: String): Integer;
begin
try
Result := 0;
StrToInt(AText);
except
on E:EConvertError do
Result := -1;
end;
end;
exports
Validlnt,
ValidDate index 1,
ValidTime index 2 name 'IsValidTime';
begin
if Length(DateToStr(Date)) < 10 then
ShowMessage('Год представлен двумя цифрами');
end.
Итак, три функции этой библиотеки обеспечивают проверку строки перед преобразованием ее в целое число, дату или время. Для обеспечения экспорта этих функций их необходимо объявить в секции exports.
При компиляции библиотеки адрес, имя и порядковый номер экспортируемой функции добавляется к специальной таблице экспорта в файле DLL.
Имена процедур и функций в секции экспорта разделяются запятыми. Внимательный взгляд на пример экспорта в листинге 1 обнаруживает три различных варианта объявления.
В первом варианте компилятор самостоятельно определяет положение функции в таблице экспорта.
При использовании ключевого слова index следующее за ним число задает положение функции в таблице экспорта относительно других таких же функций.
Ключевое слово name позволяет экспортировать функцию под другим именем.
При объявлении процедур и функций в динамических библиотеках используются различные соглашения о вызовах. Дело в том, что различные языки программирования по-разному реализуют передачу параметров в процедуру (через стек или регистры). Порядок следования параметров в стеке как раз определяется соглашением о вызовах.
Стандартный вызов в языках C++ и Object Pascal различается, но набор директив смены типа вызова позволяет обеспечить любую реализацию.
Во всех соглашениях о вызовах вызывающая процедура помещает параметры в стек. В зависимости от типа соглашения, очистка стека осуществляется вызывающей или вызываемой процедурой.
Если очистка стека выполняется вызывающей процедурой, то она успевает забрать из него возвращаемые значения.
Если очистка стека осуществляется вызываемой процедурой, то перед этим она помещает возвращаемые значения во временную область памяти.
+Помимо рассмотренных ниже директив имеются еще три типа вызовов, которые не используются и сохранены для обеспечения обратной совместимости. Это директивы near, far, export.
Инициализация и завершение работы dll
При загрузке динамической библиотеки выполняется код инициализации, который расположен в блоке begin, .end. Обычно здесь выполняются операции по заданию начальных значений используемых в функциях библиотеки переменных, проверка условий функционирования DLL, создание необходимых структур и объектов и т. д.
При загрузке динамической библиотеки в адресное пространство вызывавшего ее процесса, происходят важные события, знание которых позволит вам эффективно управлять инициализацией и выгрузкой DLL.
Итак, перед запуском кода инициализации автоматически вызывается встроенная ассемблерная процедура _initDLL (она расположена в модуле system). Она сохраняет состояние регистров процессора; получает значение экземпляра модуля библиотеки и записывает его в глобальную переменную hinstance; устанавливает для глобальной переменой isLibrary значение True (по этому значению вы всегда сможете распознать код DLL); получает из стека ряд параметров; проверяет переменную процедурного типа DLLProc:
var DLLProc: Pointer;
Эта переменная используется для проверки вызовов операционной системой точки входа DLL. С этой переменной можно связать процедуру с одним целочисленным параметром. Такая процедура называется функцией обратного вызова системного уровня.
При завершении работы динамической библиотеки вызывается процедура, на которую указывает адрес, содержащийся в переменной ExitProc:
+var ExitProc: Pointer;
Задание к практической работе
Разработать две динамические библиотеки и приложение их использующие. Каждая из библиотек должна содержать по две функции копирования файлов из практической работы №33-35. Параметры функций: имя файла приемника, имя файла источника. Для вызова функций первой библиотеки использовать явный вызов, для вызова функций второй библиотеки использовать неявный вызов.
Библиотеки должны быть разработаны нас++, приложение, вызывающее их, на VisualC++. (Обратить внимание на формат хранения параметров в стеке.)
Контрольные вопросы
1.Что такое явная функция?
2. В чем отличие явной функции от неявной?
3. Что такое ресурс?
4. Что такое DLL?
5. С помощью какой функции осуществляется вызов?