Л.Р. 1) Линейные программы на языке pascal ЦЕЛЬ-ознакомление Программа — упорядоченная последовательность действий на ЭВМ реализующая алгоритм решения задачи. Любая программа на языке Pascal состоит из трех частей, схематично ее структуру можно показать так: Данные — информация, которую можно хранить, передавать и обрабатывать. Константа — величина, значение которой не изменяется в период ее существования. Переменная — величина, значение которой может изменяться. В Pascal существует 4 основных типа переменных: integer — целый; real — вещественный; char — символьный; boolean — логический. Операторы. Оператор — предписание на языке программирования для обработки данных, этап обработки данных. Оператор присваивания. Переменной, стоящей слева от знака равенства, присваивает новое значение, задаваемое каким-либо выражением: a := 35; a := a + 1; a := sin(x * x - a) / b; Оператор ввода данных. После выполнения оператора ввода данных программа приостанавливается и ждет, когда с клавиатуры будут введены значения переменных, стоящих в скобках после оператора: read (a,b); readln (a,b); Оператор вывода данных Оператор выводит на экран сообщения и значения переменных на экран: writeln ('Введите переменные'); 1 writeln (c); write (c); Команды readln и writeln «подскажут» компьютеру перевести курсор на новую строку. ПРИМЕР: Вычислить сторону и периметр квадрата, если известна его площадь. Блок-схема алгоритма: Программа на языке Pascal: Л.Р. 2) оператор выбора ЦЕЛЬ-ознакомление Оператор выбора Назначение Оператор выбора предназначен для организации множественных ветвлений. Функционально его можно заменить условным оператором с соответствующим количеством условий ELSEIF , но имеет более простую и наглядную конструкцию. Формат оператора CASE <выражение> WHEN <константа1 [, константа]... [, константа]> THEN <операторы_1> WHEN <константа2 [, константа]... [, константа] > THEN 2 <операторы_2> ............. [WHEN OTHERS THEN < операторы > ] ENDCASE Описание Тип констант и тип CASE -выражения должны быть совместимы. Значение CASE-выражения последовательно сравнивается с константами в операторах WHEN. При первом совпадении значения CASE-выражения с константой одного из WHEN-оператора выполняются операторы, соответствующие этому условию (т.е. до следующего WHEN-оператора или до ENDCASE), после чего выполнение оператора выбора завершается. Если значение CASEвыражения не было найдено среди списка констант, выполняется ветвь WHEN OTHERS. Если она отсутствует, управление передается сразу на следующий оператор после ENDCASE. Пример оператора выбора case rel_name when "$$$SYSRL", "$$$ATTRI", "$$$USR" then definit := "System dictionary table"; when "$$$PROC", "$$$PRCD" then definit := "System stored procedures table"; when others then definit := "Unknown table class"; endcase 3 Л.Р. 3) обработка массивов ЦЕЛЬ-ознакомление Секрет могущества ЭВМ – высокая скорость и большая память. Для записи алгоритмов, работающих с большими объемами информации, в алгоритмическом языке существуют специальные табличные величины (или просто таблицы). Исполнение многих алгоритмов было бы просто невозможно, если бы соответствующие объекты не были каким-либо образом организованы: упорядочены, классифицированы, занумерованы и так далее. Итак, нужно уметь организовать не только действия, но и те объекты, над которыми эти действия производятся. Необходимо отметить, что таблицы (массивы) как основное средство представления однородной информации неизбежно используются во всех реальных компьютерных программах. На табличном принципе основана и архитектура современных ЭВМ: память машины можно рассматривать как большой массив байтов, адреса которых располагаются по возрастанию. Следовательно, без понимания информационной сущности таблиц и основных алгоритмов их обработки невозможно формирование полноценных представлений о возможностях ЭВМ и принципах их работы. Для построения сколько-нибудь сложных и содержательных программ необходимо уверенное владение общими принципами применения таблиц и базовыми приемами их обработки. В данной работе будет рассмотрен ряд простых алгоритмов, которые используются при построении более сложных. Алгоритм вычисления суммы 1. Пусть дан массив A, состоящий из n элементов: a 1, a2, a3, …, an. Нужно найти их сумму, т.е. S=a1+a2+a3+…+an. Нахождение суммы есть последовательное нахождение суммы по формулам: S=0 S=S+a2 … S=S+ai S=S+an S=S+a1 S=S+a3 … Алгоритм вычисления суммы удобно организовать циклом, взяв за параметр цикла переменную i, которая меняется от 1 до n с шагом 1, и записав в цикле формулу S=S+ai один раз. Схема алгоритма приведена на рис. 1а. 4 В схеме блок 4 присваивает S нулевое значение, блок 5 счетчику i присваивает начальное значение, блок 6 выполняет накопление суммы, блок 7 изменяет значение i на 1, блок 8 осуществляет проверку условия повторения цикла. При выполнении этого условия управление передается в начало цикла, а при невыполнении – осуществляется выход из цикла, т.к. при i=n+1 суммировать не нужно. n – в схеме предполагается число, но n может быть и переменной, значение которой равно числу элементов массива A, которое нужно вводить перед описанием массива. При разработке этого алгоритма учащимся можно предложить изменить схему на случай, если нужно найти сумму элементов, расположенных на четных местах в массиве A (Ответ: Блок 5 надо изменить на i=2 и блок 7 на i=i+2) или задать вопрос – что изменится в схеме на рис.1а, если суммировать только положительные элементы массива A? (Ответ: Перед блоком суммирования 6 нужно поставить блок проверки элемента массива ai на положительность и, если он положителен, то его суммировать, а если нет, то обходить блок суммирования.) 5 Л.Р.4 циклы ЦЕЛЬ-ознакомление Для реализации цикла с параметром в Pascal используется оператор For. Синтаксис оператора For выглядит следующим образом: For i:=n to k do <оператор>; где i параметр n начальное значение параметра k конечное значение параметра <оператор> оператор, являющийся телом For, to, do служебные цикла; цикла; цикла; цикла; слова. Переменные, обозначающие начальное, конечное значения параметра цикла и сам параметр должны иметь перечисляемый тип, причём начальное и конечное значение параметра могут быть заданы явно (изображением). Например: For i:=1 to 20 do <оператор>; При выполнении данного цикла, параметр i (целочисленный) будет при каждой итерации (повторении) принимать последовательно значения 1, 2, …,20. Приращение (шаг изменения значения) параметра для такого цикла всегда равен 1. Начальное значение параметра всегда должно быть меньше конечного. Если же i, n, k символьного типа n и k имеют значения, например, 'A' и 'Z' соответственно, то переменная i принимает последовательные значения в порядке букв алфавита: 'А', 'В', 'С', ... ,'Z'. Возможна For и i:=n другая downto конструкция k do цикла For. <оператор>; Отличие заключается в том, что приращение параметра для такого цикла всегда равно -1 и начальное значение всегда должно быть больше конечного. Если оператор, содержащийся в теле оператора For, изменяет значение параметра, то это является ошибкой. После выполнения оператора For значение параметра становится неопределенным, если только выполнение оператора For не было прервано с помощью оператора перехода. 6 В любом из перечисленных случаев, по синтаксису языка, допускается выполнение в теле цикла одного оператора. При необходимости использовать два и более операторов применяется составной оператор. Например: For begin <оператор <оператор . <оператор end; i:=n downto . k do 1>; 2>; . N>; Пример 5.1. Найти все простые числа на заданном отрезке (использовать цикл с параметром) Для организации выполнения программы определим целочисленные переменные n, k для обозначения начального и конечного значения отрезка, i, j - для обозначения параметров соответственно внешнего и внутреннего циклов, kl - счётчик для количества делителей проверяемого числа. Во внешнем цикле последовательно перебираются значения отрезка от начального до конечного. Во внутреннем цикле параметр j изменяется от 2 до округлённого значения корня квадратного проверяемого значения, при этом, если остаток от деления числа на параметр j равен нулю, то параметр является делителем данного числа. После каждого завершения работы внутреннего цикла проверяется переменная kl, и если она равна нулю, то найдено простое число. 7 Рисунок 5.4 - Блок-схема алгоритма решения задачи Program Example_5_1 (Input, Output); var n : Integer; k : Integer; i,j: Integer; kl : Integer; Begin Write ('Введите нижнюю границу отрезка - '); ReadLn (n); Write ('Введите верхнюю границу отрезка - '); ReadLn (k); WriteLn ('Все простые числа из отрезка [',n,',',k,']'); For i:=n to k do begin kl:=0; For j:=2 to Round (Sqrt(i)) do If (i MOD j)=0 then kl:=kl+1; If kl=0 then Write (i,' ') end End. 8 Л.Р.5 Динамические структуры данных: ЦЕЛЬ-ознакомление Очередь — это информационная структура, в которой для добавления элементов доступен только один конец, называемый хвостом, а для удаления — другой, называемый головой. В англоязычной литературе для обозначения очередей довольно часто используется аббревиатура FIFO (first-in-first-out — первый вошёл — первым вышел). Очередь разумнее всего моделировать, отобразив её на двунаправленный кольцевой список. В этом случае в заглавном звене будет присутствовать информация как об указателе на голову, так и на хвост очереди. Выделим типовые операции над очередями: добавление элемента в очередь (помещение в хвост); удаление элемента из очереди (удаление из головы); проверка, пуста ли очередь; очистка очереди. Вот модуль, содержание которого составляют реализованные типовые операции над очередями. {Язык Pascal} Unit Spisok2; Interface Type BT = LongInt; U = ^Zveno; Zveno = Record Inf : BT; N, P: U End; Procedure V_Och(Var First : U; X : BT); Procedure Iz_Och(Var First : U; Var X : BT); Procedure Ochistka(Var First: U); Function Pust(First : U) : Boolean; Implementation Procedure V_Och; Var Vsp : U; Begin New(Vsp); Vsp^.Inf := X; If First = Nil then begin Vsp^.N := Vsp; Vsp^.P := Vsp; First := Vsp end else begin Vsp^.N := First; Vsp^.P := First^.P; First^.P^.N := Vsp; First^.P := Vsp; end; End; Procedure Iz_Och; Var Vsp : U; 9 Begin x:=first^.inf; if First^.p=first then begin dispose(first); first:= nil end else begin Vsp := First; First := First^.N; First^.P := Vsp^.P; Dispose(Vsp) end End; Procedure Ochistka; Var Vsp : BT; Begin While Not Pust(First) Do Iz_Och(First, Vsp) End; Function Pust; Begin Pust := First = Nil End; Begin End. // Язык С++ #include <iostream.h> #include <conio.h> #include <stdlib.h> #include <time.h> typedef long BT; struct U{ BT Inf; U *N, *P;}; U *V_Och(U *First, BT X) { U *Vsp; Vsp = (U*) malloc (sizeof(U)); Vsp->Inf=X; if (!First) {Vsp->N=Vsp; Vsp->P=Vsp; First=Vsp;} else {Vsp->N=First; Vsp->P=First->P; First->P->N=Vsp; First->P=Vsp;} return First;} U *Iz_Och(U *First, BT &X) 10 { U *Vsp; X=First->Inf; if (First->P==First) {free(First); First=NULL;} else {Vsp=First; First=First->N; First->P=Vsp->P; free(Vsp);} return First;} int Pust(U *First) { return !First;} U *Ochistka(U *First) { BT Vsp; while (!Pust(First)) First=Iz_Och(First, Vsp); return First; } Пример. Напечатать в порядке возрастания первые n натуральных чисел, в разложение которых на простые множители входят только числа 2, 3, 5. Алгоритм решения. Введем три очереди x2, x3, x5, в которых будем хранить элементы, которые соответственно в 2, 3, 5 раз больше напечатанных, но еще не напечатаны. Рассмотрим наименьший из ненапечатанных элементов; пусть это x. Тогда он делится нацело на одно из чисел 2, 3, 5. x находится в одной из очередей и, следовательно, является в ней первым (меньшие напечатаны, а элементы очередей не напечатаны). Напечатав x, нужно его изъять и добавить его кратные. Длины очередей не превосходят числа напечатанных элементов. {Язык Pascal} Program Example; Uses Spisok2; Var X2, X3, X5 : U; X : BT; I, N : Word; Procedure PrintAndAdd(T : BT); Begin If T <> 1 Then Write(T : 6); V_Och(X2, T * 2); V_Och(X3, T * 3); V_Och(X5, T * 5); End; Function Min(A, B, C : BT) : BT; Var Vsp : BT; Begin If A < B Then Vsp := A Else Vsp := B; If C < Vsp Then Vsp := C; Min := Vsp End; Begin X2 := Nil; X3 := Nil; X5 := Nil; Write('Сколько чисел напечатать? '); ReadLn(N); 11 PrintAndAdd(1); For I := 1 To N Do Begin X := Min(X2^.Inf, X3^.Inf, X5^.Inf); PrintAndAdd(X); If X = X2^.Inf Then Iz_Och(X2, X); If X = X3^.Inf Then Iz_Och(X3, X); If X = X5^.Inf Then Iz_Och(X5, X); End; Ochistka(X2); Ochistka(X3); Ochistka(X5); WriteLn End. // Язык С++ #include "spis2.cpp" void PrintAndAdd(BT T); BT Min (BT A, BT B, BT C); U * X2, *X3, *X5; void main () { BT X; long I, N; X2 = NULL; X3 = NULL; X5 = NULL; cout << "Сколько чисел напечатать? "; cin >> N; PrintAndAdd(1); for (I=1;I<=N; I++) { X = Min(X2->Inf, X3->Inf, X5->Inf); PrintAndAdd(X); if (X==X2->Inf) X2=Iz_Och(X2, X); if (X==X3->Inf) X3=Iz_Och(X3, X); if (X==X5->Inf) X5=Iz_Och(X5, X); } X2=Ochistka(X2); X3=Ochistka(X3); X5=Ochistka(X5); cout << endl; } void PrintAndAdd(BT T) { if (T!=1) {cout.width(6); cout << T;} X2=V_Och(X2, T*2); X3=V_Och(X3, T*3); X5=V_Och(X5, T*5); } BT Min (BT A, BT B, BT C) { BT vsp; if (A < B) vsp=A; else vsp=B; if (C < vsp) vsp=C; return vsp; 12 Л.р.6 не рекурсивные процессоры ф-и Причиной того, что программисты называют код, автоматически сгенерированный Dreamweaver MX и Fireworks MX, программами-заготовками (ugly code), является то, что эти программы обобщают все возможные сценарии работы пользователя. Естественно, размер программ при этом увеличивается. Примером может служить такой вот код, обслуживающий обычную операцию замещения (rollover): function MM_findObj (n, d) //v4.01 var p, i, x; if (!d) d=document; if ((p=n.indexOf("?"))>0&&parent.frames.length) { d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p); if(!(x=d[n]&&d.all) x=d.all[n]; for (i=0; !x&&i<d.forms.length; i++) x=d.forms[i][n]; for (i=0; !x&&d.layers&&i<d.layers.lendth; i++) x=MM_findObj(n,d.layers[i].document); if (!x&&d.GetElementById) x=d.getElementBy!d(n); return x; } function MM_swapImageО { //v.3.0 var i, j=0, x, a=MM_swapImage.arguments; document.MM_sr= new Array; for(i=0;i< (a.length-2);i+=3) if ((MM_findObj(a[i]))!=null) {document.MM_sr[j++]=x; if (Ix.oSrc) x.oSrc=x.src; x.src=a[i+2];} } Попросту говоря, функция MM_SwapImage () получает список аргументов, содержащих данные в форме адресов URL, имен файлов рисунков или пустых строк. Например, искомые данные при помещении курсора мыши над объектом будут выглядеть следующим образом: onMouseOver = "MM_swapImage('shoes', 'images/shoes_f2.gif', 'hats','','images/hats_f2.gif',!)") '', Проблема состоит в том, что в функции не используется последний оператор If. Вместо этого функция проходит в цикле по всем наборам данных и исследует в поисках объекта весь документ. После этого адрес URL старой графики сохраняется, если используется функция swaplmageRestore (), а сам адрес URL изменяется и указывает уже на новый рисунок. Все это немного напоминает использование атомной бомбы для жарки барбекю. Естественно, барбекю поджарится, но при 13 этом будет израсходована масса излишней энергии. В данном примере не только содержится избыточный код, но и несколько излишних циклов процессора, так как для поиска объекта используется рекурсивная функция (древоподобная функция, сохраняющая вызовы на каждой ветви обработки, пока поиск проводится на всех дочерних ветвях). Само замещение можно представить всего одной строкой: onMouseOver="this.src= ' images/shoes_f2.gif ' " где this — текущий объект, вызвавший событие onMyuseOver. В данном случае это — наш рисунок. Когда курсор мыши помещается над объектом, эта строка программы указывает свойству src (это свойство аналогично атрибуту SRC дескриптора Щ€>) Щ1 новый рисунок. Еще одной характеристикой программы-заготовки является вставка приложением Dreamweaver MX на страницу ненужных дескрипторов. Например: <tdxfont face="Arial, Helvetica, src="navbar_images/jct_corner_logo.gif" heigth="52"</fontx/td> sans-serif" <img width="lll" Обратите внимание, что в данном случае отсутствует текст, а в наличии имеется только рисунок. И хотя ошибок нет, избыточный код засоряет страницу и увеличивает время ее загрузки. Еще один пример программы-заготовки создан для сервера (ColdFusion) и обслуживает команду вставки записи (insert Record): <cfif IsDefined ("FORM.MM_InsertRecord") AND FORM_MM_InsertRecord EQ "forml"> <cfquery datasource="jct"> INSERT INTO ProductType (ProductType, GraphicURL, ProductDescription) VALUES ( <cfif IsDefined("FORM.productType") AND #FORM.productType* NEQ ""> 1 #FORM. product-Type» <cfelse> NULL </cfif> ' <cfif IsDefined("FORM.graphicURL") AND #FORM.graphicURL# NEQ ""> 1#FORM.graphicURL*' <cfelse> NULL </cfif> ' <cfif IsDefined("FORM.productDescription") AND #FORM.productDescription# NEQ ""> 1#FORM.productDescription#' <cfelse> NULL </cfif> } </cfquery> <cflocation url="test2.cfm"> </cfif> 14 Этот фрагмент программы ищет параметр с именем MM_InsertRecord, передаваемый из формы f orml. Если имя формы — f orml, этот фрагмент создает запрос на вставку и проверяет все элементы формы не только на предмет их существования, но и не содержат ли они пустую строку. Если удовлетворены оба условия, значение из формы используется для запроса, в противном случае функция возвращает значение NULL. После этого страница перенаправляет вас на новую страницу. В чем же избыточность этого фрагмента? Форма является своей же страницей ответа. Такой механизм не является наилучшим. Немногим более эффективный механизм может быть заложен в программе страницы ответа и выглядит следующим образом: ccfparam name="productType" default=""> <cfparam name=" productDescription " default=""> <cfparam name="graphicURL " default=""> <cfquery datasource="jct"> INSERT INTO ProductType (ProductType, GraphicURL, ProductDescription) VALUES (#productType#, tproductDescription#, #graphicURL#) </cfquery> Дескрипторы cfparam являются удобным способом определения значений по умолчанию для того случая, когда элементы формы не существуют. В данном случае проверяется факт существования элементов; в случае отсутствия таковых они создаются и им присваиваются значения по умолчанию. Если не предоставить значения атрибутов по умолчанию, страница вызовет ошибку. Это аналогично проверке выполнения некоторого условия в каком-либо фрагменте программы. После этого дескриптор с fquery вставляет значения в базу данных либо из элементов формы, либо из значений, установленных по умолчанию. Таким образом, можно сделать вывод, что оптимизацией кода должны заниматься профессионалы 15 Л.р.7 модули Цель-изучение Borland Pascal обеспечивает вам доступ к большому числу встроенных констант, типов данных, переменных, процедур и функций. Некоторые из них специфичны для Borland Pascal, другие специфичны для приложений Windows. Их количество велико, однако, в своей программе вы редко используете их все сразу. Поэтому они разделены на связанные группы, называемые модулями. В этом случае можно использовать только те модули, которые необходимы в программе. Используя модули, вы можете разбивать программу на отдельные части и компилировать их отдельно. Программный модуль (unit) представляет собой набор констант, типов данных, переменных, процедур и функций, которые могут совместно использоваться несколькими программами. Каждый модуль аналогичен отдельной программе на Паскале: он может иметь основное тело, которое вызывается перед запуском вашей программы и осуществляет необходимую инициализацию. Все описания внутри модуля связаны друг с другом. Например, модуль Strings содержит все описания, необходимые для подпрограмм работы со строками с завершающим нулем. Borland Pascal предоставляет пользователю ряд стандартных модулей, таких как System, Crt WinCrt и др. Они поддерживают наши 16 программы Borland Pascal и все записаны в одну из трех библиотех исполняющей системы (в зависимости от целевой платформы). Библиотеки исполняющей системы для целевой платформы Таблица 7.1 +-------------------------+------------------------+ | Имя библиотеки | Целевая платформа | +-------------------------+------------------------| | TURBO.TPL | DOS реального режима | | TPW.TPL | Windows | | TPP.TPL | DOS защищенного режима | +-------------------------+------------------------+ Ваша программа может использовать любую из процедур и функций в этих модулях, и вам не потребуется писать их заново. Структура модуля Структура модуля аналогична структуре программы, однако есть несколько модуль: существенных различий. Например, рассмотрим unit <идентификатор>; interface uses <список модулей>; { Необязательный } { глобальные описания } implementation uses <список_модулей>; { Необязательный } { локальные описания } { реализация процедур и функций } begin { код инициализации } end. Заголовок модуля начинается зарезервированным словом unit, 17 за которым следует имя модуля (идентификатор) точно так же, как и в случае имени программы. Следующим элементом в модуле является ключевое слово interface. Оно обозначает начало интерфейсной секции модуля - части, доступной всем другим модулям или программам, в которых он используется. Программный модуль может использовать другие модули, для этого они определяются в операторе uses. Оператор uses (если он имеет место) может содержаться в двух местах. Во-первых он может следовать сразу после ключевого слова interface. В этом случае любые константы и типы данных, описанные в интерфейсной секции этих модулей, могут использоваться в любом описании в интерфейсной части данного модуля. Во-вторых, он может следовать немедленно за ключевым словом implementation. В этом случае все описания из этих модулей могут использоваться только в секции реализации. Интерфейсная секция Интерфейсная часть - "общедоступная" часть в модуле - начинается зарезервированным словом interface, следует сразу после заголовка модуля и заканчивается перед зарезервированным словом implementation. Интерфейс определяет, что является "видимым" (доступным) для любой программы (или модуля), использующей данный модуль. В интерфейсной части (секции) модуля можно определять 18 константы, типы данных, переменные, процедуры и функции. Как ив программе, они могут быть расположены в любом порядке, и секции могут встречаться повторно (например, ваша программа может содержать секцию var, за которой следует секция const, а затем другая секция var). Процедуры и функции, видимые для любой программы, использующей данный модуль, описываются в секции интерфейса, однако их действительные тела - реализации - находятся в секции реализации. Вам не нужно использовать описания forward, и они не допускаются. В интерфейсной части перечисляются все заголовки процедуры и функции. Секция реализации содержит программную логику процедур и функций. Секция реализации Секция реализации - "приватная" часть - начинается зарезервированным словом implementation. Все, что описано в секции интерфейса, является видимым в секции реализации: константы, типы, переменные, процедуры и функции. Кроме того, в секции реализации могут быть свои дополнительные описания, которые не являются видимыми для программ, использующих этот модуль. Программа не знает об их существовании и не может ссылаться на них или обращаться к ним. Однако, эти скрытые эле19 менты могут использоваться (и, как правило, используются) "видимыми" процедурами и функциями, то есть теми подпрограммами, чьи заголовки указаны в секции интерфейса. Оператор uses может содержаться в секции реализации (implementation) и должен непосредственно следовать за ключевым словом implementation. Обычные процедуры и функции, описанные в интерфейсной секции, то есть те из них, которые не являются подставляемыми (inline), должны повторно указываются в секции реализации. Заголовок procedure/function должен быть или идентичным тому, который указан в секции интерфейса, или иметь более краткую форму. В случае краткой формы наберите ключевое слово (procedure или function), а за ним укажите имя подпрограммы (идентификатор). Затем подпрограмма должна содержать все свои локальные описания (метки, константы, типы, переменные и вложенные процедуры и функции), за которыми должно находиться основное тело самой подпрограммы. Пусть в интерфейсной части указаны следующие описания: procedure ISwap(var V1,V2 : integer); function IMax(V1,V2 : integer) : integer; Тогда Секция реализации будет иметь следующий вид: procedure ISwap; var Temp := integer; begin Temp := V1; V1 := V2; V2 := Temp end; {конец процедуры Swap} function IMax(V1,V2 : integer) : integer; begin if V1 > V2 20 then IMax := V1 else IMax := V2 end; { конец функции Max } Подпрограммы, локальные для секции реализации (то есть не описанные в секции реализации), (несокращенный) заголовок procedure/function. должны иметь полный Секция инициализации Обычно вся секция реализации модуля заключена между зарезервированными словами implementation и end. Однако, если перед end поместить зарезервированное слово begin, а между ними операторы, то получившийся составной оператор, очень похожий на основное тело программы, становится секцией инициализации модуля (initialization). Секция инициализации представляет собой место, где инициализируются структуры данных (переменных), которые использует программный модуль или которые он делает доступными программе, использующей данный модуль. Вы можете использовать эту секцию для открытия файлов, которые программа использует позднее. При выполнении программы, использующей некоторый модуль, секция инициализации этого модуля вызывается перед запуском основного тела программы. Если программа использует более одного 21 модуля, то секции инициализации всех модулей вызываются (в порядке, указанном в операторе uses в программе) перед тем, как выполнить основное тело программы. Как используются модули? Модули, которые использует ваша программа, уже оттранслированы и хранятся, как машинный код, а не как исходный код на Паскале, поскольку они не являются включаемыми файлами. Даже интерфейсная секция хранится в специальном двоичном формате таблицы идентификаторов, используемом в Borland Pascal. Более того, определенные стандартные модули хранятся в специальном файле (TURBO.TPL, TPW.TPL или TPP.TPL) и автоматически загружаются в память вместе с Borland Pascal. В результате использование одного или нескольких модулей очень незначительно увеличивает время компиляции вашей программы (обычно менее, чем на секунду). Фактически, если модуль скомпилирован, его использование сохраняет вам время при перекомпиляции. Поскольку компилятор не перекомпилирует модуль, пока он не изменяется, использование модулей в программе ускорит процесс ее построения. Как указывалось ранее, для использования специального модуля или набора модулей необходимо в начале программы поместить опера22 тор uses, после которого указать список имен тех модулей, которые будут использоваться. Имена их должны разделяться запятыми: program MyProg; uses thisUnit, thatUnit, theOtherUnit; Когда компилятор встречает такой оператор uses, он прибавляет информацию из секции интерфейса каждого модуля к таблице идентификаторов и присоединяет машинный код, представленный в секции реализации, к самой программе. Модули присоединяются к таблице идентификаторов в указанном порядке. Порядок модулей в операторе uses значения не имеет. Если модуль thisUnit использует thatUnit или наоборот, вы можете описать их в любом порядке, а компилятор определит, какой модуль нужно скомпоновать с программой MyProg первым. Фактически, если модуль thisUnit использует thatUnit, но MyProg не вызывает непосредственно ни одну из подпрограмм в модуле thatUnit, вы можете "скрыть" подпрограммы модуля thatUnit, опустив его в операторе uses: unit thisUnit; uses thatUnit; . . . program MyProg; uses thisUnit, theOtherUnit; . . . В этом примере модуль thisUnit может вызывать любую подпрог23 рамму модуля thatUnit, а программа MyProg может вызывать любую из подпрограмм модуля thisUnit или theOtherUnit. Однако, программа MyProg не может вызывать подпрограммы модуля thatUnit, поскольку thatUnit не указывается в операторе uses программы MyProg. Если в программе не указан оператор uses, Borland Pascal в любом случае присоединит стандартный модуль System. Этот модуль обеспечит выполнение некоторых стандартных подпрограмм Borland Pascal, а также нескольких подпрограмм, специфических для Borland Pascal. Ссылки на описания модуля Как только вы включили модуль в свою программу, все константы, типы данных, переменные, процедуры и функции, описанные в секции интерфейса этого модуля, становятся доступными для вашей программы. Например, допустим, имеется следующий модуль: unit MyStuff; interface const MyValue = 915; type MyStars = (Deneb,Antares,Betelgeuse); var MyWord : string[20]; procedure SetMyWord(Star : MyStars); function TheAnswer : integer; implementation . 24 . . end. Как можно видеть здесь в интерфейсной части модуля, та часть модуля, которая находится в интерфейсной секции, является видимой для вашей программы (и может ею использоваться). С учетом этого можно написать следующую программу: program TestStuff; uses MyStuff; var I : integer; AStar : MyStars; begin Writeln(myValue); AStar := Deneb; SetMyWord(AStar); Writeln(MyWord); I := TheAnswer; Writeln(I) end. После включения в программу оператора uses MyStuff вы можете ссылаться на все идентификаторы, описанные в интерфейсной секции модуля МyStuff (МyWord, МyValue и так далее). Однако, рассмотрим следующую ситуацию: program TestStuff; uses MyStuff; const MyValue = 22; var I : integer; AStar : MyStars; function TheAnswer : integer; begin TheAnswer := 1 25 end; begin Writeln(myValue); AStar := Deneb; SetMyWord(AStar); Writeln(MyWord); I := TheAnswer; Writeln(I) end. В этой программе переопределяются некоторые из идентификаторов, описанных в МyStuff. Будучи скомпилированной и выполненной, эта программа будет использовать собственные определения для МyValue и ТheAnswer, поскольку они были описаны позднее, чем определения в МyStuff. Вероятно, вам интересно знать, каким образом в такой ситуации можно ссылаться на идентификаторы в МyStuff. Для этого необходимо перед каждым идентификатором помещать имя МyStuff с точкой (.). Например, рассмотрим еще одну версию этой программы: program TestStuff; uses MyStuff; const MyValue = 22; var I : integer; AStar : MyStars; function TheAnswer : integer; begin TheAnswer := 1 end; begin Writeln(MyStuff.MyValue); AStar := Deneb; SetMyWord(AStar); 26 Writeln(MyWord); I := MyStuff.TheAnswer Writeln(I) end. Эта третья программа даст такие же ответы, что и первая, даже в том случае, если вы переопределите MyValue и TheAnswer. В действительности вы программу следующим образом: имели полное право написать первую program TestStuff; uses MyStuff; var I : integer; AStar : MyStuff.MyStars; begin Writeln(MyStuff.MyValue); AStar := My.Stuff.Deneb; MyStuff.SetMyWord(AStar); Writeln(My.Stuff.MyWord); I := MyStuff.TheAnswer; Writeln(I) end. Отметим, что имя модуля может предшествовать любому идентификатору: константе, типу данных, переменной или подпрограмме. ;---------Prg18_1.asm-------------------------------27 -;Пример программы в COM-формате. ;Основа примера — программа Prg_3_1.asm (урок 3) ;----------------------------------------------------code segment para public 'code' ;начало сегмента кода assume cs:code, ds: code, ss: code org 100h main proc jmp EndData message db 'Введите две шестнадцатеричные цифры,$' EndData: ;ds инициализирует система mov ah, 9 mov dx, offset message int 21h xor ax, ax ;очистить регистр ax mov ah, 1h ;1h в регистр ah int 21h ;генерация прерывания с номером 21h mov dl, al ;содержимое регистра al в регистр dl sub dl, 30h ;вычитание: (dl)=(dl)-30h cmp dl, 9h ;сравнить (dl) с 9h jle M1 ;перейти на метку M1, если dl<9h или dl=9h sub dl, 7h ;вычитание: (dl)=(dl)–7h M1: ;определение метки M1 mov cl, 4h ;пересылка 4h в регистр cl shl dl, cl ;сдвиг содержимого dl на 4 разряда влево int 21h ;вызов прерывания с номером 21h sub al, 30h ;вычитание: (dl)=(dl)-30h cmp al, 9h ;сравнить (al) с 9h 28 jle M2 ;перейти на метку M2, если al<9h или al=9h sub al, 7h ;вычитание: (al)=(al)-7h M2: ;определение метки M2 add dl, al ;сложение: (dl)=(dl)+(al) mov ax, 4c00h ;пересылка 4c00h в регистр ax int 21h ;вызов прерывания с номером 21h main endp code ends ;конец сегмента кода end main ;конец программы с точкой входа main 28 Такая структура исходного файла для COM-формата обусловлена тем, что построенный на его основе исполняемый файл является слепком участка памяти. После его загрузки операционной системе не требуется производить никаких настроек. Интересно посмотреть на дамп COM-файла (рис. 1). 0000:0000 EB 26 90 C2 E2 E5 20 F8 ..&Введите две ш 0000:0010 E5 F1 F2 ED E0 ED FB E5 естнадцатеричные 0000:0020 20 F6 E8 F4 F0 CD 21 33 цифры,$......!3 0000:0030 C0 B4 01 CD 21 7E 03 80 ....!..Ђ.0Ђ..~.Ђ 0000:0040 EA 07 B1 04 D2 02 2C 07 .......!,0<.~.,. 0000:0050 02 D0 ....L.! E5 E4 E8 F2 E5 20 E4 E2 E4 F6 E0 F2 E5 F0 E8 F7 FB 2C 24 B4 09 BA 03 01 8A D0 80 EA 30 80 FA 09 E2 CD 21 2C 30 3C 09 7E B8 00 4C CD 21 Рис. 1. Дамп COM-файла В отличие от других форматов исполняемых файлов, как мы увидим ниже, по внешнему виду приведенного дампа трудно определить (см. рис. 1), что этот файл тоже может быть загружен и выполнен. Это действительно образ памяти, который загружается операционной системой в сегмент со смещением 100h и исполняется. Необязательным признаком COM-файла является наличие в качестве первой инструкции команды перехода. В нашем случае — это EB 26 (jmp EndData). Исполняемый файл MZ-формата (MS-DOS) Разработанный фирмой Microsoft COM-формат исполняемых файлов мог применяться лишь для небольших программ. Для MS-DOS, поддерживающей сегментную организацию программы, был разработан специальный формат исполняемых файлов — MZ-формат. Это название условное, но оно будет полезно нам для дальнейшего обсуждения, и вот почему. В отличие от COM-формата (.com), расширения файлов всех остальных форматов — .exe. Требуется заглянуть внутрь содержимого исполняемого файла, чтобы понять, с каким конкретно форматом файла мы имеем дело. Поэтому для однозначности с каждым типом исполняемого файла связывается короткая аббревиатура, по которой понятно, о каком формате идет речь. Итак, MZ-формат был разработан фирмой Microsoft для поддержки многосегментных программ в среде MS-DOS. С точки зрения структуры, файл MZ-формата имеет три части: 29 заголовок; таблицу размещения; программный код. Заголовок состоит из полей фиксированного размера (табл. 1). Таблица 1. Содержимое полей заголовка файла MZ-формата Смещение Длина Значение 0000h симво- 2 байта Сигнатура (фиксированная последовательность лов) — «MZ» (4d 5ah) или «ZM» (5a 4dh) (MZ — Mark Zbikowski) Весь исполняемый файл делится на страницы по 512 байт. Понятно, что совсем необязательно условие кратности длины файла этому значению. Поэтому следующее поле содержит длину остатка кода последней страницы исполняемого файла. 0002h 2 байта Количество байт в последней странице файла MZформата. Равно остатку от деления длины файла на значение 512 Поле 0004h содержит длину исполняемого файла в 512-байтных страницах. 0004h 2 байта Длина файла MZ-формата в 512-байтных страницах 0006h 2 байта Количество элементов в таблице размещения Так как заголовок может иметь переменную длину, то поле 0008h сообщает его длину в 16-байтных параграфах. Эта информация используется загрузчиком операционной системы для определения начала загрузочного модуля. 0008h 2 байта Длина заголовка в параграфах Следующие два поля сообщают минимальное и максимальное количество памяти (в параграфах), необходимое для исполнения программы, помимо размера ее программного кода. 000Ah 2 байта Минимальное количество дополнительных параграфов, необходимых для исполнения программы 000Ch 2 байта Максимальное количество дополнительных параграфов, необходимых для исполнения программы 30 Поля 000Eh и 00010h предназначены для хранения указателя стека ss:sp, но реально они формируются загрузчиком OC на стадии загрузки программы в память для выполнения. 000Eh 2 байта Начальное значение, загружаемое в регистр ss 0010h 2 байта Начальное значение, загружаемое в регистр sp 0012h 2 байта 0 или поразрядная дополненная контрольная сумма всех 16-битных слов в файле, за исключением данного поля Следующие два поля определяют точку входа в программу после ее загрузки. 0014h 2 байта Значение, загружаемое в регистр ip во время загрузки программы в память 0016h 2 байта Значение, загружаемое в регистр cs во время загрузки программы в память (формируется загрузчиком) 0018h 2 байта Смещение от начала файла таблицы размещения или значение 40h, если это исполняемый файл одного из форматов NE, PE и т. д. (см. ниже) Последнее поле предназначено для организации оверлейных программ. В нем содержится номер, в соответствии с которым впоследствии будет организована загрузка модулей, входящих в оверлейную программу. Для главной программы поле имеет значение 0. 001Ah 2 байта Номер оверлейного модуля На этом заголовок заканчивается. Далее следует информация, зависящая от того программного средства, которое сформировало данный исполняемый файл. Представленный на рис. 2 исполняемый файл был сформирован программой TLINK версии 7.1 (фирмы Borland). Эта программа формирует следующую опознавательную информацию: 001Ch 2 байта Сигнатура 01 00h 001Eh 1 байт Сигнатура 0fbh 001Fh 1 байт Версия TLINK 0020h 2 байта Сигнатура 6a72h Другие программы, например архиваторы, формирующие саморазворачивающиеся файлы (self-extracting), также помещают в эту область уникальную информацию. Зная это, вы можете уточнить информацию о программе, сформировавшей исполняемый файл. В поле со смещением 0018h находится значение смещения таблицы размещения. Что это за таблица? Таблица размещения представляет собой 31 совокупность элементов, предназначенных для связи сегментов в программе. Число элементов таблицы содержится в поле со смещением 0006h. Каждый элемент имеет следующий формат: 0000h 2 байта Смещение внутри сегмента 0002h 2 байта Сегмент размещения Во время загрузки программы в память к ней добавляется префикс программного сегмента (PSP — program segment prefix) размером 256 байт. Сама программа загружается вслед за этим префиксом. Информация из префикса может быть использована для выделения символов из командной строки, определения объема доступной памяти, информации об окружении и т. д. В заключение отметим содержимое регистров после загрузки файла MZформата Л .р.9 приложения калькулятор и редактор текстов Цель-ознакомление Windows - 98 включает ряд вспомогательных программ, называемых стандартными, которые позволяют удовлетворить многие потребности пользователя. К ним относятся: редактор текста Блокнот и более сложный WordPad, графический редактор Paint, программа Калькулятор, а также другие полезные утилиты. Блокнот Простейший текстовый редактор, в котором можно создать текстовый документ, запомнить его в файле и распечатать. Блокнот имеет резко ограниченные возможности обработки текста и хранения больших текстовых документом. Это записная книжка, ориентированная на заметки, записки, справки небольшого размера. Окно программы Блокнот имеет строку меню, состоящую из разделов "Файл", "Правка", "Поиск", "Справка". Чтобы запустить Блокнот, выберите в меню "Пуск" пункты "Программы", "Стандартные", затем "Блокнот" (Notepad). Сразу после запуска программа готова к работе. По умолчанию в Блокноте используется стандартный системный шрифт. Из меню Правка можно настроить программу на работу с другим шрифтом. В этом же меню находится очень важная функция "Перенос по словам", которая позволяет переносить текст автоматически, когда заканчивается строка. Если данная функция отключена, строка имеет 255 символов. Общий объем информации, поддерживаемый редактором, составляет около 7 кб. Информация сохраняется в файлах текстового формата (*.txt), или с таким расширением, которое задаст пользователь. Редактор очень удобен для выполнения системных операций с файлами настройки системы. Часто его используют для предварительной подготовки 32 текстов писем в электронной почте или для сохранения небольших фрагментов текстового материала. В "Блокноте" можно предварительно готовить расчетные операции для дальнейшей обработки в программе "Калькулятор". Калькулятор Калькулятор - это простая, но полезная программа, которую можно использовать так же, как карманный калькулятор. Она имеет два режима работы: обычный (standard) калькулятор и инженерный (scientific) калькулятор. Обычный калькулятор выполняет четыре арифметических действия, а также вычисление процентов, квадратного корня и обратной величины. Инженерный калькулятор может также вычислять тригонометрические и логические функции, а также переводить числа и углы в разные системы счисления и выполнять другие операции. В Windows - 98 в калькулятор добавлена возможность выполнять статистические операции. Для переключения между обычным и инженерным режимами калькулятора используйте меню "Вид" (View). Чтобы запустить калькулятор, выберите в меню "Пуск" пункты "Программы", "Стандартные" (Accessories), затем "Калькулятор" (Calculator). Л.р.10 Динамические структуры данных для языка си Цель-ознакомление Динамические структуры данных и их организация с помощью указателей. Стеки, Очереди, односвязные и двухсвязные линейные списки и кольца. Бинарные деревья. Динамические структуры данных. Как известно, по признаку изменчивости структуры данных подразделяются на три класса — статические, полустатические и динамические. К числу последних относятся такие структуры, которые в процессе существования в памяти могут изменять не только число составляющих их элементов, но и характер связей между элементами. При этом не учитывается изменение содержимого самих элементов данных. 33 Такая особенность динамических структур, как непостоянство их размера и характера отношений между элементами, приводит к тому, что на этапе создания машинного кода программа-компилятор не может выделить для всей структуры в целом участок памяти фиксированного размера, а также не может сопоставить с отдельными компонентами структуры конкретные адреса. Для решения проблемы адресации динамических структур данных используется метод, называемый динамическим распределением памяти, то есть память под отдельные элементы выделяется в момент, когда они "начинают существовать" в процессе выполнения программы, а не во время компиляции. Компилятор в этом случае выделяет фиксированный объем памяти для хранения адреса динамически размещаемого элемента, а не самого элемента. Понятие стека. Стек (stack) — это такой последовательный список с переменной длиной, включение в который нового элемента и исключение из него выполняется только с одной стороны. Говорят, что стек функционирует по принципу LIFO (Last In — First Out: "последним пришел — первым вышел"). Примером стековой организации является винтовочный магазин: последний вставленный в него патрон при стрельбе "выйдет" первым. Логическая структура стека представлена на рис. 6.1а. Элементы стека e1 ,.., en могут содержать одно или несколько полей. Важнейшие операции в стеке — включение (insertion) и исключение (deletion). Процедура включения нового элемента в стек может быть организована как последовательность следующих действий: указатель стека (stack pointer) сначала перемещается "вверх" (по рисунку 6.1) на длину слота включаемого элемента, а затем по новому значению указателя помещается очередной элемент стека. При исключении из стека сначала прочитывается элемент en (и только он), а затем указатель перемещается на длину слота "вниз". Стек считается "пустым", если указатель вершины совпадает с указателем нижней границы. Для хранения стека в памяти отводится сплошная область, граничные адреса которой являются параметрами физической структуры стека. Если в процессе заполнения стека указатель, перемещаясь "вверх", выходит за границу первоначально отведенной области, то происходит переполнение стека (stack overflow). Переполнение стека рассматривается как исключительная ситуация, требующая выполнения действий по ее ликвидации. Физическая структура стека обычно дополняется дескриптором, который содержит поля, показанные на рис. 6.16. Стеки находят широкое применение при организации памяти компьютеров. По принципу LIFO организована, например, область 34 памяти, в которой размещаются локальные статические переменные пользовательских Паскаль — подпрограмм. Стековый характер имеет структура, хранящая информацию о вызовах активированных, но не завершенных, процедур; эта структура, называемая Call Stack, формируется Отладчиком Турбо Паскаля и используется при отладке пользовательских программ. В заключение отметим одну особенность функционирования полустатических структур. На рис. б. 16 изображены элементы стека, с одинаковыми по размеру слотами. Допустим, что нас интересует процесс заполнения стека, в который помещаются локальные данные вызванных, но не завершенных Паскаль-подпрограмм. Тогда с точки зрения программиста (на высоком уровне) элемент стека занимает участок памяти, соответствующей группе локальных статических переменных вызванной подпрограммы. Поскольку для цепочки вызовов разных подпрограмм в стеке размещаются разные группы локальных данных, то такие "групповые" элементы отличаются друг от друга размерами занимаемых участков стека. "С точки зрения" микропроцессора 35 стек заполняется последовательно двухбайтовыми порциями (и очищается тоже), поэтому элементом стека на низком уровне считается ячейка, состоящая из двух байтов, т. е. слово, и все элементы одинаковы по размеру. 36