Цели работы - Северо-Кавказский горно

advertisement
СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ
Методические указания
по выполнению лабораторных работ
для студентов, обучающихся по направлению подготовки
230100.68 – «Информатика и вычислительная техника»
Составители:
А. С. Мирошников, С. А. Караева
Владикавказ 2015
0
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ
Федеральное государственное бюджетное образовательное учреждение
высшего профессионального образования
«СЕВЕРО-КАВКАЗСКИЙ ГОРНО-МЕТАЛЛУРГИЧЕСКИЙ ИНСТИТУТ
(ГОСУДАРСТВЕННЫЙ ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ)»
Кафедра автоматизированной обработки информации
СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ
Методические указания
по выполнению лабораторных работ
для студентов, обучающихся по направлению подготовки
230100.68 – «Информатика и вычислительная техника»
Составители:
А. С. Мирошников, С. А. Караева
Допущено
редакционно-издательским советом
Северо-Кавказского горно-металлургического института
(государственного технологического университета).
Протокол заседания РИСа № 4 от 16.07.2014 г.
Владикавказ 2015
1
УДК 004.42
ББК 32.973
М64
Рецензент:
кандидат технических наук, доцент
Северо-Кавказского горно-металлургического института
(государственного технологического университета)
Будаева А. А.
М64
Системное программирование: Методические указания по
выполнению лабораторных работ для студентов, обучающихся по
направлению подготовки 230100.68 – «Информатика и вычислительная техника» / Сост.: А. С. Мирошников, С. А. Караева; Северо-Кавказский горно-металлургический институт (государственный
технологический университет). – Владикавказ: Северо-Кавказский
горно-металлургический институт (государственный технологический университет). Изд-во «Терек», 2014. – 68 с.
УДК 004.42
ББК 32.973
Редактор: Хадарцева Ф. С.
Компьютерная верстка: Куликова М. П.
 Составление. ФГБОУ ВПО «Северо-Кавказский
горно-металлургический институт
(государственный технологический университет)», 2015
 Мирошников А. С., Караева С. А., составление, 2015
Подписано в печать 24.04.2015. Формат 60х84 1/16. Бумага офсетная.
Гарнитура «Таймс». Печать на ризографе. Усл. п.л. 3,95. Уч.-изд. л. 2,28.
Тираж 15 экз. Заказ №
. Северо-Кавказский горно-металлургический институт
(государственный технологический университет). Издательство «Терек».
Отпечатано в отделе оперативной полиграфии СКГМИ (ГТУ).
362021, г. Владикавказ, ул. Николаева, 44.
2
Оглавление
Лабораторная работа 1
Работа с символьными строками .............................................................
4
Лабораторная работа 2
Представление в памяти массивов и матриц ..........................................
12
Лабораторная работа 3
Структуры и связные списки ....................................................................
24
Лабораторная работа 4
Проверка оборудования ............................................................................
36
Лабораторная работа 5
Windows-окна и сообщения. Создание окна ...........................................
45
Лабораторная работа 6
Оболочка окна Windows ...........................................................................
50
Лабораторная работа 7
Обработка сообщений ...............................................................................
52
Лабораторная работа 8
Графическая подсистема Windows ..........................................................
56
Лабораторная работа 9
Работа с буфером обмена ..........................................................................
62
Лабораторная работа 10
Работа с клавиатурой ................................................................................
64
Список литературы ....................................................................................
68
3
Лабораторная работа 1
РАБОТА С СИМВОЛЬНЫМИ СТРОКАМИ
1. Цель работы
Закрепление практических навыков в работе с массивами и указателями языка C, обеспечении функциональной модульности.
2. Темы для предварительного изучения
 Указатели в языке C.
 Представление строк.
 Функции и передача параметров.
3. Постановка задачи
По индивидуальному заданию создать функцию для обработки
символьных строк. За образец брать библиотечные функции обработки строк языка C, но не применять их в своей функции. Предусмотреть обработку ошибок в задании параметров и особые случаи. Разработать два варианта заданной функции:
– используя традиционную обработку массивов;
– используя адресную арифметику.
4. Варианты индивидуальных заданий
№
Варианты индивидуальных заданий
п/п
1 Функция - Copies(s,s1,n). Назначение - копирование строки s в строку s1
n раз
2 Функция - Words(s). Назначение - подсчет слов в строке s
3 Функция - Concat(s1,s2). Назначение - конкатенация строк s1 и s2
(аналогичная библиотечная функция C - strcat)
4 Функция - Parse(s,t). Назначение - разделение строки s на две части: до
первого вхождения символа t и после него
5 Функция - Center(s1,s2,l). Назначение - центрирование - размещение
строки s1 в середине строки s2 длиной l
6 Функция - Delete(s,n,l). Назначение - удаление из строки s подстроки,
начиная с позиции n, длиной l (аналогичная библиотечная Функция есть
в Pascal)
4
7 Функция - Left(s,l). Назначение - выравнивание строки s по левому краю
до длины l
8 Функция - Right(s,l) Назначение - выравнивание строки s по правому
краю до длины l
9 Функция - Insert(s,s1,n). Назначение - вставка в строку s подстроки s1,
начиная с позиции n (аналогичная библиотечная функция есть в Pascal)
10 Функция - Reverse(s). Назначение - изменение порядка символов в строке s на противоположный
11 Функция - Pos(s,s1). Назначение - поиск первого вхождения подстроки s1
в строку s (аналогичная функция C - strstr)
12 Функция - LastPos(s,s1). Назначение - поиск последнего вхождения
подстроки s1 в строку s
13 Функция - WordIndex(s,n). Назначение - определение позиции начала в
строке s слова с номером n
14 Функция - WordLength(s,n). Назначение - определение длины слова
с номером n в строке s
15 Функция - SubWord(s,n,l). Назначение - выделение из строки sl слов,
начиная со слова с номером n
16 Функция - WordCmp(s1,s2). Назначение - сравнение строк
(с игнорированием множественных пробелов)
17 Функция - StrSpn(s,s1). Назначение - определение длины той части
строки s, которая содержит только символы из строки s1
18 Функция - StrCSpn(s,s1). Назначение - определение длины той части
строки s, которая не содержит символы из строки s1
19 Функция - Overlay(s,s1,n). Назначение - перекрытие части строки s,
начиная с позиции n, строкой s1
20 Функция - Replace(s,s1,s2). Назначение - замена в строке s комбинации
символов s1 на s2
21 Функция - Compress(s,t). Назначение - замена в строке s множественных
вхождений символа t на одно
22 Функция - Trim(s). Назначение - удаление начальных и конечных
пробелов в строке s
23 Функция - StrSet(s,n,l,t). Назначение - установка l символов строки s,
начиная с позиции n, в значение t
23 Функция - Space(s,l). Назначение - доведение строки s до длины l путем
вставки пробелов между словами.
24 Функция - Findwords(s,s1). Назначение - поиск вхождения в строку s
заданной фразы (последовательности слов) s1
25 Функция - StrType(s). Назначение - определение типа строки s
(возможные типы - строка букв, десятичное число, 16-ричное число,
двоичное число и т. д.)
26 Функция - Compul(s1,s2). Назначение - сравнение строк s1 и s2 с игнорированием различий в регистрах
5
27 Функция - Translate(s,s1,s2). Назначение - перевод в строке sсимволов,
которые входят в алфавит s1, в символы, которые входят в алфавит s2
28 Функция - Word(s). Назначение - выделение первого слова из строки s
Примечание: под "словом" везде понимается последовательность символов,
которая не содержит пробелов
5. Индивидуальное задание
 Функция – substr(s,n,l). Назначение – выделение из строки s подстроки, начиная с позиции n, длиной l.
5.1. Описание методов решения
5.1.1. Символьная строка в языке C представляется в памяти как
массив символов, в конце которого находится байт с кодом 0 – признак конца строки. Строку, как и любой другой массив можно обрабатывать либо традиционным методом – как массив, с использованием
операции индексации, либо через указатели, с использованием операций адресной арифметики. При работе со строкой как с массивом
нужно иметь в виду, что длина строки заранее неизвестна, так что
циклы должны быть организованы не со счетчиком, а до появления
признака конца строки.
5.1.2. Функция должна реализовывать поставленную задачу - и
ничего более. Это означает, что функцию можно будет, например, перенести без изменений в любую другую программу, если спецификации функции удовлетворяют условиям задачи. Это также означает,
что при ошибочном задании параметров или при каких-то особых
случаях в их значениях, функция не должна аварийно завершать программу или выводить какие-то сообщения на экран, но должна возвращать какое-то прогнозируемое значение, по которому та функция,
которая вызвала нашу, может сделать вывод об ошибке или об особом
случае.
Определим состав параметров функции:
int substr (src, dest, num, len),
где src –
строка, с которой выбираются символы;
dest –
строка, в которую записываются символы;
num – номер первого символа в строке src, с которого начинается подстрока (нумерация символов ведется с 0);
len –
длина выходной строки.
6
Возможные возвращаемые значения функции установим: 1 (задание параметров правильное) и 0 (задание не правильное). Эти значения при обращениях к функции можно будет интерпретировать как
"истина" или "ложь".
Обозначим через Lsrc длину строки src. Тогда возможны такие
варианты при задании параметров:
 num+len <= Lsrc – полностью правильное задание;
 num+len > Lsrc; num < Lsrc – правильное задание, но длина вы-
ходной строки буде меньше, чем len;
 num >= Lsrc – неправильное задание, выходная строка будет пу-
стой;
 num < 0 или len <= 0 – неправильное задание, выходная строка
будет пустой.
Заметим, что интерпретация конфигурации параметров как правильная/неправильная и выбор реакции на неправильное задание - дело исполнителя. Но исполнитель должен строго выполнять принятые
правила. Возможен также случай, когда выходная строка выйдет
большей длины, чем для нее отведено места в памяти. Однако, поскольку нашей функции неизвестен размер памяти, отведенный для
строки, функция не может распознать и обработать этот случай – так
же ведут себя и библиотечные функции языка C.
5.2. Описание логической структуры
5.2.1. Программа состоит из одного программного модуля – файл
LAB1.C. В состав модуля входят три функции – main, substr_mas и
subs_ptr. Общих переменных в программе нет. Макроконстантой N
определена максимальная длина строки – 80.
5.2.2. Функция main является главной функцией программы, она
предназначена для ввода исходных данных, вызова других функций и
вывода результатов. В функции определены переменные:
o
ss и dd – входная и выходная строки, соответственно;
o
n – номер символа, с которого должна начинаться выходная
строка;
o
l – длина выходной строки.
Функция запрашивает и вводит значение входной строки, номера
символа и длины. Далее функция вызывает функцию substr_mas, пе7
редавая ей как параметры введенные значения. Если функция
substr_mas возвращает 1, выводится на экран входная и выходная
строки, если 0 – выводится сообщение об ошибке и входная строка.
Потом входная строка делается пустой и то же самое выполняется для
функции substr_ptr.
5.2.3. Функция substr_mas выполняет поставленное задание методом массивов. Ее параметры: – src и dest – входная и выходная строки,
соответственно, представленные в виде массивов неопределенного
размера; num и len. Внутренние переменные i и j используются как
индексы в массивах.
Функция проверяет значения параметров в соответствии со случаем 4, если условия этого случая обнаружены, в первый элемент массива dest записывается признак конца строки и функция возвращает 0.
Если случай 4 не выявлен, функция просматривает num первых
символов входной строки. Если при этом будет найден признак конца
строки, это – случай 3, при этом в первый элемент массива dest записывается признак конца строки и функция возвращает 0.
Если признак конца в первых num символах не найден, выполняется цикл, в котором индекс входного массива начинает меняться от 1,
а индекс выходного - от 0. В каждой итерации этого цикла один элемент входного массива пересылается в выходной. Если пересланный
элемент является признаком конца строки (случай 2), то функция немедленно заканчивается, возвращая 1. Если в цикле не встретится конец строки, цикл завершится после len итераций. В этом случае в конец выходной строки записывается признак конца и функция возвращает 1.
5.2.4. Функция substr_ptr выполняет поставленное задание методом указателей. Ее параметры – src и dest – входная и выходная строки, соответственно, представленные в виде указателей на начала
строк; num и len.
Функция проверяет значения параметров в соответствии со случаем 4, если условия этого случая выявлены, по адресу, который задает dest, записывается признак конца строки и функция возвращает 0,
эти действия выполняются одним оператором.
Если случай 4 не обнаружен, функция пропускает num первых
символов входной строки. Это сделано циклом while, условием выхода из которого является уменьшение счетчика num до 0 или появление
8
признака конца входной строки. Важно четко представлять порядок
операций, которые выполняются в этом цикле:
o выбирается счетчик num;
o счетчик num уменьшается на 1;
o если выбранное значение счетчика было 0 – цикл завершается;
o если выбранное значение было не 0 – выбирается символ, на
который указывает указатель src;
o указатель src увеличивается на 1;
o если выбранное значение символа было 0, т. е. признак конца
строки, цикл завершается, иначе – повторяется.
После выхода из цикла проверяется значение счетчика num: если
оно не 0, это означает, что выход из цикла произошел по признаку
конца строки (случай 3), по адресу, который задает dest, записывается
признак конца строки и функция возвращает 0.
Если признак конца не найден, выполняется цикл, подобный первому циклу while, но по счетчику len. В каждой итерации этого цикла
символ, на который показывает src переписывается по адресу, задаваемому dest, после чего оба указателя увеличиваются на 1. Цикл закончится, когда будет переписано len символов или встретится признак
конца строки. В любом варианте завершения цикла по текущему адресу, который содержится в указателе dest, записывается признак конца
строки и функция завершается, возвращая 1.
5.3. Данные для тестирования
Тестирование должно обеспечить проверку работоспособности
функций для всех вариантов входных данных. Входные данные, на
которых должно проводиться тестирование, сведены в таблицу:
Вариант
1
2
3
4
src
012345
012345
012345
012345
012345
012345
012345
012345
012345
012345
num
2
0
0
5
2
0
8
–1
5
5
9
len
2
1
6
3
6
2
2
0
–1
dest
23
0
012345
5
2345
012345
пусто
пусто
пусто
пусто
5.4. Текст программы
/***************************************************************/
/*********************** Файл LAB1.C **************************/
#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;
10
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(); }
11
Лабораторная работа 2
ПРЕДСТАВЛЕНИЕ В ПАМЯТИ МАССИВОВ И МАТРИЦ
1. Цель работы
Получение практических навыков в использовании указателей и
динамических объектов в языке C, создание модульных программ и
обеспечение инкапсуляции.
2. Темы для предварительного изучения
 Указатели в языке C.
 Модульная структура программы.
 Области действия имен.
3. Постановка задачи
Для разряженной матрицы целых чисел в соответствии с индивидуальным заданием создать модуль доступа к ней, в котором обеспечить экономию памяти при размещении данных.
4. Порядок выполнения
Порядок выполнения работы и содержание отчета определены в
общих указаниях.
5. Индивидуальные задания
№ п/п
1.
2.
3.
4
5.
6.
7.
8.
9.
10.
Вид матрицы
Все нулевые элементы размещены в левой части матрицы
Все нулевые элементы размещены в правой части матрицы
Все нулевые элементы размещены выше главной диагонали
Все нулевые элементы размещены в верхней части матрицы
Все нулевые элементы размещены в нижней части матрицы
Все элементы нечетных строк – нулевые
Все элементы четных строк – нулевые
Все элементы нечетных столбцов – нулевые
Все элементы четных столбцов – нулевые
Все нулевые элементы размещены в шахматном порядке,
начиная с 1-го элемента 1-й строки
12
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
Все нулевые элементы размещены в шахматном порядке,
начиная со 2-го элемента 1-й строки
Все нулевые элементы размещены на местах с четными индексами строк и столбцов
Все нулевые элементы размещены на местах с нечетными индексами строк и столбцов
Все нулевые элементы размещены выше главной диагонали на
нечетных строках и ниже главной диагонали – на четных
Все нулевые элементы размещены ниже главной диагонали на
нечетных строках и выше главной диагонали – на четных
Все нулевые элементы размещены на главной диагонали, в
первых 3 строках выше диагонали и в последних 3 строках
ниже диагонали
Все нулевые элементы размещены на главной диагонали и в
верхней половине участка выше диагонали
Все нулевые элементы размещены на главной диагонали и в
нижней половине участка ниже диагонали
Все нулевые элементы размещены в верхней и нижней четвертях матрицы (главная и побочная диагонали делят матрицу на
четверти)
Все нулевые элементы размещены в левой и правой четвертях
матрицы (главная и побочная диагонали делят матрицу на четверти)
Все нулевые элементы размещены в левой и верхней четвертях
матрицы (главная и побочная диагонали делят матрицу на четверти)
Все нулевые элементы размещены на строках, индексы которых кратны 3
Все нулевые элементы размещены на столбцах, индексы которых кратны 3
Все нулевые элементы размещены на строках, индексы которых кратны 4
Все нулевые элементы размещены на столбцах, индексы которых кратны 4
Все нулевые элементы размещены попарно в шахматном порядке (сначала 2 нулевых)
Матрица поделена диагоналями на 4 треугольника, элементы
верхнего и нижнего треугольников нулевые
матрица поделена диагоналями на 4 треугольника, элементы
левого и правого треугольников нулевые
13
29.
30.
Матрица поделена диагоналями на 4 треугольника, элементы
правого и нижнего треугольников нулевые
Все нулевые элементы размещены квадратами 2 х 2 в шахматном порядке
Студенту надлежит выбрать, будут ли начинаться индексы в матрице с 0 или с 1.
6. Пример решения задачи
6.1. Индивидуальное задание
 матрица содержит нули ниже главной диагонали;
 индексация начинается с 0.
6.2. Описание методов решения
6.2.1. Представление в памяти
Экономное использование памяти предусматривает, что для тех
элементов матрицы, в которых наверняка содержатся нули, память
выделяться не будет. Поскольку при этом нарушается двумерная
структура матрицы, она может быть представлена в памяти как одномерный массив, но при обращении к элементам матрицы пользователь
имеет возможность обращаться к элементу по двум индексам.
6.2.2. Модульная структура программного изделия
Программное изделие должно быть отдельным модулем, файл
LAB2.C, в котором должны размещаться как данные (матрица и вспомогательная информация), так и функции, которые обеспечивают доступ. Внешний доступ к программам и данным модуля возможен
только через вызов функций чтения и записи элементов матрицы. Доступные извне элементы программного модуля должны быть описаны
в отдельном файле LAB2.H, который может включаться в программу
пользователя оператором препроцессора:
#include "lab2.h"
Пользователю должен поставляться результат компиляции – файл
LAB2.OBJ и файл LAB2.H.
6.2.3. Преобразование 2-компонентного адреса элемента матрицы, которую задает пользователь, в 1-компонентный должно выполняться отдельной функцией (так называемой функцией линеариза14
ции), вызов которой возможен только из функций модуля. Возможны
три метода преобразования адреса:
 при создании матрицы для нее создается также и дескриптор
D[N] – отдельный массив, каждый элемент которого соответствует одной строке матрицы; дескриптор заполняется значениями, подобранными так, чтобы: n = D[x] + y, где x, y – координаты пользователя (строка, столбец), n – линейная координата;
 линейная координата подсчитывается методом итерации как
сумма полезных длин всех строк, предшествующих строке x, и к
ней прибавляется смещение y-го полезного элемента относительно начала строки;
 для преобразования подбирается единое арифметическое выражение, которое реализует функцию: n = f(x,y).
Первый вариант обеспечивает быстрейший доступ к элементу
матрицы, ибо требует наименьших расчетов при каждом доступе, но
плата за это – дополнительные затраты памяти на дескриптор. Второй
вариант – наихудший по всем показателям – ибо каждый доступ требует выполнения оператора цикла, а это и медленно, и занимает память. Третий вариант может быть компромиссом, он не требует дополнительной памяти и работает быстрее, чем второй. Но выражение
для линеаризации тут будет сложнее, чем первом варианте, следовательно, и вычисляться будет медленнее.
В программном примере, который мы приводим ниже, полностью
реализован именно третий вариант, но далее мы показываем и существенные фрагменты программного кода для реализации и двух других.
6.3. Описание логической структуры
6.3.1. Общие переменные
В файле LAB2.C описаны такие статические переменные:
 int NN – размерность матрицы;
 int SIZE - количество ненулевых элементов в матрице;
 int *m_addr – адрес сжатой матрицы в памяти, начальное значение этой переменной – NULL – признак того, что память не выделена;
 int L2_RESULT – общий флаг ошибки, если после выполнения
любой функции он равен –1, то произошла ошибка.
Переменные SIZE и m_addr описаны вне функций с квалификатором static, это означает, что они доступны для всех функций в этом
модуле, но недоступны для внешних модулей. Переменная
15
L2_RESULT также описана вне всех функций, не без явного квалификатора. Эта переменная доступна не только для этого модуля, но и для
всех внешних модулей, если она в них буде описана с квалификатором extern. Такое описание имеется в файле LAB2.H.
6.3.2. Функция creat_matr
Функция creat_matr предназначена для выделения в динамической памяти места для размещения сжатой матрицы. Прототип функции:
int creat_matr ( int N );
где N – размерность матрицы.
Функция сохраняет значение параметра в собственной статической переменной и подсчитывает необходимый размер памяти для
размещения ненулевых элементов матрицы. Для выделения памяти
используется библиотечная функция C malloc. Функция возвращает –1, если при выделении произошла ошибка, или 0, если выделение
прошло нормально. При этом переменной L2_RESULT также присваивается значение 0 или –1.
6.3.3. Функция close_matr
Функция close_matr предназначена для освобождения памяти при
завершении работы с матрицей, Прототип функции:
int close_matr ( void ).
Функция возвращает 0 при успешном освобождении, –1 – при попытке освободить невыделенную память.
Если адрес матрицы в памяти имеет значения NULL, это признак
того, что память не выделялась, тогда функция возвращает –1, иначе –
освобождает память при помощи библиотечной функции free и записывает адрес матрицы – NULL. Соответственно функция также устанавливает глобальный признак ошибки – L2_RESULT.
6.3.4. Функция read_matr
Функция read_matr предназначена для чтения элемента матрицы.
Прототип функции:
int read_matr(int x, int y).
где x и y - координаты (строка и столбец). Функция возвращает значение соответствующего элемента матрицы. Если после выполнения
функции значение переменной L2_RESULT –1, то это указывает на
ошибку при обращении.
16
Проверка корректности задания координат выполняется обращением к функции ch_coord, если эта последняя возвращает ненулевое
значение, выполнение read_matr на этом и заканчивается. Если же координаты заданы верно, то проверяется попадание заданного элемента
в нулевой или ненулевой участок. Элемент находится в нулевом
участке, если для него номер строки больше, чем номер столбца. Если
элемент в нулевом участке, функция просто возвращает 0, иначе – вызывает функцию линеаризации lin и использует значение, которое
возвращает lin, как индекс в массиве m_addr, по которому и выбирает
то значения, которое возвращается.
6.3.5. Функция write_matr
Функция write_matr предназначена для записи элемента в матрицу. Прототип функции:
int write_matr(int x, int y, int value),
где x и y – координаты (строка и столбец), value – то значение, которое нужно записать. Функция возвращает значение параметра value,
или 0 – если была попытка записи в нулевой участок. Если после выполнения функции значение переменной L2_RESULT –1, то это указывает на ошибку при обращении.
Выполнение функции подобно функции read_matr с тем отличием, что, если координаты указывают на ненулевой участок, то функция записывает value в массив m_addr.
6.3.6. Функция ch_coord
Функция ch_coord предназначена для проверки корректности задания координат. Эта функция описана как static, и поэтому она может
вызываться только из этого же модуля. Прототип функции:
static char ch_coord(int x, int y),
где x и y – координаты (строка и столбец). Функция возвращает 0, если координаты верные, –1 – если неверные. Соответственно, функция
также устанавливает значение глобальной переменной L2_RESULT.
Выполнение функции собственно состоит из проверки трех условий:
 адрес матрицы не должен быть NULL, т. е., матрица должна уже
находиться в памяти;
 ни одна из координат не может быть меньше 0;
 ни одна из координат не может быть больше NN.
Если хотя бы одно из этих условий не выполняется, функция
устанавливает признак ошибки.
17
6.3.7. Функция lin
Функция lin предназначена для преобразования двумерных координат в индекс в одномерном массиве. Эта функция описана как static
и поэтому может вызываться только из этого же модуля. Прототип
функции:
static int lin(int x, int y);
где x и y – координаты (строка и столбец). Функция возвращает координату в массиве m_addr.
Выражение, значение которого вычисляет и возвращает функция,
подобрано вот из каких соображений. Пусть мы имеет такую матрицу,
как показано ниже, и нам нужно найти линейную координату элемента, обозначенного буквой A с координатами (x, y):
xxxxxx
0 xxxxx
0 0 xxAx
0 0 0 xxx
0 0 0 xx
00000x
Координату элемента можно определить как:
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.
6.4. Программа пользователя
Для проверки функционирования нашего модуля создается программный модуль, который имитирует программу пользователя. Этот
модуль обращается к функции creat_matr для создания матрицы нужного размера, заполняет ненулевую ее часть последовательно увеличивающимися числами, используя для этого функцию write_matr, и
выводит матрицу на экран, используя для выборки ее элементов
функцию read_matr. Далее в диалоговом режиме программа вводит
запрос на свои действия и читает/пишет элементы матрицы с задан18
ными координатами, обращаясь к функциям read_matr/write_matr. Если пользователь захотел закончить работу, программа вызывает функцию close_matr.
6.5. Тексты программных модулей
/********************** Файл LAB2.H *************************/
/* Описание функций и внешних переменных файла LAB2.C */
extern int L2_RESULT; /* Глобальная переменная – флаг ошибки */
/***** Выделение памяти под матрицу */
int creat_matr ( int N );
/***** Чтение элемента матрицы по заданным координатам */
int read_matr ( int x, int y );
/***** Запись элемента в матрицу по заданным координатам */
int write_matr ( int x, int y, int value );
/***** Уничтожение матрицы */
int close_matr ( void );
/***************** Конецфайла LAB2.H *************************/
/************************* Файл LAB2.C *************************/
/* В этом файле определены функции и переменные для обработки
матрицы, заполненной нулями ниже главной диагонали
*/
#include <alloc.h>
static int NN;
/* Размерностьматрицы */
static int SIZE;
/* Размерпамяти */
static int *m_addr=NULL;
/* Адрессжатойматрицы */
static int lin(int, int); /* Описаниефункциилинеаризации */
static char ch_coord(int, int); /* Описаниефункциипроверки */
int L2_RESULT;
/* Внешняя переменная, флаг ошибки */
/*********************************************************/
/*
Выделение памяти под сжатую матрицу
*/
int creat_matr ( int N ) {
/* N - размер матрицы */
NN=N;
SIZE=N*(N-1)/2+N;
if ((m_addr=(int *)malloc(SIZE*sizeof(int))) == NULL )
return L2_RESULT=–1;
else
return L2_RESULT=0;
/* Возвращает 0, если выделение прошло успешно, иначе –1 */
}
/**************************************************************/
19
/*
Уничтожение матрицы (освобождение памяти)
*/
int close_matr(void) {
if ( m_addr!=NULL ) {
free(m_addr);
m_addr=NULL;
return L2_RESULT=0;
}
else return L2_RESULT=–1;
/* Возвращает 0, если освобождение прошло успешно, иначе – –1 */
}
/***********************************************************/
/* Чтение элемента матрицы по заданным координатам */
int read_matr(int x, int y) {
/* x, y – координаты (строка, столбец) */
if ( ch_coord(x,y) ) return 0;
/* Если координаты попадают в нулевой участок – возвращается
0, иначе – применяется функция линеаризации */
return (x > y) ? 0 : m_addr[lin(x,y)];
/* Проверка успешности чтения – по переменной
L2_RESULT: 0 - без ошибок, –1 – была ошибка */
}
/*************************************************************/
/* Запись элемента матрицы по заданным координатам
*/
int write_matr(int x, int y, int value) {
/* x, y – координаты, value – записываемое значение */
if ( chcoord(x,y) ) return;
/* Если координаты попадают в нулевой участок – записи нет,
иначе - применяется функция линеаризации */
if ( x > y ) return 0;
else return m_addr[lin(x,y)]=value;
/* Проверка успешности записи – по L2_RESULT */
}
/************************************************************/
/*
Преобразование 2-мерных координат в линейные
*/
/*
(вариант 3)
*/
static int lin(int x, int y) {
int n;
n=NN-x;
return SIZE-n*(n-1)/2–n+y–x;
}
20
/***************************************************************/
/*
Проверкакорректностиобращения
*/
static char ch_coord(int x, int y) {
if ( ( m_addr==NULL ) ||
( x>SIZE ) || ( y>SIZE ) || ( x<0 ) || ( y<0 ) )
/* Если матрица не размещена в памяти, или заданные
координаты выходят за пределы матрицы */
return L2_RESULT=–1;
return L2_RESULT=0;
}
/*********************Конецфайла LAB2.C ***********************/
/************************ Файл MAIN2.C **************************/
/* "Программа пользователя" */
#include "lab2.h"
main(){
int R; /* размерность */
int i, j; /* номера строки и столбца */
int m; /* значения элемента */
int op; /* операция */
clrscr();
printf('Введите размерность матрицы >'); scanf("%d",R);
/* создание матрицы */
if ( creat_matr (R) ) {
printf("Ошибка создания матрицы\n");
exit(0);
}
/* заполнение матрицы */
for ( m=j=0; j<R; j++)
for ( i=о; i<R; i++)
write_matr(i,j,++m);
while(1) {
/* вывод матрицы на экран */
clrscr();
for (j=0; j<R; j++) {
for (i=0; i<R; i++)
printf("%3d ",read_matr(i,j));
printf("\n");
}
printf("0 - выход\n1 - чтение\n2 - запись\n>")
scanf("%d",&op);
switch(op) {
case 0:
if (close_matr()) printf("Ошибкаприуничтожении\n");
21
else printf("Матрица уничтожена\n");
exit(0);
case 1: case 2:
printf("Введитеномерстроки>");
scanf("%d",&j);
printf("Введите номер столбца >");
scanf("%d",&i);
if (op==2) {
printf("Введите значение элемента >");
scanf("%d",&m);
write_matr(j,i,m);
if (L2_RESULT<0) pritnf("Ошибказаписи\n");
}
else {
m=read_matr(j,i);
if (L2_RESULT<0) pritnf("Ошибкасчитывания\n");
else printf("Считано: %d\n",m);
}
printf("Нажмитеклавишу\n"); getch();
break;
}
}
}
/********************Конецфайла MAIN2.C ********************/
6.6. Вариант
Ниже приведены фрагменты программных кодов, которые отличают варианты, рассмотренные в 6.2.3.
Вариант 1 требует:










добавления к общим статическим переменным еще переменной:
static int *D; /* адрес дескриптора */
добавления такого блока в функцию creat_matr:
{
int i, s;
D=(int *)malloc(N*sizeof(int));
for (D[0]=0,s=NN-1,i=1; i<NN; i++)
D[i]=D[i-1]+s--;
}
изменения функции lin на:
22

static int lin(int x, int y) {
 return D[x]+y;

}
Вариант 2 требует:
 изменения функции lin на:

static int lin(int x, int y) {
 int s;


for (s=j=0; j<x; j++)

s+=NN-j;

return s+y-x;

}
23
Лабораторная работа 3
СТРУКТУРЫ И СВЯЗНЫЕ СПИСКИ
1. Цель работы
Закрепление практических навыков в работе со структурами и
указателями языка C, обеспечении функциональной модульности.
2. Темы для предварительного изучения
 Указатели в языке C.
 Структуры.
 Функции и передача параметров.
3. Постановка задачи
Для заданной прикладной области разработать описание объектов
этой области. Разработать процедуры, реализующие базовые операции
над этими объектами, в том числе:
 текстовый ввод-вывод (консольный и файловый);
 присваивание;
 задание константных значений;
 сравнение (не менее 2-х типов).
Процедуры и описания данных должны составлять отдельный
модуль (модуль типа данных). Подготовить на магнитном носителе
файл исходных данных, содержащих не менее 10 значений конкретных объектов. Используя процедуры и описания модуля типа данных,
разработать программу, обеспечивающую ввод исходных данных из
первого файла данных в память и хранение их в памяти в виде связного списка, сортировку списка по алфавитному и по числовому параметру.
4. Варианты индивидуальных заданий
Для каждой области перечислены параметры объекта. Среди параметров обязательно есть ключевое алфавитное поле (например, фамилия), которое идентифицирует объект, у каждого объекта имеется
также одно или несколько числовых полей, по которым вероятны обращения к объекту. Набор характеристик может быть расширен и
усложнен по усмотрению исполнителя.
24
№
Прикладная
п/п
область
1 Отдел кадров
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Атрибуты информации
Фамилия, имя, отчество сотрудника, должность, стаж
работы, оклад
Красная книга
Вид животного, род, семейство, место обитания,
численность популяции
Производство
Обозначение изделия, группа к которой оно относится,
год выпуска, объем выпуска, расход материала
Персональная
Фирма-изготовитель, тип процессора, тактовая частоЭВМ
та, емкость ОЗУ, емкость жесткого диска
Библиотека
Автор книги, название, год издания, код УДК, цена,
количество в библиотеке
Спутник планеты Название, название планеты-хозяина, год открытия,
диаметр, период обращения
Радиодеталь
Обозначение, тип, номинал, количество на схеме,
обозначение возможного заменителя
Текстовый
Наименование, фирма-изготовитель, количество окон,
редактор
количество шрифтов, стоимость
Телефонная
Номер абонента, фамилия, адрес, наличие блокиратостанция
ра, задолженность
Быт студентов
Фамилия, имя, отчество студента, факультет, размер
стипендии, число членов семьи
Спортивные
Фамилия, имя спортсмена, команда, вид спорта,
соревнования
зачетный результат, штрафные очки
Соревнование
Факультет, количество студентов, средний балл
факультетов по по факультету, число отличников, число двоечников
успеваемости
Участие в
Фамилия, имя, отчество студента, факультет,
с/х работах
вид работы, заработок
Сельхозработы Наименование с/х предприятия, вид собственности,
число работающих, основной вид продукции, прибыль
Сведения о семье Фамилия, имя, отчество студента, факультет,
специальность отца, специальность матери,
количество братьев и сестер
Скотоводство
Вид животных, количество особей в стаде в возрасте
до 1 года, количество особей 1–3 лет, свыше 3 лет,
смертность в каждой группе, рождаемость
Микросхема
Обозначение, разрядность, емкость, время доступа,
памяти
количество на схеме, стоимость
Описание
тип фигуры (квадрат, окружность и т. п.), координаты
изображения
на плоскости, числовые характеристики (длина
стороны, радиус и т. п.).
Лесное
Наименование зеленого массива, площадь, основная
хозяйство
порода, средний возраст, плотность деревьев на кв. км
25
20 Городской
транспорт
21 Университет
Вид транспорта, номер маршрута, начальная остановка, конечная остановка, время в пути
ФИО преподавателя, должность, название предмета,
количество часов, тип контроля
22 Оптовая база
Название товара, количество на складе, стоимость
единицы, название поставщика, срок поставки
23 Сеть магазинов Номер, название, адрес, телефон магазина, ФИО
владельца, адрес, капитал владельцев магазина
24 Авторемонтная Номер мастерской, марка, мощность и цвет
мастерская
автомобиля, ФИО и квалификация механика, тип работ
253 Зоопарк
Вид животного, кличка, возраст, категория редкости,
вес, суточный рацион мяса, овощей, молока
26 Договорная
Шифр договора, наименование организации,
деятельность
наименование контрагента, сроки выполнения заказа,
организации
сумма договора, вид договора
27 Поликлиника
ФИО и дата рождения пациента, ФИО, должность
и специализация лечащего врача, диагноз
28 Домоуправление Номер квартиры, общая площадь, полезная площадь,
количество комнат, ФИО квартиросъемщика,
количество членов семьи, количество детей в семье,
задолженность по квартплате
29 Аэропорт
Номер рейса, пункт назначения, день рейса, тип
самолета, время вылета, время в пути, является ли
маршрут международным
30 Шахматы
ФИО спортсмена, дата рождения, страна, спортивный
разряд, участие чемпионатах мира, рейтинг
31 Ипподром
Кличка лошади, масть, возраст, рейтинг, вид забега,
фамилия наездника, занятое место
32 Малая планета Название, название планеты-хозяина (для спутников),
дата открытия, диаметр, период обращения
33 Автотранспорт- Номерной знак автомобиля, марка, техническое
ное предприятие состояние, грузоподъемность, расход топлива,
табельный номер и ФИО закрепленного водителя
5. Пример решения задачи
5.1. Индивидуальное задание
Прикладная область – кафедра.
Атрибуты:
 ФИО преподавателя;
 должность;
 ученое звание.
26
5.2. Описание методов решения
5.2.1. Представление в памяти
"База данных" в оперативной памяти представляется в виде однонаправленного линейного списка. Структура элемента списка содержит четыре поля:
struct _emlp{
charname[25]; /* Ф.И.О. */
int grade; /* Должность */
int hight; /* Звание */
struct _emlp *next; /* Указатель на следующий элемент */
};
Для сокращения записи мы определяем текст struct _emlp как _emlp:
#define emlp struct _emlp
5.2.2. Модульная структура программного изделия
Программное изделие выполняется в виде одного программного
модуля, файла LAB3.C, в котором размещаются данные, функция
main и вспомогательные функции.
5.3. Описание логической структуры
5.3.1. Общие переменные
Общими переменными программы являются:
struct _emlp *emlph=NULL; /* указатель на начало списка */
char fname[]="D_STRUCT.DA1"; /* имя файла для хранения списка */
5.3.2. Функция main
В функции main введена дополнительная структура, которая описывает меню. Ее полями являются:
 op – номер элемента меню;
 *(opf) – адрес соответствующей функции обработки.
Основой функции main является цикл for в котором и осуществляется работа с базой данных. Сначала вертикально выводятся пункты
меню, затем пользователю предлагается ввести код операции,которую
он хочет проделать над базой. После чего производится вызов соответствующей функции обработки.
Рассмотрим подробнее эту операцию. В цикле перебираются все
возможные значения номера меню (их всего 10) и идет сравнение ра27
нее введенного номера с текущим, если они совпали, то по адресу,
записанному в поле *(opf) элемента массива структуры меню с текущим номером, вызывается функция обработки:
for (i=0; i<10;i++){
if (opcode==m[i].op) {
if (m[i].opf()==1) eoj=1;
break;
}
По завершению работы функции производится выход из внутреннего цикла for, и вся последовательность действий повторяется заново. Выход из внешнего цикла и, соответственно, из программы осуществляется при выборе последнего пункта меню с помощью функции exit.
5.3.3. Функция печати списка
int f_print(emlp *).
Функция печати списка f_print производит форматированный вывод всех элементов базы данных на экран. Если их количество превышает 20, то после вывода 20-ти элементов (когда заполнен весь экран)
работа функции приостанавливается до нажатия любой клавиши.
5.3.4. Функция ввода списка
int f_input(emlp *).
Функция ввода списка f_input осуществляет ввод элементов базы.
Ввод производится с помощью функции добавления элемента f_add.
Конец ввода – при вводе вместо ФИО символа '*'.
5.3.5.Функция добавления элемента в список
int f_add(emlp *.
Функция добавления элемента f_add вносит новый элемент в базу.
28
5.3.6.Функция уничтожения элемента списка
int f_delete(emlp *).
Уничтожение элемента списка производится с помощью функции
f_delete, в которой происходит переприсваивание указателей и освобождение памяти, которую занимал удаленный элемент.
5.3.7.Функция изменения значения полей элемента списка
int f_change(emlp *).
Изменение значения полей элемента списка производит функция
f_change. При изменении выводятся старые значения полей и предлагается ввести новые на место старых.
5.3.8.Функция сортировки списка
int f_sort(emlp *).
Функция сортировки списка f_sort реализована по методу "пузырька", который рассматривался в курсе "Структура и организация
данных".
5.3.9.Функция сохранения списка на диске
int f_save(emlp *).
Функция сохранения списка на диске f_save записывает все элементы базы данных в файл "D_STRUCT.DAT". Запись осуществляется
полями.
5.3.10. Перезапись списка из файла в оперативную память
int f_restore(emlp *).
Перезапись списка из файла в оперативную память производит
функция f_restore. Она читает из файла "D_STRUCT.DAT" поля записей в поля элементов списка.
29
5.4. Текст программы
/********************* Файл LAB3.C ************************/
/* Для сокращения записи типа структуры введем следующую константу */
#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',}
30
};
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("Enteroperationcode> "); /* Запроснавводномера
пункта для выполнения */
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){
31
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;
}
/************* Добавление элемента в список *******************/
intf_add() {
emlp *a, *b;
charss[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;
32
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;
}
33
/*************** Функция сортировки списка *******************/
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;
34
/* Если списка нет в памяти, то вывод соответствующего
сообщения */
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;
}
35
Лабораторная работа 4
ПРОВЕРКА ОБОРУДОВАНИЯ
1. Цель работы
Получение практических навыков в определении конфигурации и
основных характеристик ПЭВМ.
2. Темы для предварительного изучения
 Конфигурация ПЭВМ.
 Склад, назначение и
характеристики
основных
модулей
ПЭВМ.
3. Постановка задачи
Для компьютера на своем рабочем месте определить:
 тип компьютера;
 конфигурацию оборудования;
 объем оперативной памяти;
 наличие и объем расширенной памяти;
 наличие дополнительных ПЗУ;
 версию операционной системы.
4. Порядок выполнения
Порядок выполнения работы и содержание отчета определены в
общих требованиях.
5. Пример решения задачи
5.1. Структура данных программы
Программа использует, так называемый, список оборудования –
2-байтное слово в области данных BIOS по адресу 0040:0010. Назначение разрядов списка оборудования такое:
36
Биты
0
1
2, 3
4, 5
6, 7
8
9, 10, 11
12
13
14, 15
Содержимое
– Установлен в 1, если есть НГМД (см. разряды 6, 7)
– Установлен в 1, если есть сопроцессор
– Число 16-Кбайтних блоков ОЗУ на системной плате
– Код видеоадаптера: 11 – MDA, 10 – CGA, 80 колонок,
01 – CGA, 40 колонок, 00 – другой
– Число НГМД-1 (если в разряде 0 единица)
– 0, если есть канал ПДП
– Число последовательных портов RS-232
– 1, если есть джойстик
– 1, если есть последовательный принтер
– Число параллельных принтеров
5.2. Структура программы
Программа состоит только из основной функции main(). Выделения фрагментов программы в отдельные процедуры не требуется, потому что нет таких операций, которые во время работы программы
выполняются многократно.
5.3. Описание переменных
Переменные, применяемые в программе:
 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 – переменные, которые используются для задания значе-
ния регистров общего назначения и сегментных регистров, соответственно, при вызове прерывания.
37
5.4. Описание алгоритма программы
Алгоритм основной программы может быть разбит на 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,
38
эта функция изменяет содержимое 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 – младшее число.
5.5. Текст программы
/*----------------Лабораторная работа № 4------------------*/
/*-----------"Проверка состава оборудования"--------------*/
/* Подключение стандартных заголовков */
#include <dos.h>
#include <conio.h>
#include <stdio.h>
/*--------------------------------------------------------*/
39
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("Лабораторнаяработа № 5");
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");
/* Количество дисководов */
40
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
{
41
/* Предположим наличие 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);
42
/* Наличиеджойстика */
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);
43
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();
}
5.6. Результаты работы программы
В процессе работы программы на экран была выведена такая информация:
Лабораторная работа № 4
Проверка оборудования
Тип компьютера: AT
Конфигурация:
– Дисководы ГМД:
2
– Математич. сопроцессор: есть
– Тип дисплейного адаптера: VGA, кол., анал., 256 Кбайт
– Первичный блок памяти: 16 Кбайт
– Порты RS232:
2
– Джойстик:
нет
– Принтеры:
1
Объем оперативной памяти: 639 Кбайт
Объем extended-памяти: 384 Кбайт
Адрес ПЗУ = c000. Длина модуля = 24576 байт
Версия MS-DOS 6.20
44
Лабораторная работа 5
WINDOWS-ОКНА И СООБЩЕНИЯ. СОЗДАНИЕ ОКНА
Цели работы: получить представление о способе описания структуры Windows, на основании которого ОС создает окна; научиться обрабатывать сообщения Windows, не предусмотренные VCL библиотекой.
Окнами Windows являются не только главные окна, но и большинство элементов управления в них таких, как поля ввода, списки,
кнопки и т. п. Фактически любой элемент интерфейса, способный получать фокус ввода, является окном Windows. Окна могут иметь окновладельца (Parent Window). В этом случае остальные называются дочерними окнами (Child Window) и располагаются на поверхности владельца.
Поведение и внешний вид окна определяются его классом. Класс –
это внутренняя структура Windows, описывающая шаблон, на основании которого операционная система создает окна. Перед созданием
окна необходимо зарегистрировать его класс при помощи функции:
function RegisterClassEx(const WndClass: TWndClassEx): ATOM;
stdcall;
После того как класс зарегистрирован, приложение может создавать окна этого класса функцией:
function CreateWindowEx(
dwExStyle: DWORD;
lpClassName: PChar;
lpWindowName:
PChar;
dwStyle: DWORD;
X, Y, nWidth,
nHeight: Integer;
hWndParent: HWND;
hMenu: HMENU;
hInstance: HINST;
lpParam: Pointer
// расширенный стиль окна
// имя класса
// заголовок окна
// стиль окна
// размеры и позиция на экране
// идентификатор окна-владельца
// идентификатор меню окна
// идентификатор модуля, ассоциированного с // окном
// дополнительный параметр, передаваемый в
// оконную процедуру с сообщением WM_CREATE
): HWND; stdcall;
Функция CreateWindowEx позволяет задать конкретный вид окна
и уточнить информацию, полученную от класса окна.
45
Сообщения – это базовый механизм информирования программ о
событиях, на которые они должны реагировать. Ядром программы
является зарегистрированная в классе окна функция обработки сообщений, вызываемая ядром Windows при появлении событий, на которые программа должна отреагировать. Получение сообщения окном
означает вызов его оконной функции с параметрами, описывающими
передаваемое сообщение. Например, сразу после своего создания окно
получает сообщение WM_CREATE, при нажатии клавиш на клавиатуре – WM_KEYDOWN, WM_KEYUP, при перемещении мыши –
WM_MOUSEMOVE и т. п. Без обработки сообщений окно не сможет
даже отрисовать себя – рисование выполняется по получении сообщений WM_PAINT, WM_NCPAINT. В программе, написанной с использованием только Windows API, функция обработки сообщений обычно
представляет собой оператор case. Альтернативами данного оператора
являются различные сообщения, которые эта функция должна обработать.
Базовым классом, инкапсулирующим окно Windows, является
TWinControl. Когда создается экземпляр наследника этого класса,
VCL автоматически регистрирует соответствующий класс окна
Windows и создает окно. Благодаря этому наследники TWinControl
могут содержать в себе другие окна и обрабатывать сообщения
Windows. Визуальные компоненты, не являющиеся наследниками
TWinControl (такие как TLabel, TSpeedButton), не представляют собой
окна в понимании Windows. Все их события эмулируются компонентом, в который они помещены.
Центральное свойство компонента TWinControl – Handle. Оно
представляет собой идентификатор окна Windows, полученного при
создании этого компонента. Указанный идентификатор можно использовать с любыми функциями Windows API, работающими с окнами.
Пример 1. Нижеприведенный код прокручивает текст в TMemo на
одну строку вниз:
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(Memo1.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
end;
Для большинства сообщений Windows, которые должны обрабатываться окном, уже предусмотрена обработка по умолчанию. Сообщения, требующие специфической обработки, приводят к вызовам
функций – обработчиков событий, например:
46
WM_MOUSEMOVE
WM_LBUTTONDOWN,
WM_RBUTTONDOWN
WM_LBUTTONUP,
WM_RBUTTONUP
WM_LBUTTONDBLCLK
WM_KEYDOWN
WM_KEYUP
WM_PAINT
OnMouseMove
OnMouseDown
OnMouseUp
OnDblClick
OnKeyDown
OnKeyUp
OnPaint
Пример 2. Создать минимальное окно на WinAPI.
program winmin;
uses
windows, messages;
var
wc : TWndClassEx; //Переменная шаблона класса окна
MainWnd : HWND; //Описатель главного окна
Mesg : TMsg;
//Переменная для цикла сбора сообщений
Далее следует оконная процедура главного окна. Обратите внимание на служебное слово stdcall оно вынуждает компилятор генерировать особый вид кода, который может быть вызван любой windowsпрограммой, без этого служебного слова программа не сможет работать вообще}
function WindowProc(wnd:HWND; Msg : Integer; Wparam:Wparam;
Lparam:Lparam):Lresult;
stdcall;
Begin
{Далее происходит цикл обработки сообщений}
case msg of
wm_destroy : //Сообщение, посылаемое при уничтожении окна
Begin
postquitmessage(0); exit;
Result:=0;
End
else Result:=DefWindowProc(wnd,msg,wparam,lparam);
end;
End;
var
xPos,yPos,nWidth,nHeight : Integer;
begin //Тело программы
47
{Далее идет заполнение шаблона класса окна}
wc.cbSize:=sizeof(wc);
wc.style:=cs_hredraw or cs_vredraw;
wc.lpfnWndProc:=@WindowProc;
wc.cbClsExtra:=0;
wc.cbWndExtra:=0;
wc.hInstance:=HInstance;
wc.hIcon:=LoadIcon(0,idi_application);
wc.hCursor:=LoadCursor(0,idc_arrow);
wc.hbrBackground:=COLOR_BTNFACE+1;
wc.lpszMenuName:=nil;
wc.lpszClassName:='WinMin : Main';
RegisterClassEx(wc); //Регистрацияновогоклассавсистеме
{Заполнение переменных xPos,yPos,nWidth,nHeight}
xPos:=100;
yPos:=150;
nWidth:=400;
nHeight:=250;
{Создание главного окна}
MainWnd:=CreateWindowEx (
0,
//флаги расширенных стилей
'WinMin : Main', //имя класса окна, данное при заполнении структуры
wc
'Win Min',
//заголовок окна
ws_overlappedwindow, //флаги стилей окна
xPos,
//горизонтальная позиция окна
yPos,
//вертикальная позиция окна
nWidth,
//ширина окна
nHeight,
//высота окна
0,
//описатель родительского окна (parent) или окна-владельца
(owner)
0,
//описатель меню окна (меню нет, нет и описателя)
Hinstance,
//описательприложения
nil
//address of window-creation data
);
ShowWindow(MainWnd,CmdShow); //Отображаемокно
//Циклобработкисообщений
//он может слегка видоизменяться, но эти строчки присутствуют всегда
While GetMessage(Mesg,0,0,0) do
begin
TranslateMessage(Mesg);
DispatchMessage(Mesg);
end;
end.
48
Задание
1. Программным путем открыть\закрыть поддон CD-ROM.
2. Получить строку сообщения об ошибке Windows, код которой
получен функцией GetLastError.
49
Лабораторная работа 6
ОБОЛОЧКА ОКНА WINDOWS
Цели работы:
– получить представление о классе TWinControl;
– научиться создавать окна с требуемыми параметрами.
Программирование создания окон и цикла обработки сообщений
вручную является непростой и довольно низкоуровневой задачей.
VCL реализует классы, позволяющие избежать возникающих при
этом сложностей.
Базовым классом, инкапсулирующим окно Windows, является
TWinControl. Когда создается экземпляр наследника этого класса,
VCL автоматически регистрирует соответствующий класс окна
Windows и создает окно. Благодаря этому наследники TWinControl
могут содержать в себе другие окна и обрабатывать сообщения
Windows. Визуальные компоненты, не являющиеся наследниками
TWinControl (такие как TLabel, TSpeedButton), не представляют собой
окна в понимании Windows. Все их события эмулируются компонентом, в который они помещены.
Свойство Handle
Центральное свойство компонента TWinControl – Handle. Оно
представляет собой идентификатор окна Windows, полученного при
создании этого компонента. Указанный идентификатор можно использовать с любыми функциями Windows API, работающими с окнами.
Пример 1. Нижеприведенный код прокручивает текст в TMemo на
одну строку вниз:
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(Memo1.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
end;
Метод CreateParams
TWinControl перед созданием окна вызывает виртуальный метод
CreateParams, позволяя программисту задать низкоуровневые параметры создаваемого окна. В процедуру передается структура данных:
50
TCreateParams = record
Caption: PChar;
Style: Longint;
ExStyle: Longint;
X, Y: Integer;
Width, Height: Integer;
WndParent: HWND;
Param: Pointer
WindowClass:
TWndClass;
WinClassName:
array[0..63] of Char;
// Заголовок окна, соответствующий параметру
// lpWindowName
// Стиль окна, соответствующий параметру
dwStyle
// Расширенный стиль окна (dwExStyle)
// Координаты окна
// Идентификатор окна-владельца (hWndParent)
// Дополнительный параметр (lpParam)
// Структура TWndClass, позволяющая задать
// параметры класса окна
// Имя класса окна
// (lpClassName) end;
Наследники TWinControl могут перекрыть CreateParams, создавая
окна с требуемыми внешним видом и поведением. Например, необходимо создать форму, не имеющую заголовка, однако позволяющую
изменять свои размеры. Delphi не предоставляет возможности задать
такое поведение визуальными средствами, однако, перекрыв
TForm.CreateParams, мы легко добиваемся нужного эффекта.
Пример 2
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited; // Вызываемунаследованныйобработчик, позволяя
// VCL подготовить «типовую» конфигурациюокна
with Params do
// иизменяемтребуемыепараметры
Style := Style and (not WS_CAPTION) or WS_THICKFRAME or
WS_POPUP;
end;
Задание
1. Создать окно формы в виде пятиконечной звезды.
2. Создать программно кнопку.
51
Лабо раторная работа 7
ОБРАБОТКА СООБЩЕНИЙ
Цели работы:
– получить представление о сообщениях Windows;
– научиться находить место порождения сообщения;
– уметь отслеживать системные сообщения, обслуживать их.
Каждое окно Windows должно обрабатывать сообщения. VCL берет на себя работу по организации цикла сообщений и по их базовой
обработке. Для большинства сообщений Windows, которые должны
обрабатываться окном, уже предусмотрена обработка по умолчанию.
Сообщения, требующие специфической обработки, приводят к вызовам функций – обработчиков событий, например:
WM_MOUSEMOVE
WM_LBUTTONDOWN,
WM_RBUTTONDOWN
WM_LBUTTONUP,
WM_RBUTTONUP
WM_LBUTTONDBLCLK
WM_KEYDOWN
WM_KEYUP
WM_PAINT
OnMouseMove
OnMouseDown
OnMouseUp
OnDblClick
OnKeyDown
OnKeyUp
OnPaint
и так далее.
Показателен в этом смысле метод WndProc класса TWinControl
или его наследников. При этом VCL перед вызовом обработчиков
производит «упаковку» параметров сообщений в удобный для обработки и анализа вид. Понимание того, какое сообщение Windows вызывает срабатывание того или иного события VCL, очень помогает
при программировании обработчиков и совершенно необходимо при
написании собственных компонентов. Разумеется, предусматривать
отдельные обработчики для каждого из сотен сообщений, которые
могут поступить в окно, – значит неоправданно усложнять код VCL.
Поэтому для обработки остальных сообщений синтаксис Object Pascal
предусматривает создание процедур – обработчиков сообщений. Такие процедуры объявляются как:
52
procedureWMSize(varMessage: TWMSize); messageWM_SIZE;
В качестве параметра такая функция получает указатель на структуру TMessage, содержащую информацию о сообщении, переданном
окну. Для многих часто используемых сообщений в модуле
Messages.pas определены структуры, позволяющие более удобно работать с конкретными сообщениями.
Пример
Окно, обрабатывающее не предусмотренное VCL сообщение
WM_HOTKEY. Это сообщение посылается окну посредством зарегистрированной в Windows горячей клавиши, что позволяет программе
реагировать на нее, даже не имея фокуса ввода:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
// Объявляемпроцедуру-обработчик
procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Регистрируемгорячуюклавишу Ctrl+Alt+F5
RegisterHotKey(Handle, 0, MOD_CONTROL or MOD_ALT, VK_F5);
end;
procedure TForm1.WMHotKey(var Msg: TWMHotKey);
begin
// Эта процедура вызывается при получении окном
// сообщения WM_HOTKEY
inherited; // Даем форме обработать сообщение,
// если у нее уже есть его обработчик
Beep;
// Выполняем дополнительные действия
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// Отменяем регистрацию горячей клавиши
UnRegisterHotKey(Handle, 0);
end;
53
Хочется обратить ваше внимание на вызов унаследованного обработчика в методе WMHotKey. Если вы не уверены, что хотите запретить обработку предком подобного сообщения, – всегда вызывайте
унаследованный обработчик. В противном случае вы рискуете помешать VCL обрабатывать сообщения. Например, написав свой обработчик WM_PAINT и не вызвав в нем унаследованный, вы полностью
заблокируете перерисовку формы средствами VCL.
Сообщения от оконных элементов:
bm_GetCheck – определяет, являются ли селективная кнопка или
блок пpовеpки помеченными. Возвращаемое значение: Если селективная кнопка или блок пpовеpки помечен, возвpащается ненулевое значение. В пpотивном случае, возвращается нуль. Для текстовой кнопки
всегда возвpащается нуль.
bm_GetState – определяет состояние оpгана упpавления кнопки
пpи нажатии кнопки мыши или клавиши пpобела. Возвpащаемое значение: Если кнопка является подсвеченной текстовой кнопкой, на
кнопке сфокусиpован ввод и нажата кнопка мыши или клавиша
пpобела, или нажата кнопка мыши, когда куpсоp находится в кнопке,
возвpащается ненулевое значение. В пpотивном случае – возвpащается нуль.
bm_SetCheck – помечает или удаляет отметку из селективной
кнопки или блока пpовеpки.
bm_SetState – изменяет состояние кнопки или блока пpовеpки.
bm_SetStyle– изменяет стиль кнопки.
Механизм системных сообщений можно использовать для организации взаимодействия между приложениями или компонентами одного приложения.
Определить собственное сообщение можно очень просто. Windows предоставляет определенный диапазон сообщений, которые
можно использовать для любых целей (от $0400 – WM_USER до
$7FFF). Для создания пользовательского сообщения достаточно определить соответствующую константу. Делается это, отсчитывая от основы
WM_USER:
Const
WM_READY= WM_USER:+100;
54
Задание
1. Напишите приложение, которое по нажатию кнопки с последующим подтверждением запустит цикл вычислений, где значение
переменной будет отображаться в заголовке окна. По завершении вычислительная процедура отправит главному окну приложения сообщение о том, что вычисление окончено, а окно отреагирует на него
выводом информационного окна.
2. Напишите приложение, которое будет реагировать на изменение свойств экрана во время его работы.
3. Напишите приложение, которое будет реагировать на ошибку
Windows.
55
Лабораторная работа 8
ГРАФИЧЕСКАЯ ПОДСИСТЕМА WINDOWS
Цели работы:
– усвоить базовые понятия графической подсистемы Windows;
– уметь использовать Windows API с TCanvas;
– иметь представления о возможностях форматирования текста.
Программы Windows не имеют возможности прямого вывода информации на экран. Вместо этого программа, желающая что-либо
нарисовать в своем окне, должна вызвать одну из функций Windows
API, предназначенных для рисования. Такой подход позволяет
Windows обеспечить на одном экране одновременное существование
нескольких программ, каждая из которых выводит в своем окне произвольную информацию. Вопросы отсечения информации невидимых
участков окон и взаимодействия с конкретным драйвером графического устройства решаются операционной системой.
Базовым понятием графической подсистемы Windows является
контекст графического устройства – внутренняя структура, определяющая набор графических объектов, их атрибутов и графических режимов, которые могут повлиять на вывод информации. Контекст может быть связан с различными устройствами такими, как дисплей,
принтер, изображение в памяти, что позволяет использовать одни и те
же функции для вывода информации на любое из этих устройств. Разумеется, при этом необходимо учитывать такие возможности конкретного устройства, как разрешение или поддерживаемый набор цветов.
Типичный сценарий вывода информации в Windows выглядит
следующим образом:
– Получить контекст устройства;
– Сформировать графические объекты с требуемыми атрибутами
такими, как шрифт, кисть;
– Включить объекты в контекст и, возможно, сохранить при этом
старые объекты;
– Установить атрибуты контекста (режим заполнения и т. п.), сохранив старые атрибуты;
– Используя функции рисования, вывести информацию на
устройство;
– Восстановить атрибуты контекста;
56
– Восстановить графические объекты;
– Освободить созданные временные графические объекты;
– Освободить контекст устройства.
Класс TCanvas, инкапсулирующий контекст графического устройства Windows. Вместе со вспомогательными классами TFont, TBrush и
т. д., реализующими графические объекты, этот класс берет на себя
всю работу, связанную с получением и освобождением контекстов
устройства его атрибутов и графических объектов. Когда вы меняете,
например, свойства кисти (Canvas.Brush), TCanvas автоматически создаст новую кисть с нужными атрибутами и включит ее в свой контекст, а старую — корректно удалит. Наряду с этим TCanvas реализует ряд методов, позволяющих рисовать на нем без указания контекста
устройства. В итоге программист может вообще не знать принципов
работы графической подсистемы Windows, а использовать только методы TCanvas. Все классы VCL, допускающие рисование, имеют
свойство Canvas, указывающее на автоматически создаваемый экземпляр TCanvas, связанный с ним.
Использование Windows API с TCanvas
Реализовать в TCanvas все функции рисования Windows нереально, поскольку это приведет к резкому усложнению кода VCL. Однако
TCanvas имеет свойство Handle, которое представляет собой идентификатор графического контекста Windows, ассоциированного с экземпляром класса. Используя его, мы можем задействовать все многообразие функций API, предназначенных для рисования. При этом всю
работу по установке атрибутов и недопущению утечки ресурсов можно выполнить в реализации класса TCanvas.
Рассмотрим наиболее часто употребляемые функции Windows
API, которые можно использовать для рисования.
DrawText
Функция DrawText объявлена как:
function DrawText(
hDC: HDC;
// Контекст графического устройства
// (TCanvas.Handle)
lpString: PChar; // Строка для вывода на экран
nCount: Integer; // Количество символов в строке, если строка
// имеет нуль-терминатор — можно передать –1
var lpRect: TRect;
// Прямоугольник, в котором будет отрисована //
строка
57
uFormat: UINT uFormat: UINT // Флаги форматирования
): Integer; stdcall; // Функция возвращает высоту текста в пикселях
Эта функция имеет широкие возможности по форматированию
текста, задаваемые параметром uFormat. Данный параметр представляет собой набор битовых флагов, задающих способ выравнивания и
дополнительные операции над текстом.
Рассмотрим некоторые из этих флагов:
DT_SINGLELINE
DT_WORDBREAK
DT_LEFT
DT_CENTER
DT_RIGHT
DT_END_ELLIPSIS
DT_PATH_ELLIPSIS
Текст выводится в одну строку, при этом игнорируются символы перевода строки и возврата каретки. С данным флагом могут комбинироваться
DT_TOP, DT_BOTTOM и DT_VCENTER, задающие вертикальное выравнивание текста
Текст разбивается на строки так, чтобы он поместился по ширине в прямоугольнике lpRect. Символы возврата каретки и перевода строки также приводят к переходу на новую строку
Выводимый текст выравнивается по левому краю,
по центру или по правому краю
Если строка не помещается в lpRect, ее конец заменяется на три точки. Это бывает удобно использовать для вывода полей, имеющих значительную
длину
Используется с путями, содержащими символ ‘\’.
Если строка не помещается в lpRect, то часть ее из
середины заменяется на три точки
Рассмотрим в качестве примера создание списка, содержащего две
колонки. Первая из них содержит номер позиции, вторая — сопроводительный текст, высота которого может изменяться. Для создания
такого списка разместим на форме TListBox, установив его свойство
Style в lbOwnerDrawVariable. После этого создадим следующие обработчики событий:
procedureTForm1.FormCreate(Sender: TObject);
begin
with ListBox1.Items do begin // Заполняемсписок
Add('Первая позиция');
Add('Вторая позиция. Ее описание имеет значительную длину');
Add('Третья позиция'#13'В ней'#13'имеются переводы строки');
Add('Четвертая позиция');
end;
58
end;
procedure TForm1.ListBox1MeasureItem(Control: TWinControl;
Index: Integer; var Height: Integer);
var
R: TRect;
begin
// Заполняем R, оставив 20 пикселей для первой колонки
// и 4 пикселя для учета рамки компонента
R := Rect(20, 0, ListBox1.Width-4, Height);
// И вычисляем высоту прямоугольника, необходимую
// для вывода всего текста
Height := DrawText(ListBox1.Canvas.Handle,
PChar(ListBox1.Items[Index]), -1, R,
DT_CALCRECT or DT_WORDBREAK);
end;
procedure TForm1.ListBox1DrawItem(Control: TWinControl;
Index: Integer; Rect: TRect; State: TOwnerDrawState);
begin
with ListBox1 do begin
// Очищаемзонурисования
Canvas.FillRect(Rect);
// Выводим номер позиции, центрируя его по вертикали
DrawText(Canvas.Handle, PChar(IntToStr(Index + 1)), -1, Rect,
DT_VCENTER or DT_SINGLELINE);
// Получаем зону для вывода остального текста
Inc(Rect.Left, 20);
// И выводим его
DrawText(Canvas.Handle, PChar(Items[Index]), -1, Rect,
DT_WORDBREAK);
end;
end;
DrawEdge
Функция DrawEdge бывает очень полезна при написании собственных компонентов. Она автоматизирует задачу рисования трехмерных кнопок, рамок и т. п. Функция объявлена как:
function DrawEdge(
hdc: HDC;
// Графический контекст
var qrc: TRect; // Координаты рамки
edge: UINT; // Тип рамки
grfFlags: UINT // Тип бордюра
): BOOL; stdcall;
59
Рамка рисуется в виде комбинации из двух прямоугольников –
внутреннего (inner) и внешнего (outer). Каждый из них может быть
выпуклым (raised) либо вдавленным (sunken). Тип рамки определяется
параметром edge, который представляет собой битовую маску из следующих значений:
BDR_RAISEDINNER
BDR_SUNKENINNER
BDR_RAISEDOUTER
BDR_SUNKENOUTER
Задают выпуклый либо вдавленный внутренний
контур
Задают выпуклый либо вдавленный внешний контур
Также имеются предопределенные флаги:
EDGE_BUMP – выпуклая рамка;
EDGE_ETCHED – вдавленная рамка;
EDGE_RAISED – выпуклая кнопка;
EDGE_SUNKEN – вдавленная кнопка.
Параметр grfFlags определяет тип бордюра. Это набор следующих флагов:
BF_BOTTOM
BF_TOP
BF_LEFT
BF_RIGHT
BF_BOTTOMLEFT
BF_BOTTOMRIGHT
BF_TOPLEFT
BF_TOPRIGHT
BF_RECT
BF_DIAGONAL
BF_DIAGONAL_ENDBOTTOMLEFT
BF_DIAGONAL_ENDBOTTOMRIGHT
BF_DIAGONAL_ENDTOPLEFT
BF_DIAGONAL_ENDTOPRIGHT
BF_FLAT
BF_SOFT
BF_MONO
BF_MIDDLE
BF_ADJUST
60
Рисуется соответствующая сторона
рамки
Рисуется соответствующий угол
Рисуется вся рамка
Рисует бордюр по диагонали. Используется для создания кнопок,
разбитых по диагонали на две секции
Позволяют
получить
плоские
кнопки для создания интерфейса в
стиле Office 97
Рисует одномерную рамку
Заливает внутреннюю область
рамки текущей кистью
Корректирует параметр qrc так, что
после отрисовки он соответствует
внутренней области рамки. Удобно
применять для рисования в дальнейшем
Задание
1. Организуйте отрисовку кнопок, реагирующих на различные
действия пользователя.
2. Создайте нестандартную форму окна проекта.
61
Лабораторная работа 9
РАБОТА С БУФЕРОМ ОБМЕНА
Цели работы:
– научиться работать с буфером обмена данных;
– знать наиболее распространенные форматы данных;
– уметь применять методы работы с буфером.
Буфер обмена – это наиболее часто используемый механизм Windows для обмена данными между приложениями. Тип данных в буфере называется форматом данных.
Важнейшие форматы данных:
CF_TEXT
CF_UNICODETEXT
CF_BITMAP
CF_PICTURE
CF_TIFF
CF_WAVE
Обычный текст, конец текста обозначается
нулевым байтом NUL
Текст Unicode, в наборе символов, предназначенный для передачи символов множества
национальных алфавитов, каждый символ
представлен двумя байтами
Изображение в формате BMP
Изображение типа TPicture
Изображение в формате TIFF (Tag Image File
Format)
Звуковой фрагмент в формате WAV
Методы работы с буфером:
CopyToClipboard
CutToClipboard
PasteFromClipboard
Копирует выделенный фрагмент в
буфер
Перемещает выделенный объект в
буфер
Вставляет содержимое буфера в
текущую позицию
Если требуется поместить в буфер не текст, а данные другого типа, то надо непосредственно обращаться к буферу Windows с помощью методов класса ТClipboard. Буфер в Delphi представлен глобальной переменной – объектом класса ТClipboard. Этот объект не нужно
явно создавать. Его возвращает функция Clipboard.
62
Например:
Clipboard.Assign(Image1.Picture);
Свойства классаТClipboard:
AsText
Formats
Представляет содержимое буфера в текстовом виде
Массив форматов, в которых может быть
представлено текущее содержимое буфера
Задание
1. Напишите приложение, позволяющее отслеживать все текстовые фрагменты, проходящие через буфер обмена.
2. Создайте текстовый редактор.
63
Л а б о р а т о р н а я работа 10
РАБОТА С КЛАВИАТУРОЙ
Цели работы:
– усвоить понятие HotKeys;
– научиться создавать собственные комбинации горячих клавиш;
– уметь получать раскладку клавиатуры.
HotKeys – горячие клавиши
HotKeys – комбинации клавиш, на которые может реагировать
приложение, даже если оно не имеет фокуса или запущено в трее.
Hotkeys состоит из сочетания нажатия клавиш модификаторов
(Alt, Control, Shift), и клавиши, которая имеет виртуальный код.
Прежде создадим обработчик события WM_HOTKEY. Для этого
объявим в классе TForm1 следующий метод:
– private procedure WM_HotKeyHandler (var Message: TMessage);
– message WM_HOTKEY;
И определим его вот таким образом:
procedure TForm1.WM_HotKeyHandler (var Message: TMessage);
var
idHotKey: integer; //идентификатор, но об этом – позже
fuModifiers: word; //модификатор MOD_XX
uVirtKey: word; //код виртуальной клавиши VK_XX
begin
// параметры сообщения получаем так:
idHotkey:= Message.wParam;
fuModifiers:= LOWORD(Message.lParam);
uVirtKey:= HIWORD(Message.lParam);
if (fuModifiers = MOD_ALT) AND (uVirtKey = VK_F10) then
caption:='Alt-F10 нажата';
inherited;
end;
В этом примере обработчик сообщения WM_HOTKEY проверяет,
являются ли полученные параметры сигналом о нажатии комбинации
Alt-F10, и в случае положительного ответа в заголовок окна главной
формы выводится соответствующая строка.
64
Теперь обратимся непосредственно к созданию горячей клавиши в нашем примере это будет все та же Alt-F10. Вначале ее нужно зарегистрировать в системе. Как это делается? При помощи функции:
BOOL RegisterHotKey(HWND hWnd, int id, UINT fsModifiers, UINT vk);
· hWnd – окно, обрабатывающее сообщение WM_HOTKEY,
· fsModifiers
–
модификаторы
(MOD_ALT,
MOD_CONTROL,
MOD_SHIFT, MOD_WIN),
· vk – виртуальный код клавиши (см. константы с префиксом VK_).
Для приложения значение id может лежать в диапазоне
0000h..BFFFh, а для разделяемых динамических библиотек диапазон
будет таким: C000h..FFFFh.
Однако во избежание конфликтов между горячими клавишами
различных процессов целесообразно использовать значение, возвращаемое функцией GlobalAddAtom, передавая ей в качестве параметра
некую null-terminated строку длиной до 255 символов.
Вот как это делается:
Объявим глобальную переменную keyid: integer;
Она станет атомом, который создастся вышеописанной функцией,
и будет служить идентификатором горячей клавиши. Для удобства
поместим на форму две кнопки – первая будет создавать HotKey, вторая – уничтожать.
Итак, создаем и регистрируем горячую клавишу:
procedure TForm1.Button1Click(Sender: TObject);
begin
keyid:=GlobalAddAtom('My Hotkey'); //создаематом
RegisterHotKey(handle,// сообщение о HotKey будет получать форма
keyid, // регистрируем атом как id
MOD_ALT,// модификатор у нас - клавиша Alt
VK_F10 // вирт. клавиша - F10
);
end;
А следующий код отменяет зарегистрированную клавишу, и удаляет атом:
procedure TForm1.Button3Click(Sender: TObject);
begin
UnregisterHotKey(handle, keyid);
GlobalDeleteAtom(keyid);
end;
65
Рассмотрим еще один важный аспект работы с клавиатурой - способ отслеживания состояние клавиш Num Lock, Caps Lock, Scroll Lock
и Insert.
Во-первых, зададим переменную Key типа word.
Этой переменной можем присвоить значение одной из констант:
VK_NUMLOCK
VK_CAPITAL
VK_SCROLL
VK_INSERT
Теперь:
Var state: TKeyboardState;
begin
GetKeyboardState(state); //получить состояние клавиши
if Odd(state[VK_NUMLOCK]) then ; //клавиша "включена"
//как управлять состоянием клавиши?
state[key] := state[key] XOR 1; //цикличнопереключить
state[key] := state[key] OR 1; //включить
state[key] := state[key] AND (NOT 1); //выключить
SetKeyboardState(state); //установим новое значение
end;
Программное переключение раскладки клавиатуры
ActivateKeyboardLayout(0,HKL_NEXT) – циклично переключает раскладку.
Загрузить русскую можно с помощью кода:
LoadKeyboardLayout('00000419', KLF_ACTIVATE),
английскую –
LoadKeyboardLayout('00000409',KLF_ACTIVATE).
Задание
1. Создайте приложение, содержащее собственные комбинации
горячих клавиш.
2. Получите название активной раскладки вашего приложения,
получите раскладку в чужой программе, смените раскладку в активном окне.
66
Требования к оформлению работ
По каждой лабораторной работе составляется отчет, который должен содержать:
– титульный лист;
– название и цель работы;
– лабораторное задание;
– описание метода решения задачи;
– схему программы;
– распечатку программы и результатов ее выполнения;
– пояснительный текст к программе (описание структуры программы, назначения ее основных переменных, способов реализации
отдельных функций и т. д.);
– выводы, которые должны доказывать или оценивать правильность составленной программы или объяснять допущенные ошибки.
Программа должна включать:
– комментарий, в котором указывается номер лабораторной работы, фамилия и учебная группа студента, выполнившего ее;
– вывод исходных данных по следующей схеме:
<идентификатор переменной> = <значение переменной>;
– вывод результатов с комментариями.
Отчет оформляется на листах формата А4 (297 х 210).
67
Список литературы
1. Харт Д. Системное программирование в среде Windows. М.:
Изд. дом «Вильямс», 2005. 592 с.
2. Ганеев Р. М. Проектирование интерфейса пользователя средствами Win32 API: Учебное пособие для вузов. М.: Горячая линия –
Телеком, 2007. 358 с.
68
Download