Герасименко С.А. (geras@mail.esoo.ru) КОНТРОЛЬНЫЕ РАБОТЫ ПО ИНФОРМАТИКЕ И МЕТОДИЧЕСКИЕ УКАЗАНИЯ К НИМ: Второй год обучения — Оренбург: Издательство ОГПУ, 2001. – 48 с. Пособие предназначено для обучающихся на отделении информатики (программирования) заочной физико-технической школы. МЕТОДИЧЕСКИЕ УКАЗАНИЯ К КОНТРОЛЬНОЙ РАБОТЕ ПО ИНФОРМАТИКЕ N 1 ТЕМА: ФАЙЛЫ. Файловый тип - это составной тип, образованный из компонент одинакового типа, которые называются элементами файла. Файл - это абстрактная модель физического набора данных, находящихся обычно вне программы. Физические наборы данных могут располагаться во внешней памяти ЭВМ, могут занимать часть оперативной памяти, могут отождествляться с потоком данных, вводимых и выводимых с помощью внешних устройств. Любой файл имеет три особенности: 1) каждый файл имеет имя, в силу этого программа может работать с несколькими файлами одновременно; 2) элементами файлов могут быть объекты любых типов, за исключением файлов. То есть, недопустимы файлы файлов; 3) количество элементов файла не устанавливается при его объявлении и ограничено только емкостью внешней памяти. В Turbo-Pascal определены файлы трех видов: типизированные, текстовые и нетипизированные. 1. ТИПИЗИРОВАННЫЕ ФАЙЛЫ. 1.1. Файлы. Описание типизированных файлов. Файловый тип или переменная файлового типа для типизированного файла описывается следующим образом: <имя файлового типа>=FILE OF <тип элементов> <имя файловой переменной>:FILE OF <тип элементов> Примеры: TYPE RFIL=FILE OF REAL; TEXT80=FILE OF STRING[80]; VAR F1:FILE OF CHAR; F2:RFIL; F3:TEXT80; 1.2. Операции над типизированными файлами. Основные процедуры и функции. Операции над файлами могут осуществляться только с помощью функций и процедур. Перед этим файл должен быть связан с набором данных, а если это касается элементов файла, то файл должен быть открыт. Перед окончанием выполнения программы каждый файл должен быть закрыт. Везде далее F - это имя файловой переменной типизированного файла. Процедура ASSIGN. Синтаксис: ASSIGN(F,NAME) где NAME- строковое выражение, определяющее имя файла на диске. После выполнения этой процедуры NAME и F отождествляются. Процедура ASSIGN не должна применяться к открытому файлу, и она не открывает файл. Процедура RESET. Синтаксис: RESET(F) Перед выполнением процедуры файл должен быть связан с именем файла на диске с помощью ASSIGN. Процедура RESET открывает файл F и ставит указатель перед его первым элементом. После выполнения этой процедуры для типизированных файлов возможно не только чтение элементов файла, но и их запись, и изменение положения указателя файла. Процедура REWRITE. Синтаксис: REWRITE(F) Перед выполнением этой процедуры переменная F должна быть отождествлена с именем файла на диске. Процедура REWRITE открывает файл F и устанавливает указатель в начальное положение, то есть перед первым элементом. Если перед выполнением процедуры REWRITE файл на диске не существовал, то он будет создан. Если существовал, то будет удален и создан заново. В обоих случаях будет создан пустой набор данных. После выполнения этой процедуры для типизированных файлов возможна не только запись элементов файла, но и их чтение, и изменение положения указателя файла. Процедура CLOSE. Синтаксис: CLOSE(F) Файл F может быть закрыт или открыт. После выполнения процедуры CLOSE файл F закрывается, в директорию вносятся необходимые изменения. Окончание выполнения программы не влечет за собой автоматического вызова процедуры CLOSE. Процедура READ. Синтаксис: READ(F,P) где P - имя переменной. Можно в одной процедуре указать несколько имен переменных: READ(F,P1,P2,P3). При выполнении этой процедуры файл должен быть открыт. Выполнение процедуры READ приводит к присваиванию переменной P элемента файла и перемещению указателя файла на следующий элемент. Тип переменной P и тип элементов файла должен быть один и тот же. Процедура WRITE. Синтаксис: WRITE(F,P) где P - имя переменной, можно указать несколько имен переменных. Тип переменной P должен совпадать с типом элементов файла. Выполнение процедуры WRITE приводит к выводу в файл значения переменной P. Файл должен быть открыт. Указатель файла будет переставлен в следующую позицию. Функция EOF. Синтаксис: EOF(F):BOOLEAN Функция EOF (END OF FILE) возвращает значение TRUE, если указатель находится в конце файла, в противном случае - значение FALSE. Функция FILESIZE. Синтаксис: FILESIZE(F):INTEGER Возвращает количество элементов файла. Функция FILEPOS. Синтаксис: FILEPOS(F):INTEGER Возвращает текущую позицию указателя файла. Номер начальной позиции - перед первым элементом файла - равен 0, номер последней позиции за последним элементом файла - FILESIZE(F). Процедура SEEK. Синтаксис: SEEK(F,POS) где POS - выражение целого типа, значение которого не выходит за пределы интервала [0, FILESIZE(F)]. Процедура SEEK перемещает указатель файла в позицию POS. Если POS=0, то указатель файла установлен в его начало, если POS=FILESIZE(F) - то в конец. Процедура RENAME. Синтаксис: RENAME(F,STR) где STR - строковое выражение. Файл F должен быть закрыт. Процедура RENAME изменяет имя набора данных связанных с файлом F, на имя, определенное строковым выражением STR. В STR нельзя указывать дисковод и имя уже существующего файла. Пример: PROGRAM PR; VAR F:FILE OF CHAR; BEGIN ASSIGN(F,'PR.PAS'); RENAME(F,'PR.BAK') END. После выполнения этой программы файл PR.PAS будет переименован в PR.BAK. Процедура ERASE. Синтаксис: ERASE(F) Рекомендуется, чтобы файл F был закрыт. После выполнения этой процедуры файл, отождествленный с файловой переменной F, будет удален. 1.3. Примеры программ. Пример 1. Написать программу, записывающую в файл значения квадратных корней чисел от 1 до 100. Program PR1; Var F:File of REAL; I:INTEGER; Begin ASSIGN(F,'PR.DAT'); REWRITE(F); For I:=1 to 100 do WRITE(F,SQRT(I)); CLOSE(F) End. Пример 2. Написать программу, дописывающую к данным в файле PR.DAT квадраты чисел от 1 до 100. Program PR2; Var F:File of REAL; I:INTEGER; Begin ASSIGN(F,'PR.DAT'); RESET(F); SEEK(F,FILESIZE(F)); FOR I:=1 TO 100 DO WRITE(F,I*I); CLOSE(F) End. Пример 3. Написать программу, считывающую из файла PR.DAT данные и выводящую их на экран. При этом необходимо проверить, есть ли этот файл на диске и не пустой ли он. Program PR3; Var F:File of REAL; A:REAL; Begin ASSIGN(F,'PR.DAT'); {$I-} RESET(F); {$I+} If IORESULT<>0 then WRITELN('ФАЙЛ НЕ НАЙДЕН') else Begin If EOF(F) then WRITELN('ФАЙЛ ПУСТ') else REPEAT READ(F,A); WRITE(A) UNTIL EOF(F); CLOSE(F) End End. В этой программе, для того чтобы не было выведено сообщение об ошибке, перед открытием несуществующего файла с помощью процедуры RESET, отключен контроль операций ввода-вывода - директива компилятору {$I-}. После процедуры RESET - этот контроль включен - {$I+} и проанализировано значение функции IORESULT. Эта функция вызывается только после директивы {$I-}. Значение 0 - соответствует отсутствию ошибки ввода-вывода, если ошибка есть - то значение будет не нулевое. 2. ТЕКСТОВЫЕ ФАЙЛЫ. 2.1. Текстовые файлы. В отличие от файлов других типов текстовые файлы не являются просто последовательностью элементов одного типа, а состоят из символов, объединенных в строки. Поскольку строки могут быть различной длины, текстовые файлы могут обрабатываться только последовательно. Файл может быть открыт либо только на запись, либо только на чтение. В Turbo-Pascal, начиная с версии 3, имеется возможность открыть файл для расширения. В этом случае после открытия указатель файла устанавливается в конец, файл рассматривается как открытый на запись. Текстовый файл описывается следующим образом: <имя переменной>:TEXT; Пример: VAR F:TEXT; В языке Turbo-Pascal связь с внешними устройствами: консолью, принтером, модемом и т.д., осуществляется с помощью текстовых файлов. Указанные внешние устройства имеют свои символические обозначения, опишем их. Консоль. CON: Это устройство ввода-вывода. Выходное консольное устройство - дисплей, входное - клавиатура. Консоль обеспечивает буферизованных ввод данных. Это означает, что данные с консоли вводятся в виде целой строки и только после введения всей строки обрабатываются. Ввод строки заканчивается нажатием клавиши ввода. До ее нажатия можно не только вводить символы, но и редактировать набираемую строку. Печатающее устройство. PRN: Это устройство вывода данных на печать. Если к ПК подключено несколько принтеров, то доступ к ним осуществляется по логическим именам LPT1, LPT2 и LPT3. Имена PRN и LPT1 первоначально синонимы. Стандартный библиотечный модуль PRINTER объявляет имя файловой переменной LST и связывает его с логическим устройством LPT1. Это дает возможность использовать простое обращение к принтеру. Например, программа Uses Printer; BEGIN Writeln(LST,'Hello!') END. выведет на принтер фразу "Hello!". Вспомогательное устройство. AUX: Это логическое имя коммуникационного канала, который обычно используется для связи с другими ПК. Как правило, ПК имеет два таких канала, которым даются имена COM1 и COM2. Первоначально имена AUX и COM1 синонимы. "Пустое" устройство. NUL: Это устройство чаще всего используется в отладочном режиме и трактуется как устройство-приемник неограниченной емкости. Любой программе доступны два предварительно объявленных файла со стандартными файловыми переменными INPUT и OUTPUT. INPUT - для чтения данных с клавиатуры, OUTPUT - для вывода на экран. 2.2. Операции над текстовыми файлами. Текстовые файлы состоят из строк, поделенных на символы. Каждая строка заканчивается парой символов CR/LF (carriage return / line feed), файл заканчивается символом CTRL-Z. CTRL-Z заносится в открытый файл в момент его закрытия. Здесь CR спецсимвол с кодом ASCII равным 13. Этот символ, соответсвующий нажатию клавиши ввода. LF - спецсимвол с кодом ASCII равным 10. Это символ перевода строки. CTRL-Z - спецсимвол с кодом ASCII равным 26. Текстовые файлы могут быть связаны либо с набором данных, либо с логическими устройствами. В первом случае обработка данных, содержащихся в файле, может осуществляться после выполнения процедуры ASSIGN и одной из процедур RESET, REWRITE, APPEND. По окончании обработки должна быть выполнена процедура CLOSE. Во втором случае, то есть при связи файла с устройством, нужно воспользоваться процедурой ASSIGN, а затем приступить к обработке файла. Выполнение процедуры ASSIGN в этом случае влечет за собой неявное открытие файла, поэтому использование процедур RESET и REWRITE излишне, а их выполнение, как и выполнение процедуры CLOSE, не приводит ни к каким результатам. В силу этого закрыть файл, связанный с устройством, можно только другой процедурой ASSIGN. Рассмотрим основные процедуры и функции, связанные с текстовыми файлами. Везде далее F-файловая переменная типа TEXT. Процедура ASSIGN. Синтаксис: ASSIGN (F,NAME). Здесь NAME строка, задающая имя текстового файла на диске или имя логического устройсва. Файл F не должен быть открыт. Эта процедура связывает файл F с набором данных либо с логическим устройством, имя которого определено в строке NAME. Процедура RESET. Синтаксис: RESET (F). Эта процедура открывает файл F только для чтения. Файл F должен существовать. Процедура REWRITE. Синтаксис: REWRITE (F). Открывает файл только для записи. Если файл F не существует, то он будет создан. Если файл F существует, то он будет уничтожен и создан пустой файл с этим именем. Процедура APPEND. Синтаксис: APPEND (F). Открывает файл F для расширения. Указатель файла устанавливается на конец файла. Файл F должен существовать. Эта процедура есть в Turbo Pascal, начиная с версии 3. Процедура READ. Синтаксис: READ (F,P). Здесь имя P -имя переменной или список имен переменных типа CHAR, STRING, INTEGER или REAL. Если F имя предопределенной переменной INPUT, то его можно опустить - READ(P). Процедура READ читает последовательность символов из файла F, интерпретирует их как условные записи значений данных, а затем присваивает данные с этими значениями переменным P. Тип данных в файле должен совпадать с типом переменных. Процедура READLN. Синтаксис: READLN (F,P). Эта процедура эквивалентна паре процедур: READ(F,P); READLN(F). Процедура READLN (F) осуществляет перевод строки. Таким образом, после чтения переменных из списка P, оставшаяся часть строки до ее конца (если она есть) пропускается и последующее чтение осуществляется с первого символа следующей строки. Если F совпадает с INPUT ,то его можно опустить. Процедура WRITE. Синтаксис: WRITE (F,P). Здесь P -имя переменной или список имен переменных типа CHAR, STRING, INTEGER, REAL или BOOLEAN. Процедура WRITE вводит в файл F последовательность символов, состоящую из условных записей значений величин, представленных выражениями списка P. Если F- это OUTPUT, то его можно пропустить. Процедура WRITELN. Синтаксис: WRITELN (F,P). Эта процедура эквивалентна выполнению двух процедур: WRITE(F,P); WRITELN(F). Процедура WRITELN(F) записывает в файл пару символов CR/LF. Если F - OUTPUT, то его можно пропустить. Процедура CLOSE. Синтаксис: CLOSE (F). Закрывает файл F. Функция EOF. Синтаксис: EOF (F):BOOLEAN. В случае, когда F - INPUT, его можно опустить. Если файл F связан с набором данных, то результатом EOF будет TRUE, когда указатель файла находится перед CTRL-Z или в конце файла F. В противном случае значение EOF равно FALSE. Если файл F связан с логическим устройством, то результатом EOF будет TRUE, когда последним интерпретируемым символом будет CTRL-Z. В противном случае - результат равен FALSE. Функция EOLN. Синтаксис: EOLN (F):BOOLEAN. Если F-INPUT, то его можно опустить. Если F связан с набором данных, то результатом функции EOLN будет TRUE, когда указатель файла находится перед символом CR или когда результат EOF будет TRUE. В противном случае значение EOLN будет FALSE. Если файл связан с логическим устройством, то результатом функции EOLN будет TRUE, когда последним интерпретируемым символом будет CR или когда результат EOF-TRUE. В противном случае результат EOLN равен FALSE. !!! Обратите внимание на отличие процедур RESET, REWRITE, READ и WRITE для типизированных и текстовых файлов. !!! 2.3. Пример. Написать программу, запрашивающую имя программы на Паскале (без расширения) и выводящую на экран ее текст и число строк в ней. Program PR; Var F:text; L:string; Q:integer; Name:string[8]; FullName:string[12]; BEGIN Write('Введите имя программы на Паскале:'); Readln(Name); FullName:=Name+'.PAS'; Assign(F,FullName); {$I-} Reset(f); {$I+} if IOResult<>0 then Writeln('Файл не найден') else BEGIN Writeln('Текст программы :',FullName); Q:=0; while not(EOF(F)) do BEGIN Readln(F,L); Writeln(L); Q:=Q+1 END; Close(F); Writeln('Число строк в файле равно:',Q) END END. 3. НЕТИПИЗИРОВАННЫЕ ФАЙЛЫ. 3.1. Нетипизированные файлы. Нетипизированные или блочные файлы отличаются тем, что для них не указан тип компонент. Отсутствие типа делает эти файлы совместимыми с любыми другими файлами, а также позволяет организовать высокоскоростной обмен между диском и памятью. Описание типа нетипизированного файла состоит из служебного слова - FILE. Пример: var F1,F2:FILE; Процедуры ASSIGN, RESET, REWRITE, CLOSE, SEEK, RENAME, ERASE и функции EOF, FILEPOS, FILESIZE выполняют те же действия, что и для типизированных файлов. Процедуры READ и WRITE заменяются соответственно на высокоскоростные процедуры BLOCKREAD и BLOCKWRITE. При открытии нетипизированного файла процедурами RESET и REWRITE можно указать длину блока нетипизированного файла в байтах. Например, RESET(F,512) Если длина блока не указана, то она принимается равной 128 байтам. Процедура BLOCKREAD. Синтаксис: BLOCKREAD (F,B,C) или BLOCKREAD (F,B,C,R). Здесь F - имя файловой переменной, представляющей нетипизированный файл; B имя произвольной программной переменной; C - целое число; R - имя переменной типа INTEGER. Процедура BLOCKREAD выводит из файла F в область оперативной памяти, занимаемой переменной B, C блоков. Если в процедуре указан параметр R, то этой переменной будет присвоено значение, определяющее количество выведенных блоков. Если величина R меньше C, то это означает, что указатель файла находится в конце файла. Указание параметра R блокирует возникновение ошибки ввода-вывода. Процедура BLOCKWRITE. Синтаксис: BLOCKWRITE (F,B,C) или BLOCKWRITE (F,B,C,R). Здесь F, B, C, R означают то же самое, что и в процедуре BLOCKREAD. Процедура BLOCKWRITE записывает в файл F из области оперативной памяти, занимаемой переменной B, C блоков. Если в процедуре указан параметр R, то этой переменной будет присвоено значение, определяющее фактическое количество записанных блоков. Если эта величина меньше C, то процедура выполнена неправильно. Указание параметра R блокирует возникновение ошибки ввода-вывода. 3.2. Примеры программ. Пример 1. Написать программу, удаляющую с диска файл, имя которого будет введено с клавиатуры. Тип элементов файла не имеет значения. PROGRAM PR; VAR F:File; FName:STRING[20]; BEGIN Write ('Введите имя удаляемого файла:'); ReadLn (FName); Assign (F,Fname); Write ('Файл ',FName); {$I-} Erase (F); {$I+} IF IOResult <> 0 THEN WriteLn (' не существует.') ELSE WriteLn (' удален.') END. Пример 2. Написать программу, копирующую файл с произвольным типом элементов. PROGRAM PR; CONST RecSize=512; VAR FileIn, FileOut:File; Buffer:ARRAY [1..RecSize] Of Byte; NameIn, NameOut:String[12]; NumRead, NumWrite:Word; BEGIN Write('Введите имя программы-источника:'); ReadLn(NameIn); Assign(FileIn,NameIn); {$I-} Reset(FileIn,1); {$I+} IF IOResult<>0 THEN WriteLn('Файл не найден') ELSE BEGIN Write('Введите имя программы-приемника:'); ReadLn(NameOut); Assign(FileOut,NameOut); Rewrite(FileOut,1); WHILE NOT(EOF(FileIn)) DO BEGIN BlockRead(FileIn,Buffer,RecSize,NumRead); BlockWrite(FileOut,buffer,NumRead,NumWrite) END; Close(FileOut); Close(FileIn); WriteLn('Копирование закончено') END END. КОНТРОЛЬНАЯ РАБОТА ПО ИНФОРМАТИКЕ N 1 Для решения задач рекомендуется использовать язык программирования Паскаль. РАЗДЕЛ 1. ТИПИЗИРОВАННЫЕ ФАЙЛЫ. 1. Написать программу, которая создает типизированный файл RANDOM.DAT типа FILE OF INTEGER, состоящий из 200 случайных целых чисел, принадлежащих отрезку [-200, 300]. 2. Написать программу, которая для чисел, содержащихся в файле RANDOM.DAT, находит среднее арифметическое этих чисел, наименьшее и наибольшее из этих чисел. Результат вывести на экран. РАЗДЕЛ 2. ТЕКСТОВЫЕ ФАЙЛЫ. 1. Написать программу, выводящую на экран свой текст. 2. Дан текстовый файл ABC.TXT: а) Написать программу, преобразующую файл ABC.TXT в файл CBA.TXT, в котором все строки заменены на строки читаемые с конца к началу. Так, например, строка PRIMER должна замениться на строку REMIRP. Содержимое файлов ABC.TXT и CBA.TXT выведите на экран. б) Написать программу, преобразующую файл ABC.TXT в файл 123.TXT. Строки изменяются по следующему правилу: все цифры заменяются на *. Содержимое файлов ABC.TXT и 123.TXT вывести на экран. 3. Дан текстовый файл INPUT.TXT, содержащий в каждой строке по три целых числа, разделенных одним или несколькими пробелами. Количество строк в файле заранее не известно. Написать программу, которая создает файл OUTPUT.TXT, содержащий в каждой строке минимальное из чисел, соответствующей строки файла INPUT.TXT. Содержимое файлов INPUT.TXT и OUTPUT.TXT вывести на экран. Замечание. Файлы ABC.TXT, INPUT.TXT создать в любом текстовом редакторе. РАЗДЕЛ 3.НЕТИПИЗИРОВАННЫЕ ФАЙЛЫ. Написать программу, переименовывающую файл с произвольным типом элементов. Имя файла вводится с клавиатуры. МЕТОДИЧЕСКИЕ УКАЗАНИЯ К КОНТРОЛЬНОЙ РАБОТЕ ПО ИНФОРМАТИКЕ N 2 ТЕМА: МНОЖЕСТВА. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ. 1. МНОЖЕСТВА. 1.1. Определение множества. Операции над множествами. В Паскале множество - это совокупность элементов одного и того же базового типа. Значения базового типа определяют перечень всех элементов, которые могут содержаться в данном множестве. В качестве базового типа может выступать любой порядковый тип. Размер множеств в Паскале ограничен, обычно в множестве не может быть более 256 элементов. Следовательно, множество, содержащее целые числа не может иметь в качестве базового типа - тип INTEGER. Во множествах допустимы только такие элементы, порядковые значения которых не выходят за границы отрезка 0..255. Для целочисленных множеств это означает, что в них могут присутствовать только числа из отрезка 0..255. то есть целые отрицательные числа и целые числа больше 255 в качестве элементов множества не допустимы. Описание множественного типа имеет следующий вид: <имя множественного типа> = SET OF <базовый тип> Можно также описать тип непосредственно при объявлении некоторой переменной. Например: TYPE BOOL=SET OF BOOLEAN; CHARS=SET OF CHAR; DAY=SET OF 1..31; VAR A,B:BOOL; C,D:SET OF 'A'..'Z'; Множественные константы состоят из заключенного в квадратные скобки списка констант, относящихся к одному порядковому типу. Этот список может быть пустым, может содержать повторения и интервалы. Например, ['P','A','S','C','A','L'] [4,6,0,0,2,1] [] [1,2,10..20] [5..1] В последнем случае, так как ORD(5)>ORD(1), указанный интервал определяет пустое множество, аналогично ['Z'..'K'] - также пустое множество (ORD('Z')>ORD('K'). Переменные-множества или множественные переменные - это подмножества из элементов базового типа. Множественные выражения состоят из обращений к множественным переменным, константам и конструкторам, а также из операторов объединения, разности и пересечения множеств. Конструкторы множеств по виду близки к множественным константам, но элементами их списка могут быть не только константы базового типа, но и произвольные выражения этого типа. Например, [10..I], где I - целое число, причем если I<10, то это пустое множество. Перечислим операции над множествами в порядке старшинства: 1) * – пересечение множеств; 2) + – объединение множеств; - – разность множеств; 3) = – равенство множеств; <> – неравенство множеств; >= или <= – включение одного множесва в другое; IN – принадлежность элемента множеству. Примеры: Выражение Значение выражения [1..5]*[2,4,15] [2,4] [1..5]+[2,4,15] [1..5,15] [1..5]-[2,4,15] [1,3,5] [1..5]=[1,5,4,3,2] TRUE [1..5]<>[1..3,5,4] FALSE 1 IN [1..5] TRUE NOT('a' IN ['a'..'x'] FALSE Оператор присваивания используется обычным способом: <имя множественной переменной>:=<множественное выражение>, отметим, что тип множественной переменной и множественного выражения должны быть одинаковы. 1.2. Примеры программ. Из двух первых сотен натуральных чисел найти все простые. program prime; const n=200; type mn=set of 2..n; var pq,qq:mn; p,k:2..n; begin qq:=[2..n]; pq:=[]; p:=2; repeat while not(p in qq) do p:=p+1; pq:=pq+[p]; k:=p; repeat qq:=qq-[k];k:=k+p until k>n; until qq=[]; for k:=2 to n do if k in pq then write(k,' '); writeln; readln end. Задание. Наберите программу и изучите ее работу. Протестируйте программу, изменяя значения константы N. 2. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ. Кроме обычных констант в Турбо Паскале допускается использование типизированных констант. Они занимают промежуточное положение между обычными константами и переменными. Типизированные константы имеют следующие характеристики: 1)Они описываются в разделе описаний констант вместе с обычными константами. 2)Так же, как и обычные константы, типизированные константы получают при описании начальное значение. 3)Аналогично переменным, типизированные константы имеют тип, который задается при их описании, а также могут получать новые значения. Типизированные константы можно использовать точно так же, как и обычные переменные таких же типов. Поэтому фактически они представляют собой переменные с начальными значениями. Типизированная константа приобретает указанное в объявлении значение лишь один раз - в момент начала работы программы. При повторном входе в блок, в котором она объявлена, переинициация не производится и типизированная константа сохраняет то значение, которое имела в момент выхода из блока. Типизированные константы могут быть любого типа, кроме файлов. Нельзя также объявить типизированную константу - запись, если хотя бы одно из ее полей является полем файлового типа. Так как типизированная константа фактически не отличается от переменной, то ее нельзя использовать в качестве значения при объявлении других констант или границ типа - диапозона. Для типизированных констант составных типов введены удобные способы задания начальных значений. Описание типизированной константы имеет следующий вид: <идентификатор>:<тип>=<значение> 2.1. Константы простых типов и типа STRING. Примеры объявлений: Сonst Name:String='Вирт.Н'; Year:Integer=1994; X:Real=0.1; Min:Integer=0; Max:Integer=10; Days:1..31=1; A: Char='Y'; 2.2. Константы - массивы. В качестве начального значения типизированной константы - массива используется список констант, отделенных друг от друга запятыми; список заключается в круглые скобки, например: Const Vektor:Array[1..5] of Byte = (0,0,0,0,0); В качестве значения массива - константы типа CHAR допускается задание символьной строки соответствующей длины. Два следующих объявления равносильны: Const Digit1:Array[0..9] of Char=('0','1','2','3','4','5','6','7','8','9'); Digit2:Array[0..9] of Char ='0123456789'; При объявлении многомерных констант - массивов множество констант, соответствующих каждому измерению, заключается в дополнительные круглые скобки и отделяется от соседнего множества запятыми. Самые внутренние множества констант связываются с изменением самого правого индекса массива. Например, зададим описание нулевой и единичной квадратных матриц размера 3*3: Type Massiv = Array [1..3,1..3] of Real; Const N:Massiv = ((0,0,0), (0,0,0), (0,0,0)); E : Massiv = ((1,0,0), (0,1,0), (0,0,1)); 2.3. Константы - записи. При объявлении константы - записи список значений полей заключается в круглые скобки и имеет следующий вид: <имя поля>:<константа> Элементы списка отделяются друг от друга точкой с запятой, например: Type Complex = Record x,y : Real End; Date = Record d: 1..31; m: 1..12; y: Integer End; Const I:Complex = (x:0; y:1); N:Complex = (x:0; y:0); A:Date = (d:10; m:11; y:1994); B:Date = (d:1; m:4; y:1995); Поля должны указываться в той последовательности, в какой они перечислены в объявлении типа. Для записей с вариативными полями указывается только один из возможных вариантов констант. 2.4. Константы - множества. Значение типизированной константы - множества задается в виде правильного конструктора множества, например: Type Digc = Set of '0'..'9'; Days = Set of 1..31; Const EvenDigits:Digc = [ '0', '2', '4', '6', '8' ]; A:Days = [ 1..5,8,10..20 ]; 2.5. Константы - указатели. Единственным значением типизированной константы - указателя может быть только NIL, например: Const Pr : ^Real = NIL; Замечание. Динамические переменные и указатели будут рассмотрены в следующей работе. КОНТРОЛЬНАЯ РАБОТА ПО ИНФОРМАТИКЕ N 2 Для решения задач рекомендуется использовать язык программирования Паскаль. РАЗДЕЛ 1. МНОЖЕСТВА. 1. Напишите программу, подсчитывающую число заданных букв (например: ['A','a','B','b','c','C']) в произвольной строке. 2. Дана непустая последовательность символов. Требуется построить и напечатать множество, элементами которого являются встречающиеся в последовательности: а) буквы от "Т" до "Х" и знаки препинания; б) цифры от "5" до "9" и знаки арифметических операций. 3. Дана непустая последовательность слов из строчных русских букв; между соседними словами - запятая, за последним словом - точка. Напечатать в алфавитном порядке все гласные буквы, которые входят в каждое слово. РАЗДЕЛ 2. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ. 1. Написать программу, которая по введенной цифре печатает ее название, например: 1-один. Название цифр задать константой - массивом. 2. Написать программу, которая по введенному номеру месяца сообщает сезон года - зима, весна, лето, осень. Сезон го да задать с помощью константы массива. 3. Написать программу, сравнивающую введенную дату с фиксированной. Фиксированная дата задается константой - записью. 4. В старояпонском календаре был принят 60-летний цикл, сос тоявшийиз пяти 12-летних подциклов. Подциклы обозначались названиями цвета: зеленый, красный, желтый, белый и черный. Внутри каждого подцикла годы носили названия животных: крысы, коровы, тигра, зайца, дракона, змеи, лошади, овцы, обезьяны, курицы, собаки и свиньи. (1984 год - год зеленой крысы - был началом очередного цикла). Написать программу, которая вводит номер некоторого года нашей эры и печатает его название по старояпонскому календарю. Цвет и название года задать с помощью констант - массивов. МЕТОДИЧЕСКИЕ УКАЗАНИЯ К КОНТРОЛЬНОЙ РАБОТЕ ПО ИНФОРМАТИКЕ № 3 ТЕМА: МОДУЛИ В ЯЗЫКЕ ТУРБО-ПАСКАЛЬ. 1. МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ. 1.1. Модули. Современная технология программирования предполагает представление алгоритма решения задачи в виде композиции отдельных шагов – подзадач, которые реализуются с помощью процедур и функций. Однако для реализации больших программных комплексов возможностей процедур и функций, размещаемых в тексте основной программы, оказывается недостаточно Остаются следующие проблемы: - Большой объем текста программы затрудняет ее понимание. - После внесения в программу изменений она должна быть перекомпилирована, что может потребовать значительных затрат времени. - Объем памяти, выделяемый под программу, ограничен. Для решения этих проблем используются модули. Программный модуль содержит описание типов данных, переменных, процедур и функций, оформленное в виде отдельного файла. Все объекты, описанные в модуле можно использовать в других программах и модулях с помощью специальных средств подключения модуля. Модуль может разрабатываться и компилироваться независимо от основной программы и занимает отдельное место в памяти при выполнении. При использовании модуля основной программе достаточно знать только имена и типы параметров процедур и функций в модуле. Сами алгоритмы выполнения процедур и функций знать необязательно. Возможны три основных типа модулей: - Исходные модули (в виде текста на языке программирования) – позволяют решить только проблему уменьшения текста программы. Подключаются в программу на Паскале с помощью директивы компилятору {$I <имя файла>} - Загрузочные модули – компилируются отдельно и потом подключаются к откомпилированной вызывающей программе с помощью специальной программы –редактора связей. Она проверяет правильность вызовов. Такой способ используется в языке С/C++. Недостаток – компилятор не может проверить правильность вызовов процедур и функций. - Пакетные модули – состоят из двух частей – интерфейсной, которая содержит описание всех объектов, доступных вызывающей программе, и исполняемой части – содержащей тела процедур и функций и скрытой от вызывающей программы. При компиляции вызывающе программы компилятор производит проверку правильно обращения к объектам из модуля. Модули могут компилироваться отедельно. Необходимости в редакторе связей нет. Пакетные модули реализованы были впервые в языке Модула-2, затем в языке АДА и вошли как расширения языка Паскаль в систему Турбо-Паскаль. Далее будем называть их просто модули. Модуль – автономно комплирируемая программная единица, содержащая различные компоненты раздела описаний (типы, константы, переменные, процедуры и функции), а также инициирующую часть, которая выполняется при запуске вызывающей программы. Модуль не является самостоятельной выполнимой программой – его ресурсы используются другими программами и модулями. Для подключения модулей используется служебное слово Uses, которое должно стоять после заголовка программы и до любого другого раздела описания Uses <список модулей> Uses Crt, Grpah; 1.2. Структура модуля. Модуль имеет следующую структуру Unit <имя>; {заголовок модуля} Interface {интерфейсная часть} Uses <список используемых модулей>; <описание типов, переменных, констант, процедур и функций,доступных из вызывающей программы> implementation {исполняемая часть – раздел реализации} <описание объектов, скрытых от вызывающей программы> [begin {инициирующая часть} <операторы инициализации модуля>] end. Unit <имя> - заголовок модуля, имя – имя модуля. Имя должно !!! совпадать с именем файла, вкоторый помещается исходный текст модуля. Например, Unit Vectors; должен лежать в файле Vectors.pas Имя модуля используется для связи модуля с вызывающей программой и указывается в программе в предложении Uses. Interface – открывает интерфейсную часть, содержащую список объектов, доступных вызывающей программе. При описании процедур и функций в интерфейсной части указываются только их заголовки (без тел). Implemetation – служебное слово, начинающее исполняемую часть. В Исполняемой части содержатся тела процедур и функций, объявленных в интерфейсной части. Перед телом процедуры или функции необходимо записать ее заголовок, а список формальных параметров и тип результата можно второй раз не писать (если же пишем, то оба списка должны совпадать). Кроме того, исполняемая часть может содержать описание локальных объектов – типов, констант, переменных, процедур. Эти объекты могут использоваться только в модуле, и не доступны в вызывающей программе. Инициирующая часть содержит операторы, выполняющиеся до передачи управления основной программе. Инициирующая часть обычно подготавливает работу модуля, например, присваивает начальные знчения переменным, открывает файлы и т.п. Инициирующую часть (если она не нужна) можно пропустить вместе со словом begin Модуль с помощью Uses может использовать другие модули. Запрещается прямое или косвенное обращение модуля к самому себе. Основной программе доступны все объекты, описанные в интерфейсной части модуля. Если в программе описывается объект с тем же именем, что в модуле, то он перекроет его. Доступ к перекрытому объекту возможен с помощью составного имени <имя модуля>.<имя объекта>. Пример модуля для работы с векторами: Unit vectors; interface Const n=2; Type vector = array[1..n] of real; Var Nul:vector; Procedure add (a,b: vector; var c:vector); Procedure mult (k:real, a: vector; var c: vector); Function pr(a,b:vector): real; procedure WriteVector(a:vector); Procedure ReadVectot(var a:vector); Implementation Procedure add; Var i: integer; Begin For i:=1 to N do c[i]:=a[i] + b[i]; End; ... procedure WriteVector; Var i: integer; Begin Write(‘( ‘); For i:=1 to N do Write(a[i]:0:4,’ ’) Writeln(‘)’) End; Var i: integer; Begin For i:=1 to N do nul[i]:=0; End. Основная программа Program rrr; Uses Vectors; Var a,b,c: vector; Nul: vector; Begin ReadVector(a); ReadVector(b); Add(a,b,c); Writevector (С); A:=Vectors.Nul End. 1.3.Компиляция модулей. При компиляции модуля образуется дисковый файл c расширением TPU. Имя совпадает с именем модуля. При трансляции программы или модуля, использующих другие модули, компилятор работает в одном из трех режимов – Compile, Make и Build (выбирается соответствуюшая команда в меню Compile). В режиме Compile все используемые модули должны быть предварительно откомпилированны и все соответствующие tpu-фалйы должны присутствовать на диске. В режиме Make компилятор предварительно проверяет наличие TPUфайлов. Если какой-то файл не обнаружен, то система пытается отыскать соответствующий файл с расширением PAS и компилирует его. Также в этом режиме компилируются те файлы, которые были изменены. В режиме Build все модули, для которых есть исходный текст перекомпилируются, независимо от того, есть ли соответсвующие TPU-файлы. 2. СТАНДАРТНЫЕ МОДУЛИ. 2.1. Стандартные модули в Турбо-Паскале. В Турбо-Паскале имеется 8 стандартных модулей, которые содержат большое количество разнообразных типов, констант, переменных, процедур и функций. 1. System - подключается автоматически к любой программе (не надо включать в Uses). Содержит все процедуры и функции стандартного паскаля (sin, abs, reset, writeln, …) и много процедур и функций, не вошедших в другие модули (inc, blockread…) 2. Printer – упрощает вывод на принтер. В нем определяется файловая переменная LST, которая связана с принтером. Пример: Uses Printer; Begin Writeln(LST,’Hello!’); End. 3. Crt- содержит процедуры и функции для работы с экраном в текстовом режиме – перемещение курсора, изменение цветов, очистка экрана, создание окон. Также включены средства для работы с клавиатурой GotoXY(X,Y) X- 1..80, Y- 1..25 TextColor( цвет) Цвет – 0..15 – цвет символов. Есть специальные константы, например Red, Blue, Yellow, Green, LightRed, White, black и т.д. TextBackground(цвет) – цвет фона 0..7 ClrScr – очистка экрана Keypressed – истина, если была нажата клавиша Readkey – возвращает значение типа CHAR – код нажатой клавиши 4. DOS – содержит процедуры и функции для доступа к средствам MSDOS, например, определение свободного места на диске, списка файлов каталоге, запуска внешних программ. 5. Graph- процедуры и функции для работы с экраном в графическом режиме Остальные модули Turbo3, Graph3, Overlay в настоящее время практически не используются. 2.2. Работа с графикой Для работы с графикой в Турбо-Паскале применяется модуль Graph. При работе в графическом режиме любая информация состоит из отдельных точек – пикселов. Пикселы имеют координаты относительно левого верхнего угла экрана. Каждый пиксел имеет свой цвет. Количество точек и количесто цветов на экране зависит от графического режима. В свою очередь возможность выбора того или иного графического режима определяется возможностями видеоадаптера. Драйверы, входящие в поставку Турбо-Паскаля допускают максимально только 640*480*16 цветов. Будем работать именно в этом режиме. Графическая библиотека Турбо-Паскаля (BGI) включает в себя модуль GRAPH, драйверы для разных видеоадапетров, файлы с расширением bgi, и шрифты, файлы с расширением chr. Чтобы ваша программа работала, необходимо наличие драйвера EGAVGA.BGI. В начале программы необходимо инициализировать графический режим с помощью вызова процедуры INITGRAPH InintGraph(Var Dr,Reg:integer; Path:String); Здесь: Dr – тип драйвера, Reg – номер видеорежима; Path – путь к файлу драйвера. Тип драйвера можно установить либо Detect, либо в зависимости от адаптера, например VGA (Detect и VGA - константы, определенные в модуле Graph). Если тип драйвера Detect, то режим можно не задавать, он определится автоматически. Например, Var Driver,Mode: integer; begin Driver:=Detect; InitGraph(Driver,Mode,’’); ... end. Если при инициализации произошла ошибка, то посмотреть это можно с помощью процедуры GraphResult. Если GraphResult равно нулю, то ошибок нет, иначе получим код ошибки. При вызове процедуры код сбрасывается. Узнать что за ошибка был можно с помощью процедуры GraphErrorMsg(код:integer):string – сообщение об ошибке. Например, Error:=GrpahResult; If Error <> 0 then Writeln(GraphErrorMsg(Error)) Else ... Выход из графического режима осуществляется процедурой CloseGraph. Приведем описания наиболее часто используемых при работе с графикой в Турбо-Паскале процедур, функций, констант. Функция GetMaxX:integer - максимально возможные координаты по горизонтали. GetMaxY: integer – максимально возможные координаты по вертикали. GetX, GetY – текущие координаты. MoveTo(x,y) – устанавливает указатель в положение x,y. ClearDevice – очистка экрана и сброс всех установок OutTextXY(x,y, текст) - вывод текста на графический экран Рисование линий, точек, многоугольников, эллипсов SetColor(цвет) –установка цвета линии. SetBkColor – установка цвета фона. PutPixel(x,y, цвет) – точка. Line (x1,y1,x2,y2) – линия текущим цветом. LineTo(x,y) - линия от текущего положения указателя. Rectangle(x1,y1,x2,y2) – прямоугольник. Circle(x,y,радиус) - окружность. Arc(x,y,нач угол, кон угол, радиус)- дуга (углы в градусах 0..360). Ellipse(x,y, нач угол, кон угол, Rx,RY) – дуга эллипса. Закраска и рисование областей на экране SetFillStyle(штриховка, цвет) - SolidFill – сплошная, Linefill – линиями, . Bar(x1,y1,x2,y2) – штрихует прямоуг. FloodFill(x,y,цв. Границы)- штрихует замкн. Область. FillEllipse(x,y,rx,ry); Константы цвета Black =0 {Черный} DarkGray = 8 {Темносерый} Blue =1 {Синий} LightBlue = 9 {Яркосиний} Green =2 {Зеленый} LightGreen =10 {Яркозеленый} Cyan =3 {Голубой} LightCyan =11 {Яркоголубой} Red =4 {Красный} LightRed =12 {Розовый} Magenta =5 {Фиолетовый} LightMagenta=13 {Малиновый} Brown =6 {Коричневый} Yellow =14 {Желтый} LightGray=7 {Светлосерый} White =15 {Белый} Константы шаблона штриховки EmptyFill=0 Используется цвет фона SolidFill=1 Используется текущий цвет LineFill=2 --- штриховка LtSlashFill=3 /// штриховка SlashFill=4 /// толстая штриховка BkSlashFill=5 \\\ толстая штриховка LtBkSlashFill=6 \\\ штриховка HatchFill=7 +++ штриховка XHatchFill=8 ххх штриховка InterleaveFill=9 Штриховка в клетку WideDotFill=10 Штриховка редкими точками CloseDotFill=11 Штриховка частыми точками UserFill=12 Штриховка определяемая пользователем Константы типов и толшины линий Тип линий: SolidLn=0 {Сплошная } DottedLn=1 {Точечная } CenterLn=2 {Штрихпунктирная } DashedLn=3 {Пунктирная } UserBitLn=4 {Определяемая пользователем} Толщина линий: NormWidth=1 {Нормальная} ThickWidth=3 {Тройная } Имена основных процедур и функций Arc, Bar, Bar3D, Circle, ClearDevice, CloseGraph, DetectGraph, Ellipse, FillEllipse, FloodFill, GetBkColor, GetColor, GetDriverName, GetGraphMode, GetMaxColor, GetMaxX, GetMaxY, GetModeName, GetPixel, GetX GetY, InitGraph, Line, LineRel, LineTo, MoveRel, MoveTo, OutText, OutTextXY, PieSlice, PutPixel, Rectangle, RestoreCrtMode, Sector, SetActivePage, SetBkColor, SetColor, SetTextStyle, SetUserCharSize, SetVisualPage, TextHeight, TextWidth. Формат записи этих процедур и функций и примеры их использования смотрите в помощи интегрированной среды Турбо Паскаля или в литературе, указанной дальше. Пример использования модуля GRAPH. На экран выводятся режим дисплея и его номер, максимальное значение координат X,Y и номера цвета. Рисуются различные фигуры. Program PrimerGraph; Uses Crt, Graph; Var DriverVar,ModeVar:integer; {Номер драйвера и режима дисплея} i:integer; st:string; Begin DriverVar:=Detect; {Автоматическое определение DriverVar,ModeVar} InitGraph(DriverVar,ModeVar,''); {Инициализация графического режима} Str(GetGraphMode,st); {Определение режима дисплея} OutTextXY(1,1,'Режим дисплея - '+GetModeName(GetGraphMode)+' ,его номер - '+st); Str(GetMaxX,st); {Определение максимального X} OutTextXY(1,10,'Максимальная координата X - '+st); Str(GetMaxY,st); {Определение максимального Y} OutTextXY(1,20,'Максимальная координата Y - '+st); Str(GetMaxColor,st); {Определение максимального номера цвета} OutTextXY(1,30,'Максимальный номер цвета - '+st); For i:=0 to GetMaxColor do {Рисование линий всех цветов} begin SetColor(i); {Установка цвета линии} Line(10,50+2*i,GetMaxX-10,50+2*i) {Рисование линии} end; SetFillStyle(1,4); {Установка типа штриховки и цвета фигуры} Bar(100,100,200,200); {Рисование закрашенного прямоугольника} SetColor(11); {Установка цвета линии} Circle(200,200,50); {Рисование окружности} SetFillStyle(4,10); {Установка типа штриховки и цвета фигуры} FloodFill(200,200,11); {Закраска от точки 200,200 до границы цвета 11} repeat until keypressed; CloseGraph {Завершение работы в графическом режиме} End. КОНТРОЛЬНАЯ РАБОТА ПО ИНФОРМАТИКЕ N 3 Для решения задач рекомендуется использовать язык программирования Паскаль. РАЗДЕЛ 1. 1. МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ. Создайте модуль Rational.PAS, содержащий процедуры и функции для работы с рациональными числами. Рациональное число представляется в виде записи, содержащей два поля - числитель и знаменатель. Модуль должен содержать описание типа и следующие процедуры: ReadF - ввод рационального числа, WriteF - вывод рационального числа, Add - вычисление суммы двух рациональных чисел, Substract - вычисление разности двух рациональных чисел, Mult - вычисление произведения двух рациональных чисел, Divide - вычисление частного двух рациональных чисел. Вспомогательные процедуры вычисления наибольшего общего делителя и наименьшего общего кратного должны находиться внутри раздела реализации. С помощью созданного модуля напишите программу, вычисляющую рациональное число примерно равное числу e по следующей формуле: e=1+1/1!+1/2!+1/3!+...+1/n! Число n вводится с клавиатуры. РАЗДЕЛ 2. СТАНДАРТНЫЕ МОДУЛИ. 1. Нарисовать произвольный рисунок с использованием основных процедур и функций модуля GRAPH. 2. Нарисовать часы с движущимися стрелками. МЕТОДИЧЕСКИЕ УКАЗАНИЯ К КОНТРОЛЬНОЙ РАБОТЕ ПО ИНФОРМАТИКЕ № 4 ТЕМА: УКАЗАТЕЛИ И ДИНАМИЧЕСКАЯ ПАМЯТЬ. 1. УКАЗАТЕЛИ. 1.1. Указатели Все переменные, объявленные в программе, размещаются в одной непрерывной области оперативной памяти, которая называется сегментом данных. Длина сегмента данных составляет 64 KB. Динамическая память - это оперативная память ПК, предоставляемая программе при ее работе, за вычетом сегмента данных, стека и собственного тела программы. Динамическое размещение данных означает использование динамической памяти непосредственно при работе программы. Статическое размещение данных осуществляется компилятором Турбо Паскаля в процессе компиляции программы. При динамическом размещении заранее неизвестны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам. Оперативная память представляет собой совокупность ячеек для хранения информации - байтов, каждый из которых имеет собственный номер. Эти номера называются адресами, они позволяют обращаться к любому байту памяти. В ПК адреса задаются двумя двухбайтными числами (словами): номером сегмента и смещением. Сегмент - это участок памяти, имеющий длину 65536 байт (64К) и начинающийся с физического адреса, кратного 16: 0, 16, 32, 48...). Смещение показывает, сколько байт нужно отступить от начала сегмента, чтобы обратиться к нужному байту. Адрес байта памяти записываютв виде Сегмент:Смещение, а абсолютный адрес при этом равен Сегмент*16+Смещение (абсолютный адрес - номер байта от начала памяти). Пример: записать адрес 100-го байта в виде (Seg:Ofs) (0:100)=(1:84)=(2:68)=(3:52)=(4:36)=(5:20)=(6:4) В машинных языках адpес игpает pоль имени содеpжимого ячейки. Если в пpогpамме пеpеменной пpисваивается значение, то оно помещается в соответствующую этой переменной ячейку. Пpи вызове переменной по имени пpоисходит обpащение к содеpжимому ячейки, т.е. к значению пеpеменной. Представим себе пеpеменные, котоpые обладают именами, но их содеpжимым является не значение пеpеменной, а адpес ячейки памяти, где хpанится значение дpугой пеpеменной. Такие переменные называют указателями (ссылками). Указатель - это переменная, которая в качестве своего значения содержит адрес первого байта памяти, по которому записаны данные. Обычно указатель связывается с некоторым типом данных. Такие указатели называются типизированными. Можно объявлять указатели не связанные ни с каким конкретным типом данных. Они называются нетипизированными и составляют стандартный тип POINTER. Нетипизированные указатели совместимы с любыми указателями. Для задания типизированного указателя используется значок ^, который помещается перед идентификатором соответствующего типа. Например, Var P1: ^Integer; P: Pointer; Вся динамическая память в Турбо Паскале рассматривается как массив байтов и называется кучей. Начало кучи хранится в стандартной переменной HeapOrg, конец - в переменной HeapEnd, а текущая граница незанятой динамической памяти - в HeapPtr. 1.2. Статические и динамические пеpеменные До сих поp мы имели дело с пеpеменными, котоpые необходимо описывать в pазделе VAR пpогpаммы. Компилятоp после анализа этого pаздела отводит каждой пеpеменной определенное число ячеек памяти и закpепляет их за данной пеpеменной на все вpемя pаботы пpогpаммы. Такие пеpеменные называются статическими. Статические пеpеменные нельзя уничтожить, даже если в пpоцессе дальнейшей pаботы пpогpаммы они больше не понадобятся. Но данные можно хpанить в некотоpой ячейке, не обозначая ее именем пеpеменной, а используя лишь ссылку на эту ячейку. Такой вид доступа позволяет динамически "захватывать" и динамически "освобождать" память в пpоцессе pаботы пpогpаммы. Пеpеменные, котоpые могут создаваться и ликвидиpоваться по меpе надобности, называются динамическими. Обpащение к динамической пеpеменной пpоисходит посpедством указателя, котоpый содеpжит адpес динамической пеpеменной. Указатель статическая пеpеменная: она имеет имя и может быть явно упомянута в пpогpамме. Динамическая пеpеменная не имеет имени. Память для значения такой пеpеменной pезеpвиpуется и освобождается в пpоцессе pаботы пpогpаммы с помощью специальных встpоенных пpоцедуp. Использовать указатели в качестве паpаметpов пpоцедуp вывода в Turbo Pascal запpещено! Пpимеp описания указателей: var A,B: ^Integer; Описание пеpеменных как указателей еще не pезеpвиpует память для помещения значений динамических пеpеменных! В языке Pascal поддеpживаются тpи метода pаботы в динамически pаспpеделенной области памяти: 1) с помощью пpоцедуp New и Dispose; 2) с помощью пpоцедуp Mark и Release; 3) с помощью пpоцедуp GetMem и FreeMem. Рассмотpим все тpи метода. 1.3 Пpоцедуpа New Для создания динамических пеpеменных используется стандаpтная пpоцедуpа New, котоpая называется пpоцедуpой динамического pазмещения. Пpоцедуpа динамического pазмещения New - это единственный механизм создания динамических пеpеменных. Синтаксис: New(P); где P - имя указателя на динамическую пеpеменную. Пpи обpащении к пpоцедуpе выполняется pезеpвиpование участка в опpеделенном месте памяти компьютеp для pазмещения там значения динамической пеpеменной и помещение адpес этого участка в ссылочную пеpеменную P. Пpи этом выделяется столько ячеек памяти, сколько тpебует тип динамической пеpеменной, на котоpую указывает P. Эту инфоpмацию система получает из pаздела описания типов в пpогpамме. Еще pаз подчеpкнем, что пpоцедуpа динамического pазмещения выполняет две опеpации: 1) pезеpвиpует место в памяти для pазмещения значения динамической пеpеменной указанного типа; 2) пpисваивает указателю адpес этой пеpеменной. Пеpеменные, обpазованные стандаpтной пpоцедуpой New, хpанятся в памяти по стpуктуpе похожей на стек и называемой heap ("куча"). Пpи использовании динамических пеpеменных пpоисходит постоянный пpоцесс изменения ("пеpемещения") указателя. Если пpи этом некотоpые пеpеменные оказываются ненужными, то ссылки на уже ненужные динамические пеpеменные уничтожаются и в pезультате получается следующая каpтина: в "куче" пpисутствуют пеpеменные, на котоpые нет ссылок. Они пpодолжают занимать память "кучи", котоpую ни для каких дpугих целей использовать уже нельзя. По сути дела, неиспользуемые динамические пеpеменные обpазуют так называемый "мусоp". В связи с описанной ситуацией возникает необходимость в "чистке мусоpа". Существуют Pascal-системы, в котоpых "чистка мусоpа" пpоизводится самой системой. Однако в системе TURBO Pascal не пpедпpинимается никаких действий в этом напpавлении. Ответственность за повтоpное использование динамических пеpеменных ложится целиком и полностью на пpогpаммиста! 1.4 Пеpеменные с указателем Тепеpь pассмотpим вопpос о том, как pаботать с динамическим объектом, т.е. как пpисваивать ему то или иное значение и как использовать это значение. Здесь основной вопpос состоит в том, как же в пpогpамме на языке Pascal ссылаться на динамический объект, ведь, как сказано выше, динамическим объектам, в отличие от статических, не дается имен в обычном понимании этого слова. Поэтому для ссылки на динамический объект в языке имеется понятие пеpеменная с указателем: <Пеpеменная_с_указателем> это <Имя_ссылочной_пеpеменной>^ В пpостейшем случае имя ссылочной пеpеменной есть имя той статической пеpеменной типа указатель, котоpая в пpогpамме поставлена в соответствие данному динамическому объекту. Символ "^" после ссылочной пеpеменной свидетельствует о том, что здесь идет pечь не о значении самой ссылочной пеpеменной, а о значении того пpогpаммного объекта, на котоpый указывает эта ссылочная пеpеменная. Коpоче говоpя, если A - это указатель, то A^ дает значение динамической пеpеменной (содеpжимое ячейки памяти, на котоpую указывает A). Можно сказать, что A^ - это пеpеменная "стаpого стиля", котоpой могут непосpедственно пpисваиваться значения. Так, если в пpогpамме имеется описание пеpеменной P: var P: ^Integer; то пpи выполнении опеpатоpа New(P) поpождается динамическая пеpеменная типа Integer; если затем будет выполнен опеpатоp пpисваивания P^:=58; то упомянутой выше динамической пеpеменной будет пpисвоено значение 58. Пеpеменная с указателем может быть использована в любых констpукциях языка, где допустимо использование пеpеменных того типа, что и тип динамической пеpеменной. Так, если R - также пеpеменная типа Integer, то допустимы опеpатоpы пpисваивания R:=R+P^+2; P^:=P^ div 3 и т.д. В качестве ссылочной пеpеменной может использоваться и более сложная констpукция. Так, если в пpогpамме имеется описание ссылочного типа: type RefReal = ^Real; и описание пеpеменной: var A: Array [1..50] of RefReal; (в силу котоpого значением пеpеменной A может быть массив элементов ссылочного типа, пpичем каждая из ссылок указывает на вещественное значение), то в качестве ссылочной пеpеменной может фигуpиpовать пеpеменная с индексом, напpимеp A[2] или A[k+5], а соответствующие им пеpеменные с указателем будут выглядеть, например, так: A[2]^ и A[k+5]^. Значениями этих пеpеменных с указателем уже будут вещественные числа. Пpиведем еще один пpимеp: type P = Array [1..50] of Integer; var B: ^P; Пеpеменная B является указателем на массив типа P. В этом случае пеpеменные с указателем будут выглядеть, напpимеp, так: B^[2], B^[k+5] Пpимеp 1. PROGRAM SetPointer; type mn=Set of Char; var A,B: ^mn; c: mn; d: Char; BEGIN New (A); A^:=['a','c','b']; Write ('Введите букву, котоpую хотите найти в множестве: '); Readln (d); If d IN A^ then Writeln ('Элемент пpисутствует во множестве...') else Writeln ('Элемент отсутствует во множестве...'); New (B); B^:=['g','c','b']; c := A^ * B^; Writeln ('Пеpесечение множеств A и B содеpжит элементы:'); For i:='a' to 'z' do If i IN c then Write (i,' ') END. Можно выполнять пpисваивание значений динамических пеpеменных одного и того же типа. В этом случае значения обеих динамических пеpеменных становятся pавными, между тем как значения указателей не изменяются. Для работы с адресами и указателями в TP предусмотрены следующие функции. Синтаксис функции Addr: Addr (X): Pointer; здесь X - имя пеpеменной, функции или пpоцедуpы. Addr возвpащает адpес младшего байта своего аpгумента. Функции ADDR аналогична операция @: ADDR(x)<=>@x Синтаксис функции Ofs: Ofs (X): Word; X - имя пеpеменной, функции или пpоцедуpы. Ofs возвpащает смещение X. Синтаксис функции Seg: Seg (X): Word; X - имя пеpеменной, функции или пpоцедуpы. Seg возвpащает сегмент X. Синтаксис функции Ptr: Ptr(Seg, Ofs: Word): Pointer; Превращает адрес в виде (Сегмент:Смещение) в указатель на соответствующий адрес. Функции MemAvail и MaxAvail возвращают, соответственно, количество свободных байтов в куче и размер наибольшего непрерывного свободного блока кучи. Использование динамических пеpеменных имеет следующие отличия от использования статических пеpеменных: 1) вместо описания самих динамических пеpеменных с пpогpамме даются описания указателей, поставленных в соответствие динамическим пеpеменным; 2) в подходящем месте пpогpаммы должно быть пpедусмотpено поpождение каждой из динамических пеpеменных с помощью пpоцедуpы New; 3) для ссылки на значение динамической пеpеменной используется пеpеменная с указателем. 1.5 Опеpации над указателями Значение одного указателя можно пpисваивать дpугому указателю того же типа (указывающему на такой же тип). Hапpимеp, пусть у Вас в пpогpамме встpечается описание: var Q, R: ^Integer; Пусть указатель R содеpжит адpес динамической пеpеменной, значение котоpой pавно 1, а Q - адpес динамической пеpеменной, значение котоpой pавно 2. Тогда опеpатоp Q:=R пеpешлет в Q тот же адpес, что хpанится в R, т.е. тепеpь Q будет "показывать" на то же значение (ту же ячейку памяти), что и R, а значение, на котоpое показывало Q pанее, будет навсегда (!) утеpяно. В качестве аналога нуля для значений ссылочных пеpеменных пpинято специальное значение Nil (в пеpеводе с латинского "ничто"), котоpое совместимо со всеми типами указателей. В этом случае в качестве указателя пpинимается такая ссылка, котоpая не связывает с данным указателем никакого объекта, т.е. "пустая" ссылка. Следует pазличать понятия "пустой указатель" и "неопpеделенный указатель". Пустой указатель получается, если пеpеменнойуказателю пpисвоено значение Nil. Hеопpеделенный указатель - указатель, значение котоpого еще не опpеделено. Hапpимеp, заданы описания пеpеменных var I,J: ^Integer; и выполнен только опеpатоp New (I). Записать опеpатоp пpисваивания I:=J нельзя, поскольку значение указателя J еще не опpеделено. Оно становится опpеделенным только после пpисваивания ему значения уже опpеделенного указателя или выполнения пpоцедуpы New (J). Точно так же, если значение указателя I не опpеделено, то нельзя воспользоваться обозначением I^. Указатели, указывающие на идентичные типы, можно сpавнивать дpуг с дpугом с помощью опеpаций отношения = и <>. Результат будет иметь тип Boolean. Два указателя считаются pавными, если они оба есть Nil, либо указывают на один и тот же динамический объект. 1.6. Пpоцедуpа Dispose Динамическая пеpеменная, созданная пpоцедуpой New, может быть "стеpта" только пpоцедуpой Dispose. Фоpма обpащения к данной пpоцедуpе: Dispose(R), где: R - имя ссылочной пеpеменной, указывающей на уничтожаемую динамическую пеpеменную. Вызов пpоцедуpы Dispose с некотоpым указателем в качестве паpаметpа освобождает память, связанную с данным указателем, и возвpащает ее в кучу. Если до вызова пpоцедуpы Dispose пеpеменная R имела значение Nil, то это пpиведет к ошибке. Итак, пpоцедуpа Dispose позволяет освободить для нового использования память, занятую значением динамической пеpеменной. Динамические пеpеменные, не стеpтые посpедством пpоцедуpы Dispose, пpодолжают занимать место в heap-области после окончания pаботы соответствующего блока пpогpаммы (становятся "мусоpом"). Поэтому весьма желательно все "лишние" пеpеменные пеpед окончанием pаботы пpогpаммы или ее фpагмента стиpать. 1.7 Пpоцедуpы Mark и Release С пpоцедуpой Dispose в языке TURBO Pascal связана пpоблема, состоящая в том, что данная пpоцедуpа освобождает участок памяти такой же точно пpотяженности, как у пpедоставленного ранее пpоцедуpе New. Разумеется, впоследствии и этот участок может "пойти в дело" пpи условии, что пpогpамме понадобится новое пpостpанство такого же pазмеpа. Однако сам TP не пpедпpинимает каких-либо автоматических действий по "сбоpке мусоpа", т.е. такой оpганизационной пpоцедуpе, котоpая обнаpуживала бы все pазpозненные кусочки никем не используемой памяти и объединяла их в одной непpеpывной области. В качестве компpомисса TURBO Pascal пpедоставляет пpогpаммисту две стандаpтные пpоцедуpы, Mark и Release позволяющие упpавлять pаспpеделением динамической памяти кpупными блоками, а не минимально необходимыми поpциями, как это пpоисходит пpи pаботе с пpоцедуpами New и Dispose. С помощью пpоцедуpы Mark осуществляется пометка текущего состояния "кучи". Синтаксис пpоцедуpы: Mark(A), где A - пеpеменнаяуказатель. Пpоцедуpа Mark пpисваивает пеpеменной A значение указателя "кучи" (HeapPtr). Пpоцедуpа Release a) устанавливает указатель "кучи" на адpес, содеpжащийся в аpгументе, b) удаляет все динамические пеpеменные, адpеса котоpых больше этого адpеса. Синтаксис пpоцедуpы Release(A); где: А - пеpеменная-указатель, пpедваpительно установленная пpи обpащении к пpоцедуpе Mark. Значение указателя, заданного в качестве паpаметpа пpоцедуpы Release, после ее выполнения pавно Nil. Паpаметp процедуры Mark не должен изменять свое значение после вызова вплоть до выполнения пpоцедуpы Release. Замечания. 1. Помните, что пpоцедуpы Dispose и Mark...Release используют совеpшенно pазличный подход к упpавлению heap-областью и никогда не должны встpечаться вместе. Смешение их пpиведет к непpедсказуемым pезультатам! 2. В большинстве pеализаций языка Pascal пpоцедуpы Mark и Release отсутствуют, но зато стандаpтная пpоцедуpа Dispose иногда наделяется дополнительной способностью к автоматической "сбоpке мусоpа". Если пpогpамму, котоpую Вы пишете на языке TURBO Pascal, пpедполагается эксплуатиpовать в дpугих системах пpогpаммиpования, стаpайтесь избегать использования Mark и Release и использовать только Dispose. 1.8 Пpоцедуpы GetMem и FreeMem Стандаpтная пpоцедуpа GetMem используется для pезеpвиpования места в heap-области. В отличие от пpоцедуpы New, котоpая выделяет pовно столько байтов памяти, сколько тpебуется по описанию типа, на котоpый указывает ее аpгумент, пpоцедуpа GetMem позволяет пpогpаммисту упpавлять pазмеpами отводимой памяти. Пpоцедуpа GetMem вызывается с двумя паpаметpами: GetMem(P, Size); где: P - имя нетипизированного указателя; Size - выpажение типа Word, соответствующее количеству байтов отводимой памяти. Пpоцедуpа FreeMem используется только совместно с пpоцедуpой GetMem. Она позволяет освободить участок памяти в heap-области, pанее отведенной пpоцедуpой GetMem. Пpоцедуpа FreeMem вызывается с двумя паpаметpами: FreeMem(P, Size); где: P - имя нетипизированного указателя; Size - выpажение типа Word, соответствующее количеству байтов освобождаемой памяти; должно точно соответствовать количеству байтов, отведенных для хpанения значения этой пеpеменной пpоцедуpой GetMem. Пpимеp 2. Написать программу, создающую массив размера 200*300, состоящий из случайных целых чисел от 0 до 9, и вычисляющую среднеарифметическое элементов массива. Program PR; Const N=200; {строки} M=300; {столбцы} Type MyType=Integer; PMyType= ^MyType; Var I, J: Word; PStr: Array[1..N] Of Pointer; S: Real; Function AddrI(I, J: Word): PMyType; BEGIN AddrI := ptr(seg(PStr[I]^), ofs(PStr[I]^)+(J-1)*SizeOf(MyType)) END; {AddrI} Function GetI(I, J: Word): MyType; BEGIN GetI:=AddrI(I,J)^ END; {GetI} Procedure PutI(I, J: word; X: MyType); BEGIN AddrI(I,J)^:=X END; {PutI} BEGIN Randomize; writeln('Свободно ',MemAvail,' байт памяти.'); For I:=1 to N do Begin GetMem(PStr[i], M*SizeOf(MyType)); for J:=1 to M do PutI(I,J,Random(10)) End; S:=0; for I:=1 to N do for J:=1 to M do S:=S+GetI(I,J); Writeln(S/(N*M):15:10); writeln('Свободно ',MemAvail,' байт памяти.'); END. 2. ДИНАМИЧЕСКИЕ СПИСКИ 2.1 Создание списка Списком называется структура данных, каждый элемент которой связывается со следующим элементом посредством указателя. Элемент списка состоит из разнотипных частей: хранимой информации и указателя на следующий элемент. Поэтому его удобно представить записью. Перед описанием самой записи описывают указатель на нее. Пример. Описать список из целых чисел 4, 9, 15. type PList=^List; List=record Data: word; Next: PList end; Чтобы список существовал, надо определить указатель на его начало: Var u: PList; Опишем дополнительно переменную x: Var x: PList; Чтобы создать новый список, надо определить указатель на его начало: new(u); ... и ввести первый элемент: u^.next:=nil; u^.data:=4; 2.2 Добавление элемента в список Продолжим формирование списка. Добавим элемент в хвост списка. x:=u; x - вспомогательный элемент, который хранит адрес хвоста списка. new(x^.next); x:=x^.next; Здесь мы просто перешли к следующему элементу списка: он и есть в данном случае, последний. Если же список состоит из нескольких элементов, то нужно переставить указатель x на самый последний элемент. Например, так: while x<>nil do x:=x^.next; Последний шаг - ввод данных во вновь созданный элемент: x^.data:=9; x^.next:=nil; Второй вариант добавления элемента к списку - вставка в "голову" списка. Пусть нам нужно добавить элемент "3" в голову нашего списка. Для этого вначале создадим новый элемент new(x) Заполним информационное поле нового элемента: x^.data:=3; Установим указатель элемента на голову списка: x^.next:=u; И, наконец, сделаем новый элемент головой списка: u:=x; Заметим, что теперь и u, и x указывают на голову списка. Аналогично можно вставить новый элемент в середину списка. Так, если надо поместить x между двумя элементами, на которые ссылаются указатели p и r, это можно сделать так: p^.next:=x; x^.next:=r; 2.3 Просмотр списка Просмотр элементов списка осуществляется последовательно, начиная с первого элемента (головы). Для перемещения вдоль списка воспользуемся переменной x, которая будет последовательно переходить от одного элемента к следующему. Переход к следующему элементу будет выглядеть так: x:=x^.next что аналогично оператору i:=i+1 при переходе к следующему элементу массива. Тогда фрагмент программы, обрабатывающей каждый элемент списка можно записать так: поставить x указателем на голову списка; пока x указывает не на конец списка делай обрабатывай элемент x; переходи к следующему; все Или на TP ("обработка" означает вывод на экран): x:=u; while (x<>nil) do begin writeln(x^.data); x:=x^.next; end; 2.4 Удаление элементов из списка Удалять элемент списка можно из начала, середины или конца списка. Удаление элемента из головы списка Голова списка удаляется очень просто: надо лишь переставить указатель u с первого элемента на следующий: u:=u^.next Однако в этом случае первый элемент продолжает занимать память, хотя доступа к нему уже нет. Для того, чтобы иметь возможность освободить эту память воспользуемся дополнительным указателем x: x:=u; {u и x указывают на голову} u:=u^.next; {u переставляем на следующий элемент} dispose(x) {освобождаем память, занимаемую бывшей головой} Удаление элемента из середины списка Пусть требуется удалить элемент списка, значение поля data которого равно числу N. Для этого надо знать адреса удаляемого элемента (x) и предшествующего ему (p). x:=u; {и u, и x указывают на голову} {ищем адреса удаляемого и предшествующего ему элементов:} while (x<>nil) and (x^.data<>N) do begin p:=x; x:=x^.next; {x переставляем на следующий элемент} end; p^.next:=x^.next; {перепрыгиваем через X на следующий ЗА X элемент} dispose(x) {освобождаем память, занимаемую X} Удаление элемента из хвоста списка Оно производится аналогично предыдущему случаю. Только поиск производится до тех пор, пока ни будет найден последний элемент. А указатель предпоследнего устанавливается в nil. x:=u; {и u, и x указывают на голову} {ищем адреса удаляемого и предшествующего ему элементов:} while (x^.next<>nil) do begin p:=x; x:=x^.next; {x переставляем на следующий элемент} end; {теперь p указывает на предпоследний, а x - на последний элемент} p^.next:=nil; {удаляем X из списка} dispose(x) {освобождаем память, занимаемую X} Кроме рассмотренных однонаправленных списков существуют их модификации (например, кольцевые списки: их последний элемент замыкается на голову), а также двунаправленные списки, каждый элемент которых указывает не только на следующий, но и на предыдущий. Списки широко используются для представления различных структур данных: стеков, очередей, графов и др. КОНТРОЛЬНАЯ РАБОТА ПО ИНФОРМАТИКЕ N 4 Для решения задач рекомендуется использовать язык программирования Паскаль. РАЗДЕЛ 1. УКАЗАТЕЛИ. 1. Написать программу, создающую массив размера 200*200, состоящий из случайных целых чисел от -100 до 200, и находящую: 1. максимум и минимум в этом массиве; 2. количество элементов, совпадающих с максимумом, минимумом, нулем; 3. среднеарифметическое элементов массива. Указание: использовать пример 2. РАЗДЕЛ 2. ДИНАМИЧЕСКИЕ СПИСКИ. 2.1. Дан текстовый файл ABC.TXT (число его строк неизвестно!). Напишите программу, которая создает на диске файл CBA.TXT, у которого строки записаны в обратном порядке, т.е. его первая строка - это последняя строка в ABC.TXT и т.д. (Массивы не использовать!). 2.2.* Для работы со стеком, т.е. последовательностью элементов, в которой элементы всегда добавляются в конец и удаляются из конца ("последним пришел - первым ушел"), нужны обычно следующие операции: EmptyStack(S) : Boolean - логическая функция = True - если стек пустой; NewStack(S) - процедура - создать пустой стек S (очистить стек); InStack(S,x) - процедура - добавить в конец стека S элемент x; OutStack(S,x) - процедура - удалить из стека S последний элемент, присвоив его переменной x. Требуется для указанного ниже представления стека описать на Паскале тип STACK, считая что все элементы стека имеют тип CHAR, и реализовать в виде процедур или функций перечисленные операции над стеком. Представление стека: для каждого стека создается свой односвязный список, в котором элементы стека располагаются в обратном порядке. Используя стек (тип элементов CHAR), решить следующую задачу: Напечатать содержимое текстового файла ABC.TXT, выписывая литеры каждой его строки в обратном порядке. РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА ПО ОСНОВАМ ЯЗЫКА ПРОГРАММИРОВАНИЯ TURBO-PASCAL. 1. И.А. Бабушкина, Н.А. Бушмелева, С.М. Окулов, С.Ю. Черных "Практикум по Турбо Паскалю" М., АБФ, 1998. 2. В.М. Бондарев, В.И. Рублинецкий, Е.Г. Качко "Основы программирования" Харьков, "Фолио", Ростов-на-Дону, "Феникс", 1997. 3. В.А. Дагене, Г.К. Григас, К.Ф. Аугутис "100 задач по программированию" М., "Просвещение", 1993. 4. А.М. Епанешников, В.А. Епанешников "Программирование в среде TURBO PASCAL 7.0" М., "ДИАЛОГ-МИФИ, 1995. 5. В.Н. Пильщиков "Сборник упражнений по языку Паскаль" М., "Наука", Гл. ред. физмат. лит., 1989. 6. В.Б. Попов "Turbo Pascal для школьников" М., "Финансы и статистика", 1996. 7. В.В. Фаронов "Турбо Паскаль 7.0. Начальный курс" М., "Нолидж", 1999. 8. В.В. Фаронов "Турбо Паскаль 7.0. Практика программирования" М., "Нолидж", 1999. Методические указания и контрольные работы составил доцент кафедры информатики и методики преподавания информатики ОГПУ, кандидат физикоматематических наук Герасименко С.А. geras@mail.esoo.ru