Created using
htm2chm 3.0

Download it NOW!


general index

Программирование на языке С++

Примеры программирования на языке С++

Другие книги по программированию


general index

Basic
  • Введение в Visual Basic
  • Уроки программирования баз данных в Visual Basic
  • Учебные примеры
  • Кирпичики Visual Basic
  • Советы тем, кто программирует на Visual Basic

    C++

  • Visual C++ для начинающих
  • Си для профессионалов

    • FoxPro

  • Microsoft FoxPro. Краткое руководство по системе управления базами данных для Windows

    • Fortran

  • Учебник по Fortran

    • Pascal

  • Курс лекций по Pascal для начинающих
  • Borland Pascal 7.0. Руководство пользователя.
  • Borland Pascal 7.0 & Objects. Руководство по языку.
  • Процедуры и функции Pascal
  • Примеры программ на Pascal
  • Руководство разработчика на Delphi 5 (англ.) Для прочтения данной книги Вам необходимо установить Adobe Acrobat Reader

    • Assembler

  • Программирование на Ассемблере
  • Системное программирование на Assembler
  • Assembler в примерах

    • SQL

  • Структуризированный язык запросов (SQL)

    • Программирование для Интернет

  • Первые шаги програмирования HTML
  • Азы языка HTML
  • Практическое введение в программирование на HTML и JavaScript
  • Подробный курс Java
  • Самые популярные Java-скрипты
  • Советы web-designer'у
  • Dynamic HTML
  • Введение в Cascading Style Sheets
  • Каскадные таблицы стилей, уровень 1. Спецификация CSS1.
  • Введение в Perl
  • VB Script
  • VRML - Virtual reality modellnig language
  • XML - Extensible markup language
  • CGI - Common Gateway Interface
  • Flash 4 - новая революция в web-design'е!


Советы тем, кто программирует на Visual Basic

Андрей Колесов, Ольга Павлова

Совет 191. Как определять високосный год

Те, кто думают, что високосный год — это тот, который делится без остатка на четыре, глубоко заблуждаются. Так определяется високосный год в Юлианском календаре (старый стиль). А вот в Григорианском (новый стиль) — для устранения несоответствия календарного и солнечного (астрономического) года 100-й год не считается високосным, но каждый 400-й — считается. Ошибка средней продолжительности года в этом алгоритме составляет всего 26 секунд.

Таким образом, алгоритм определения високосного года выглядит следующим образом:

Public Function IsLeapYear(iYear As Integer)

' Проверка високосного года

If (iYear Mod 4 = 0) And _

((iYear Mod 100 <> 0) Or (iYear Mod 400 = 0))

Then IsLeapYear = True ' високосный

Else

IsLeapYear = False ' не високосный

End If

End Function

Но можно придумать алгоритм гораздо проще. Еще раз -- что такое високосный год? Правильно, тот, который имеет дату 29 февраля. А значит, работает такой алгоритм:

Function IsLeap (sYear As String) As Integer

IsLeap = False

If IsDate ("02/29/" & sYear) Then IsLeap = True

End Function

Однако по нашему мнению, данный алгоритм определения високосного года можно применять только к Григорианскому календарю, который был введен в действие в 1582 году. А до этого момента следует использовать правило Юлианского календаря (подробнее об этом см. статью "Y2K: как вести календарь" в этом же номере).

Совет 192. Форматирование числа при выводе

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

Function PadToString(myValue, Digits) As String

Dim Digits, MyValue

PadToString = String(Digits - Len(myValue), _

"0") & myValue

End Function

Сделав, например, такое обращение

NewStr$ = PadToString(1978, 8)

вы получите строковую переменную 00001978. Обратите внимание, что Digits и myValue — переменные типа Variant.

Совет 193. Как измерять временные интервалы

Для измерения временных интервалов между двумя произвольными моментами времени удобнее всего использовать встроенную функцию:

DateDiff (Interval, StartDate, EndDate[, FirstDay,[ FirstWeek]])

Строковая переменная Interval задает единицы измерения интервала — от секунд до года. Однако часто при тестировании скорости выполнения различных программных конструкций (BenchMark) секундной дискретности бывает недостаточно. В этом случае лучше воспользоваться еще одними внутренними системными часами Windows, которые измеряют время в "тиках" — миллисекундах с момента последнего старта или перезагрузки операционной системы. (При непрерывной работе компьютера обнуление счетчика происходит примерно каждые 49 суток.) Чтение значений "тиков" в Win32 производится с помощью API-функции GetTickCount$ (в Win16 для этого использовалась функция GetCurrentTime$).

Пример вычисления временных интервалов в программе может выглядеть таким образом:

Private Declare Function GetTickCount& Lib "kernel32" ()

Private Sub Form_Load()

Dim dStart As Date

Dim lCount&, i&, strValue$, msec&

'

lCount = 1000000 ' счетчик циклов

dStart = Now

msec& = GetTickCount&

For i = 1 To lCount

strValue = "Петя" & "+" & "Вася"

Next

MsgBox "Интервал: сек = " & _

DateDiff("s", dStart, Now) & _

" мсек = " & GetTickCount& - msec&

End Sub

Однако следует иметь в виду, что вычисленный таким образом интервал не является "чистым" временем выполнения данного программного кода. В него, включается также время выполнения более приоритетных заданий операционной системы -- работа других приложений, системных драйверов и пр. Для более точного определения "чистого" времени выполнения конкретного вычислительного процесса с точностью до 100 наносекунд в составе Win32 API системы Windows NT имеется функция GetProcessTime.

Совет 194. Как узнать значения кодовых таблиц

Как известно, Windows использует в своей работе две кодовые таблицы. Мы обычно называем их таблицами "DOS" и "Windows", но формально они именуются "OEM" и "ANSI". Довольно многие функции обработки строковых переменных зависят от значения кодовых таблиц, установленных при инсталляции системы, которые можно определить с помощью функций API:

Private Declare Function GetACP Lib _

"kernel32" () As Long

Private Declare Function GetOEMCP Lib _

"kernel32" () As Long

Private Sub Form_Load()

MsgBox "ACP = " & GetACP & vbCrLf & _

"OEMCP = " & GetOEMCP

End Sub

В принципе значения кодовых таблиц можно менять уже после инсталляции Windows. Мы не советуем злоупотреблять этим, но такая коррекция может быть полезной при тестировании и анализе работы программ. Параметры ACP и OEMCP хранятся в разделе HKEY_Local_Machine/System/CurrentControlSet/Control/Nls/Codepage файла Реестра и редактируются с помощью утилиты REGEDIT.EXE. Для их активизации нужно перезагрузить Windows.

Номера таблиц, которые могут понадобиться:

OEMCP = 866 (Russian), 437 (US — default), 850 (International), 855 (Cyrilic)

ACP = 1250 (Eastern European), 1251 (Cyrilic & Russian), 1252 (US & Western European), 1200 (Unicode)

СОВЕТ 195. Как запустить Web-браузер из VB-приложения

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

Введите следующий код в раздел General для модуля:

Declare Function ShellExecute Lib "shell32.dll" _

Alias "ShellExecuteA" _

(ByVal hwnd As Long, ByVal lpOperation As String, _

ByVal lpFile As String, ByVal lpParameters As String, _

ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

Public Const SW_SHOWNORMAL As Long = 1

Public Const SW_SHOWMAXIMIZED As Long = 3

Public Const SW_SHOWDEFAULT As Long = 10

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

Public Sub RunBrowser(strURL As String, iWindowStyle As Integer)

Dim lSuccess As Long

'-- Shell to default browser

lSuccess = ShellExecute(Me.hwnd, "Open", _

strURL, 0&, 0&, iWindowStyle)

End Sub

Для запуска какого-либо Web-узла, к примеру www.visual.2000.ru, используйте такой вызов функции:

Call RunBrowser ("www.visual.2000.ru", SW_SHOWNORMAL)

СОВЕТ 196. Чтобы избежать появления ошибок, преобразовывайте значения NULL в пустые строковые переменные

При получении значений NULL из объекта Recordset могут возникать ошибки. Одним из способов избежать такой ситуации является проверка значения поля, и в том случае, если оно равно NULL, преобразование его в пустую строковую переменную или в ноль. Например,

If isnull(rs("Field")) then tmp="" else tmp=rs("Field")

form.textfield=tmp

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

form.textfield=format(rs("Field"))

СОВЕТ 197. Создание Word-документов при помощи VB-кода

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

Шаг 1. Добавьте к проекту ссылку к Microsoft Word 8.0 Object Library (команда Project|References).

Шаг 2. Введите следующий код для создания экземпляра Word и напишите какой-либо текст в новом документе:

Dim objWord As New Word.Application

'-- Выводит Microsoft Word

objWord.Visible = True

'-- Добавляет новый документ

objWord.Documents.Add

'-- Вводит текст в документ

objWord.Selection.TypeText "Visual Basic!"

'-- Выделяет весь текст

objWord.Selection.WholeStory

'-- Изменяет размер шрифта

objWord.Selection.Font.Size = 50

Set objWord = Nothing

Шаг 3. Внимательно изучите Object Browser, чтобы воспользоваться другими свойствами и методами, предоставляемыми объектом Word.

СОВЕТ 198. Разбор полей строковой переменной при помощи функции SPLIT

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

Dim strAnimals As String

Dim iCounter As Integer

Dim arrAnimals() As String

strAnimals = "Cats,Dogs,Horses,Birds"

'-- Синтаксический разбор строки

arrAnimals = Split(strAnimals, ",")

'-- Просмотр массива в цикле

For iCounter = LBound(arrAnimals) To UBound(arrAnimals)

MsgBox arrAnimals(iCounter)

Next

СОВЕТ 199. Создайте свою собственную функцию Format

Команда Format, входящая в состав VB5, работает практически аналогично команде Print. Отличие состоит в том, что Format укорачивает выводимую строковую переменную, если количество символов форматирования превышает ее длину Для решения этой проблемы создадим свою функцию с названием FormatNum.

Public Function FormatNum(MyNumber As Double, FormatStr As String)

' Эта функция возвращает число, отформатированное как строковая

'переменная, содержащая требуемое минимальное количество символов

'

' MyNumber -- Используйте CDbl(MyNumber) при вызове функции, чтобы

' избежать появления ошибки о несовпадении типов

'

FormatNum = Format(MyNumber, FormatStr)

If Len(FormatNum) < Len(FormatStr) Then

FormatNum = Space(Len(FormatStr) - Len(FormatNum)) _

& FormatNum

End If

End Function

Использование новой функции Format проиллюстрируем на таком примере:

Private Sub Form_Load()

Dim MyVar, FormatStr$, f1$, f2$

MyVar = 12.1

FormatStr$ = "####.##"

f1$ = Format$(MyVar, FormatStr$)

f2$ = FormatNum(MyVar, FormatStr$)

MsgBox f1$ & " " & Len(f1$) & vbCrLf & _

f2$ & " " & Len(f2$)

End Sub

СОВЕТ 200. Как осуществить выход из Windows с помощью VB

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

Declare Function ExitWindowsEx& Lib _

"user32" (ByVal uFlags&, ByVal wReserved&)

' константы, необходимые для выхода из Windows

Global Const EWX_FORCE = 4 ' закрытие неактивных приложений

Global Const EWX_LOGOFF = 0 ' выход из системы

Global Const EWX_REBOOT = 2 ' перезагрузка

Global Const EWX_SHUTDOWN = 1 ' закрытие системы

Для выключения Windows используйте такой вызов функции:

' выключает компьютер

lresult = ExitWindowsEx(EWX_SHUTDOWN, 0&)

Для выполнения других операций замените первый параметр при вызове функции ExitWindowsEx на соответствующую константу.

СОВЕТ 201. Для ускорения процесса загрузки изменяйте базовые адреса компонентов типа in-process

При загрузке собственного компонента типа in-process в ходе выполнения VB-приложения, этот компонент размещается, начиная с некоторого базового адреса памяти.

Как можно поменять этот адрес для своего компонента? Для этого откройте диалоговое окно Project Properties и выберите вкладку Compile. Адрес вводится в поле DLL Base Address в виде десятичного или шестнадцатеричного целого числа без знака. По умолчанию используется значение &H11000000 (285,212,672). Если вы забудете изменить его, ваш компонент вступит в противоречие с любым другим компонентом типа in-process, скомпилированным с учетом используемого по умолчанию адреса. Поэтому рекомендуем вам задавать какой-либо отличный от него адрес.

Выбирайте базовый адрес в диапазоне между 16 Мбайт (16 777 216 или &H1000000) и 2 Гбайт (2,147,483,648 или &H80000000). При этом он должен быть кратным 64 Кбайт. Область памяти, используемая вашим компонентом, начинается с исходного базового адреса и имеет размер скомпилированного файла, округленного до следующего числа, кратного 64 Кб.

Ваша программа не может превышать 2 Гбайт, поэтому максимальный базовый адрес фактически равен 2 Гбайт минус область памяти, занимаемая созданным компонентом. Исполняемые файлы обычно будут загружаться по логическому адресу в 4 Мбайт. Область памяти меньше 4 Мбайт резервируется для Windows 95, а области свыше 2 Гбайт -- для Windows 95 и Windows NT.

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

Совет 202. Зачем нужен параметр Alias в операторе Declare.

Издавна (еще в версиях для DOS) в операторе Declare использовался ключ Alias для переопределения имени вызываемой процедуры. Конструкция

Declare Function vbname Lib libname [Alias aliasname]

означает, что к процедуре, записанной в библиотеке libname под именем aliasname, в VB-программе обращаются под именем vbname. Обычно это применяется в двух случаях:

  1. Когда имя aliasname просто недоступно в VB. В частности, при обращении к функциям, которые имеют (по принятым в C правилам) в начале названия символ подчеркивания, например _lOpen. Или когда внешняя функция использует имя, которое совпадает с зарезервированным ключевым словом VB (скажем SetFocus).
  2. Когда подразумевается, что VB-программа может работать с двумя версиями одной и той же (с точки зрения функциональности) процедуры. В этом случае гораздо проще исправить один оператор Declare:

Declare Function vbname Lib libname Alias aliasname1

на

Declare Function vbname Lib libname Alias aliasname2

чем менять во всей программе имя процедуры vbname.

Второй вариант особенно часто используется для обеспечения преемственности кода при переходе от Win16 к Win32 — имена многих API-функций при этом поменялись. В этой связи нужно специально выделить часто встречающийся случай, когда к привычному имени, типа SomeFunction (Win16), прибавился суффикс A и получилось SomeFunctionA (Win32). Почему так случилось, расскажет следующий совет.

Совет 203. Помните: VB использует кодировку ANSI при обращении к API-функциям.

Дело в том, что Windows 3.x (Win16) использовала только один формат хранения символьных данных (строковых переменных) — ANSI (набор однобайтовых символов). Система Win32 представлена двумя вариантами — Windows 9x и Windows NT, которые применяют разные внутренние форматы: соответственно ASNI и Unicode (набор целочисленных, двубайтовых символов). В результате, одна API-функция в Win32, например GetWindowText, фактически реализована в двух вариантах, с добавлением к имени суффикса A (символы передаются в кодировке ANSI) и W (Wide = Unicode): GetWindowTextA и GetWindowTextW. При этом следует иметь в виду, что физически в Windows 9x и Windows NT используется разная реализация таких функций.

Windows 9x допускает использование только ANSI-версий процедур и совсем не поддерживает Unicode. Но при этом она включает оба варианта функций, второй из которых на самом деле не работает. Представляется вполне логичным, если бы обращение, скажем к GetWindowTextW, вызывало бы сообщение об отсутствии такой функции. Однако в реальности данная функция не выполняет никаких действий и возвращает в вызывающую программу нулевое значение, которое можно интерпретировать как ошибку. Однако если программный код:

ItemHeight& = SendMessageA(.hWnd, LB_GETITEMHEIGHT, 0, 0)

NewIndex& = ListHeight& \ ItemHeight&

работает нормально, то замена SendMessageA на SendMessageW вызовет программную ошибку "деление на ноль".

В Windows NT оба варианта функций являются рабочими, что позволяет использовать кодировку и ANSI, и Unicode. В первом случае нужно обращаться к A-процедуре, которая преобразовывает символьный код из ANSI в Unicode и передает управление на собственно обработку в W-процедуру. Во втором случае нужно обращаться напрямую в W-процедуру.

32-разрядные версии Visual Basic используют Unicode для внутреннего хранения строковых переменных. Однако при обращении к API- или DLL-функциям, описанным с помощью оператора Declare, производится автоматическое преобразование символьных данных в ANSI (а потом -- обратно). Таким образом, при работе с VB нужно обращаться к ANSI-варианту API-функций как в Windows 9x, так и Windows NT.

В принципе имя, типа SendMessageA, можно использовать непосредственно в программе. Но общепринятым считается применение имени функции без суффикса, для чего применяется параметр Alias:

Declare Function SendMessage ... Alias "SendMessageA"

Во-первых, это обеспечивает определенную совместимость имен с API Win16, а во-вторых, подчеркивает, что используется один из вариантов функции SendMessage.


Первые шаги - Ступенька первая, инструментарий.

Главная | Оглавление | Словарь

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

D:\первые шаги\

Теперь откроем блокнот (notepad) и скопируем туда следующий текст:

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body>
Здравствуйте, это моя первая страница.
<br>
Добро пожаловать! :)
</body>
</html> (прим.)

А теперь сохраним этот документ, присвоив ему имя *.html

D:\первые шаги\index.html

Теперь откроем, допустим, Internet Explore (не закрывайте блокнот, он нам еще пригодится).

Файл - Открыть - кнопка Обзор - Наш документ (index.html).

Если мы чего-то изменили в нашем *.html документе (в блокноте), то, чтобы посмотреть как это выглядит в Internet Explorer, надо не забывать нажимать в IE (Internet Explorer) кнопку ОБНОВИТЬ (тоже самое касается других броузеров:) Если изменений не видно, то это значит, что вы где-то что-то неправильно написали, или забыли сохранить документ:)

Посмотрим теперь, что у нас получилось, и разберемся как оно так получилось:)

Шагнуть на следующую ступеньку


Первые шаги - Ступенька вторая, что такое хтмл (html), обязательные тэги.

Главная | Оглавление | Словарь

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body>
Здравствуйте, это моя первая страница.
<br>
Добро пожаловать! :)
</body>
</html> (прим.)

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

Пример тэга: <br> (перенос текста на другую строку, что-то вроде Enter:). Попробуйте между тэгами Body его понатыкать - посмотрите, что получится:)

Т.е. все, что находится между < и > - это тэг. Текст, не находящися между такими скобками < > - весь виден, при просмотре в броузере.

Как мы видим на нашем примере тэгов много и они разные:). Обратите внимание на картинку, она тут не просто так, она показывает нам обязательные тэги.

<html>

Этот тэг должен открывать документ. Если есть открывающий тэг, то должен быть и закрывающий:

</html>

И так всегда по умолчанию. Некоторые тэги, вроде <br>, не требуют закрывающего тэга. Но вернемся к нашему документу и рисунку.


Первые шаги - Ступенька третья, тэги body и head.

Главная | Оглавление | Словарь

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body>
Здравствуйте, это моя первая страница.
<br>
Добро пожаловать! :)
</body>
</html> (прим.)

<head> </head> - голова документа
<body> </body> - тело документа

Все тэги, расположенные между <head> </head>, это что-то вроде служебной информации (наша служба и опасна и трудна, и на первый взгляд как-будто не видна:). Например <title> - заголовок. Зачем он? Откройте IE (Internet Explore) с нашим документом и устремите свой взгляд выше всех командных строк (на заголовок окна)... Увидели?:)

Все тэги, расположенные между <body> </body> - непосредственное содержание документа. Следущие несколько ступенек будут посвящены именно этим тэгам: мы узнаем как менять цвет текста, фона, как делать текст крупнее-мельче, поговорим о картинках, таблицах и многом другом:)


Первые шаги - Ступенька четвертая, учимся раскрашивать, как задать цвет текста в документе, палитра (все цвета интернета).

Главная | Оглавление | Словарь

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

Зачем все это? А затем, что все цвета прописываются именно таким способом.

Например белый цвет - ffffff

Но вернемся к нашей страничке. Давайте окрасим слова Добро Пожаловать в красный.

<font color="#CC0000">Добро пожаловать! :)</font>

Теперь попробуйте вместо СС0000 поподставлять другие значения цветов.

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body>
Здравствуйте, это моя первая страница.
<br>
<font color="#CC0000"> Добро пожаловать! :) </font>
</body>
</html> (прим.)

Тэг <font></font> - многофункционален. Им может задаваться не только цвет текста в конкретной части документа, но и размер шрифта, и вид шрифта (Arial), но об этом чуть позже.

Как же еще задавать цвета в документе? - вспомним об открывающем теге <body>:

<body text="#336699">

Это значит, что весь текст страницы будет синим, кроме текста, для которого мы специально прописали <font></font> (если цвет в <body> не задавать, то по умолчанию он будет черным).

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699">
Здравствуйте, это моя первая страница.
<br>
<font color="#CC0000"> Добро пожаловать!</font> :)
</body>
</html> (прим.)

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


Первые шаги - Ступенька пятая, цвет фона в документе, палитра (все цвета интернета).

Главная | Оглавление | Словарь

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

Цвет фона устанавливается в уже нам знакомом тэге <body>:

<body bgcolor="#000000">

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

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
Здравствуйте, это моя первая страница.
<br>
<font color="#CC0000"> Добро пожаловать!</font> :)
</body>
</html> (прим.)

Обратите внимание: мы одновременно можем прописать в теге <body> и цвет текста в документе, и цвет фона (это на всякий случай, если вы еще не поняли:).

<body text="#336699 bgcolor="#000000">

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

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

Пример: Девочка в ярко-розовых джинсах и противном салатовом топике с надписью Kiss me, безусловно, привлечет внимание, но надолго ли? Не уподобляйтесь такой девочке:).

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


Первые шаги - Ступенька шестая, параграфы, центрирование, расположение текста по левому, по правому краю, по центру.

Главная | Оглавление | Словарь

В этой главе мы поговорим о параграфах. Параграфы вводятся тэгом:

<p></p>

С помощью параграфов мы можем центрировать текст:

<p align="center">текст</p>

С помощью параграфов мы можем выровнять текст по левому краю:

<p align="left">текст</p>

Или по правому краю документа:

<p align="right">текст</p>

Теперь введем параграфы в наш документ и посмотрим наглядно, что получится (в наш пример я подставила параграф с параметром центрирования текста (center), попробуйте подставить в параграф другие параметры со значениями Left или Right).

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<p align="center">
Здравствуйте, это моя первая страница.
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </p>
</body>
</html> (прим.)

Заметьте, что текст в документе, если не задавать параграфы, всегда выравнивается по умолчанию по левому краю. Также запомните, что к параграфу уже не нужен тэг <br> для переноса строки, т.к. перенос задается по умолчанию. Но что делать, если вам этот перенос никак не нужен? Есть тэг альтернативный <p align="center">:

<center>текст</center>

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
Здравствуйте, это моя первая страница.
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
</body>
</html> (прим.)

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

<p align="justify">текст</p>

Это мой любимый атрибут параграфа, он выравнивает текст по обоим краям документа, и все становится милым и аккуратным. Почему же это опасный атрибут? Да не работает он в старых версиях броузеров, поэтому у тех, кто использует старые броузеры, он может не дейcтвовать (не отображаться), и вся красота-ляпота да аккуратность сойдет на нет (т.е. текст выровняется в таком случае по левому краю документа). Тем не менее я атрибутом justify злоупотребляю:)

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
Здравствуйте, это моя первая страница.
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли посмотреть мои фотографии, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появться еще один виртуальный друг? :)
</p>
</body>
</html> (прим.)

Я тут за вас набросала примерный текст вступления (вы придумаете, конечно, что-нибудь свое). Теперь вставьте пример в наш документ и посмотрите за что я так люблю justify:)

Посмотрели? - Шагаем дальше.


Первые шаги - Ступенька седьмая, заголовки, увеличение, уменьшение, размер текста.

Главная | Оглавление | Словарь

В этой главе мы научимся выделять текст при помощи заголовков и узнаем еще одну функцию тэга <font></font>.

Итак, мы уже немного умеем форматировать текст, но посмотрите на наш документ. Мне кажется, что фраза "Здравствуйте, это моя первая страница" так и просится, чтобы ее выделили. Для этого можно использовать заголовки:

<H1> текст </H1>

<H2> текст </H2>

<H3> текст </H3>

<H4> текст </H4>

<H5> текст </H5>
<H6> текст </H6>

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

В пример я ввела заголовок третьего уровня <H3></H3> (мой любимый размер:). Смотрите, что получилось.

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли посмотреть мои фотографии, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится еще один виртуальный друг? :)
</p>
</body>
</html> (прим.)

Заголовки предназначены для выделения небольшой части текста (строки, фразы), НО, если вы хотите выделить большой фрагмент текста, или только одно слово, при этом без переноса строки, то как быть? С заголовками такое не пройдет, поэтому вспомним о тэге <font></font>:
<font size="+4"> текст </font>
<font size="+3"> текст </font>
<font size="+2"> текст </font>
<font size="+1"> текст </font>
<font size="+0"> текст </font>
<font size="-1"> текст </font>
<font size="-2"> текст </font>

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

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли посмотреть мои фотографии, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится <font size="+1"> еще один виртуальный друг? :)</font>
</p>
</body>
</html> (прим.)

В нашем примере я выделила слова "еще один виртуальный друг?", пусть все думают, что вы горите желанием завести еще одного виртуального друга:)

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

Фух:) топаем дальше.


Первые шаги - Ступенька восьмая, жирный текст, курсив, как задать шрифт.

Главная | Оглавление | Словарь

Теперь настала пора поговорить о том как делается курсив, подчеркнутый текст, полужирный текст и фиксированный текст:

<b> Полужирный текст </b>
<i> Наклонный текст (курсив) </i>
<u> Подчеркнутый текст </u>

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

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли посмотреть мои фотографии, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится <b> еще один виртуальный друг? :)</b>
</p>
</body>
</html> (прим.)

Теперь пара строк о фиксированом шрифте. Что это такое за шрифт? Это шрифт с символами одинаковой ширины, оттого и зовется фиксированным. А тэг для него следующий:

<tt> fixed - фиксированный шрифт </tt>

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

<pre> текст     (куча пробелов)     текст </pre>

Текст между <pre> и </pre> тоже фиксированный, но тут есть одна большая особенность, он не только фиксированный, но еще и выводится с точностью до пробела так, как вы его набили в блокноте, сей странный тэг может быть полезен, допустим, для форматирования стихотворений, не злоупотребляйте им, т.к. он коварный и противный:)

Запомните, к одному фрагменту текста может применятся сразу несколько тэгов:

<tt><b><i> текст </i></b></tt>

В нашем примере текст фиксированный, полужирный и курсивный (вот так-то:), так что не бойтесь использовать различные комбинации тэгов, эксперементируйте, но с умом:)

Хочу прежде, чем мы перейдем к картинкам, также упомянуть об одном интересном атрибуте тэга <font>:

<font face="ARIAL"> текст (шрифт Arial)</font> (прим.)

С помощью атрибута face в нашем примере я задала шрифт Arial. Таким образом вы можете задать и какой-нибудь экзотический шрифт для своей страницы, но это немного рискованно, т.к. у вашего посетителя может не оказаться такого шрифта, поэтому все же лучше задавайте стандартные (Arial (без надсечек), Times Roman (пропорциональный), Courier (равноширинный) и другие).

Все-все, теперь идем рисовать:)

О других тэгах для форматирования текста вы можете узнать в главе 31.


Первые шаги - Ступенька девятая, как вставлять картинку в документ, как прописывать путь.

Главная | Оглавление | Словарь

Сядем, отдохнем, поговорим о планах на будущее: как рисовать, как пользоваться графическими редакторами я вас учить не буду, я только посоветую вам Adobe Photoshop и Image Styler 1.0, т.к. именно этими двумя я в основном и пользуюсь.

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

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

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

<img src="my.jpg">

Вместо my.jpg мы можем подставить имя любой картинки (me.gif, main.png). Самое главное понять, что все расположенное между кавычками - ссылка (путь к картинке). Наш пример говорит о том, что картинка лежит в том же каталоге (директории, папке), в которой лежит и наш документ. Если картинка лежит в поддиректории то ссылка на неё будет выглядеть так:

<img src="/my/my.jpg">

Если картинка лежит на уровень выше, а документ находится в поддиректории, то ссылка на неё будет такой:

<img src="../my.jpg">

Если картинка лежит на другом сайте, то путь прописывается полностью:

<img src="http://www.homepage.ru/my/my.jpg">

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

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

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
<img src="primtocodephoto.gif"> Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли посмотреть мои фотографии, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится <b> еще один виртуальный друг? :)</b>
</p>
</body>
</html> (прим.)

Посмотрите, что у нас получилось. Не очень симпатишно, не так ли? Было бы лучше, если бы текст аккуратно располагался сбоку от картинки. Идем дальше, чтобы узнать, как это сделать.


Первые шаги - Ступенька десятая, текст и картинка, обтекание текстом картинки, как сделать рисунок фоном документа.

Главная | Оглавление | Словарь

Итак, мы задались вопросом как же сделать так, чтобы текст располагался весь рядом с картинкой, а не только одна его строчка. Все очень просто. У некоторых тэгов есть параметры (атрибуты), параметр может задаваться один, а может их быть несколько. Например у тэга <p> - <p align="justify">. Параметр align есть и у картинок:

<img src="pr1.png" align="left">

Это означает, что картинка будет прижата к левому краю экрана, а текст будет обтекать ее справа. Чтобы сделать наооборот (картинка справа, текст слева) надо прописать right:

<img src="pr1.png" align="right">

Но это не все: текст может располагаться внизу картинки (это по умолчанию) - (1), посередине - (2), и вверху - (3):

(1) - <img src="pr1.png" align="bottom">
(2) - <img src="pr1.png" align="middle">
(3) - <img src="pr1.png" align="top">

Кроме параметра align существует еще несколько параметров:

(1) - <img src="pr1.png" Vspace="10">
(2) - <img src="pr1.png" Hspace="30">
(3) - <img src="pr1.png" alt="моя фотография">
(4) - <img src="pr1.png" width="100">
(5) - <img src="pr1.png" height="200">
(6) - <img src="pr1.png" border="5">

Теперь последуют обяснения по пунктам.

(1) - параметр vspace - задает расстояние между текстом и рисунком (по вертикали). Расстояние задается в пикселях (pixel - минимальная единица изображения, точка. Например разрешение экрана 800х600 - 800 на 600 точек). В нашем примере расстояние равно 10 пикселям.

(2) - параметр hspace - тоже задает расстояние между текстом и рисунком, но по горизонтали. Расстояние задается в пикселях. В нашем примере оно равно 30 пикселям (точкам).

(3) - параметр alt - краткое описание картинки. Если навести курсором мыши на рисунок, и так подержать его (курсор) несколько секунд выскочит описание картинки. В нашем случае это будет фраза - "моя фотография". Если параметр alt не задавать, описания не будет. Но умные люди говорят, что описание картинкам задавать следует (особенно, если это кнопки), т.к. есть особенные люди, которые бродят по интернету с отключенной графикой. Без alt им не будет видно на что жать, т.к. картинка не отображается, а при заданном alt, можно увидеть надпись, для чего она (картинка) предназначалась.

(4) - параметр width - ширина самой картинки (в пикселях). Если ширину не задавать специально, то по умолчанию она будет равна реальной ширине картинки (а так вы можете ее сделать или уже или шире).

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

(6) - параметр border - рамка вокруг самой картинки (в пикселях). Можно не задавать.

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

<img src="pr1.png" align="left" HSPACE=30 VSPACE=5 alt="моя фотография">

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

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
<img src="primtocodephoto.gif" align="left" HSPACE=30 VSPACE=5 alt="моя фотография"> Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли посмотреть мои фотографии, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. <br><br> А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится <b> еще один виртуальный друг? :) </b><br><br> На фотографии изображен(а) я. Качество картинки не очень хорошее, к сожалению, поэтому она не четкая и разглядеть черты моего лица немного проблематично. Но в целом заметно, что я вполне ничего:) <br><br> Если ты так тоже думаешь, то давай как-нибудь встретимcя, поболтаем, чайку попьем в кафешке?:) Кто знает, может быть мы и в реальной жизни станем друзьями:)
</p>
</body>
</html> (прим.)

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

Вот мы узнали о том, как расположить текст относительно картинки (+ еще несколько нужных параметров), но мы не говорили как расположить саму картинку в центре экрана (справа, слева). Здесь все очень просто, картинки как и текст можно расположить при помощи параграфов (<p></p>), это наглядно представлено в нашем примере.

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

<body text="#336699" bgcolor="#000000" background="ваш_фон.jpg">

Параметр Background и указывет на то, где лежит фоновая картинка, в нашем примере он указывает на то, что наша фоновая картинка лежит в той же директории (папке), что и документ.

Но зачем оставлять параметр bgcolor, если есть background? А вдруг фоновая картинка не загрузится (представьте, такое может быть), тогда сами поймете зачем.

Если вы достаточно поигрались с параметрами картинок, то продолжим наше обучение.


Первые шаги - Ступенька одиннадцатая, как прописывать ссылки на другие документы, как изменять цвета ссылок.

Главная | Оглавление | Словарь

Теперь пора поговорить о ссылках. Наша страничка может состоять из нескольких документов. Один из них главный (index.html или main.html) - он открывается первым и должен обязательно лежать на вашем сайте в интернете.

Остальные документы вы можете называть как угодно (photos.html, about_me.html, my_pets.html, friends.html, gh516hgd.html). Они все могут лежать в одной директории (папке), а могут в разных.

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

Для начала создадим новый документ (в нашем примере prf.html) в той же директории (папке), где находится наш главный документ index.html. Содержание документа выдумайте сами, у вас для этого уже достаточное знание. Пусть prf.html - документ с вашими фотографиями. Тогда мы можем без зазрения совести фразу "посмотреть мои фотографии" сделать ссылкой на prf.html:

<a href="prf.html">посмотреть мои фотографии</a> (прим.)

Тэг <a></a> делает ссылкой заключенную в него картинку или фразу (текст). Принципы прописывания пути здесь такие же как в случае с картинками:

(1) - <a href="prf.html">мои фотографии</a>
(2) - <a href="/photos/prf.html">мои фотографии</a>
(3) - <a href="http://www.homepage.ru/prf.html">мои фотографии</a>

В случае (1) документ лежит в той же директории (папке), что и документ, в котором мы ссылаемся на prf.html, в случае (2) документ лежит в поддиректории /photos, в случае (3) мы ссылаемся на сайт http://www.homepage.ru, где лежит нужный нам документ.

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

<body text="#336699" bgcolor="#000000" link="#339999" alink="#339999" vlink="#339999">

Разберемся что к чему: link - цвет ссылки, alink - цвет активной ссылки (нажатой), vlink - цвет уже посещенной ссылки. В нашем примере цвета одинаковые, но они могут быть разными, также не карается законом, если цвет ссылки такой же как цвет текста документа (ссылка же выделяется чертой). Итак пропишем цвета для ссылки и саму ссылку в нашем документе:

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000" link="#339999" alink="#339999" vlink="#339999">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
<img src="primtocodephoto.gif" align="left" HSPACE=30 VSPACE=5 alt="моя фотография"> Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли <a href="prf.html">посмотреть мои фотографии</a>, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. <br><br> А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится <b> еще один виртуальный друг? :) </b><br><br> На фотографии изображен(а) я. Качество картинки не очень хорошее, к сожалению, поэтому она не четкая и разглядеть черты моего лица немного проблематично. Но в целом заметно, что я вполне ничего:) <br><br> Если ты так тоже думаешь, то давай как-нибудь встретимcя, поболтаем, чайку попьем в кафешке?:) Кто знает, может быть мы и в реальной жизни станем друзьями:)
</p>
</body>
</html> (прим.)

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

<a href="mailto:pochta@mail.ru"> pochta@mail.ru - пишите письма </a> (прим.)

Запомните это раз и навсегда и не пытайтесь ее прописать по другому. Ссылка на документ, картинку или файл - одно дело, а ссылка на почтовый ящик - другое (да, но цвета всех ссылок в документе одинаковые:).

Читайте также главу 36, о ссылках внутри одного документа.


Первые шаги - Ступенька двенадцатая, как сделать картинку ссылкой, как убрать рамку (рамочку) вокруг катинки.

Главная | Оглавление | Словарь

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

<a href="prf.html"> <img src="primtocodephoto.gif" align="left" HSPACE=30 VSPACE=5 alt="моя фотография"> </a>

Это мы введем в наш пример, сделав ссылкой на документ с фотографиями (prf.html) картинку pr1.png:

<html>
<head>
<title>Мой первый шаг </title>
</head>
<body text="#336699" bgcolor="#000000" link="#339999" alink="#339999" vlink="#339999">
<center>
<H3>Здравствуйте, это моя первая страница.</H3>
<br>
<font color="#CC0000"> Добро пожаловать!</font> :) </center>
<p align="justify">
<a href="prf.html"><img src="primtocodephoto.gif" align="left" HSPACE=30 VSPACE=5 alt="моя фотография"></a> Я совсем недавно начал(а) знакомиться с виртуальной жизнью, но мне по давней традиции тоже захотелось создать свою домашнюю страничку для моих новых виртуальных друзей и знакомых, чтобы они могли <a href="prf.html">посмотреть мои фотографии</a>, почитать обо мне, черкнуть пару строчек в мою гостевую книгу. <br><br> А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится <b> еще один виртуальный друг? :) </b><br><br> На фотографии изображен(а) я. Качество картинки не очень хорошее, к сожалению, поэтому она не четкая и разглядеть черты моего лица немного проблематично. Но в целом заметно, что я вполне ничего:) <br><br> Если ты так тоже думаешь, то давай как-нибудь встретимcя, поболтаем, чайку попьем в кафешке?:) Кто знает, может быть мы и в реальной жизни станем друзьями:)
</p>
</body>
</html> (прим.)

Теперь посмотрите в Explorer, что у нас получилось. Картинка primtocodephoto.gif стала ссылкой. Если вы заметили (а если не заметили, то обратите внимание): вокруг картинки появилась рамочка. Эту рамочку можно оставить, если вам она нравится, а можно убрать, если вы зададите картинке параметр border="0"

<img src="primtocodephoto.gif" align="left" HSPACE=30 VSPACE=5 alt="моя фотография" border="0"> (прим.)

Если вы думаете, что на этом ваше мучение с изучением ссылок закончилось, то это зря вы так:) - у нас впереди еще целая ступенька.

Читайте также - Ступенька 33 - Навигационные карты. Map. Как сделать часть картинки ссылкой.


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

Главная | Оглавление | Словарь

Если вы уже достаточно попутешествовали по интернету, то вы должны были заметить, что ссылка может быть не только на документ с расширением *.html, но и на многие другие (*.doc, *.mp3, *.jpg, *.gif, *.txt, *.zip, *.exe и т.д:) Как это делается? Да все по тому же принципу:

<a href="http://www.melody.ru/music.mp3"> скачать песню </a>

Если адрес указан таким способом:

<a href="http://www.melody.ru/"> музыкальный сайт </a>

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

Но поговорим особо о картинках, многие из вас, наверное, сталкивались с таким явлением: когда жмешь на маленькую картинку, то загружается большая в том же или в новом окне. Как это делается? Тут никаких хитростей: делаем ссылкой картинку (допустим small.jpg) и ссылаемся на другую картинку (допустим big.jpg):

 <a href="big.jpg"> <img src="small.jpg"> </a>

В этом случае большая картинка откроется в том же окне. Но как же сделать так, чтобы картинка (или любой другой файл-документ) открылась в новом окне? Дело в том, что у тэга <a></a> есть параметр target:

<a href="big.jpg" target="_blank"> <img src="small.jpg"> </a>

Итак, target="_blank" - указывает на то, что документ (картинка в нашем случае), на который ведет ссылка, откроется в новом окне броузера (Explorer`а). В наш большой пример я это вводить не буду. Т.к. не хочу заводить большие картинки на сайте, и т.к. вы должны уже сами все подставлять и сами эксперементировать:)

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


Первые шаги - Ступенька четрынадцатая, о списках.

Главная | Оглавление | Словарь

Вот мы добрались и до не очень часто используемых тэгов. Как же так? - возмутится бедный мой спутник - А таблицы? А фреймы? - Ну, это темы других лестничных пролетов, а мы еще и первого не одолели:) Теперь, когда я вас порадовала этим фактом, продолжим обучение:)

Я думаю: самая пора поговорить о списках. Что такое списки? Это лучше демонстрировать на наглядном примере:

  • Текст один
  • Текст другой
  • Текст сякой
  • Текст эдакий

    • Скорее всего вы с этим сталкивались. Все это удобно в определенных ситуациях. В каких? Загляните, например, в раздел Оглавление и увидите:)

    Теперь разберемся, какими же тэгами обозначаются списки:

    <li>Текст один</li>
    <li>Текст другой</li>
    <li>Текст сякой</li>
    <li>Текст эдакий</li>

    Да-да, вcе опять оказалось очень простым:) Именно тэгом <li></li> они и задаются. Возникает вопрос: можно ли помимо закрашенного кружочка поописать еще чего-нибудь подобное?

  • По умолчанию - (1)
  • Полый кружок - (2)
  • Не будь таким квадратным - (3)

    •

    У тэга <li> есть параметр type:

    В случае (1) - <li type="disk">,
    в случае (2) - <li type="circle">,
    в случае (3) - <li type="square">.

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

    <ul>Ваш текст</ul>

    Чтобы отступ (табуляция) был больше надо вкладывать тэг <ul> в самого себя:

    <ul><ul>Два отступа</ul></ul>
    <ul><ul><ul>Три отступа</ul></ul></ul>

    Зачем я завела разговор об этом тэге, вместо того, чтобы рассказать о том, что еще можно сделать со списками? Хм:) ну, у меня все со злым умыслом:

    • Сдвиг один
      • Сдвиг другой
        • Сдвиг сякой

    Хочу обратить ваше внимание, что это прописано без параметра type, но при помощи тэга <ul>:

    <ul><li>Сдвиг один</li></ul>
    <ul><ul><li>Сдвиг другой</li></ul></ul>
    <ul><ul><ul><li>Сдвиг сякой</li></ul></ul></ul>

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


Первые шаги - Ступенька пятнадцатая, о линиях.

Главная | Оглавление | Словарь

В последнее время я все реже и реже встречаю такой элемент, как линии. Теперь их увидишь разве что в гостевых книгах, или на форумах. Линия задается тэгом <Hr> и не требует закрывающего тэга:

Вот такая она, в форме канавочки. У линии есть много разных параметров:

(1) <Hr align="right"> (center или left)
(2) <Hr width="30%"> (ширина линии в процентах/пикселях)
(3) <Hr size="6"> (толщина линии)
(4) <Hr NoShade> (отмена объемности)
(5) <Hr color="cc0000"> (цвет линии, только в IE)

Выглядит все это так - Первый (1):

Второй (2):

Третий (3):

Четвертый (4):

Пятый (5):

Вот и вся премудрость с линиями. Естественно, эти параметры могут употребляться одновременно.

Не злоупотребляейте параметром Color, т.к. он действует только в Интернет Иксплорер (если вам так хочется все же цветную линию, то сделайте, допустим красную картинку 1x1 пиксель и вставьте ее в свой документ. Тут вам приходятся параметры картинок height и width. Пусть height=1, а width=500 - вот вам и линия:). О параметрах картинок говорится в главе 10).

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

У нас осталась еще одна главка, после которой мы перейдем к таблицам.


Первые шаги - Ступенька шестнадцатая, о спецсимволах.

Главная | Оглавление | Словарь

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

(1) - &lt; - <
(2) - &gt; - >
(3) - &quot; - "
(4) - &nbsp; - пробел
(5) - &amp; - &

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

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

Символ пробела (4). Зачем он нужен? Ну, допустим вам понадобилось пять пробелов подряд, а тэг <pre> с его принудительным переносом строки вам вовсе не нужен, вот тогда поймете, ведь спецсимвол пробела можно написать хоть тысячу раз:).

Но тут существует одно но:

слово1&nbsp;слово2&nbsp;слово3 = слово1 слово2 слово3

Ни одно из этих слов не перенесется на другую строку без двух других, и хотя посетитель увидит обычную фразу, где между словами стоит пробел, но по идее это же одно длинное слово, а не три, т.к. &nbsp; - неделимый символ пробела:) - учтите и используйте с умом.

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

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

Вот такая это сложная штука жизнь %) Полную коллекцию спецсимволов вы можете изучить здесь. А можете и не изучать.


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

Главная | Оглавление | Словарь

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

Таблица задается тэгом:

<table></table>

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

<tr></tr> - строчка таблицы
<td></td> - столбец (ячейка) таблицы

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

<table>
<tr></tr>
<tr></tr>
</table>

Сначала задаем строки. В нашем примере их две. Теперь в каждой строке зададим по три столбца (ячейки):

<table>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</table>

Мой вам совет, для начала рисуйте таблицу на листе бумаге, чтобы все наглядно видеть.

Итак, теперь нам надо заполнить получившийся каркас:

<table>
<tr>
<td>1x1</td>
<td>1x2</td>
<td>1x3</td>
</tr>
<tr>
<td>2x1</td>
<td>2x2</td>
<td>2x3</td>
</tr>
</table>

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

1x1 | 1x2 | 1x3
2x1 | 2x2 | 2x3

Увы, фона (голубенького или желтенького, как в примере) еще не видно. Фон задается параметром bgcolor="цвет_фона". Фон можно задать для таблицы в целом, для ряда, для столбца (в пределе одного ряда). В нашем случае мы задаем фон для каждого столбца.

<table>
<tr>
<td bgcolor="#FFCC33">1x1</td>
<td bgcolor="#336699">1x2</td>
<td bgcolor="#FFCC33">1x3</td>
</tr>
<tr>
<td bgcolor="#336699">2x1</td>
<td bgcolor="#FFCC33">2x2</td>
<td bgcolor="#336699">2x3</td>
</tr>
</table>

Вот, что у нас получилось:

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


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

Главная | Оглавление | Словарь

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

А получилось у нас еще только вот такая таблица:

В чем же дело? А в том, что мы не задали высоту и ширину ячейкам таблицы нашей:) Вспомним о параметрах height и width - вы можете их задать для всей таблицы, для одного ряда, для ячейки (столбца). Вспомним, что высота и ширина могут задаваться как в пикселях, так и процентах. В нашем случае мы зададим ширину и высоту в пикселях для столбцов (ячеек).

<table>
<tr>
<td height="35" width="50" bgcolor="#FFCC33"> 1x1 </td>
<td width="50" bgcolor="#336699"> 1x2 </td>
<td width="50" bgcolor="#FFCC33"> 1x3 </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699"> 2x1 </td>
<td width="50" bgcolor="#FFCC33"> 2x2 </td>
<td width="50" bgcolor="#336699"> 2x3 </td>
</tr>
</table>

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

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

Вернемся к нашему примеру: теперь мы почти получили то, что хотели:)

Теперь нам осталось лишь выровнять содержимое внутри таблицы:

<table>
<tr>
<td height="35" width="50" bgcolor="#FFCC33"> <center> 1x1 </center> </td>
<td width="50" bgcolor="#336699"> <center> 1x2 </center> </td>
<td width="50" bgcolor="#FFCC33"> <center>1x3 </center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699"> <center> 2x1 </center> </td>
<td width="50" bgcolor="#FFCC33"> <center> 2x2 </center> </td>
<td width="50" bgcolor="#336699"> <center> 2x3 </center> </td>
</tr>
</table>

И... готово!:)

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

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

Давайте шагнем на следующую ступеньку и продолжим разговор о таблицах.


Первые шаги - Ступенька девятнадцатая, как создавать таблицы, вертикальное вравнивание.

Главная | Оглавление | Словарь

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

Вернемся к нашему примеру, и поговорим о вертикальном выравнивании содержимого таблицы, т.е. о том как можно сделать так, чтобы содержимое ячейки не только располагалось ровно посередине ее (как по умолчанию), а еще вверху или внизу. Вертикальное выравнивание задается следующим аттрибутом - valign="middle" (top, bottom) - содержимое конкретной ячейки будет находится в середине ячейки (наверху или внизу):

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

<table>
<tr>
<td height="35" width="50" bgcolor="#FFCC33" valign="top"> <center>1x1</center> </td>
<td width="50" bgcolor="#336699"> <center>1x2</center> </td>
<td width="50" bgcolor="#FFCC33" valign="bottom"> <center>1x3</center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699" valign="bottom"> <center>2x1</center> </td>
<td width="50" bgcolor="#FFCC33"> <center>2x2</center> </td>
<td width="50" bgcolor="#336699" valign="top"> <center>2x3</center> </td>
</tr>
</table>

Я прописала valign не для всех ячеек, только для тех, где я захотела чтобы текст располагался сверху или снизу, чтобы зря не тратить время и усилия. Существование параметра valign значительно облегчает нашу с вами жизнь, страшно даже подумать, как пришлось бы мучиться, если бы его не было:).

А теперь перейдем к следующей ступеньке, чтобы узнать о других аттрибутах, облегчающих нашу трудную жизнь.


Первые шаги - Ступенька двадцатая, как создавать таблицы.

Главная | Оглавление | Словарь

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

В этом примере мы использовали параметр colspan=2, прописав его для ячейки 1х1. Код будет выглядеть следующим образом:

<table>
<tr>
<td height="35" bgcolor="#FFCC33" colspan="2"> <center>1x1</center> </td>
<td width="50" bgcolor="#336699"> <center>1x2</center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699"> <center>2x1</center> </td>
<td width="50" bgcolor="#FFCC33"> <center>2x2</center> </td>
<td width="50" bgcolor="#336699"> <center>2x3</center> </td>
</tr>
</table>

Прошу вас обратить внимание, на то, что параметр width для ячейки 1х1 в нашем примере не указан, если вас так тянет задавать этот параметр, то в нашем примере для ячейки 1х1 его надо было бы прописать равным 100 пикселям, т.к. все-таки ячейка 1х1 длинее других в два раза.

И второе на что прошу вас обратить внимание, в нашем примере нет ячейки 1х3, т.е. в первом ряду всего лишь две ячейки, т.к. ячейка 1х1 равна сама по себе двум ячейкам по длинне (что мы и указали параметром colspan). Если бы мы прописали ячейку 1х3, тогда у нас получилась бы такая ерунда:

Теперь, когда мы разбрались с параметром colspan, разберемся с параметром rowspan. Принцип действия тут тот же:

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

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


Первые шаги - Ступенька двадцатая, как создавать таблицы.

Первое задание:

<table>
<tr>
<td height="35" bgcolor="#FFCC33"> <center>1x1</center> </td>
<td width="50" bgcolor="#FFCC33"> <center>1x2</center> </td>
<td width="50" bgcolor="#336699" rowspan="2"> <center>1x3</center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699"> <center>2x1</center> </td>
<td width="50" bgcolor="#FFCC33"> <center>2x2</center> </td>
</tr>
</table>

Второе задание:

<table>
<tr>
<td height="35" bgcolor="#FFCC33" colspan="2"> <center>1x1</center> </td>
<td width="50" bgcolor="#336699" rowspan="2"> <center>1x2</center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699"> <center>2x1</center> </td>
<td width="50" bgcolor="#FFCC33"> <center>2x2</center> </td>
</tr>
</table>


Первые шаги - Ступенька двадцать первая, как создавать таблицы.

Главная | Оглавление | Словарь

Итак, мы остановились на такой таблице:

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

Такая красота достигается с помощью атрибута cellspacing, равного нулю:

<table cellspacing=0>
<tr>
<td height="35" bgcolor="#FFCC33" colspan="2"> <center>1x1</center> </td>
<td width="50" bgcolor="#336699" rowspan="2"> <center>1x2</center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699"> <center>2x1</center> </td>
<td width="50" bgcolor="#FFCC33"> <center>2x2</center> </td>
</tr>
</table>

Можно наооборот увеличить пространство между ячейками, допустим пусть cellspacing=5, тогда получим такое:

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

Обычно атрибут cellspacing, рассматривается в руководствах и учебниках в паре с атрибутом cellpadding, который добаляет свободное пространство между содержимым ячейки и ее границами. Чтобы было видно нагляднее я для начала прижму текст ячеек первого ряда к верху, в нижнего - к низу, используя атрибут valign:

Теперь зададим атрибут cellpadding=5:

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

Для тех, кто все еще не может составить код сам - вот он (для последнего варианта таблицы):

<table cellpadding=5>
<tr>
<td height="35" bgcolor="#FFCC33" colspan="2" valign="top"> <center>1x1</center> </td>
<td width="50" bgcolor="#336699" rowspan="2" valign="top"> <center>1x2</center> </td>
</tr>
<tr>
<td height="35" width="50" bgcolor="#336699" valign="bottom"> <center>2x1</center> </td>
<td width="50" bgcolor="#FFCC33" valign="bottom"> <center>2x2</center> </td>
</tr>
</table>

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


Первые шаги - Ступенька двацать вторая, как создавать таблицы.

Главная | Оглавление | Словарь

Когда мы учились делать таблицы, то я упоминала о вложенных таблицах. Что же это такое и в чем их особенность? - это обычные таблицы, которые располагаются в ячейках другой таблицы, и больше ничего особенного в них нет... почти.

Допустим у нас уже есть большая таблица, две колонки которой забиты текстом какого-то отчета, а третья между ними для красоты:

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

буки | 65% населения
бяки | 20% населения
др. | 15% населения

За сим все

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

<table>
<tr>
<td width="200" valign="top" background="blue.gif" align="center">С каждым днем в интернете появляется все больше бяк и бук. Это особенные существа, роль которых в развитии современного общества не понятна, но тем не менее само их присутсвие заметно. Буки и бяки требуют особого обращения к себе, если обращаться к ним как к нормальным человеческим особям, то вы поняты не будете.</td>
<td width="10" background="white.gif">&nbsp;</td>
<td width="200" valign="top" background="blue.gif" align="center">Вот таблица, которая показывает сколько бук, бяк и других обитает в интернете:
<br><br>

<table cellspacing="3">
<tr>
<td width="50" background="white.gif">буки</td><td background="white.gif">65% населения</td>
</tr>
<tr>
<td width="50" background="white.gif">бяки</td><td background="white.gif">20% населения</td>
</tr>
<tr>
<td width="50" background="white.gif">др.</td><td background="white.gif">15% населения</td>
</tr>
</table>

<br><br>
За сим все</td>
</tr>
</table>

Увы, код получился объемный, но зато в нем нет ничего сложного: вложенная таблица от обычной ничем не отличается.

Но все же здесь есть некоторые нюансы, на которые следует обратить внимание: background="картинка.gif" - раньше мы говорили о параметре bgcolor, который задает цвет фона для таблицы (ряда, ячейки), а параметр background задает фоновую картинку для таблицы (ряда, ячейки).

А теперь сам нюанс: вроде было бы логичнее прописать просто белый фон для вложенной таблицы (bgcolor="#ffffff"), а не мучаться, создавая просто белую картинку и делая ее фоном (background="white.gif"), ведь так оно быстрее. Да, быстрее, но дело в том, что если IE (Internet Explorer) отображает параметр bgcolor для вложенных таблиц, то NN (Netscape Navigator) этот параметр для вложенных таблиц отказывается отображать, поэтому приходиться идти обходным путем, используя background.

Вот так мы и убили двух зайцев сразу: узнали, что можно задать background для таблиц, да при этом ознакомились с некоторыми тонкостями ремесла :). Теперь будем добивать оставшихся заек:

<td width="10" background="white.gif">&nbsp;</td>

Давайте обратимся к коду нашей таблицы и вспомним, что эту ячейку мы ввели для красоты. Чтобы ячейка не пустовала, в нее введен &nbsp; (символ неразрывного пробела). Это не просто так. Дело в том, что есть любители использовать конструкцию типа: <td></td>. Вот такую конструкцию NN просто напросто игнорирует, т.к. не любит пустых ячеек, поэтому для корректного отображения вашей таблицы везде вставляйте в пустые ячейки или &nbsp;, или мелкую картинку 1х1 пикселов.

И последняя зайка: align="center" (right, left). Помните, мы уже говорили о параметре align, так вот, как видите, его можно задать для содержимого ячейки (см. наш пример), только вот удобного align="justify" тут нет.

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


Первые шаги - Ступенька двацать вторая, как создавать таблицы.

Главная | Оглавление | Словарь

В этой последней главе, посвященной таблицам, мы поговорим о рамках. Рамка вводится параметром border. Зададим рамку равную 3 пикселям:

<table border="3">

Остальную таблицу допишете сами. Выглядеть это будет вот так:

таблица с рамкой

Нашей рамке мы можем задать цвет. Пусть он будет черный в нашем примере:

<table border="3" bordercolor="#000000">

таблица с рамкой

Вот и вся премудрость. В руководствах и справочниках вы можете встретить параметры bordercolorlight и bordercolordark - эти параметры понимает только ИЕ (Internet Explorer), поэтому не рекомендую их употреблять. Вобще-то не так трудно создать таблицу, гораздо труднее, чтобы это выглядело под разными броузерами одинаково. Возьмем те же рамки:

Различные таблицы в IE | Различные таблицы в NN

Полагаю это достаточно наглядный пример (есть над чем задуматься).

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

Ну, уж если вы можете махнуть рукой на тех, кто не желает использовать ИЕ, то вот на то, что у разных пользователей разное разрешение экрана вы не должны забивать. Для тех кто не знал: два самых используемых разрешения - 800x600 и 1024x768.

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

Собственно это все о таблицах. Напоследок пара полезных ссылочек:

- Опыты с табличными рамками
- Специфические таблицы


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

Главная | Оглавление | Словарь

Вот мы добрались и до фреймов (frames). Что же это такое и чем они замечательны? Фреймы позволяют нам открыть в окне броузера - не один, а сразу несколько документов (допустим, документ menu.html, который содержит меню, logo.html - документ, который содержит логотип, шапку страницы, и content.html - документ с непосредственным содержанием нашего сайта).

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

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

Т.к. первый документ на сайте, который показывается посетителю это index.html (или вроде него), то мы его и будем мучать, пусть он будет фрейм-документом. Создадим документ index.html:

<html>
<head>
<title>Хождение по фреймам</title>
</head>
</html>

На первый взгляд - вполне стандартное начало, только вот... опа! нет тэга body! И это не ошибка, фрейм-документ не содержит тэга body, такого обязательного в других случаях.

Что ж, не будем грустить о тэге body, и найдем ему вполне достойного заместителя:

<html>
<head>
<title>Хождение по фреймам</title>
<frameset></frameset>
</head>
</html>

Здравствуйте Frameset, какое красивое имя, говорят, что вы решили изменить кое-что в жизни нашего сайта и ввести фреймы :), очень приятно, мы всегда рады новым и практичным решениям.

Теперь надо найти frameset достойных помощников и задать ему некоторые жизнено необходимые параметры, но это на следующей ступеньке.


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

Главная | Оглавление | Словарь

Прежде, чем что-нибудь предпринимать дальше, надо решить по какому принципу мы будем размещать наши документы, и вообще, какие документы мы будем показывать посетителю одновременно. Предлагаю классический вариант - logo.html, menu.html, content.html - о котором я упоминала в предыдущей главе.

Расположить мы можем это по разному, вот для примера четыре варианта из множества возможных:

Итак, сначала создадим такой вариант:

<html>
<head>
<title>Хождение по фреймам</title>
<frameset rows="100,*,150">
<frame src="logo.html">
<frame src="content.html">
<frame src="menu.html">
</frameset>
</head>
</html> (прим.)

Теперь мне следует объясниться...

Начнем с параметра rows - в нашем примере это выглядит так: rows="100,150,*" - а если перевести, то мы получим следующее - "... наш документ делиться на несколько рядов (строк). Высота первого ряда - 100 пикселов, третьего - 150, а второй занимает все оставшееся пространство", - полагаю, здесь должно быть все ясно.

Тэг frame сообщает броузеру какие же документы у нас будут в каждом ряду (строке). В нашем случае: первый ряд - logo.html (документ с логотипом), второй ряд - займет документ с непосредственным содержанием (content.html), а третий - меню. Если вы хотите, чтобы меню было во втором ряду, то вам следует поменять его местами с content.html

<html>
<head>
<title>Хождение по фреймам</title>
<frameset rows="100,*,150">
<frame src="logo.html">
<frame src="menu.html">
<frame src="content.html">
</frameset>
</head>
</html> (прим.)

Посмотрите что у нас получилось. Опаньки... Поменять-то мы их местами - поменяли, а вот теперь надо задать новые значения параметру rows, чтобы меню у нас снова занимало только 150 пикселов по высоте, а содержание - все остальное:

<html>
<head>
<title>Хождение по фреймам</title>
<frameset rows="100,150,*">
<frame src="logo.html">
<frame src="menu.html">
<frame src="content.html">
</frameset>
</head>
</html> (прим.)

Вот теперь другое дело:) Все-таки от перемены мест слагаемых кое-что меняется...


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

Главная | Оглавление | Словарь

Эту главу мы начнем с небольшого фокуса: замените параметр rows, на cols -

<html>
<head>
<title>Хождение по фреймам</title>
<frameset cols="100,150,*">
<frame src="logo.html">
<frame src="menu.html">
<frame src="content.html">
</frameset>
</head>
</html> (прим.)

Ну-ка, посмотрите наш пример. Ловкость рук и никакого мошенства :)

Параметр cols делит окно нашего броузера не на ряды, а на колонки. Давайте переведем это - cols="100,150,*": "...наш документ теперь делится на колонки. Первая колонка имеет ширину - 100 пикселов, вторая - 150, а третья занимает все оставшееся место".

Кстати, мы получили еще один из четырех вариантов, которые собирались сделать:

Итак, подведем итоги. Мы можем делить окно нашего броузера либо на ряды, либо на колонки, с помошью параметров тэга <frameset> Cols и Rows. Другого способа деления не существует. Одновременно эти параметры использовать нельзя.

При помощи rows мы разбиваем окно на ряды и задаем какую высоту будет иметь каждый ряд, при помощи cols разбиваем окно на колонки и задаем какую ширину будет иметь каждая колонка.

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

<frameset cols="10%,15%,75%">

Помните, что в сумме это все должно равняться 100%. А то бывают же умники...


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

Главная | Оглавление | Словарь

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

или

В предыдущей главе я сказала, что мы можем делить окно только на ряды или колонки. Тогда как же расположить наши документы в окне так, как указано на рисунках? А разве я говорила, что ряд в свою очередь нельзя разбить на несколько колонок, а колонку на несколько рядов? Нет, не говорила :Ъ.

Начнем с первого рисунка:

Каким образом мы будем делить окно? - На ряды. В первом ряду у нас будет располагаться logo.html, а второй ряд мы поделим на две колонки, в которых будут располагаться документы menu.html и content.html.

<html>
<head>
<title>Хождение по фреймам</title>
<frameset rows="100,*">
<frame src="logo.html">
<???>
</frameset>
</head>
</html>

Принцип построения ясен, только вот как обозначить ряд, разбитый на две колонки? Тут нам поможет господин Frameset.

<html>
<head>
<title>Хождение по фреймам</title>
<frameset rows="100,*">

<frame src="logo.html">
<frameset cols="150,*">
<frame src="menu.html">
<frame src="content.html">
</frameset>

</frameset>
</head>
</html> (прим.)

Для тех, кто не уследил за движениями моих рук, поясняю. Первый ряд мы оформили как положено, при помощи тэга frame. Во втором ряду на сцену выгодит тэг <frameset></frameset>.

С помощью параметра cols тэга <frameset></frameset> мы делим второй ряд на две колонки (первая шириной 150 пикселов, вторая по ширине занимает все оставшееся пространство). А тэги <frame>, которые содержит <frameset></frameset> определяют какие документы будут показаны в колонках (menu.html и content.html).

Теперь разберем вариант, изображенный на втором рисунке:

Здесь мы будем делить окно на колонки. Вторая колонка будет содержать в себе документ content.html (содержание), а первую колонку мы разобьем на два ряда, и поместим в них документы logo.html и menu.html.

<html>
<head>
<title>Хождение по фреймам</title>
<frameset cols="100,*">

<frameset rows="100,*">
<frame src="logo.html">
<frame src="menu.html">
</frameset>
<frame src="content.html">

</frameset>
</head>
</html> (прим.)

Вот так. Ничего сложного здесь нет:)


Первые шаги - Ступенька двадцать восьмая, учимся создавать фреймы, параметры тэга frame.

Главная | Оглавление | Словарь

Допустим, мы решили остановиться на последнем варианте, значит будем приводит это в более-менее нормальный вид. Для начала избавимся от полосы прокрутки (скрулинга) во фрейме с logo.html.

<html>
<head>
<title>Хождение по фреймам</title>
<frameset cols="100,*">

<frameset rows="100,*">
<frame src="logo.html" scrolling="no">
<frame src="menu.html">
</frameset>
<frame src="content.html">

</frameset>
</head>
</html> (прим.)

Scrolling - параметр тэга <frame>. Он может принимать несколько значений: no - это значит совсем не будет полосы прокрутки, ни при каких обстоятельствах; yes - это значит полоса прокрутки будет всегда; auto - полоса прокрутки появиться только тогда, когда она нужна.

Собственно, параметр scrolling="auto", можно не прописывать, т.к. если параметр scrolling не задан, то полоса прокрутки появиться, если она нужна, а если нет - ее не будет.

Теперь давайте избавимся от рамок между фреймами. Для этого мы используем параметр border, с которым мы раньше уже встречались. Итак, border="0".

<html>
<head>
<title>Хождение по фреймам</title>
<frameset cols="100,*" border="0">

<frameset rows="100,*">
<frame src="logo.html" scrolling="no">
<frame src="menu.html">
</frameset>
<frame src="content.html">

</frameset>
</head>
</html> (прим.)

Так уже лучше :). Но все же, что-то еще хочеться исправить... А! Картинку с чёртом так и хочется прижать в левый верхний угол:

<html>
<head>
<title>Хождение по фреймам</title>
<frameset cols="100,*" border="0">

<frameset rows="100,*">
<frame src="logo.html" scrolling="no" marginwidth="0" marginheight="0">
<frame src="menu.html">
</frameset>
<frame src="content.html">

</frameset>
</head>
</html> (прим.)

Прижали. Теперь давайте познакомимся с параметрами тэга <frame> marginheight и marginwidth.

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

Вот такие пироги:) Остальное будет на других ступеньках.


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

Главная | Оглавление | Словарь

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

Итак, обратимся к нашему примеру. Нажмите на любую из ссылок. Документ, на который введет ссылка, откроется в том же фрейме. А нам бы надо сделать так, чтобы он открылся во фрейме с основным содержанием, а меню осталось в нетронутом виде. Как это сделать?

Для начала, господа, познакомьтесь с новым параметром тэга <frame> - name. Пустим name в дело:

<html>
<head>
<title>Хождение по фреймам</title>
<frameset cols="100,*" border="0">

<frameset rows="100,*">
<frame src="logo.html" scrolling="no" marginwidth="0" marginheight="0">
<frame src="menu.html">
</frameset>
<frame src="content.html" name="window-1">

</frameset>
</head>
</html>

Параметр name задает имя для фрейма (в нашем случае для того, который содержит документ content.html).

Имя фрейма может быть в дальнейшем использовано для ссылки на него из других документов (фреймов), с помощью параметра тэга <a> target (target="имя_фрейма").

Как это выглядит? Обратимся к документу menu.html.

<html>
<head>
<title>Документ с Меню</title>
<body background="cherti3.gif" text="#ffffff" link="#ffffff" alink="#ffffff" vlink="#ffffff">
<center>
<a href="content.html">Главная</a>
<a href="tumki.html">Тумки</a>
<a href="bumki.html">Бумки</a>
<a href="tururumki.html">Турурумки</a>
<a href="tra-la-la.html">Траляля</a>
</center>
</body>
</head>
</html>

Вот такой он, в нашем случае. Теперь для каждой ссылки укажем параметр target="window-1", где window-1 - это имя фрейма, в котором у нас располагается документ с основным содержанием (content.html).

<html>
<head>
<title>Документ с Меню</title>
<body background="cherti3.gif" text="#ffffff" link="#ffffff" alink="#ffffff" vlink="#ffffff">
<center>
<a href="content.html" target="window-1">Главная</a>
<a href="tumki.html" target="window-1">Тумки</a>
<a href="bumki.html" target="window-1">Бумки</a>
<a href="tururumki.html" target="window-1">Турурумки</a>
<a href="tra-la-la.html" target="window-1">Траляля</a>
</center>
</body>
</head>
</html> (прим.)

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

Бывают ситуации, когда нам нужно, чтобы открываемый документ открылся во все окно, для этого надо параметру target задать значение _top:

<a href="project.html" target="_top">Мой проект о рыбках</a>

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


Первые шаги - Ступенька тридцатая, фреймы, плавающий фрейм, встроенный, iframe, norezise, noframes.

Главная | Оглавление | Словарь

Вот мы добрались и до последней главы о фреймах. Здесь мы поговорим об IFrame - "плавающем" фрейме и вы получите несколько полезных ссылок напоследок.

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

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

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

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

Теперь обещанные ссылки:

- Страшный всемирный заговор - почему фреймы не так страшны
- Я люблю фреймы
- Фреймы как альтернативное средство навигации
- Встроенные фреймы : элемент IFRAME

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


Первые шаги - Ступенька тридцать первая, Какие же еще теги определяют стиль шрифта, используются для его форматирования? Это - Big, Small, Strike (или S), Sub и Sup.

Главная | Оглавление | Словарь

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

Какие же еще теги определяют стиль шрифта, используются для его форматирования? Это - Big, Small, Strike (или S), Sub и Sup.

1) Big представляется крупным шрифтом, а Small малым шрифтом относительно основного текста:

<small> Малый </small>
Нормальный текст
<big> Большой </big>

2) Тэги Strike и S представляют текст перечеркнутым шрифтом:

<strike> Перечеркнутый </strike>
<s> Перечеркнутый </s>

Какая разница между S и Strike? Да никакой. Просто один из них появился в более ранней спецификации, а другой в более поздней, но оба они имеют право на жизнь.

3) Тэги Sup и Sub - определяют верхний и нижний индексы. Sup - верхний, Sub - нижний. Где они могут пригодится? Ну, например при написании какой-нибудь формулы - H2O (все, что мы выносим со школьной скамьи:).

Верхний индекс <sup>sup</sup>
Нижний индекс <sub>sub</sub>


Первые шаги - Ступенька тридцать вторая. Завершение обтекания текстом объекта (картинки). Параметр тэга BR - clear. Как прервать, завершить обътекание текста картинкой, чтобы он начинался под ней.

Главная | Оглавление | Словарь

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

В этой главе мы поговорим о параметре тэга Br - Clear. Зачем он нужен? Допустим текст обтекает какой-либо объект (например, картинку):

А мои дети и внуки будут пользоваться моей славой. Смогут купить сосиски, не стоя в огромной очереди. Отварят их, да побольше, наложат в тарелку горкой. Принесут к моим гранитным ногам. "Кушай, великий предок!" Я посмотрю на них с неба, ласково так, с любовью: "Кушайте сами сосиски. И живите в мире и счастье."

Нам нужно, чтобы текст после слов "к моим гранитным ногам" обрывался, и "кушай, великий предок" - уже продолжалось под картинкой. Вы, конечно, можете ввести несколько тэгов br и решить эту проблему. Но если вдруг картинка (объект) будет большой, текста много, то тут вы уже явно не обойдетесь несколькими br, тут вам придется употребить их с десяток и больше. Так вот, чтобы вам не пришлось мучатся в таких ситуациях, и существует параметр clear:

А мои дети и внуки будут пользоваться моей славой. Смогут купить сосиски, не стоя в огромной очереди. Отварят их, да побольше, наложат в тарелку горкой. Принесут к моим гранитным ногам.
"Кушай, великий предок!" Я посмотрю на них с неба, ласково так, с любовью: "Кушайте сами сосиски. И живите в мире и счастье."

Теперь пример кода:

<img src="clearpri.gif" align="left">
А мои дети и внуки будут пользоваться моей славой. Смогут купить сосиски, не стоя в огромной очереди. Отварят их, да побольше, наложат в тарелку горкой. Принесут к моим гранитным ногам.
<br clear="left">
"Кушай, великий предок!" Я посмотрю на них с неба, ласково так, с любовью: "Кушайте сами сосиски. И живите в мире и счастье."

У параметра clear существует несколько значений - all, left, right.

All - завершить обтекание объекта текстом. Left - завершить обтекание текстом объекта, выровненного по левому краю. Right - завершить обтекание текстом объекта, выровненного по правому краю.

В нашем примере мы задали clear="left", т.к. наша картинка выровнена по левому краю.

Вот и вся премудрость. Текст примера - это отрывок из стихотворения "Первый!" одного моего знакомого, который в виртуальной реальности величает себя Мирзавчиком, не имеет никакого отношения к нашему обучению, и упомянут лишь из уважения к авторскому праву.


Первые шаги - Ступенька тридцать третья. Навигационные карты. Map. Как сделать часть картинки ссылкой, как сделать так, чтобы при нажатии в одно место на картинке мы попадали на одну страницу, а при нажатии на другую область картинки - на другую страницу.

Главная | Оглавление | Словарь

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

Сначала теория.

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

<map>
<area ...>
<area ...>
...
<area ...>
</map>

Дальше уже будем тренироваться на кошках :) (на конкретных примерах)

Для начала нам нужна картинка. Возьмем такую:

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

Итак, мы уже знаем, что геометрические области и то, куда пользователь попадет при нажатии на них, определяет тэг Area, естественно, при помощи своих параметров. Это параметры shape и coords.

Параметр shape - определяет форму области (будет ли она прямоугольником (shape="rect"), кругом (shape="circle") или многоугольником (shape="poly")). Сейчас мы работаем с прямоугольной областью, поэтому:

<map>
<area shape="rect">
</map>

Параметр coords - определяет координаты (положение нашей геометрической формы). Число координат и порядок их значений зависят от выбранной нами формы.

"Стоп! стоп!" - скажете вы, - "Какие координаты?" - Те самые, вспоминайте школу и уроки геометрии :)

Полагаю, что это всем должно быть ясно. Отсчет ведется от левого верхнего угла картинки, считайте его началом координат (0;0). Если мы работаем с прямоугольной областью, то нам нужны координаты верхнего-левого и нижнего-правого углов области. Порядок записи координат для параметра coords следующий:

<area shape="rect" coords="x1,y1,x2,y2">

В нашем примере у прямоугольника №1 координаты такие:

x1=25, y1=36, x2=114, y2=98

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

<map>
<area shape="rect" coords="25,36,114,98">
</map>

Теперь пропишем, куда будет ссылаться наша область, для этого нам понадобится уже знакомый нам параметр href:

<map>
<area href="drugoy_document.html" shape="rect" coords="25,36,114,98">
</map>

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

У тэга Map есть параметр name - имя карты, давайте назовем карту - karta1:

<map name="karta1">
<area href="drugoy_document.html" shape="rect" coords="25,36,114,98">
</map>

Для того, чтобы связать карту с картинкой, надо использовать атрибут usemap="#имя_карты" для картинки:

<img src="bluerects.gif" usemap="#karta1">

... Куча текста и всякого содержания, или ничего...

<map name="karta1">
<area href="drugoy_document.html" shape="rect" coords="25,36,114,98">
</map>

Смотрим результат (жмите на прямоугольник №1):

Теперь сами пропишите Area для прямоугольника №2 для тренировки (x1=153, y1=11, x2=219, y2=127), а затем шагайте на следующую ступеньку, где мы продолжим разговор о картах.


Первые шаги - Ступенька тридцать четвертая. Навигационные карты. Map. Как сделать часть картинки ссылкой, как сделать так, чтобы при нажатии в одно место на картинке мы попадали на одну страницу, а при нажатии на другую область картинки - на другую страницу.

Главная | Оглавление | Словарь

Для создания круглой области нам нужны будут координаты ее центра (x и y) и длинна радиуса (R) в пикселях. Т.е. порядок записи следующий:

<area shape="circle" coords="x,y,R">

Давайте будет опять работать с геометрической областью №1. В нашем случае координаты для маленького круга будут такие: x=46, y=48; а длинна радиуса - R=35. Запишем:

<map>
<area shape="circle" coords="46,48,35">
</map>

Теперь, когда самое главное записано, пропишем имя карты, куда она ссылается и привяжем карту к рисунку:

<img src="mapcircle.gif" usemap="#karta2">

... Куча текста и всякого содержания, или ничего...

<map name="karta2">
<area href="drugoy_document_2.html" shape="circle" coords="46,48,35">
</map>

Кстати, для карты мы можем прописать ALT для каждой области, сейчас у нас только одна область, но все равно, пропишем для нее ALT:

<img src="mapcircle.gif" usemap="#karta2">

... Куча текста и всякого содержания, или ничего...

<map name="karta2">
<area href="drugoy_document_2.html" shape="circle" coords="46,48,35" alt="маленький круг">
</map>

Теперь при наведении на область №1 будет всплывать подсказка, что же это такое :)

Давайте посмотрим результат (нажмите на первый круг):

маленький круг

Теперь вы самостоятельно сделаете ссылкой второй круг (x=158, y=75, R=53), чтобы закрепить пройденное.


Первые шаги - Ступенька тридцать пятая. Навигационные карты. Map. Как сделать часть картинки ссылкой, как сделать так, чтобы при нажатии в одно место на картинке мы попадали на одну страницу, а при нажатии на другую область картинки - на другую страницу.

Главная | Оглавление | Словарь

Вот мы, наконец, добрались и до многоугольников.

Бу-бу-бу! :) Страшно?

На самом деле тут нет ничего страшного. Вы указываете точки (координаты углов), они как бы соединяются, и мы можем получить очень разнообразные фигуры (посмотрите на фигуру 2, правда здорово?). Используя poly, мы можем делать самые разнообразные области, от скромного треугольника до шикарной звезды.

Сейчас мы будем работать с фигурой №2, потому что у нее меньше углов (да, ленивая я, решила облегчить себе жизнь на один угол:).

Для начала зададим тип области:

<map>
<area shape="poly">
</map>

Координаты пишутся по следующему принципу:

<area shape="poly" coords="x1,y1,x2,y2,...,xN,yN">

Расшифровывается это так: "координаты первого угла (x1,y1), координаты второго угла (x2,y2), еще много углов и их координат (...), координаты последнего угла (xN,yN)". Т.е. для нашего пятиугольника запись полностью будет выглядеть так:

<area shape="poly" coords="x1,y1,x2,y2,x3,y3,x4,y4,x5,y5">

Теперь подставим реальные значения координат в наш код:

<map>
<area shape="poly" coords="168,9,232,29,200,97,223,129,153,119">
</map>

Дальше уже прописываем ссылку, имя карты, и привязываем карту к рисунку (это везде по одному и тому же принципу):

<img src="mappoly.gif" usemap="#karta3">

... Куча текста и всякого содержания, или ничего...

<map name="karta3">
<area href="drugoy_document_3.html" shape="poly" coords="168,9,232,29,200,97,223,129,153,119">
</map>

Теперь проверим, получилось ли у нас это безобразие (в этот раз вторая фигура, жмите):

Не забудьте прописать самостоятельно первую область (шестиугольник) - x1=54, y1=20, x2=109, y2=20, x3=147, y3=58, x4=109, y4=96, x5=54, y5=96, x6=16, y6=58.

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

1 - Мы можем одновременно использовать разные области, например круг и многоугольник:

<img src="mappoly.gif" usemap="#karta3">

... Куча текста и всякого содержания, или ничего...

<map name="karta3">
<area href="drugoy_document_3.html" shape="circle" coords="46,48,35,">
<area href="drugoy_document_3.html" shape="poly" coords="168,9,232,29,200,97,223,129,153,119">
</map>

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

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

Надеюсь, с картами вам теперь все ясно, и вы можете продолжать обучение :)


Первые шаги - Ступенька тридцать шестая, ссылка внутри того же документа, внутрь, якоря, anchor.

Главная | Оглавление | Словарь

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

Способ первый - с помощью атрибута name (имя закладки) тега A:

<H2>Григорий Остер, "Вредные советы.<BR>
Книга для непослушных детей и их родителей".</H2>
<A href="#stih1">Ссылка на стих первый</A><BR>
<A href="#stih2">Ссылка на стих второй</A><BR>
<A href="#stih3">Ссылка на стих третий</A><BR><BR>
<PRE>
Недавно ученые открыли, что на свете бывают непослушные дети, которые все делают наоборот. Им дают полезный совет: "Умывайтесь по утрам" - они берут и не умываются. Им говорят: "Здоровайтесь друг с другом" - они тут же начинают не здороваться. Ученые придумали, что таким детям нужно давать не полезные, а вредные советы. Они все сделают наоборот, и получится как раз правильно.
</PRE>
<H3><A name="stih1">Стих первый</A></H3>
<PRE>
... тра-ля-ля 1...
</PRE>
<H3><A name="stih2">Стих второй</A></H3>
<PRE>
... тра-ля-ля 2...
</PRE>
<H3><A name="stih3">Стих третий</A></H3>
<PRE>
... тра-ля-ля 3...
</PRE> (прим.)

Заголовки (стих первый, стих второй, стих третий) в нашем примере мы сделали закладками, использовав параметр тега A - name:

<A name="stih3">Стих третий</A>

Затем, без стеснения, мы сделали на них ссылки:

<A href="#stih3">Ссылка на стих третий</A>

Заметьте - <A href="#stih3"> - символ решетки (#) перед именем закладки, на которую мы ссылаемся, обязателен. Вы можете ссылаться на закладку и из других документов, тогда ссылка будет выглядеть следующим образом:

<A href="ancorpri.html#stih3"> Ссылка на стих третий из какого-то другого документа </A>

или

<A href="http://www.mysite.ru/ancorpri.html#stih1"> Ссылка на стих третий из какого-то другого документа </A>

Способ второй - есть такой атрибут (параметр) - id, который назначает тегу (элементу) уникальное имя в пределах одного документа (кроме тегов Base, Head, Html, Meta, Script, Style, Title). Что мешает нам задать id, например, тегу H3 (заголовкам)?

<H2>Григорий Остер, "Вредные советы.<BR>
Книга для непослушных детей и их родителей".</H2>
<A href="#stih1">Ссылка на стих первый</A><BR>
<A href="#stih2">Ссылка на стих второй</A><BR>
<A href="#stih3">Ссылка на стих третий</A><BR><BR>
<PRE>
Недавно ученые открыли, что на свете бывают непослушные дети, которые все делают наоборот. Им дают полезный совет: "Умывайтесь по утрам" - они берут и не умываются. Им говорят: "Здоровайтесь друг с другом" - они тут же начинают не здороваться. Ученые придумали, что таким детям нужно давать не полезные, а вредные советы. Они все сделают наоборот, и получится как раз правильно.
</PRE>
<H3 id="stih1">Стих первый</H3>
<PRE>
... тра-ля-ля 1...
</PRE>
<H3 id="stih2">Стих второй</H3>
<PRE>
... тра-ля-ля 2...
</PRE>
<H3 id="stih3">Стих третий</H3>
<PRE>
... тра-ля-ля 3...
</PRE> (прим.)

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

Также хочу вас преостеречь, никогда не делайте так:

<A href="#stih">Ссылка на ...</A><BR>
...
<A name="STIH">Закладка</A>

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

На этом мучения с закладками закончены, а напоследок ловите две ссылочки:

- Не взлетим, так поплаваем. Уберите якоря со своих сайтов
- Анкор, еще анкор


Первые шаги - Словарь, тэги.

Главная | Оглавление | Словарь

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

  • <a href="другой_документ.html"></a> - ссылка - о тэге.
    • target=_blank - открывает ссылку в новом окне.

    • name="anchor1" - имя закладки.

  • <area> - оперделяет геометрические области внутри карты и ссылки, связанныес каждой областью - о тэге.
    • shape="rect" (circle, poly) - тип области (прямоугольник, круг, многоугольник)

    • coords="x1,y1,..." - координаты, количество зависит от типа области

    • alt="описание" - описание, подсказка

    • href="document.html" - ссылка

  • <b></b> - жирный текст - о тэге.

  • <big></big> - крупный текст - о тэге.

  • <body></body> - обязательный тэг - о тэге.

    • <body text="#cc0000"> - задает цвет текста всего документа

    • <body bgcolor="#000000"> - задает цвет фона документа

    • <body background="картинка.jpg"> - задает фоновую картинку

    • <body link="#ff9999"> - задает цвет ссылки

    • <body alink="#ff9999"> - задает цвет активной (нажатой) ссылки

    • <body vlink="#ff9999"> - задает цвет посещенной ссылки

  • <br> - перенос строки (принудительный) - о тэге.
    • clear="all" (left, right) - завершение обтекания текстом объекта (картинки).

  • <center></center> - центрирование текста - о тэге.

  • <font></font> - сам по себе погоды не делает, всегда употребляется с одним из параметров, приведенных ниже - о тэге.

    • <font color="#cc0000">текст</font> - задает цвет текста выбранной части документа.

    • <font size="+0">текст</font> - задает размер шрифта текста (от -2 до +4)

    • <font face="Arial">текст</font> - задает определенный шрифт в документе (можно прописать какой-нибудь экзотический шрифт)

  • <frame src="menu.html"> - определяет фрэйм и его свойства внутри frameset - главы 24 - 30.

    • marginheight="0" - определяет ширину верхнего и нижнего полей фрэйма.

    • marginwidth="0" - определяет ширину левого и правого полей фрэйма.

    • scrolling="yes" (no, auto) - линейка прокрутки будет всегда (никогда, если надо).

    • name="window-1" - имя фрейма, используется для ссылки на него из других документов (фреймов), с помощью параметра тэга <a> target (target="имя_фрейма").

  • <frameset></frameset> - определяет фрэймовую структуру документа. Открывает и закрывает список фрэймов - главы 24 - 30.

    • rows="100,200,*" - определяет количество и размеры горизонтальных фрэймов (рядов). Размеры задаются в процентах или пикселях.

    • cols="10%,20%,70%" - определяет количество и размеры вертикальных фрэймов (колонок).Размеры задаются в процентах или пикселах.

    • border - определяет ширину рамок фрэймов в пикселах.

  • <Hx></Hx> - заголовок- о тэге.

  • <head></head> - обязательный тэг - о тэге.

  • <hr> - линия - о тэге.
    • <Hr align="right"> (center или left>

    • <Hr width="30%"> (ширина линии в процентах)

    • <Hr size="6"> (толщина линии)

    • <Hr NoShade> (отмена объемности)

    • <Hr color="cc0000"> (цвет линии, только в IE)

  • <html></html> - обязательный тэг - о тэге.

  • <i></i> - выделяет текст курсивом - о тэге.

  • <img src="ваша_картинка.jpg"> - рисунок - о тэге

    • Border="5" - рамка покруг картинки

    • width="500" - ширина картинки

    • height="100" - высота картинки

    • align="left" - расположение текста по отношению к картинке (right, top, middle, left, bottom)

    • vspace="10" - расстояние от картинки до текста по вертикали

    • hspace="30" - расстояние от картинки до текста по горизонтали

    • alt="описание" - описание картинки

    • usemap="#karta1" - ссылка на карту

  • <map></map> - навигационные карты - о тэге

    • name="karta1" - имя карты

  • <li></li> - cписки - о тэге

    • type="disk" - полный кружок (по умочанию)

    • type="circle" - полый кружок

    • type="square" - квадратик

  • <p></p> - параграф - о тэге.

    • <p align="center">текст</p> - центрирование текста.

    • <p align="right">текст</p> - выравнивание текста по правому краю документа.

    • <p align="left">текст</p> - выравнивание текста по левому краю документа.

    • <p align="justify">текст</p> - выравнивание текста по обоим краям документа.
  • <pre></pre> - форматированый текст - о тэге.

  • <s></s> - перечеркнутый текст - о тэге.

  • <small></small> - малый текст - о тэге.

  • <stike></strike> - перечеркнутый текст - о тэге.

  • <sub></sub> - нижний индекс - о тэге.

  • <sup></sup> - верхний индекс - о тэге.

  • <title></title> - название документа - о тэге.

  • <table></table> - таблицы - о тэге, главы 17-23.

    • <tr></tr> - строчка (ряд) таблицы.

    • <td></td> - столбец (ячейка) таблицы.

    • bgcolor="#FFCC33" - цвет фона таблицы (строки, ячейки).

    • background="картинка.gif" - задает фоновый рисунок для таблицы (строки, ячейки).

    • width="50" или width="50%"- ширина таблицы (строки, ячейки) в пикселях или процентах.

    • height="45" или height="45%"- высота таблицы (строки, ячейки) в пикселях или процентах.

    • align="center" (right, left) - выравнивает содержимое ячейки относительно ее центра (правого или левого краев).

    • valign="middle" (top, bottom) - вертикальное выравнивание содержимого строки (ячейки) по середине (наверху или внизу).

    • colspan="2" - растянуть ячейку на несколько стобцов.

    • rowspan="2" - растянуть ячейку на несколько рядов.

    • cellspacing="5" - задает пространство между ячейками.

    • cellpadding="5" - задает пространство внутри ячейки между ее содержимым и границами.

    • border="3" - задает толщину рамки таблицы.

    • bordercolor="#000000" - задает цвет рамки таблицы.

  • <tt></tt> - фиксированный (моноширинный) текст - о тэге

  • <u></u> - подчеркнутый текст - о тэге.

  • <ul></ul> - отступ (табуляция) - о тэге.


Первые шаги - Ступенька тридцать шестая.

Григорий Остер, "Вредные советы.
Книга для непослушных детей и их родителей".

Ссылка на стих первый
Ссылка на стих второй
Ссылка на стих третий

Недавно  ученые открыли, что  на свете бывают непослушные дети, которые
все  делают наоборот.  Им дают полезный совет: "Умывайтесь  по утрам" -  они
берут и не умываются. Им говорят: "Здоровайтесь друг с другом" - они тут  же
начинают не здороваться. Ученые придумали, что таким детям нужно  давать  не
полезные,  а вредные советы. Они все  сделают наоборот,  и получится как раз
правильно.

Стих первый

     Рукам никогда нигде
     Не трогай ничего.
     Не впутывайся ни во что
     И никуда не лезь.
     В сторонку молча отойди,
     Стань скромно в уголке
     И тихо стой, не шевелясь,
     До старости своей.

     Нет приятнее занятья,
     Чем в носу поковырять.
     Всем ужасно интересно,
     Что там спрятано внутри.
     А кому смотреть противно,
     Тот пускай и не глядит.
     Мы же в нос к нему не лезем,
     Пусть и он не пристает.
     Если вас поймала мама

     За любимым делом вашим,
     Например, за рисованьем
     В коридоре на обоях,
     Объясните ей, что это -
     Ваш сюрприз к Восьмому марта.
     Называется картина:
     "Милой мамочки портрет".

     Если ты весь мир насилья
     Собираешься разрушить,
     И при этом стать мечтаешь
     Всем, не будучи ничем,
     Смело двигайся за нами
     По проложенной дороге,
     Мы тебе дорогу эту
     Можем даже уступить.

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

Стих второй

     Если вы по коридору
     Мчитесь на велосипеде,
     А навстречу вам из ванной
     Вышел папа погулять,
     Не сворачивайте в кухню,
     В кухне - твердый холодильник.
     Тормозите лучше в папу.
     Папа мягкий. Он простит.

     Если вас навек сплотили,
     Озарили и ведут,
     Не пытайтесь уклониться
     От движенья к торжеству.
     Все равно на труд поднимет
     И на подвиг вдохновит
     Вас великий и могучий,
     И надежный наш оплот.

     Главным делом жизни вашей
     Может стать любой пустяк.
     Надо только твердо верить,
     Что важнее дела нет.
     И тогда не помешает
     Вам ни холод, ни жара,
     Задыхаясь от восторга,
     Заниматься чепухой.

     Бейте палками лягушек.
     Это очень интересно.
     Отрывайте крылья мухам,
     Пусть побегают пешком.
     Тренируйтесь ежедневно,
     И наступит день счастливый -
     Вас в какое-нибудь царство
     Примут главным палачом.

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

Стих третий

     Если вы собрались другу
     Рассказать свою беду,
     Брать за пуговицу друга
     Бесполезно - убежит,
     И на память вам оставит
     Эту пуговицу друг.
     Лучше дать ему подножку,
     На пол бросить, сверху сесть
     И тогда уже подробно
     Рассказать свою беду.

     Если ты пришел к знакомым,
     Не здоровайся ни с кем.
     Слов: "пожалуйста", "спасибо"
     Никому не говори.
     Отвернись и на вопросы
     Ни на чьи не отвечай.
     И тогда никто не скажет
     Про тебя, что ты болтун.

     Если что-нибудь случилось,
     И никто не виноват,
     Не ходи туда, иначе
     Виноватым будешь ты.
     Спрячься где-нибудь в сторонке.
     А потом иди домой.
     И про то, что видел это,
     Никому не говори.

     Если не купили вам пирожное
     И в кино с собой не взяли вечером,
     Нужно на родителей обидеться,
     И уйти без шапки в ночь холодную.
     Но не просто так
     Бродить по улицам,
     А в дремучий темный
     Лес отправиться.
     Там вам сразу волк
     Голодный встретиться,
     И, конечно, быстро
     Вас он скушает.
     Вот тогда узнают папа с мамою,
     Закричат, заплачут и забегают.
     И помчатся покупать пирожное,
     И в кино с собой
     Возьмут вас вечером.

     Посмотрите, что творится
     В каждом доме по ночам.
     Отвернувшись к стенке носом,
     Молча взрослые лежат.
     Шевелят они губами
     В беспросветной темноте
     И с закрытыми глазами
     Пяткой дергают во сне.

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

Все!


Хождение по фреймам

бумки - бумки

бум-бум-бум-бум
бум-бум-бум-бум
бум-бум-бум-бум, ну и т.д...


Хождение по фреймам

Ну, а здесь содержание...

много, очень много
всякого и разного
текста, картинок, ну и т.д...


Первые шаги - Учебник (руководство) по хтмл (html).

Главная | Оглавление | Словарь

Инструментарий. Основные понятия.

  • Ступенька 1 - "Основной инструментарий, учимся работать руками".
  • Ступенька 2 - "Хтмл - язык ли? Гуси, гуси , тэги, tag`и".
  • Ступенька 3 - "Обязательные тэги. Зачем телу голова, а голове тело?"

    Простейшие.

  • Ступенька 4 - "Раскрась сам. Изменяем цвет текста".
  • Ступенька 5 - "Как изменять цвет фона. Оранжевый галстук".
  • Ступенька 6 - "Параграфы. Учимся выравнивать текст".
  • Ступенька 7 - "Что такое заголовки и как задавать размер буковок".
  • Ступенька 8 - "Курсив, жирный текст, подчеркнутый и другие".
  • Ступенька 9 - "Что такое путь?" Как вставлять картинки".
  • Ступенька 10 - "Что можно сделать с картинкой".
  • Ступенька 11 - "Ссылка и как с нею бороться".
  • Ступенька 12 - "Может ли картинка быть ссылкой".
  • Ступенька 13 - "Куда податься, на что ссылаться. Новое окно".
  • Ступенька 14 - "Что такое списки, или Он меня посчитал".
  • Ступенька 15 - "О линиях. Просто и полезно".
  • Ступенька 16 - "Спецсимволы или Головная боль".

    Таблицы.

  • Ступенька 17 - "Учимся создавать таблицы".
  • Ступенька 18 - "Учимся создавать таблицы" продолжение.
  • Ступенька 19 - "Таблицы, вертикальное выравнивание (valign)".
  • Ступенька 20 - "Таблицы, учимся растягивать ячейки (rowspan, colspan)"
  • Ступенька 21 - "Таблицы, что такое cellspacing и cellpadding. Что делать с пространством".
  • Ступенька 22 - "Вложенные таблицы и некоторые нюансы".
  • Ступенька 23 - "О рамках таблиц, и брюзжание о таблицах напоследок".

    Фреймы.

  • Ступенька 24 - "Вступление. Зачем они (фреймы) нужны."
  • Ступенька 25 - "Учимся создавать фреймы".
  • Ступенька 26 - "Учимся создавать фреймы" продолжение.
  • Ступенька 27 - "Учимся создавать фреймы" продолжение.
  • Ступенька 28 - "Параметры тэга Frame. Убираем полосу прокрутки, избавляемся от рамок, регулируем ширину полей фрейма".
  • Ступенька 29 - "Как делать так, чтобы при нажатии на ссылку документ открывался в другом фрейме, в полное окно".
  • Ступенька 30 - "Последняя глава о фреймах. Что такое IFrame. Несколько ссылок напоследок.".

    Дополнительные.

  • Ступенька 31 - "Форматирование текста. Тэги Big, Small, Strike (или S), Sub и Sup" (крупный, малый текст, перечеркнутый, верхний и нижний индексы).
  • Ступенька 32 - Завершение обтекания текстом объекта (картинки).
  • Ступенька 33 - Навигационные карты. Map. Как сделать часть картинки ссылкой.
  • Ступенька 34 - Навигационные карты 2.
  • Ступенька 35 - Навигационные карты 3.
  • Ступенька 36 - Ссылка внутри документа, якоря, anchor.

    Примечания.

  • Примечание (2) - Несколько ссылок на тему "История HTML".
  • Примечание (8) - Работа со шрифтами (ссылки).
  • Примечание (11) - Подсказка к текстовой ссылке.
  • Примечание (11) - Дополнительные опции Mailto.
  • Примечание (12) - Как задавать цвет рамок вокруг картинки? И нужны ли рамки?
  • Примечание (24) - "Фреймы хорошо, или все же плохо?"

    Вот пока что все главы, скоро будут другие:)


Первые шаги - Навигационные карты. Map. Как сделать часть картинки ссылкой, как сделать так, чтобы при нажатии в одно место на картинке мы попадали на одну страницу, а при нажатии на другую область картинки - на другую страницу.

Главная | Оглавление | Словарь

Браво, маэстро! Вернитесь назад :)

Назад


Первые шаги - Навигационные карты. Map. Как сделать часть картинки ссылкой, как сделать так, чтобы при нажатии в одно место на картинке мы попадали на одну страницу, а при нажатии на другую область картинки - на другую страницу.

Главная | Оглавление | Словарь

Ну, как, все понятно?

Назад


Первые шаги - Навигационные карты. Map. Как сделать часть картинки ссылкой, как сделать так, чтобы при нажатии в одно место на картинке мы попадали на одну страницу, а при нажатии на другую область картинки - на другую страницу.

Главная | Оглавление | Словарь

Я же говорила, что это просто :)

Назад


Первые шаги - Учебник (руководство) по хтмл (html).

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

Алленова Наталья

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

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

  • Оглавление
  • Словарь
  • Учебник

    • Учебник будет периодически дополняться, так что следите за Новостями.

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


Первые шаги - Ступенька n-ая.

Главная | Оглавление | Словарь

Пока что все. Учебник будет переодически дополняться, так что следите за Новостями.

Вы можете вернуться к началу.

Все свои вопросы, негодования, пожелания кидайте в автора по почте или по аське (521771).


Хождение по фреймам

Главная Тумки Бумки Турурумки Траляля


Документ с Меню

Главная Тумки Бумки Турурумки Траляля


Мой первый шаг

Здравствуйте, это моя первая страница.

Мои Фотографии :)


Мой первый шаг

 Здравствуйте, это моя первая страница.
Добро пожаловать! :)


Мой первый шаг

 Мой первый шаг Здравствуйте, это моя первая страница.
Добро пожаловать! :)


Мой первый шаг

Здравствуйте, это моя первая страница.

Добро пожаловать! :)

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


Мой первый шаг

Здравствуйте, это моя первая страница.

Добро пожаловать! :)

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

А может и просто случайный посетитель вдруг захочет познакомится со мной, и у меня появится еще один виртуальный друг? :)

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

Если ты так тоже думаешь, то давай как-нибудь встретимcя, поболтаем, чайку попьем в кафешке?:) Кто знает, может быть мы и в реальной жизни станем друзьями:)


Мой первый шаг

Здравствуйте, это моя первая страница.
Добро пожаловать! :)

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


Первые шаги - шрифты - это и не мелочь вовсе...

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

Рекомендую почитать на досуге:

  • Справочник по работе со шрифтами
  • Тайп-дизайн (шрифтовой дизайн)
  • Анатомия текста
  • Акценты в шрифтовых композициях
  • Техника коллажа. Работа со шрифтами
  • Техника коллажа. Работа со шрифтами II


Первые шаги - примечание, об отношении к фреймам.

Крошка сын пришел к отцу и спросила кроха: "Папа, фреймы хорошо, или все же плохо?"

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

Я не буду разглагольстовать о всех проблемах связанных с фреймами и их решением, дам лишь пару ссылок:

  • А чем вам фреймы не по душе? (обсуждение на форуме IXBT.com)
  • Страницы vs. Фреймы (обсуждение на форуме Deforum.ru)

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

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


Первые шаги - Несколько ссылок на тему История HTML.

Несколько ссылок на тему "История HTML":

  • Краткая История HTML

    Статья Дмитрия Кирсанова (личности, знаменитой в области веб-дизайна). К сожалению, она была написана в 97 году, но актуальности не потеряла - история она и есть история.

  • Введение. Историческая справка.

    Историческая справка о возникновении HTML, о его стандартизации усилиями W3C и о путях дальнейшего развития.

  • Что такое HTML

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


Первые шаги - Как задавать цвет рамки вокруг картинки? И вообще,профессионалы используют эти рамки или это полная фигня,которая только рябит в глазах?

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

Как задавать цвет рамки?

Цвет рамки вокруг картинки задается параметром bodercolor, например:

 <img src="picture.gif" border="3" bordercolor="#ff0000">

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

Но, если ваша картинка будет являться ссылкой, то рамка вокруг нее будет того цвета, который мы задали для ссылок в тэге body с помощью link, vlink, alink (об этих параметрах мы говорили в уроке 11-том).

Используют ли профессионалы рамки?

Когда как, все зависит от ситуации. Может быть картинка обрамленная рамкой придаст аккуратности вашему сайту, а может быть наооборот - вам эти рамки совсем не нужны, т.к. портят внешний вид вашего сайта, тогда их можно убрать. "Думайте сами, решайте сами, иметь или не иметь" :)


Первые шаги - Примечание, некоторые опции mailto.

<a href="mailto:pochta@mail.ru"> pochta@mail.ru </a>

У mailto есть еще некоторые опции:

- ?subject=Тема пиcьма
- &Body=Текст вашего сообщения
- &cc=copy@mail.ru,copy2@mail.ru (копии письма через запятую)
- &bcc=hidden_copy@mail.ru,hidden_copy2@mail.ru (скрытые копии письма через запятую)

Все вместе это будет выглядеть так:

<a href="mailto:pochta@mail.ru?subject=Hello
&Body=text&cc=copy@mail.ru
&bcc=hidden_copy@mail.ru"> pochta@mail.ru </a>

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


Первые шаги - Как сделать, чтобы при наведении мышки на ссылку (текстовую) была подсказка?

Одна из читательниц задала мне такой вопрос: "Как сделать, чтобы при наведении мышки на ссылку была подсказка?". Я удивилась, зачем это нужно, но тем не менее вопрос был задан, вопрос был интересен, вопрос пошел на примечание:)

В старых спецификациях Html (3.2 и ниже) такое не предусмотрено, стало быть и старые броузеры того, о чем пойдет речь в этом примечании, не поддерживают (IE ниже 5.0 и NN 4.x и ниже). Зато в спецификации Html 4.0 порывы пользователя задать примечание к тексту учитываются. Итак, господа, знакомтесь - атрибут title.

Атрибут title (не путать с тэгом TITLE) предлагает информацию об элементе, для которого он устанавливается. Мы можем установать его например для тега <a>:

<a href="ssilka.html" title="Ваша подсказка"> текст-ссылка </a>

Наведите курсор мыши на слова "текст-ссылка", и, если ваш броузер поддерживает спецификацию Html 4.0, то вы увидите то самое желанное.

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

Поближе ознакомиться с этим атрибутом, меня побудила статья "Об ACRONYM'е замолвите слово" на сайте "Заметки Html Кодера", поэтому хочу сказать Александру Шуркаеву спасибо, что помог мне встретить во всеоружии очередной вопрос:)


Хождение по фреймам

тра-ля-ля - тра-ля-ля

ля-ля-ля-тра-ля
тра-ля-тра-ля-ля
вот-дурью-маюсь-ля-тра-ля, ну и т.д...


Хождение по фреймам

тумки - тумки

тум-тум-тум-тум
тум-тум-тум-тум
тум-тум-тум-тум, ну и т.д...


Хождение по фреймам

турурумки - турурумки

турум-тум-тум
тум-тум-турурум
турум-тум-тум, ну и т.д...


Untitled

Глава 1

Введение

Добро пожаловать в программирование на языке ассемблера для IBM

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

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


Untitled

Программирование на языке Ассемблера

Почему вас могло бы заинтересовать программирование на языке

ассемблера? Cегодня повсюду используются такие языки высокого

уровня как Бэйсик, Фортран и Паскаль . Возможно, вы уже знакомы по

крайней мере с одним языком высокого уровня. Если вы постоянно

пльзуютесь персональным компьютером IBM, то вы знаете, что

интерпритатор Бэйсика является частью системы. Зачем же возиться

еще с одним языком программирования, тем более с таким, который

сулит определенные трудности? Очевидно, даже располагая современ-

ными могучими языками, вы все еще нуждаетесь в ассемблере из-за его

эффективности и точности.

Ассемблерные программы могут быть очень эффективными. Из

программистов, с равными навыками и способностями, работающий на

языке ассемблера создаст программу более компактную и быстро-

действущую, чем такая же программа, написанная на языке высокого

уровня. Это так практически для всех небольших или средних

программ. К сожалению, по мере возрастания размеров, программы на

языке ассемблера теряют часть своих преимуществ. Это происходит

из-за необходимого в ассемблерной программе внимания к деалям. Как

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

действия компьютера. В небольших программах это позволяет оптими-

зировать работу программы с аппаратными средствами. В больших же

программах огромное количество деталей может помешать вам эффек-

тивно работать над самой программой, даже если отдельные компоненты

программы окажутся очень неплохими. Безусловно, программирование на

языке ассемблера отвечает потребностям не каждой программы.

Программы на языке ассемблера очень точны. Поскольку этот язык

позволяет программисту непосредственно работать со всем аппаратным

обеспечением, ассемблерная программа может делать то, что недоступ-

но никакой другой программе . Несомненно, что в программировании

устройств ввода-вывода, где требуется контроль над отдельными

разрядами регистров устроиства, программирование на языке

ассемблера - единственный подходящий выбор.

Ясно, что эффективность и точность языка ассемблера дают

определенные преимущества. Но его детализированность создает и

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

язык ассемблера?

Конечно вы должны пользоваться программами на языке

ассемблера, когда нет другого способа написать программу. Например,

программисты фирмы IBM писали с использованием процедур ассемблера

все программы управления устройствами ввода-вывода для IBM PC. Для

управления устройствами ввода-вывода и системой прерываний,

потребовалась та точность языка ассемблера которую не может

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

языке ассемблера в фирме IBM писались процедуры диагностики,

которые должны проверять каждую деталь аппаратуры.

Язык ассемблера необходим также и в тех случаях, когда главными

являются рабочие характеристики програмы. Это может быть время

исполнения или конечный размер программы. Библиотека математических

процедур Фортрана - пример программы, требующей хороших характе-

ристик как в отношении времени, так и размера. Математические

процедуры являтся частью любой программы на Фортране, поэтому они

должны занимать как можно меньше места. Кроме того, эти процедуры

управляют всеми математическими функциями в фортрановской программе

и часто используются. Следовательно, они должны исполняться быстро.

Какая программа не подходит для языка ассемблера? Конечно, вы

можете написать на нем любую программу, однако с большой программой

лучше работать в языке высокого уровня, таком как Бэйсик или

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

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

аппаратного оборудования и процессора. Языки высокого уровня

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

Очевидно далее, что вы нуждаетесь в соединении программ языка

ассемблера с программами языков высокого уровня. Эдесь мы ограни-

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

которых он хорошо подходит, таких как управление вводом-выводом.

Заключительная же глава книги посвящена прямо проблеме соединения

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

методы предоставляют вам все лучшее, что есть в обоих мирах. Вы

можете, когда необходимы точность и эффективность, использовать

процедуры на языке ассемблера - и процедуры высокого уровня для

программы в целом. Все что вы для этого должны сделать - сцепить их

вместе.

И последняя причина для изучения программирования на языке

ассемблера. Только через написание программ на этом уровне

детализации вы можете понять как работает машина на самом нижнем

уровне. Если вы хотите узнать о компьютере все, вы должны быть

знакомы с его языком ассемблера. Единственный способ добиться этого

- писать программы на этом языке. Простое чтение этой книги дела не

сдвинет.


Untitled

Персональный компьютер фирмы IBM (IBM PC)

Почему в этом тексте IBM PC берется в качестве базовой для изучения

программирования на языке ассемлера? Для этого есть несколько

причин. Во-первых, IBM PC - новая и мощная машина. Как персональный

компьютер она обладает расширенными возможностями выходящими за

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

дальше, PC использует микропроцессор Intel 8088. Этот процессор мо-

жет выполнять 16-битовую арифметику и адресацию над более чем мил-

лионом символов памяти. Эти возможности ставят его ближе по

мощности к большим ЭВМ, чем к ранним персональным компьютерам.

Во-вторых, IBM PC располагает всеми средствами разработки

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

ассемблера. Кроме ассемблера, фирма IBM поставляет текстовый

редактор, редактор связей и дисковую опеационную систему для того

чтобы объединить их все вместе. Имеется даже отладчик чтобы помочь

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

Наконец, IBM PC - хорошая система для изучения языка

ассемблера из-за ее доступности. Это недорогая машина, которая тем

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

вания на языке ассемблера. Более того, как "персональный"

компьютер, машина принадлежит вам по крайней мере на время испол-

нения программы. Это означает, что вы можете попытаться выполнить

то, что нельзя делать на более крупной машине, общей с другими

пользователями. Вы можете овладеть оборудованием ввода-вывода и

запрограммировать их для выполнения интересных вещей. Вы можете

делать что захотите с любой частью системы - даже если это

"вырубает" систему. Поскольку это персональная машина, то при

возникновении проблемы вы просто выключаете машину и запускаете ее

снова. Единственный человек, которому вы при этом можете помешать -

это вы сами. Как персональная машина, IBM PC является великолепным

инструментом для разработки программ.


Об этой книге

Данная книга является введением в персональный компьютер IBM и его

язык ассемблера. Хотя основное внимание здесь уделено программиро-

ванию на языке ассемблера, этот текст описыват также программные

аспекты основных особенностей аппаратного обеспечения машины. Вы

узнаете как работают устройства ввода-вывода и как программа

заставляет их работать правильно. Вы также научитесь писать

собственные программы на языке ассемблера с использованием IBM PC.

После того как вы напишете эти программы, эта книга покажет вам как

связать их с программами высокого уровня или встроить вашу

программу в систему.

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

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

для демонстрации действительно работающих программ. Эти примеры

помогут вам начать. Однако единственный способ научиться чему-либо

в программировании - это сделать программу самому. Вы должны со-

вершать свои собственные ошибки чтобы чему-либо научиться. Желаю

достигнуть успехов и получить удовольствие.


Untitled

Глава 2

Основы компьютерных вычислений

В этой главе разъясняются свойства компьютеров. Она расскажет вам

как компьютеры работают и почему они делают это именно так. Неко-

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

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

для вас новыми.

Двоичная арифметика

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

Это значит, что каждый элемент хранимой информации может иметь

только два состояния. Эти состояния обозначаются как "включен" и

"выключен", "истина" и "ложь", или "1" и "0". Компьютер хранит эти

значения в виде уровней напряжения. К счастью у нас нет нужды свя-

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

только с числами. Используя простейшие числа 0 и 1, можно

выполнять очень сложные вычисления. Из-за двоичного представления

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

двоичным основанием. Арифметика с основанием 2 пользуется только

двумя цифрами: 0 и 1. Мы обычно применяем систему исчисления по

основанию 10. В десятичной арифметике употребляется десять

различных цифр - от 0 до 9. Двоичную арифметику можно представить

себе как систему для людей, имеющих только два пальца.

Ограничение лишь десятью цифрами в десятичной арифметике не ме-

шает нам представлять более крупные числа. Мы пользуемся

многозначными числами, в каждой позиции которых стоят разные

степени 10. Самая правая цифра любого числа обозначает число

едениц, соседняя слева - количество десятков, следующая - число

сотен и т.д. Прогрессия справа налево выстраивается такая:

10**0, 10**1, 10**2 и т.д. Число 2368 в дейстительности

представляет 2 тысячи, 3 сотни, 6 десятков и 8 едениц. Фиг. 2.1

показывает ,говоря матическим языком, разложение числа 2368.

_____________________________________________________

2368 = 2 * 10**3 + 3 * 10**2 + 6 * 10**1 + 8 * 10**0

= 2000 + 300 + 60 + 8

_____________________________________________________

Фиг. 2.1 Десятичное представление

Арифметика с основанием 2 или двоичная система аналогична деся-

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

степеням 2 а не 10. Числа больше 1 представляются многозначными

числами, так же как в десятичной арифметике многозначное представ-

ление получают числа больше 9. Каждая цифра в двоичной системе

называется бит от Binary digIT (двоичная цифра). Позиция каждого

бита в числе соответствует некоторой степени 2. Фиг. 2.2

показывает значение двоичного числа 101101B.

101101B = 1*2**5 + 0*2**4 + 1*2**3 + 1*2**2 + 0*2**1 + 1*2**0

= 32 + 0 + 8 + 4 + 0 + 1

= 45

____________________________________________________________

Фиг. 2.2 Двоичное представление

Мы будем пользоваться суффиксом "B" для обозначения чисел в

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

не имеющих суффикса. Например, 2368 - это десятичное число, а

101101B - двоичное. В математической литературе для обозначения

системы исчисления обычно используется индекс. Мы будем

пользоваться символом "B", поскольку ассемблер IBM для обозначения

двоичных чисел применяет именно его.

В таблице Фиг. 2.3 для представления максимального десятичного

числа требуется 4 бита. Для более крупных чисел потребуется еще

больше бит. Двоичным числом состоящим из n бит можно изобразить

число величиной 2**n-1. То есть двоичное число в n бит длиной

может

Десятичное Двоичное

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

1 1

2 10

3 11

4 100

5 101

6 110

7 111

8 1000

9 1001

10 1010

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

Фиг. 2.3 Первые 10 целых

единственным образом представить любое целое от 0 до 2**n-1. Для

4-х битового примера на Фиг 2.3 самое большое такое число равно 15

(2**4-1).

Для каждого конкретного микропроцессора сществует максмальный

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

микропроцессора 8088, используемого в IBM PC, внутренние операции

производятся над числами длиной 16 бит. Максимальное целое, кото-

рое можно представить в 16 битах, равно 2**16-1 или 65 535. Однако

такая беззнаковая арифметика допускает числа только от 0 до 65 535.

Для обозначения отрицательных чисел нам потребуются изменения в

этой схеме.


Untitled

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

Для изображения как положительных, так и отрицательных чисел 8088

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

арифметике самый левый бит целого числа указывает на его знак. По-

ложительные числа имеют 0 в старшем бите, а отрицательные - 1. По-

ложительные числа имеют одинаковое значение в знаковой и без-

знаковой арифметике. У отрицательных же значение иное. Для того

чтобы сделать число отрицательным, изменить его знак на минус, оно

дополняется и результат увеличиваетя на единицу. В 4-х битовом

примере 5 имеет значение 0101B, в то время как -5 равно 1011B.

Пример на Фиг. 2.4 показывает этот метод.

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

двоичного дополнения. То есть -0 равен 0. Для любого n-битового

числа в системе с двоичным дополнением наибольшее значение сос-

тавляет 2**(n-1)-1, а наименьшее -2**(n-1). Ноль - единственен. В

4-х битовой системе наибольшее число, как показано на Фиг. 2.6,

равно 7, а наименьшее -8.

_________________________

5 = 0101B

Для изменения знака:

Дополняем 1010B

Добавляем 1 0001B

----- __________________________________

-5 = 1011B 0 = 0000B

Для изменеия знака: Для изменения знака:

Дополняем 0100B Дополняем 1111B

добавляем 1 0001B Добавляем 1 0000B

----- -----

5 = 0101B -0 = 0000B = 0

_________________________ __________________________________

Фиг. 2.4 Двоичное допол- Фиг. 2.5 То же самое с нулем

нение 5

Как мы увидим позднее, процессор 8088 может работать с одним и

тем же целым как со значением имеющим или не имеющим знак. Выбор

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

программа работает в арифметике со знаком, процессор 8088 применяет

при проведении операций двоичное дополнение.

Десятичное Двоичное Десятичное Двоичное

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

7 0111 -1 1111

6 0110 -2 1110

5 0101 -3 1101

4 0100 -4 1100

3 0011 -5 1011

2 0010 -6 1010

1 0001 -7 1001 Фиг. 2.6 Числа

0 0000 -8 1000 с двоичным

------------------------------------------------- дополнением


Untitled

Шестнадцатиричное представление

Двоичная арифметика хороша для компьютера, поскольку он имеет дело

только с еденицами и нулями. Но человеческое восприятие требует

более компактного представления. Мы будем пользоваться шестнадца-

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

Шестнадцатеричное представление чисел - это система исчисления

по основанию 16. Каждая цифра в числе может иметь значение от 0 до

15. Каждый разряд в числе является степенью 16. Шестнадцатеричное

представляение - удобный метод записи двоичной информации. Каждая

шестнадцатеричная цифра соответствует четырем битам. Для преобра-

зования двоичного числа в шестнадцатеричное разбейте его на группы

по 4 бита и прочитайте каждую группу как шестнадцатеричную цифру.

Это дает уплотнение записи один к четырем - очень удобно для

разумного существа.

Небольшая трудность здесь связанна с тем, что у нас имеются

цифры только от 0 до 9. Числа от 10 до 15 мы будем представлять

первыми шестью буквами латинского алфавита: от A до F. Таблица

соответствия между десятичными, шестнадцатеричными и двоичными

цифрами приводится на Фиг. 2.7.

Как показано в этой таблице, каждая шестнадцатеричная цифра со-

ответствует точно 4-м битам какого-либо двоичного числа.

Шестнадцатеричное представление обычно для машин, в которых

размер слова кратен 4. Поскольку слово в 8088 составляет 16 бит,мы

будем пользо- ваться шестнадцатеричной записью. Каждое 16-битовое

значение пред- ставляется четырьмя шестнадцатеричными цифрами. В

этой книге числа в шестнадцатеричной записи будут обозначаться

суффиксом "H", а двоичные числа - суффиксом "B".

Десятичные Двоичные Шестнадцатер. Десятичные Двоичные Шестнадцатер.

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

0 0000 0 8 1000 8

1 0001 1 9 1001 9

2 0010 2 10 1010 A

3 0011 3 11 1011 B

4 0100 4 12 1100 C

5 0101 5 13 1101 D

6 0110 6 14 1110 E

7 0111 7 15 1111 F

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

Фиг. 2.7 Шестнадцатеричная нумерация

Десятичные числа пишутся без суффикса или с суффиксом "D". Это в

точности соответствует записи чисел в языке ассемблера. Для

предсталения данных в ассемблерной программе можно пользоваться лю-

бой из трех рассмотренных систем (десятичная, двоичная и шестнадца-

теричная).

При записи шестнадцатеричных чисел важно убедиться, что ассемб-

лер воспримет их как числа. Если вы ввели "FAH", то это может быть

или шестнадцатеричное число FA, или имя переменной FAH. Ассемблер

предполагает, что число начинается с цифры и что метка начинается с

буквы. Поэтому "FAH" для ассемблера оказывается переменной. Если

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

"0FAH": это число имеет желаемое значение и начинается заведомо с

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

которое начинается со значений от A до F должен предшествовать 0.


Untitled

Машинный язык и язык Ассемблера

Мы уже видели, как из нулей и единиц, хранимых в ЭВМ, формируются

числа.Теперь мы посмотрим как комбинации тех же значений 0 и 1

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

Машинная программа представляет собой последовательность ко-

манд (инструкций). Эти команды "объясняют" компьютеру, что он

должен делать. Это похоже на рецепты в кулинарной книге. В

рецепте имеется описание действий, которые необходимы для

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

имеет последовательность команд, которые точно описывают ему

последовательность действий. Этот набор команд называется

программой. Процесс построения корректного набора команд

называют программированием компьютера. В нашей аналогии с рецептом

рецепт является программой, а тот кто его написал - программистом.

Роль компьютера здесь играет повар, готовящий еду.

Реальная программа, которую выполняет компьютер, это последова-

тельность едениц и нулей, связанных с памятью компьютера. Эту стро-

ку бит принято называть машинным языком. Машинный язык - это тот

язык который машина понимает. Компьютер извлекает команды машинного

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

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

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

данной главы.

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

сложить два числа в 8088 (например, содержимое регистров AX BX -

краткое описание регистров сейчас последует), команда будет

выглядеть таким образом:

0000001111000011B (или 03C3H)

Эти два байта точно указывают компьютеру какую опреацию произвести.

Аналогично, для вычитания двух чисел (вычитание регистра BX из ре-

гистра AX) мы будем иметь в машинном языке

0010101111000011B (или 2BC3H)

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

обсуждении основ работы 8088 с ними приходится чато сталкиваться.

Регистр - это часть процессора, предназанченная для сохранения дан-

ных. К данным, сохраненным в регистре, процессор получает доступ

очень быстро - намного быстрее, чем к данным, хранимым в памяти.

Возможно еще специальное использование регистров в некоторых коман-

дах. В третьей главе будет дано полное описание регистров 8088.

Хотя машинный язык - это действительно прекрасно, если вы явля-

етесь компьютером, он труден для программистов - людей. К счастью,

существует более простой способ программирования. Этим методом,

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

языке ассемблера.

Язык ассемблера, как язык программирования, т.е. более понятный

программисту, чем машинный, язык, все еще сохраняет все значения

машинного языка. Компьютер читает программы на языке ассемблера и

переводит их в машинный язык, в ту форму, которая понятна ЭВМ. Этот

процесс, называемый "ассемблированием" программы, фактически

является переводом с одного языка на другой. Операцию перевода с

языка ассемблера на машинный язык выполняет программа, называемая

ассемблером.

Чтобы лучше понять разницу, давайте взглянем на примеры,

которыми мы уже пользовались выше. Ассемблерная команда для

сложения содержимого регистров AX и BX проста:

ADD AX,BX

Аналогично, для вычитания регистра BX из регистра AX мы напишем:

SUB AX,BX

Ассемблер превращает эти предложения в форму, которую мы видели вы-

ше. Компьютер сам управляется с проблемой превращения файла с по-

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

бы выполнить процессор.

Язык ассемблера не похож на языки типа Фортран, Кобол или

Паскаль. Эти языки, как и многие подобные им, являются языками

высокого уровня. Языки высокого уровня разработаны для того, чтобы

можно было иметь дело непосредственно с задачей, решаемой

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

языками, поскольку описывают процедуру, используемую для решения

задачи. Языки высокого уровня машинно-независимы. Программа,

написанная на Фортране для IBM PC будет правильно работать и давать

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

программирования не зависит от машины.

Програмы же на языке ассемблера непосредственно относятся к той

машине, на которой они должны выполняться. Язык ассемблера машин-

нозависимый. Язык ассемблера для IBM PC принципиально отличен от

языка ассемблера для IBM/370. Это связано с тем, что команды языка

ассемблера почти один к одному переводятся в команды машинного

языка т.е. каждая команда языка ассемблера обычно преобразуется

точно в одну команду машинного языка. Поскольку машинные языки

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

Обычно каждое утверждение языка ассемблера генерирует одну команду

машинного языка. В некоторых случаях это не так, потому что

существуют команды, которые не являются частью выполняемой програм-

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

ра, который должен выполнять их во время ассемблирования. Пример

директивы ассемблеру (такой предназначенной только для него

команды) -

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

Эта инструкция сообщает ассемблеру заголовок программы. После

трансляции ассемблером прграммы этот заголовок - "Пример

программы"- появляется в верхней части каждой страницы сообщений

ассемблера. Эта инструкция имеет смысл только для ассемблера. В

8088 нет команды, которая могла бы выполнить эту опреацию.


Untitled

Синаксис языка Ассемблера

Прежде чем двигаться дальше, обсудим синтаксис команд языка ассем-

блера.Мы должны выделить основные компоненты языка ассемблера,

чтобы можно было затем обозначать эти компоненты с помощью

сиандартных терминов.

Команда языка ассемблера состоит из четырех частей. Фиг. 2.8

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

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

PART1: ADD AX,BX ;Добавить к длине буфера

Метка ОпКод Операнды Комментарий

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

Фиг. 2.8 Синтаксис языка ассемблера

Единственная обязательная часть команды языка ассемблера - ОпКод

(сокращение от ОПерационный КОД). Программисты иногда называют ма-

шинные команды кодами операций. Операционный код в утверждении язы-

ка асемблера определяет, какую опреацию должен будет выполнить про-

цессор, в нашем примере - операцию сложения (по английски - add -

прим. перев.).

Поле операндов содержит дополнительную информацию о команде,

например, какие значения участвуют в операции. Поле операндов

определяется операционным кодом. Каждому коду операции должно

соответствовать определенное число операндов. Для команды ADD

требуется два операнда; операция перемены знака (NEG) обходится

лишь одним, а для некоторых команд, например, команды десятичной

коррекции DAA, операнды не нужны. В главе 4 описаны эти команды и

их операнды.

Метка и комментарий необязательны в команде. Поле метки позво-

ляет обозначить какое-либо конкретное место в памяти компьютера.

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

адрес какой либо команды трудно, если вообще возможно. Метка

позволяет идентифицировать определенное место в памяти заданным

программистом именем. Говоря технически, поле метки содержит

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

титься к этой команде позднее, то мы делаем это через символьное

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

инструкции. Использование меток - одна из причин предпочтительности

языка ассемблера перед машинным языком. Превращением же симво-

лических имен в реальные адреса ведает ассемблер.

Поле комментариев служит для удобства программиста.

Программист может использовать это поле для сообщения

дополнительной информации о команде. Комментарий не обязательно

жестко связан с командой. Вы можете отвести под комментарий целую

строку, поставив в ее начале символ ";". Это позвляет программисту

в ключить в листинг ассембле- ра блок собственной информации, к

примеру, описание используемого алгоритма.

У каждого есть собственное представление о том, как следует

комментировать программы, и вы наверняка тоже скоро выработаете

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

которая относится непосредственно к решаемой проблеме. В при-

веденном примере было бы бессмысленно комментировать команду

чем-нибудь вроде "сложить AX и BX". Это не более, чем повторение

операционного кода и операндов (разве что в переводе с английского

- прим.перев.). Если уж вы намерены связаться с комментариями, то

делайте их достойными труда их написания и чтения.


Untitled

Принципы работы Ассемблера

Рассмотрим теперь работу ассемблера в целом. Детали будут

обсуждены позднее, но сейчас нам нужно ввести новые термины и

ознакомиться с реальным результатом работы ассемблера.

Ассемблер берет программу, написанную на языке ассемблера, и

превращает ее в машинный язык. Файл, который содержит программу на

языке ассемблера, называют исходным файлом. Выход и ассемблера в

действительности является не собственно машинным языком, а

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

файл называют объектным файлом. Данные в нем называются объектным

кодом. Для получения из него настоящего машинного кода объектный

код должен быть несколько изменен. Для IBM PC это делает программа

редактор связей LINK. Шаг преобразования объектных кодов в

машинные принято называть построением связей или редактированием

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

следующих глав.

Помимо преобразования исходного кода в объектный ассемблер

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

ассемблерный листинг. Он содержит сообщение о действиях

ассемблера. Зтот файл содержит исходный код вместе с

комментариями, а также объектный код, сформированный ассемблером.

Фиг. 2.9 дает пример листинга ассемблера, иногда называемого

распечаткой.

Microsoft (R) Macro Assembler Version 5.00 10/28/88

16:35:34

Фиг. 2.9 Пример ассемблирования Page 1-1

1 PAGE ,132

2 TITLE Фиг. 2.9 Пример ассемблирования

3 0000 CODE SEGMENT

4 ASSUME CS:CODE

5

6 0000 03 C3 PART1: ADD AX,BX ; Сложить с длиной буфера

7

8 0002 CODE ENDS

9 END

Фиг. 2.9 Пример ассемблирования

Взяв пример команды ассемблера, рассмотрим результаты работы

ассемблера. В правой части распечатки находятся исходные команды.

В левой части - информация, сгенерированная ассемблером. Первая

колонка содержит номер каждой строки распечатки. Ассемблер

устанавливает эти номера для исходного файла. Они строк не

обязательно соотносятся с номерами строк в исходном файле

сформированном текстовым редактором.

Во второй колонке содержатся адреса инструкций. Программа LINK

может их изменить, но они являются лучшим предположением, которое

может сделать ассемблер на шаге ассемблирования. Следующая колонка

- код команды на машинном языке. Поскольу команды 8088 имеют длину

от 8 до 56 бит, это поле будет изменяться в размере. Кроме того,

программа LINK может изменить некоторую информацию в поле объектных

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

оперирующих с адресами. Однако, за исключением адресов, листинг

ассемблера дает верные машинные коды, которые и будут в дальнейшем

исполняться.

В большинстве примеров программ мы будем использовать листинг

ассемблера. Это позволит нам сразу видеть вырабатываемый ассембле-

ром код.

Другой создаваемый ассемблером файл - файл перекрестных сыылок.

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

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

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

ссылками для того, чтобы выявить все команды, которые обращаются к

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

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

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

ссылках будет обсуждаться в главе 5.


Untitled

Биты, байты и слова

Мы назвали "битом" двоичную цифру, еденичное значение 0 или 1. Для

удобства введем специальные названия для некоторых последо-

вательностей битов. Группу из 8 бит принято называть байтом. Во

всей документации IBM и в этой книге о любых 8 битах информации

говорится как о байте. Байт заслужил свое собственное имя по

нескольким причинам. Элементарная ячейка памяти имеет длину 8 бит.

При каждом обращении к паамяти IBM PC для процессора запрашивыается

ровно 8 бит информации. Как мы увидим позднее, отдельные команды

8088 могут производить арифметические и логические опреации над

группами в 8 бит. Байт - наименьшая еденица информации, с которой

8088 может манипулировать непосредственно. 8088 может одной

операцией сложить два 8-битовых числа, но не может этого проделать

с 4-битовыми. Кроме того IBM PC использует байт для представления

одного символа. Используя один байт можно представить 256 (2**8)

отдельных элементов, таких, например, как графические символы. В

следующем пункте мы рассмотрим набор символов IBM PC.

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

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

фактически и будет состоять в определении содержимого памяти для

выполнения программы. В основном исходный текст ассемблера состоит

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

в байт памяти ассемблер располагает специальным механизмом -

определением байта (dtfine byte) или псевдокомандой DB. DB не

является командой 8088. Это команда ассемблеру поместить в память

определенные значения. Псевдокоманда

DB 23

дает ассемблеру задание сохранить десятичное значение 23 в текущий

байт памяти. А оператор

DB 1,2,3,4

сохраняет значения от 1 до 4 по четырем последовательным адресам в

памяти.

В программах на языке ассемблера оператор DB применяют для

определения областей памяти. В предыдущих примерах мы размещали в

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

информация для перекодировки чисел. Мы составим несколько

примеров, в которых используется определенная подобным образом

информация. Кроме того встречаются ситуации, когда программе

требуется место в памяти для сохранения данных в процессе

исполнения. Во время ассемблирования программы содержимое этого

участка памяти неизвестно: собственно, это содержимое будет

переменным во время исполнения программы. Инструкция

DB ?

сообщает ассемблеру о необходимости выделить один байт памяти, не

изменяя его содержимое. В Этом байте может оказаться любое

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

команда не поместит в него определенное значение.

Нам может потребоваться выделить и большое количество байтов,

например, чтобы оставить область памяти для массива. Мы можем это

сделать так:

DB 25 DUP(?)

Этой инструкцией выделяется 25 байт памяти. Ключевое слово DUP в

этой псевдокоманде означает повторить (duplicate). Число 25

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

памяти. Значение или значения в скобках ассемблер использует для

инициализации этой области памяти. В данном случае это значение

неизвестно. Для инициализации области с одним и тем же значением

выражение, например,

DB 17 DUP(31)

создает 17 байт со значением 31 каждый. Наконец,

DB 30 DUP(1,2,3,4,5)

выделяет 30 байт со значениями от 1 до 5 в первых пяти байтах. Сле-

дующие пять байт тоже имеют значения от 1 до 5 и т.д. Ассемблер

повторяет значения в скобках пока не будут заполнены все 30 байт.

Иногда нам хочется обратиться к набору бит меньшему чем байт.

Принят размер 4 бит. В 4 битах мы можем представить все 10

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

пользоваться термином "полубайт". Этот термин (в оригинале

"nybble" - прим. перев.), который достиг широкого применения,

позволяет нам говорить о данных, меньших, чем "байт".

Термин "Слово" имеет для программиста значение отличное от

принятого в языке. В применении к ЭВМ слово - это наибольшее

количество бит, с которым машина может обращаться как с единым

элементом. Для системы IBM/370 слово составляет 32 бит, а для

семейства Intel 8088 - 16. Поэтому термин "слово" имеет

неопределенный смысл, пока не известна конкретная машина.

Размер слова в 8088 составляет 16 бит. Этот размер

определяется каналами передачи данных в процессоре. Над числами до

16 бит 8088 может призводить операции одной командой. Любое более

крупное число потребует более одной команды. Существуют команды,

которые манипулируют и с меньшими объемами памяти, как, например,

команда сложения двух 8-битовых чисел. Несколько инструкций

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

двух 32-битовых чисел потребуется уже две команды, складывающие по

16 бит каждая. Наибольшее число над которым мы можем производить

элементарные операции типа сложения имеет размер машинного слова.

Аналогично команде определения байта памяти, существует и

инструкция для определения слова памяти. Оператор ассемблера DW

означает определение слова (defie word). Первое утверждение DW на

Фиг. 2.10 определяет 16 бит памяти со значением 1234H. Как и в

случае с байтами, мы можем использовать оператор DUP для

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

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

операнд "?".

Microsoft (R) Macro Assembler Version 5.00 10/29/88

16:10:44

Фиг. 2.10 Примеры определения слов Page 1-1

1 PAGE ,132

2 TITLE Фиг. 2.10 Примеры определения слов

3

4 0000 1234 DW 1234H

5 0002 0003[ DW 3 DUP(5678H)

6 5678

7 ]

8

9 0008 ???? DW ?

10

11 END

Фиг. 2.10 Примеры определения слов

Одна из обескураживающих черт 8088 - это его манера хранения

слов в памяти. На Фиг. 2.10, хоть мы и определяли значение слова

как 1234Н, ассемблер сохранит в памяти значение 3412Н, по крайней

мере так это выглядит. Посмотрим, как это получается.

Допустим, слово 1234Н сохранено в ячейках 100 и 101. 8088 тре-

бует, чтобы ассемблер поместил значение 34Н в ячейку 100, а 12Н - в

101. Легче всего запомнить это так, что ассемблер сохраняет

младший байт слова в ячейку памяти с меньшим адресом, а старший

байт - с большим. На Фиг. 2.11 показано, содержимое памяти после

того, как ассемблер поместит в нее данные. Пока вы не привыкнете к

такому методу, вам

Адрес Значение

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

. .

. .

100 34Н

101 12Н

. .

. . Фиг. 2.11 Представление

------------------------- в памяти DW 1234H

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

будете смешивать операции над байтами и над словами одной и той же

области в памяти, вам незачем беспокоиться об этом неожиданном

"переключении" байтов. Программа может спокойно работать со

словами, а 8088 всегда разберется что к чему. Только в том случае,

если вы захотите обратиться к конкретному байту какого-либо слова,

вам придется иметь дело с фактическим способом хранения слов в

памяти семейства 8088. Ассемблер обращает внимание на структуру

слов в распечатке программы, то есть изображает слова в объектном

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

наоборот. Вы сможете различать слова благодаря тому, что ассемблер

записывает их шестнадцатеричными цифрами без пробелов.

Однако остался еще один тип данных, который постоянно использу-

ется программах на языке ассемблера для микропроцессора 8088. Это

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

двойными словами для хранения адресов и очень больших чисел. Чтобы

определить область, содержащую значение двойного слова, оператор

ассемблера

DD значение

генерирует поле размером в 4 байта. DD означает операцию выделения

двойнго слова (define doubleword). Так же как в случае с DW -

опратором, ассемблер размещает в памяти младший байт ниже, а

старший - выше. В таком же порядке сохраняются средние два байта.

Аналогично операторам DB и DW вы можете пользоваться функцией DUP и

применять операнд "?" для того чтобы оставить область

неопределенной.

Ассемблер может генерировать и другие структуры данных. Их об-

суждение мы отложим, пока не дойдем до некоторых свойств макроас-

семблера и сопроцессора 8087. Остальные структуры данных

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

Числовом сопроцессоре или для определения собственных структур

данных.


Untitled

Нумерация бит

Иногда нам будет требоваться идентифицировать отдельные биты в бай-

те или слове. Для этого мы называем номер бита. Индекс или номер

каждого бита - это степень двойки, соответствующая позиции этого

бита. Самый младший бит - нулевой, поскольку он представляет два в

нулевой степени. Самый старший бит в байте - седьмой - 2**7.

Самый старший бит в слове - 15-й. Фиг. 2.12 показывает 16-битовое

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

ЪДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДї

АДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДЩ

Фиг. 2.12 Нумерация бит

слово с пронумерованными битами. Такой способ нумерации бит принят

во всей документации IBM PC.


Набор символов

Как мы заметили выше, мы можем рассматривать каждый байт информации

не как двоичное число, а как символьное значение. Каждое из

двоичных чисел от 0 до 255 может представлять определенный символ.

Фиг. 2.13 показывает множество символов IBM PC. Колонки здесь

соответствуют старшим 4 битам символьного кода, а ряды - младшим 4

битам этого кода. Так, позиция таблицы 41Н соответствует символу

"A", а код 5ЕН представляет символ "^".

ЪДДДВДДДТДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї ЪДДДВДДДТДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї

ідесі= >є 0 і16 і32 і48 і64 і80 і96 і112і ідесі= >є128і144і160і176і192і208і224і240і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і ішстє 0 і 1 і 2 і 3 і 4 і 5 і 6 і 7 і і ішстє 8 і 9 і A і B і C і D і E і F і

ЖНННШНННОНННШНННШНННШНННШНННШНННШНННШНННµ ЖНННШНННОНННШНННШНННШНННШНННШНННШНННШНННµ

і 0 і 0 єпусі > іпрбі 0 і @ і P і ` і p і і 0 і 0 є А і Р і а і ° і А і Р і р і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 1 і 1 є і < і ! і 1 і A і Q і a і q і і 1 і 1 є Б і С і б і ± і Б і С і с і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 2 і 2 є і і " і 2 і B і R і b і r і і 2 і 2 є В і Т і в і І і В і Т і т і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 3 і 3 є і ! і # і 3 і C і S і c і s і і 3 і 3 є Г і У і г і і і Г і У і у і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 4 і 4 є і і $ і 4 і D і T і d і t і і 4 і 4 є Д і Ф і д і ґ і Д і Ф і ф і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 5 і 5 є і і % і 5 і E і U і e і u і і 5 і 5 є Е і Х і е і µ і Е і Х і х і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 6 і 6 є і і & і 6 і F і V і f і v і і 6 і 6 є Ж і Ц і ж і ¶ і Ж і Ц і ц і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 7 і 7 є і і ' і 7 і G і W і g і w і і 7 і 7 є З і Ч і з і · і З і Ч і ч і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 8 і 8 є і і ( і 8 і H і X і h і x і і 8 і 8 є И і Ш і и і ё і И і Ш і ш і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і 9 і 9 є і і ) і 9 і I і Y і i і y і і 9 і 9 є Й і Щ і й і № і Й і Щ і щ і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і10 і A є і і * і : і J і Z і j і z і і10 і A є К і Ъ і к і є і К і Ъ і ъ і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і11 і B є і і + і ; і K і [ і k і { і і11 і B є Л і Ы і л і » і Л і Ы і ы і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і12 і C є і і , і < і L і \ і l і | і і12 і C є М і Ь і м і ј і М і Ь і ь і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і13 і D є і і - і = і M і ] і m і } і і13 і D є Н і Э і н і Ѕ і Н і Э і э і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і14 і E є і і . і > і N і ^ і n і ~ і і14 і E є О і Ю і о і ѕ і О і Ю і ю і і

ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ

і15 і F є і і / і ? і O і _ і o і  і і15 і F є П і Я і п і ї і П і Я і я і і

АДДДБДДДРДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ АДДДБДДДРДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ

Фиг. 2.13 Набор символов IBM

 Набор символов IBM PC является расширением набора символов

ASCII (Американский стандартный код для обмена информацией). В

наборе ASCII значения символов от 20Н до 7ЕН представляют обычные

символы латинского алфавита, числовые символы и знаки препинания.

Коды от 0Н до 1FH обычно служат управляюшими символами. На Фиг.

2.14 показаны управляющие символы ASCII из этого диапазона. Эти

символы имеют значение при передаче на принтеры IBM или другие

ASCII-принтеры. Однако на Фиг. 2.13 видно, что эти управляющие

символы могут также появляться на экране в виде графических симво-

лов. В IBM PC управляющая часть таблицы ASCII используется для

графических изображений, с целью более полно реализовать возмож-

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

любой из 256 кодов, то нет оснований строго регламентировать

применение какого-либо из кодов. Разработчики рассматривали все 32

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

образом для графического изображения и обычно не печатаемые

принтером. Короче говоря, первые 32 значения являются управляющими

кодами при передаче их на принтер, но изображаются как графические

символы при выводе их на дисплей.

Символьные значения от 80Н до 0FFH являются расширением набора

символов ASCII для IBM PC. Эти символы подобраны разработчиками

IBM так, чтобы расширить изобразительные возможности компьютера.

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

использовать IBM PC в самых разнообразных приложениях.

Код Символ Значение

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

0 NUL Пусто

7 BEL Сигнал

9 HT Горизонтальная табуляция

0A LF Пропуск строки

0B VT Вертикальная табуляция

0C FF Прогон страницы

0D CR Возврат каретки

0E SO Шаг назад

0F SI Шаг вперед

11 DC1 Управление 1

12 DC2 Управление 2

13 DC3 Управление 3

14 DC4 Управление 4

18 CAN Стоп

1B ESC Выход

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Фиг. 2.14 Управляющие коды IBM

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

мять для их дальнейшего использования программой. Примером может

служить сообщение, которое в определенный момент выполнения прог-

раммы должно быть выдано оператору. Вместо потска кодов символов в

таблице, мы можем сразу ввести строку символов в текст программы.

Ассемблер позволяет это сделать с помощю оператора DB. В поле

Microsoft (R) Macro Assembler Version 5.00 10/31/88 22:30:38

Фиг. 2.15 Определение байтов для текста ASCII Page 1-1

1 PAGE ,132

2 TITLE Фиг. 2.15 Определение байтов для текста ASCII

3

4 0000 9D E2 AE 20 E1 AE AE DB 'Это сообщение',10,13

5 A1 E9 A5 AD A8 A5 0A

6 0D

7

8 END

Фиг. 2.15 Определение байтов для текста ASCII

операндов мы вместо ввода чисел (кодов) помещаем заключенную в

кавычки строку символов. Ассемблер подберет соответствующие

значения кодов и поместит их в память - каждый символ в отдельный

байт. Так ассемблер может работать только с символами в диапазоне

от 20Н до 0FFH. в диапазоне от 0Н до 1FH символы должны вводиться

в программу в виде чисел, а не ограниченной кавычками строки. Это

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

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

Пример на Фиг. 2.15 показывает создание 15 байт данных в

программе. Первые 13 байтов соответствуют 13-ти символам текстовой

строки заключенной в кавычки. Первый байт имеет значение 9DH,

второй 0E2H и т.д. Последние два байта в 17-ти байтном сообщении -

это коды возврата каретки и прогона строки. Если мы отправим это

17-байтное собщение на принтер, он напечатает заключенный в кавычки

текст. Управляющие символы предписывают принтеру перейти после

этого на следующую строку документа.


Untitled

Принципы работы компьютера

Ниже описаны некоторые основные принципы работы компьютера. Эти

принципы важны для понимания 8088 и его работы. Все, что говорится

в этом разделе, верно и для других компьютеов. В соответствующих

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

8088, хотя основная часть сведений, относящихся только к 8088,

появится в следующей главе.

Работа компьютера состоит в выборке команд из памяти и их

выполнении. Каждая команда проходит через этот двухшаговый

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

регистров процессора. Этот регистр называют счетчиком программы

или указателем команды. Он является "маркером" текущей

выполняемой команды. То место в памяти, на которое указывает этот

регистр, содержит следующую команду, которую должен будет выбрать и

выполнить процессор. Процессор читает в этом месте один или

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

Затем процессор увеличивает указатель в соответствии с числом

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

команду. Этот цикл повторяется для всех без исключения команд.

Нормальное выполнение программы является последовательным, от одной

команды к другой, расположенной следом.

Процессор может изменить последовательный цикл выборки-исполне-

ния при выполнении команды, которая помещает в указатель команд но-

вое значение. Такие команды являются командами передачи

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

область. Инструкция перехода или выбора варианта является самым

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

задает адрес команды, которая должна выполняться следующей. Цикл в

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

на Фиг. 2.16 на языке ассемблера 8088 показывает сохранение одного

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

перехода в конце цикла приводит к повторному выполнению его команд.

Microsoft (R) Macro Assembler Version 5.00 11/2/88 21:30:42

Фиг. 2.16 Команда перехода Page 1-1

1 PAGE ,132

2 TITLE Фиг. 2.16 Команда перехода

3 0000 CODE SEGMENT

4 ASSUME CS:CODE

5

6 0000 MEM LABEL BYTE

7

8 0000 FIG2_16:

9 0000 2E: C6 87 0000 R 00 MOV MEM[BX],0

10 0006 43 INC BX

11 0007 EB F7 JMP FIG2_16

12

13 0009 CODE ENDS

14 END

Фиг. 2.16 Команда перехода

Обратите внимание, что в команде JMP для определения сле-

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

"FIG2_16". Это - еще одна из возможностей ассемблера. Хотя в

машинном языке требуется абсолютный адрес следующей команды, язык

ассемблера требует лишь программно определенную метку. Ассемблер

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

команду машинного языка.

Команда перехода не обязательно должна быть безусловной как в

приведенном примере. 8088 располагает множством команд перехода,

которые выполняются в соответствии с некоторым кодом условия.

Значение кода условия устанавливают другие команды при их

выполнении процессором. Условие, указанное в команде условного

перехода, сравнивается с кодом условия, сохраненного в регистре

состояний. Если условия совпадают, то процессор переходит по

указанному адресу. В противном случае процессор игнорирует

переход, и выполнение программы продолжается в обычном

последовательном порядке. На Фиг. 2.17 предыдущий пример изменен.

Цикл в этом примере прерывается, когда значение BX становится

равным 1000.

На Фиг. 2.17 появляется новая команда сравнения, которая

устанавливает коды состояния. Команда условного перехода (JNE

(Jump if Not Equal) переход, если не равны) выполняет переход на

"FIG2_17", если условие выполнено. Если условие не выполняется,

8088 выполняет команду, следующую за условным переходом, в данном

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

Microsoft (R) Macro Assembler Version 5.00 11/2/88 22:31:33

Фиг. 2.17 Команда условного перехода Page 1-1

1 PAGE ,132

2 TITLE Фиг. 2.17 Команда условного перехода

3 0000 CODE SEGMENT

4 ASSUME CS:CODE

5

6 0000 MEM LABEL BYTE

7

8 0000 FIG2_17:

9 0000 2E: C6 87 0000 R 00 MOV MEM[BX],0

10 0006 43 INC BX

11 0007 81 FB 03E8 CMP BX,1000

12 000B EB F3 JMP FIG2_17

13

14 000D CODE ENDS

15 END

Фиг. 2.17 Команда условного перехода

значения данных в процессе выполнения программы. Ход выполнения

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

проверки.


Untitled

Процедуры

Другая форма команды перехода - переход к подпрограмме. Некоторая

последовательность команд образует процедуру. Эта последователь-

ность реализует функцию, которая выполняется в программе

неоднократно и в разных местах. Вместо многократного повторения

этой последовательности во всех необходимых местах, программист

помещает эти команды в одном месте. Такая часть программы

становится подпрограммой или процедурой.

Каждый раз как в программе потребуется выполняемая процедурой

функция, она передает управление в эту процедуру командой перехода

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

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

Команда вызова сохраняет адрес следующей за ней команды. Этот

адрес, называемый адресом возврата, указывает дорогу обратно к

исходной последовательности команд.

Давайте посмотрим, как работает вызов процедуры. Пусть, напри-

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

местах 32-битовые числа. У микропроцессора 8088 нет команд,

которые выполняли бы такое сложение. Мы можем написать короткую

последовательность команд, которая будет выполнять сложение

32-битовых чисел. Эта часть программы будет процедурой.

Программист пишет эту подпрограмму точно также как любую другую

часть программы. Она является частью программы на языке

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

программист будет иногда сталкиваться с необходимостью сложить два

32-битовых числа. Вместо того, чтобы писать команды для выполнения

этого сложения, в программу включают вызов процедуры 32-битового

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

программы. Вызов этой процедуры производит впечатление мощной

команды 8088, так как один такой вызов выполняет 32-битовое

сложение.

При выполнении программы выозов процедуры выполняет не само

сложение с двойной точностью, а передачу управления соответствующей

процедуре. Процессор выполняет команды процедуры, реализующей

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

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

берет адрес, который был сохранен командой вызова и помещает его

обратно в указатель команд. Это заставляет программу вернуться к

команде, следующей за вызовом процедуры. Вызов процедуры как бы

временно отводит течение программы в русло процедуры. После

выполнения процедуры выполнение возвращается к основной программе.

Команды, которые обеспечивают выполнение процедуры - CALL и

RETURN. CALL - это переход на процедуру. CALL сохраняет текущее

значение указателя команд в специальном месте памяти. Это

сохраненное значение указателя команд является адресом возврата.

Команда RETURN читает сохраненное значение указателя команд, поме-

щает его в указатель команд процессора и возвращает управление в

точку, следующую за командой CALL. Пример на Фиг. 2.18 показывает

процедуру, вызываемую из двух различных точек программы.

Поскольку программа начинает свое выполнение с самого начала,

она сразу же попадает на команду A1. Команда CALL передает

управление в точку SOBROUTINE. Выполняя команду CALL, процессор

в том числе сохраняет адрес точки A2. После выполнения процедуры

команда RET (от английского return - возврат) восстанавливает

сохраненное значение A2. Управление возвращается к главной

программе. Дальше в главной прграмме выполняется CALL в точке A3,

что приводит к повторному выполнению подпрограммы. На этот раз

процессор сохраняет значение A4. После выполнения процедуры во

второй раз управление возвращается в A4. Обратите внимание, что

оба раза выполнялась одна процедура. В первый раз возврат после ее

Microsoft (R) Macro Assembler Version 5.00 5/11/80 16:25:59

Фиг. 2.18 Использование процедуры Page 1-1

PAGE ,132

TITLE Фиг. 2.18 Использование процедуры

0000 CODE SEGMENT

ASSUME CS:CODE

0000 E8 0008 R A1: CALL SUBROUTINE

0003 40 A2: INC AX

0004 E8 0008 R A3: CALL SUBROUTINE

0007 43 A4: INC BX

;----- Здесь программа продолжается . . .

0008 SUBROUTINE PROC NEAR

0008 B8 0000 MOV AX,0

000B BB 0000 MOV BX,0

000E C3 RET

000F SUBROUTINE ENDP

000F CODE ENDS

END

Фиг. 2.18 Использование процедуры

выполнения осуществлялся на A2, во второй раз - на A4.

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

множества различных мест и каждый раз правильно находоить точку

возврата.

Где же хранится адрес возврата во время выполнения процедуры?

Существует множество возможностей, но микропроцессор 8088

использует для хранения этого значения стек.


Untitled

Стек

Стек - это структура данных, которая используется для временного

хранения информации. Программа может поместить данные в стек

(PUSH) или забрать их оттуда (POP). Стековая структура данных

предполагает упорядочивание помещенных в него данных специальным

образом. Во всех случаях первым из стека извлекается то, что было

в нем сохранено последним. Такая организация хранения данных

сокращенно обозначается LIFO (last in, first out - последний

введенный первым выводится). Если мы поместили в стек сначала A,

затем B, то первое, что мы извлечем из него будет B. Следующая

команда извлечения (POP) вернет A. Информация возвращается из

стека в порядке, строго противоположном порядку ее помещения в

стек.

Стек противоположен очереди. Очередь - это обычная последова-

тельность, подобная очередям на почте или в магазине. Это

структура данных типа "первым вошел - первым вышел" (first in,

first out: FIFO). Тот, кто первым встал в очередь, первым и

покинет ее. Стек и очередь - очень разные вещи.

Компьютер снабжает стек зарезервированным участком памяти и

указателем, называемым указателем стека. Программа использует ука-

затель стека для того, чтобы фиксировать последние помещенные в

стек данные, в отличие от почты, где сами элементы очереди продви-

гаются вперед по мере движения очереди. В компьютере намного легче

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

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

выполнение операций POP и PUSH указатель стека соответственно

увеличивается или уменьшается.

Фиг. 2.19 дает пример стека. В части (a) изображен стек после

того как в него последовательно помещены значения A, B, C. Ука-

затель стека указывает на текущую вершину стека, в данном случае на

C. В части (b) в стек помещается еще одно значение: D. Операция

PUSH уменьшает указатель стека SP (от Stack Pointer - указатель

стека), который теперь указывает на новую вершину D. Указатель

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

Фиг. 2.19(c) показывает состояние стека после операции POP.

Этой операцией значение D было извлечено из стека. Команда POP

помещает извлеченное из стека значение в указанное место. Если в

части (c) выполнялась команда POP AX, то процессор поместил

значение D в регистр AX (это уже дополнительный аспект, который мы

обсудим в следующей главе). POP увеличивает указатель стека.

Теперь он указывает на новую вершину, C. Заметим, что элементы

извлекаются из стека по описанному принципу LIFO. Последним

помещенным в стек элементом был D и он же первым извлечен из стека.

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

теперь уже не является частью стека. Логическая граница стека

находится по адресу, хранящемуся в его указателе. В данном случае

вершина стека оказывается ниже ячейки со значением D.

На Фиг. 2.19(d) видно, что происходит с D при помещении в сетк

нового элемента E. Элемент E записывается на место D и становится

новой вершиной стека. Мораль из этой истории такова, что хотя

извлеченные из стека значения могут оставаться в памяти, полагаться

на это не следует.

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

і і і D і<ДД SP і D і і E і<ДД SP

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

і C і<ДД SP і C і і C і<ДД SP і C і

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

і B і і B і і B і і B і

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

і A і і A і і A і і A і

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

(a) (b) (c) (d)

Фиг. 2.19 Пример работы стека

В приведенном примере подразумевался принцип построения стека

процессора 8088. Указатель стека постоянно указывает на текущую

вершину стека. Операция PUSH уменьшает указатель стека, POP

увеличивает его. Стек растет в направлении уменьшения адресов в

памяти. Основание стека располагается в памяти по большему адресу,

чем его вершина. Если вы нарисуете изображения стека с наименьшим

адресом сверху, как на Фиг. 2.19, то вершина стека окажется в

верхней части рисунка.

Мы занялись обсуждением стека потому, что стек используется для

хранения адреса возврата из процедуры. Как это делается?

Каждая команда CALL вызывает как бы выполнению команды PUSH для

стека - сохраняет в стеке адрес возврата. Команда RET извлекает из

стека, подобно команде POP, адрес возврата и помещает его в

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

возврата потому, что это позволяет вкладывать процедуры одна в

другую. Что такое вложение? На Фиг. 2.20 показан пример

вложенных процедур.

На Фиг. 2.20 показана абсурдная программа, которую мы

используем как пример вложения процедур. Часть (a) показывает

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

процедура MAIN, она вызывает процедуру SUBROUTINE_A. В это время

процессор сохраняет в стек адрес возврата. Часть (b) показывает

адрес возврата 103 помещенным в стек. SUBROUTINE_A в процессе

своего выполнения вызывает SUBROUNINE_B. Команда этого вызова

сохраняет адрес возврата 108 в SUBROUNINE_A. Когда SUBROUNINE_B

заканчивается, команда возврата извлекает из стека значение 108,

как показано в части (d). Процессор помещает это значение в

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

листинге ассемблера, адрес 108 относится к SUBROUNINE_A и следует

сразу за вызовом SUBROUNINE_B. Затем SUBROUNINE_A заканчивается.

Команда возврата извлекает из стека значение 103 для указателя

команд. Адрес 103 относится к процедуре MAIN и следует сразу за

вызовом SUBROUNINE_A.

Наиболее важным в примере на Фиг. 2.20 является вложение

процедур. Одна процедура может вызывать другую, а команда возврата

всегда обеспечивает правильный возврат управления. Единственное,

Microsoft (R) Macro Assembler Version 5.00 11/10/88 23:18:17

Фиг. 2.20 Вызов вложенных процедур Page 1-1

PAGE ,132

TITLE Фиг. 2.20 Вызов вложенных процедур

0000 CODE SEGMENT

ASSUME CS:CODE

0100 ORG 100H

0100 E8 0104 R MAIN: CALL SUBROUTINE_A

0103 40 INC AX

;----- Здесь главная процедура продолжается . . .

0104 SUBROUTINE_A PROC NEAR

0104 43 INC BX

0105 E8 0109 R CALL SUBROUTINE_B

0108 C3 RET

0109 SUBROUTINE_A ENDP

0109 SUBROUTINE_B PROC NEAR

0109 41 INC CX

010A C3 RET

010B SUBROUTINE_B ENDP

010B CODE ENDS

END

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

і і і і і108іДД SP і108і

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

і і і103іДД SP і103і і103іДД SP

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

іxxxіДД SP іxxxі іxxxі іxxxі

ГДДДґ ГДДДґ ГДДДґ ГДДДґ

(a) (b) (c) (d)

Фиг. 2.20 Вызов вложенных процедур

что ограничивает глубину вложения процедур (сколько процедур может

вызывать другие) - это размер стека. Пока в стеке имеется место

для очередного адреса возврата, можно производить вложенный вызов

процедуры. Структура стека LIFO дает гарантию правильной

последовательности возвратов.

Пример программы на Фиг. 2.20 показывает также использование

еще одной псевдооперации ассемблера - PROC. Оператор PROC

используется ассемблером для идентификации процедур. Как мы

дальше увидим, ассемблер должен знать, как далеко располагается

процедура и как возвращаться к точке ее вызова. Операнд NEAR

определяет процедуру как расположенную в пределах легкой

досигаемости вызывающей программы. Мы еще вернемся к оператору

PROC, когда будем обсуждать реальное действие команд CALL и JMP.


Прерывания

Механизм прерываний - существенная часть любой вычислительной сис-

темы. Как мы увидим, он важен и для IBM PC. Структура прерываний

предоставляет эффективное средство для связи устройств вводоа-выво-

да с процессором. Нам прерывания интересны потому, что управление

прерываниями - прерогатива программирования на языке ассемблера. В

языках высокого уровня отсутствуют средства для работы с

прерываниями на машинном уровне.

Прерывания обычно вызываются внешними устройствами. Прерывание

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

ответить внешнему устройству. В IBM PC клавиатура посылает сигнал

прерывания всякий раз при нажатии любой клавиши. Прерывание

клавиатуры заставляет процессор прекратить текущую деятельность и

считать набранный на клавиатуре символ.

Легко понять, за что прерывания получили свое название. Преры-

вание сигнализирует о необходимости "прервать" текущее действие

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

постоянного контроля за внешними устройствами. Если бы, например,

клавиатура пользователя не вызывала прерываний, то процессор был бы

вынужден непрерывно проверять клавиатуру, чтобы обнаружить нажатие

клавиши. Каждая написанная для компьютера программа была бы

вынуждена делать одно и то же, и им пришлось бы очень часто

тестировать клавиатуру. Но наличие прерываний снимают это

требование, и программа может выполняться без постоянного

тестирования клавиатуры. Каждый раз, как клавиатура получает

какую-либо информацию, она сигнализирует об этом процессору. После

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

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

Работа 8088 с прерываниями во многом напоминает его обращение с

процедурами. Прерывание не может прекратить работу процессора во

время выполнения команды. Сначала 8088 закончит выполнение текущей

команды, но следующую уже проигнорирует. Вместо ее выполнения про-

цессор действует так, как будто следующая команда была вызовом про-

цедуры. Он сохраняет адрес очередной команды в стеке и переходит в

специальную процедуру, которую называют программой обработки

прерываний. Эта процедура содержит команды для работы с вызвавшим

прерывание устройством. В случае с клавиатурой программа обработки

прерывания считывает символ и сохраняет его для дальнейшего

использования. После того как она закончит работу с устройством,

происходит возврат в точку прерывания. Процессор извлекает из

стека адрес возврата и продолжает выполнение программы как будто

ничего не случилось.

Поскольку прерывание вызывается внешним устройством, оно может

произойти в любой момент выполнения программы. Программа не может

предпринять каких-либо действий чтобы подготовиться к прерыванию,

так как не может предвидеть, когда пользователь нажмет на клавишу

клавиатуры. Отсюда следует, что прерывание не должно изменять

данные в прерываемой программе. Если прерывание иозменит

какое-либо значение в программе, то она не сможет нормально

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

В ходе прерывания 8088 автоматически сохраняет некоторые уста-

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

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

которые она может изменить во время своего выполнения. Эти данные

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

прерванную программу, программа обработки прерывания должна вернуть

измененным данным те значения, которые они имели в момент

прерывания. Факт возникновения прерывания должен остаться

"невидимым" для выполняемой программы.

Поскольку сигнал прерывания могут посылать процессору многие

устройстваэ, 8088 имеет механизм ориентации прерываний. Это озна-

чет, что 8088 определяет, какое устройство вызвало прерывание и пе-

редает управление программе обработки прерывания, соответствующей

этому устройству. Процессор атоматически управляет веторизацией

зап росов на прерывания. Программе обработки прерывания не

требуется перед обработкой прерывания определять, какое устройство

его вызвало. Это сокращает время реакции на прерывание и упрощает

программирование прерываний.

В программамах встречаются такие участки, выполнение которых не

может быть прервано. Например, это может быть кусок программы,

который должен выполняться очень быстро, чтобы закончить выполнение

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

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

программа должна иметь возможность задержать или предотвратить

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

прерываний во время выполнения таких критических участков. После

прохождения этих участков программа должна восстановить способность

системы прерываний вызывать прерывания. Программа не может

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

запросившим прерывание, может произойти какая-нибудь неприятность.

Если прерывание клавиатуры не считает символ до того как оператор

нажмет другую клавишу, второй симол может быть потерян. В 8088

имеется возможность блокировать все внешние прерывания. IBM PC

имеет более развитую возможность выбирать, каким из устройств можно

вызывать прерывание, а каким нет. Программа может использовать эту

возможность для выбора наиболее важных устройств, которым можно

разрешить прерывания, а менее критическим запретить. Способы

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


Untitled

Глава 3

Микропроцессор 8088

8088 входит в семейство микропроцессоров, которое включает микро-

процессоры 8088, 8086 и математический сопроцессор 8087.

Микропроцессор 8086 является старшим братом 8088. Он располагает

набором команд, идентичным командам 8088. Однако выполнение на нем

более эффективно чем на 8088, потому что его шина, основное

средство сообщения между процессором и памятью, имеет в два раза

большую пропускную способность. 16-битовая шина позволяет

пересылать в процессор целое слово за одну операцию. 8-битовая шина

8088 требует для выполнения той же самой задачи уже два цикла

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

два процессора идентичны. Программа, написанная для 8088, на 8086

будет выполняться точно также, разве что быстрее. Очень трудно

написать программу, которая могла бы определить, на каком из этих

двух процессоров она выполняется, не обращаясь к хронометражу с

привлечением внешних средств.

Математический сопроцессор 8087 расширяет набор команд 8086 и

8088. Он разделяет обработку потока команд с ведущим процессором

8088 или 8086 и выполняет те команды, которые связаны с арифметикой

высокой точности или с операциями над числами с плавающей точкой.

8087 может быть установлен на IBM PC. В главе 7 математический

сопроцессор будет описан более подробно.


Untitled

Модель программирования 8088

Для того, чтобы понять 8088 и научиться программировать для него,

мы начнем с его внутреннего устройства. Внутри процессора имеются

специальные ячейки памяти, называемые регистрами. В регистрах можно

ЪДДДДДДДДДДДДДВДДДДДДДДДДДДДї

AX і AH і AL і

і і і

BX і BH і BL і

і і і Регистры общего

CX і CH і CL і назанчения

і і і

DX і DH і DL і

АДДДДДДДДДДДДДБДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і SI і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і DI і Адресные регистры

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і BP і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і SP і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і IP і Регистры управления

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FLAGS і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і CS і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і DS і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ Сегментные регистры

і ES і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SS і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Фиг. 3.1 Регистры 8088

сохранять данные-операнды или адреса памяти. Поскольку регистры

расположены внутри самого процессора, он имеет очень быстрый доступ

к находящимся в них данным, намного более быстрый, чем к данным в

памяти. Если в программе требуется быстрыый доступ к какой-либо

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

Набор регистров 8088 состоит из нескольких групп. Все регистры

8088 показаны по группам на Фиг 3.1.


Untitled

Регистры общего назначения

В первую группу входят регистры, используемые в основном для вы-

числений. Все эти общие регистры имеют размер 16 бит, но программа

может работать и со старшими или младшими 8-ю битами каждого регис-

тра отдельно. Например. регистр AX состоит из 16 бит. Программа мо-

жет обратиться к старшим 8 битам AX как к регистру AH, а младшие 8

бит образуют регистр AL. То же самое верно для регистров BX, CX и

DX. Программа может рассмматривать эту группу регистров как четыре

16-битовых, восемь 8-битовых или некоторую комбинацию 8- и 16-бито-

вых регистров.

Основное назначение группы общих регистров - хранить операнды.

Общие регистры характерны способностью хранить как слово, так байт

данных. Однако эти регистры при выполнении определенных операций

имеют специальное назначение, либо они могут иметь особые

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

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

функций этих регистров.

Регистр AX соответствует сумматору более ранних процессоров.

Хотя 8088 значительно более универсален, например, в части

арифметических операций, чем ранние машины вроде процессора 8080,

регистр AX имеет несколько специальных функций. Фирма Intel

оптимизировала набор команд 8088, привлекая к выполнению некоторых

операций регистр AX. Например, существуют непосредственные

операции, в которых один из операндов подразумевается самой

командой. Непосредственные операции с регистрами AX и AL (16- и

8-битовый сумматоры соответственно) обычно требуют более короткой

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

общего назначения. А меньший размер команды позволяет получать

более компактные и быстродействующие программы.

Регистр BX служит как регистром для вычислений, так и адресным

регистром. При использовании в качестве 16-битового регистра он мо-

жет служить для определения адреса операнда. Способы адресации для

микропроцессора 8088 выделены в следующий пункт.

Набор команд 8088 использует регистр CX в качестве счетчика к

некоторым иструкциям. Эти команды используют находящееся в CX зна-

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

Регистр DX служит как расширение аккумулятора для

многоразрядных операций умножения и деления. В этих 32-битовых

операциях участвут одновременно регистры AX и DX.


Untitled

Регистры адресации

В процессоре 8088 имеется четыре 16-битовых регистров, которые мо-

гут принимать участие в адресации операндов. Один из них является

одновременно регистром общего назначения - регистр базы BX. Другие

Microsoft (R) Macro Assembler Version 5.00 11/17/88 21:42:25

Фиг 3.2 Адресация операндов Page 1-1

PAGE ,132

TITLE Фиг 3.2 Адресация операндов

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,SS:CODE

0123 ORG 123H

0123 ???? OPND DW ?

0200 ORG 200H

0200 43 INC BX ; Увеличение регистра

0201 FF 06 0123 R INC OPND ; Увеличение ячейки памяти

0205 FF 07 INC WORD PTR [BX] ; Увеличение слова памяти

0207 FF 87 0123 R INC [OPND+BX] ; Смещение плюс индекс

020B FF 84 0123 R INC [OPND+SI]

020F FF 84 0123 R INC OPND[SI] ; Другой способ - тот же результат

0213 FF 85 0123 R INC OPND + [DI]

0217 FF 86 0123 R INC [OPND] + [BP]

021B FF 00 INC WORD PTR [BX + SI] ; База плюс индекс

021D FF 03 INC WORD PTR [BP] + [DI]

021F FF 81 0123 R INC [OPND + BX + DI] ; База + индекс + смещение

0223 FF 82 0123 R INC OPND[BP][SI]

0227 CODE ENDS

END

Фиг 3.2 Адресация операндов

три - это указатель базы (Base Poiner - BP), индекс источника

(Source Index - SI) и индекс назначения (Destination Index - DI).

Программа может использовать регистры BP, SI и DI в качестве

16-битовых операндов, но отдельные байты в них недоступны. Основное

назначение этих регистров - поставлять 16-битовые значения,

используемые в формировании адресов операндов.

Каждая команда в 8088 задает для выполнения некоторую операцию.

Разные операции могут иметь от нуля до двух операндов. Например,

команде разрешения прерываний Set Interrupt (STI) операнды не

нужны. Команда увеличения (INC) требует, чтобы программист опреде-

лил один операнд, - регистр или ячейку памяти, - который должен

быть увеличен на еденицу. Команда ADD (сложение) должна иметь два

операнда - складываемые величины. Некоторые команды неявно задают

расположение операнда, но большинство команд позволяют программисту

выбирать в качестве операнда регистр или ячейку памяти. Если в

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

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

у вас есть много различных способов.

Хорошим примером служит команда INC. Она имеет единственный

операнд. На Фиг. 3.2 изображен листинг ассемблера с несколькими

различными вариантами команды INC. Первая команда INC называет в

качестве операнда регистр BX. Заметим, что в поле операндов в этом

случае кроме BX ничего нет. Остальные команды в примере указывают в

качестве операнда ячеку памяти. И хотя в них иногда появляется имя

регистра BX, он не является здесь самим операндом, а используется

для определения его адреса.


Untitled

Прямая адресация

Простейший способ определить операнд в памяти - даать имя ячейке

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

ссылки на соответствующий участок памяти. Вот как этот метод

используется в команде

INC OPND

В примере команда ассемблера

OPND DW ?

объявляет OPND участком памяти длиной в слово. Когда программа

ипользует OPND в качестве операнда, ассемблер помещает адрес OPND в

машинную команду. В данном примере вы можете видеть адрес 0123 как

часть команды в объектном коде. Этот способ называют прямой

адресацией, поскольку команда непосредственно в себе содержит адрес

операнда.


Untitled

Вычисление адресов

В способе прямой адресации памяти привлекательна простота, но во

многих случаях программа вынуждена вычислять действительный адрес в

памяти. Простейший пример - операции с вектором, одномерным

массивом. В программе на языке Фортран такую структуру можно

создать оператором

DIMENTION OPND(20)

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

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

разным элементам в соответствии со значением индекса, например,

OPND(5). Написание программы на языке ассемблера требует от

программиста вычисления местонахождения пятого элемента в поле

данных OPND. Затем программа может использовать полученное значение

для прямой адресации. Однако, в случае с выражением OPND(I), где I

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

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

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

Набор команд 8088 допускает несколько способов определения ис-

полнительного адреса (Effective Address - EA) операнда. Эти способы

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

предназначено для облегчения задачи определения исполнительных

адресов. Благодаря правильному выбору способа адресации программист

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

Формула для определения I-го элемента массива OPND такова:

EA = адрес базы OPND + (I * длина),

где длина - это длина каждого элемента массива. В данном примере

OPND - массив, состоящий из слов, поэтому каждый элемент в нем

имеет длину 2 байта. Тогда формула выглядит так:

EA = адрес базы + (I * 2)

Для вычисления этого адреса требуется по крайней мере один ре-

гистр, содержащий адрес операнда. Программа может вычислить испол-

нительный адрес, оставив результат в одном из регистров. Тогда,

вместо указния адреса в самой команде INC, можно просто указать,

какой из регистров его содержит.

Для хранения адресов операндов программа может использовать лю-

бой из четырех адресных регистров. Так, в нашем примере программа

добавляет к адресу базы 2*I и помещает результат в регистр BX. Со-

ответствующий элемент вектора в этом случае будет увеличиваться ко-

мандой

INC WORD PTR [BX]

Выражение [BX] сообщает ассемблеру, что регистр BX содержит ад-

рес операнда, а не является операндом сам по себе. Скобки [ и ],

заключающие какое-либо значение, указывают ассемблеру, что это

значение - адрес. Другая часть операндного выражения, WORD PTR,

требуется ассемблеру для информации, что операнд является

переменной типа WORD (слово). Далее мы обсудим оператор PTR более

подробно.


Untitled

Адресация через базу и смещение

Поскольку вычисление для операнда адреса, состоящего из базы и

индекса, встречается довольно часто, среди способов адресации 8088

есть такие, что позволяют автоматически производить идексирующее

сложение. Вместо выполнения всех вычислений, программа может

определить только величину 2*I и поместить ее в регистр BX. Команда

INC [OPND + BX]

вычисляет исполнительный адрес через сложение адреса базы OPND со

значением индекса в BX. Этой командой достигается тот же самый ре-

зультат что и в предыдущем случае, но меньшим числом команд. Обра-

тите внимание, что в этой команде ассемблеру не требуется подсказка

WORD PTR, потому что ассемблер уже знает, что OPND является пере-

менной типа WORD. Оператор PTR требуется только в тех случаях, ког-

да ассемблер не может определить тип операнда.

Любой из четырех адресных регистров может быть использован в

качестве индекса при базе. Фиг. 3.2 показывает возможные способы

адресации через базу и индекс. Вы видите, что ассемблер допускает

несколько способов записи операции адресации. В группе, состоящей

из пяти команд на Фиг. 3.2, во всех командах адрес базы OPND

складывается с указанным рядом индексным регистром.

Надо отметить, что в команде, содержащей базисный адрес,

регистр не обязательно должен содержать именно значение индекса.

Действительно, поскольку BX называется базисным регистром, кажется

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

примера предположим, что программа использует множество разных

векторов с одинаковой длиной и размером элементов. Такую структуру

может иметь, например, классный журнал, в котором векторам

соответствуют наборы оценок за каждую контрольную работу.

Программа, вычисляющая оценку пятого ученика в классе по I-й работе

будет иметь уже известное значение индекса (5) а базу (вектор

данной контрольной работы) - вычисляемую в ходе выполнения

программы.

Индексный регистр может содержать как адрес базы вектора, так и

значение индекса в векторе. Поскольку константное значение в

команде может оказаться и базой и индексом (или вовсе чем-нибудь

известным только программисту), то это значение называют смещением.

Оно соответствует расстояню или смещению от адреса в регистре до

исполнительного адреса, по которому происходит обращение.


Untitled

База + индекс + смещение

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

вычисляемым индексом. Как показано на Фиг.3.2, программа может

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

Для формирования исполнительного адреса в команде могут сочетаться

любой из регистров базы (BX и BP) с любым из индексных регистров

(SI и DI). В программе может быть также указано смещение,

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

адресации обладает максимальной гибкостью, так как позволяет

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

значение. Такая возможность не всегда требуется, но доступна в

любой момент.

Пример с классным журналом демонстрирует случай, когда програм-

ма может вычислять и адрес базы, и индекс вектора. Для того, чтобы

определить отметку I-го ученика за J-ю контрольную, потребовалось

бы установить адрес базы на J-й вектор и индексировать его по I-му

элементу.

На рис.3.3 приведена сводка восьми различных способов

адресации, возможных в микропроцессоре 8088. В команде могут

использоваться любые из четырех адресных регистров и смещение либо

комбинация базисного регистра и индексного вместе со смещением.

Смысл колонки, обозначенной R/M, будет объяснен позже.

R/M Адрес операнда

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

000 [BX + SI + СМЕЩЕНИЕ]

001 [BX + DI + СМЕЩЕНИЕ]

010 [BP + SI + СМЕЩЕНИЕ]

011 [BP + DI + СМЕЩЕНИЕ]

100 [SI + СМЕЩЕНИЕ]

101 [DI + СМЕЩЕНИЕ]

110 [BP + СМЕЩЕНИЕ]

111 [BX + СМЕЩЕНИЕ]

----------------------------- Фиг. 3.3 Способы адресации 8088

В наборе команд 8088 поле смещения для адресации оптимизирова-

но таким образом, чтобы минимизировать кличество треуемых байт. Ко-

манда может не содержать поле смещения, когда смещение равно нулю.

Если смещение находится в диапазоне от -127 до 127, то для него

достаточно одного байта. Когда же для его обозначения требуется

полное 16-битовое поле адреса, то поле смещения будет занимать два

байта. Таким образом, поле смещения может по необходимости иметь

длину 0, 1 или 2 байта. Когда оно имеет длину один байт, двоичное

число перед форимроваием адереса получает распостранение знака. Это

означает, что процессор перед выполнением сложения помещает старший

бит смещения в старшие 8 бит 16-битового значения. Это позволяет

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

здесь то, что ассемблер сам определяет нужную длину и выбирает

правильную и наиболее короткую команду для выполнения этой работы.

Но не смотря даже на все эти возможности адресации, набор

команд 8088 допускает только по одному операнду памяти в одной

команде. Двухоперандная команда ADD позволяет складывать либо

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

сложить две ячейки памяти. Это и означает, что команда содержит

только один адрес памяти.


Untitled

Байт MOD R-M

Как же адресная информация передается микропроцессору в машинном

языке? 8088 использует почти для всех операций адресации байт

MOD-R/M (байт режима адресации и регистра/модификатора - прим.

перев.). Фиг.3.4 показывает формат этого байта команды. Байт

MOD-R/M следует за байтом кода операции и определяет один операнд

памяти команды 8088. Этот байт может вместо ячейки памяти указывать

и регистр. Такое единство структуры позволяет реализовать все

возможности адресации операндов.

ЪДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДї

і і і і

АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ

Режим Заданная Регистр-

команда модификатор

Режим Смещение

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

00 DISP=0, нет байтов смещения

01 -128<DISP<127, однобайтовое смещение

10 -32768<DISP<32767, двухбайтовое смещение

11 В поле r/m - регистр, а не адрес

Фиг. 3.4 Байт режима адресации и регистра-модификатора

Первые два бита байта MOD-R/M определяют выбранный способ ад-

ресации. Эти два бита определяют число байт смещения, которые

следуют за байтом MOD-R/M - ни одного, один или два. Последние 3

бита байта MOD-R/M определяют вид адреса - одну из восьми комбина-

ций базисных и индексных регистров. Это поле называется полем R/M -

полем регистра/модификатора. Это те самые 3 бита из колноки R/M на

Фиг.3.3, где показаны возможные комбинации при адресации. Значение

оставшихся 3-х бит в середине байта MOD-R/M зависят от конкретной

команды. Для команды с двумя операндами, вроде ADD, это поле

указывает регистр, являющийся вторым операндом. Для команды с одним

операндом, как INC, эти три бита обычно составляют часть самого

кода операции. 8088 не знает, что имеет дело с командой INC, пока

не расшифрует средние три бита байта MOD-R/M.

Этот же байт используется еще в паре специальных случаев. Если

в команде определен регистр, а не адрес памяти, то в поле режима

помещается код 11B, чтобы сообщить микропроцессору, что поле R/M

содержит код регистра, а не адрес памяти. Наконец, вы возможно

заметили, что в механизме работы с байтом MOD-R/M не предусмотрена

прямая адресация. 8088 рассматривает как прямую адресацию случай

типа [BP + СМЕЩЕНИЕ] при нулевом смещении. В этом случае поле

смещения имеет длину два байта, и в вычислении адреса не участвует

ни один регистр. Из-за этого особого случая доступ к ячейке памяти,

на которую указывает регистр BP, в машинном коде требуется

однобайтовое поле смещения с нулевым значением. Использование в той

же ситуации регистров BX, SI и DI не требует байта смещения. В

следующем пункте будет показано еще одно различие между адресацией

через регистры BP и BX.


Untitled

Физическая адресация

Все, что до сих пор говорилось об адресации, относится к генерации

так называемого смещения (offset) адреса. Смещение имеет 16-битовое

значение. 8088 сегментирует память таким образом, что можно адресо-

ваться к памяти большей чем 64K. В этом пункте бует показан способ

сегментауии 8088.

Поскольку размер слова в микропроцессоре 8088 равен 16 бит, для

него естественно генерировать адреса в 16 бит длиной. Это делает

доступными для прямой адрессации 2**16 элементов или 65 535 байт

памяти. Однако для некоторых программ 64K ячеек памяти

недостаточно. Поэтому фирма INTEL сконструировала 8088 для

адресации 2**20 байт или одного мегабайта памяти.

Для получения 20-битовой адресации требуется еще четыре бита к

имеющимся 16-ти. Добавочные 4 бита адресной информации берутся из

сегментных регистров. Сегментные регистры сами имеют размер 16 бит.

8088 комбинирует 16-битовый адрес смещения и 16-битовый регистр

сегмета как показано на Фиг.3.5. Процессор дополняет сегментный ре-

гистр 4-мя нулевыми битами, что составляет вместе полное 20-битовое

значение. К расширенному значению сегмента процессор добавляет ад-

рес сммещения, определяемый через вычисление адреса. 20-битовый ре-

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

ЪДДДДДДДДДї

і Сегмент і 0000

АДДДДДДДДДЩ

ЪДДДДДДДДДДДї

+ і Смещение і

АДДДДДДДДДДДЩ

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

ЪДДДДДДДДДДДДДДДДї

і20-битовый адресі

АДДДДДДДДДДДДДДДДЩ

Фиг.3.5 Вычисление адреса с сегментом и смещением

Каждая обращающаяся к памяти команда может сформировать только

16-битовый адрес операнда. В действительности процессор применяет

этот адрес внутри определенного сегмента. Фиг. 3.6 иллюстриирует

такой способ применения сегментации.

ЕДДДДДДДДДДДДДДДДДДґ ДДДДДВДДДДСегментный регистр

іі і і

іі і смещение

іі і і

Сегмент іГДДДДДДДДДДДДДДДДДДЕ ДДДДДБДДДДАдресуемая область

64К ГДДДДДДДДДДДДДДДДДДґ

іі і

іі і

іі і

ЕДДДДДДДДДДДДДДДДДДґ

і і

Фиг.3.6 Сегментация.

Начальный адрес сегмента всегда имеет нули в младших четырех

битах. Адрес с этим свойством имеет каждая шестнадцатая ячейка

памяти. Конструируя расположение данных в своей программе помните,

что сегмент всегда должен приходиться на такую 16-битовую границу.

Эти границы называются также границами параграфов.


Untitled

Сегментные регистры

INTEL 8088 имеет четыре сегментных регистра: CS,DS,SS и ES - для

кодового, данных-, стекового и дополнительного сегментов

соответственно. Это их обычное использование, но применение этих

регистров может именяться в соответствии с потребностями программы.

8088 использует регистр сегмента программы для идентификации

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

программу. В сочетании с указателем команд регистр CS используется

для указания текущей команды. Каждая выполняемая команда находится

в ячейке, на которую указывает пара регистров CS:IP.

Комбинация сегментного регистра с регистром смещения для

указания физического адреса записывается в виде сегмент:смещение,

например, CS:IP. Значение сегмента стоит перед двоеточием, смещение

- после. Такая нотация используется и для регистров, и для

абсолютных адресов. Вы можете писать такие адреса как CS:100,

DS:BX, 570:100, или 630:DI.

Регистр сегмента данных (DS) процессор использует для обычного

доступа к данным. Схемы адресации для операндов, которые мы

рассматривали в предыдущем пункте, дают 16-битовое смещение, и в

большинстве случаев для формирования исполнительного адреса процес-

сор комбинирует это смещение с ргеистром DS.

Регистр сегмента стека указывает на системный стек. Команды

PUSH, POP, CALL и RET управляют данными в стеке в позиции по адресу

SS:SP. Регистр SP - указатель стека - служит для определения

смещения в стеке. Кроме того, сегмент стека подразумевается по

умолчанию при адресации с использованием регистра BP. Это дает

доступ к данным в стеке с использованием в качестве указателя

регистра BP. В следующей главе есть пункт о стековых операциях,

который демонстрирует, каким образом адресация через BP упрощает

связь с данными в стеке.

Наконец, регистр дополнительного сегмента используется 8088 для

доступа к данным, когда требуется более одного сенмента. Обычной

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

памяти в другую. Между областями, находящимися не внутри одного и

того же блока памяти размером 64К, невозможно произвести обмен дан-

ными, используя единственный сегментный регистр. Имея в распоряже-

нии дополнительный сегментный регистр, программа, как показано на

Фиг. 3.7, может указать одновременно исходный и целевой сегменты.

Регистр DS указывает область исходных данных, а регистр ES -

і і

ГДДДДДДДДДДДДДДДДДДДДґ ДДДДД DS

Копировать і Сегмент - і

отсюда ДДДДДДДДґ і

і і источник і

і ГДДДДДДДДДДДДДДДДДДДДґ

і і і

і і і

і ГДДДДДДДДДДДДДДДДДДДДґ ДДДДД ES

і і Сегмент і

і і і

сюда ДДДДДДДДґ назначения і

ГДДДДДДДДДДДДДДДДДДДДґ

Фиг.3.7 Копирование из сегмента в сегмент

сегмент назначения. Для передачи данных существуют специальные

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

ES для указания исходного и целевого регистров.


Untitled

Предназначение сегментов

Каждый из сегментных регистров имеет свое, отмеченное выше назна-

чение. В некоторых случаях, однако, более удобна связь с данными

вне сегмента данных, например, с небольшой областью данных в

программе. В большинстве случаев программа работает с данными в той

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

требуется ссылка на локальную переменную, находящуюся в кодовом

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

изменять обычное использование сегментов. Фиг.3.8 показывает такую

органзацию программы.

ГДДДДДДДДДДДДДДДДДДДЕДДДДДД CS

і Программа и і

і і

і локальные данные і

і ГДДДДДД CS:LOCAL_VALUE

ГДДДДДДДДДДДДДДДДДДДґ

і і

і і

і і

ЕДДДДДДДДДДДДДДДДДДДЕДДДДДД DS

і Основная і

і і

і область даных і

і і

ГДДДДДДДДДДДДДДДДДДДґ

і і

Фиг. 3.8 Переназначение CS на локальные данные.

Вместо изменения значения регистра DS, чтобы он указывал на

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

что переменная расположена в сегменте кодов (Code Segment).

INC CS:LOCAL_VARIABLE

Это делается с помощью префикса "CS:". В машинном языке команда

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

обычной командой машинного языыка. 8088 понимает этот префикс пере-

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

Вместо регистра DS процессор использует для вычисления физического

адреса данных регистр CS. Одного префикса в команде всегда

достаточно, так как 8088 может адресоваться в ней не более чем к

одной ячейке памяти.

Для нормального обращения к данным команда может использовать

любой из четырех сегментных регистров. Регистр DS используется по

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

регистр, то используется DS. Помните, что при использовании в

адресных вычислениях регистра BP сегментом по умолчанию становится

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

сегментных регистров, указав его в адресном выражении. Некоторые

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

Это - команды обработки строк. Строковая команда определяет

использование регистров неявным образом, и оно не может быть

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

пользование сегментами.


Untitled

Оператор Segment

Решить проблему адресации сегментов поможет ассемблер. В одной из

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

составляющие ее сегменты. Кроме того специальные команды сообщают

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

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

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

связанную с регистром DS, но доступную через другой сегментный

регистр, то ассемблер сам сформирует правильный префикс. Это

позволяет программисту работать непосредственно с данными и текстом

программы, оставив ассемблеру работу по осуществлению адресации.

Объявление сегментов позволяет ассемблеру следить за тем, какие

сегменты доступны через сегментные регистры и определять возможные

ошибки. Например, в программе могут появиться переменные, которые

недоступны из-за того, что на сегмент этой переменной не указывает

ни один из сегментных регистров. Ассемблер квалифицирует это как

Microsoft (R) Macro Assembler Version 5.00 1/1/80 03:53:05

Фиг. 3.9 Сегменты Page 1-1

PAGE ,132

TITLE Фиг. 3.9 Сегменты

0000 DATA SEGMENT

0000 01 VAR1 DB 1 ; Переменная в сегменте DATA

0001 DATA ENDS

0000 BUFFER SEGMENT

0000 02 VAR2 DB 2 ; Переменная в сегменте BUFFER

0001 BUFFER ENDS

0000 CODE SEGMENT

0000 03 VAR3 DB 3 ; Переменная в сегменте CODE

ASSUME CS:CODE, DS:DATA, ES:BUFFER

0001 FE 06 0000 R INC VAR1 ; Переменная из сегмента DATA

0005 26: FE 06 0000 R INC VAR2 ; Переменная из сегмента BUFFER

000A 2E: FE 06 0000 R INC VAR3 ; Переменная из сегмента CODE

000F CODE ENDS

END

Фиг. 3.9 Сегменты

ошибку. Она возникает из-за того, что в программе не обеспечена

адресуемость. Это ограничение, но лучше обнаружить ошибку при

ассемблировании, чем во время работы программы.

Оператор SEGMENT определяет все сегменты, давая каждому из них

имя. Программа на Фиг. 3.9 демонстрирует определение нескольких

сегментов. В качестве имени сегмента может использоваться любое

допустимое имя переменной. Утверждение SEGMENT сообщает ассемблеру,

что все следующие команды и данные во время выполнения программы

будут находиться в этом сегменте. Оператор ENDS указывает конец

текущего сегмента. В этом утверждении тоже указывается имя сегмен-

та. Каждому утверждению SEGMENT должно соответствовать утверждение

ENDS. В противном случае ассемблер запутается и выдаст сообщение об

ошибке.


Оператор Assume

После того как сегменты в программе определены, ассемблер должен

узнать, как будут установлены сегментные регистры во время

выполнения программы. В примере на Фиг. 3.9 всего три сегмента, но

болшая программа может иметь намного больше. Располагая всего

четырьмя сегментными регистрами, большая программа может адресо-

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

необходимо сообщить, к каким именно сегментам происходит адресация

во время выполнения. Это делается с помощью оператора ASSUME

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

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

сегментом, на который тот в данный момент указывает.

Фиг. 3.9 иллюстрирует такие сегментные операции. В этом примере

имеется три сегмента: DATA, BUFFER и CODE. Имена для них выбраны

произвольно. Их выбирает программист, а для ассемблера они не имеют

значения. Например, вы можете назвать сегмент именем CODE, а

использовать его только для данных и наооборот. Лучше всего, конеч-

но, называть сегменты так, чтобы их имена имели какой-то смысл в

данной программе. В нашем примере сегменты DATA и BUFFER оба имеют

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

сегмент лишь с одной ячейкой памяти, но сейчас это служит для

примера. Если программа обращается к данным во многих участках

адресуемого в 8088 пространства, то ей требуется много определений

сегментов. Например, программа управления устройствами доступа IBM

PC может обращаться к памяти в системной области данных,

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

программа в любом другом месте. Каждая из этих областей является

сегментом и должна быть определена в программе.

Утверждение ASSUME на Фиг. 3.9 предписывает ассемблеру работать

с учетом следующей установки сегментных регистров: регистр CS со-

держит начальный адрес сегмента CODE, регистр DS указывает на DATA,

а регистр ES определяет сегмент BUFFER. Утверждение ASSUME

(полагать, считать - прим. перев.) означает именно то, что оно

предписывает ассемблеру. Ассемблер обрабатывает исходный текст

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

указано в этом утверждении. Установка сегментных регистров, сделан-

ная в этом утверждении, остается при ассемблировании в силе пока

другое такое же утверждение не определит новые установки. Ассемблер

обрабатывает эти утверждения последовательно, даже если программа

ветвится и закручивается в циклы. Утверждение ASSUME остается в

силе, пока ассемблер не встретит при последовательном просмотре

программы следующее. Заметим, что в утверждении ASSUME не обязано

определять все сегментные регистры. В нашем примере не объявлен

регистр SS. На практике содержимое сегментного ргистра может быть

временами и неизвестно в программе. В этих случаях утверждение

ASSUME должно указывать сегмент NOTHING. Например, утверждение

ASSUME ES:NOTHING

сообщает ассемблеру, что программа не знает, куда указывает допол-

нительный сегментный регистр. Поскольку значение регистра неизвест-

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

Важно отметить, что утверждение ASSUME не генерирует команд ма-

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

регистры установлены в соответствии с указанной в этом утверждении

информацией. Добиться правильное установки сегментов - забота

программиста. Аналогично, ассемблер не может проверить, что утверж-

дение ASSUME при выполнении будет соответствовать содержимому

сегментных регистров. Из-за того, что программа может прийти к

любому конкретному ASSUME множеством разных путей, за его

корректность отвечает программист. В нашем примере предполагается,

что сегментные регистры устанавливаются до выполнения данного кус-

ка программы. Если они установленыы неверно, то программа будет вы-

полняться неправильно даже если ассемблирование прошло успешно.

Первая команда увеличивает значение VAR1, находящейся в сегмен-

те данных. Ассемблер полагает, что регистр DS указывает на сегмент

DATA в соответствии с утверждением ASSUME. Поскольку регистр DS

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

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

этой инструкцией 4-байтовая машинная команда не содержит сегментно-

го префикса.

Вторая команда определяет переменную VAR2, которая находится в

сегменте названном BUFFER. Программа сообщила ассемблеру, что

дополнительный сегментный регистр указывает на сегмент BUFFER. Для

увеличения VAR2 ассемблер генерирует четырехбайтовую команду машин-

ного языка, но ей предшествует команда с однобайтовым префиксом,

которая отменяет использование регистра DS в этой команде.

Префиксный байт 26H говорит процессору использовать при создании

20-битового адреса памяти регистр ES. В колонке объектных кодов на

листинге ассемблер отмечает префиксную команду двоеточием.

Третья команда изменяет переменную VAR3 в сегменте CODE.

Утверждение ASSUME связывает этот сегмент с регистром CS. Ассемблер

автоматически генерирует соответствующий префикс переназначения

сегмента. В данном случае префикс 2EH предписывает процессору

использовать при вычислении испольнительного адреса регистр CS.

Вначале утверждение ASSUME покажется излишеством. В первое

время при написании программы естественно забывать о его примене-

нии. Ассемблер выдаст массу сообщений об ошибках чтобы помочь вам в

вашей забывчивости. Но при достаточном опыте, утверждение ASSUME

помогает программисту ассемблера сосредоточиться на структурах дан-

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

сегментные регистры для адресации требуемых для программы данных.

Ассемблер облегчит бремя запоминания для каждой команды, где

располагаются данные и какой сегментный регистр должен быть

использован чтобы попасть к ним.

Программа может использовать утверждение SEGMENT для передачи

информации другим программам. Оператор SEGMENT может задавать

выравнивание сегмента в памяти, способ его комбинирования с други-

ми сегментами и имя его типа. Для программистов IBM PC особый

интерес представляют два вида выравнивания сегментов. Выравнивание

по параграфам (тип PARA) размещает начало сегмента с начала

параграфа - ячейки памяти, адрес которой в памяти кратен 16-ти .

Это означает, что первый байт сегмента будет иметь смещение 0

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

(тип BYTE), наоборот, размещает сегмент в любом месте памяти. В

этом случае сегментный регистр может и не указывать на первый байт

сегмента. В программе может потребоваться ненулевое смещение для

доступа к началу сегмента.

Различные способы связывания сегментов задает параметр типа

связи. Особенно это полезно при модульном программировании.

Описание PUBLIC приводит к объединению всех сегментов с одинаковыми

именами в один большой сегмент. Например, можно объединить все

сегменты кодов. Это приведет к соединению разных подпрограмм в их

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

- AT, при указании которого в сочетании с адресным выражением,

сегмент располагается по заданному абсолютному адресу. Такое

объявление необходимо при работе с данными в фиксированном месте,

например, с векторами прерываний в начале памяти.

Намного более полное описание описание утверждения SEGMENT

можно найти в справочном томе к макроассемблеру IBM PC. Некоторые

из возможностей опертора SEGMENT мы будем использовать далее в

примерах.


Untitled

Управляющие рагистры

Для операций управления в процессоре 8088 используются главным

образом три 16-битовых регистра. Это указатель стека (SP), указа-

тель команды (IP) и регистр флагов. Два регистра-указателя процес-

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

памяти. Регистр флагов содержит коды состояний, которые программа

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

Указатель команд

Указатель команд - это 16-битовый регистр, который содержит смеще-

ние очередной команды. Как показано в предыдущем пункте, Процессор

использует регистр CS совместно с регистром IP для формирования 20-

битового физического адреса. Регистр CS определяет сегмент выполня-

емой программы, а IP задает смещение.

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

регистра, существует несколько способов задать ход выполнения

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

нормальном выполнении программы. При извлечении процессором команды

из памяти и ее выполнении значение регистра IP увеличивается на

размер команды в байтах. Тепер пара CS:IP указывает на следующую

команду.

Для изменения порядка выполнения команд используются команды

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

дает переход внутри одного сегмента. Этот тип перехода называется

внутрисегментным или близким (NEAR) переходом. Спецификация для

него требует лишь новое значение для регистра IP. Регистр CS

остается без изменений. Близкий переход может передавать управление

только внутри текущего сегмента, поэтому дальность перехода

ограничена сверху 64K байт. Для перехода к боле отдаленному участку

программы требуется переход другого типа.

Переход второго типа называется межсегментным или далеким (FAR)

переходом. При этом переходе процессор назначает новые значения как

для IP так и для CS. Этот переход позволяет выполнять новую прог-

рамму расположенную в любом месте адресного пространства 8088. Но

для выполнения такого перехода команда должна определить новые

значения для обоих регистров - CS и IP. При прямом переходе это

требует пяти-байтовой команды : один байт на код операции и по два байта для регистров CS и IP.


Untitled

Указатель стека

Регистр указателя стека (SP) - это 16-битовый регистр, который

определяет текущее смещение вершины стека. Процессор использует

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

ния физического адреса. Процессор использует пару регистров SS:SP

для всех стековых ссылок, включая PUSH, POP, CALL и RETURN. Пара

SS:SP всегда указывает текущую вершину стека. Когда команда

помещает в стек слово, регистр SP уменьшается на два. При извлече-

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

растет в направлении меньших адресов памяти. Все стековые опера-

ции используют значения размером в слово. Отдельный байт не может

быть ни помещен в стек ни взят из него.

Процессор изменяет регистр SP для того чтобы отразить действия

над стеком. На регистр SS ни одна из стековых операций не влияет.

Программа устанавливает регистр SS независимо от какой либо опера-

ции PUSH (поместить в стек) или POP (извлечь), после чего он

указывает на сегмент стека. Это означает, что системный стек

ограничен в размере 64 килобайтами. Например, если программа

устанавливает регистр SS в 1000H, а регистр SP - на 0, то первое,

что будет помещено в стек, разместится по адресам 1000:FFFFH и

1000:FFFEH. Последовательно помещаемые в стек данные будут

размещаться в нем по все более младшим адресам, пока последий

объект не будет расположен в 1000:0001H и 1000:0000H. Если

программа поместит в стек в этот момент еще что-нибудь, то оно

разместится в 1000:FFFFH и 1000:FFFEH, то есть в первых байтах

используемых стеком. Поскольку стек теперь "замкнулся", ранее

помещенные в него данные разрушены.

При нормальном использовании длина стека не достигает 64K. Если

программа устанавливает стек в 512 байт, например, то она может

инициализировать пару SS:SP как 1000:2000H. Первая ячейка стека

будет 1000:01FFH, а последняя доступная - 1000:0000H. Если програм-

ма поместит в стек более 256 слов, уменьшение регистра SP вызовет

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

сначала сегмента стека. После этого программа будет помещать

стековые данные в область, которая в данном случае не

предназначалась для стека - начиная с 1000:FFFFH. При этом может

произойти одна из двух неприятных вещей. Стек может перекрыть коды

- 49 -

или данные программы и разрушить их, либо стековые данные будут

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

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

поэтому единственный выход - оставлять для стека как можно больше

места. В случае персонального компьютера IBM, использующего

дисковую операционную систему IBM рекомендуется размер стека не

меньше 128 байт. Это дает достаточно места для удовлетворения

потребностей в стеке различных служебных программ DOS и системы

вместе с обычными запросами самой программы.


Untitled

Регистр флагов

Последний управляющий регистр - 16-битовый регистр флагов. Этот

регистр содержит информацию, которая используется побитно, а не в

качестве 16-битового числа. Биты флагового регистра имеют значение

для процессора по-отдельности. Некоторые из этих бит содержат коды

условий, установленные последней выполненой командой. Программа

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

может тестировать коды условий и на основе полученных значений

выбирать последовательность выполнения. Другие биты в регистре

флагов показывают состояние процессора при выполнении текущей

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

Регистр флагов лучше всего описывать последовательно, по одному

биту. Структура регистра флагов показано на Фиг. 3.10. Заметим,

что здесь определены не все биты. Остальные зарезервированы, то

есть в настоящее время их значение не определено. Однако в даль-

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

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

неизменность значения зарезервированных бит.

номер бита 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

ЪДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДї

іXXіXXіXXіXXіOFіDFіIFіTFіSFіZFіXXіAFіXXіPFіXXіCFі

АДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДЩ

Фиг. 3.10 Регистр флагов

Все флаги младшего байта регистра устанавливаются арифметичес-

кими или логическими операциями процессора. Например, операция ADD

устанавливает все флаги в младшем байте в соответствии с ее

результатом. За исключением флага переполнения, все флаги старшего

байта устанавливаются специально предназначенными для этого коман-

дами. Флаги старшего байта отражают состояние процессора 8088 и

будут влиять на режим выполнения программы. Флаги в младшем байте -

это коды условия и могут быть испольлзованы в командах условных

переходов для изменения порядка выполнения программы.


Untitled

Флаг знака

Флаг знака (SF) показывает, является результат последней арифмети-

ческой операции положиельным или отрицательным. Установка бита

знака отражает значение старшего бита последнего результата. Если

последний результат отрицателен, то бит знака устанавливается в

еденицу, а если положительный или нулевой, то - в ноль.


Untitled

Флаг нуля.

Флаг нуля (ZF) показывает, что результат последней операции

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

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

числа. Результат будет нулевым, если значения идентичны и не

нулевым, если они различны.


Untitled

Флаг четности

Флаг четности (PF) показывает, является ли число едениц результата

последеней операции четным. Четность - это способ контроля значений

данных. Бит четности - это дополнительный бит, который проверяет

значения других бит. Программа может использовать флаг четности для

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

Четность используется в оперативной памяти IBM PC для контроля

сохраненных в ней данных. Такой контроль четности выполняется

непосредственно аппаратными средствами и не влияет на бит флага

четности.

Флаг четности устанавливается в "1", если результат операции

имеет четное число едениц и переустанавливается в "0", если число

едениц результата нечетно. В обычных арифметических и логических

операциях знак четности использкется мало.


Untitled

Флаг переноса

Флаг переноса (CF) служит для поддержания процессором многоразряд-

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

сложения или вычитания 8088 может работать с не более чем

16-битовыми числами. Однако в некоторых случаях приходится

манипулировать с числами превышающими 2**16. Например, для сложения

двух 32-битовых чисел программе придется сложить сначала младшие

части чисел, а затем - старшие. На Фиг 3.11 показано сложение

32-битовых чисел 22223333H и 44445555H.

В этом примере складываются сначала младшие 16-битовые згначе-

ния с получением результата 8888H. Затем складываются старшие 16-

второе сложение первое сложение

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

2222 3333

4444 5555

---- ----

6666 8888 Фиг. 3.11

--------------------------------------- 32-битовое сложение

битовые значения с результатом 6666H. 32-битовый результат равен

66668888H. Для получения 32-битового результата требуется два

16-битовых сложения. Для 48-битового числа потребуется уже три

16-битовых сложения и т.д. Для выполнения сложения программа должна

расчленять каждое большое число на 16-бтовые куски.

Однако, приведенный пример достаточно прост. Результат первого

16-битового сложения не влиял на второе. В общем же случае сложения

возможен перенос из одной позиции в другую. Когда процессор

выполняет слоожение двух 16-битовых чисел, он автоматически выпол-

няет переносы. Когда же программа складывает два 32-битовых числа,

как в нашем примере, то ей приходится запоминать перенос в первом

сложении и использовать его при сложении вторых 16-битовых

значений. На Фиг. 3.12 показано сложение чисел 22224444H и

3333EEEEH. В этом примере перенос от первого сложения отражается на

втором сложении.

втоорое сложение первое сложение

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

2222 4444

3333 EEEE

1 (перенос от первого)

---- -----

5556 13332

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

Фиг. 3.12 32-битовое сложение с переносом

Первое 16-битовое сложение 4444H и EEEEH дает результат 13332H.

Поскольку результат имеет длину 17 бит, он не может быть помещен в

16-битовый регистр. Флаг переноса регистра состояний примет этот

дополнительный бит арифметической информации. При втором 16-битовом

сложении складываются не только числа 2222H и 3333H, но и значение

флага переноса. Существует две формы команды сложения: команда ADD

складывает два 16-битовых числа, давая 17-битовый результат, а

команда сложения с переносом ADC складывает два 16-битовых числа и

значение флага переноса, давая также 17-битовый результат. В

примере на рис.3.12 для первой операции сложения использовалась

команда ADD, а для второй операции сложения команда ADC.

Оба приведенных примера используют флаг переноса для выполнения

арифметики повышенной точности. В первом примере после сложения

3333H и 5555H получился нулевой перенос; когда команда ADC

прибавляет значение переноса к числам 2222H и 4444H, получается

правильный результат 6666H. Во втором примере флаг переноса был

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

В случае арифметики еще большей точности программа может снова

и снова использовать в процессе сложения флаг переноса. Каждое

16-битовое сложение устанавливает флаг переноса в соответствии с

его результатом, а программа может сложить следующие по старшинству

части чисел с полученным значением флага переноса. В каждом случае

флаг переноса содержит семнадцатый бит предыдущего результата, и

программа должна использовать это значение при сложении следующих,

более старших, частей чисел.

Флаг переноса служит и для другой важной цели. Когда программа

выполняет вычитание, существует возможность заема из одной позиции

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

заема из одной части числа в другую.

Вычитать целые числа, в несколько слов длиной программа может

таким же путем, как и складывать. Сначала вычитаются младшие части

чисел, с получением 16-битового результата. Команда вычитания (SUB)

устанавливает флаг переноса, отражая заем. Следующее 16-битовое

вычитание программа выполняет с заемом. Команда вычитания с заемом

(SBB) наряду с обычным вычитанием вычитает из результата флаг

переноса. Как и при сложении программа может осуществлять вычитание

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

значения заема.

Микропроцессор 8088 трактует флаг переноса как истинный заем,

то есть если в результате вычитания появляется заем, микропроцессор

устанавливает флаг переноса равным 1. Он показывает, что программа

должна вычесть 1 из результата вычитания старших частей чисел. Если

заема нет, процессор сбрасывает флаг переноса на ноль. Это

означает, что программе не нужно вычитать 1 из старшей части числа.

Микропроцессор устанавливает флаг переноса как индикатор заема

при расширенном вычитании. Наряду с расширением точности программа

может сипользовать флаг переноса для определения соотношения двух

чисел. Если флаг переноса установлен, вычитаемое значение больше

уменьшаемого; если флаг переноса не установлен, вычитаемое значение

меньше или равно уменьшаемому. Это означает, что флаг переноса

становится первичным индикатором при определении соотношения двух

чисел: после того, как программа вычитает два числа, флаг переноса

указывает, какое из них больше. Таким способом программа может

проверять целые числа без знака, включая такие приложения, как

сортировка строк символов. В случае чисел со знаком для определения

соотношения чисел программе нужна дополнительная информация. В

следующей главе в разделе "Условные переходы" обсуждаются все

способы тестирования чисел.


Untitled

Дополнительный флаг переноса

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

дополнительного переноса (AUX), по крайней мере непосредственно.

Изучив команды условных переходов микропроцессора 8088, вы увидите,

что прямое тестирование этого флага невозможно. Микропроцессор

имеет флаг дополнительного переноса AUX для очень конкретной цели:

он позволяет микропроцессору выполнять десятичные вычисления в

двоичной кодировке (Binary-coded-decimal arithmetic - BCD - арифметика).

BCD - арифметика отличается от той, которую мы обсудили в гл.2.

Она основана на десятичной системе счисления. При осуществлении

десятичной арифметики в двоичной кодировке любая дасятичная цифра

представляется четырьмя битами (полубайтом). Каждый полубайт может

представлять значения от 0 до 9; значения от 0AH до 0FH не

используются. Это означает, что один байт может представлять

десятичные числа от 0 до 99.

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

кажется расточительным, двоичная кодировка десятичныч чисел не

использует 6 из 16 возможных состояний каждого полубайта. Однако во

многих случаях применения микропроцессора этот способ дает

непосредственное представление вводимых оператором чисел.

Большинство людей более подготовлено к работе с десятичными, а не с

двоичными или шестнадцатеричными числами. Как только потребности

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

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

легко преобразуемом к формату ввода-вывода. Двоичная кодировка

десятичных чисел дает такую возможность. BCD - арифметика также

решает возникающую в некоторых двоичных системах проблему

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

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

счисления не полностью соответствует десятичной системе. В

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

может оказаться неверной. Иногда это называют проблемой "потери

пенни", так как эти случаи наиболее заметны в финансовых

приложениях. BCD - представление чисел дает альтернативу для таких

вычислений.

Набор команд микропроцессора 8088 не имеет специальных команд

сложения и вычитания для BCD-арифметики. Здесь используется обычное

сложение и вычитание, применяемые как и при обычном двоичном

представлении. Результат сложения двух BCD-чисел на обычном

математическом процессоре может оказаться неправельным BCD-числом;

Для устранения этого микропроцессор 8088 имеет несколько команд,

которые преобразуют результат к правильной двоичной кодировке

десятичного числа. Для корректного проведения этого преобразования

процессор должен знать, был ли перенос из младшего полубайта в

старший во время сложения. Пример на Фиг. 3.13 показывает, почему

38

+ 29

----

61 Фиг. 3.13 Десятичное сложение

это необходимо. Десятичная сумма 38 + 29 равна 67. Но двоичная

сумма 38H + 29H равна 61H. Если числа 38 и 29 представляют

BCD-значения, то после операции сложения программа должна выполнить

коррекцию. Команда десятичной коррекции для сложения (DAA)

преобразует число в форму BCD. В нашем случае число представлено в

десятичной форме, т.е. оба полубайта находятся в диапазоне от 0 до

9, однако результат неверен. В данном случае сложение устанавливает

флаг дополнительного переноса, который показаывает, что произошел

перенос из младшей цифры (8 + 9). Этот флаг сообщает команде DAA,

чтобы она прибавила к результату 6, давая правильный результат 67.

Микропроцессор также использует флаг дополнительного переноса

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

команды десятичной коррекции для вычитания DAS. Существуют также

две другие команды, которые используют флаг AUX для определения

правильности действий. Эти команды, символьная коррекция для

вычитания AAS и символьная коррекция для сложения AAA, выполняют ту

же BCD-коррекцию, что и команды DAA и DAS. Команды AAA и AAS

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

при котором каждая десятичная цифра занимает один байт. Это

представление, еще более расточающее память, чем BCD, допускает

очень удобну юперекодировку из кода ASCII в числовое представление

и обратно. Числа от 0 до 9 представляются в коде ASCII значениями

от 30H до 39H, и преобразование в этот код и обратно просто

означает сложение или вычитание 30H. В следующей главе

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

коррекции.


Untitled

Флаг переполнения

Флаг переполнения OF - единственный флаг в старшем байте регистра

флагов, который устанавливается обычными арифметическими

операциями. Остальные флаги старшего байта находятся под прямым

управлением программиста. Флаг переполнения - еще один

арифметический флаг, как флаг нуля и переноса. Флаг переполнения

необходим для арифметики в дополнительном коде в такой же степени,

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

В арифметике чисел, представленных в дополнительном коде,

старший бит используется для хранения знака числа. Сумматор

микропроцессора работает как со знакопеременными числами, так и

беззнаковыми. Обычно сложение чисел со знаком дает верный

результат. Однако накоторые из чисел, представленных в дополни-

тельном коде, при сложении дают неверный результат. Пример на Фиг.

3.14 - сложение двух 8-битовых чисел, представленных в коде

двоичного дополнения, - иллюстрирует этот случай. Если 72H и 53H -

Шестнадцатеричное Десятичный эквивалент

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

72H 114

+ 53H + 83

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

0C5H 197

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

Фиг. 3.14 8-битовое сложение

с переполнением

числа без знака, то результат их сложения верен. Если же это числа,

представленные в дополнительном коде со знаком, то результат

сложения 0C5H неверен, в коде двоичного дополнения он равен -59.

сложение положительных чисел никогда не дает в результате отри-

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

значений 8-битовых чисел в двоичном коде (от 127 до -128). Этот

эффект принято называть переполнением, так как сумма вышла за

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

Важно заметить, что переполнение и перенос - два различных

флага и имеют разное значение. На Фиг. 3.14 нет переноса, так как

сложение без знака дает правильный результат, а есть переполнение:

что сложение со знаком дает неверный результат. Возможен и случай

одновременно переноса и переполнения, как показано на Фиг. 3.15.

Шестнадцатеричное Двоичное дополнение Беззнаковое

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

8EH -114 142

0ADH - 83 173

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

1 3BH -197 315

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

Фиг. 3.15 8-битовое сложение с переносом и перепонением

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

-197 выходит за пределы диапазона представимости в дополнительном

коде. Это показано тем, что 8-битовый результат 3BH - положительное

число. Кроме того в этом примере устанавливается флаг переноса,

означающий, что сложение без знака дало число, большее

максимального представимого. В случае 8-битовых чисел максимальное

число равно 255.

Вообще говоря, операция сложения в процессоре

выполняется одинаково над числами и со знаком, и без знака, а также

с десятичными числами. Флаги переноса, дополнительного переноса и

переполнения содержат информацию о выполненной операции, позволяя

программе определить верный результат в используемой системе

счисления. Флаг переполнения показывает, что результат

арифметической операции выходит за пределы диапазона чисел,

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

переноса, который показывает, что произошел перенос из старшего

бита результата.


Untitled

Флаг захвата

Флаг захвата (специального прерывания) TF помогает при отладке

программ. Этот флаг устанавливается не в результате работы

микропроцессора, а - программой, с помощью специальной команды.

Этот флаг называется также флагом трассировки или шага.

Когда этот флаг установлен, после выполнения каждой команды

возникает прерывание. Эффект при этом такой же, как если бы после

каждой команды некоторое внешнее устройство запрашивало прерывание.

Прерывание по трассировке передает управление в ячейку,

определенную вектором прерывания 4. Во время процедуры прерывания

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

позволяет программе обработки прерывания по трассировке избежать

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

трассировке возвращает управление программе пользователя, он

восстанавливает начальное состояние регистра флагов, в котором флаг

трассировки установлен. Микропроцессор выполняет следующую команду

пользователя, и снова возникает специальное прерывание. Обработчик

прерываний по трассировке получает управление после каждой команды

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

Отладчик DOS использует флаг трассировки. Одной из функций

отладчика является пошаговое выполнение, при котором перед каждым

возвращением управления к отладчику выполняется одна команда

программы пользователя. Это прерывание инициируется флагом захвата.

Полное описание процедуры прерывания дано в разделе "Векторы

прерываний".


Untitled

Флаг прерываний

Флаг прерываний IF управляет внешними прерываниями. Во время

выполнения тех фрагментов программы пользователя, где внешние

прерывания разрешать нежелательно, программа может сбросить флаг

прерываний. Пока флаг прерываний сброшен в 0, никакие внешние

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

прерываний равным 1, внешние устройства могут порождать прерывания.

Управляет флагом прерываний программа пользователя.

IBM PC использует несколько методов обслуживания прерываний.

Флаг прерываний регистра состояния блокирует все внешние

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

Для тех случаев, когда программе надо заблокировать только

некоторые из прерываний, существует отдельный регистр масок

прерываний. Этот регистр может запретить или разрешить отдельные

внешние прерывания. В гл.8, описывающей аппаратуру IBM PC, будет

рассмотрено использование этого регистра.


Untitled

Флаг направления

Последним флагом в регистре флагов является флаг направления DF.

Набор команд микропроцессора 8088 содержит несколько команд

обработки строк, которые работают с большими блоками данных. Эти

команды обработывают блоки данных побайтно или по одному слову

памяти за раз. Индексные регистры указывают на блоки данных. После

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

чтобы он указывал на следующий элемент блока.

Строковые операции используют флаг направления для определения

направления продвижения по блоку данных. Если флаг направления

сброшен в 0, команды обработки строк увеличивают значение

индексного регистра, а если флаг направления установлен в 1, то они

умень- шают это значение. Флаг направления позволяет одному набору

строковых команд обслуживать оба направления в зависимости от

установки флага. В некоторых случаях желательно пересылать строку с

увеличением адресов, а в других лучше всего использовать уменьшение

адреса.

В качестве примера предположим, что в программе используется

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

Если программа пересылает блок, с большего адреса памяти на

меньший, она сбрасывает флаг направления, чтобы увеличивать

значения индексных регистров после каждой пересылки; если же

пересылка производится на больший адрес памяти, флаг направления

устанавливается в 1, показывая уменьшение индексных регистров. В

случае большинства пересылок не имеет значения, как именно

установлен этот флаг. Но если конечное положение блока перекрывает

его начальное положение, а флаг направления уствновлен неверно, то

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

Рисунок 3.16 иллюстрирует пример пересылки блоков. Исходный

блок данных имеет длину 200H байт и расположен от 300H до 4FFH.

Нужно переслать его на новое место, расположив от 400H до 5FFH;

исходное и результирующее поля перекрываются.

300 ГДДДДДДДДДДДЕДДД Указатель 300 ГДДДДДДДДДДДґ

і і источника і і

і і SI і і

і і і і

Источник 400 ГДДДДДДДДДДДЕДДД Указатель 400 ГДДДДДДДДДДДґ

і і назначения і і

і і DI і і

і і і і

Назначение 500 ГДДДДДДДДДДДґ 500 ГДДДДДДДДДДДґ<-- Указатель

і і і і источника

і і і і

і і і і

600 ГДДДДДДДДДДДґ 600 ГДДДДДДДДДДДґ<-- Указатель

Фиг. 3.16 Флаг направления

В примере на Фиг.3.16(а) указатели источника и результата

установлены на начала соответствующих блоков: указатель источника

на 300H, а указатель результата на 400H. Флаг направления в примере

сброшен, так, чтобы указатели увеличивались после каждой пересылки.

Как показано на рисунке, после пересылки с помощью строковой

операции 100H байт, указатель источника переместится на блок

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

пересылки. Пересылка последних 100H байт будет неправильной, так

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

В части (b) примера указатели и источника, и результата

установлены на концы блоков. Флаг направления установлен так, что

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

способе данные пересылаются верно.

Программы ввода-вывода для IBM PC дают характерный пример

использования флага направления для перемещения изображения на

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

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

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

команды пересылают данные в меньшие адреса памяти. Когда программа

опускает символы на экране вниз, команды пересылают данные в

большие адреса памяти. В каждом случае программа устанавливает или

сбрасывает флаг направления в соответствии с направлением

пересылаемых данных.


Векторы прерываний

Еще одна важная составная часть микропроцессора 8088 - механизм

прерываний. Эта компонента системы встроена в микропроцессор, и

обеспечивает эффективные методы обработки прерываний.

Когда микропроцессор 8088 получает сигнал о необходимости

прерывания, он определяет, какое из усройств требует обслуживания

посредством аппаратной процедуры, известной как цикл подтверждения

прерывания. В IBM PC для обслуживания внешних прерываний

используется контроллер прерываний 8259 фирмы Intel. Контроллер

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

ответ на цикл подтверждения прерывания микропроцессора 8088. Это

число, находящееся в диапазоне от 0 до 255, - номер прерывания

внешнего усройства, вызвавшего прерывание. В персональной ЭВМ

контроллер прерываний обслуживает восемь внешних прерываний,

которым соответствуют номера от 8 до 15.

Как только микропроцессор 8088 получает номер прерывания, он

должен передать управление соответствующей программе обработки

прерывания. Первые 1024 байт памяти микропроцессора 8088

зарезервированы для векторов прерываний. Каждому из 256 возможных

прерываний отводится четырехбайтовая область. Прерывание 0 имеет

четыре байта по адресам от 0 до 3, прерывание 1 - от 4 до 7, и т.д.

Каждая четырехбайтовая ячейка содержит указатель на соответствующий

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

смещение адреса программы обработки прерывания, а последние два

байта - сегмент. Для задания значения этого поля может

использоваться оператор определения двойного слова DD.

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

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

находиться в любом месте адресного пространства микропроцессора, он

должен обслужить прерывание, как вызов типа FAR, т.е. перед тем,

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

сохранит сегмент и смещение текущей команды в программе. Кроме того

возврат из программы обработки прерывания должен вернуть машину в

точности в то состояние, в котором она была в момент возникновения

прерывания. Чтобы помочь в этом, микропроцессор 8088 также

сохраняет регистр флагов в стеке. Это означает, что эти действия

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

Сохранение регистра флагов означает также сохранение и текущего

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

сбрасывает флаг разрешения прерывания, так что программа обработки

прерывания уже не может быть прервана другим прерыванием. Команда

возврата из прерывания, которая восстанавливает регистр флагов,

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

прерываний в состояние предшествующее возникновению прерывания.

Когда возникает прерывание, микропроцессор помещает в стек

региср флагов, за которым следуют регистры CS и IP. 8088 использует

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

прерывания, и передать ей управление. Теперь уже эта программа

отвечает за сохранение регистров, которые она использует, и

восстановление их перед возвратом управления в прерванную

процедуру. Для возврата из прерывания используется специальная

команда IRET. Она извлекает верхние три слова из стека и помещает

их в регистры IP, CS и регистр флагов. В следующих главах мы

приведем несколько примеров, использующих механизм прерываний.

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

непосредственно, без запроса внешних прерываний. Существуют

команды, которые заставляют микропроцессор работать так, как будто

при их выполнения возникло внешнее прерывание. Такие действия

называются программными прерываниями, так как они порождаются

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

помещает все три управляющих регистра в стек и выбирает вектор

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

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

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

Программные прерывания придают большую гибкость системе 8088.

В случае обычных вызовов подпрограммы программист до ее выполнения

обязан знать, где она находится. Но если программа вызывает под-

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

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

программе нет нужды знать ее местонахождение. Единственным пара-

метром, котрый требуется от программиста, вызывающего подпрограмму,

является номер вектора прерываний. Управляющие программы и опера-

ционная система фирмы IBM очень выгодно используют этот механизм.

Программные прерывания дают доступ к сервисным программам системы.

Программам пользователя не нужно знать точные адреса, которые могут

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

Кроме того, сервисные подпрограммы могут быть подменены в любой

момент времени простой заменой четырехбайтового вектора,

указывающего на новую программу, без всякой модификации программ,

использующих эти подпрограммы. В гл.10 мы приведем несколько

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


Untitled

Глава 4

Набор команд микропроцессора 8088

В этой главе обсуждается набор команд микропроцессора 8088. В

предыдущих главах было рассмотрено расположение регистров и

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

операции, которые можно выполнять с помощью регистров.

Разобьем команды микропроцессора 8088 на несколько групп.

Принадлежность команды к той или иной группе будет определяться

функцией, которую она выполняет. В этой главе описываются группы

команд и объясняется, как лучше всего ими пользоваться, но не будет

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

руководство по Макроассемблеру).

Пересылка данных

Команды пересылки данных обычно наиболее часто используются из

всего набора команд любой ЭВМ, и микропроцессор 8088 - не

исключение. Большая часть каждой задачи по обработке данных

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

Программа может выполнять некоторую обработку информации по мере

того, как она пересылается, но громадная часть работы сводится

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

команды пересылки данных микропроцессора 8088, а в разделе,

посвященном обработке строк - остальная часть таких команд.

Команда пересылки

Команда MOV - основная команда пересылки данных, которая пересылает

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

или из регистра в регистр. Команда MOV может также занести число,

определенное программистом, в регистр или в память.

В действительности команда MOV - это целое семейство машинных

команд микропроцессора 8088. Таблица, в которую сведены варианты

всех машинных команд микропроцессора 8088, приведена в приложении

А. Беглый просмотр этой таблицы показывает, что существует семь

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

из этих команд с помощью единого названия операции MOV. Ассемблер

порождает правильную машинную команду, основываясь на типах

операндов, которые написал программист; и это одна из причин, по

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

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

регистр, байт памяти, слово памяти, сегментный регистр, или

что=нибудь еще. Такое назначение типов позволяет ассемблеру

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

команды MOV ассемблер должен решить, какой из семи вариантов

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

программистом.

На Фиг.4.1 представлены различные способы, которыми в

микропроцессоре 8088 можно переслать данные из одного места в

другое. Каждый прямоугольник означает здесь регистр или ячейку

памяти. Стрелки показывают пути пересылки данных, которые допускает

набор команд микропроцессора 8088. Основной путь - из памяти в

регистры и наоборот. С данными, помещенными в регистры, можно

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

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

данные. Кроме того, все команды микропроцессора 8088 могут указать

только один операнд памяти. Поэтому, например, команда сложения ADD

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

Микропроцессор 8088 не имеет возможности сложить одну ячейку памяти

с другой с помощью одной команды.

ЪДДДДДДДДДДДДДДДДДДДДї

і і

і Непосредственные і

АДДДДДДВДДДДДДВДДДДДДЩ

ЪДДДДДДДДДДї і і ЪДДДДДДДДДДДї

і і і і і Регистры і

і Г<ДДДДДДДДДДДЩ АДДДДДДДДДДД>ґ AX і

і і і BX і

і Память Г<ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД>ґ CX і

і і і DX і

і Г<ДДДДДДДДДДДї ЪДДДДДДДДДДД>ґ SI і

і і і і і DI і

і і і і і BP і

і і і і і SP і

АДДДДДДДДДДЩ v v АДДДДДДДДДДДЩ

ЪДДДДДДБДДДДДДБДДДДДДДї

і Сегментные регистры і

і CS DS ES SS і

АДДДДДДДДДДДДДДДДДДДДДЩ

Фиг.4.1 Операции пересылки данных

В самой команде MOV может содежаться новое содержимое регистра.

Такая форма операнда называется непосредственным оперндом; данные

находятся в самой команде и не требуют вычисления адреса. Вы можете

рассматривать эту форму адресации как специальный тип, при котором

операнд находится в самой команде, а не где=то в другом месте

памяти или в регистре. Кроме команд пересылки, у микропроцессора

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

Из Фиг.4.1 также ясно, что команда может переслать

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

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

с непосредственным операндом имеет одно направление.

Наконец, команда MOV может записать сегментный регистр в память

или регистр. Она может также загрузить сегментный регистр из памяти

или из другого регистра. Однако не существует команды загрузки

сегментного регистра данными с непосредственным операндом; это

означает, что загружать сегментный регистр такими данными

непроизводительно. Если в программе необходимо поместить известное

значение в сегментный регистр, нужно сначала записать это значение

в один из регистров или в ячейку памяти, а затем можно уже

пересылать это значение в сегментный регистр. На Фиг. 4.2 показано,

как это сделать.

icrosoft (R) Macro Assembler Version 5.00 1/1/80 04:00:28

Фиг. 4.2 Команда пересылки Page 1-1

PAGE ,132

TITLE Фиг. 4.2 Команда пересылки

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 EXWORD LABEL WORD

0000 EXBYTE LABEL BYTE

0000 8B C3 MOV AX,BX ; Регистр BX --> Регистр AX

0002 8B D8 MOV BX,AX ; Регистр AX --> Регистр BX

0004 8B 0E 0000 R MOV CX,EXWORD ; Память --> Регистр

0008 89 16 0000 R MOV EXWORD,DX ; Регистр --> Память

000C 8A 2E 0000 R MOV CH,EXBYTE ; Память --> Регистр (байт)

0010 88 36 0000 R MOV EXBYTE,DH ; Регистр --> Память (байт)

0014 BE 03E8 MOV SI,1000 ; Непосредственное --> Регистр

0017 B3 17 MOV BL,23 ; Непосредственное --> Регистр (байт)

0019 C7 06 0000 R 07D0 MOV EXWORD,2000 ; Непосредственное --> Память

001F C6 06 0000 R 2E MOV EXBYTE,46 ; Непосредственное --> Память (байт)

0024 A1 0000 R MOV AX,EXWORD ; Память --> Аккумулятор

0027 A0 0000 R MOV AL,EXBYTE ; Память --> Аккумулятор (байт)

002A A3 0000 R MOV EXWORD,AX ; Аккумулятор --> Память

002D A2 0000 R MOV EXBYTE,AL ; Аккумулятор --> Память (байт)

0030 8E 1E 0000 R MOV DS,EXWORD ; Память --> Сегментный регистр

0034 8E D8 MOV DS,AX ; Регистр --> Сегментный регистр

0036 8C 1E 0000 R MOV EXWORD,DS ; Сегментный регистр --> Память

003A 8C C0 MOV AX,ES ; Сегментный регистр --> Регистр

;----- Непосредственное значение в сегментный регистр

003C B8 ---- R MOV AX,CODE ; Взять непосредственное значение

003F 8E D8 MOV DS,AX ; Загрузить его в сегментный регистр

0041 CODE ENDS

END

Фиг. 4.2 Команды пересылки

На Фиг. 4.2 изображен листинг ассемблера некоторых возможных

вариантов команды MOV. Единственная команда ассемблера MOV

порождает несколько различных машинных команд.

Рассматривая Фиг.4.2, обратите внимание на сантаксис команды

MOV. Команда MOV имеет два операнда: источник и результат. В

команде они следуют друг за другом, источник следует за

результатом. Первая команда на рисунке MOV AX, BX пересылает

содержимое регистра BX в регистр AX. Следующая команда обратна

предыдущей, содержимое регистра AX пересылается в регистр BX.

Команда MOV не меняет источник, т.е. команда

MOV AX, BX

меняет регистр AX, результат, но не меняет регистр BX,

источник.

Никакие из команд MOV не меняют флагов состояния. Хотя иногда

это кажется неудобным, но является наилучшим способом работы с

флагами. Как мы увидим далее, микропроцессор 8088 имеет команды,

которые могут эффективно проверить любую ячейку памяти так, что

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

установка флагов при пересылке не нужна, рассмотрим арифметику

повышенной точности. Когда программа выполняет вычисления

повышенной точности, она должна переслать части операндов в

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

пересылка не меняет ни одного флага, а это позволяет флагам

обслуживать арифметику повышенной точности.

Как было замечено, существует несколько различных вариантов

команд пересылки на машинном языке. Объектный код на Фиг. 4.2

иллюстрирует эти варианты. Если вас интересует структура машинного

языка, вы можете сравнить объектный код с описанием машинного языка

в приложении А. Такое сравнение поможет выяснить значение отдельных

битов в машинном коде. Например, вы сможете увидеть значения данных

с непосредственным операндом в командах. К счастью, для того, чтобы

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

работает ассемблер.

Если вы хотите достичь наибольшей возможной эффективности

программ, вам надо изучить объектный код на Фиг. 4.2. Число байтов

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

для выполнения этой команды. Например, команда пересылки, которая

берет непосредственное значение и посылает его в память, занимает 6

байт. Набор команд микропроцессора 8088 содержит несколько команд,

оптимизированных для работы с аккумулятором AX либо AL.

Использование этих команд поможет вам сэкономить время и место в

программах, где это важно.

Последние две команды на Фиг. 4.2 показывают, как занести

непосредственное значение в сегментный регистр. Любой другой

регистр, в примере это регистр AX, может временно содержать

непосредственное значение перед его записью в сегментный регистр.

Есть и другеи команды, которые переносят данные. Пример на

Фиг. 4.3 иллюстрирует эти команды.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:33

Фиг. 4.3 Команды пересылки данных Page 1-1

PAGE ,132

TITLE Фиг. 4.3 Команды пересылки данных

0000 CODE SEGMENT

ASSUME CS:CODE, DS:CODE

0000 EXDWORD LABEL DWORD

0000 EXWORD LABEL WORD

0000 EXBYTE LABEL BYTE

0000 87 D9 XCHG BX,CX ; Регистр BX <--> Регистр CX

0002 87 1E 0000 R XCHG BX,EXWORD ; Регистр BX <--> Память

0006 93 XCHG AX,BX ; Регистр AX <--> Регистр BX

0007 E4 20 IN AL,020H ; Порт 20H --> AL

0009 EC IN AL,DX ; Порт (DX) --> AL

000A E6 21 OUT 021H,AL ; AL --> Порт 021H

000C EE OUT DX,AL ; AL --> Порт (DX)

000D 8D 36 0000 R LEA SI,EXWORD ; Адрес(EXWORD) --> SI

0011 C5 36 0000 R LDS SI,EXDWORD ; M(EXDWORD) --> SI

; M(EXDWORD+2) --> DS

0015 C4 3E 0000 R LES DI,EXDWORD ; M(EXDWORD) --> DI

; M(EXDWORD+2) --> ES

0019 9F LAHF ; Флаги --> AH

001A 9E SAHF ; AH --> Флаги

001B D7 XLAT EXBYTE ; M(BX+AL) --> AL

001C CODE ENDS

END

Фиг. 4.3 Команды пересылки данных


Untitled

Команда замены

Команда замены XCHG просто меняет местами содержимое двух ячеек.

Эта команда может поменять местами содержимое двух регистров, или

регистра и памяти. При этом в качестве операндов не могут

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

Команда XCHG заменяет три команды пересылки и не требует

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

существовола, программе потребовалось бы три пересылки, чтобы

обменять значения в регистре AX и в регистре BX. Сначала она должна

была бы переслать содержимое регистра AX в рабочую ячейку, затем

переслать содержимое регистра BX в регистр AX, и наконец, переслать

содержимое рабочей ячейки в регистр BX. Команда XCHG одна выполняет

эту операцию.


Untitled

Команды ввода и вывода

Для выполнения операций ввода и вывода микропроцессор 8088 имеет

команды IN и OUT соответственно. Каждое устройство ввода=вывода

IBM PC имеет один или больше встроенных регистров, с которыми могут

работать эти команды. Каждое устройство ввода=вывода имеет адрес

для каждого регистра в устройстве. Это адресное пространство

отличается от адресного пространтва памяти; всего существует 216,

или 65536 адресов ввода=вывода, доступных микропроцессору 8088. В

IBM PC 512 из этих адресов назначены системному каналу ввода=вывода

и могут использоваться различными адаптерами. Другие 256 адресов

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

устройствами ввода=вывода.

Команда IN пересылает данные из устройства ввода=вывода в

регистр AL. Эта команда может указать адрес устройства ввода=вывода

двумя различными способами. Если адрес устройства находится в

пределах 0 - 255, он может содержаться в команде как

непосредственное значение. Если адрес больше 255, команда сообщает

это косвенно. В случае косвенной команды адрес устройства

ввода=вывода содержится в регистре DX. Регистр DX может содержать

адреса всех устройств ввода=вывода, включая те, номера которых

меньше 256.

Аналогично работает команда OUT, за исключением того, что она

записывает регистр AL в регистр устройства ввода=вывода. Адреса в

команде OUT указываются так же, как и в команде IN.

Команды IN и OUT также могут пересылать слова в устройства

ввода=вывода и из них. В случае работы со словами источником и

приемником является регистр AX. Так как у микропроцессора 8088

однобайтовая внешняя шина, устройства ввода=вывода IBM PC работают

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

что операции ввода=вывода слов не используются в персональной ЭВМ.

Однако пословные операции ввода=вывода имеют смысл в системе с

микропроцессором 8086, который имеет тот же набор команд.


Untitled

Загрузка исполнительного адреса

Команда загрузки действительного адреса LEA очень похожа на команду

MOV. Но вместо пересылки данных из ячейки памяти в регистр команда

LEA загружает в регистр адрес двнных. Так как набор команд

микропроцессора 8088 разрешает иметь в команде только один адрес

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

Команда LEA может ссылаться на операнд источника с помощью любого

типа адресации, который можно указать байтом mod=r/m.

Во многих случаях команда LEA идентична команде MOV с

непосредственным операндом. Команды

MOV BX, OFFSET EXWORD

LEA BX, EXWORD

делают одно и то же. Первая команда - это непосредственная

пересылка, которая использует смещение переменной EXWORD. Оператор

OFFSET говорит ассемблеру о том, что в регистр BX надо загрузить

смещение адресного значения (все адресные значения имеют две части

- сегмент и смещение) переменной EXWORD. Команда LEA вычисляет

действительный адрес переменной EXWORD и помещает его в регистр BX.

В этом случае команды выполняют одинаковые действия.

Но если бы программа загружала в регистр BX адрес десятого

байта массива, на который указывает регистр DI, команда LEA

выглядела бы следующим образом

LEA BX, 10[DI]

Микропроцессор выполнил бы вычисление адреса, используя

информацию из байта mod=r/m в точности, как в случае команды MOV.

Затем он поместил бы вычисленное смещение, а не данные, по этому

адресу в регистр BX. Аналогичной команде с непосредственным

операндом MOV, которая могла бы выполнять ту же функцию, нет. У

ассемблера здесь нет способа определения непосредственного

значения, так как адрес неизвестен во время ассемблирования.


Untitled

Загрузка указателя

Поскольку механизм адресации микропроцессора 8088 требует

определения как сегмента, так и смещения каждой переменной,

желательно загрузить всю эту адресную информацию единственной

командой. Эту работу выполняют команды LDS и LES. Команда

LDS SI, EXDWORD

загружает регистровую пару DS:SI значениями сегмента и

смещения, содержащимися в переменной EXDWORD. Команда LDS загружает

в регистр SI значение смещения, расположенное по адресу EXDWORD, а

в регистр DS - значение сегмента, расположенное по адресу

EXDWORD+2. Команда LDS одна загружает два 16=битовых регистра

значением указателя, взятого из некоторой ячейки памяти. Так как

эта команда устанавливает и сегментный регистр, и регистр смещения,

программа может сразу адресоваться к объекту, на который этот адрес

указывает. Программа может организовать указатель из сегмента и

смещения во время ассемблирования с помощью оператора DD, который

порождает 32=битовое поле данных. Если операндом DD является

адресное выражение, двухсловное поле будет содержать сегмент и

смещение адресного значения в том же самом формате, который

используется в командах LDS и LES.

Команда LES идентична LDS, за исключением того, что она

загружает регистр ES. С помощью одной команды записать значения

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

указателя двумя командами пересылки слов, а не одной командой

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

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

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

быть, иногда меняет его во время смены режимов работы системы. А

вот читается указатель, вероятно, достаточно часто. В последующих

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

записываются.


Untitled

Пересылка флагов

Набор команд микропроцессора 8088 имеет команды LAHF и SAHF в

первую очередь для совместимости с набором команд микропроцессора

8080. Команда LAHF берет 8 младших бит регистра флагов - а эти

флаги совпадают с флагами микропроцессора 8080 - и засылает их в

регистр AH. Команда SAHF действует наоборот, младший байт регистра

флагов загружает из регистра AH.

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

системы команд микропроцессора 8080 в команды микропроцессора 8088.

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

микропроцессора 8080 в стековые операции микропроцессора 8088.


Untitled

Перекодировка

Команда перекодировки XLAT преобразует информацию из одного

представления в другое. Команда XLAT преобразует значение в

регистре AL в другое значение, выбираемое из таблицы, на которую

указывает регистр BX. На Фиг.4.4 схематически показано, как

работает эта команда. Регистр BX вместе с выбранным сегментным

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

К этому адресу таблицы команда прибавляет содержимое регистра AL,

значение между 0 и 255. Данные, расположенные по этому адресу,

команда XLAT пересылает в регистр AL. Команда XLAT выполняет

операцию просмотра таблицы.

Команду XLAT хорошо использовать при кодировании и

декодировании текстовых данных. С помощью этой команды программа

может организовать простую замену кодов символов. В следующем

примере десять символов кода ASCII от 0 до 9 перекодируются в целях

передачи. Этот метод может использоваться в системе для

перекодировки информации, передаваемой из одной машины в другую.

Когда данные принимаются, другая программа возвращает

закодированные символы к их первоначальному виду. На Фиг. 4.5

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

На Фиг.4.5 изображены две таблицы перекодировки, одна для

передачи, а другая для приема. Чтобы передать значение 5, программа

находит значение 5 в таблице передачи (а), из которой извлекает

значение 6, которое передает. Когда это значение принимается,

программа декодирования ищет 6 в таблице приема (b), чтобы

перекодировать его в истинное значение 5.

На Фиг. 4.6 показана подпрограмма, которая производит это

декодирование. Подпрограмма перекодирования читает начальное

значение из порта ввода=вывода, и возвращает кодированное или

декодированное значение в вызывающую программу через регистр AL.

Одна и та же программа выполняет как кодирова- ние, так и

декодирование, меняя таблицы кодировки.

Сначала подпрограмма читает данные из вводного порта 40H в

регистр AL. Затем она вычитает значение "0" в коде ASCII из

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

что символ "0" дает значение 0 в регистре AL, символ "1" дает 1, и

т.д. Команда LDS загружает указатель нужной таблицы в пару

регистров DS:BX. Загружая этот указатель из ячейки памяти - в

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:38

Фиг. 4.6 Пример перекодировки Page 1-1

PAGE ,132

TITLE Фиг. 4.6 Пример перекодировки

0000 CODE SEGMENT

ASSUME CS:CODE, DS:CODE

; Эта программа вводит значение из порта 040H и

; декодирует его, используя таблицу перекодировки.

; Так как одна и та же программа используется как для

; кодировки, так и декодировки, указатель TABLE_POINTER

; указывает на соответсвующую таблицу перекодировки.

; Вызывая подпрограмму, необходимо установить

; этот указатель на соответствующую таблицу.

0000 TRANSLATE PROC NEAR ; Подпрограмма TRANSLATE

0000 E4 40 IN AL,040H ; Ввод значения из порта

0002 2C 30 SUB AL,"0" ; Значение относительно символа "0",

; т.е. относительно начала таблицы

0004 C5 1E 000A R LDS BX,TABLE_POINTER ; (DS,BX) указывает на таблицу

0008 D7 XLAT XMIT_TABLE ; Перекодировка числа

0009 C3 RET

000A 000E ---- R TABLE_POINTER DD XMIT_TABLE

000E 35 37 39 31 33 36 38 XMIT_TABLE DB '5791368024'

30 32 34

0018 37 33 38 34 39 30 35 RECV_TABLE DB '7384905162'

31 36 32

0022 TRANSLATE ENDP

0022 CODE ENDS

END

Фиг. 4.6 Пример перекодировки

примере TABLE_POINTER - подпрограмма может использовать любую

таблицу перекодировки. В этой программе имеются две табдицы, одна

из них для передачи, названная XMIT_TABLE, которая соответствует

Фиг.4.5(а), другая - таблица приема, названная RECV_TABLE, -

соответствует Фиг.4.5(б). Перед вызовом подпрограммы головная

программа должна записать нужный адресный указатель в переменную

TABLE_POINTER. Если головная программа принимает коды, она должна

поместить адрес таблицы RECV_TABLE в переменную TABLE_POINTER.

Заметим, что эта подпрограмма может проделать любую перекодировку,

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

Команда XLAT выполняет перекодировку по таблице, на которую

указывает пара регистров DS:BX. В регистре AL находится значение

между 0 и 9. Команда XLAT складывает это значение с содержимым

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

Команда RET возвращает управление в вызывающую программу.

Другим обычным случаем использования команды XLAT является

смена кода представления символов в одной машине на код

представления в другой машине. IBM PC, работает в коде ASCII, а

большинство машин фирмы IBM используют код EBCDIC (Extended

Binary=Coded=Decimal Interchange Code - расширенный

двоично=кодированный десятичный код обмена информации). Чтобы

связываться с такими машинами, в программе надо перекодировать

символы, и команда XLAT естественным образом подходит для этой

функции.

Итак, команда XLAT является весьма мощным средством

перекодировки байтовой или символьной информации. Мощность этой

команды делает ее редко используемой, так как возможность

использовать ее преимущества возникает не часто. Однако помните об

этой команде на тот случай, когда она окажется полностью

оправданной.


Untitled

Операции со стеком

В гл.3 обсуждалось, как реализован стек в микропроцессоре 8088.

Микропроцессор 8088 адресует стек с помощью регистровой пары SS:SP.

Помещение объектов в стек приводит к тому, что он растет в сторону

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

запоминания адресов возврата из подпрограмм. В этом разделе

рассматриваются некоторые команды, которые непосредственно работают

со стеком.

Фиг.4.7 иллюстрирует ассемблированные стековые команды.

Мнемоника команд очевидна; за кодами операций PUSH и POP следует

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

является помещение и извлечение из стека регистра флагов, которые

используют мнемонику PUSHF и POPF соответственно. Содержимое любой

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

возможные способы адресации, также может быть помещено или

извлечено из стека.

При любых действиях со стеком в микропроцессоре 8088 базовой

единицей информации является 16=битовое слово. Длина любого

объекта, помещаемого в стек либо извлекаемого из стека, составляет

одно или несколько слов. Байтовых команд, связанных с засылкой

данных или извлечением их из стека, не существует. Если, например,

программе необходимо сохранить содержимое регистра AL а стеке, она

должна поместить содержимое регистра AX, так как не существует

способа сохранения только содержимого регистра AL.

Основное назначение стека - временное хранение информации. Как

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

программа также может сохранять данные. Если программа хочет

использовать регистр, пусть даже сохранить текущие данные, она

может послать значение этого регистра в стек. Эти данные

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

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:43

Фиг. 4.7 Операции со стеком Page 1-1

PAGE ,132

TITLE Фиг. 4.7 Операции со стеком

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 EXWORD LABEL WORD

0000 50 PUSH AX ; Поместить регистр в стек

0001 56 PUSH SI

0002 0E PUSH CS ; Можно поместить в стек сегментный регистр

0003 FF 36 0000 R PUSH EXWORD ; Можно также поместить в стек ячейку памяти

0007 8F 06 0000 R POP EXWORD ; Можно извлечь то, что в помещено в стек

000B 07 POP ES ; Можно извлечь в другое место

000C 5F POP DI

000D 5B POP BX

000E 9C PUSHF ; Другая мнемоника для флагов

000F 9D POPF

;----- Пример, демонстрирующий передачу параметров

0010 50 PUSH AX

0011 53 PUSH BX

0012 51 PUSH CX

0013 52 PUSH DX

0014 E8 0017 R CALL SUBROUTINE ; Передача управления

; ... ; Продолжение программы

0017 SUBROUTINE PROC NEAR

0017 8B EC MOV BP, SP ; Занесение в BP адреса стека

0019 8B 46 02 MOV AX, [BP+2] ; Выборка последнего параметра (DX)

001C 8B 5E 04 MOV BX, [BP+4] ; Выборка третьего параметра (CX)

001F 8B 4E 06 MOV CX, [BP+6] ; Выборка второго параметра (BX)

0022 8B 56 08 MOV DX, [BP+8] ; Выборка первого параметра (AX)

; ...

0025 C2 0008 RET 8 ; Возврат с уничтожением поля параметров

0028 SUBROUTINE ENDP

0028 CODE ENDS

END

Фиг. 4.7 Операции со стеком

программе нужно ввести код из порта ввода=вывода 3DAH, а в регистре

DX находятся важные данные. Следующая последовательность команд

PUSH DX

MOV DX, 3DAH

IN AL, DX

POP DX

сохраняет регистр DX в стеке на то время, пока он нужен в

программе для выполнения команды IN.

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

начале программы. В большинстве случаев подпрограмма старается

избегать изменения содержимого любого регистра. Поэтому

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

адресов, помещает все необходимые ей регистры в стек до выполнения

команд обработки. Затем, после выполнения, подпрограмма

восстанавливает регистры из стека с помощью команд POP.

Помните о том, что стек - это структура типа LIFO. Если в вашей

программе выполняется последовательность команд

PUSH BX

PUSH CX

POP BX

POP CX

то результирующим эффектом будет обмен значений в регистрах BX

и CX. Только тот факт, что в команде PUSH был указан регистр BX, не

означает, что команда POP, указывающая на тот же регистр,

восстанавливает первоначальное содержимое регистра BX. Еще одним

важным моментом является то, что команды PUSH и POP должны быть

сбалансированы, т.е. каждой команде PUSH должна соответствовать

команда POP. Точно так же, как и в случае скобок в арифметическом

выражении, если посылки и извлечения из стека не сбалансированы,

результаты будут неверны. Более того, несбалансированные команды

PUSH/POP обычно приводят к возврату из подпрограмм по адресу

значения данных, а не значения указателя команд из=за того, что

микропроцессор 8088 записывает в стек адрес возврата. Обычно это

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

никогда не писал. Поэтому баланс стековых команд обязателен. Будьте

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

переход вокруг стековых операций; можно легко выпустить из виду

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

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

в качестве буфера при некоторых пересылках; в частности, не

существует команды пересылки, которая бы переносила данные из

одного сегментного регистра в другой. В обычном случае загрузка

одного сегментного регистра из другого требует сначала загрузки его

значения а промежуточный регистр. Это достигается следующей

последовательностью из двух команд:

MOV AX,CS ;переслать значение регистра

;CS в регистр AX

MOV DS,AX ;загрузить это значение в

; регистр DS

Каждая из этих команд имеет длину несколько байт, и эта

последовательность разрушает содержимое регистра AX. Альтернативным

подходом может быть

PUSH CS ; регистр CS поместить в стек

POP DS ; поместить это значение в регистр DS

Результирующий эффект этой последовательности команд тот же,

регистр DS загружается из регистра CS. Здесь длина программы -

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

Однако эти две команды занимают больше времени, так как нужны

дополнительные циклы чтения и записи в стек. Это - метод потери в

скорости выполнения ради уменьшения размера объектного кода.


Передача параметров

Стек также служит удобным местом для передачи информации в

подпрограммы и из них. Обычно программа передает параметры в

подпрограмму, помещая их в регистры, однако в некоторых случаях

число параметров превышает размеры регистрового пространства. В

таких случаях програииа может поместить параметры в стек до

выполнения команды CALL (вызов подпрограммы). Как мы увидим в

гл.10, стек является единственным средством передачи параметров в

подпрограммы, написанные на языке ассемблера, из языков высокого

уровня Бейсик и Фортран.

Подпрограмма может очень эффективно загружать эти параметры из

стека. В обычных случаях программа читает информацию из стека

единственным способом - извлекая ее оттуда. Вместо этого

подпрограмма может использовать регистр BP, как указатель на

область стека. Когда программа передает параметры через стек, одной

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

MOV BP, SP

которая загружвет регистр BP текущим значением указателя стека.

Поскольку регистр BP - адресный регистр, подпрограмма может

использовать его при адресных вычислениях, а это означает, что все

параметры доступны как смещения относительно регистра BP.

Конструкторы микропроцессора 8088 определенно помнили об

описанном выше методе передачи параметров, так как при доступе к

данным регистр BP использует по умолчанию регистр стекового

сегмента SS в качестве сегментного регистра. Во всех других

нормальных случаях доступа к данным микропроцессор использует

регистр DS. Поскольку стек находится в стековом сегменте,

регистровую пару SS:BP очень естественно использовать для адресации

информации в стеке.

На Фиг. 4.7 изображен пример, демонстрирующий использование

регистра BP для доступа к параметрам, переданным через стек. В этом

примере головная программа перед выполнением команды CALL поместила

четыре слова в стек. Подпрограмма загружает в BP указатель данных в

стеке. Заметим, что смещения, используемые для доступа к данным в

стеке, учитывают тот факт, что адрес возврата также был записан в

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

В подпрограмме этого примера в вершине стека лежит адрес

возврата, и регистр BP содержит смещение этой ячейки. Двумя байтами

ниже в стеке лежит помещенный последним параметр, регистр DX;

далее, через двухбайтовые интервалы - регистры CX, BX и AX. Таким

образом, правильным адресом для чтения параметра, содержащегося в

регистре DX, будет [BP+2], а другие адреса следуют через

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

регистре DX, попадает в регистр AX, CX в BX и т.д.

Подпрограмма может использовать регистр BP для адресации стека

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

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

время выполнения значения в регистрах трудно. Помещение этих

значений в стек и загрузка указателя этой области в регистр BP

решает проблему.

Многим подпрограммам в течение их выполнения также необходима

локальная память, и подпрограммы могут динамически расположить ее в

стеке. Всякий раз, когда программа вызывается, она может вычесть

размер этой области памяти из содержимого указателя стека. Так как

стек растет по направлению к младшим адресам, вычитание числа из

регистра SP идентично помещению в стек такого же количества данных

- за исключением тех данных, которые не инициализированы. После

этого подпрограмма может использовать регистр BP для адресации

такой области памяти. Когда наступает момент возврата, подпрограмма

может прибавить соответствующее значение к указателю стека, и тем

самым восстановить его прежнее значение. Динамическая организация

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

тогда, когда она необходима для работы, и не занимает эту память

все остальное время, поэтому программу можно выполнять на машине с

малым объемом памяти, что невозможно при другой организации данных.

Но лучшим является то, что программист не должен создавать сложную

подсистему управления памятью, так как все находится под

управлением стековой структуры.

Оператор возврата из подпрограммы на Фиг. 4.7 демонстрирует еще

одну возможность набора команд микропроцессора 8088. Команда

возврата из подпрограммы RET может иметь операнд, который

представляет собой значение, прибавляемое микропроцессором к

содержимому указателя стека после извлечения адреса возврата. В

примере используется значение 8; это означает, что восемь байт, или

четыре слова данных должны быть удалены из стека после возврата.

Эти значения исчезают навсегда. Результат тот же, какой был бы в

итоге извлечения значений из стека, чтобы уничтожить их; команда

возврата уже сделала это автоматически.

Такой метод удаления информации из стека срабатывает только в

случае параметров, которые вызывающая программа помешает в стек.

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

памяти из стека перед выполнением возврата. Она должна сделать это

явно, а не с помощью команды возврата, так как область данных лежит

между текущей вершиной стека и адресом возврата.

Подпрограмма может возвратить в стеке некоторую информацию

вызывающей программе. Если вызывающая программа помешает параметры

в стек, подпрограмма может изменить их значения и оставить в стеке,

а вызывающая программа может извлечь их после возврата. Если

подпрограмма возвращает только один параметр, но вызывалась с тремя

параметрами в стеке, то выполнить возврат она может с помощью

команды RET 4. При этом последние два параметра извлекаются из

стека и только возвращаемый параметр остается в стеке.

В гл.10, где мы используем подпрограммы на языке ассемблера с

языками высокого уровня, головная программа помещает параметры в

стек. Но эти параметры - адреса данных, а не собственно данные. Это

означает, что ассемблерная подпрограмма не должна возвращать

параметры в стеке и обязана извлечь все параметры из стека при

возврате.


Untitled

Арифметические команды

Арифметические команды любого микропроцессора привлекают к себе

наибольшее внимание. Каждый заинтересован в выполнении

арифметических вычислений, и именно эти команды проделывают такую

работу. Хотя их немного, они выполняют большинство преобразований

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

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

Команды пересылки используют большинство принципов работы

команд микропроцессора 8088, а при изучении арифметических команд

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

Сложение

Команда ADD выполняет сложение указанных операндов, представленных

в двоичном дополнительном коде. Микропроцессор помещает результат

на место первого операнда после того, как сложит оба операнда.

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

в соответствии с результатом сложения. Например, команда

ADD AX,BX

складывает содержимое регистра BX с содержимым регистра AX, и

оставляет результат в регистре AX. Регистр флагов сообщает о том,

был ли результат нулевым, отрицательным, имел ли четность, перенос

или переполнение.

Фиг. 4.8 кратко иллюстрирует варианты команды ADD.

Существуют две формы сложения, 8=битовое и 16=битовое. В различных

формах сложения принимают участие различные регистры. Ассемблер

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

Содержимое байтового регистра (например, CH) не может быть

прибавлено к ячейке памяти, которая не имеет тип BYTE. Если ячейка

памяти является одним из операндов, она может быть либо

операндом=результатом, либо неизменяемым операндом. Тем самым

команда может прибавить содержимое регистра к ячейке памяти и

возвратить результат в память. Одним из операндов может также быть

непосредственное значение. На Фиг. 4.9 показан листинг ассемблера с

накоторыми арифметическими командами.

Команда сложения с переносом ADC - это та же команда ADD, за

исключением того, что в сумму включается флаг переноса. Для любой

формы команды ADD существует сравнимая с ней команда ADC.

ЪДДДДДДДДї ЪДДДДДДДДї ЪДДДДДДДДї

і AX і і AX і і AX і

і BX і і BX і і BX і

і CX і і CX і ДДДДДДД> і CX і

і DX і і DX і і DX і

АДДДДДДДДЩ АДДДДДДДДЩ АДДДДДДДДЩ

ЪДДДДДДДДї + ЪДДДДДДДДї ЪДДДДДДДДї

і SI і і SI і і SI і

і DI і і DI і і DI і

і BP і і BP і ДДДДДДД> і BP і

і SP і і SP і і SP і

АДДДДДДДДЩ АДДДДДДДДЩ АДДДДДДДДЩ

ЪДДДДДДДДї

і Память і

і(слова) і

АДДДДДДДДЩ

ЪДДДДДДДДДї

іНепосред-і

іственный і

АДДДДДДДДДЩ

ЪДДДДДДДДДї ЪДДДДДДДДДї ЪДДДДДДДДДї

і AH і і AH і і AH і

і AL і і AL і і AL і

і BH і і BH і і BH і

і BL і і BL і і BL і

і CH і і CH і і CH і

і CL і + і CL і і CL і

і DH і і DH і і DH і

і DL і і DL і і DL і

АДДДДДДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДЩ

ЪДДДДДДДДДї ЪДДДДДДДДДї

і Память і ДДДДДДД> і Память і

і(слова) і і(слова) і

АДДДДДДДДДЩ АДДДДДДДДДЩ

ЪДДДДДДДДДї

іНепосред-і

іственный і

АДДДДДДДДДЩ

Фиг. 4.8 Операции сложения

Обе команды сложения, как ADD, так и ADC, устанавливают равным

1 флаг переноса, если произошел перенос из старшего разряда

результата. Команда ADD складывает два операнда, не обращая

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

Если флаг переноса равен 0, результат равен результату выполнения

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

больше результата команды ADD. Таким образом, программа может

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

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:49

Фиг. 4.9 Арифметические команды Page 1-1

PAGE ,132

TITLE Фиг. 4.9 Арифметические команды

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 EXBYTE LABEL BYTE

0000 EXWORD LABEL WORD

0000 03 1E 0000 R ADD BX,EXWORD ; BX <- BX + [EXWORD]

0004 29 0E 0000 R SUB EXWORD,CX ; [EXWORD] <- [EXWORD] - CX

0008 12 3E 0000 R ADC BH,EXBYTE ; BH <- BH + [EXBYTE] + Carry

000C 18 0E 0000 R SBB EXBYTE,CL ; [EXBYTE] <- [EXBYTE] - CL - Carry

0010 F7 1E 0000 R NEG EXWORD ; [EXWORD] <- -[EXWORD]

0014 FE 06 0000 R INC EXBYTE ; [EXBYTE] <- [EXBYTE] + 1

0018 4E DEC SI ; SI <- SI - 1

0019 81 C7 00C8 ADD DI,200 ; DI <- DI + 200

001D 83 EC 64 SUB SP,100 ; SP <- SP - 100

0020 83 D1 0A ADC CX,10 ; CX <- CX + 10 + Carry

0023 83 1E 0000 R 14 SBB EXWORD,20 ; [EXWORD] <- [EXWORD] - 20 - Carry

0028 3B C3 CMP AX,BX ; Установка флагов по AX - BX

002A 81 FE 01F4 CMP SI,500 ; Установка флагов по SI - 500

002E F6 26 0000 R MUL EXBYTE ; AX <- AL * [EXBYTE]

0032 F7 EB IMUL BX ; DX:AX <- AX * BX

0034 F7 36 0000 R DIV EXWORD ; AX <- DX:AX / [EXWORD]

0038 F6 FD IDIV CH ; AL <- AX / CH

003A 27 DAA ; Десятичное коррекция для сложения

003B 2F DAS ; Десятичное коррекция для вычитания

003C 37 AAA ; ASCII коррекция для сложения

003D 3F AAS ; ASCII коррекция для вычитания

003E D4 0A AAM ; ASCII коррекция для умножения

0040 D5 0A AAD ; ASCII коррекция для деления

0042 98 CBW ; AX <- расширенное по знаку AL

0043 99 CWD ; DX:AX <- расширенное по знаку AX

0044 CODE ENDS

END

Фиг. 4.9 Арифметические команды

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:54

Фиг. 4.10 Пример вычислений с повышенной точностью Page 1-1

PAGE ,132

TITLE Фиг. 4.10 Пример вычислений с повышенной точностью

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ???????? VALUE1 DD ? ; Область данных размером 32 разряда

0004 ???????? VALUE2 DD ?

;----- Сложение двух 32-разрядных чисел

0008 A1 0000 R MOV AX,WORD PTR VALUE1

000B 01 06 0004 R ADD WORD PTR VALUE2,AX ; Сложение младших 16 разрядов

000F A1 0002 R MOV AX,WORD PTR VALUE1+2

0012 11 06 0006 R ADC WORD PTR VALUE2+2,AX ; Сложение старших 16 разрядов

;----- Вычитание двух 32-разрядных чисел

0016 A1 0000 R MOV AX,WORD PTR VALUE1

0019 29 06 0004 R SUB WORD PTR VALUE2,AX ; Вычитание младшей части

001D A1 0002 R MOV AX,WORD PTR VALUE1+2

0020 19 06 0006 R SBB WORD PTR VALUE2+2,AX ; Вычитание старшей части

0024 CODE ENDS

END

Фиг. 4.10 Пример с повышенной точностью

Фиг. 4.10 иллюстрирует сложение пары 32=битовых чисел; в

примере складываются 32=битовые числа поля VALUE1 и поля VALUE2, а

результат помещается в поле VALUE2. Заметим, что один из операндов

должен быть помещен в регистр. В первом сложении используеся

команда ADD, так как текущее значение флага переноса несущественно

для первого сложения. После соответствующего размещения операндов

программа на Фиг. 4.10 выполняет второе сложение с помощью команды

ADC, с учетом флага переноса, установленного предыдущим сложением.

Это также хороший пример показывающий, почему команда MOV не

устанавливает никаких флагов. Если бы команда MOV изменяла флаги,

выполнить правильно второе сложение было бы гораздо труднее.


Untitled

Вычитание

Команды вычитания SUB и SBB идентичны командам сложения, за

исключением того, что они выполняют вычитание, а не сложение. Вы

можете скорректировать Фиг.4.8 для вычитания, изменив знак "+" на

знак "-". Вычитание устанавливает флаги состояния в соответствии с

результатом операции, причем флаг переноса теперь означает заем.

Например, команда

SUB AX, BX

вычитает значение регистра BX из значения регистра AX, а затем

помещает результат в регистр AX. Флаги состояния изменяются так,

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

Команда вычитания с заемом SBB решает задачи вычитания

повышенной точности. Команда SBB учитывает флаг заема при

вычитании, т.е. значение заема вычитается из результата,

полученного при нормальном вычитании. На Фиг. 4.10 показано

вычитание повышенной точности, выполненное с теми же значениями,

что и сложение. В этом примере значение поля VALUE1 вычитается из

значения поля VALUE2, помещая результат в поле VALUE2.


Untitled

Арифметика с одним операндом

Команда отрицания NEG - это оператор смены знака. Она меняет знак

двоичного дополнительного кода операнда=байта или слова. Другие

две команды с одним операндом изменяют значение оперенда на 1.

Команда увеличения INC прибавляет 1 к операнду, а команда

уменьшения DEC вычитает 1 из операнда. С помощью команд увеличения

и уменьшения можно перемещать указатель по массиву ячеек памяти.

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

по циклу уменьшает счетчик, а когда его значение достигнет 0, цикл

завершается.

Все эти однооперандные команды могут иметь в качестве операнда

как байт, так и слово. Если любая из этих команд указывает ячейку

памяти с помощью одного из косвенных способов адресации, например

[BX+SI], ассемблер нуждается в помощи, так как ему необходимо знать

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

Команда может использовать модификаторы BYTE PTR или WORD PTR,

чтобы описать операнд.

Эти три команды влияют на регистр состояния точно так же, как

это делают арифметические команды. Прибавление 1, вычитание 1 и

вычитание из 0 идентичны соответственно INC, DEC и NEG; однако

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


Untitled

Сравнение

Команда сравнения CMP сравнивает два числа, вычитая одно из

другого. Она не записывает результат, но флаги состояния

устанавливает в соответствии с результатом. Эта команда изменяет

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

как и команда вычитания; однако команды сравнения с заемом не

существует.

Сравнение с повышенной точностью требует чуть больше усилий,

чем сравнение байтов или слов. Фактически в этих случаях много

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

Фиг. 4.11 показано сравнение пары 32=битовых чисел в памяти с

использованием регистра AX в качестве области временного хранения.

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

результате своего выполнения устанавливает коды условия. Флаг

переноса определяет, какое из чисел больше: если флаг равен 1,

число VALUE больше.

Вторая программа на Фиг. 4.11 проверяет два 32=битовых числа на

равенство. Программа сохраняет младший результат, а затем

комбинирует его со старшим, и таким образом выясняет

эквивалентность результата нулю. Команда OR описана в следующем

разделе, а здесь существенно то, что она комбинирует два значения

так, что окончательное значение равно 0 тогда и только тогда, когда

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

сравнения - значение флага нуля; если он равен 1, числа равны.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:59

Фиг. 4.11 Сравнение чисел заданных с повышенной точностью Page 1-1

PAGE ,132

TITLE Фиг. 4.11 Сравнение чисел заданных с повышенной точностью

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ???????? VALUE1 DD ? ; Область данных размером 32 разряда

0004 ???????? VALUE2 DD ?

0008 FIG4_11 PROC NEAR

;----- Сравниваются по неравенству два 32-разрядных числа

0008 COMPARE_UNEQUAL:

0008 A1 0000 R MOV AX, WORD PTR VALUE1

000B 2B 06 0004 R SUB AX, WORD PTR VALUE2 ; Вычитание младшей части

000F A1 0002 R MOV AX, WORD PTR VALUE1+2

0012 1B 06 0006 R SBB AX, WORD PTR VALUE2+2 ; Вычитание старшей части

0016 C3 RET ; Возврат с установленными флагами

;----- Сравниваются по равенству два 32-разрядных числа

0017 COMPARE_EQUAL:

0017 A1 0000 R MOV AX, WORD PTR VALUE1

001A 2B 06 0004 R SUB AX, WORD PTR VALUE2 ; Вычитание младшей части

001E 8B D8 MOV BX, AX ; В BX младшая часть результата

0020 A1 0002 R MOV AX, WORD PTR VALUE1+2

0023 1B 06 0006 R SBB AX, WORD PTR VALUE2+2 ; Вычитание старшей части

0027 0B C3 OR AX, BX ; Объединение результатов

0029 C3 RET ; Флаг Z показывает равенство

002A FIG4_11 ENDP

002A CODE ENDS

END

Фиг. 4.11 Сравнение с повышенной точностью


Untitled

Десятичная коррекция

Те же самые команды, что и для чисел в двоичном дополнительном

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

двоично=десятичном коде BCD. Однако результат арифметических

операций может оказаться неправильным для двоично=десятичного

представления. Команды десятичной коррекции корректируют

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

Десятичная коррекция для сложения DAA и десятичная коррекция

для вычитания DAS используются для работы только с упакованными

десятичными числами. В упакованном десятичном числе каждый байт

содержит две десятичные цифры. Команды DAA и DAS работают только с

байтом данных, содержащимся в регистре AL. В связи с этими

присущими командам ограничени- ями, ни у DAA, ни у DAS операндов

нет.

На Фиг. 4.12 показаны два примера. В первом примере

складываются два упакованных десятичных числа. Оба числа состоят из

двух десятичных цифр, поэтому они представлены единственными

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

регистре AL. Непосредственно за этим следует команда DAA, которая

корректирует результат сложения, преобразуя его в упакованную

десятичную форму. После команды DAA в регистре AL остается

правильное упакованное десятичное число в диапазоне 0 - 99. Если

результат меньше 100, регистр содержит ответ, а флаг переноса

содержит 0. Если результат находится в диапазоне 100 - 198, то в

регистре AL остаются две младшие десятичные цифры, а флаг переноса

установлен равным 1, показывая, что был перенос.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:04

Фиг. 4.12 Пример двоично-десятичной арифметики Page 1-1

PAGE ,132

TITLE Фиг. 4.12 Пример двоично-десятичной арифметики

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ?? BCD1 DB ? ; Две десятичные цифры упакованного числа

0001 ?? BCD2 DB ?

0002 ???? BCD1L DW ? ; Четыре десятичные цифры упакованного числа

0004 ???? BCD2L DW ?

0006 FIG4_12 PROC NEAR

;----- Сложение двух упакованных чисел

0006 DAA_EXAMPLE:

0006 A0 0000 R MOV AL, BCD1 ; Взять первое упакованное число

0009 02 06 0001 R ADD AL, BCD2 ; Добавить второе

000D 27 DAA ; Преобразование упакованного числа

000E C3 RET

;----- Сложение двух упакованных чисел размером по 4 цифры (2 байта)

000F DAA_LONG:

000F A0 0002 R MOV AL, BYTE PTR BCD1L

0012 02 06 0004 R ADD AL, BYTE PTR BCD2L ; Добавление младшей части числа

0016 27 DAA ; Коррекция упакованного числа

0017 A2 0004 R MOV BYTE PTR BCD2L, AL ; Сохранение младшей части

001A A0 0003 R MOV AL, BYTE PTR BCD1L+1

Фиг. 4.12 Примеры вычислений с BCD (начало)

001D 12 06 0005 R ADC AL, BYTE PTR BCD2L+1 ; Добавление старшей части числа

0021 27 DAA ; Коррекция упакованного числа

0022 A2 0005 R MOV BYTE PTR BCD2L+1, AL ; Сохранение старшей части

0025 C3 RET

;----- Сложение двух упакованных чисел

0026 DAS_EXAMPLE:

0026 A0 0000 R MOV AL, BCD1

0029 2A 06 0001 R SUB AL, BCD2 ; Вычитание значений

002D 2F DAS ; Коррекция упакованного числа

002E C3 RET

002F FIG4_12 ENDP

002F CODE ENDS

END

Фиг. 4.12 Примеры вычислений с BCD (продоложение)

Команда DAA правильно устанавливает регистр флагов. Если в

результате сложения получилось значение в диапазоне 100 - 198, флаг

переноса показывает перенос из старшей десятичной позиции.

Аналогично, нулевой результат оставляет установленным в 1 флаг

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

знака и переполнения не имеют значения, хотя флаг знака

устанавливается, если установлен старший бит регистра AL. Команда

DAA использует флаг дополнительного переноса для определения вида

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

переноса неопределен.

Второй пример на Фиг. 4.12 демонстрирует десятичное сложение

повышенной точности. Оно весьма похоже на двоичную арифметику с

повышенной точностью, за исключением того, что после сложения

каждого байта появляется команда DAA. Из=за ограничений, присущих

команде DAA, в примере нельзя было сложить два упакованных

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

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

арифметика.

Наконец, на Фиг. 4.12 показано, как использовать команду DAS.

Это делается так же, как и при сложении, но команда DAS должна

следовать за вычитанием. Здесь тоже допустимы только байтовые

операции.


Untitled

Симовльная коррекция: сложение и вычитание

Команды символьной коррекции очень похожи на команды десятичной

коррекции. Они следуют за сложением или вычитанием распакованных

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

команды десятичной коррекции DAA и DAS для упакованных десятичных

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

десятичных чисел. В распакованных десятичных числах цифры от 0 до

9 представляются одним байтом. Такая конструкция числа называется

символьной десятичной из=за того, что такие числа просто

преобразовывать в символьный вид и наоборот (прибавлять и вычитать

30H, соответственно).

После сложения двух распакованных десятичных чисел программа

обычно выполняет команду символьной коррекции при сложении AAA,

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

представление десятичного числа. Правила сложения идентичны

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

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

большее 9, командам AAA и AAS требуется для работы не только

регистр AL. В случае команды AAA младшая цифра скорректированного

результата остается в регистре AL. Если десятичное сложение привело

к переносу из младшей цифры, команда AAA устанавливает равными 1

флаги переноса и дополнительного переноса. В других случаях она

сбрасывает их в 0. Содержимое других флагов не определено после

команды коррекции. Команды символьной коррекции отличаются от

десятичных команд тем, что они влияют на содержимое регистра AH, а

также устанавливают флаг переноса, если есть перенос из младшей

значащей цифры.

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

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

и результат этой байтовой операции должен быть помещен в регистр

AL. Результат команды символьной коррекции остается в регистре AL,

и если вычитание привело к появлению заема, команда AAS уменьшает

регистр AH, а также устанавливает флаги переноса и дополнительного

переноса. В противном случае флаги сбрасываются. Другие флаги после

команды не определены.


Untitled

Умножение

Микропроцессор 8088 значительно мощнее предшествовавших ему

8=битовых устройств. Одна из причин увеличения мощности -

добавление команд умножения и деления к набору команд

микропроцессора. В прежних микропроцессорах выполнение операций

умножения и деления требовало вызова подпрограмм на языке

ассемблера.

Существует две команды умножения. Покоманде MUL умножаются два

целых числа без знака и дает результат без знака. По команде IMUL

умножаются целые числа со знаком. При умножении целых чисел в

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

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

и значение.

Обе команды умножения работают как с байтами, так и со словами.

Однако диапазон форм представления операндов гораздо уже, чем для

команд сложения и вычитания. Фиг. 4.13 иллюстрирует варианты

команды умножения. Чтобы умножить 8 бит на 8 бит, один из операндов

должен быть в регистре AL, а результат всегда оказывается в

регистре AX. Результат может иметь длину вплоть до 16 бит

(максимальное получаемое значение равно 255 * 255 = 65025). Чтобы

умножить 16 бит на 16 бит, один из операндов нужно поместить в

регистр AX. Результат, который может быть длиной до 32 бит

(максимальное значение 65535 * 65535 < 2+32) помещается в пару

регистров; в регистре DX содержатся старшие 16 бит результата, а

врегистре AX - младшие 16 бит. Умножение не допускает

непосредственного операнда.

Установка флагов командой умножения несколько отличается от

других арифметических команд. Единственные имеющие смысл два флага

- это флаги переноса и переполнения, и они по=разному

устанавливаются двумя командами.

Команда умножения без знака MUL устанавливает оба флага, если

старшая половина резул в регистре AL получится 2AH; команда AAM

преобразует этот результат, оставляя в регистре AH число 04H, и

02H в регистре AL - или распакованное десятичное число 42 в паре

регистров AH:AL.

ЪДДДДДДДДї

і AX і

і BX і

і CX і

і DX і

ЪДДДДДДДДї АДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї

і AX і * ЪДДДДДДДДї ДДДДД> і DX і AX і

АДДДДДДДДЩ і SI і АДДДДДДДДБДДДДДДДДЩ

і DI і

і BP і

і SP і

АДДДДДДДДЩ

ЪДДДДДДДДї

і Память і

і(слова) і

АДДДДДДДДЩ

(a) Умножение слов

ЪДДДДДДДДДї

і AH і

і AL і

і BH і

і BL і

і CH і

і CL і

і DH і

і DL і

ЪДДДДДДДДї АДДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї

і AL і * ЪДДДДДДДДДї ДДДДД> і AH і AL і

АДДДДДДДДЩ і Память і АДДДДДДДДБДДДДДДДДЩ

і(байты) і

АДДДДДДДДДЩ

(b) умножение байтов

Фиг. 4.13 Операции умножения

Целое умножение со знаком (IMUL) устанавливает флаги переноса и

переполнения в соответствии с тем же критерием, т.е. эти флаги

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

тоько своей младшей половиной. Однако, поскольку число имеет знак,

то задача не сводится только к сравнению старшей половины

результата с нулем. Команда IMUL устанавливает флаги, если старшая

половина результата не является распространением знака младшей. Это

значит, что в случае положительного результата проверка будет такой

же, как для команды MUL - установка флага происходит при ненулевой

старшей половине результата (но самый старший бит равен нулю,

указывая на положительность результата). В случае отрицательного

результата IMUL устанавливает флаги, если старшая половина

результата состоит не только из едениц (но старший бит равен 1,

указывая на отрицательность результата). Например, перемножение

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

результат менше -128 - наименьшего числа, представимого в одном

байте. Другой пример, перемножение слов с положительным

результатом, устанавливает флаги, если результат превышает 32 767 -

наиболшее представимое одним словом число.


Untitled

Символьная коррекция: умножение

Когда в программе перемножаются два неупакованных десятичных числа,

результат в регистре AL является двоичным числом. Поскольку

наибольшее неупакованное десятичное в двоичном представлении число

равно 9, то максимальный результат при BCD-умножении без упаковки

равен 81. Однако, этот результат не является значимым неупакованным

BCD-представлением этого числа. Команда символьной коррекции для

умножения (AAM - от ASCII Adjust for Multiply) переводит такой

двоичный результат в неупакованный десятичный. Командой AAM старшая

десятичная цифра результата помещается в регистр AH, а в AL

остается младшая десятичная цифра. Например, если программа

перемножает значения 6 и 7 и результат в AL равен 2AH, то команда

AAM преобразует результат, помещая в AH 04H, а в AL - 02H, что

соответсвует неупакованному десятичному числу 42 в регистрах AH:AL.

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

помощью деления числа в регистре AL на 10. Она помещает частное в

регистр AH, оставляя остаток в регистре AL. Команда AAM

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

регистре AL. Так как результат - распакованное десятичное число,

знак всегда положителен, а знак нуля устанавливается равным 1,

только если исходное число кратно 10 - т.е. если младшая значащая

десятичная цифра равна 0. Остальные флаги после команды AAM

остаются неопределенными. Флаг переноса теперь не имеет смысла,

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

дает результата, превосходящего число, представимое двумя

десятичными цифрами.

Программа также всегда может использовать команду AAM для

деления двоичного числа в регистре AL на 10. В таком виде она может

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

делит однобайтовое число в регистре AL на 10. Частное помещается в

регистр AH, остаток - в регистр AL.


Untitled

Команда деления

Одна из арифметических операций микропроцессора 8088 - деление.

Как и в случае умножения, существует две формы деления - одна для

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

коде IDIV. Любая форма деления может работать с байтами и словами.

Команда деления DIV выполняет деление без знака и дает как

частное, так и остаток. Как и в случае умножения, операнды должны

находиться на специфических местах. Также подобно умножению, для

деления одно из этих чисел в два раза длиннее обычного операнда:

делимое является операндом двойной длины. Байтовые команды делят

16=битовое делимое на 8=битовый делитель. В результате деления

получается два числа. Деление помещает частное в регистр AL, а

остаток в регистр AH. Такое расположение операндов делает команду

деления дополнительной к команде умножения; это означает, что

умножение регистра AL на байтовый операнд, а затем деление регистра

AX на тот же операнд возвращает регистр AL к его первоначальному

состоянию. Регистр AH будет содержать 0, поскольку остатка нет.

Фиг. 4.14 схематически иллюстрирует команду деления.

ЪДДДДДДДДї

і AX і

і BX і

і CX і

і DX і Остаток

ЪДДДДДДДДВДДДДДДДДї АДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї

і DX і AX і * ЪДДДДДДДДї ДДДДД> і DX і AX і

АДДДДДДДДБДДДДДДДДЩ і SI і АДДДДДДДДБДДДДДДДДЩ

і DI і

і BP і

і SP і

АДДДДДДДДЩ

ЪДДДДДДДДї

і Память і

і(слова) і

АДДДДДДДДЩ

(a) Деление слов

ЪДДДДДДДДДї

і AH і

і AL і

і BH і

і BL і

і CH і

і CL і

і DH і

і DL і Остаток

ЪДДДДДДДДї АДДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї

і AL і * ЪДДДДДДДДДї ДДДДД> і AH і AL і

АДДДДДДДДЩ і Память і АДДДДДДДДБДДДДДДДДЩ

і(байты) і

АДДДДДДДДДЩ

(b) Деление байтов

Фиг.4.14 Операции деления

Команда, работающая со словами, делит 32=битовое делимое на

16=битовый делитель. Делимое находится в паре регистров DX:AX,

причем регистр DX содержит старшую значащую часть, а регистр AX -

младшую. Деление слов помещает частное в регистр AX, а остаток в

регистр DX. Здесь опять=таки умножение и деление взаимно

дополнительны: умножение регистра AX на слово, а затем деление его

на то же слово возвращает регистр AX к его первоначальному

состоянию. Теперь регистр DX становится нулевым, так как остатка

нет.

Ни один из флагов состояния не определен после команды деления.

Однако во время деления может возникнуть ошибка значимости. Если

частное больше, чем может быть помещено в регистр результата,

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

байтов частное должно быть меньше 256, и меньше 65535 в случае

операции со словами. Микропроцессор не устанавливает никаких флагов

для сигнализации при этой ошибке, вместо этого он выполняет

программное прерывание уровня 0. Как и в случае других программных

прерываний, это прерывание по делению на 0 сохраняет флаги, регистр

кодового сегмента и указатель команды в стеке. Затем микропроцессор

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

адресу 0. Подпрограмма деления на 0 должна предпринять

соответствующие действия по обработке этой ошибки. (Прерывание 0

называется делением на 0 даже тогда, когда это прерывание возбудило

деление на число, отличное от нуля. В документации фирмы Intel это

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

было бы назвать прерыванием по переполнению после деления).

Деление целых чисел со знаком IDIV отличается от команды DIV

только тем, что оно учитывает знаки обоих операндов. Если результат

положителен, все происходит так же, как было описано для команды

DIV, за исключением того, что максимальное значение частного

соответственно равно 127 и 32767 для байтов и слов. Если результат

отрицателен, частное усекается, а остаток имеет тот же знак, что и

делимое. Минимальные значения частных для отрицательных результатов

-128 и -32768 для байтов и слов.

Делимое (AX) Делитель(MOD-R/M) Частное (AL) Остаток(AH)

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

7 2 3 1

7 -2 -3 1

-7 2 -3 -1

-7 -2 3 -1

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

Фиг. 4.15 Примеры деления со знаком

На Фиг. 4.15 показаны четыре примера деления, а также

полученные в них результаты. Все примеры, приведенные здесь,

байтовые, т.е. делимое находится в регистре AX, а делитель

указывается байтом mod=r/m. Деление помещает частное в регистр AL,

а остаток в регистр AH. Заметим, что знак остатка всегда тот же,

что и у делимого. Значение частного всегда усекается в направлении

нуля.


Untitled

Символьная коррекция: деление

Так же, как и другие арифметические операции, деление имеет

соответствующую команду для обслуживания распакованных десятичных

чисел. Однако в отличие от других команд, програииа должна

выполнять команду символьной коррекции деления AAD до выполнения

команды деления. Команда AAD берет две цифры распакованного

десятичного числа из регистра AX (старшая значащая цифра адресуется

в регистре AH) и преобразует его в двоичное число в регистре AL,

оставляя в регистре AH нуль. После этого в регистре AX оказывается

значение, готовое для деления на десятичное распакованное число,

состоящее из одной цифры. Команда AAD устанавливает коды условия в

соответствии с результатом в регистре AL. Флаги нечетности, знака

и нуля соответствуют значению AL, а остальные неизвестны.

Есть случаи, когда после деления может оказаться, что частное -

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

случае переполнение после деления не регистрируется. В худшем

случае 99 делится на 1, давая частное 99, число, меньшее

максимального как для команды DIV, так и для команды IDIV, так что

переполнение не возникает. Однако это число больше максимального

распакованного десятичного числа из одной цифры, которое равно 9.

Существует два метода борьбы с таким случаем. Во=первых, после

каждой последовательности команд AAD=DIV можно проверять, не

превысило ли частное 9, и вызывать соответствующую обработку

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

деления, чтобы преобразовать частное в распакованное десятичное

число из двух цифр. Но в этом случае программа должна где=либо

сохранить остаток до выполнения команды AAM, так как она разрушит

содержимое регистра AH. Этот способ порождает десятичный результат,

состоящий из двух цифр, после деления значения из двух цифр на

число из одной цифры. Но если распакованный десятичный делитель

нулевой, то деление вызовет прерывание по делению на нуль,

показывая, что произошло переполнение при делении.


Команда преобразования

Когда программа выполняет целое деление со знаком, возникает

проблема, если делимое - байтовый операнд. Иногда нужно разделить

байтовое значение на байтовое, но команда деления требует, чтобы

делимое занимало регистр AX. В случае деления со знаком

необходимо, чтобы значение в регистре AX было правильной копией

числа, представленного в дополнительном коде. Команда

преобразования байта в слово CBW решает эту задачу; она берет число

из регистра AL и расширяет его знак в регистр AH. Таким образом,

если значение в регистре AL положительно, команда заполняет регистр

AH нулями, если же значение в регистре AL отрицательно, она

устанавливает в регистре AH все единицы. Команда CBW загружает в

регистр AX 16=битовое число, равное значению исходного байта в

регистре AL. В случае деления слов команда преобразования слова в

двойное слово CWD выполняет идентичную функцию. Команда CWD

расширяет знак слова из регистра AX в регистр DX. Эти две команды

расширяют операнды до выполнения целого деления со знаком.

В случае целого деления без знака при тех же условиях знака уже

не существует, и его не надо расширять в старшую часть делимого. В

этом случае правильным является заполнение регистра AH (или

регистра DX) нулями перед делением. Существует много команд,

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

непосредственным операндом, или просто

SUB AH,AH

что гарантирует обнуление регистра AH.


Untitled

Арифметический пример

Чтобы проиллюстрировать функции, которые мы рассмотрели в

предыдущих разделах, давайте решим арифиетическую задачу на языке

ассемблера. Пример прост, но использует многие команды. Задача

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

которых некоторые числа постоянны, а другие переменны. Все числа

являются 16=битовыми целыми числами со знаком.

Формула вычислений:

A * 2 + B * C

X = ------------------

D - 3

Эта задача решается подпрограммой на языке ассемблера,

изображенной на Фиг. 4.16. Подпрограмма сначала выполняет два

умножения. Так как микропроцессор 8088 всегда помещает результат

16=битового умножения в пару регистров DX:AX, в примере результат

первого умножения переносится в пару регистров BX:CX перед

выполнением второго умножения. Когда оба умножения завершены,

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

32=битовые результаты, в программе требуется сложение повышенной

точности. Это сложение оставляет результат в DX:AX. В примере

знаменатель вычисляется в регистре CX, а затем на него делится

числитель. Программа записывает частное из регистра AX в переменную

результата X. Остаток в этой задаче игнорируется.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:10

Фиг. 4.16 Пример арифметических вычислений Page 1-1

PAGE ,132

TITLE Фиг. 4.16 Пример арифметических вычислений

;-------------------------------------------------------------

; Производятся вычисления по формуле

;

; A * 2 + B * C

; X = -------------------

; D - 3
;

; Все переменные - 16-разрядные целые числа со знаком

;-------------------------------------------------------------

Фиг. 4.16 Арифметический пример (начало)

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ???? X DW ? ; Память для переменных

0002 ???? A DW ?

0004 ???? B DW ?

0006 ???? C DW ?

0008 ???? D DW ?

000A FIG4_16 PROC NEAR

000A B8 0002 MOV AX, 2 ; Загрузка константы

000D F7 2E 0002 R IMUL A ; DX:AX = A * 2

0011 8B DA MOV BX, DX

0013 8B C8 MOV CX, AX ; BX:CX = A * 2

0015 A1 0004 R MOV AX, B

0018 F7 2E 0006 R IMUL C ; DX:AX = B * C

001C 03 C1 ADD AX, CX

001E 13 D3 ADC DX, BX ; DX:AX = A * 2 + B * C

0020 8B 0E 0008 R MOV CX, D

0024 83 E9 03 SUB CX, 3 ; CX = D - 3

0027 F7 F9 IDIV CX ; AX = (A*2 + B*C) / (D-3)

0029 A3 0000 R MOV X, AX ; Сохранение результата

002C C3 RET

002D FIG4_16 ENDP

002D CODE ENDS

END

Фиг. 4.16 Арифметический пример (продолжение)


Untitled

Логические операции

Следующий класс команд - логические команды. Эти команды, точно

так же, как и арифметические команды, преобразуют данные, но делают

это не арифметически. В то время как команды сложения и вычитания

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

значениями 0 и 1, которые использует ЭВМ. В общем случае, эти

команды позволяют программе выполнять битовые операции.

Четырьмя основными логическими командами являются AND (и), OR

(или), XOR (исключающее или), NOT (не). Существуют и другие

логические функции, состоящие из этих четырех функций, но в

микропроцессоре 8088 для них нет соответствующих команд. Эти четыре

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

кода.

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

основывается на определении единицы и нуля, как истины (TRUE) и лжи

(FALSE) соответственно. Предложение NOT TRUE (не истина) - это

FALSE (ложь), а предложение NOT FALSE (не ложь) - это TRUE

(истина). Команда NOT инвертирует все биты числа данных. Иначе

говоря, команда NOT эквивалентна вычитанию данных из величины,

состоящей из всех единиц. Фиг. 4.17 показывает, как оператор NOT

действует на единственный бит.

Значение NOT(Значение)

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

0 1

1 0

------------------------------------- Фиг. 4.17 Операция NOT

Остальные три логические функции имеют два операнда. На

Фиг.4.18 показаны результаты действий, произведенных каждой

функцией над парой бит.

X Y X AND Y X OR Y X XOR Y

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

0 0 0 0 0

0 1 0 1 1

1 0 0 1 1

1 1 1 1 0

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

Фиг. 4.18 Логические операции

Поскольку микропроцессор 8088 работает с байтами или словами,

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

байтовая команда выполняет логическое И со значениями нулевого бита

обеих операндов а помещает результат в бит 0 результата. Затем эта

команда повторяет функцию И с битами от первого до седьмого. В

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

операндов.

Функция AND равна 1 только тогда, когда оба операнда равны 1. В

терминах истинности, результат есть истина только тогда, когда и X,

и Y истинны. Функция OR дает 1, если хотя бы один из операндов

равен 1. Результат есть истина, если либо X, либо Y являются

истинными. Результат функции XOR равен 1, только если один из

операндов равен 1, а другой равен 0. Если же оба операнда равны 0,

или оба равны 1, то результат равен 0. Функция исключающее ИЛИ в

точности соответствует сложению, у которого игнорируется перенос.

Фиг. 4.19 иллюстрирует логические команды микропроцессора

8088. Команде NOT требуется один операнд, а ее форма идентична

команде NEG. Остальные логические команды копируют синтаксис команд

сложения и вычитания.

Когда микропроцессор 8088 делает логическую операцию, он

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

не арифметическая, флаги переноса и переполнения всегда

устанавливаются равными 0. Флаг дополнительного переноса после

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

флаги (знак, нуль) правильно отражают результат операции.

Исключение представляет команда NOT, которая не изменяет ни одного

флага.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:15

Фиг. 4.19 Логические команды Page 1-1

PAGE ,132

TITLE Фиг. 4.19 Логические команды

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 EXBYTE LABEL BYTE

0000 EXWORD LABEL WORD

0000 22 06 0000 R AND AL, EXBYTE ; AL <- AL and [EXBYTE]

0004 81 E3 9FEF AND BX, 1001111111101111B ; BX <- BX and 9FEFH

0008 80 26 0000 R 03 AND EXBYTE, 00000011B ; [EXBYTE] <- [EXBYTE] and 3

000D 08 2E 0000 R OR EXBYTE, CH ; [EXBYTE] <- [EXBYTE] or CH

0011 0B 16 0000 R OR DX, EXWORD ; DX <- DX or [EXWORD]

0015 0D FFF9 OR AX, 0FFF9H ; AX <- AX or 0FFF9H

0018 33 1E 0000 R XOR BX, EXWORD ; BX <- BX xor [EXWORD]

001C 30 1E 0000 R XOR EXBYTE, BL ; [EXBYTE] <- [EXBYTE] xor BL

0020 34 EF XOR AL, 0EFH ; AL <- AL xor 0EFH

0022 F7 D1 NOT CX ; CX <- not CX

0024 F6 16 0000 R NOT EXBYTE ; [EXBYTE] <- not [EXBYTE]

0028 F7 06 0000 R 0003 TEST EXWORD, 0003H ; Установка флагов по [EXWORD] and 3

002E 84 E0 TEST AH, AL ; Установка флагов по (AH and AL)

0030 A9 0002 TEST AX, 02H ; Установка флагов по (AX and 2)

0033 D1 C1 ROL CX, 1 ; Циклический сдвиг влево на 1

0035 D3 0E 0000 R ROR EXWORD, CL ; Циклический сдвиг вправо на CL

0039 D0 16 0000 R RCL EXBYTE, 1 ; Циклический сдвиг с переносом

; влево на 1

003D D3 DB RCR BX, CL ; Циклический сдвиг с переносом

; вправо на CL

003F D1 E0 SHL AX, 1 ; Сдвиг логический влево на 1

0041 D1 E0 SAL AX, 1 ; Сдвиг арифметический влево на 1

0043 D3 EB SHR BX, CL ; Сдвиг логический вправо на CL

0045 D0 3E 0000 R SAR EXBYTE, 1 ; Сдвиг арифметический вправо на 1

0049 CODE ENDS

END

Фиг. 4.19 Логические команды

Первоочередное назначение логических операций в микропроцессоре

8088 - работа с битами. Самой малой единицей данных, с которой

может работать этот микропроцессор, является байт. Ни одна из

арифметических команд не может непосредственно выделить или

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

обрабатывать отдельные биты.

Почему интересны однобитовые операции? Во многих случаях

программа должна хранить значение индикатора - истина - ложь. Этот

бит может означать, что печатающее устройство занято, что нажата

регистровая клавиша, или что инициализация программы выполнена. В

таких случаях расточительно отводить байт для хранения

единственного бита информации. Программа может объединить несколько

таких битов в одном байте, если у нее есть способ выделения

отдельных битов для их проверки и установки. Такое объединение

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

ввода=вывода, которые имеют различные адреса. Устройству

ввода=вывода гораздо проще работать с разными битами по одному

адресу, чем распознавать многие адреса.

Логические команды могут выделить отдельные биты в байте или

слове так, что они могут быть установлены, сброшены, проверены. Для

выделения битов эти команды используют маску. Значение маски

используется командой побитно. Чтобы установить какой=либо один

бит, нужно использовать команду OR. В этом случае все значения

маски - нули, кроме единицы на месте устанавливаемого бита. Команда

OR над маской и другим операндом устанавливает 1 в выбранном бите,

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

оператор AND может сбросить единственный бит. В маске все разряды

единичные, кроме сбрасываемого бита. Этот бит сбросится в 0, а

остальные останутся без изменений.

Программисты не используют функцию исключающее или столь же

часто, как команды AND и OR, но она тоже бывает полезна. Команда

может выполнить взаимное дополнение одного бита с данными. Запишите

маску для команды XOR так, чтобы на месте инвертируемого бита была

1, а на всех других местах 0. Когда команда XOR выполнится, биты,

соответствовавшие нулям, останутся без изменений, а биты,

соответствовавшие единицам маски, инвертируются. Если начальное

значение бита было 0, 1 XOR 0 дает 1, дополнение к 0, а если

начальное значение было 1, 1 XOR 1 дает 0, дополнение к 1.

Последняя логическая команда - TEST (проверка). Эта команда

идентична команде AND, за исключением того, что она не записывает

результат, но устанавливает флаги в соответствии с ним, т.е.

команда TEST соответствует команде AND, как команда CMP

соответствует команде SUB. Эта команда проверяет заданный бит, или

набор битов внутри байта или слова.

Как работает команда проверки? Предположим, программа хочет

проверить младший значащий бит байта, бит 0. Программа порождает

маску 01H либо в регистре, либо как непосредственное значение.

Команда TEST (или AND) дает результат с гарантированными нулями по

всем позициям, за исключением бита 0; значение бита 0 отражает

значение оригинала. Если нулевой бит оригинала содержит 0, то бит

остается нулевым. Если он сначала единичен, результат ненулевой, и

флаг нуля сбрасывается; если же бит 0, результат нулевой, и флаг

нуля устанавливается. Таким образом, программа может проверить

единственный бит, выполняя команды TEST и AND с маской, которая

имеет единственную единицу на месте проверяемого бита; регистр

флагов отразит состояние этого единственного бита. Команда TEST

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

команда не изменяет поле результата.


Untitled

Операции сдвига и поворота

Остальные логические команды на Фиг. 4.19 выполняют сдвиги данных.

Команда сдвига перемещает все биты в поле данных либо вправо, либо

влево. Это можно проиллюстрировать церковной скамьей, на которой

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

и садится на край скамьи, остальные сидящие на ней сдвигаются на

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

такого сдвига вытесняется с нее. Команда сдвига делает в точности

то же самое, только вместо женщин и мужчин здесь выступают нули и

еденицы.

На Фиг.4.20 показаны восемь различных команд сдвига; у этих

команд имеются некоторые вариации. Сначала мы рассмотрим общие для

этих команд черты.

Как и другие логические команды, сдвиги работают с байтами и

словами. Каждая команда указывает единственный операнд. Этот

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

команды используют байт mod=r/m для описания операнда.

ЪДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДї

ЪДДДДї і ЪДДДДДДДДДї і і ЪДДДДДДДДДДї і ЪДДДДї

і CY Г<ДБДґ ДАННЫЕ Г<Щ АД>ґ ДАННЫЕ ГДБД>ґ CY і

АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ

ROL ROR

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і ЪДДДДї ЪДДДДДДДДДї і і ЪДДДДДДДДДДї ЪДДДДї і

АДДґ CY Г<ДДДґ ДАННЫЕ Г<ДДЩ АДДД>ґ ДАННЫЕ ГДДД>ґ CY ГДЩ

АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ

RCL RCR

ЪДДДДї ЪДДДДДДДДДї ЪДДДДДДДДДДї ЪДДДДї

і CY Г<ДДДґ ДАННЫЕ Г<ДДД 0 0 ДДД>ґ ДАННЫЕ ГДДД>ґ CY і

АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ

SHL SHR

ЪДДДДДДДї

і і

ЪДДДДї ЪДДДДДДДДДї і ЪДДДБДДДДДДї ЪДДДДї

і CY Г<ДДДґ ДАННЫЕ Г<ДДД 0 АДД>ґ ДАННЫЕ ГДДД>ґ CY і

АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ

SAL SAR

Фиг. 4.20 Операции сдвига.

Во всех командах сдвига определяется счетчик сдвигов, т.е.

программа указывает число битов, на которое надо сделать сдвиг; это

число и есть счетчик сдвигов. Его наиболее распространенное

значение - единица. Такой счетчик сдвигает биты операнда на одну

позицию. Однако команда может задать произвольный счетчик сдвигов,

занося его значение в регистр CL пред сдвигом. Если в команде

указано, что счетчик сдвигов надо взять из регистра CL, значение

этого регистра определяет число сдвигов битов. Число в регистре CL

может быть любым от 0 до 255, но его практически имеющие смысл

значения лежат в пределах 0 - 16. Значение 0 не вызывает сдвига, а

любое значение больше 16 сдвигает битов больше, чем содержит

операнд.

Другая общая черта команд сдвига - это установка флага

переноса. Бит, попадающий за пределы операнда, имеет специальное

место. Команды сдвига помещают последний выдвинутый из операнда бит

в флаг переноса. Если сдвиг был на один бит, то бит из дальнего

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

многобитового сдвига, вдвигаемый в перенос бит появляется изнутри

операнда. Флаг переноса имеет значение для операций повышенной

точности. Поскольку операнд операции сдвига может иметь максимум 16

бит, программа может организовать работу с данными большего размера

с помощью нескольких сдвигов и флага переноса. Программа

"разрезает" операнд на 16=битовые куски, а затем сдвигает каждую

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

для передачи выдвинутой информации в следующую часть сдвигаемого

операнда.

Верхние четыре команды на Фиг.4.20 - команды циклического

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

Циклические сдвиги переносят появляющийся в конце операнда бит в

другой конец. Циклический сдвиг влево ROL и циклический сдвиг

вправо ROR различаются лишь направлением сдвига данных. Аналогично,

циклический сдвиг влево с переносом RCL и циклический сдвиг вправо

с переносом RCR являются зеркальным отражением друг друга. Команды

ROL и RCL различаются в трактовке флага переноса. Байтовая команда

RCL рассматривает данные как 9=битовые, причем роль девятого бита

играет флаг переноса. Если операнд - слово, команда ROL циклически

сдвигает 16 бит, а команда RCL циклически сдвигает 17 бит.

Команды снизу Фиг.4.20 не возвращают выдвигаемые из операнда

биты в свой операнд. Эти биты попадают в флаг переноса, а затем

просто исчезают. Значение, вдвигаемое в операнд, определяется типом

сдвига. В случае логического сдвига вдвигаемый бит всегда 0;

арифметический сдвиг выбирает вдвигаемый бит таким, чтобы сохранить

знак операнда.

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

логических команд? Сдвиг числа на одну позицию (бит) эквивалентен

умножению или делению этого числа на 2. В десятичной системе

счисления, добавление нуля в конце числа умножает его на 10. В

двоичной арифметике добавление 0 в конце умножает число на 2. Так

как ЭВМ не может добавить другой бит в конце операнда, операция

сдвига действует аналогично. Команда сдвига влево перемещает все

биты влево на одну позицию, а в младшую позицию помещает 0. Таким

образом, сдвиг влево умножает число на 2. Если величина сдвига

больше единицы, число умножается на 2, возведенное в степень,

равную содержимому счетчика сдвигов. Например, сдвиг влево на 3

бита эквивалентен умножению на 8.

Сдвиг числа вправо - это то же самое деление на 2. Сдвинутый

операнд - частное, а флаг переноса - остаток. Если счетчик сдвигов

больше 1, операнд по=прежнему есть частное, а остаток теряется.

Таким образом, команды сдвига делают эффективным умножение и

деление на степень 2. Фактически, воэможность замены умножения

сдвигом становится хорошим выходом в ситуациях, когда необходимо

исключить умножение, даже если множитель не есть степень 2.

При арифметическом сдвиге вместо деления на 2 отрицательного

числа возникает следующая проблема. Если команда вдвигает 0 в

старший бит, результат становится положительным. Команда

арифметического сдвига вправо SAR решает эту проблему путем

восстановления значения старшего бита во время сдвига. Поэтому

отрицательное число остается отрицательным, а положительное -

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

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

логического сдвига влево SHL и арифметического сдвига влево SAL

идентичны.

В связи с арифметической природой, все команды сдвогов влияют

на флаг переполнения так же, как и на флаг переноса. Флаг

переполнения не определен в случае счетчиков сдвига больших

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

переполнения только в случае, если в результате операции изменился

знак числа. Если старший бит не изменился, флаг переполнения

сбрасывается, т.е. флаг переполнения показывает, дает ли

подразумеваемое сдвигом умножение или деление правильный результат

в дополнительном коде.

На Фиг. 4.21 приведены два примера команд сдвига. Первый пример

демонстрирует умножение на число с помощью команд сдвига влево. В

примере выполняется умножение на 9, не являющееся степенью 2.

Сначала в примере данные сдвигаются влево на три позиции, чтобы

умножить число на 8. Затем программа складывает полученное значение

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

умноженному на 9.

Недостатки этого метода очевидны. Он требует много больше

команд, чем простое умножение - которое выглядело бы примерно так:

PUSH DX

MOV DX,9

IMUL DX

POP DX

Кроме того, умножение на 9 с помощью сдвига дает 16=битовый

результат, а не 32=битовый, как команда IMUL.

Все же в программе умножение с помощью сдвига может оказаться

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

- скорость выполнения. Команда IMUL требует много времени, тогда

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

Фиг. 4.21, метод сдвига работает примерно на 25% быстрее. Выигрыш

небольшой, но может оказаться решающим для приложения, зависящего

от умножения целых чисел на 9. Умножения на степень 2 могут дать и

больший выигрыш в скорости выполнения.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:21

Фиг. 4.21 Примеры инструкций сдвига Page 1-1

PAGE ,132

TITLE Фиг. 4.21 Примеры инструкций сдвига

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

;--------------------------------------------------

; Эта программа умножает число, заданное в регистре AX

; на 9 без использования команды умножения

;--------------------------------------------------

0000 MUL9 PROC NEAR

0000 51 PUSH CX ; Сохранение регистра CX в стеке

0001 50 PUSH AX ; Временное сохранение AX

0002 B1 03 MOV CL, 3 ; Будем сдвигать регистр AX на 3 разряда,

0004 D3 F8 SAR AX, CL ; тем самым уножая на 8

0006 8B C8 MOV CX, AX ; CX <- AX * 8

0008 58 POP AX ; Восстановление AX

0009 03 C1 ADD AX, CX ; AX <- исходное значение * 9

000B 59 POP CX ;

000C C3 RET

000D MUL9 ENDP

;--------------------------------------------------

; Эта программа программа выделяет один бит в

; регистре AX, номер которого задан в регистре CL

;--------------------------------------------------

000D 53 PUSH BX ; Сохранение регистра BX в стеке

000E BB 0001 MOV BX, 1 ; Создание маски (1 в разряде 0 регистра BX)

0011 D3 C3 ROL BX, CL ; Сдвиг маски

0013 23 C3 AND AX, BX ; Выделение требуемого разряда

0015 5B POP BX ; Восстановление регистра BX

0016 C3 RET

0017 CODE ENDS

END

Фиг. 4.21 Примеры сдвига

Второй пример на Фиг. 4.21 показывает, как использовать сдвиг

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

фрагмент предполагает, что исходная информация находится в регистре

AX, а регистр CL содержит номер бита, выбираемого из регистра AX:

если содержимое регистра CL равно 8, из регистра AX выбирается бит

8. Программа сдвигает маску в регистре BX на указанную в регистре

CL позицию, а команда AND изолирует выбранный бит.

Для того чтобы этот пример работал правильно, число в регистре

CL должно быть в диапазоне 0 - 15. Можно было бы использовать

команду AND, чтобы выделить младшие четыре бита значения сдвига в

регистре CL; команда AND CL, 0FH гарантирует, что число в регистре

CL находится в пределах 0 - 15. Вы можете изменить этот пример так,

чтобы выделить более одного бита из слова. Можно было бы выделить

тетраду из 16=битового слова, заменив значение маски в регистре BX.


Untitled

Команды обработки строк

Одной из функций, в которой в наборе команд микропроцессора 8088

уделено особое внимание, является обработка строк. Строка символов

или чисел, с которыми программа работает, как с группой, является

обычным типом данных. Программа пересылает строку из одного места

в другое, сравнивает ее с другими строками, а также ищет в ней

заданное значение. Обычным типом данных является строка символов.

Программа представляет каждое слово, предложение либо другую

структуру строкой символов в памяти. Функции редактирования,

например, в большой степени используют операции поиска и пересылки.

Строковые команды микропроцессора 8088 выполняют эти операции с

минимальными программными затратами, а также при минимальном

времени исполнения.

Сначала давайте обсудим принципы работы со строками. Программа

может выполнять строковые операции как над байтами, так и над

словами; отдельные элементы строк могут иметь 8 либо 16 бит.

Строковые команды не используют способы адресации, используемые

остальными командами обработки. Фактически строковые команды очень

конкретны в адресации и не допускают каких=либо вариаций. Строковые

команды адресуют операнды комбинациями регистров DS:SI либо ES:DI.

Операнды источника используют регистровую пару DS:SI, а операнды

результата регистровую пару ES:DI, откуда и названия

индекс=регистров источника и результата. Все строковые команды

имеют встроенную коррекцию адреса после выполнения операции. Строка

состоит из многих элементов, но строковые команды обработки строк

могут работать только с одним элементом в каждый момент времени,

поэтому программа тоже работает со строкой по одному элементу в

момент времени. Автоматическое увеличение или уменьшение адреса

дает возможность быстрой обработки строковых данных. Флаг

направления в регистре состояния управляет направлением обработки.

Когда он установлен равным 1, адрес уменьшается, если флаг сброшен

в 0, то увеличивается. Размер операнда определяет количество

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

адрес на 1 после каждой операции, а команды обработки строк над

словами изменяют адрес на 2. Тем самым после выполнения операции

указатель ссылается на следующий элемент строки.


Untitled

Загрузка и запись

Листинг ассемблера на Фиг. 4.22 показывает различные строковые

команды. Загрузка строки LODS и запись строки STOS являются

простейшими строковыми командами. Если программа указывает

байтовый операнд в команде LODS, то она загружает в регистр AL

байт, на который указывает пара регистров DS:SI. Затем она

изменяет регистр SI на единицу; он увеличивается, либо уменьшается,

в зависимости от состояния флага направления. Если команда LODS

указывает на слово, то она загружает регистр AX и изменяет регистр

SI на 2. Команда STOS строго противоположна, и записывает байт из

регистра AL либо слово из регистра AX в ячейку памяти. В случае

записи ячейка определяется парой регистров ES:DI. Команда записи

изменяет регистр DI либо на единицу, либо на 2, в зависимости от

типа операнда.

Программист может писать на ассемблере команду LODS (а также и

все другие строковые команды) различными способами. Тип операнда

можно указать частью кода операции, либо ассемдлер может определить

тип элемента строки, основываясь на операнде, присутствующем в

команде. Как показано на Фиг. 4.22, команда

LODS EXBYTE

порождает команду загрузки строк байтов, так же как и команда

LODSB.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:26

Фиг. 4.22 Команды обработки строк Page 1-1

PAGE ,132

TITLE Фиг. 4.22 Команды обработки строк

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,ES:CODE

0000 EXBYTE LABEL BYTE

0000 EXWORD LABEL WORD

0000 EXBYTE1 LABEL BYTE

0000 EXWORD1 LABEL WORD

0000 AC LODS EXBYTE ; Загрузка AL из DS:SI

0001 AD LODS EXWORD ; Загрузка AX из DS:SI

0002 AC LODSB ; Загрузка AL из DS:SI

0003 AA STOS EXBYTE ; Сохранение AL в ES:DI

0004 AB STOS EXWORD ; Сохранение AX в ES:DI

0005 AB STOSW ; Сохранение AX в ES:DI

0006 F3/ AA REP STOSB ; Сохранение AL в ES:DI в цикле CX раз

0008 A4 MOVS EXBYTE1, EXBYTE ; Пересылка байта [ES:DI] <- [DS:SI]

0009 A5 MOVS EXWORD1, EXWORD ; Пересылка слова [ES:DI] <- [DS:SI]

000A A4 MOVSB ; Пересылка байта [ES:DI] <- [DS:SI]

000B F3/ A5 REP MOVSW ; Пересылка CX слов [ES:DI] <- [DS:SI]

000D AE SCAS EXBYTE1 ; Сравнение AL с [ES:DI]

000E F3/ AE REPE SCASB ; Сравнение AL с [ES:DI] пока равно

0010 F2/ AF REPNE SCASW ; Сравнение AX с [ES:DI] пока не равно

0012 A7 CMPS EXWORD, EXWORD1 ; Сравнение слова [DS:SI] с [ES:DI]

0013 F3/ A7 REPE CMPSW ; Сравнение слов [DS:SI] с [ES:DI] пока

; равно в цикле CX раз

0015 F2/ A6 REPNE CMPSB ; Сравнение байт [DS:SI] с [ES:DI] пока

; не равно в цикле CX раз

0017 CODE ENDS

END

Фиг.4.22 Строковые команды

В первом случае ассемблер определяет, что строка состоит из

байта, поскольку EXBYTE - переменная типа BYTE. Во втором случае

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

Собственно ассемблер не требует поля операнда. Программисты чаще

используют вторую форму, так как не имеют имени переменной,

связанной со строкой. Программа динамически располагает строку в

памяти, для нее не существует фиксированного места, и,

следовательно, нет и имени переменной. Команда STOS аналогична.

Чтобы непосредственно указать строку слов, а не байтов,

используются коды операций LODSW и STOSW. Ассемблер должен знать,

для байтовой строки или строки слов написана команда, поскольку

машинные команды различны для различных типов строк. Эта разница

определяет значение, на которое надо изменить индексный регистр.

Операнд в команде должен быть указан в том случае, если в

программе используются основные формы команд LODS и STOS. Если

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

формы LODSB и STOSB. Преимущество использования основной формы LODS

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

проверяет не только тип операнда, но и возможность его адресации.

Так как команда LODS работает с объектами только в сегменте DS,

оператор ASSUME должен соответственно описывать расположение

сегмента поименованной переменной. Аналогично, ассемблер проверяет

основную форму команды STOS на адресацию сегмента ES. Любая форма

приемлема для ассемблера, но лучше использовать основную форму,

чтобы позволить ассемблеру наилучшим способом проверить наличие

ошибок в программе до выполнения.


Untitled

Префикс REP

Существует специальный случай использования строковых команд. Есть

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

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

специальной сегментной адресации, он предшествует обычной команде и

модифицирует ее работу. А именно, этот префикс вводит строковую

команду в цикл. Мнемоника префикса REP происходит от английского

слова Repeat - повторить. Микропроцессор 8088 использует этот

префикс в тесной связи с регистром CX, который указывает число

повторений команды.

Примером является команда STOSB. Команда

REP STOSB

есть специальная форма команды записи байта. Эта команда

повторяется до тех пор, пока содержимое регистра CX не уменьшится

до 0. Команда STOSB записывает байт из регистра AL в ячейку памяти,

которая указывается парой регистров ES:DI, а затем увеличивает или

уменьшает регистр DI на единицу так же, как и обычная команда

STOSB. Затем префикс REP уменьшает регистр CX, и если он теперь не

нуль, повторяет всю команду целиком. Запись строки повторяется до

тех пор, пока регистр CX не достигнет нуля.

Такая возможность превращает команду STOS в команду заполнения.

Программа помещает заполнитель в регистр AL, счетчик байта в

регистр CX, адрес блока в пару регистров ES:DI и сбрасывает флаг

направления. Затем команда REP STOSB заполняет блок памяти

значением из регистра AL. Такой фрагмент кода показан на Фиг. 4.23.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:31

Фиг. 4.23 Заполнение области памяти Page 1-1

PAGE ,132

TITLE Фиг. 4.23 Заполнение области памяти

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,ES:CODE

;--------------------------------------

; В этом примере область данных BYTE_BLOCK

; заполняется значением 01H

;--------------------------------------

0000 8D 3E 000C R LEA DI, BYTE_BLOCK ; DI <- адрес области данных

0004 B9 0032 90 MOV CX, BYTE_BLOCK_LENGTH ; CX <- размер заполняемой области

0008 B0 01 MOV AL, 01H ; Символ для заполнения

000A F3/ AA REP STOS BYTE_BLOCK ; Заполнение

000C 0032[ BYTE_BLOCK DB 50 DUP(?)

??

]

= 0032 BYTE_BLOCK_LENGTH EQU $-BYTE_BLOCK

003E CODE ENDS

END

Фиг. 4.23 Заполнение блока

В случае команды LODS префикс REP не имеет смысла. Загрузка

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

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

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

строк.


Untitled

Пересылка строки

Может показаться удобным использовать команды LODS и STOS для

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

существует другая команда, пересылка строки MOVS. Эта команда

подобна комбинации команд LODS и STOS. Она берет данные из пары

регистров [DS:SI], помещает их в пару регистров [ES:DI], и изменяет

как регистр SI, так и регистр DI, чтобы они указывали на следующую

ячейку в каждой строке. Команда MOVS делает это одна, и не

загружает аккумулятор во время пересылки. Команда MOVS делает

сочетание LODS и STOS более быстрым и дающим меньше побочных

эффектов.

Команда MOVS указывает два операнда памяти. Только MOVS и еще

одна строковая команда CMPS работают с двумя операндами памяти. Все

остальные команды требуют, чтобы один или оба операнда находились в

регистре микропроцессора. Как и команды LODS и STOS, команда MOVS

работает как с байтами, так и со словами. Поскольку строковые

команды имеют дело с жестко заданными адресами, для определения

типов служат только операнды, написанные программистом. Команда

должна иметь оба операнда, и оба они должны быть одинаковых типов,

иначе программист может указать тип пересылки частью кода операции,

т.е. команда MOVSB и случае байтовых строк или команда MOVSW для

строк, состоящих из слов. Если в программе используется основная

форма, команда MOVS, ассемблер проверяет переменные на правильность

сегментной адресации, а также проверяет их типы.

Комбинация команды MOVS с префиксом REP дает эффективную

команду пересылки блока. Имея счетчик в регистре CX и показывающий

направление пересылки флаг направления, команда REP MOVS пересылает

данные из одного места памяти в другое очень быстро.

Микропроцессор, выполняющий команду REP MOVS, пересылает данные с

максимально возможной скоростью. Он больше не выбирает никакие

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

пересылки - это пересылка.

Установка флага направления критична для правильной работы

команды REP MOVS. Различные виды установки флага направления

обсуждались в гл.3 именно на примере команды пересылки, и в

программе необходимо придерживаться рекомендаций, данных в этой

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


Команды сканирования и сравнения

Две оставшиеся строковые команды используются в программах для

сравнения строковой информации. Первая из них - команда,

сканирование строки SCAS. Эта команда сравнивает значение в

регистре AL или регистре AX с операндом в памяти, на который

ссылается пара регистров ES:DI. Команда SCAS устанавливает флаги

нуля, переноса и переполнения, показывая результат сравнения

аккумулятора и ячейки памяти, и изменяет регистр DI так, чтобы он

указывал на следующий операнд в строке.

Команда SCAS не может использовать обычный префикс REP для

сканирования длинной строки информации. Точно так же, как команда

REP LODS не имеет смысла, команда REP SCAS не позволяет программе

контролировать каждое сравнение. Вместо этого существует два

варианта префикса REP - "повторять пока равно" REPE и "повторять

пока не равно" REPNE. Как и в случае обычного префикса REP,

программа загружает в регистр CX длину строки. Если указан префикс

REPE, команда выполняется ло тех пор, пока содержимое регистра AL

(или AX) не перестанет совпадать с ячейками памяти, или пока

содержимое регистра CX не станет равно 0. Пока аккумулятор

совпадает с ячейкой памяти, сканирование продолжается. Команда

REPNE в точности противоположна команде REPE. Сканирование

продолжается до тех пор, пока аккумулятор не совпадает с ячейкой

памяти.

Комбинация команд SCAS и REPNE позволяет программе выполнять

быстрый поиск по таблице. Чтобы найти объект в таблице, программа

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

Фиг. 4.24 показано, как команда SCAS выполняет эту функцию. В

регистре AL содержится аргумент сравнения. Таблица SCAN_TABLE

содержит значения, среди которых ведется поиск, а в регистре CX

находится длина таблицы. Команда REPNE SCASB сканирует таблицу до

тех пор, пока содержимое аккумулятора не станет равно элементу

строки. В этом месте регистр DI указывает на байт таблицы,

непосредственно следующий за сравнением. Вы можете определить

смещение совпавшего объекта, вычитая единицу из регистра DI после

метки FOUND. Программа может использовать эту информацию для

доступа к другой таблице, или таблицам, которые содержат

информацию, соответствующую этим исходным данным. Нужно обратить

особое внимание на команду JE после команды сканирования.

Существуют два случая, в которых управление передается этой

команде: байт в строке совпал с регистром AL и условие, задаваемое

префиксом REPNE, больше не выполняется; либо регистр CX достиг

нулевого значения без нахождения соответствующего числа в таблице.

В некоторых случаях создаются ситуации, исключающие появление

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

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

FOUND после команды сканирования, если команда установила флаг нуля

(или равенства). Тем самым гарантируется, что сравнение найдено.

Если же регистр CX достиг нуля, последняя итерация сканирования

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

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:36

Фиг. 4.24 Поиск в таблице Page 1-1

PAGE ,132

TITLE Фиг. 4.24 Поиск в таблице

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,ES:CODE

;--------------------------------------

; Поиск значения AL в таблице

;--------------------------------------

0000 8D 3E 000C R LEA DI, SCAN_TABLE ; Адрес таблицы

0004 B9 000B 90 MOV CX, SCAN_TABLE_LENGTH ; Длина таблицы

0008 F2/ AE REPNE SCASB ; Поиск

000A 74 00 JE FOUND ; Если равно, то значение найдено

; ... ; Иначе значение не найдено

000C FOUND:

;----- продолжение программы

000C 89 96 93 8A 85 8D 83 SCAN_TABLE DB 'ЙЦУКЕНГШЩЗХ'

98 99 87 95

= 000B SCAN_TABLE_LENGTH EQU $-SCAN_TABLE

0017 CODE ENDS

END

Фиг. 4.24 Сканирование таблицы

Последняя строковая команда - сравнение строк CMPS. Подобно

сканированию строки, это - команда сравнения. Подобно команде MOVS,

она работает с двумя операндами памяти. Команда CMPS сравнивает

строку по адресу DS:SI со строкой по адресу ES:DI, и соответственно

устанавливает флаги. Как и для команды SCAS, в данном случае

использовать префикс REP нельзя, а префиксы REPE и REPNE можно

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

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:41

Фиг. 4.25 Сравнение строк Page 1-1

PAGE ,132

TITLE Фиг. 4.25 Сравнение строк

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,ES:CODE

;--------------------------------------

; Сравнивается 5-символьная строка с таблицей

; таких 5-символьных строк. Выход из программы

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

;--------------------------------------

0000 FIG4_25 PROC NEAR

0000 8D 36 001D R LEA SI, ARGUMENT ; Адрес строки

0004 8D 3E 0022 R LEA DI, COMPARE_TABLE ; Адрес таблицы

0008 BB 0000 MOV BX, 0 ; В BX cчетчик просмотренных строк

000B COMPARE_LOOP:

000B 56 PUSH SI ; Сохранение адреса строки

000C 57 PUSH DI ; Сохранение адреса таблицы

000D B9 0005 MOV CX, 5 ; Сравниваются 5 байт

0010 F3/ A6 REPE CMPS ARGUMENT,COMPARE_TABLE ; Сравнение

0012 5F POP DI ; Восстановление

0013 5E POP SI ; регистров

0014 74 06 JE FOUND ; Искомая строка найдена

0016 83 C7 05 ADD DI, 5 ; Сдвиг указателя на следующую

; строку в таблице

0019 43 INC BX ; Номер текущей строки в таблице

001A EB EF JMP COMPARE_LOOP ; Цикл

001C FOUND:

001C C3 RET

001D FIG4_25 ENDP

001D 41 42 43 44 45 ARGUMENT DB 'ABCDE'

0022 COMPARE_TABLE LABEL BYTE

0022 51 57 45 52 54 50 4F DB 'QWERT','POIUY','ASDFG','LKJHG'

49 55 59 41 53 44 46

47 4C 4B 4A 48 47

0036 5A 58 43 56 42 4D 4E DB 'ZXCVB','MNBVC','VWXYZ','ABCDE'

42 56 43 56 57 58 59

5A 41 42 43 44 45

004A CODE ENDS

END

Фиг. 4.25 Сравнение строк

Фиг. 4.25 демонстрирует пример использования команды CMPS.

Этот пример сравнивает пятисимвольную исходную строку с таблицей

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

строки с элементом таблицы. Когда строка найдена, в регистре BX

нахолится индекс строки. В программе используется префикс REPE, так

что команда сравненния строк выполняется до тех пор, пока один из

символов аргумента не совпадает с символом таблицы. Если все пять

символов совпали, программа находит правильный элемент. Команда JE

("переход, если равно") проверяет результат команды CMPS. Если

сравнение завершилось из=за несоответствия символов, флаг нуля

показывает ненулевое состояние. Если же команда CMPS завершилась

потому, что счетчик CX стал нулевым, флаг нуля покажет совпадение и

произойдет переход на метку FOUND. Вы можете заметить, что в этом

примере отсутствуют некоторые необходимые детали, которые смогли бы

сделать его хорошей программой. Например, он никак не обрабатывает

случай, когда исходная строка не совпала ни с одним элементм

таблицы. Любой хороший программист скажет вам, что исключительные

ситуации нужно обрабатывать всегда.


Untitled

Команды передачи управления

Команды передачи управления нужны для того, чтобы передавать

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

также команды вызова подпрограмм. Команды вызова подпрограмм

вызывают подпрограммы, а команды перехода передают управление

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

условного перехода позволяет ЭВМ думать. Условные команды могут

проверить результат предыдущих действий и изменить течение

программы на основе полученного результата. Если бы команды

условного перехода не сеществовали, программирование для ЭВМ было

бы много проще, но и менне продуктивно.

Первое, что нужно рассмотреть при обсуждении команд передачи

управления - это методы адресации, используемые для определения

адреса ячейки, куда передается управление. Хотя операнд команды

перехода - такая же ссылка к памяти, как и ссылка к данным,

программы используют адреса перехода иначе, чем адреса данных.

Поэтому для адресации точки перехода существуют лучшие способы.


Untitled

Близкие и далекие переходы

Команды перехода модифицируют указатель команды IP, и, возможно,

регистр сегмента кодов CS. Эти регистры показывают, какая

следующая команда должна быть выполнена. Команда перехода является

специальным случаем пересылки MOV данных в регистр или пару

регистров; и некоторые ЭВМ действительно выполняют команду перехода

именно таким способом. Однако способы загрузки пары регистров

CS:IP в микропроцессоре 8088 во многом отличаются от способов,

используемых для других регистров.

Прежде всего мы должны ввести некоторые определения. Если

команда перехода изменяет только регистр IP, это близкий переход

(NEAR=переход), так как переход происходит внутри сегмента. Если

переход изменяет регистр CS, это далекий FAR=переход.

Аттрибуты NEAR и FAR используются при работе ассемблера. Любая

программная метка в программе на языке ассемблера имеет атрибут

либо NEAR, либо FAR, так же, как данные имеют атрибуты BYTE или

WORD. В некоторых примерах этой главы имеются процедуры, которые

используют атрибут NEAR в операторе PROC. Это означает, что метка,

связанная с оператором PROC (имя процедуры) имеет атрибут NEAR.

Ассемблер использует эту информацию для того, чтобы определить,

какой тип команды перехода или вызова породить при переходе к этой

метке. Поскольку большинство процедур - подпрограммы, атрибут NEAR

или FAR оператора PROC также определяет тип порождаемой команды

возврата. Вызов FAR=процедуры сохраняет значения как регистра CS,

так и регистра IP, тогда как вызов NEAR=процедуры оставляет в стеке

только значение регистра IP. Команда возврата должна учитывать,

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

правильному месту.


Untitled

Адресация переходов

Если адрес перехода или вызова подпрограммы является частью самой

коамнды (как данные в командах с непосредственным операндом), это -

непосредственный переход. Если адрес перехода команды содержится в

регистре или ячейке памяти, это - косвенный переход, так как

команда требует загрузки адреса, извлекаемого из некоторого

промежуточного места хранения; программа не может перейти прямо в

необходимое место, и должа идти туда косвенно.

Существует два метода вычисления адреса перехода. Если в

команде указано значение адреса, это абсолютный переход, т.е.

переход по абсолютному адресу. Команда может указать место

перехода, как некоторое расстояние от нее самой. Этот метод

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

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

программа наиболее часто переходит к близлежащим ячейкам; команда

перехода может использовать однобайтовое смещение. Если смещение

трактуется, как число в дополнительном коде, то двухбайтовая

команда относительного перехода (один байт - код операции, и один

байт - смещение) может выполнить переход на 127 байт вперед или на

128 байт назад внутри программы. Микропроцессор 8088 имеет два типа

относительных переходов: один имеет однобайтовое смещение, другой -

двухбайтовое.

В микропроцессоре 8088 все условные переходы имеют однобайтовое

смещение. Иногда это неудобно, например в случае условного перехода

к ячейке, находящейся на расстоянии в 150 байт от текущего места.

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

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

перехода. В обычных же случаях однобайтовые смещения условных

переходов в микропроцессоре 8088 минимизируют объем программы,

необходимой для реализации любой заданной функции.

При расчете смещения относительного перехода микропроцессор

8088 отсчитывает смещения от значения указателя команд, которое

получится после выполнения команды. Фиг. 4.26 показывает разные

примеры команд относительного перехода. Если точка перехода следует

непосредственно за переходом, смещение равно 0. При переходе к

самой команде перехода смещение равно -2. При двухбайтовом смещении

переход может быть сделан в диапазоне -32768 - 32767 байт от

значения регистра IP после выполнения команды перехода.


Untitled

Безусловные переходы

Безусловные переход - это такой переход, который передает

управление всякий раз, когда он выполняется. Наоборот, услловный

переход проверяет текущее состояние машины, чтобы определить,

передавать управление или нет. Существует два вида команд

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

Все команды вызова CALL - безусловны. Различные команды CALL

показаны на Фиг. 4.27. Близкий вызов CALL, или NEAR CALL, указывает

новое значение регистра IP и сохраняет старое значение регистра IP

в стеке в качестве адреса возврата. Далекий вызов CALL, или FAR

CALL, задает новые значения сегмента и смещения для дальнейшего

выполнения программы и сохраняет в стеке как регистр IP, так и

регистр CS. Близкий непосредственный вызов CALL - это относительный

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

команды вызова - абсолютные переходы. Непосредственный вызов FAR

CALL требует четырехбайтовое поле операнда для указания новых

значений для регистров CS и IP. Косвенные переходы используют байт

адресации mod=r/m для указания операнда=регистра или памяти; этот

операнд содержит адрес подпрограммы. Косвенные вызовы типа NEAR

загружают однословный операнд в регистр IP. Вызовы типа FAR

загружают двойное слово из памяти в пару регистров CS:IP; первое

слово загружается в регистр IP, а второе - в регистр CS. Если

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

вызова, результат непредсказуем; микропроцессор 8088 берет новое

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

использовать эту модификацию команды.

Командам CALL соответствуют команды возврата RET. Все возвраты

- косвенные переходы, поскольку они извлекают адрес перехода из

вершины стека. Близкий возврат извлекает из стека одно слово и

помещает его в регистр IP, а далекий возврат извлекает два слова,

помещая слово из меньшего адреса в регистр IP, а слово из большего

адреса в регистр CS.

Программы могут модифицировать возвраты как типа NEAR, так и

типа FAR, указывая параметр счетчика байтов. Команда возврата

прибавляет его значение к указателю стека после извлечения из него

адреса (адресов) возврата. Такая команда позволяет программе

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

POP; тем самым подчеркивается, что стек - носитель передаваемых

подпрограмме параметров. Такой стиль работы со стеком мы уже

обсуждали во всех подробностях ранее в разделе "Работа со стеком".

Команды безусловного перехода JMP идентичны командам CALL по их

возможностям адресации. Однако существует дополнительная команда

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

относительного перехода (команда короткого перехода).

Соответствующей ей команды CALL не существует, так как вызовы

подпрограмм, расположенных поблизости, происходят очень редко.

Команды переходов используют те же методы генерации адреса, что и

команды вызова.

Сделаем сдесь замечание об оптимизации кода и о том, как

работает ассемблер. По мере того, как ассемблер делает первый

переход по тексту программы и назначает адреса командам, он должен

решить, использовать двух= или трехбайтовую разновидность команды

JMP. Если это переход назад, т.е. на место, уже известное

ассемблеру, он может определить правильное смещение; тем самым

ассемблер знает, находится ли переход в диапазоне короткого

смещения. Однако, если переход делается вперед, на метку, о которой

ассемблер еще не знает, он должен предположить, что метка находится

далее, чем 128 байт от текущего места. Затем ассемблер порождает

длинную форму команды перехода. Худший случай ассемблер обязан

выбирать потому, что потом уже не может возвратиться назад и

увеличить размер команды. Затем ассемблер заместит трехбайтовую

команду перехода двухбайтовой командой JMP и однобайтовой командой

NOP, если обнаружит, что переход делается ближе 128 байт от

текущего места. Так как такой переход выполняется несколько

быстрее, время выполнения в этом случае сокращается, но объектный

код остается больше необходимого.

Если программисту заранее известно, что переход вперед делается

на место, лежащее в диапазоне 128 байт от текущего места, он может

об этом сообщить ассемблеру с помощью следующей строки:

JMP SHORT LABEL

Аттрибут SHORT заставляет ассемблер сформировать короткую форму

SHORT команды перехода, даже если он еще не встречал метку. Если же

программист сделал ошибку и переход в действительности не может

быть коротким, ассемблер выдает сообщение об ошибке. На Фиг. 4.26

дан пример оператора SHORT.

Фиг. 4.28 показывает, как можно устроить таблицу переходов

с помощью команды косвенного перехода. В этом примере делается

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

в регистре AL. Аналогичная программа могла бы вызвать подпрограмму

по индексу. Это - реализация на языке ассемблера оператора CASE,

который существует в некоторых языках высокого уровня.


Untitled

Переходы по условию

Условные переходы делятся на две группы: проверяющие результаты

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

итерациями фрагмента программы. Все условные преходы имеют

однобайтовое смещение. Если условный переход осуществляется на

место, находящееся дальше 128 байт, нужно использовать специальную

конструкцию. Например, допустим, что программе надо перейти к

метке ZERO, если установлен флаг нуля; эта метка находится дальше

128 байт от текущего места. Программа в этом случае выглядит

примерно так:

JNZ CONTINUE

JMP ZERO

CONTINUE:

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:56

Фиг. 4.28 Таблица переходов Page 1-1

PAGE ,132

TITLE Фиг. 4.28 Таблица переходов

0000 CODE SEGMENT

ASSUME CS:CODE

;----------------------------------------

; В этом примере демонстрируется программа,

; осуществляющая переход в зависимости от

; значения регистра AL. В регистре находится

; индекс в таблице переходов необходимой программы

;----------------------------------------

0000 2A FF SUB BH, BH ; BH <- 0

0002 8A D8 MOV BL, AL ; Индекс загружается в регистр BL

0004 D1 E3 SHL BX, 1 ; * 2 для получения смещения

0006 2E: FF A7 000B R JMP CS:[BX + BRANCH_TABLE] ; Косвенный близкий переход

000B BRANCH_TABLE LABEL WORD

000B 0011 R DW ROUTINE_ONE

000D 0011 R DW ROUTINE_TWO

000F 0011 R DW ROUTINE_THREE

; ...

0011 ROUTINE_ONE LABEL NEAR

0011 ROUTINE_TWO LABEL NEAR

0011 ROUTINE_THREE LABEL NEAR

0011 CODE ENDS

END

Фиг. 4.28 Таблица переходов

Здесь используется условный переход с противоположным условием.

На метку ZERO управление передает команда безусловного перехода,

которая может использовать смещение вплоть до 32768 байт, а в

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

Если целью является минимизация программ, этого метода нужно

избегать, так как он превращает команду условного перехода в

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

приводит к тому, что место перехода попадает в нужный диапазон.

Однако не стоит особенно стараться минимизировать программу. В

большинстве случаев не имеет особого значения, насколько у

программы большой объем, лишь бы он не превышал заданного. Это

имело бы смысл, если бы вы пытались сделать нечто помещающееся в

модуль ПЗУ постоянного объема, но обычно усилия, затрачиваемые на

изменения программы, не дают ощутимого выигрыша.


Untitled

Проверки кода условия

Первая группа команд условного перехода проверяет текущее состояние

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

делает переход (или не делает). Команды условного перехода не

устанавливают флаги, а только проверяют их текущее состояние.

Ранее рассмотренные арифметические и логические команды

устанавливают флаги. В последующих примерах предположим, что

команда сравнения CMP уже установила флаги.

На Фиг.4.29 показаны команды условного перехода и проверяемые

ими флаги. В строках рисунка перечислены команды условного

перехода, а в пяти колонках показано состояние флагов. Буква X в

любой позиции означает, что команда не проверяет флаг. Цифра 0

означает, что этот флаг должен быть сброшен, чтобы условие было

выполнено и переход произошел. Цифра 1 означает, что флаг должен

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

таблицы показывают выражение, которое должно быть истинно, чтобы

переход произошел. Так сделано для арифметических переходов, и мы

обсудим их далее более подробно.

На Фиг.4.29 условные переходы разделены на три группы:

непосредственно проверяющие один из флагов; делающие арифметическое

сравнение без знака; делающие арифметическое сравнение со знаком.

На Фиг.4.29а показана проверка отдельных флагов. Условный

переход может проверить каждый из этих пяти флагов непосредственно

на 0 или 1. Проверка флага переноса показана на рисунке в группе

арифметики без знака, поскольку она имеет еще и арифметический

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

одной мнемоники, если даже выполняется одна и та же проверка.

Например, проверку флага нуля осуществляет команда JZ (переход,

если результат операции равен нулю). Однако команда JE (переход,

если равно) также порождает ту же команду. Следующая

последовательность поясняет смысл этого:

CMP AX, BX

JE LABEL

Команда CMP вычитает содержимое регистра BX из содержимого

регистра AX, устанавливая флаги в соответствии с результатом. Так

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

показывает равенство. Аналогично, команда JNZ (переход, если не

нуль) идентичен команде JNE (переход, если не равно). Команда JP

(переход по четности) - то же самое, что и команда JPE (переход при

наличии четности); команда JNP (переход по нечетности) - то же

самое, что команда JPO (переход при отсутствии четности).

Команды Флаги

условного

перехода OF CY Z P S Комментарий

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

JE/JZ X X 1 X X

JP/JPE X X X 1 X

JO 1 X X X X

JS X X X X 1

JNE/JNZ X X 0 X X

JNP/JPO X X X 0 X

JNO 0 X X X X

JNS X X X X 0

(a)

JL/JNGE a X X X b a NEQ b

JLE/JNG a X 1 X b Z OR (A NEQ B)

JNL/JGE a X X X b a = b

JNLE/JG a X 0 X b (NOT Z) AND (a=b)

(b)

JB/JNAE/JC X 1 X X X

JBE/JNA X 1 1 X X CY OR Z

JNB/JAE/JN X 0 X X X

JNBE/JA X 0 0 X X (NOT CY) AND (NOT Z)

(c)

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

Фиг. 4.29 Проверка флагов перехода по условию. (a) Проверка флага

(b) Арифметика со знаком; (c) беззнаковая арифметика.

Следующую группу команд условного перехода на Фиг.4.29б

составляют арифметические сравнения со знаком. Существуют четыре

условия, которые могут быть проверены: меньше (JL), меньше или

равно (JLE), больше (JG), больше или равно (JGE). Другие четыре

мнемоники - отрицания этих четырех. В случае арифметики со знаком

ассемблер использует мнемонику в названии команд "меньше" (less) и

"больше" (greater). Далее мы увидим, что для арифметики без знака

ассемблер использует мнемонику "выше" (above) и "ниже" (below).

Арифметические выражения можно понять, используя их вместе с

командой CMP. Например,

CMP AX,BX

JL LABEL

Преход произойдет, если содержимое регистра AX меньше

содержимого регистра BX. Вы можете читать комбинацию команд

сравнения и условного перехода вместе, как один оператор: операнд

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

ним следует исходный операнд. Другой пример:

CMP CX,WORD_IN_MEMORY

JNLE LABEL

- можно прочитать так: "переход, если содержимое регистра CX не

меньше, чем, или равен содержимому ячейки памяти WORD_IN_MEMORY.

Этот прием можно использовать для определения значения любой команды

арифметического перехода, учитывающей знак или не учитывающей.

Как показано на Фиг.4.29б арифметические сравнения со знаком

проверяют одновременно несколько флагов. Фактически каждая из этих

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

и, возможно, флага нуля. Например, в команде JL требуется, чтобы

флаги переполнения и знака имели разные значения. Если они имеют

одинаковое значение, первый операнд не был меньше второго.

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

арифметических сравнений.

Когда сравниваются два числа со знаком, возможны четыре

комбинации флагов знака и переполнения. Рассмотрим каждую из

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

привело к данному результату. Предположим, что в каждом случае

флаги установила команда CMP, вычитавшая два операнда.

Знак S = 0, переполнение O = 0.

Условие S=0 означает, что результат вычитания положителен.

Условие O=0 означает, что переполнения не было, т.е. результат,

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

чисел, дающее положительный результат, показывает, что первое число

больше второго, и поэтому имеет место соотношение "больше". Однако

вычитание двух равных чисел также дает положительный результат, так

что условие S=0, O=0 означает "больше или равно".

S=1, O=0

В этом случае O=0 означает, что результат верен, а S=1 говорит

о том, что он отрицателен. Чтобы получить отрицательный результат,

большее число должно вычитаться из меньшего, и соотношение означает

"меньше".

S = 0, O = 1

Здесь O=1 показывает, что результат неверен, т.е. вышел за

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

положительных чисел дало отрицательный результат или наоборот. В

данном случае это сравнение показывает, что знак результата

неверен; поэтому результат этого сравнения идентичен случаю, когда

S=1, O=0, что означает "меньше".

S = 1, O = 1

Снова O=1 говорит о том, что знак результата неверен. Поэтому

вычитание должно было привести к очень большому положительному

числу, и соотношение будет "больше или равно".

В некоторых случаях также учитывается флаг нуля. Например,

команда JLE выполняется, если условие есть "меньше" (знак и

переполнение разные) или "равно" (флаг нуля равен 1). Эти три флага

позволяют микропроцессору 8088 проверить все возможные комбинации

чисел со знаком.

Последняя часть таблицы (Фиг.4.29в) показывает условия,

проверяемые для арифметики без знака. Как и в случае арифметики со

знаком, существуют четыре возможные соотношения между операндами,

которые может проверить микропроцессор. Для того чтобы отличить

команды условного перехода ориентированные на беззнаковую

арифметику от знаковой арифметики, используются слова "выше" и

"ниже" в названии команд. Вероятно, этим выражается точка зрения

создателей набора команд, заключающаяся в том, что арифметика без

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

отрицательных адресов не бывает. "Выше" и "ниже" показывают

расположение значения адресов внутри адресного пространства, в то

время как "больше" и "меньше" говорит о соотношении чисел со

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

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

сравниваемых операндов. Например, программа сравнивает два числа со

знаком, а использует команду JA (переход, если выше).

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

соотношения двух чисел, считая их числами без знака, т.е. именно

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

Микропроцессор 8088 при сравнении двух чисел без знака

учитывает только два флага. Флаг переноса показывает, какое из

чисел больше. Сравнение устанавливает флаг переноса, если первый

операнд ниже второго или сбрасывает флаг переноса, если первый

операнд либо выше, либо равен второму операнду, и флаг нуля

определяет, что в данном случае верно.

Сравнения без знака можно читать так же, как и сравнения со

знаком. Например,

CMP AX,BX

JA LABEL

- переход на метку LABEL происходит, если регистр AX выше

регистра BX. Условный переход выполняется всегда, если объявленное

соотношение существует между первым и вторым операндами

предшествовавшей команды сравнения.


Untitled

Управление циклами

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

управления циклами в программах. Поскольку программые циклы

используются часто, желательно эффективное управление циклом. На

Фиг. 4.30 показаны четыре команды, созданные для того, чтобы

облегчить программирование циклов на языке ассемблера

микропроцессора 8088.

Так же, как строковые команды используют регистр CX в качестве

счетчика, команды цикла LOOP используют регистр CX в качестве

счетчика цикла. Все эти команды неявно рассматривают регистр CX как

счетчик итераций цикла. Простейшая команда среди них - команда

LOOP. Команда LOOP уменьшает регистр CX и передает управление на

метку, если содержимое регистра CX не равно 0. Если вычитание

единицы из регистра CX не привело к нулевому результату, команда

LOOP не делает перехода, и выполняется следующая команда.

Приведенный ниже программный фрагмент демонстрирует обычное

использование команды LOOP.

MOV CX,LOOP_COUNT

BEGIN_LOOP:

; ... тело цикла

LOOP BEGIN_LOOP

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:01

Фиг. 4.30 Команды цикла Page 1-1

PAGE ,132

TITLE Фиг. 4.30 Команды цикла

0000 CODE SEGMENT

ASSUME CS:CODE

;----------------------------------------

; В этом примере демонстрируются команды цикла.

; Команды в примере не являются законченной программой.

;----------------------------------------

0000 E3 06 JCXZ END_OF_LOOP ; Конец цикла, если CX равно 0

0002 BEGIN_LOOP:

; .... Тело цикла

0002 E2 FE LOOP BEGIN_LOOP ; Переход пока регистр CX не станет равен 0

; .... Если проверяется какое-либо условие, то

0004 E1 FC LOOPE BEGIN_LOOP ; Переход по равенству в условии и

; значение регистра CX не равно 0

; .... Или

0006 E0 FA LOOPNE BEGIN_LOOP ; Переход по неравенству в условиии и

; значение регистра CX не равно 0

0008 END_OF_LOOP:

0008 CODE ENDS

END

Фиг. 4.30 Команда цикла

Программа помещает число итераций цикла в регистр CX перед

выполнением цикла. Затем выполняется тело цикла, а следом за ним

команда LOOP. Она уменьшает счетчик на единицу, что соответствует

единственной, только что выполненной итерации цикла. Если теперь

счетчик в регистре CX равен 0, программа продолжает выполняться

после команды LOOP. Если счетчик не равен 0, управление

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

телу цикла. Тело цикла выполняется столько раз, сколько было

сначала задано содержимым регистра CX. Единственное важное

замечание: если программа внутри цикла изменяет регистр CX, число

итераций цикла не будет соответствовать начальному значению в

регистре CX.

Описанный метод одинаково хорошо работает, когда число циклов

известно во время ассемблирования (как в примере, где LOOP_COUNT -

непосредственно заносимое значение), и когда число циклов

определяется во время выполнения. Если вычисленное число оказалось

равным 0, цикл выполнится 65536 раз. Когда микропроцессор 8088

выполняет первую команду LOOP, он уменьшает CX от 0 до 0FFFFH, и

поскольку теперь регистр CX ненулевой, повторяет цикл. Таким

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

случай. Этот специальный случай обрабатывается командой JCXZ

(переход, если содержимое регистра CX равно 0). Эта команда

проверяет текущее содержимое регистра CX, и делает переход, если

оно равно нулю. Команда не проверяет ни одного флага, и не влияет

ни на один из них. Следующий пример аналогичен предыдущему, за

исключением того, что он загружает регистр CX из ячейки памяти,

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

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

использует команду JCXZ, чтобы проверить, нужно ли полностью

пропустить тело цикла.

MOV CX,LOOP_COUNT_WORD

JCXZ END_OF_LOOP

BEGIN_LOOP:

; ... тело цикла

LOOP BEGIN_LOOP

END_OF_LOOP:

В программе не нужно использовать команду JCXZ в каждом цикле с

вычисляемым счетчиком. Если программист знает, что счетчик циклов

никогда не будет равен нулю, проверка не нужна. Однако опыт

показывает, что значение, которое "никогда" не должно появиться,

обычно появляется в первую очередь, как только вы начинаете

выполнять программу.

Оставшиеся две команды цикла предоставляют еще большие

возможностей при управлении циклами. Эти команды аналогичны

префиксам REPE и REPNE. Если команда LOOP выходит из цикла, только

когда в регистре CX оказывается нуль, то команда LOOPE (цикл, пока

равно) выходит из цикла, если установлен флаг нуля, или если в

регистре CX получился 0. Тем самым становится возможным

двойственное завершение цикла. Программа может загрузить в регистр

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

конце каждого цикла на условие завершения. Команда LOOPNE (цикл,

пока не равно) выполняет обратную к описанной проверку флага нуля:

цикл здесь завершается, если регистр достиг нуля, или если

установлен флаг нуля.

Следующий пример показывает использование команды LOOPNE. В

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

сумма которых точно равна 100. Так как в каждой итерации перед

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

нельзя.

В примере предполагается, что пары регистров DS:SI и ES:DI

инициализированы так, чтобы указывать на эти списки.

MOV CX,MAX_LOOP_COUNT ;максимальное число заходов

BEGIN_LOOP:

LODSB ;чтение числа из первого списка

ADD AL,ES:[DI] ;прибавить из второго списка

INC DI ;указатель на следующий элемент

CMP AL,100 ;проверка на нужное значение

LOOPNE BEGIN_LOOP ;снова, если не равно и не все

JE MATCH_FOUND ;переход сюда, чтобы определить конец


Untitled

Команды управления микропроцессором

Оставшиеся команды микропроцессора 8088 управляют его работой.

Многие из них устанавливают или сбрасывают некоторые флаги

состояния.

Установка флагов

Есть три команды, которые непосредственно управляют состоянием

флага переноса. Команды STC, CLC, CMC соответственно могут

устанавлмвать, сбрасывать и изменять флаг переноса. Этот флаг -

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

благодаря важности флага переноса при операциях с повышенной

точностью. Флаг переноса критичен на промежуточных шагах любых

многословных операций. Возможность сбрасывать или устанавливать

флаг переноса может помочь при циклической обработке с повышенной

точностью. На Фиг. 4.31 показан пример использования команды CLC.

Цикл внутри примера складывает отдельные байты двух 10-разрядных

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

так как за каждую итерацию она обрабатывает две цифры. Информация

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

переноса. Команда CLC сбрасывает флаг переноса перед первым циклом

для того, чтобы перед первым сложением не было переноса. Флаг

переноса также важен в операциях сдвига, где он становится девятым

или семнадцатым битом регистра во время выполнения операции.

Два флага состояния микропроцессора имеют специальные команды

работающие с ними. Программа может установить или сбросить маску

прерываний соответственно командами STI и CLI. Команда STI включает

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

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

прерываний.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:06

Фиг. 4.31 Десятичная арифметика повышенной точности Page 1-1

PAGE ,132

TITLE Фиг. 4.31 Десятичная арифметика повышенной точности

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

= 0005 NUMBER_LENGTH EQU 5 ; 5 байт для упакованного числа

0000 0005[ NUMBER_ONE DB NUMBER_LENGTH DUP (?)

??

]

0005 0005[ NUMBER_TWO DB NUMBER_LENGTH DUP (?)

??

]

;----------------------------------------

; Эта программа складывает два десятичных упакованных

; числа (NUMBER_ONE и NUMBER_TWO) и заносит результат

; в NUMBER_TWO.

;----------------------------------------

000A START_ADD:

000A B9 0005 MOV CX, NUMBER_LENGTH ; Определение длины чисел

;----- Установка индексных регистров на младший байт складываемых чисел

000D 8D 36 0004 R LEA SI, NUMBER_ONE + NUMBER_LENGTH - 1

0011 8D 3E 0009 R LEA DI, NUMBER_TWO + NUMBER_LENGTH - 1

0015 F8 CLC ; Нет младших разрядов

0016 ADD_LOOP:

0016 8A 04 MOV AL, [SI] ; Взять байт из первого числа

0018 12 05 ADC AL, [DI] ; Добавить из второго с учетом переноса

001A 27 DAA ; Коррекция до упакованного формата

001B 88 05 MOV [DI], AL ; Занесение байта результата

001D 9C PUSHF ; Сохранение флага переноса (CF)

001E 4E DEC SI ; Сдвиг указателя первого числа

001F 4F DEC DI ; Сдвиг указателя второго числа

0020 9D POPF ; Восстановление флагов

0021 E2 F3 LOOP ADD_LOOP ; Обработка следующего байта

0023 CODE ENDS

END

Фиг. 4.31 Операции с BCD повышенной точности

Программа может установить или сбросить флаг направления с

помощью команд STD и CLD. Команда CLD сбрасывает флаг направления,

приводя к тому, что строковые команды ведут обработку при

возрастающих адресах памяти. Команда STD устанавливает флаг, в

результате чего строковые команды уменьшают адресный указатель

после каждого выполнения.


Специальные команды

Команда NOP - еще одна удобная команда микропроцессора 8088. Она

не делает ничего - "нет операции". Тщательный анализ машинных

команд показывает, что это в действительности команда XCHG. А

именно, это

XCHG AX,AX

что эквивалентно "ничего не выполнять", и бывают ситуации,

когда такую команду желательно использовать, чтобы выждать

некоторое время. В небольшом цикле, предназначенном для

определенной временной задержки, можно использовать команды NOP для

заполнения тела цикла, достигая тем самым точной выдержки времени

выполнения цикла (хотя цикл - не лучший способ временной задержки,

если интервал не очень маленький). Разработчики IBM PC требуют

использовать NOP в некоторых местах, чтобы удовлетворять

определенным временным требованиям. Например, программа не может

иметь доступ в схему таймера чаще, чем раз в одну микросекунду. Две

последовательно идущие команды IN нарушают это требование, так что

между командами IN должно быть выполнено несколько команд NOP.

Команда HLP останавливает ЭВМ; после выполнеиня этой команды

микропроцессор останавливается. Если прерывания заблокированы во

время останова, ЭВМ полностью "замирает". В этой сиутации

единственная возможность запустить ЭВМ заново - выключить питание и

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

останова микропроцессора, они продолжают восприниматься и

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

выполнеиня команды IRET в обработчике программа продолжает

выполнение с ячейки, следующей за командой HLT. Команду HLT можно

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

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

Разработчики персональной ЭВМ используют команду останова только

тогда, когда возникает катастрофическая ошибка оборудования и

дальнейшая работа бессмысленна.

Команда LOCK - это командный префикс, такой же, как подавление

сегментации или REP-префикс. Она предназначена для

мультипроцессорных систем, в которых несколько микропроцессоров

могут одновременно работать с одними и теми же ячейками памяти.

Префикс LOCK вынуждает микропроцессор 8088 захватить линии

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

память на время обработки команды с префиксом. Лучший пример этого

- установка- проверка флага в общей памяти.

MOV AL,1

LOCK XCHG AL,FLAG_BYTE

CMP AL,1

В этом примере байт FLAG_BYTE содержит нулевой или единичный

индикатор. Микропроцессор устанавливает флаг равным единице, когда

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

некоторые системные действия, которые может выполнять в данный

момент времени лишь один микропроцессор. Перед входом в

"охраняемую" область микропроцессор должен проверить, не работает

ли в ней другой микропроцессор. Если это так, он должен подождать

перед входом; иначе он может войти в область. В примере перед

командой XCHG используется префикс LOCK. Префикс LOCK дает

микропроцессору право исключительного доступа в течение выполнения

команды XCHG, которая читает содержимое ячейки памяти, а затем

записывает данные в эту же ячейку. Команда XCHG записывает 1 из

регистра AL в поле FLAG_BYTE, засылая его текущее значение в

регистр AL. Теперь, если регистр AL содержит 1, то в "охраняемой"

области находится другой микропроцессор, и проверяющий

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

микропроцессор может войти в "охраняемую" область, а команда XCHG

уже установила поле FLAG_BYTE равным 1, чтобы больше ни один

микропроцессор не смог войти туда. Префикс LOCK препятствует любому

другому микропроцессору проверять поле FLAG_BYTE в течение

короткого интервала времени между проверкой и установкой ячейки

флага.

К сожалению, описание работы префикса LOCK носит чисто

теоретический характер. IBM PC не реализует аппаратные средства,

необходимые для работы LOCK.

Команда WAIT останавливает выполнение программы

микропроцессором, аналогично команде HLT. Но в случае команды WAIT

выполнение программы возобновляется, когда один из внешних выводов

микропроцессора 8088, вывод TEST, становится активен. Если вывод

TEST активен во время выполнения команды WAIT, остановки не

возникает вообще. Если вывод TEST неактивен, микропроцессор ждет до

тех пор, пока он не станет активен. Микропроцессор 8088 использует

эту команду вместе с командой ESC, чтобы работать с арифметическим

сопроцессором 8087.

Команда ESC дает возможность расширить набор команд

микропроцессора 8088 без изменений самого микропроцессора. Команда

содержит поле режима адресации и может указать любую ячейку памяти

с помощью обычных способов адресации микропроцессора 8088. Однако

микропроцессор ничего не делает в случае этой команды, кроме того,

что читает данные из соответствующей ячейки и просто их

отбрасывает.

Команда ESC позволяет другому микропроцессору, или так

называемому сопроцессору, наблюдать за работой микропроцессора

8088. Команда ESC активизирует сопроцессор, и он выполняет ее, как

собственную. Если сопроцессору нужен адрес памяти, микропроцессор

8088 выдает этот адрес в цикле фиктивного чтения. Затем сопроцессор

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

того, что ему нужно. Эффективность команды ESC станет очевидна в

гл.7, где рассматривается арифметический сопроцессор 8087,

сопроцессор микропроцессора 8088.


Untitled

Глава 5

Dos и Ассемблер

В этой главе излагаются все детали, необходимые для ассемблирования

и выполнения программ. В предыдущих главах объяснялось, как

работает микропроцессор 8088. Теперь время проверить полученные

знания, так как только самостоятельное составление и успешная

прогонка программ могут дать полное представление о системе команд

микропроцессора 8088.

В данной главе будут рассмотрены четыре основные этапа

подготовки программы: редактирование, ассемблирование, компоновка

(редактирование связей) и отладка. Каждый из этих этапов

осуществляется с помощью отдельной программы и отдельного набора

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

операционной системы (DOS). Три из этих программ входят в DOS и

могут быть заказаны вместе с IBM PC. Ассемблер не входит в DOS, но

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

только программы, разработанные фирмой IBM.


Untitled

Дисковая операционная система

Поскольку для подготовки и выполнения программ мы собираемся

использовать DOS, то начнем с рассмотрения того, что такое DOS, и

каковы ее функции. DOS обеспечивает операционную среду, в которой

выполняются другие программы. В больших ЭВМ операционная система

представляет собой программу, которая управляет работой всей

машины. В большинстве случаев у таких ЭВМ имеется несколько

пользователей, конкурирующих между собой из-за доступа к ресурсам

машины. Операционная система играет роль рефери, который решает,

кто из пользователей, когда и к каким ресурсам имеет доступ.

Операционная система позволяет пользователям работать, не мешая

друг другу, а также предлагает им выбор сервисных программ, которые

освобождают их от всех трудностей, связанных с учетом аппаратных

особенностей ЭВМ. Эти программы исключают выполнение пользователем

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

тем самым, нарушают правильную работу аппаратных средств.

Значительная часть операционных систем больших ЭВМ устроена так,

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

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

здесь выше индивидуальной свободы пользовательских программ.

В небольших компьютерных системах, подобных IBM PC,

операционная система служит иной цели. В каждый момент времени с

персональной ЭВМ работает только один пользователь, и управление

ресурсами машины осуществляется извне: ими распоряжается тот, кто

сидит за клавиатурой. На программном уровне разрешены любые

последовательности команд. Выполнение программы может привести к

неприятностям только для того пользоватея, который в этот момент

работает с ЭВМ, и то только в том смысле, что его программа или

данные будут для него потеряны.

Назначение операционной системы IBM PC состоит в том, чтобы

обеспечить операционную среду и набор сервисных программ для

пользователей. В качестве пользователя может быть программист или

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

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

DOS. Однако, когда текстовый редактор записывает файл на дискету,

то пользователем DOS является текстовый редактор. Вместо того,

чтобы самому осуществить запись файла, текстовый редактор

пользуется услугами DOS.

Основная функция DOS IBM PC - это обеспечить файловую систему и

операционную среду для программ. Файловая система представляет

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

дискеты или жесткого диска. Если все прикладные программы для

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

этими данными, и кроме того, в каждом случае при разработке

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


Untitled

Файловая система

На каждой дискете, используемой в IBM PC, может храниться от 160 до

360 Кбайт информации, а на жестком диске - более 107 байт.

Очевидным образом возникает задача ведения архива. При таких

объемах информации необходим способ полного упорядочения хранимой

информации. В качестве пользователя DOS вы заинтересованы в том,

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

программа на языке ассемблера. Вас не интересует, где эти данные

будут располагаться на дискете. Физическое распределение этих

данных на поверхности дискеты - это заботы системы.

Основной единицей хранения данных является файл. Файл - это

совокупность данных, интерпретируемых некоторым образом. Владелец,

или создатель файла присваивает ему имя. Это имя может быть

использовано при любых ссылках на эти данные для того, чтобы

обеспечить к ним доступ. Ссылка на данные не требует никаких

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

Любой файл состоит из записей. Каждая запись - это отдельный

элемент данных, но не обязательно один байт. Чтобы понять, что

такое файлы и записи, лучше всего вспомнить, в каком значении эти

слова употребляются в делопроизводстве.

Файл - это большой ящик или папка, в которой хранится множество

документов. На файловой папке обычно указано ее название - имя

файла. В папке собраны отдельные записи. Например, в файле

преподаватели могут хранить контрольные работы, написанные

студентами. Каждая отдельная контрольная работа, входящая в файл,

соответствует одной записи. Собранные и храняшиеся у преподавателя

записи содержательно именуются, например "первая контрольная

работа". Чтобы отыскать чью-то контрольную работу, преподаватель

сначала находит нужный файл, а затем просматривает этот файл в

поисках требуемой записи.

Как все это соотносится теперь с файлами, обрабатываемыми ЭВМ?

Файл представляет собой совокупность связанных между собой данных,

и у файла есть имя. Записи - это то, что составляет файл, Размер и

содержание записей определяются программистом, DOS не проверяет

формат записей, а просто помещает их в файл. Для системы DOS любая

запись - это совокупность байтов в файле. Содержание байтов,

составляющих запись, определяется программистом.

Рассмотрим тепрь программу на языке ассемблера как пример

файла. У программы есть имя, и это имя станет именем

соответствующего файла. Файл состоит из записей, где каждая запись

представляет собой один оператор языка ассемблера. Формат любой

записи ни о чем не говорит DOS, он понятен только ассемблеру.

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

ассемблера. Для DOS не важно, как записи разбиваются на поля, это

дело прикладной программы, в данном случае - ассемблера.


Untitled

Имена файлов

Рассмотрим сначала, как система DOS формирует имена файлов. Каждое

сформированное DOS имя файла состоит из двух частей. Первая часть

имени файла имеет длину от 1 до 8 символов. Эта часть определяется

пользователем и соответствует присвоенному им "имени" файла.

Вторая часть имени, называемая расширением, имеет длину от 1 до 3

символов. Эта часть, определяющая "тип" файла, обычно задается

прикладной программой. Имя и его расширение разделяются точкой.

Например, в имени файла "COMMAND.COM" COMMAND является именем, а

COM - расширением.

В некоторых случаях расширение имени файла определяется самим

пользователем. При этои DOS или прикладная программа используют это

расширение для идентификации типа файла. В имени файла

"COMMAND.COM" .COM определяет файл команд. С ассемблированием

связан один входной файл и от одного до трех выходных файлов.

Расширение входного ассемблерного файла есть .ASM, а выходных

файлов: объектного - .OBJ, листинга - .LST и файла перекрестных

ссылок - .CRF. Во многих случаях в прикладных программах требуется,

чтобы у имени файлов были определенные расширения.

ДИРЕКТОРИИ

Так как на любой дискете может поместиться большое количество

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

файла было бы неэкономно. Операционная система позволяет хранить

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

На магнитком носителе может храниться более одного файла

благодаря тому, что система организует каталог содержимого диска.

Этот каталог напоминает оглавление книги. В нем перечислены все

файлы, имеющиеся на диске или дискете. Кроме имени файла DOS

помещает в каталоге и другую необходимую и полезную информацию.

Прежде всего в нем имеются указатели, которые нужны для определения

фактического местонахождения данных на магнитном носителе. Кроме

этого, каталог удобен тем, что имеет временные ярлыки для каждого

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

DOS производит запись соответствующей даты и времени. Это очень

удобно в тех случаях, когда существует множество копий некоторой

информации и вам требуется самая последняя версия.

Организация каталога на диске решает задачу хранения нескольких

файлов на одном диске или дискете. Однако в каждый момент времени

DOS может обращаться только к одному дисководу. Если в системе

имеется больше, чем один дисковод с гибким или жестким диском, то

нужно сообщить DOS, на каком из них расположен файл. В этом случае

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

Например, у файла COMMAND.COM в дисководе A уточненное полное имя

будет A:COMMAND.COM.


Untitled

Командный процессор

Наряду с файловой системой DOS обеспечивает операционную среду для

прикладных программ. Первый компонент DOS с которым сталкивается

пользователь, - это командный процессор. Эта часть DOS берет на

себя обработку вводимых пользователем команд и запуск прикладных

программ.

В первый момент после включения питания "интеллектуаль- ность"

IBM PC невысока: возможно и имеется большой потенциал, но

способностей хоть как-то реализовать его немного. В постоянной

памяти ЭВМ хранятся программы тестирования компонентов системы -

POST (Power=On Self=Test - самопроверка при включении) и установки

в начальное состояние устройств ввода-вывода. Остальная часть

записанной в ПЗУ базовой системы ввода-вывода BIOS (Basic Input

Output System) предоставляет программисту, работающему с языком

ассемблера, набор средств, которые помогают ему обращаться к

аппаратным средствам, не беспокоясь о том, как технически они

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

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

Последнее является функцией DOS. После установки ЭВМ в

начальное состояние программа POST выполныет загрузку DOS с диска

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

системы. Первое, что делает DOS, - загружает минимальную программу,

необходимую для загрузки остальной части DOS. По окончании процесса

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

указание на авторские права. Помимо всего прочего в названии указан

номер соответствующей версии операционной системы. Иногда этот

номер играет важную роль, так как каждая новая версия означает

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

После операции загрузки система готова к приему команд от

оператора (за исключением специального случая, о котором будет

сказано ниже). На этом этапе управление передается командному

процессору - DOS произвела загрузку в память командного процессора,

файловой системы и других служебных программ, и все они готовы

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

индицируется им следующим запросом к оператору

A>

Этот запрос содержит двоякий смысл. Символ ">" означает

готовность командного процессора к приему команды. Префикс "A"

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

как DOS может обрабатывать файлы, расположенные только на каком-то

одном диске, то в случае одновременной работы с несколькими дисками

или дискетами пользователь должен указать, к какому из дисководов

следует обратиться системе. В персональной ЭВМ дисководы

обозначаются буквами латинского алфавита. У вычислительной системы

с двумя накопителями на гибких дисках имеется дисковод A: и

дисковод B:. Жесткий диск обычно обозначается C: (После

наименования дисковода следует двоеточие.) Обычно файлы,

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

системой по умолчанию, если только оператор не изменит этот

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

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

файла с любого другого дисковода нужно кроме имени файла указать

DOS наименование дисковода.

Только в ответ на команды, которые поступают от пользователя

DOS выполняет какие-то действия. Все команды, относящиеся к DOS,

вводятся в ответ на запрос с ее стороны, индицируемый символом ">".

Пользователь вводит имя нужной ему команды, после чего поступившая

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

эту заявку, зависит от команды, которую ввел пользователь. Имеются

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

команда может инициировать нерезидентное, или транзитное

выполнение. Для выполнения таких команд должен существовать

определенный файл на диске.

Встроенные команды обеспечивают поддержку файловой системы. Они

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

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

команду, интерпретатор командных строк передает управление

соответствующей программе DOS. Программа реализует свою функцию,

соответствующую спецификации введенной команды, затем возвращает

управление DOS. На Фиг.5.1 приведены резидентные команды дисковой

операционной системы.

Команда Действие

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

COPY Копирует файл с одного места в другое

DATE Вывод/модификация текущей даты

DIR Вывод каталога дискеты

ERASE Удаление файла с дискеты

PAUSE Система ждет нажатия клавиши

REM Вывод комментария

RENAME Изменить имя файла

TIME Вывод/модификация текущего времени

TYPE Вывод содержимого файла

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

Фиг. 5.1 Резидентные команды DOS

Примером резидентной команды может служить команда DIR, которая

выводит на дисплей справочник дискеты. На Фиг.5.2 показаны выходные

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

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

записанного на ней файла уазываются его имя и тип, а также длина в

байтах, дата и время его создания. Так как для загрузки любой

программы DOS необходимо выполнить команду чтения каталога, то эта

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

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

A>dir

Volume in drive A has no label

Directory of A:\

COMMAND COM 37637 17/06/88 12:00

AUTOEXEC BAT 38 4/03/89 17:33

CONFIG SYS 96 17/06/88 12:00

COUNTRY SYS 12838 17/06/88 12:00

DISKCOPY COM 10428 17/06/88 12:00

DISPLAY SYS 15741 17/06/88 12:00

FDISK COM 70151 17/06/88 12:00

FORMAT COM 22923 17/06/88 12:00

KEYB COM 14759 17/06/88 12:00

KEYBOARD SYS 23360 3/08/88 12:00

REPLACE EXE 17199 17/06/88 12:00

SELECT COM 3674 3/08/88 12:00

SELECT HLP 27562 3/08/88 12:00

SELECT PRT 1594 3/08/88 12:00

SYS COM 11472 17/06/88 12:00

012345 678 109 17/06/88 12:00

16 File(s) 17408 bytes free

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

Фиг. 5.2 Каталог дискеты

Если пользователь введет нерезидентную команду, то командный

процессор попытается загрузить ее с диска или дискеты. В этом

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

имя файла совпадает с именем команды, интерпретатор просматривает

справочник в поисках такого файла, а когда находит, загружает его в

память. Затем интерпретатор передает управление загруженной

программе, которая теперь может реализовывать свои функции.

Но не каждый файл может быть загружен с помощью командного

процессора. Тип файла должен быть либо .COM, либо .EXE, что

соответствует либо файлу команды, либо выполняемому файлу. Конечным

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

связей является файл типа .EXE. Отсюда вытекает возможность

написания собственной системной команды. Если написали,

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

затем оставили эту программу на дискете, то ее можно загружать и

выполнять так же, как и любую другую программу DOS. Именно это

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

управлением DOS.

Существуют различия между файлами типа .COM и типа .EXE. Они

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

Хотя обычно после этапа редактирования связей получаются файлы типа

.EXE, существуют некоторые причины для использования и файлов типа

.COM. В следующем разделе будут обсуждаться различия между типами

файлов и тем, как преобразовывать файл типа .EXE в файл типа .COM.

Рассмотрим теперь пример вызова программы. Хорошей иллюстрацией

здесь может служить ассемблер. Чтобы вызвать ассемблер, нужно

ввести команду

A>ASM

Каталог дискеты содержит файл с именем ASM.EXE. Это и есть

ассемблер. После ввода команды ASM, он просматривает дискету в

дисководе A: (выбранном по умолчанию). Найдя файл с именем ASM.EXE,

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

Теперь вычислительная система находится под управлением ассемблера.

При благополучном завершении трансляции ассемблер вернет управление

командному процессору. Заметьте, что файл, содержащий ассемблер -

это файл типа .EXE, поэтому он может быть загружен командным

процессором.

Если ассемблер находится на дискете, установленной в дисководе

A:, то пользователь может обратиться к другому дисководу следующим

образом:

A>B:ASM

Префикс B: указывает DOS, что файл находится на дискете в

дисководе B:. Файл полностью определяется не только указанием его

имени, но и дисковода, с которого он будет считан. Одного имени

файла достаточно только при считывании файла с дисковода,

выбранного по умолчанию. Для того чтобы оттранслировать файл,

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

который находится на дискете в дисководе A:, нужно ввести следующую

команду:

A>ASM B:FILE.ASM

В этой команде одновременно задаются программы ASM, считываемые

с дисковода, который выбран по умолчанию, и исходный файл FILE.ASM,

считываемый с дисковода B:.

Это можно сделать и другим способом: задать команду B:, которая

предписывает DOS сделать дисководом, выбираемым по умолчанию,

дисковод B:.

A>B:

B>A:ASM FILE.ASM

Заметьте, что после этого запрос со стороны системы меняется на

B>. Приведенная в этом примере команда по своему действию полностью

идентична команде из предыдущего примера.

Кроме того, интерпретатор командных строк может работать с

файлом, называемом файлом с пакетом команд, с расширением имени

.BAT. Этот тип файла совершенно отличен лт файлов типа .COM и типа

.EXE. Файл типа .BAT не содержит выполняемого машинного кода, а

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

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

выполняются DOS по очереди. Можно считать, что файл типа .BAT

заменяет процедуру ввода команд с клавиатуры, так как они

содержатся непосредственно в соответствующем файле. После того,

как система закончила обработку пакетного файла, она обращается за

следующей командой к клавиатуре. Все эти особенности делают файл

типа .BAT удобным средством выполнения повторяющихся заданий. После

того, как такой файл уже создан, единственная команда обращения к

нему заменяет ввод всех содержащихся в нем команд.

В системе допускается специальный файл с именем AUTOEXEC.BAT.

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

DOS обращается к нему, передавая управление командам, составляющим

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

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

прикладную программу, использующую DOS. (При этом говорят, что

программа была написана с "привязкой к DOS".) В случае создания

файла AUTOEXEC.BAT, инициирующего выполнение прикладной программы,

оператору, работающему с этой прикладной программой, совершенно не

нужно будет знать, как работает интерпретатор командных строк. Он

знает, что управление осуществляет программа и это главное.


Untitled

Функции DOS

Интерпретатор командных строк обеспечивает средства, необходимые

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

выполняться. Кроме того, DOS уже во время выполнения программы

обеспечивает доступ к файловой системе с помощью механизма функций

DOS. В данном разделе объясняется, что представляют собой эти

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

Программа использует функции DOS посредством программного

прерывания. Благодаря этому, программа может вызывать

соответствующую служебную программу, не зная ее адреса. Нужное

прерывание задается программистом. А во время инициализации DOS

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

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

по мере получения других версий DOS нет необходимости вносить

изменения в программы. На Фиг.5.3 приводятся векторы прерывания

DOS.

Прерывание Действие

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

20H Окончание программы

21H Вызов функции (см. Фиг. 5.5)

22H Адрес завершения

23H Адрес CTRL-BREAK

24H Обработчик кртической ошибки

25H Чтение с диска по абсолютному адресу

26H Запись на диск по абсолютному адресу

27H Завершить, но остаться резидентно

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

Фиг. 5.3 Прерывания DOS

Некоторые прерывания фактически предназначены для

пользовательских подпрограмм. Прерывания 22H, 23H и 24H являются

указателями на подпрограммы, которые могут быть в программе

пользователя. Эти векторы определяют программу, которая должна

выполняться при наступлении соответствующей ситуации. Например, при

нажатии клавиши CTRL-BREAK DOS выполняет прерывание 23H. Обычно

нажатие этой клавиши вызывает останов программы. Обычно DOS

выполняет стандартную обработку соответствующих ситуаций. Если же

программе нужно обрабатывать их иначе, то она может изменить вектор

прерывания.

В качестве примера рассмотрим прерывание 24H - обработку

неустранимой ошибки. Всякий раз обнаруживая ошибку система

инициирует прерывание. Обычно соответствующий вектор указывает на

программу в DOS, которая выводит на дисплей сообщение об ошибке. В

случае ошибки, связанной с диском, DOS выводит сообщение о дисковой

ошибке. Пользователь может по своему усмотрению сделать повторную

попытку выполнения операции, вызвавшей ошибку, либо закончить

выполнение, либо пропустить операцию, вызвавшую ошибку.

Предположим, например, что вы пишите программу форматирования

дискет. Это операция, в ходе которой на поверхности дискеты

происходит физическая разметка дорожек и секторов. Вместе с

форматированием дискеты проверяется отсутствие сбойных участков.

Последняя операция называется верификацией дискеты. Так как при

верификации допускается присутствие на дискете одно-двух сбойных

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

дальнейшем их случайно не использовать. Данная программа

форматирования заменяет обработку неустранимых ошибок. Вам не

нужно, чтобы DOS выводила для пользователя сообщение об ошибке.

Вместо этого вы хотите, чтобы обработка ошибки верификации

происходила в самой программе, и соответствующий участок на диске

помечался как сбойный. Для этого вы заменяете вектор прерывания в

ячейке 0:0090H(24H*4) указателем на свою подпрограмму обработки

ошибок. Когда DOS зафиксирует дисковую ошибку, ваша подпрограмма

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

этом пользователя.

Прерывания 25H и 26H связывают между собой две части системы. С

точки зрения своей файловой структуры DOS фактически состоит из

двух компонент. Одна из них, IBMBIO.COM, включает программы

непосредственного доступа к аппаратным средствам. Другая,

IBMDOS.COM реализует файловую систему. Упомянутые два прерывания

используются, когда компонента DOS, относящаяся к файловой системе,

обращается к другой компоненте - BIO. Хотя эти прерывания и могут

использоваться программистом, основное их назначение - разделить

две части DOS. Прерывание 25H осуществляет чтение, а прерывание 26H

- запись информации с абсолютной адресацией диска. На этом уровне

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

файле.

Прерывания 20H и 27H обеспечивают механизм возврата управления

к DOS после выполнения программы. Прерывание 20H соответствует

нормальному завершению работы программы. Прерывание 27H интересно

тем, что хотя оно связано с завершением программы, занимаемая

программой область памяти не возвращается обратно в распоряжение

DOS. Все содержимое данной области сохраняется неизменным до тех

пор, пока не будет выключено питание или выполнена переустановка

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

нужно ввести специальную обработку прерываний, либо аналогичную

функцию, которая должна сохраниться как часть системного

программного обеспечения. В гл.10 будет приведен пример

использования прерывания INT 27H для расширения системы. В DOS

имеется особенность работы с прерываниями 20H и 27H. Их

Значение

в AH Функция

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

0 Завершение программы

1 Ввод с клавиатуры

2 Вывод на экран

3 Дополнительный ввод (асинхронная коммуникация)

4 дополнительный вывод

5 Вывод на принтер

6 Прямой ввод/вывод с консоли

7 Прямой ввод с консоли без эха

8 Ввод с консоли без эха

9 вывод строки

OAH Буферизованный ввод с клавиатуры

0BH Проверка состояния клавиатуры

0CH Очистка буфера клавиатуры и функция # в AL

0DH Переустановка диска

0EH Выбор диска

0FH Открытие файла

10H Закрытие файла

11H Поиск первого

12H Поиск следующего

13H Уничтожение файла

14H Последовательное чтение

15H Последовательная запись

16H Создание файйла

17H Переименование файла

19H Текущий диск

1AH Установка адреса обмена с диском

1BH Адрес таблицы размещения

21H Прямое чтение

22H Прямая запись

23H Размер файла

24H Установка записи для прямого обращения

25H Установка вектора прерывания

26H Создание нового програмного сегмента

27H Прямое чтение блока

28H Прямая запись блока

29H Анализ имени файла

2AH Чтение даты

2BH Установка даты

2CH Получение времени

2DH Установка времени

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

Фиг. 5.4 Функции прерывания DOS 21H

использование предпочтительней в файле типа .COM, так как файл типа

.EXE имеет существенно другой формат и использование в нем этих

функций DOS немного сложнее. В следующем разделе будут рассмотрены

различия между файлами типа .COM и типа .EXE и то, почему

упомянутые прерывания, связанные с завершением работы программы,

выполняются в них по-разному.

Прерывание 21H является прерыванием, через которое происходи

обращение к основным функциям DOS. Это прерывание обеспечивает

доступ к системе ввода-вывода, управляемой DOS. На Фиг.5.4

представлены все возможные функции, использующие это прерывание.

Выбор функции в программе осуществляется с помощью записи в регистр

AH нужного значения перед выполнением прерывания 21H.

Параметры этих функций приводятся в приложении D руководства по

DOS. Вместо подробного ознакомления мы разберем пример, в котором

использованы некоторые из них. В частности, этот пример включает

функции DOS, связанные с диском.


Блок управления файлом (FCB)

Прежде, чем перейти к упомянутому примеру, необходимо рассмотреть

формируемую DOS структуру данных - блок управления файлом FCB (File

Control Block), который является существенным элементом файловой

системы и участвует во всех файловых операциях.

Блок управления файлом обеспечивает связь пользовательской

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

обращение к блоку FCB. На Фиг.5.5 показан состав стандартного

блока FCB. Имеется модификация блока FCB, называемая расширенным

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

"скрыть" файл. Скрытый файл защищен от записи. Это значит, что

программа не может модифицировать содержимое этого файла, не

изменив предварительно его блока FCB. Скрытый файл не фигурирует в

листинге справочника. Скрыть файл - один из простейших способов

защиты файла от неумелого пользователя. В приводимых примерах

используются только стандартные блоки FCB.

Поля данных блока FCB охватывают все атрибуты файла. Номер

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

файла и дата яаляются атрибутами файла, которые приводятся в

листинге справочника. Оставшиеся поля - текущий номер блока, длина

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

определения местоположения внутри файла при операциях чтения и

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

пользователем записи. Так как все операции чтения и записи в файл

начинаются с границы записи, то длина записи определяет количество

данных, обрабатываемых во время каждой из этих операций.

Существуют два способа определения текущей записи при обращении

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

обрабатываются по порядку. При этом текущий номер блока и

относительный номер записи определяют запись, которая будет

обрабатываться следующей. По мере того, как программа выполняет

операции чтения или записи, DOS увеличивает на 1 относительный

номер записи, чтобы он указывал на следующую запись. Выполнение

ЪДДДДї

0 і і Номер носителя

ГДДДДЕДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДї

1 і і і і і і і і і Имя файла

ГДДДДЕДДДДЕДДДДЕДДДДБДДДДБДДДДБДДДДБДДДДЩ

9 і і і і Расширение

ГДДДДЕДДДДЕДДДДЩ

0С і і і Текущий блок

ГДДДДЕДДДДґ

0E і і і Размер записи

ГДДДДЕДДДДЕДДДДВДДДДї

10 і і і і і Размер файла

ГДДДДЕДДДДЕДДДДБДДДДЩ

14 і і і Дата

ГДДДДЕДДДДЕДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДДї

16 і і і і і і і і і і і Зарезервировано

ГДДДДЕДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДДЩ

20 і і Относительная запись

ГДДДДЕДДДДВДДДДВДДДДї (нумерация в блоке)

21 і і і і і Номер записи

АДДДДБДДДДБДДДДБДДДДЩ прямого доступа

Фиг. 5.5 Блок управления файлом

последовательных операций идет в направлении от начала к концу

файла. Примером последовательного файла служит программный файл на

языке ассемблера. Ассемблер осуществляет чтение записей от начала

до конца этого файла.

Программа может обращаться к файлу, используя также и

произвольный доступ. Произвольный в данном случае не означает, что

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

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

По-другому такой файл называют файлом с прямым доступом, так как у

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

Программа может обращаться к файлу любым из двух указанных

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

в блоке FCB значение поля относительного номера записи. При

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

программой. В файловой системе файл с произвольным доступом

является аналогом вектора. Точно так же, как для обращения к любому

элементу вектора программа должна задать значение соответствующего

индекса, так и при обращение к записи в файле с произвольным

доступом номер записи должен определяться программой.

Теперь можно рассмотреть пример программы, использующей блок

FCB и функции DOS. На Фиг. 5.6 приведена длинная программа, которая

реально мало что делает. Ее цель - продемонстрировать использование

блока FCB и функци DOS, связанных с прерыванием 21H. Однако

содержащиеся в этой программе операции могут применяться в реальных

задачах.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:12

Фиг. 5.6 Пример использования функций ДОС Page 1-1

PAGE ,132

TITLE Фиг. 5.6 Пример использования функций ДОС

0000 CODE SEGMENT

005C ORG 05CH ; Положение первого поля FCB

005C FCB LABEL BYTE ; Метка всего поля FCB

005C ?? FCB_DRIVE DB ? ; Номер устройства

005D 0008[ FCB_NAME DB 8 DUP (?) ; Имя файла

??

]

0065 0003[ FCB_EXT DB 3 DUP (?) ; Тип файла

??

]

0068 ???? FCB_BLOCK DW ? ; Номер текущего блока

006A ???? FCB_RECORD_SIZE DW ? ; Размер записи

006C ???????? FCB_FILE_SIZE DD ? ; Размер файла

0070 ???? FCB_DATE DW ? ; Дата последнего изменения

0072 000A[ FCB_RESV DB 10 DUP (?) ; Зарезервировано ДОС

??

]

007C ?? FCB_CURRENT_RECORD DB ? ; Номер текущей записи

007D ???????? FCB_RANDOM_RECORD DD ? ; Номер записи при прямом

; доступе

0090 ORG 090H

0090 DISK_TRANSFER_ADDRESS LABEL BYTE ; Буфер для данных

0100 ORG 100H

ASSUME CS:CODE,DS:CODE,ES:CODE

0100 E9 01CD R JMP PROGRAM_START ; Переход на начало программы

= 0020 RECORD_SIZE EQU 32 ; Размер записи

0103 03 00 00 00 00 KEYBOARD_BUFFER DB 3, 0, 0, 0, 0 ; Буфер ввода с клавиатуры

0108 94 A0 A9 AB 20 E3 A6 FILE_ERROR_MSG DB 'Файл уже существует', 10, 13, '$'

A5 20 E1 E3 E9 A5 E1

E2 A2 E3 A5 E2 0A 0D

24

011E 8D A5 A2 AE A7 AC AE BAD_OPEN_MSG DB 'Невозможно открыть файл', 10, 13, '$'

A6 AD AE 20 AE E2 AA

E0 EB E2 EC 20 E4 A0

A9 AB 0A 0D 24

0138 8E E8 A8 A1 AA A0 20 BAD_WRITE_MSG DB 'Ошибка при записи в файл', 10, 13, '$'

AF E0 A8 20 A7 A0 AF

A8 E1 A8 20 A2 20 E4

A0 A9 AB 0A 0D 24

0153 8E E8 A8 A1 AA A0 20 BAD_READ_MSG DB 'Ошибка при чтении файла', 10, 13, '$'

AF E0 A8 20 E7 E2 A5

AD A8 A8 20 E4 A0 A9

AB A0 0A 0D 24

016D 8E E8 A8 A1 AA A0 20 BAD_CLOSE_MSG DB 'Ошибка при закрытии файла', 10, 13, '$'

Фиг. 5.6 Пример использования функциямй DOS (начало)

AF E0 A8 20 A7 A0 AA

E0 EB E2 A8 A8 20 E4

A0 A9 AB A0 0A 0D 24

0189 8F E0 A8 20 A2 A2 AE INPUT_BAD_MSG DB 'При вводе требуется два символа', 10, 13, '$'

A4 A5 20 E2 E0 A5 A1

E3 A5 E2 E1 EF 20 A4

A2 A0 20 E1 A8 AC A2

AE AB A0 0A 0D 24

01AB 82 A2 AE A4 20 A4 AE CHAR_BAD_MSG DB 'Ввод должен быть: символ|символ', 10, 13, '$'

AB A6 A5 AD 20 A1 EB

E2 EC 3A 20 E1 A8 AC

A2 AE AB 7C E1 A8 AC

A2 AE AB 0A 0D 24

;----- Установка буфера (Области связи с диском)

01CD PROGRAM_START:

01CD B4 1A MOV AH, 1AH ; Установка буфера

01CF 8D 16 0090 R LEA DX, DISK_TRANSFER_ADDRESS

01D3 CD 21 INT 21H

;----- Поиск файла

01D5 B4 11 MOV AH, 11H ; Поиск файла с заданным

01D7 8D 16 005C R LEA DX, FCB ; именем

01DB CD 21 INT 21H

01DD 0A C0 OR AL, AL

01DF 75 0A JNZ NO_FILE ; Переход если файл новый

01E1 8D 16 0108 R LEA DX, FILE_ERROR_MSG ; Сообщение о неправильном

01E5 ERROR_EXIT: ; имени файла

01E5 B4 09 MOV AH, 9H ; Вывод сообщения на экран

01E7 CD 21 INT 21H ; и выход из программы

01E9 CD 20 INT 20H

;----- Создание нового файла

01EB NO_FILE:

01EB B4 16 MOV AH, 16H ; Создание файла

01ED 8D 16 005C R LEA DX, FCB

01F1 CD 21 INT 21H

01F3 0A C0 OR AL, AL ; Проверка на успех

01F5 74 06 JZ CREATE_OK

01F7 8D 16 011E R LEA DX, BAD_OPEN_MSG ; Сообщение об ошибке при

01FB EB E8 JMP ERROR_EXIT ; создании файла

;----- Установка параметров FCB

01FD CREATE_OK:

01FD C6 06 007C R 00 MOV FCB_CURRENT_RECORD, 0 ; Инициализация номера

0202 C7 06 007D R 0000 MOV WORD PTR FCB_RANDOM_RECORD, 0 ; записи

0208 C7 06 007F R 0000 MOV WORD PTR FCB_RANDOM_RECORD+2, 0

020E C7 06 006A R 0020 MOV WORD PTR FCB_RECORD_SIZE, RECORD_SIZE

Фиг. 5.6 Пример использования функциямй DOS (продолжение)

;----- Вывод в файл

0214 B0 41 MOV AL, 'A' ; В этом цикле выводятся

0216 CHARACTER_LOOP: ; символы алфавита

0216 8D 3E 0090 R LEA DI, DISK_TRANSFER_ADDRESS

021A B9 0020 MOV CX, RECORD_SIZE

021D F3/ AA REP STOSB ; Заполнение буфера символами

021F 50 PUSH AX ; Сохранение символа

0220 8D 16 005C R LEA DX, FCB

0224 B4 15 MOV AH, 15H ; Последовательный вывод

0226 CD 21 INT 21H ; блока символов

0228 0A C0 OR AL, AL ; Проверка на ошибку

022A 58 POP AX ; Восстановление символа

022B 74 06 JZ WRITE_OK

022D 8D 16 0138 R LEA DX, BAD_WRITE_MSG ; Ошибка записи

0231 EB B2 JMP ERROR_EXIT

0233 WRITE_OK:

0233 FE C0 INC AL ; Следующий символ

0235 3C 5B CMP AL, 'Z'+1 ; Проверка на окончание вывода

0237 75 DD JNE CHARACTER_LOOP ; Вывод следующего символа

;----- Изменение файла

0239 KEYBOARD_LOOP: ; Цикл ввода символов

0239 8D 16 0103 R LEA DX, KEYBOARD_BUFFER ; с клавиатуры и

023D B4 0A MOV AH, 0AH ; изменение файла

023F CD 21 INT 21H ; Ввод с клавиатуры

0241 80 3E 0104 R 02 CMP KEYBOARD_BUFFER+1, 2 ; Было введено два символа?

0246 74 0A JE KEY_INPUT_OK

0248 8D 16 0189 R LEA DX, INPUT_BAD_MSG ; Сообщение об ошибке

024C KEYBOARD_ERROR:

024C B4 09 MOV AH, 9H

024E CD 21 INT 21H ; Печать сообщения об ошибке

0250 EB E7 JMP KEYBOARD_LOOP ; Повторный ввод

0252 KEY_INPUT_OK:

0252 8D 16 01AB R LEA DX, CHAR_BAD_MSG ; Установка указателя на

; сообщение об ошибке

0256 A0 0105 R MOV AL, KEYBOARD_BUFFER+2 ; Выборка первого символа

0259 3C 24 CMP AL, '$' ; Сравнение с символом конца

025B 75 03 JNE CHANGE_RECORD ; Изменение записи

025D EB 5C 90 JMP PROGRAM_EXIT

;----- Чтение старой записи из файла

0260 CHANGE_RECORD:

0260 3C 41 CMP AL, 'A' ; Проверка на то, что символ

0262 7C E8 JL KEYBOARD_ERROR ; находится в диапазоне A-Z

0264 3C 5A CMP AL, 'Z'

0266 77 E4 JA KEYBOARD_ERROR

0268 2A E4 SUB AH, AH ; Преобразование символа в

026A 2C 41 SUB AL, 'A' ; номер записи

026C A3 007D R MOV word ptr FCB_RANDOM_RECORD, AX ; Занесение номера в FCB

026F 8D 16 005C R LEA DX, FCB

0273 B4 21 MOV AH, 21H ; Прямое чтение записи

0275 CD 21 INT 21H

Фиг. 5.6 Пример использования функциямй DOS (продолжение)

0277 0A C0 OR AL, AL ; Проверка на ошибку

0279 74 07 JE RANDOM_RECORD_OK

027B 8D 16 0153 R LEA DX, BAD_READ_MSG ; Сообщение об ошибке

027F E9 01E5 R JMP ERROR_EXIT

;----- Вывод старой записи на экран

0282 RANDOM_RECORD_OK:

0282 C6 06 00B0 R 0A MOV DISK_TRANSFER_ADDRESS+32, 10 ; Занесение символов конца

0287 C6 06 00B1 R 0D MOV DISK_TRANSFER_ADDRESS+33, 13 ; строки и конца вывода

028C C6 06 00B2 R 24 MOV DISK_TRANSFER_ADDRESS+34, '$' ; в буфер

0291 B4 09 MOV AH, 9H

0293 8D 16 0090 R LEA DX, DISK_TRANSFER_ADDRESS

0297 CD 21 INT 21H ; Вывод записи на экран

;----- Изменение файла

0299 A0 0106 R MOV AL, KEYBOARD_BUFFER+3 ; Выборка символа, на который будет

029C B9 001F MOV CX, RECORD_SIZE-1 ; заменяться содержимое записи

029F 8D 3E 0091 R LEA DI, DISK_TRANSFER_ADDRESS+1

02A3 F3/ AA REP STOSB ; Замена 31 символа

02A5 B4 22 MOV AH, 22H

02A7 8D 16 005C R LEA DX, FCB

02AB CD 21 INT 21H ; Прямая запись на файла на

; старое место

02AD 0A C0 OR AL, AL ; Проверка на ошибку

02AF 74 07 JZ RANDOM_WRITE_OK

02B1 8D 16 0138 R LEA DX, BAD_WRITE_MSG ; Сообщение об ошибке

02B5 E9 01E5 R JMP ERROR_EXIT

02B8 RANDOM_WRITE_OK:

02B8 E9 0239 R JMP KEYBOARD_LOOP ; Обработка следующей записи

;----- Конец программы

02BB PROGRAM_EXIT:

02BB B4 10 MOV AH, 10H ; Закрытие файла

02BD 8D 16 005C R LEA DX, FCB

02C1 CD 21 INT 21H

02C3 0A C0 OR AL, AL ; Проверка на ошибку

02C5 74 07 JZ CLOSE_OK

02C7 8D 16 016D R LEA DX, BAD_CLOSE_MSG ; Сообщение об ошибке

02CB E9 01E5 R JMP ERROR_EXIT

02CE CLOSE_OK:

02CE CD 20 INT 20H ; Возврат в ДОС

02D0 CODE ENDS

END

Фиг. 5.6 Пример использования функциямй DOS (продолжение)

Приведенная программа состоит из двух частей. В первой части

создается файл из 26 записей длиной по 32 байта. Каждая запись

соответствует одной букве алфавита: запись 1 - это "AAAA...A",

запись 2 - "BBBB...B" и т.д. Данный файл формируется

последовательно. Во второй части программы этот файл Mincho"'> рассматривается как файл с произвольным доступом. В ответ на

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

выводит на дисплей одну из 26 записей. С помощью этого же ввода

осущкствляется редактирование записи: 31 символ записи, начиная со

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

Выполнение программы завершается при вводе с клавиатуры символа

"$".

В этом примере моделируется справочная база данных. На первом

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

втором этапе выполняется обработка произвольных запросов к базе

данных и редактирование ее содержимого. Хотя любая реальная

программа управления базой данных гораздо сложнее, чем приведенный

здесь пример, однако он является хорошей иллюстрацией основных

функций, относящихся к работе с файлами.

Программа, приведенная на Фиг. 5.6, - это файл типа .COM. В

следующем разделе мы обсудим различие между этим файлом и файлом

типа .EXE. Использование в данном примере файла типа .COM позволяет

для завершения работы программы воспользоваться прерыванием INT

20H. В случае файла типа .COM начало программы в сегменте должно

иметь смещение 100H. Первые 100H байт программного сегмента

называются программным префиксом PSP и предназначены для хранения

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

написанной на языке ассемблера.

Смещение блока FCB в сегменте равно 05CH, и использование в

программе этого на первый взгляд произвольного адреса имеет

определенные основания. Запись информации в данный блок FCB

осуществляется командным процессором, входящим в состав DOS. После

того, как пользователь вызвал программу, указав ее имя, DOS

просматривает оставшуюся часть командной строки в поисках имен

файлов. Первое встретившееся в команде имя помещается в блок FCB,

смещение которого равно 05CH. Если в командной строке будет еще

одно имя, то оно запишется в блок FCB со смещением 06CH. Так как в

нашеи примере фигурирует только один файл, то используют только

блок FCB со смещением 05CH. Запуск описываемой программы

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

A> FIG5=6 TEST.FIL

FIG5=6 - имя программы. В каталоге программа фигурирует под

именем FIG5=6.COM. Файл TEST.FIL является файлом, который программа

создает и затем модифицирует. Интерпретатор командных строк

выбирает имя "TEST.FIL" и помещает его в соответствующее поле блока

FCB со смещением 05CH. Рассматривая имя файла в качестве параметра

командной строки, можно с помощью описываемой программы создавать и

модифицировать любой файл. Если бы имя файла входило в ассемблерную

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

Начиная со смещения 05CH, текст программы соответствует

структуре блока FCB. Первая ячейка идентифицируется меткой блока

FCB. Каждое из полей FCB имеет свою метку и длину, так что

программа может обрабатывать их непосредственно. Например, чтобы

задать длину записи, программа модифицирует переменную

FCB_RECORD_SIZE.

Начиная с адреса 080H помещается еще одно специальное поле

программного префикса. Эта область памяти размером 128 байт

отводится по умолчанию под область связи с диском DTA (Disk

Transfer Area) и используется DOS в качестве буфера для всех

файловых записей. Всякий раз, записывая или читая запись, DOS

использует буфер области DTA. Во время инициализации DOS

устанавливает смещение области DTA в программном сегменте равным

080H. Программа может изменить это значение, используя функцию 1AH

прерывания 21H. Это смещение должно быть изменено, еслм длина

записи больше, чем 128 байт. В рассматриваемом примере область

связи с диском сдвигается до смещения в сегменте, равного 90H. Это

связано с тем, что блок FCB со смещением 05CH выходит за границу с

адресом 80H. Если бы граница области DTA определялась соглашением,

принятым по умолчанию, то при передаче файлов информация в

последнем байте блока FCB была бы разрушена. В этом байте

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

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

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

исключить.

Первая команда программы имеет смещение 100H и является

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

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

происходит гораздо успешней, если все данные помещаются перед

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

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

большей надежности данные помещаются в начале программы.

В первой части программы устанавливается область связи с

диском. В данной программе эта буферная область имеет смещение 90H.

Так как длина записи равняется только 32 байтам, то впереди

программы имется достаточно места.

Далее в программе используется прерывание 21H и функция DOS для

поиска файла с именем, совпадающим с именем, записанным в блоке

FCB. Обратите внимание, что пара регистров DC:DX указывает на блок

FCB, как и должно быть при любых файловых операциях. Если система

обнаружит файл с идентичным именем, программа завершит работу,

выдав сообщение об ошибке и сохранив существующий уже файл. В

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

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

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

програма была бы составлена так, чтобы охватывать случай как новых,

так и уже существующих файлов.

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

NO_FILE. Формировать блок FCB до этого данной программе не нужно,

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

файловая операция не сможет быть выполнена, например, из-за

отсутствия свободного места на диске или отсутствия места в

каталоге диска, то программа завершит выполнение и выдаст

соответствующее сообщение об ошибке.

При любом обращении DOS к файлу программа должна, прежде всего,

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

операционной системой и пользовательской программой. В процессе

этой операции DOS просматривает справочник диска, находит нужный

файл (или не находит, что соответствует сбойной ситуации) и

заполняет поле блока FCB, относящееся к длине файла. После того,

как файл открыт, DOS не должна просматривать справочник диска

всякий раз, когда происходит обращение к файлу. Система DOS

сохраняет в блоке FCB информацию о файле до тех пор, пока она этот

файл не "закроет". Термины "закрыть" и "открыть" файл еще раз

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

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

содержащимся в ней бумагами, и закрыть до того, как ее уберут снова

в бюро.

В рассматриваемом примере открытие файла происходит при его

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

установлена с помощью функции открытия файла (AH=0FH). Если

открытие файла произошло успешно, программма изменяет значения

некоторых полей блока FCB. В частности, длина записи должна быть

установлена равной 32 байт, так как по умолчанию DOS считает ее

равной 128 байт.

Часть программы с именем CHARACTER_LOOP передает в файл 26

записей. Определяющий символ каждой записи передается в буфер

области DTA оператором REP STOSB. Отдельные записи передаются на

диск с помощью функции последовательной записи (AH=0AH). Программа

также выполняет проверку на отсутствие ошибок.

Начиная с метки KEYBOARD_LOOP программа переходит от

формирования файла к запросу=коррекции записей в нем. В данном

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

поддерживаемый DOS. Это позволяет пользователю вводить строку из

двух символов и, если необходимо, осуществлять редактирование.

После ввода двух символов должна быть нажата клавиша окончания

ввода. Программа выполняет проверку правильности ввода, и если

последний не удовлетворяет требованиям, то он отвергается и

выдается сообщение об ошибке.

При вводе пользователем символа "$" программа заканчивает

работу. В случае же ввода символов от "A" до "Z" программа

осуществляет чтение соответствующей записи и выводит на дисплей ее

содержимое. В оставшиеся 31 байт буфера программа записывает второй

из введенных с клавиатуры символов. Модицифированная таким образом

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

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

Аналогично тому, как функция открытия файла устанавливала связь

между DOS и пользовательской программой,

??????

файла существенно для гарантии того, что DOS записала на диск все

модифицированные записи. Во время нормального выполнения программы

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

ускоряет выполнение, так как DOS не должна обращаться к диску по

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

диск содержимого буфера.

Приведенный на Фиг. 5.6 пример программы иллюстрирует основные

способы обращения к файлу с помощью DOS. Эта программа ничего

полезного не выполняет, однако приближение ее к реальным задачам

потребовало бы намного большего числа команд, которые мало что

прояснили бы в отношении использования функций DOS. В данной

программе важно обратить внимание на необходимость проверки ошибок

после каждой операции, выполняемой DOS. В то время, как DOS с

помощью прерывания 24H обрабатывает аппаратные ошибки,

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

совпадение имен файлов или отсутствие свободного места на дискете.

В данном примере обработка ошибок проста и состоит из вывода

соответствующего сообщения и завершения работ программы. Обработка

ошибок в реальных программах значительно сложнее и более актуальна,

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

быть исключена.

И наконец, возможно, что проработав с данной программой, вы

будете не очень удовлетворены ею как пользователь: в ней

отсутствует запрос на ввод данных, сообщения об ошибках лаконичны и

некоторые из выводимых сообщений частично накладываются на

предыдущие, затирая их. Данная программа нуждается в доработке

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

ее написании или тщательно в ней не разобравшийся.


Untitled

Файлы .com и .exe

В предыдущем примере рассматривался файл типа .COM. Однако

результатом процесса ассемблирования-редактирования связей является

обычно файл типа .EXE. Зачем нужен файл типа .COM, если проще

получить файл типа .COM?

У каждого из обоих типов файлов есть свои преимущества. И чтобы

принять обоснованное решение о том, какой из них предпочесть в

каждом конкретном случае, нужно представлять их отличия.

Главное различие между файлами типа .COM и типа .EXE связано с

форматом записи соответствующего объектного файла на дискете. Оба

типа файлов являются программами, записанными на машинном языке.

Программа, записанная в файле типа .COM может сразу выполняться.

DOS может непосредственно загрузить его в память машины с дискеты.

После этого DOS передает управление в сегмент памяти, отведенный

для команд, в точку со смещением 100H. Файл типа .EXE

непосредственно выполнен быть не может. У соответствующего

объектного файла, хранящегося на дискете, имеется заголовок. В нем

содержится информация, сгенерированная редактором связей. Наиболее

важная ее часть относится к информации, связанной с перемещением. В

то время, как у файла типа .COM перемещаем один сегмент команд, у

файла типа .EXE могут быть перемещены многие различные сегменты.

Это ограничивает максимальный размер файла .COM 64 кбайтами, если

только программа не подгружает еще и другие сегменты. Файл типа

.EXE может содержать ряд сегментов, которые динамически

перемещаются в пределах программной области.

В чем состоит перемещаемость? Во время ассемблирования

программа расположена в каком-то определенном месте памяти. Как

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

сегмент со смещением 0. В ассемблерных листингах рядом с некоторыми

адресами стоят символы R. Это означает, что данный адрес является

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

иметь смещение, отличное от 0, то упомянутый адрес должен быть

изменен. Обычно перемещением занимается редактор связей. Однако

пересчет значений некоторых адресов не может быть выполнен до

загрузки программы. В каждом файле типа .EXE имеется информация о

таких адресах.

Файл типа .COM не является перемещаемым. У такого файла

отсутствует информация, необходимая для перемещения. Вместо этого у

программы, составляющей файл типа .COM, должен быть перемещаем

сегмент команд. Это означает, что хотя сам сегмент команд можно

модифицировать, начальное смещение всегда должно быть одним и тем

же. В такой программе все смещения должны оставаться неизмененными.

Кроме того, от программиста требуется предусмотреть, чтобы при

любой операции с сегментными регистрами (например, запись в

сегментный регистр полученного значения) всегда производилось

обращение к регистру текущего сегмента команд. Примером правильной

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

значения сегмента команд будет:

PUSH CS

POP DS

Иногда может показаться заманчивым реализовать ту же операцию с

помощью следующей последовательности команд (предположим, что как и

в приведенной на Фиг. 5.6 программе имя сегмента команд - "CODE")

MOV AX,CODE

MOV DS,AX

Для программного файла типа .COM эта запись будет неправильной.

В момент ассемблирования и редактирования связей сегментное

значение для сегмента CODE неизвестно. Оно определяется только при

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

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

перемещения), то в данном случае программа будет выполняться

неправильно.

Между описываемыми типами файлов имеются различия в отношении

установки значений сегметных регистров и расположения стека. Для

файла типа .COM значения регистров CS, DS, ES и SS устанавливаются

DOS, равными такими, что они указывают на тот сегмент, в который

они загружают программу. Значение регистра SP устанавливается так,

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

Таким образом программа занимает начало, а стек - конец сегмента.

В головной метке файла типа .EXE задаются значения регистров

CS, IP, SS и SP. Значения регистров DS и ES DOS устанавливает таким

образом, чтобы они указывали на тот сегмент, в который загружается

программа. Регистр CS указывает на сегмент, который был

идентифицирован как сегмент содержащий стартовый адрес программы.

Если в файле типа .COM программа должна иметь начальное смещение в

сегменте команд равным 100H, то в программном файле типа .EXE

начальный адрес может иметь другое значение. Как показано ниже,

значение этого адреса может содержаться в операторе END:

END START_LOCATION

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

загрузки программы управление следует передать на метку

START_LOCATION.

В обоих типах файлов используется программный префикс PSP,

который образуют первые 100H байтов того сегмента, куда загружается

программа. В этой области памяти хранится специальная информация, о

которой говорилось при рассмотрении программы, приведенной на

Фиг. 5.6. В случае файла типа .EXE регистры DS и ES указывают на

эту область данных, тогда как значения регистров CS и SS

устанавливаются на соответствующих этапах

ассемблирования-редактирования связей. В случае файла типа .COM все

регистры указывают на PSP. Это обеспечивает обоим типам файлов

непосредственный доступ к информации, хранящейся в порядке

программного сегмента.

Преимущества файла типа .COM состоит в том, что в этом случае

регистр CS указывает на PSP, а в файле типа .EXE - нет. Прерывания

20H и 27H, связанные с завершением выполнения программы и передачей

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

на PSP. В случае файла типа .EXE это осуществить сложно. К

счастью, следующая последовательность команд позволяет в

программном файле типа .EXE передать управление обратно DOS.

PROGRAM PROG FAR

PUSH DS ; запись сегмента с PSP

MOV AX,0

PUSH AX ; запись в стек смещения 0

...

RET

PROGRAM ENDP

В относящейся к PSP ячейке со смещением 0 содержится команда

INT 20H. Запись в стек состояния регистра DS и нуля устанавливает

значение адреса длинного возврата, равное адресу PSP со смещением

0. Выполнив команду возврата, программа перейдет к команде INT 20H.

Но к этому моменту в регистре CS уже будет храниться значение PSP,

и команда прерывания INT 20H возвратит управление DOS.

Для прерывания 27H, при котором завершается работа программы и

управление передается DOS с сохранением программы в памяти,

аналогичного способа реализации нет. Хотя имеются методы записи в

регистр CS правильного значения перед прерыванием 27H, обычно более

простым является организация программы в виде файла типа .COM.

И наконец, файл типа .COM занимает на диске меньше места, чес

файл типа .EXE с такой же как в файле типа .COM программой. Так как

у файла типа .COM отсутствует заголовок, то и места для него на

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

DEBUG будет изложен метод, позволяющий преобразовывать файл типа

.EXE в файл типа .COM.


Создание программы на языке Ассемблера

Путь от идеи к готовой для выполнения программы состоит из

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

программы на языке ассемблера для IBM PC. К обсуждаемым здесь

средствам относятся: строковый редактор, ассмеблер, редактор

связей (компоновщик) и отладчик. С помощью редактора создаются

исходные программы на языке ассемблера. Ассемблер преобразует

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

машинному языку. Редактор связей трансформирует объектный код в

файл типа .EXE, содержащий программу, которая готова к выполнению.

И наконец, программа-отладчик может оказать помощь в выявлении

ошибок в программе.

СТРОКОВЫЙ РЕДАКТОР ДОС

Редактор строк формирует текстовые файлы. Содержимое текстового

файла представлено в коде ASCII. Редактор позволяет вводить нужный

вам текст в файл. Если затем понадобится изменить файл, то для

этого опять используется редактор.

Редактор строк (EDLIN) входит в состав DOS IBM PC. Файл

EDLIN.COM является внешней командой, т.е. загружается в память

только по запросу. Для запуска редактора должна быть выполнена

следующая команда:

A>EDLIN FILE.ASM

где FILE.ASM - имя текстового файла, с которым будет работать

редактор. Файл FILE.ASM уже может существовать, как в случае, когда

вы вносите изменения в сформированную программу. Если же такого

файла еще нет, то он будет создан с помощью редактора EDLIN. По

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

FILE.ASM. Если в данном редактировании файл не создавался, то

редактор EDLIN выполняет переименование старого варианта файла

FILE.ASM в файл FILE.BAK (backup - копия). Создание копии файла

позволяет с ее помощью устранить любые серьезные ошибки, допущенные

при редактировании файла. Вы ликвидируете файл со внесенными при

редактировании ошибками и переименовываете файл .BAK в файл .ASM.

Таким образом, это служит подстраховкой при редактировании текста.

Редактор EDLIN является редактором строк. Каждую строку текста

он обрабатывает отдельно и этим отличается от экранного редактора,

который выводит на экран целый фрагмент редактируемого текстового

файла. При работе с экранным редактором, таким как Персональный

Редактор (Personal Editor), разработанный фирмой IBM, вы

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

экрана в то время, как с помошью редактора EDLIN каждый раз

редактируется только одна строчка текста. Если вы собираетесь

основательно заняться программированием на языке ассемблера, то вам

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

Персональный Редактор является очень хорошим экранным редактором

текста. Именно с его помощью были подготовлены все примеры в этой

книге. Редактор EDLIN рассматривается здесь только потому, что он

входит в состав DOS. Однако учмтывая, что это не лучший вариант

редактора, описание редактора EDLIN включает только краткое

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

подробней ознакомиться с командами редактора EDLIN, следует

обратиться к руководству по DOS.

На Фиг.5.7 подытожены сведения о командах редактора EDLIN. При

разборе примера мы будеи на них ссылаться.

Действие Синтаксис

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

Изменение строки [n] A

Уничтожение строки [строка][,строка] D

Редактировать строку [строка]

Конец редактирования E

Вставка строки [строка] I

Просмотр строк [строка][,строка] L

Выход из редактора Q

Замена контекста [строка][,строка] [?] ЗаменяемаяСтрока [<F6>Строка]

Поиск контекста [строка][,строка] [?] СтрокаПоиска

Записать строки [n] W

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

Фиг. 5.7 Команды EDLIN (Copyright IBM 1981)

Фиг. 5.8 иллюстрирует выполнение двух этапов редактирования

с использованием редактора EDLIN. В первой части редактирования

создается файл с именем FIG5=8.ASM. Сообщение "New file" ("новый

файл") указывает на то, что файла с таким именем до этого не было.

Символом "*" обозначается запрос со стороны редактора EDLIN. Всякий

раз, когда на дисплее появляется этот символ, можно вводить любую

из команд, приведенных на Фиг.5.7. Команда "I" позволяет вводить

текст в файл. По мере ввода текста редактор EDLIN указывает на

экране номера строк. Ввод порции текста завершается нажатием

клавиши Control=Z (на листинге - ^Z). Команда E завершает этап

редактирования, записывает файл и возвращает управление DOS.

На второй части Фиг. 5.8 показано редактирование файла,

созданного в первой чати. Редактор вызывается той же самой

командной строкой, а сообщение "End of input file" ("конец ввода

файла") указывает на то, что данный файл уже существует. Команда L

выводит на экран текущее содержимое файла. В данном примере

вносятся изменения в некоторые строки программы при выполнении

команды редактирования. Если ввести символ "3", то на дисплей будет

A>EGLIN FIG5_8.ASM

New file

*I

1:* PAGE ,132

2:*CODE SEGMENT

3:* ASSUME CS:CODE

4:*

5:*START: PROC NEAR

6:* MOV AX,CODE

8:* MOV DS,AX

8:* RET

9:*START: ENDP

10:*END

11:*^C

*E

A>

A>EDLIN FIG5_8.ASM

End of input file

*L

1:* PAGE ,132

2: CODE SEGMENT

3: ASSUME CS:CODE

4:

5: START PROC NEAR

6: MOV DS,AX

8: MOV DS,AX

8: RET

9: START ENDP

10: END

*3

2:*CODE SEGMENT

2:*CODE SEGMENT,DS:CODE

*5

5:*START: PROC NEAR

5:*START: PROC NEAR

*6

6:* MOV AX,CODE

6:* MOV AX,CS

*9I

9:*

10:*^C

*L

1:* PAGE ,132

2: CODE SEGMENT

3: ASSUME CS:CODE

4:

5: START PROC NEAR

6: MOV DS,AX

8: MOV DS,AX

8: RET

9:

10: START ENDP

11: END

*E

A>

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

Фиг. 5.8 Пример работы с EDLIN

выведена третья строка, готовая для редактирования. У системы

имеется набор встроенных команд построчного редактирования, которые

приведены на Фиг.5.9. Эти команды редактирования можно использовать

как при работе с редактором EDLIN, как и всегда, когда

осуществляется ввод символов с клавиатуры под управлением DOS, в

частности, при вводе команд, относящизся к командному процессору.

Вернемся к примеру, приведенному на Фиг. 5.8. После того, как

строка с указанным номером выведена на дисплей, ее можно

отредактировать с помощью команд DOS. Клавиша F3 позволяет повторно

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

строки. На примере редактирования строки 5 показано использование

клавиша DEL для удаления из поля метки символа ":". При

редактировании строки 6 используется клавиша (курсор вправо) для

установки курсора после ",". Вслед за этим код "CODE" заменяется на

символ "CS". Кроме того, для наглядности текста в данном примере

производится вставка пустой строки перед строчкой 9. После этого

опять выдается листинг файла для того, чтобы были видны внесенные

при редактировании изменения. Команда E - окончание редактирования

- возвращает управление DOS.

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

DEL - Уничтожение следующего символа в хранимой строке

ESC - Отмена редактирования хранимой строки

F1 или -> Копировать символ из хранимой строки

F2 - Копировать все символы до заданного

F3 - Копировать оставшиеся символы на экран

F4 - Пропустить все символы до заданного

F5 - Принять отредактированную строку для продолжения

INS - Вставить символ в строку (отмена - INS повторно)

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

Фиг. 5.9 Клавиши редактирования в DOS

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

помощью редактора EDLIN и команж редактирования, вхадящих в DOS.

Лучший способ изучить их - это просмотреть руководство по DOS и

затем все операции попробовать. Однако в случае серьезных

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


Untitled

Ассемблер и макроассемблер

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

ассемблер. Существуют две версии ассемблера: полная версия,

называемая Макроассемблером, которая на специальной, отведенной для

программы ассемблера дискете, именуется MASM.EXE, и менее полная

версия - Ассемблер, или ASM.EXE - без возможности работать с

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

MASM требуется объем памяти 96К, то для программы ASM достаточно

64К. Указанные значения никак не связаны с объемом памяти,

занимаемой исходной программой. Они относятся к объему памяти,

необходимой для ассемблирования, а не выполнения уже

оттранслированной программы. Поэтому возможна ситуация, когда для

разработки пользовательской программы, требующей для своего

выполнения только 4К, необходима ЭВМ с объемом памяти не менее 64К.

Входной информацией для ассемблера является исходный файл,

созданный редактором EDLIN, либо аналогичным редактором. Исходный

файл - это текстовый файл в кодах ASCII. В результате работы

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

представляет собой вариант исходной программы, записанной на

машинном языке. Объектный файл - это еще не совсем готовая для

выполнения программа, однако она близка к реальномй машинному

языку. Листинговый файл является текстовым явйлом в кодах ASCII,

включающим как исходную информацию, так и информацию, полученную в

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

являются ассемблерными листинговыми файлами. И наконец, ассемблер

может сформировать файл перекрестных ссылок. Этот файл, не

являющийся ни программой на машинном языке, ни текстовым файорм,

содержит информацию об использовании символов и меток в

ассемблерной программе. Как и в случае объектного файла, перед

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

дополнительная обработка.

Запуск ассемблера осуществляется командой DOS

A> ASM

или

A>MASM

Команда ASM запускает усеченный вариант ассемблера, а команда

MASM - Макроассемблер. После того, как ассемблер начинает

выполняться, он просит указать: какие файлы будут использованы при

ассемблировании. На Фиг. 5.10 приводится последовательность команд

для запуска ассемблера.

После того, как команда ASM введена, DOS загружает в память

ассемблер. Ассемблер выводит на экран название версии и переходит

к выдаче запросов к оператору. Если в вашей системе имеется только

один дисковод с гибким диском, то на этом этапе вы можете вынуть

дискету с ассемблером и вставить дискету с данными. Ассемблер

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

ввести только имя этого файла без указания типа .ASM. Кроме этого,

ассемблер запрашивает имена выходных файлов. Объектному файлу

ассемблер присваивает то же имя, что и у исходного файла, но с

расширением .OBJ, если только вы не захотите его изменить. В

данном примере ответ оператора "B:" является указанием ассемблеру,

чтобы он записал объектный яайл на диск в дисковод B:. Аналогичные

ответы оператора на запросы по поводу листингового файла и файла

перекрестных ссылок сообщают ассемблеру, чтобы эти файлы он также

записал на диск в дисководе B:. В справочнике диска,

установленного в дисководе B:, можно увидеть все полученные в

результате ассемблирования файлы.

Для всех запросов имеются режимы, применяемые по умолчанию.

Если в ответ на любой из этих запросов нажать клавишу возврата, то

ассемблер использует режим по умолчанию. Для листингового файла и

файла перекрестных ссылок по умолчанию принимается значение NUL.

Для DOS файл типа NUL - это специальный файл: все, что записывается

в файл NUL не доступно и не может быть восстановлено. Файл

NUL относится к файлам типа WOF (write=only file - файл только для

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

A>ASM

The IBM Personal Computer Assembler

Version 1.00 (C)Copyright IBM Corp 1981

Source filename [.ASM]: B:FIG5_10

Object filename [FIG5_10.obj] B:

Source lisying [NUL.LST] B:

Cross reference [NUL.CRF] B:

Warning Severe

Errors Errors

0 0

A>DIR B:FIG5_10.*

FIG5_10 ASM 44 1-01-83 12:00a

FIG5_10 LST 426 1-01-83 12:00a

FIG5_10 OBJ 40 1-01-83 12:00a

FIG5_10 CRF 19 1-01-83 12:00a

A>

A>B:

A>A:ASM FIG5_10,,,,

The IBM Personal Computer Assembler

Version 1.00 (C)Copyright IBM Corp 1981

Warning Severe

Errors Errors

0 0

B>

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

Фиг. 5.10 Выполнение ассемблирования

записи).

Если ассемблер во время ассемблирования обнаружит какие- нибудь

ошибки, он записывает их в листинговый файл. Кроме того, он выводит

их на дисплей, в результате чего можно непосредственно исправить

любые из обнаруженных ощибок и нет необходимости выискивать их в

листинговом файле. Если вы пользуетесь сокращенной версией

ассемблера, ASM, то информация об ошибках содержит только их коды.

В случае Макроассемблера, MSAM, выводится и код, и информация об

ошибке. В уменьшенном варианте ассемблера не остается места для

текстового сообщения об ошибке. В нижней части Фиг. 5.10 показан

более простой способ запуска Макроассемблера. Этот метод удобен,

когда в системе имеется два дисковода с гибкими дисками. В этом

случае дискета с ассемблером устанавливается в дисковод A:, а

дискета с информацией, содержащий исходный файл, - в дисковод B:.

Устанавливается режим выбора по умолчанию дисковода B:. Ассемблер

запускается с помощью команды A:ASM. В оставшейся части команды -

FIG5=10,,,; - содержится вся информация для ассемблера, которая

запрашивалась у оператора в предыдущем примере. FIG5=10 определяет

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

запятых служит указанием ассемблеру сгенерировать объектный,

листинговый файл и файл перекрестных ссылок в соответствии со

стандартным соглашением об именах файлов. При этом методе

ассемблирования получаются точно такие же результаты, что и в

первом случае.

Существует много способов того, как указать ассемблеру имена

файлов. Дап рассмотренных выше примера соответствуют крайним

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

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

запросах. Более подробно различные варианты, возможные в команде

ASM (или MASM), описываются в справочном руководстве по

Макроассемблеру.

После окончания ассемблирования можно воспользоваться

полученными выходными файлами. Объектный файл является исходным для

следующего этапа формирования выполняемой программы на машинном

языке. Это - этап редактирования связей LINK, который описывается в

следующем разделе.

В листинговом файле соединяются исходный файл и читабельный

вариант программы на машинном языке. Этот файл может быть выведен

на дисплей с помощью команды дисковой операционной системы TYPE,

например,

A> TYPE B:FIG5=11.LST

Команда TYPE загружает в память содержимое файла и выводит его

на дисплей. Одновременно этот же файл может быть выведен на печать,

если перед выполнением команды TYPE нажать клавиши Ctrl=PrtSc.

Нажатие Ctrl=PrtSc предписывает DOS осуществлять вывод как на

экран, так и на принтер. В результате листинг выводится и на

дисплей, и на печать. Ширину листинга следует задать равной 132

символам. Это выполняется с помощью команды ассемблера PAGE,

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

приводимых примерах программ. Команда

PAGE ,132

предписывает ассемблеру установить ширину листингового файла

равной 132 символам. Кроме того, перед выводом на принтер нужно

установить ширину печатаемой колонки. Это можно выполнить с помощью

команды MODE дисковой операционной системы.

A> MODE LPT1:132.

Данная команда устанавливает режим, при котором на принтере

фирмы IBM печатается 132 колонки. В этом случае листинговый файл

распечатывается без смещения строк, которого нельзя избежать на

дисплее.


Untitled

Таблица символических имен

В листинговом файле содержится дополнительная информация, о которой

до сих пор в этой книге нигде не говорилось. После листинга

программы выводится таблица символических имен. На Фиг. 5.11

показан пример такой таблицы, которая является таблицей имен для

программы, приведенной на Фиг. 5.6. В этой таблице перечислены все

символические имена, определенные в программе, и кроме того,

указаны атрибуты каждого из них. Будучи очень формальной системой,

ассемблер хранит эту информацию и для удобства пользователя

приводит ее в листинговом файле. Символические имена

подразделяются при этом на метки, переменные и константы. В

таблице приведено значение каждого имени, если оно известно, и

кроме того, безотносительно к типу данных - его длина.

Microsoft (R) Macro Assembler Version 4.00 4/15/89 23:14:35

Фиг. 5.6 Пример использования функций ДОС Symbols-1

Segments and Groups:

N a m e Size Align Combine Class

CODE . . . . . . . . . . . . . . 02D0 PARA NONE

Symbols:

N a m e Type Value Attr

BAD_CLOSE_MSG . . . . . . . . . L BYTE 016D CODE

BAD_OPEN_MSG . . . . . . . . . . L BYTE 011E CODE

BAD_READ_MSG . . . . . . . . . . L BYTE 0153 CODE

BAD_WRITE_MSG . . . . . . . . . L BYTE 0138 CODE

CHANGE_RECORD . . . . . . . . . L NEAR 0260 CODE

CHARACTER_LOOP . . . . . . . . . L NEAR 0216 CODE

CHAR_BAD_MSG . . . . . . . . . . L BYTE 01AB CODE

CLOSE_OK . . . . . . . . . . . . L NEAR 02CE CODE

CREATE_OK . . . . . . . . . . . L NEAR 01FD CODE

DISK_TRANSFER_ADDRESS . . . . . L BYTE 0090 CODE

ERROR_EXIT . . . . . . . . . . . L NEAR 01E5 CODE

FCB . . . . . . . . . . . . . . L BYTE 005C CODE

FCB_BLOCK . . . . . . . . . . . L WORD 0068 CODE

FCB_CURRENT_RECORD . . . . . . . L BYTE 007C CODE

FCB_DATE . . . . . . . . . . . . L WORD 0070 CODE

FCB_DRIVE . . . . . . . . . . . L BYTE 005C CODE

FCB_EXT . . . . . . . . . . . . L BYTE 0065 CODE Length = 0003

FCB_FILE_SIZE . . . . . . . . . L DWORD 006C CODE

FCB_NAME . . . . . . . . . . . . L BYTE 005D CODE Length = 0008

FCB_RANDOM_RECORD . . . . . . . L DWORD 007D CODE

FCB_RECORD_SIZE . . . . . . . . L WORD 006A CODE

FCB_RESV . . . . . . . . . . . . L BYTE 0072 CODE Length = 000A

FILE_ERROR_MSG . . . . . . . . . L BYTE 0108 CODE

INPUT_BAD_MSG . . . . . . . . . L BYTE 0189 CODE

Фиг. 5.11 Таблица символических имен

для программы Фиг. 5.6 (начало)

KEYBOARD_BUFFER . . . . . . . . L BYTE 0103 CODE

KEYBOARD_ERROR . . . . . . . . . L NEAR 024C CODE

KEYBOARD_LOOP . . . . . . . . . L NEAR 0239 CODE

KEY_INPUT_OK . . . . . . . . . . L NEAR 0252 CODE

NO_FILE . . . . . . . . . . . . L NEAR 01EB CODE

PROGRAM_EXIT . . . . . . . . . . L NEAR 02BB CODE

PROGRAM_START . . . . . . . . . L NEAR 01CD CODE

RANDOM_RECORD_OK . . . . . . . . L NEAR 0282 CODE

RANDOM_WRITE_OK . . . . . . . . L NEAR 02B8 CODE

RECORD_SIZE . . . . . . . . . . Number 0020

WRITE_OK . . . . . . . . . . . . L NEAR 0233 CODE

173 Source Lines

173 Total Lines

57 Symbols

48738 Bytes symbol space free

0 Warning Errors

0 Severe Errors

Фиг. 5.11 Таблица символических имен

для программы Фиг. 5.6 (продолжение)


Untitled

Перекрестные ссылки

Чтобы воспользоваться файлом перекрестных ссылок, сформированным

ассемблером, требуется дополнительная обработка. Для перевода

файла типа .CRF в текстовый файл в коде ASCII нужно выполнить

программу CREF. Программа CREF запускается точно так же, как и

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

файла: входной файл типа .CRF и выходной файл типа .REF. При

вводе команды DOS A> CREF запрашиваются имена двух файлов.

Альтернативой может служить команда A> CREF B:FIG5=10, B, которая в

качестве входного файла использует файл B:FIG5=10.CRF и формирует

файл B:FIG5=10.REF. Как и случае с ассемблером имеются и другие

варианты данной команды, описанные в справочном руководстве по

Макроассемблеру.

На Фиг. 5.12 показан выход формирователя перекрестных ссылок.

Данная конкретная таблица перекрестных ссылок получена для

программы, приведенной на Фиг. 5.6. В левом столбце перечислены все

символические имена, определенные в программе. Напротив каждого

символичсекого имени приводится последовательность целочисленных

параметров. Числа соответствуют номерам строк, в которых появляется

это имя. Если за номером строки следует символ #, то имя юыло

определено в этой строке. Если же символ # отсутствует, то значит в

этой строке содержится ссылка на имя.

Фиг. 5.6 Пример использования функций ДОС

Symbol Cross Reference (# is definition) Cref-1

BAD_CLOSE_MSG . . . . . . . . . 50# 169

BAD_OPEN_MSG . . . . . . . . . . 37# 86

BAD_READ_MSG . . . . . . . . . . 46# 141

BAD_WRITE_MSG . . . . . . . . . 41# 105 159

CHANGE_RECORD . . . . . . . . . 126 128#

CHARACTER_LOOP . . . . . . . . . 94# 110

CHAR_BAD_MSG . . . . . . . . . . 60# 123

CLOSE_OK . . . . . . . . . . . . 168 171#

CREATE_OK . . . . . . . . . . . 3# 28 28 173

DISK_TRANSFER_ADDRESS . . . . . 26# 68 95 144 145 146 148 152

ERROR_EXIT . . . . . . . . . . . 76# 87 106 142 160 170

FCB . . . . . . . . . . . . . . 5# 71 82 99 136 155 165

FCB_BLOCK . . . . . . . . . . . 15#

FCB_CURRENT_RECORD . . . . . . . 23# 89

FCB_DATE . . . . . . . . . . . . 18#

FCB_DRIVE . . . . . . . . . . . 6#

FCB_EXT . . . . . . . . . . . . 11#

FCB_FILE_SIZE . . . . . . . . . 17#

FCB_NAME . . . . . . . . . . . . 7#

FCB_RANDOM_RECORD . . . . . . . 24# 90 91 135

FCB_RECORD_SIZE . . . . . . . . 16# 92

FCB_RESV . . . . . . . . . . . . 19#

FILE_ERROR_MSG . . . . . . . . . 33# 75

INPUT_BAD_MSG . . . . . . . . . 54# 117

KEYBOARD_BUFFER . . . . . . . . 32# 112 115 124 150

KEYBOARD_ERROR . . . . . . . . . 118# 130 132

KEYBOARD_LOOP . . . . . . . . . 111# 121 162

KEY_INPUT_OK . . . . . . . . . . 116# 122#

NO_FILE . . . . . . . . . . . . 74 80#

PROGRAM_EXIT . . . . . . . . . . 127 163#

PROGRAM_START . . . . . . . . . 29 66#

RANDOM_RECORD_OK . . . . . . . . 140 143#

RANDOM_WRITE_OK . . . . . . . . 158 161#

RECORD_SIZE . . . . . . . . . . 31# 92 96 151

WRITE_OK . . . . . . . . . . . . 104 107#

 Фиг. 5.12 Таблица перекрестных ссылок для

программы Фиг. 5.6

Как может быть использован листинг перекрестных ссылок?

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

используется каждое имя. Например, если переменная получает Mincho"'> неверное значение, то листинг перекрестных ссылок показывает все

команды, где имеются ссылки на данное символическое имя. Это

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

выполнение программы. Возможно также, что вы занимаетесь

модификацией уже существующей программы, написанной кем-то другим

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

работает. Если вы захотели изменить одну из подпрограмм, то вам

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

используют. Листинг перекрестных ссылок показывает все команды CALL

(а так же другие команды, имеющие к этому отношение), в которых

есть ссылки на это символическое имя. Проанализировав

соответствующие места программы, вы можете решить: допустимо ли

вносимое изменение для всех тех участков, откуда вызввается данная

подпрограмма. Листинг перекрестных ссылок значительно облегчает

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


Untitled

Редактор связей

Программа, полученная на выходе ассемблера, еще не готова к

выполнению. Прежде, чем сформированный ассемблером объектный код

может выполняться, должна быть выполнена его "привязка".

Программа редактирования связей LINK (LINK.EXE), записанная на

той же дискете, что и другие распространенные в DOS программы,

фактически реализует две различные функции. Во-первых, она может

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

Во-вторых, на основе ассемблерного объектного модуля редактор связи

формирует выполняемый загружаемый модуль. Рассмотрим по отдельности

обе эти функции программы LINK.

Многомодульность

Как указывает само имя программы LINK, ее основное назначение

"связать", или объединить, несколько объектных модулей в один

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

относились к одномодульным программам, т.е. все, что они должны

были выполнять, реализовывалось в одном исходном модуле. Однако

этот путь не всегда возможен или желателен.

Имеется ряд причин, в силу которых программа программа сожет

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

Редактирование очень большой программы становится весьма громоздким

и трудным делом, а ее ассемблирование занимает много времени.

Предположим, что вы допустили ошибку в единственной строке

ассемблерной программы, состоящей из 5000 строк. Чтобы изменить

одну эту строку, необходимо выполнить редактирование всей

программы. После этого нужно оттранслировать всю программу из 5000

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

быстрого этапа редактирования связей программа готова к выполнению.

Предположим, теперь, что вместо того, чтобы иметь дело с

программой, состоящей из 5000 строк, вы разбиваете ее на десять

программых модулей, по 500 строк в каждом. Для внесения изменений в

единственную строку вам необходимо выполнить редактирование только

исходного файла из 500 строк. Ассемблирование программы из 500

строк занимает значительно меньше времени, чем программы из 5000

строк. Этап редактирования связей все так же будет занимать

относительно немного времени, особенно по сравнению с

ассемблированием большой программы. Уменьшение размеров отдельных

модулей позволяет более быстро осуществить процесс

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

Другая причина разбиения программы на меньшие модули связана с

этапом ее разработки. В случае больших программных средств в их

разработке, как правило, участвуют несколько человек. Если имеется

один исходный файл, то отдельные программисты вынуждены работать с

ним по очереди. Такой подход очень быстро становится неудобным.

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

возможности их универсального использования. Предположим, что

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

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

особую функцию с хорошо документированной спецификацией входа и

выхода. Через некоторое время при написании новой программы вы

захотите использовать в ней ту же самую подпрограмму. Если эта

подпрограмма оформлена в виде отдельного программного модуля, то

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

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

программы и перенести его в новую программу. После одной-двух

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

Разбиение программы на модули требует от программиста ряда

действий. Во-первых, нужно тщательно продумать, как ваша программа

будет строиться из меньших компонентов. Во-вторых, нужно определить

как входные, так и выходные параметры этих меньших программ. И

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

модулями. Первые два пункта относятся к основам программирования и

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

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

Если разработанная вами программа состоит из нескольких

модулей, то в этом случае у ведущей, или основной программы должна

быть возможность вызывать эти подпрограммы. Это реализуется

командой CALL, имеющей один операнд - метку соответствующей

подпрограммы. Во всех рассмотренных до сих пор примерах

подпрограмма была частью того же самого программного модуля, так

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

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

правильное значение смещения для поля адреса в команде.

Теперь мы сталкиваемся с ситуацией, когда ассемблирование

подпрограммы осуществляется отдельно от ассемблирования команды

CALL. Это означает, что ассемблер не может определить правильный

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

подпрограммы и основной программы осуществляется раздельно, у

ассемблера нет способа предсказать правильное значение адреса.

Однако эту задачу может выполнить программа LINK. Определение

адресов производится на этапе редактирования связей. Так как при

работе программы LINK ее входом являются все программные модули, то

редактору связей известно, где будет конец каждой из подпрограмм.

После этого редактор связей может определить значения тех адресов,

которые были известны ассемблеру.


EXTRN и PUBLIC

Редактор связей не может, однако, выполнить все, о чем говорилось

выше, самостоятельно. Ассемблер должен получить от программиста

информацию о подпрограммах, относящихся к другому программному

модулю. Это ввполняется с помощью оператора PUBLIC, извещающего

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

программам. Кроме того, программист указывает ассмеблеру, какие из

символических имен является внешними для данного программного

модуля. В языке ассемблера это реализуется оператором EXTRN,

который объявляет соответствующее имя внешним для текущего

ассемблирования, чтобы оно могло быть правильно обработано.

Ассемблер помечает данную команду таким образом, чтобы редактор

связей мог впоследствии найти ее и вставить туда правильное

значение адреса.

Оператор EXTRN выполняет две фуекции. Во-первых, он сообщает

ассемблеру, что указанное символическое имя является внешним для

текущего ассемблирования. Конечно, на этом этапе ассемблер мог бы

считать, что любое имя, не идентифицированное им в процессе

ассемблирования, является внешним. Однако, если когда-нибудь вы

ошиблись в указании имен, то ассемблер решит, что имеется в виду

внешнее имя, и не выдает сообщения об ошибке. Это отложит индикацию

ошибки до этапа редактирования связей. Для большинства

программистов это слишком поздно, особенно, если речь идет о чем-то

простом, вроде описки. Таким образом, ассемблер индицирует ошибку в

случае любого не определенного им символического имени.

Вторая функция оператора EXTRN состоит в том, что он указывает

ассемблеру тип соответствующего символического имени. Так как

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

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

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

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

Тип имени подпрограммы или другой программной метки может быть либо

NEAR, либо FAR, в зависимости от того, в каком сегменте она

находится. От программиста требуется указать в операторе EXTRN тип

символического имени. Так как кроме того ассемблером осуществляется

посегментная адресация программы, то оператор EXTERN указывает на

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

синтаксис оператора EXTRN, а определяется местоположением этого

оператора в программе. Ассемблер считает, что внешнее имя относится

к тому же сегменту, в котором появляется оператор EXTERN для этого

символического имени.

На Фиг. 5.13 приведен пример ассемблерной программы,

иллюстрирующей использование оператора EXTRN. Здесь имеются два

имени, являющиеся внешними для данной программы. OUTPUT_CHARACTER

обозначает однобайтовую переменную. Соответствующий этой переменной

атрибут ":BYTE" указывается после имени переменной. Указатель NEAR

программной метки OUTPUT_ROUTINE говорит о том, что она находится в

том же сегменте. Хотя приведенная на Фиг. 5.13 прогграмма содержит

ссылки на эти символические имена, при трансляции ассемблер знает,

как ему сегментировать правильные команды. Если бы оператор EXTRN

отсутствовал в программе, то в этом случае ассемблер

инициализировал бы ошибки. Из ассемблерного листинга видно, что

после поля адреса в командах, ссылающихся на внешние имена, стоит

символ E.

icrosoft (R) Macro Assembler Version 5.00 4/2/89 16:06:36

Фиг. 5.13 Основная программа Page 1-1

PAGE ,132

TITLE Фиг. 5.13 Основная программа

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?) ; Резервирование места для стека

????

]

0080 STACK ENDS

0000 CODE SEGMENT PUBLIC

EXTRN OUTPUT_ROUTINE:NEAR, OUTPUT_CHARACTER:BYTE

ASSUME CS:CODE

0000 START PROC FAR

0000 1E PUSH DS ; Сегмент адреса возврата

0001 B8 0000 MOV AX, 0

0004 50 PUSH AX ; Смещение адреса возврата

0005 FC CLD ; Установка направления

0006 8C C8 MOV AX, CS ; Установка сегментного регистра

0008 8E D8 MOV DS, AX

ASSUME DS:CODE ; Индикация состояния регистра

000A 8D 36 001D R LEA SI, MESSAGE ; Адрес строки сообщения

000E CLOOP:

000E AC LODSB ; Выборка следующего байта сообщения

000F A2 0000 E MOV OUTPUT_CHARACTER, AL ; Сохранение в памяти символа

0012 E8 0000 E CALL OUTPUT_ROUTINE ; Вывод символа

0015 80 3E 0000 E 0A CMP OUTPUT_CHARACTER, 10 ; Проверка на символ конца сообщения

001A 75 F2 JNE CLOOP ; Обработка следующего символа

001C CB RET ; Возврат в ДОС

001D 9D E2 A0 20 AF E0 AE MESSAGE DB 'Эта программа - тест', 13, 10

A3 E0 A0 AC AC A0 20

2D 20 E2 A5 E1 E2 0D

0A

0033 START ENDP

0033 CODE ENDS

END START

Фиг. 5.13 Главная процедура

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:28

Фиг. 5.14 Подпрограмма вывода Page 1-1

PAGE ,132

TITLE Фиг. 5.14 Подпрограмма вывода

0000 CODE SEGMENT PUBLIC

ASSUME CS:CODE,DS:CODE ; Это должно быть так при вызове

PUBLIC OUTPUT_CHARACTER, OUTPUT_ROUTINE

0000 ?? OUTPUT_CHARACTER DB ?

0001 OUTPUT_ROUTINE PROC NEAR

0001 A0 0000 R MOV AL, OUTPUT_CHARACTER ; Выборка выводимого символа

0004 B4 0E MOV AH, 14 ; Функция вывода в BIOS

0006 BB 0000 MOV BX, 0 ; Установка номера страницы

0009 BA 0000 MOV DX, 0

000C CD 10 INT 10H ; Вызов подпрограммы вывода на экран

000E C3 RET ; Возврат в вызывающую программу

000F OUTPUT_ROUTINE ENDP

000F CODE ENDS

END

Фиг. 5.14 Процедура вывода

Рассмотрим эту же задачу с другой стороны. Каким образом

редактор связей узнает о местоположении внешних имен? На Фиг. 5.14

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

относящаяся к Фиг. 5.13. Переменные и программные метки, на которые

имеются ссылки в программе, на Фиг. 5.13, объявлены в подпрограмме

с помощью оператора PUBLIC. Это означает, что их имена доступны для

другого программного модуля. Ни на какие другие переменные или

программные метки в этой программе, не указанные в операторе

PUBLIC, ссылки в других программах невозможны. Хотя это может

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

PUBLIC, то возникла бы другая трудность. Это означало бы, что

каждое имя в любом из модулей, которые вы могли бы связать между

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

использовать одно и то же символическое имя дважды в разных

модулях. Это может быть серьезным препятствием для повторного

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

возможно и через несколько лет, а помнить все символические имена и

следить за тем, чтобы ни одно из них не повторялось дважды довольно

сложно. Заметьте, что в операторе PUBLIC не требуется указывать

атрибуты имен: об этом заботятся обычные операторы языка

ассемблера.

Программа LINK устанавливает соответствие между всеми внешними

именами и соответствующими операторами PUBLIC, которые их

объявляют. После этого редактор связей записывает правильные

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

Обрабатываются те поля в командах, рядом с которыми в ассемблерном

листинге стоял символ "E".

Кроме того, ассемблер осуществляет объединение любых сегментов

с одними тем же именем. В случае программ на Фиг. 5.13 и П5.14

основная программа и подпрограмма принадлежат одному и тому же

сегменту с именем CODE. Так как в операторе EXTRN основной

программы для программы OUTPUT_ROUTINE указан атрибут NEAR, то

желательно, чтобы эта программа была в том же сегменте. Атрибут

PUBLIC в операторе SEGMENT указывает редактору связей объединить

оба программных модуля в один выполняемый сегмент.

В программе на Фиг. 5.13 есть еще один сегмент, который следует

рассмотреть. Данная программа выполняется как программа типа .EXE.

При передаче управления программе типа .EXE система DOS организует

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

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

.EXE. Подготовить все для стека обязан программист. Если он этого

не сделает, то редактор связей выдает соответствующее сообщение. В

обычной ситуации это не может служить препятствием для выполнения

программы. Однако в таком случае параметры стека для программы

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

оказаться неподходящими. За подготовку стека отвечает сегмент

STACK, входящий в программу на Фиг.5.13. Его имя STACK и задание

соответствующего атрибута равным STACK говорят о том, что это

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

того, проверяет, правильно ли установлен указатель стека в момент,

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


Untitled

Операция редактирования связей

Рассмотрим теперь операции, с помощью которых описанные выше

программные модули были объединены в один исполняемый модуль.

Ассемблирование программ выполняется с помощью команд, описанных в

предыдущем разделе:

B:>A:MAS FIG5=13,,,;

B:>A:MASM FIG5=14,,,;

При этом получается два объектных модуля FIG5=13.OBJ и

FIG5=14.OBJ. Для объединения этих модулей вызывается программа

LINK. На Фиг. 5.15 приведены операции, с которых начинается работа

программы LINK.

В данном примере предполагается, что дискета с DOS установлена

в дисководе A:, рабочая дискета - в дисководе B:, и дисковод B:

выбирается по умолчанию. После запуска программа LINK запрашивает

пользователя, для каких объектных файлов следует выполнить

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

.OBJ. Если связываемых модулей больше одного, то их имена вводятся

через разделитель "+". В данном примере выполняется редактирование

связей для модулей FIG5=13 и FIG5=14.

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

A>LINK

IBM Personal Computer Linker

Version 1.10 (c)Copyright IBM Corp 1982

Object Modules [.OBJ]: B:FIG5_13+B:FIG5_14

RunFile [A:FIG5_13.EXE]: B:

List File [NUL.MAP]: B:

Libraries {.LIB]:

A>

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

Фиг. 5.15 Выполнение редактирования связей

Модули связываются в том же порядке, в каком их имена

передаются программе LINK. В данном случае программа FIG5=13

предшествует программе FIG5=14. Перечисление модулей в обратном

порядке привело бы к такому же обратному порядку их расположения в

итоговом модуле. Как правило, порядок формирования программы

безразличен. Единственное исключение делается для входной точки

программы.

Следующий запрос редактора связей относится к имени

исполняемого, или рабочего, файла. По умолчанию ему присваивается

имя первого из объектных модулей с расширением .EXE. Вводя другое

имя, можно изменить имя файла, но не его расширение .EXE.

Далее запрашивается имя файла для хранения карты связей.

Допускается любое имя, однако в режиме по умолчанию формирование

карты не производится. В нашем примере ввод символа B: является

указанием редактору связей записать карту связей на дисководе B:.

Редактор связей выбрал для этого файла имя FIG5=13.MAP. Полученный

в результате операции связывания файл FIG5=13.MAP приводится на

Фиг. 5.16 и будет рассмотрен в следующем параграфе.

Последний запрос редактора связей касается библиотек программ,

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

случае некоторых языков высокого уровня на этом шаге может

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

Для программ на языке ассемблера такой необходимости нет.


Untitled

Карта связей

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

связей. Так как рассматриваемый пример прост, то и карта не очень

содержательна. Каждому сегменту в исполняемом файле соответствует

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

начального и конечного адресов каждого сегмента в том виде, в каком

они будут загружены в память. Заметьте, что размер сегмента CODE

равен 3CH. Это является суммарным размером сегментов CODE в двух

программных модулях. Другой сегмент в нашем примере - это сегмент

STACK размером 80H байт.

По поводу адресов, приведенных в карте связи, нужно сделать

следующие замечания. Во-первых, все они являются 20- битовыми

адресами и отсчитываются от ячейки 0. Так как загрузка программы

будет осуществляться DOS, то занрузчик изменит значения этих

адресов. Однако относительно друг друга их значения останутся

такими же. В отношении сегментов следует еще отметить, что в памяти

они не располагаются последовательно. Хотя длина сегмента CODE

равняется только 3CH, сегмент STACK начинается с адреса 40H.

Сегменты должны располагаться на границах параграфов для того,

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

привязке к границам параграфов, сегментные регистры указывают точно

на первую ячейку сегмента. Следовательно, в нашем случае редактор

связей расположил сегмент STACK на первой границе параграфа сразу

же после конечного адреса сегмента CODE. С учетом того, что конец

сегмента CODE равен 3BH, следующий адрес ячейки, который делится на

16, будет равен 40H.

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

Start Stop Length Name Class

00000H 0007FH 00080H STACK

00080H 000CEH 0004FH CODE

Program entry point at 0008:0000

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

Фиг. 5_16 Карта связей для Фиг. 5.13 и 5.14

Возможно вы заметили, что общая длина сегментов CODE в

действительности не равна 3CH. В отсутствие других спецификаций

редактор связей поместил начало каждой части сегмента CODE на

границах параграфов. Длина первого модуля FIG5=13 равняется 2BH.

Следующий модуль, FIG5=14, редактор связей поместил на следующей

границе параграфа, в данном случае по адресу 30H. Так как длина

второго модуля равна 0CH, общий размер сегмента CODE будет равен

03CH. Выравнивание по границам параграфов выполняется по умолчанию

совместно с ассемблированием и редактированием связей. Оператор

языка ассемблера SEGMENT может, если нужно, изменить это

выравнивание на побайтовое (BYTE), либо пословно (WORD). При

выравнивании типа BYTE программные программные упаковываются в один

сегмент. В смысле экономии памяти это наиболее эффективный способ

объединения модулей. Однако выравнивание по границе параграфа

гарантирует, что во время выполнения программы не возникает никаких

трудностей с адресацией сегментов. Если в программе производятся

вычисления адресов и выравнивание производилось не по границам

параграфов, то возможно возникновение ошибок.

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

файла. Этот адрес, как и другие, вычисляется по отношению к началу

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

несколько способов указания стартового адреса для программы типа

.EXE. При одном из способов программа выполняется, начиная с

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

тем, чтобы в первом байте первого сегмента рабочего файла

содержалась команда, с которой вы хотите начать выполнение. Более

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

END головной программы. На Фиг. 5.13 последним в программе является

оператор

END START

где START - это метка первой выполняемой команды. Так как

модули связаны друг с другом в определенном порядке, то эта же

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

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

образом. В этом можно убедиться на практике.

При любой операции связывания должен быть только один оператор

END с указанием стартового адреса. Обратите внимание, что в

подпрограмме FIG5=14 в операторе END входная точка не указана. Если

имеется более одной входной точки, то редактор связей обычно

выбирает ту, которая указана последней. Лучше непосредственно

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

редактор связей выберет не ту входную точку. Имейте в виду, что

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

случае файла типа .EXE. Программа типа .COM всегда выполняется,

начиная со смещения 100H сегмента команд.


Untitled

Отладчик DEBUG

Программа DEBUG (отладчик) дает средство обнаружения ошибок при

работе с программой, транслированной в машинный язык. Программа

DEBUG обеспечивает возможность пошагово выполнять программу и

следить за тем, что при этом происходит. Программа DEBUG - это еще

одно программное средство, поставляемое как часть DOS. Вы

загружаете ее так же, как и любую другую программу, и работаете в

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

ожидает каких-либо действий со стороны пользователя, то свой запрос

она обозначает символом "-".

Вместо перечисления всех команд, которые входят в программу

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

составленной программы, приведенной на Фиг. 5.13 и П5.14. На

Фиг. 5.17 приведен соответствующий листинг.

В данном примере сначала вызывается программа DEBUG и

указывается та программа, которую предполагается отлаживать - в

нашем случае программа FIG5=13.EXE. После того, как программа DEBUG

загружена, она производит загрузку отлаживаемой программы.

Управление теперь принадлежит отладчику, и он с помощью символа "-"

показывает, что ожидает ввода. До тех пор, пока вы не введете для

него указаний, с программой ничего происходить не будет.

Команда R выводит содержимое всех регистров в момент,

соответствующий загрузке программы FIG5=13 и передаче ей управления.

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

может, значений флагов. Флаг NV указывает на отсутствие

переполнения, флаг UP - флаг направления и т.д. При выводе

содержимого регистров в последней строке приводится следующая

выполняемая команда. В ячейке 04C5:0000 записана команда PUSH DS.

B>A:DEBUG FIG5_13.EXE

-R

                  AX=0000  BX=0000
CX=0120  DX=0000  SP=FFF0
BP=0000  SI=0000  DI=0000

DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=0000 NV UP DI PL NZ NA PO NC

2C26:0000 1E PUSH DS

-U

2C26:0000 1E PUSH DS

2C26:0001 B80000 MOV AX,0000

2C26:0004 50 PUSH AX

2C26:0005 FC CLD

2C26:0006 8CC8 MOV AX,CS

2C26:0008 8ED8 MOV DS,AX

2C26:000A 8D361D00 LEA SI,[001D]

2C26:000E AC LODSB

2C26:000F A24000 MOV [0040],AL

2C26:0012 E82C00 CALL 0041

2C26:0015 803E40000A CMP [0040],0A

2C26:001A 75F2 JNZ 000E

2C26:001C CB RET Far

2C26:001D 9D POPF

2C26:001E E2A0 LOOP FFC0

2C26:0020 20AFE0AE AND [BX+AEE0],CH

2C26:0024 A3E0A0 MOV [A0E0],AX

-D2C26:0

2C0E:0000 CD 20 00 A0 00 9A EE FE 1D F0 ED 04 04 1C 3C 01 . ............<.

2C0E:0010 22 1B EB 04 22 1B 04 1C 01 01 01 00 02 06 FF FF "..."...........

2C0E:0020 FF FF FF FF FF FF FF FF FF FF FF FF 08 2C D0 FF .............,..

2C0E:0030 04 1C 14 00 18 00 0E 2C FF FF FF FF 00 00 00 00 .......,........

2C0E:0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

2C0E:0050 CD 21 CB 00 00 00 00 00 00 00 00 00 00 20 20 20 .!...........

2C0E:0060 20 20 20 20 20 20 20 20 00 00 00 00 00 20 20 20 .....

2C0E:0070 20 20 20 20 20 20 20 20 00 00 00 00 00 00 00 00 ........

-RAX

AX 0000

:1234

-E 2C0E:21

2C0E:0021 69. 73. 20. 61.20 20.

-G 3C

                  AX=0E54  BX=0000
CX=003F  DX=0000  SP=FFEA
BP=0000  SI=001D  DI=0000

DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=003C NV UP DI PL NZ NA PO NC

2C26:003C CD10 INT 10

-T

                  AX=0E54  BX=0000
CX=003F  DX=0000  SP=FFEA
BP=0000  SI=001D  DI=0000

DS=2C26 ES=2C26 SS=2C26 CS=F000 IP=0165 NV UP DI PL NZ NA PO NC

F000:F065 FB STI

-T 

Фиг. 5.17 Листинг отладчика для Фиг 5.13 и 4.14 (начало)

                  AX=0E54  BX=0000
CX=003F  DX=0000  SP=FFEA
BP=0000  SI=001D  DI=0000

DS=2C26 ES=2C26 SS=2C26 CS=F000 IP=0166 NV UP DI PL NZ NA PE NC

F000:F066 FC CLD

-G 2C26:3E

T

                  AX=0E54  BX=0000
CX=003F  DX=0000  SP=FFEA
BP=0000  SI=001D  DI=0000

DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=013E NV UP DI PL NZ NA PO NC

2C26:003E RET

-G

Эта программа - тест

Program terminated normally

-R

                  AX=0754  BX=0000
CX=003F  DX=0000  SP=FFEA
BP=0000  SI=001D  DI=0000

DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=003E NV UP DI PL NZ NA PO NC

2C26:003E C3 RET

-Q

B>

Фиг. 5.17 Листинг отладчика для Фиг 5.13 и 4.14 (продолжение)

Здесь следует немного задержаться и проанализировать

информацию, которая записывается в регистры. Содержимое регистров

соответствует моменту, когда программа FIG5=13 получает управление

от командного процессора. Обратите внимание, что пара регистров

CS:IP указывает на первую команду, определяемую оператором END

программы. Регистры DS и ES указывают на префикс программного

сегмента. И наконец, пара регистров SS:SP указывает на сегмент

STACK. Описанное состояние регистров будет сравниваться позднее с

аналогичным состоянием регистров для файла типа .COM.

Чтобы просмотреть большее число команд, надо ввести символ "U"

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

следующих команд. Это удобно при обладке программы, для которой нет

листинга. Дизассемблирование программы позволяет просмотреть ее

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

программу внесены небольшие изменения. Так как ваш листинг больше

не соответствует той программе, которая находится в памяти, то ее

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

каждой команды.

Однако у дизассемблирования с помощью программы DEBUG имеется

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

комментарии (может быть очень важные для понимания программы), и

ячейки памяти идентифицируются только по адресу, а не по имени

переменной. Например, хранящаяся в ячейке 04C5:000E команда имеет,

как показано на Фиг.5.13, следующий вид:

MOV OUTPUT_CHARACTER,AL

а в дизассемблированном виде

MOV [0030],AL

Это одна и та же команда. Для программиста, выполняющего

отладку, имя переменной OUTPUT_CHARACTER говорит больше, чем адрес

ячейки [0030]. Однако программе DEBUG не известны имена переменных,

и она вынуждена оперировать фактическими адресами.

Кроме того, программа, DEBUG не обеспечивает той же самой

ассемблерной мнемоники, которую воспринимает ассемблер. Это значит,

что некоторые команды будут выглядеть по-разному. Команда из ячейки

04C5:0014 будет при дмзассемблировании иметь вид

CMP B,[3000],0A

но та же саммая команда на Фиг.5.13 представлена в виде:

CMP OUTPUT_CHARACTER,10

Программа дизассемблирования как на входе, так и на выходе

работает только с шестнадцатеричными значениями. Этим объясняется

появление значения 0A. Мы уже выяснили, почему получается значение

[0030], вместо имени OUTPUT_CHARACTER. Что же такое символ "B,"?

Ассемблер оперирует переменными вполне определенного типа. Это

значит, что во время ассемблирования тип переменных может иметь

значение: байт, слово или какой-нибудь другой. Таким образом, когда

программист вводит команду, содержащую ссылку на область памяти, то

ассемблеру известен размер этой области. На программа DEBUG не

имеет представления о длине переменной, записанной по адресу

[0030]. Однако программе дизассемблирования точно известно, что

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

непосредственно в команде, по адресу [0030]. Таким образом, символ

"B," указывает на то, что непосредственная операция состоит в

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

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

CMP BYTE PTR [0030],10

Вы можете рассматривать символ "B," как сокращение BYTE PTR.

Аналогично символ W используется для WORD PTR,L - для длинного

(far) возврата и т.д.

Вместе с командой в дизассемблированном виде вводится и ее

объектный код. Как вы можете заметить, по адресу 04C5:001C записаны

какие-то команды, которых нет на Фиг. 5.13. Эта область данных,

содержащая строку "Это тест". Однако команде, осуществляющей

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

и начинаются данные. Таким образом она все интерпретирует как

команды. (Кстати, именно эта последовательность команд выполнялась

бы, если в вашей программе был сделан переход в рассматриваемую

область данных.)

Команда вывода на экран D позволяет просмотреть на дисплее

области данных. Отображение на экране состоит из двух частей.

Вместе с листингом содержимого ячеек памяти в шестнадцатеричном

представлении приводятся символы в коде ASCII, которые

соответствуют этим значениям. Если отображение команд подобным

образом не имеет смысла, то область данных представляется очень

ясно. Когда вам будет нечем особенно заняться, вы можете

попробовать написать такую программу, команды которой в коде ASCII

соответствуют инициалам вашего имени. С помощью отладчика можно

изменять содержимое регистров и ячеек памяти. Если ввести символ R

(регистр) и затем тип регистра, то на дисплей будет выведено

содержимое этого регистра с возможностью его коррекции. Если нажать

клавишу "возврат", то содержимое регистра останется прежним. Оно

будет изменено, если ввести новое значение.

Можно также модифицировать содержимое ячеек памяти. Ввод

символа E (редактирование) позволяет это сделать. При этом

программа DEBUG выводит на дисплей значения отдельных ячеек памяти,

после которых следует символ . Вы можете изменить содержимое

ячейки, вводя новое значение, либо нажать клавишу пробела, чтобы

перейти к следующей ячейке, или - клавишу "возврат", чтобы

вернуться к режиму запроса следующей команды отладчика. В

рассматриваемом примере значения в первых трех ячейках остаются

прежними. Содержимое ячейки 04C5:0024 изменено со значения 61H на

значенее 20H. Так как эта ячейка входит в область данных, то

выводимое сообщение будет отличаться от тог, которое было в

транслированной программе.

В любой команде, обращающейся к ячейкам памяти, предполагается,

что адрес является частью команды. Команда E, как и команда

отображения, выводит на дисплей содержимое ячейки по указанному в

ней адресу. Точно так же можно было использовать адрес в команде

дизассемблирования. Можно ввести адрес в виде сегмента и смещения,

или только смещения. Если вы указали только смещение, то

соответствующий сегментный регистр будет выбран программой DEBUG. В

случае команды U используется регистр CS, а для команд D и E по

умолчанию сегмент будет определяться регистром DS.

Теперь попытаемся выполнить эту программу. Ее можно просто

запустить и посмотреть, что будет происходить. На для этого не

нужна программа DEBUG. Программа DEBUG позволяет задать точки

останова, называемые "точками прерывания" программы. Благодаря

введению в программу таких точек можно возвращать управление

программе DEBUG. Это дает еще одну возможность проверки состояния

памяти и регистров для того, чтобы контролировать ход выполнения

программы.

Команда G, (выполнять) передает управление от программы DEBUG

пользовательской программе. Выполнение команд начинается с ячейки,

задаваемой парой регистров CS:IP (так же, как в реальном

микропроцессоре). Тестируемая программа продолжает выполняться до

тех пор, пока она не пройдет точку прерывания. В нашем примере мы

задали точку прерывания по адресу 3CH. Так как мы указали только

смещение, то для определения сегмента программа DEBUG использует

содержимое регистра CS. Из листинга, приведенного на Фиг. 5.14,

видно, что смещение 3CH соответствует команде INT 10H. В

рассматриваемом примере программы было выбрано именно это место,

потому что это - та точка, где управление передается подпрограмме

BIOS, вызываемой из ПЗУ. Проверка программы в этой точке

гарантирует, что мя установили регистры в нужное состояние перед

выполнением подпрограммы BIOS.

Как только встречается точка прерывания, управление

возвращается программе DEBUG. При этом, так же как и в случае

команды R, на дисплей выводятся содержимое регистров и следующая

выполняемая команда. Так как управление опять передано программе

DEBUG, вы можете использовать любую из команд отладки.

Имеются ограничения на использование точек прерывания.

Фактически точка прерывания реализуется кодом операции 0CCH.

Соответствующая этому коду команда вызывает прерывание INT 3.

Данное математическое прерывание возвращает управление программе

DEBUG. Если какая-то команда возвращает управление отладчику, то

точка прерывания должна находиться в начале этой команды. Если же

точка прерывания выбрана где-то в другом месте, то управление не

будет возвращено отладчику и будет выполняться не та команда

программы, которая предполагалась. Например, если бы было задано

"=G 3D", то по адресу 3CH была бы команда INT 0CCH, и дальнейшее

выполнение программы предсказать трудно.

Если точка прерывания выбрана осмотрительно, то никаких

осложнений не будет. Командв "G" позволяет задать до десяти точек

прерывания. После прохождения любой из них происходит

восстановление исходных значений точек прерывания. Выполнение

команды отладки "G" без указания точек прерывания никогда не выйдет

ни на какую из ранее заданных точек прерывания, потому что все они

были удалены. Если вы запустили программу, и произошел ее останов и

она зациклилась, то возможно, что вернуть управление, которое

передано программе, удастся только с помощью клавиши системного

сброса Ctrl=Alt=Del, т.е. вам придется начать все с начала.

Запуская незнакомую программу, следует быть осторожным.

Если вы захотите ввести пермаментную точку прерывания,

воспользуйтесь командой E для изменения значения первого байта

команды на значение 0CCH. Эта точка прерывания будет там постоянно

или, по крайней мере, до тех пор, пока вы ее не измените. Возможно

вы захотите использовать это прерывание в точке входа в

подпрограмму обработки ошибки и проанализируете возникающие ошибки

более тщательно, чем при передаче их обработки программе.

Существует еще одно обстоятельство, связанное с точками

прерывания, о котором следует помнить. Если вы попытаетесь задать

точку прерывания в области, относящейся к ПЗУ, то ничего не

получится. Так как вы не можете менять содержимое ПЗУ, то команда

0CCH никогда туда не запишется.

Рассмотрим следующую команду отладчика - команда трассировки T.

Данная команда инициирует выполнение одной команды отлаженной

программы. В нашем примере команда T выполнена несколько раз. Как

вы можете убедиться, выполняется несколько первых команд BIOS,

вызванной по прерыванию INT 10H. Подпрограмма BIOS, естественно,

находится в ПЗУ. Команда трассировки позволяет "приостановить"

программу при ее выполнении в ПЗУ.

Перед передачей управления пользовательской программе команда

трассировки выставляет в регистре флагов соответствующий бит

трассировки. Этот бит инициирует прерывание INT 1 после выполнения

каждой команды. Вектор прерывания INT 1 возвращает управление

программе DEBUG. Выполнение прерывания INT 1 автоматически

сбрасывает бит трассировки в исходное состояние. Это значит, что

программа DEBUG не прерывается после выполнения каждой ее команды.

Команда трассировки служит прекрасным средством "пробиться" через

трудный участок программы. При этом программа DEBUG выводит на

экран каждую команду вместе с содержимым регистров как раз в

момент, предшествующий выполнению этой команды. Так как в данном

режиме используются не точки прерывания, а собственно прерывания,

то можно выполнять трассировку даже программ ПЗУ.

Вернемся к нашему примеру. Команда =G 4C5:3E обеспечивает

полное выполнение подпрограммы BIOS. Обратите внимание, что

программа вывела на дисплей символ "Э". Вызванная по прерыванию 10H

подпрограмма BIOS выводит символы на дисплей. В данном случае это

первый символ выводимого сообщения. Так как теперь можно быть

уверенными, что наша программа выполняется правильно, то, введя

символ "G", мы обеспечим выполнение программы до конца без точек

прерывания.

В данном примере рассматривался файл типа .EXE, и потому для

возврата управления системе DOS мы не могли использовать прерывание

INT 20H. Вместо этого программа записала в стек состояние регистра

DS и значение 0. Управление передается обратно системе DOS в конце

основной программы с помощью команды возврата типа FAR. Программа

DEBUG распознает это и фиксирует состояние машины в конце

тестируемой программы. Если бы это был файл типа .EXE, то

прерывание INT 20H аналогичным образом вернуло бы управление

программе DEBUG. Теперь, уделив достаточно времени этому примеру,

мы можем выйти из программы DEBUG и вернуться в систему DOS с

помощью команды завершения Q.


Преодразование файла типа .exe в файл типа .com.

На дискете с DOS имеется сервисная программа под именем EXE2BIN.

Эта программа преобразует файл типа .EXE в файл типа .COM. Однако

программа EXE2BIN работает не со всеми файлами. Далее излагается

метод использования программы DEBUG, с помощью которого любая

программа преобразуется в файл типа .COM.

На Фиг. 5.18 приведена программа, которую мы будем

преобразовывать. Эта программа выполняет точно такие же функции,

что и программа предыдущего примера, а именно - выводит на дисплей

фразу : "Это тест". Однако в данной программе эта строка выводится

на дисплей по прерыванию INT 21H с помощью функции 9 DOS.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:33

Фиг. 5.18 Пример преобразования файла типа .EXE в тип .COM Page 1-1

PAGE ,132

TITLE Фиг. 5.18 Пример преобразования файла типа .EXE в тип .COM

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0100 ORG 100H

0100 8D 16 010A R LEA DX, MESSAGE

0104 B4 09 MOV AH, 9H ; Функция вывода строки ДОС

0106 CD 21 INT 21H ; Вывод строки на экран

0108 CD 20 INT 20H ; Возврат в ДОС

010A 9D E2 A0 20 AF E0 AE MESSAGE DB 'Эта программа - тест', 10, 13, '$'

A3 E0 A0 AC AC A0 20

2D 20 E2 A5 E1 E2 0A

0D 24

0121 CODE ENDS

END

Фиг. 5.18 Пример перевода .EXE в .COM

Обратите внимание, что данная программа записана как файл типа

.COM. На это указывает оператор ORG 100H, предшествующий первой

команде. Остальная часть программы должна быть перемещаемым

сегментом команд, и об этом не нужно забывать при написании

программы, которая будет преобразовываться в файл типа .COM.

Ассемблирование и редактирование связей этой программы

осуществляется обычным способом. Однако до запуска программы DEBUG

нужно изменить в имени файла тип .EXE на тип .COM. Это необходимо

сделать, так как программа DEBUG не позволяет записывать файл типа

.EXE. На Фиг. 5.19 показана последовательность шагов, которую нужно

выполнить. В данном примере вводится команда программы DEBUG без

имени файла. В качестве имени можно было бы в данной строке указать

FIG5=18.COM, зато его отсутствие позволило продемонстрировать

некоторые другие функции программы DEBUG. Команда N отладчика

позволяет задать имя файла. После этого команда L выполняет

загрузку файла в память. Если указать имя файла в команде DEBUG, то

последняя выполнит все то же самое, что и команды N и L.

Теперь, когда файл загружен, вы обнаружите, что в

дествительности программа загрузилась, начиная со смещения 400H.

Команда M сдвигает содержимое области памяти с 400H на 100H. Длина

области, равная 1000H, была выбрана для гарантии того, что там

поместится вся программа. Теперь программа соответствует формату

файла типа .COM и может быть опять записана на дискету. Однако

прежде, чем это сделать, вы изменяете содержимое регистра CX, чтобы

он указал фактическую длину программы. При любом считывании и

записи файлов на дискету, осуществляемых программой DEBUG, длина

файла хранится в регистре CX. Так как файл типа .COM теперь намного

короче, чем был файл типа .EXE, то мы можем сэкономить дисковую

B>A:ASM FIG5_18,,,;

The IBM Personal Computer Assembler

Version 1.00 (c)Copyright IBM Corp 1981

Warnings Severe

Errors Errors

0 0

B>A:LINK FIG5_18,,,;

Ibm Personal Computer Linker

Version 1.10 (C)Copyright IBM Corp 1982

Warning: No STACK segment

There was 1 error detected.

B>RENAME FIGS5_18.EXE FIGS5_18.COM

B>A:DEBUG

-NFIGS_18.COM

-L

-M 400 1000 100

-U100 10F

06D7:0100 BA091 MOV DX,0109

06D7:0103 B409 MOV AH,09

06D7:0105 CD21 INT 21

06D7:0107 CD20 INT 20

06D7:0109 54 PUSH SP

06D7:010A 68 DB 68

06D7:010B 69 DB 69

06D7:010C 7320 JNC 012E

06D7:010E 69 DB 69

06D7:010F 7320 JNC 0131

-D100

06D7:0100 BA 09 01 B4 09 CD 21 CD-20 54 68 69 73 20 69 73 :..4.M!M' This is

06D7:0110 20 61 20 74 65 73 74 DA-0D 24 00 00 00 00 00 00 a test..$.......

06D7:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

06D7:0130 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

06D7:0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

06D7:0150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

06D7:0160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

06D7:0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

-RCX

CX 0380

:120

-W

Writing 0120 bytes

-Q 

Фиг. 5.19 Пример преобразования из.EXE в .COM (начало)

>BDEBUG FIG5_18.COM

-R

                  AX=0000  BX=0000
CX=0120  DX=0000  SP=FFF0
BP=0000  SI=0000  DI=0000

DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI NZ NA PO NC

-Q

B>FIG5_18

Эта программа - тест

Фиг. 5.19 Пример преобразования из.EXE в .COM (продолжение)

память, задав в регистре CX правильное значение для программы.

Команда W записывает файл обратно на дискету. Кстати, это еще одно

преимущество использования файлов типа .COM. Программа DEBUG не

будет записывать файл типа .EXE на дискету, потому что в памяти

отсутствует информация головной метки. В то же время файл типа .COM

может быть записан на дискету программой DEBUG. Если вы отлаживаете

программу и вам нужно изменить в ней один или два байта без ее

повторного ассемблирования (это называется "латанием" программы),

то это можно сделать. Просто внесите в программу изменения,

убедитесь, что регистр CX установлен правильно, и с помощью команды

W запишите программу на дискету.

Команда Описание

-------------------------------------------------

D Вывод содержимого памяти

E Изменить содержимое памяти

F Заполнить блок памяти

G Выполнять программу

H Шестнадцатеричное сложение и вычитание

I Считать и показать значение из порта

L Загрузить с диска

M Переслать блок памяти

N Назначить имя файла

O Вывести значение в порт

Q Выход из отладчика

R Вывести значения регистров

S Поиск строки байт

T Выполнить одну команду

U Дизассемблировать блок кода

W Записать данные на диск

--------------------------------------------------

Фиг. 5.20 Команды DEBUG

В результате работы отладчика получился новый вариант программы

FIG5=18.COM. Обращая внимание на состояние регистров, мы видим, как

они устанавливаются в случае файла типа .COM. Сравните это с

показанным на Фиг.5.17 состоянием регистров для файла типа .EXE.

Разница между ними поможет уяснить некоторые различия между файлами

типа .COM и типа .EXE.

Имеются и другие команды, используемые при работе с отладчиком

DEBUG. На Фиг. 5.20 приведен полный набор команд для работы с

программой DEBUG. В руководстве по DOS подробно описаны эти

команды.


Untitled

Глава 6

Свойства Макроассемблера

В этой части будут описаны некоторые свойства макроассемблера,

разработанного в фирме IBM. Хотя мы уже рассмотрели все команды

процессора 8088, в ассемблере имеются и другие команды. Мы уже

обсудили некоторые из этих псевдокоманд, например, операторы

определения данных DB и DW. В этой главе будут введены более мощные

средства языка ассемблера. Их объединяет то, что их использование

делает написание программ на языке ассемблера более простым и

легким.

В этой главе мы рассмотрим две больших темы. Первая - механизм

макрокоманд Макроассемблера. Макрокоманды - мощный инструмент

генерирования машинных команд. Пониманние механизма макрокоманд

будет необходимо в главе 7, где обсуждается математический

сопроцессор 8087. Вторая тема касается структур данных. Мы уже

рассматривали определение данных и их типов. Здесь мы определим

структуры данных, состоящие из наборов байт и слов. В этой главе

рассматриваются сегменты, структуры и записи для определения

данных.


Untitled

Макроопределения

Макрокоманда - это программный инструмент, который позволяет вам

создавать собственные операции ассемблера. На самом деле макро-

определения относятся к механизму препроцессора. Макропроцессор

позволяет определять новые коды операций для процессора. В этом

определении вы, в частности, сообщаете ассемблеру текст выполняемой

операции. Когда ассемблер встречает этот вновь определенный код

операции, он обращается к сохраненному определению макрокоманды и

помещает в транслируемый участок программы текст из этого

определения. Например, в программе могут быть определены в качестве

макрокоманд часто используемые последовательности команд. Каждый

раз, когда эти команды должны быть вставлены в текст программы,

программист может вместо этого воспользоваться макрокомандой.

В использовании макрокоманды можно выделить два шага. На первом

шаге макрокоманда определяется в программе. Программист присваивает

ей имя и определение. Определение состоит из из операций ассемблера

и команд, которые будут генерироваться каждый раз при появлении

имени макрокоманды. Второй шаг - применение макрокоманды. Это

происходит когда ассемблер встречает ее имя в качестве кода

операции. Ассемблер заменяет это имя указанными в определении

командами.

Возьмем в качестве примера команды сопроцессора 8087, который

мы обсудим в глве 7. В написании программ с использованием команд

числового процессора 8087 возникают некоторые трудности. В

макроассемблере отсутствуют коды операций 8087. Для использования

8087 вы должны сформировать его команды с помощью либо оператора

определения данных, либо кодов операций WAIT и ESC. Лучше всего это

делать через определение макрокоманды, что позволит вам писать

команды 8087. После этого программа может пользоваться командами

8087, хотя они и не входят в язык ассемблера.

В программировании на языке ассемблера макрокоманды исполь-

зуются наиболее часто. Хотя видимых причин не применять макропро-

цессор в языках высокого уровня нет, там макрокоманды встречаются

довольно редко. Макроассемблер для IBM PC поддерживает

макрокоманды. Как мы уже отмечали, существует две версии

ассемблера. Малый ассемблер, ASM, не поддерживает это средство.

Полный ассемблер, MASM, допускает все макрооперации, которые обсуж-

даются в этой главе. Для использования MASM ваш персональный

компьютер должен иметь как миимум 96K оперативной памяти.

Простейшая макрокоманда, которую можно использовать как код

операции 8087, - FENI. Макроассеблер 8088 не распознает ключевого

слова FENI, которое в действительности является командой для 8087.

Фиг. 6.1 показывает два шага макро-процесса: определение

макрокоманды FENI и ее последующий вызов в программе. Фиг.6.1

состоит из двух частей: часть (a) - это исходный файл для

программы, а часть (b) содержит листинг ассемблера для нее. Два

варианта на Фиг. 6.1 разделены, чтобы показать, какой из них

написан программистом, а какой сгенерирован макропроцессором.

Программа определяет макрокоманду с помощью ключевого слова

MACRO. На Фиг. 6.1 макроопределение выглядит так:

FENI MACRO

;---- Тело макрокоманды

ENDM

Оператор MACRO является кодом псевдооперации. Эта конкретная псев-

дооперация сообщает ассемблеру, что начинается определение макроко-

манды. В поле имени операции указано это имя, которое программа

приписывает определяемой макрокоманде, в нашем случае FENI. Команды

PAGE ,132

TITLE Фиг. 6.1 Макрокоманда

FENI MACRO

DB 0DBH, 0E0H

ENDM

CODE SEGMENT

ASSUME CS:CODE

FENI

CODE ENDS

END

Фиг. 6.1 (a) Исходный файл для программы

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:38

Фиг. 6.1 Макрокоманда Page 1-1

PAGE ,132

TITLE Фиг. 6.1 Макрокоманда

FENI MACRO

DB 0DBH, 0E0H

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE

FENI

0000 DB E0 1 DB 0DBH, 0E0H

0002 CODE ENDS

END

Фиг. 6.1 (b) Листинг ассмблера программы

Фиг.6.1 Макроопределение.(a) исходный файл; (b) листинг ассемблера.

(или действия ассемблера), которые будут заменять имя макрокоманды,

следуют за строкой заголовка. Наконец, ключевое слово ENDM

указывает ассемблеру на конец определения. Текст между операторами

MSCRO и ENDM называется телом макрокоманды. На Фиг. 6.1 телом

макрокоманды FENI является оператор определения байтов. Поскольку в

8088 нет команды, которая соответствовала бы команде FENI код

машинного языка для этой команды должен состять из операторов DB.

Важно заметить, что во время определения макрокоманды код

машинного языка еще не генерируется. Это можно утверждать, потому

что колонки адреса и данных в листинге ассемблера пусты. Когда

ассемблер впервые встречае макроопределение, он сохраняет его для

дальнейшего использования. Затем программа на Фиг. 6.1 привлекает

макрокоманду FENI. Программист использует имя макрокоманды FENI как

если бы это был код оперции ассемблера типа CLD или DAA, а

ассемблер обращается к сохраненному определению макрокоманды FENI.

Ассемблер берет текст из тела макроопределения и помещает его в той

же позиции транслируемой программы. Знак "+", появляющийся слева от

оператора DB в распечатке ассемблера, указывает на то, что эта

строка вставлена макропроцессором. Если сравнить исходный текст с

ассемблируемым, вы увидите в исходном тексте только команду FENI, в

то время как на листинге ассемблера за командой FENI следует тело

макрокоманды. В данном случае оно представлено одним оператором DB.

Этот простой пример демонстрирует большие возможности

макропроцессора. Возникла необходимость в коде операции FENI,

который не предусмотрен ассемблером. При отсутствии механизма

макрокоманд программист был бы вынужден вместо операции FENI каждый

раз записывать ее код:

DB 0DBH, 0E0H

Имея же в распоряжении такой механизм, можно определить

макрокоманду FENI и в дальнейшем в этой же программе использовать

как код операции только ее. Для использования подобных макрокоманд

есть две серьезные причины. Во-первых, они облегчают написание

программы. Во-вторых, при чтении текста программы оператор FENI,

выглядит гораздо более осмысленным, чем DB 0DBH,0E0H.

Макрокоманду можно сравнить с подпрограммой. Подпрограмма - это

участок программы, определяемый в единственном месте программы,

Программа может передать управление подпрограмме из любой своей

точки. Использование подпрограмм экономит время написания и объем

памяти, занимаемый программой. Вместо того, чтобы каждый раз

переписывать команды подпрограммы, когда необходимо ее выполнение,

вы вставляете ее вызов. Подпрограмма выполняет свою определенную

функцию, и управление возвращантся в точку вызова.

Макрокоманда точно также определяется в ассемблируемой

программе в единственном месте. После того, как макрокоманда

определена, ее можно привлечь ("вызвать") в любой точке

транслируемой программы. Использование макрокоманды экономит время

составления программ и место, занимаемое исходным файлом. Вместо

того, чтобы всякий раз, когда в них возникает необходимость,

переписывать входящие в макрокоманду команды, в программу

вставляется вызов макрокоманды. Ассемблер генерирует заданные в

определении команды и переходит к обработке следующего кода

операции.

Разница между макрокомандой и подпрограммой заключается в

моменте их использования. Макрокоманда является операцией текстовой

обработки. Она определяется и "выполняется" во время

ассемблирования. Выполнение макрокоманды состоит в замене имени

макрокоманды текстом, составляющим тело макрокоманды. Подпрограмма

же хотя и определяется при ассемблировании, но выполняется не

раньше, чем сама программа. Мы будем говорить, что макрокоманда

выполняется при ассемблировании, а подпрограмма - во время

выполнения программы.

Лучший способ отличить макрокоманду от подпрограммы - это

запомнить, когда они проявляются. На самом деле, макропроцессор не

является атрибутом языка программирования. Допустим, вы - адвокат,

и составляете завещания для множества разных людей. Так как

завещания часто похожи, то вы могли бы задать набор

"макрозавещаний", которые будут содержать совпадающие части

составляемых завещаний. Первая часть завещания, где перечислены

участвующие в нем стороны, будет уникальной. Оставшаяся часть будет

состоять из различных макрозавещаний, охватывающих стандартные

куски завещаний. Результатом работы "процессора завещаний" будет

текстовый документ. Макрозавещания раскрываются и образуют

стандартную часть завещания. Вам остается только заполнить

переменные фрагменты документа между макрозавещаниями.

Но если макрокоманды и подпрограммы во многих отношениях так

похожи, то зачем использовать вместо процедуры макрокоманду?

Действительно, во многих случаях применима любая из них.

Последовательность команд может быть определена и как макрокоманда,

и как подпрограмма. Когда вам потребуется эта последовательность,

вы можете вызвать соответственно макрокоманду или подпрограмму.

Какую именно - зависит от того, как вы определили данную

последовательность команд. Выбор последнего задается соображениями

времени выполнения программы и объема занимаемой ею памяти. В

большинстве случаев использование макрокоманд приводит к более

длинным программа, т.е. для реализации одной и той же функции

требуется больше байтов объектного кода. Однако такая программа

выполняется быстрее так как отсутствуют временные издержки,

связанные с вызовом подпрограммы и возвратом в программу каждый

раз, когда требуется данная последовательность команд. Для

минимизации размера программы слудет использовать подпрограммы.

Чтобы иметь программу с максимальным быстродействием, вы

пользуетесь макрокомандами.

В случае макрокоманды FENI на Фиг. 6.1, выбор в пользу

макрокоманды очевиден. Здесь соответствующий участок программы в

качестве макрокоманды не только выполняется быстрее нежели, в

качестве подпрограммы, но и занимает меньше памяти. Команда CALL

для близкой процедуры требует три байта. Макрокоманды FENI - только

два байта. В случае макрокоманд для процессора 8087 для реализации

тех же функций через процедуры потребовалось бы больше байтов

объектного кода. Кроме того использование макрокоманд сокращает

время выполнения программы.


Untitled

Аргументы макрокоманд

В обработке макрокоманд применяется одно из ценных свойств

процедур: в генерацию макрокоманды можно вносить изменения с

помощью параметров. Точно так же, как параметры подпрограммы могут

влиять на ее выполнение, параметры макрокоманды определяют

фактически генерируемые команды. И так же, как и процедуры,

макрокоманды без параметров встречаются относительно редко.

Рассмотрим еще один простой пример. Мы составили программу так,

что во многих ее местах содержимое определенной ячейки памяти

складывается с различными константами. Вместо того, чтобы много раз

писать команду

ADD MEMORY_BYTE,5

или

ADD MEMORY_BYTE,7

нам хотелось бы воспользоваться для нее соответствующей

макрокомандой. Однако во всех приведенных командах константы

разные. Поэтому мы сделаем константу параметром макрокоманды. На

Фиг. 6.2 показаны определение и применение макрокоманды ADDBYTE. В

этом примере в качестве параметра в определении макрокоманды

используется символическое имя CONSTANT. Любые символичесике имена,

появляющиеся в поле операнда оператора MACRO, интерпретируются как

параметры. В момент определения макрокоманды у имени CONSTANT нет

никакого значения: оно просто резервирует место в тексте

макрокоманды. Позднее, при вызове и обработке текста макрокоманды,

вместо символического имени в определении макрокоманды

подставляется определенное значение параметра.

Важно отметить, что параметр макрокоманды - это текстовый

параметр. Так как макропроцессор фактически является текстовым

процессорорм, то он не отличает цифры от букв и наоборот. Это

позволяет при вызове макрокоманды использовать вместо чисел

символические имена. Любой смысл приписывается символьной строке не

макропроцессором, а ассемблером. Макропроцессор подставляет

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:43

Фиг. 6.2 Макрокоманда с аргументом Page 1-1

PAGE ,132

TITLE Фиг. 6.2 Макрокоманда с аргументом

ADDBYTE MACRO CONSTANT

ADD MEMORY_BYTE, CONSTANT

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE

0000 ?? MEMORY_BYTE DB ?

= 0004 FOUR EQU 4 ; Симвользое изображение константы

ADDBYTE 2

0001 2E: 80 06 0000 R 02 1 ADD MEMORY_BYTE, 2

ADDBYTE 4

0007 2E: 80 06 0000 R 04 1 ADD MEMORY_BYTE, 4

ADDBYTE FOUR

000D 2E: 80 06 0000 R 04 1 ADD MEMORY_BYTE, FOUR

0013 CODE ENDS

END

Фиг. 6.2 Аргументы макрокоманды

текстовую строку из вызова макрокоманды на место символического

имени в определении макрокоманды. Таким образом программа может

использовать константное значение "FOUR" с тем же успехом, что и

константу "4".

Возможность использовать символические имена в качестве

параметров макрокоманд принципиально важна для следующего примера

макрокоманды. Этой макрокоманда, одной из команд сопроцессора 8087,

требуется параметр, который при обычном ее использовании почти

всегда бывает символическим именем. Макрокоманда FLDCW - это

команда сопроцессора 8087, которая задает ячейку памяти. Так как в

программах на языке ассемблера в большинстве случаев обращаются к

ячейкам памяти с помощью символических имен, то желательно

сохранить этот способ и для программирования сопроцессора 8087.

На Фиг.6.3 приводится макрокоманда FLDCW и несколько обрашений

к ней. Заметьте, что макрокоманда FLDCW использует в качестве

параметра символическое имя "SOURCE". Параметр SOURCE является

адресом, с которого сопроцессор 8087 загружает управляющее слово.

Для генерации требуемого машинного кода макрокоманда FLDCW

использует команду 8088 ESC. Однако для того, чтобы определить байт

mod=r/m команды, команде ESC требуется значение адреса. Как раз для

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:47

Фиг. 6.3 Макрокоманда для команды FLDCW Page 1-1

PAGE ,132

TITLE Фиг. 6.3 Макрокоманда для команды FLDCW

FLDCW MACRO SOURCE

DB 09BH

ESC 0DH, SOURCE

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE

0000 ???? MEMORY_LOCATION DW ?

FLDCW MEMORY_LOCATION

0002 9B 1 DB 09BH

0003 2E: D9 2E 0000 R 1 ESC 0DH, MEMORY_LOCATION

FLDCW ES:[DI]

0008 9B 1 DB 09BH

0009 26: D9 2D 1 ESC 0DH, ES:[DI]

FLDCW MEMORY_LOCATION[BX+SI]

000C 9B 1 DB 09BH

000D 2E: D9 A8 0000 R 1 ESC 0DH, MEMORY_LOCATION[BX+SI]

0012 CODE ENDS

END

Фиг. 6.3. Макрокомнда FLDCW

этого макрокоманда и использует параметр SOURCE. Такая организация

макрокоманды FLDCW позволяет программировать весьма естественным

способом. Точно так же как пишется

INC MEMORY_LOCATION

вы можно написать команду для сопроцессора 8087

FLDCW MEMORY_LOCATION

Это справедливыо не только для адресов, заданных символическими

именами, но и для других способов адресации. На Фиг. 6.3 показано

несколько примеров задания операнда с помощью адресации по базе и

индексу. Так как макропроцессор воспринимает параметр как какой-то

фрагмент текста, то параметр может быть образован любой символьной

строкой, какую вы пожелаете.

Можно задать макрокоманду и с несколькими параметрами.

Единственное, что ограничивает число параметров макрокоманды, это

длина ассемблерной строки. Все, что следует за оператором MACRO

интерпретируется макропроцессором как параметр. Для разделения

символических имен в определении макрокоманды пользуются запятыми.

Оператор MACRO с тремя параметрами будет выглядеть следующим

образом:

EXAMPLE MACRO ARG1, ARG2, ARG3

Аналогично, при вызове макрокоманды вы должны задать значение

каждого из параметров. Если вы хотите пропустить какой-то параметр,

то ассемблер подставит вместо него символьную строку нулевой длины.

Иногда это полезно, но часто приводит к неправильной трансляции.

Если макрокоманда имеет более одного параметра, то при вызове

макрокоманды относящийся к параметрам текст разделяется запятыми.

Это в точности совпадает со способом задания нескольких параметров

к любой из команд микропроцессора 8088, поэтому будет вполне

естественным для вас. Вызов макрокоманды с тремя параметрами может

выглядеть так:

EXAMPLE 5, [BX], MEMORY_BYTE

В следующем примере вы увидите некоторые возможности множествен-

ности параметров.


Ассемблирование по условию

До сих пор макрокоманды не отличались от подпрограмм с точки зрения

как их функционирования, так и использования параметров. Далее, нам

требуется возмоность ассемблирования в зависимости от условия. Так

же как ход выполнения подпрограммы может меняться в зависимости от

некоторых условицй в момент выполнения, так и у макрокоманды должна

быть возможность изменять в момент транслирования генерацию

соответствующего машинного кода в зависимости от удовлетворения

условий.

Макроассемблер фирмы IBM допускает условное ассемблирование. На

самом деле, условное ассемблирование не обязательно входит только в

макрокоманду. Программа может использовать условное транслирование

в любом месте ассемблерного текста. Однако наиболее часто оно

встречается в макрокомандах. В IBM PC условное транслирование

поддерживается только Макроассемблером MASM.

Так же, как и выполнение макрокоманд, условное ассемблирование

происходит во время трансляции, а не выполнения программы. Условное

транслирование позволяет программисту "запрограммировать" ассемблер

на транслирование различных последовательостей кодов. Ассемблер

определяет, что ему транслировать по параметру, известному во время

ассемблирования. Хотя эта возможность может использоваться

программой в любой момент ассемблирования, мы изучим прежде всего,

как она влияет на ассемблирование макрокоманд.

Фиг. 6.4 иллюстрирует условное транслирование при расширении

макрокоманды FIDIVR сопроцессора 8087. Условное тарнслирование

требуетя данной макрокоманде из-за разделения переменных в

ассемблере по типам. Как мы увидим в гл.7, команда, обозначенная

FIDIVR, может применяться к операндам двух типов. Операнд может

быть двух- или четырехбайтовым целым числом. Мы хотим, чтобы

ассемблер выбрал правильный машинный код в зависимости от типа

операнда. Как мы вмдели, у команды ADD в действительности имеется

несколько форм, в зависимости от того, какие операнды представлены

ассемблеру, который выбирает верную форму машинной команды в

зависиммости от этих операндов. Мы хотим делать то же самое для

команды FIDIVR Но в теперь макропроцессор должен определить тип

операнда и сгенерировать правильную команду.

У команды FIDIVR может быть один из двух типов операндов, и в

зависимости от этого будут различаться результирующие команды.

Таким образом, расширение макрокоманды FIDIVR должно

соответствовать нужному операнду. Это обеспечивается двумя

средствами языка: условным транслированием и оператором TYPE.

В языке ассемблера имеется оператор TYPE, который возвращает

Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:42

Фиг. 6.4 Условное транслирование Page 1-1

PAGE ,132

TITLE Фиг. 6.4 Условное транслирование

FIDIVR MACRO SOURCE

IFE 2 - TYPE SOURCE

DB 09BH ;; FWAIT

ESC 037H,SOURCE ;; FIDIVR слово

ENDIF

IFE 4 - TYPE SOURCE

DB 09BH ;; FWAIT

ESC 017H,SOURCE ;; FIDIVR короткое целое

ENDIF

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ???? TWO_BYTE DW ?

0002 ???????? FOUR_BYTE DD ?

0006 ?? ONE_BYTE DB ?

FIDIVR TWO_BYTE

0007 9B 1 DB 09BH ;

0008 DE 3E 0000 R 1 ESC 037H,TWO_BYTE ;

FIDIVR FOUR_BYTE

000C 9B 1 DB 09BH ;

000D DA 3E 0002 R 1 ESC 017H,FOUR_BYTE ;

FIDIVR ONE_BYTE

0011 CODE ENDS

END

Фиг. 6.4. Ассемблирование макрокоманд по условию

значение, равное длине операнда. В случае FIDIVR мы ожидаем, что

операнд будет двух- или четырехбайтовым целым числом.

Выражение

IFE 2-TYPE SOURCE

в макрокоманде FIDIVR проверяет длину операнда SOURCE.

Арифметическое выражение 2=TYPE SOURCE, равняется 0, если операнд

SOURCE является двухбайтовым целым числом, и принимает ненулевое

значение при любом другом типе операнда. Оператор IFE

(транслировать, если равно) сообщает ассемблеру, что нужно

транслировать все последующие команды, если выражение в поле

операнда равно 0. Таким образом, оператор IFE вырабатывает значение

истина, если операнд SOURCE является двухбайтовым целым числом. В

этом случае происходит трансляция всех команд, следующих за

оператором IFE до тех пор, пока не встретится оператор ENDIF. В

нашем примере это означает, что если операнд является двухбайтовым

целым числом, то ассемблируется участок программы

DB 09BH

ESC 37H,SOURCE

В первом вызове макрокоманды на Фиг. 6.4 в качестве операнда

используется двухбайтовое целое число. Поэтому, для расширения этой

макрокоманды ассемблер выбирает команду ESC 37H.

Так как команда FIDIVR имеет два варианта, соответствующие двум

разным типам операндов, то в при выполнении другого условия,

макрокоманда использует второй оператор IFE. Когда операнд является

четырехбайтовым целым числом, макрокоманда генерирует код ESC 17H.

На Фиг. 6.4 показаны два разных расширения одной макрокоманды.

Обратите внимание, что в последнем из показанных на Фиг. 6.4

вызовов макрокоманды операнд не удовлетворяет ни одному из условий.

Так как ни один из операторов IFE не вырабатывает значения

"истина", то и ассемблироваться не будет ни один из них. В этом

случае макропроцессор не генерирует никакого кода.

С помощью оператора IFE ассемблер может проверять выполнение

различных условий. Эти условия приведены в таблице на Фиг. 6.5.

Общая форма оператора IF имеет вид:

IFхх выражение

...

ELSE

...

ENDIF

Если значение условия "истина", то ассемблер обрабатывает

участок программы, следующий за оператором IFхх. Этот транслируемый

участок программы заканчивается либо оператором ELSE или ENDIF.

Оператор ELSE не является обязательным. Если он имеется, то

следующий за ним участок программы будет транслирован при

невыполнении условия в операторе IF. Оператор ENDIF завершает

условное ассемблирование и является обязательным.

IF-операция Ассемблировать если:

---------------------------------------------------------------

IF выражение Выражение не равно 0

IFE выражение Выражение равно 0

IFDEF имя Имя уже было описано как внешнее

IFNDEF имя Имя еще не описывалось

IFB <аргумент> Аргумент пуст

IFNB <аргумент> Аргумент не пуст

IFIDN <арг1>,<арг2> Строка арг1 идентична строке арг2

IFDIF <арг1>,<арг2> Строка арг1 отличается от строки арг2

IF1 Первый поход ассемблера

IF2 Второй проход ассемблера

---------------------------------------------------------------

Фиг. 6.5 Операторы IF для условного ассемблирования

Рассмотрим еще один пример, чтобы познакомиться с некоторыми

другими вариантами использования ассемблирования по условию. На

Фиг. 6.6 показано применение другого условного оператора - IFB, а

также использование вложенных условных операторов. Макрокомандой

здесь является FLD - команда загрузки сопроцессора 8087. Для

транслирования этой команды требуется несколько условных

операторов, так как она может применяться в следующих вариантах:

FLD

FLD 1

FLD короткое_вещественное

FLD длинное_вещественное

FLD временное_вещественное

Поле операнда макрокоманды FLD может быть пустым, содержать

константу, или четырехбайтовую, восьмибайтовую либо десятибайтовую

переменную. Макрокоманда должна распознать каждый из перечисленныз

случаев и сгенерировать правильный программный код. (все эти типы

данных рассмотрены очень подробно в гл.7.)

Оператор IFB проверяет наличие операнда. Если операнд

отсутствует, то ассемблер генерирует соответствующий этому случаю

программный код, так как оператор IFB вырабатывает значение

"истина". Это иллюстрирует первый вызов макрокоманды, когда

генерируется код

DB 09BH,0D9H,0C 1H

Оператор EXITM, содержащийся в этой части условного оператора

IF, реализует выход из макрокоманды. Каждый раз, когда при

расширении макрокоманды ассемблеру встречается этот оператор,

расширение заканчивается, как если бы встретился ENDM. В данном

случае ассемблер пропускает оставшуюся часть макроопределения. При

таком выходе из макрокоманды в ассемблерном листинге появляется

предупреждающее сообщение "Open conditionals:1" ("Незавершенные

условные операторы: 1"). Оно предупреждает вас, что ассемблер не Mincho"'> Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:47

Фиг. 6.6 Вложенные условные макрокоманды Page 1-1

PAGE ,132

TITLE Фиг. 6.6 Вложенные условные макрокоманды

FLD MACRO SOURCE

IFB <SOURCE>

DB 09BH,0D9H,0C1H ;; FLD ST(1)

EXITM

ELSE

IFE TYPE SOURCE

DB 09BH,0D9H,0C0H+SOURCE

;; FLD ST(i)

ENDIF

IFE 4 - TYPE SOURCE

DB 09BH

ESC 8,SOURCE ;; FLD короткое плавающее

ENDIF

IFE 8 - TYPE SOURCE

DB 09BH

ESC 40,SOURCE ;; FLD длинное плавающее

ENDIF

IFE 4 - TYPE SOURCE

DB 09BH

ESC 01DH,SOURCE ;; FLD временное плавающее

ENDIF

ENDIF

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ???????? FOUR_BYTE DD ?

0004 ???????????????? EIGHT_BYTE DQ ?

000C ??????????????????? TEN_BYTE DT ?

?

FLD

0016 9B D9 C1 1 DB 09BH,0D9H,0C1H ;

FLD 1

0019 9B D9 C1 1 DB 09BH,0D9H,0C0H+1

FLD FOUR_BYTE

001C 9B 1 DB 09BH

001D D9 06 0000 R 1 ESC 8,FOUR_BYTE ;

0021 9B 1 DB 09BH

0022 DB 2E 0000 R 1 ESC 01DH,FOUR_BYTE ;

FLD EIGHT_BYTE

0026 9B 1 DB 09BH

0027 DD 06 0004 R 1 ESC 40,EIGHT_BYTE ;

FLD TEN_BYTE

002B CODE ENDS

END

Фиг. 6.6 Вложенное условное ассемблирование

встретил оператора ENDIF, закрывающего условный оператор. Это

происходит в результате раннего входа из макрокоманды. Хотя это и

нежелательно, но ни к каким разрушительным последствиям не

приводит. Если оператор EXITM расположен вне условного оператора,

то предупреждение не выводится.

Оператор EXITM необходим в данной макрокоманде, так как

ассемблер проверяет все условные операторы, даже если они не

транслируются. В нашем случае, если операнд SOURCE пуст, оператор

ELSE предотвращает генерацию всех других вариантов команды FLD.

Однако, продолжая просмотр, ассемблер проверяет оператора

IFE TYPE SOURCE

хотя и не может сгенерировать никакого кода. Если SOURCE пусто,

то ассемблер фиксирует синтаксическую ошибку. Вы можете не обращать

внимания на эту ошибку, но принимать трансляцию с сообщениями об

ошибках идет в разрез с нашими правилами. С другой стороны,

использование оператора EXITM приводит к предупреждению "Открытое

условие". Оно нежелательно, но это меньшее из двух зол.

Заметим, что макрокоманда FLD использует ветвь ELSE чтобы

указать на необходимость вычисления выражения в поле операнда

только в случае непустоты этого поля. IF-выражения, содержащие

оператор TYPE, определяют, какой тип операнда использовался при

вызове макрокоманды. Хотя это не упоминается в Руководстве по

Макроассемблеру, оператор TYPE возвращает значение 0, если операнд

является не символическим именем, а константой. Выбор такого

способа в данной макрокоманде вызван скорее соображениями

работоспособности, чем стремлением к элегантности.


Untitled

Макрокоманды повторения

Для тех случаев, когда нужно несколько раз повторять один и тот же

фрагмент программы в макроассемблере имеется несколько специальных

макрокоманд. Это операторы REPT, IRT и IRPC. Каждый из них

действует как макрокоманда в макрокоманде и приводит к генерации

следующего за ним участка программы, пока ассемблеру не встретится

операнд ENDM.

Для простого повторения последрвательности команд используется

макрокоманда REPT. Последовательность

REPT выражение

;... тело макрокоманды REPT

ENDM

дублирует команды, составляющих тело данной макрокоманды. Значение

выражения определяет число повторений текста.

С помощью макрокоманды IRP можно при каждом повторении

использовать разные параметры. При использовании конструкции

IRP фиктивный параметр,<список>

;... тело макрокоманды IRP

ENDM

ассемблер осуществляет столько проходов тела макрокоманды,

сколько указано элементов в списке. При каждом проходе ассемблер

подставляет вместо фиктивного параметра следующий по порядку

элемент списка. Элементы списка должны быть числовыми выражениями.

Если вы хотите использовать в списке символьные значения,

применяйте макрокоманду IRPC. Последователность

IRPC фиктивный параметр, строка символов

;... тело макрокоманды IRPC

ENDM

осуществляет по одному проходу тела макрокоманды для каждого

символа. При этом фиктивный параметр ассемблер каждый раз заменяет

следующим по порядку символом из строки. На Фиг. 6.7 показаны

примеры применения описанных макрокоманд повтора.

Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:54

Фиг. 6.7 Макроповторения Page 1-1

PAGE ,132

TITLE Фиг. 6.7 Макроповторения

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

REPT 3 ; Повторить команду 3 раза

INC AX

ENDM

0000 40 1 INC AX

0001 40 1 INC AX

0002 40 1 INC AX

IRP VALUE,<5,10,15,20>

ADD AX,VALUE

ENDM

0003 05 0005 1 ADD AX,5

0006 05 000A 1 ADD AX,10

0009 05 000F 1 ADD AX,15

000C 05 0014 1 ADD AX,20

IRPC CHAR, ABCD

ADD AX,CHAR&X

ENDM

000F 03 C0 1 ADD AX,AX

0011 03 C3 1 ADD AX,BX

0013 03 C1 1 ADD AX,CX

0015 03 C2 1 ADD AX,DX

0017 CODE ENDS

END

Фиг. 6.7 Макрокоманды повтора


Untitled

Макрооператоры

Пример макрокоманды IRPC на Фиг. 6.7 иллюстрирует, в частности,

применение символа "&". Это - оператор макрокоманд, который служит

для соединения двух элементов. В данном примере оператор "&"

соединяет параметр CHAR со строкой-константой "X". Как вы видите,

при этом формируется действительное имя регистра.

Еще одним удобным средством при программировании макрокоманд

является оператор LOCAL. Оператор LOCAL задает метку, которая

используется только в данной макрокоманде. Эта метка должна быть

уникальной для каждого вызова данной макрокоманды. Предположим, что

вы хотите написать макрокоманду, в которой должен быть кусок

программы следующего вида:

AAAAA: ADD AL,[BX]

INC BX

LOOP AAAAA

При первом вызове этой макрокоманды никаких сложностей не

Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:59

Фиг. 6.8 Использование команды LOCAL Page 1-1

PAGE ,132

TITLE Фиг. 6.8 Использование команды LOCAL

PAUSE MACRO TIME

LOCAL LABEL

MOV CX,TIME

LABEL: LOOP LABEL

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE

PAUSE 100

0000 B9 0064 1 MOV CX,100

0003 E2 FE 1 ??0000: LOOP ??0000

PAUSE 1000

0005 B9 03E8 1 MOV CX,1000

0008 E2 FE 1 ??0001: LOOP ??0001

000A CODE ENDS

END

Фиг. 6.8 Использование команды LOCAL

возникает. Однако при ее повторном использовании в той же

программе, метка AAAAA появляется второй раз. Ассемблер не может

допустить двух меток с одним и тем же именем в одной программе и

отмечает это как ошибку.

Проблема будет разрешена, если объявить метку AAAAA локальной

(LOCAL) для данной макрокоманды. Тогда ассемблер установит сформи-

рованное им уникальное имя для каждого случая применения метки

AAAAA. Встретив имя типа LOCAL впервые, ассемблер меняет его на имя

"??0000". Во второй раз - на имя "??0001" и т.д. Каждое имя в

транслируемой программе будет уникальным, поэтому никаких ошибок не

возникнет. На Фиг. 6.8 показано использование оператора LOCAL.

Здесь макрокоманда PAUSE устанавливает счетчик цикла и затем орга-

низует цикл с помощью метки, объявленной LOCAL. Данная макрокоманда

позволяет организовывать в выполнении программы паузу переменной

длительности. Если в макрокоманде необходимо использовать описатель

LOCAL, то он должен быть первым оператором макрокоманды, сразу же

следуя за оператором MACRO.

Символ Значение

---------------------------------------------------------------

;; Комментарий, имспользуемый только в макроопределении

& Соединение текста с параметром

! Вводить следующий символ без интерпретации

% Преобразовать следующее выражение в значение

---------------------------------------------------------------

Фиг. 6.9 Макросимволы

Существуют специальные символы, которые помогут вам управлять

макрокомандами и их параметрами. В таблице на Фиг. 6.9 показаны

четыре таких символов с объяснением их значений.

На Фиг.6.10 приведен ассемблерный листинг программы, в которой

эти символы применяются. Мы уже видели символ ";;" в некоторых

макрокомандах сопроцессора 8087. Этот специальный указатель поля

комментариев указывает макропроцессору на необходимость исключить

поле комментариев при расширении данной макрокоманды. Это позволит

включать в макрокоманду комментарии, не допуская их появления при

каждом расширении данной макрокоманды. Применение символа "&" мы

также встречали в программе на Фиг. 6.7.

Символ "!" позволяет вам в качестве следующего за ним знака

поставить любой символ. Это понадобится, если вы захотите включить

в макрокоманду какой-либо специальный символ, например, "%", чтобы

он не вызывал при этом никакой макрооперации. И наконец, оператор

"%" преобразует символическое имя в числовое значение, которое это-

му имени в данный момент соответствует. Вы можете воспользоваться

этой возможностью для ведения нумерации при генерации макрокоманды.

В нашем примере на Фиг. 6.10 макрокоманда нумерует выводимые

сообщения в соответствии со значением символического имени VALUE.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:15

Фиг. 6.10 Специальные символы в макрокомандах Page 1-1

PAGE ,132

TITLE Фиг. 6.10 Специальные символы в макрокомандах

= 0000 VALUE EQU 0

EXAMPLE MACRO PARAMETER

DB 'MSG&PARAMETER' ;; Комментарий появится только в определении

INC AX

ENDM

0000 CODE SEGMENT

ASSUME CS:CODE

EXAMPLE %VALUE

0000 4D 53 47 30 1 DB 'MSG0' ;

0004 40 1 INC AX

0005 CODE ENDS

END

Фиг. 6.10 Специальные символы в макрокомандах


Untitled

Команды INCLUDE

Оператор ассемблера INCLUDE осуществляет вставку текста из другого

файла в транслируемую программу. Оператор INCLUDE особенно удобен в

случае работы с набором макрокоманд, например, макрокомандами

сопроцессора 8087. Все команды сопроцессора 8087 представлены

макрокомандами. Весь такой набор макрокоманд или какое-то его под-

множемтво вам приходится включать в любую транслируемую программу,

где применяется сопроцессор 8087. Однако копировать эти макроко-

манды в каждый исходный файл неудобно. Кроме того, они занимают

столько места, что исходные файлы быстро заполнили бы всю вашу дис-

кету, если бы вы попытались копировать макрокоманды в каждый файл.

Эта проблема решается в языке ассемблера с помощью оператора

INCLUDE. Оператор

ICLUDE имяфайла

считывает указанный файл и включает его как часть в ассемблируемую

программу. Ассемблер помещает нужный файл в то место, где

расположен оператор INCLUDE. Оператор INCLUDE естественно

использовать по отношению к библиотекам макрокоманд, например,

макрокоманд сопроцессора 8087. Вы помещаете оператор INCLUDE в

начало программы - и любая команда сопроцессора 8087 в вашей

программе будет ассемблироваться правильно.

Аналогично, вы можете использовать оператор INCLUDE для

подключения других частей программы. Если вы захотите разбить вашу

программу на меньшие исходные файлы, но ассемблировать ее как один

файл, то главный исходный файл может состоять из операторов INCLUDE

для всех вспомогательных исходных файлов. Однако как было показано

в гл.5, ассемблирование небольших модулей и связывание их с помощью

программы LINK в большинстве случаев предпочтительнее.

Другой вариант для использования оператора INCLUDE - структура

данных. Вы можете использовать некоторую структуру данных в

нескольких программах. Определение этой структуры данных можно

хранить в виде отдельного файла, и с помощью оператора INCLUDE

обращаться к ней из любой программы, где они требуются. Далее в

этой главе мы рассмотрим способы, с помощью которых программа может

задавать и использовать структуры данных.

Если включаемый файл является файлом макроопределений, то не

имеет смысла, чтобы он появлялся в ассемблерном листинге каждый раз

при трансляции программы. Чтобы исключить макроопределения из

листинга, но сохранить их для генерации программного кода, вы

можете воспользоваться условным оператором IF1. Последовательность

IF1

INCLUDE 87MAC.LIB

ENDM

приводит к включению в программу файла 87MAC.LIB при первом

проходе ассемблера. Именно во время первого прохода ассемблер

осуществляет расширение всех макрокоманд до их окончательной формы.

При втором проходе макроопределения ассемблеру не нужны. Это

ускоряет процесс ассемблирования, так как во время второго прохода

файл макрокоманд не считывается. Ассемблер не печатает файл

макрокоманд, так как листинговый файл формируется при втором

проходе. На Фиг. 6.11 показано использование в программе

Microsoft (R) Macro Assembler Version 5.00 1/1/80 01:21:40

Фиг. 6.11 Вставка макроопределений для 8087 Page 1-1

PAGE ,132

TITLE Фиг. 6.11 Вставка макроопределений для 8087

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 ???? TWO_BYTE DW ?

0002 9B DB E0 FENI

0005 9B DE 3E 0000 R FIDIVR TWO_BYTE

000A 9B D9 C1 FLD

000D CODE ENDS

END

Фиг. 6.11 Вставка макроопределений для 8087

последовательности IF1 ... INCLUDE ... ENDM для макрокоманд

сопроцессора 8087. Приведены как исходный, так и листинговый файлы

данной программы. Ассемблер правильно обрабатывает команды

сопроцессора 8087, опуская печать определений соответствующих

макрокоманд.


Сегменты

Ранее уже рассматривался оператор SEGMENT. Теперь есть возможность

рассмотреть его более подробно и исследовать дополнительные

возможности, которые он предоставляет.

До сих пор в большинстве примеров программ присутствовал только

один оператор SEGMENT. Так как программный код должен находиться в

некотором сегменте, то нужно присвоить ему имя. Учитывая, что

ассемблер должен суметь определить адрес сегмента, единственный

оператор ASSUME в прграмме идентифицирует только один сегмент

программы. В подобных случаях возможности сегментации программ

микропроцессора 8088 используются не полностью, но часто это и не

нужно. Если программа и ее данные помещаются в пределах одной и той

же адресуемой области памяти объемом 64 кбайт, то нет необходимости

использовать возможности процессора в сегментации памяти.

Существуют ситуации, когда в программе нужно использовать более

одного оператора SEGMENT. Одно из таких применений рассматривалоясв

гл.5 в нескольких примерах, использующих DOS. В этих примерах в

программе определялся сегмент STACK. Имя, выбранное для сегмента,

несущественно, но его тип, указанный в операторе SEGMENT, должен

быть STACK, так как файлу типа .EXE для выполнения программы

необходимо отвести стековую область. Если в программе не задать

сегмент STACK, то загрузчик DOS сохранит организацию стека в

некотором месте памяти, которое может оказаться неприемлемым. В

этом случае программа может работать недостаточно хорошо.

Другое назначение оператора SEGMENT - расположением данных в

определенном месте памяти. Как известно, при использовании DOS

лучше всего, если программа имеет перемещаемый программный сегмент.

В этом случае нас не заботит, куда DOS загружает программу. Но в

некоторых случаях фактическое расположение команд или данных

оказывается существенным. В этих случаях для задания местоположения

данных можно воспользоваться директивой AT оператора SEGMENT.

Чтобы понять значение указателя AT, рассмотрим пример. В этом

примере программа использует как Отправную точку систему BIOS, хра-

нящаяся в ПЗУ персональной ЭВМ. Хотя язык ассемблера является очень

эффективным средством программирования, с другой стороны это

довольно трудный инструмент, особенно для больших программ. Поэтому

выбор языка ассемблера обусловливается свойствами, которые делают

его выгодным для решения определенной задачи. В случае IBM PC язык

ассемблера - лучший язык для программирования функций, выполняемых

ROM BIOS. Эти функции можно охарактеризовать как управление устрой-

ствами ввода-вывода, где обычно требуется оперировать с отдельными

битами. Программирование подобных задач сводится к возможности ма-

нипулировать содержимым точно заданных ячеек памяти и портов ввода-

вывода. Язык ассемблера также используется в тех случаях, когда

необходима минимизация размера программы или максимальное быстро-

действие программы. Всем эти требования предъявляет и система ROM

BIOS.

В рассматриваемом примере используется часть BIOS. В одной из

последующих глав будет рассмотрено, как заменять части системы

BIOS. Однако в данном случае нас интересует доступ к наборам

данных, которые использует ROM BIOS. Если вы посмотрите

ассемблерный листинг для ROM BIOS (он приводится в приложении A

технического руководства по IBM PC), то увидите, что сегмент DATA

располагается в сегменте 40H или по абсолютному адресу 400H.

Приведенная на Фиг. 6.12 программа обращается в область данных ПЗУ

системы BIOS c определенной целью. В сегменте DATA имеется

переменная KB_FLAG, которая указывает текущее состояние

переключателя регистров. Одна из жалоб, часто высказываемых по

поводу клавиатуры IBM, состоит в том, что неизвестно, работаете ли

вы в верхнем регистре (CAPS LOCK) или в нижнем. Программа на Фиг.

6.12 считывает значение бита, соответствующего CAPS LOCK, и

изображает его в верхнем правом углу цветного графического дисплея.

Хотя в данной программе это не реализовано, мы будем предполагать,

что при реальном использовании этого фрагмента программы, верхний

правый угол экрана зарезервируется для описанного индикатора.

Сегмент DATA на Фиг. 6.12 показывает, как программист может

передать в программу информацию, расположенную по абсолютным адре-

сам. Оператор DATA SEGMENT использует директиву AT для того, чтобы

обеспечить безусловную привязку данного сегмента к параграфу 40H.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:25

Фиг. 6.12 Использование сегментов Page 1-1

PAGE ,132

TITLE Фиг. 6.12 Использование сегментов

0000 DATA SEGMENT AT 40H

0017 ORG 17H

0017 ?? KB_FLAG DB ?

= 0040 CAPS_STATE EQU 40H

0018 DATA ENDS

0000 VIDEO SEGMENT AT 0B800H

009E ORG 158

009E ?? INDICATOR DB ?

009F VIDEO ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 CAPS PROC FAR

0000 1E START: PUSH DS ; Адрес возврата

0001 B8 0000 MOV AX,0

0004 50 PUSH AX

0005 B8 ---- R MOV AX,DATA ; Адрес сегмента DATA

0008 8E D8 MOV DS,AX

ASSUME DS:DATA

000A B8 ---- R MOV AX,VIDEO ; Адрес сегмента VIDEO

000D 8E C0 MOV ES,AX

Фиг. 6.12 Расположение сегмента (начало)

ASSUME ES:VIDEO

000F DISPLAY_CAPS:

000F B0 18 MOV AL,18H ; Символ "стрелка вверх" имеет код 18H

0011 F6 06 0017 R 40 TEST KB_FLAG,CAPS_STATE ; Определение состояния клавиши CAPS

0016 75 02 JNZ CAPS_LOCK

0018 B0 19 MOV AL,19H ; Символ "стрелка вниз" имеет код 19H

001A CAPS_LOCK:

001A 26: A2 009E R MOV INDICATOR,AL ; Вывод в верхний левый угол экрана

001E B4 06 MOV AH,6 ; Функция ДОС ввода с клавиатуры

; и вывода на дисплей

0020 B2 FF MOV DL,0FFH ; Направление - ввод с клавиатуры

0022 CD 21 INT 21H

0024 3C 00 CMP AL,0 ; Проверка на наличие символа

0026 74 E7 JZ DISPLAY_CAPS ; Нет символа

0028 3C 25 CMP AL,'%' ; Проверка на символ конца

002A 74 08 JE RETURN

002C B4 02 MOV AH,2 ; Функция вывода на дисплей

002E 8A D0 MOV DL,AL ; Выводимый символ

0030 CD 21 INT 21H

0032 EB DB JMP DISPLAY_CAPS ; Повторение

0034 RETURN:

0034 CB RET ; Возврат в ДОС

0035 CAPS ENDP

0035 CODE ENDS

END START

Фиг. 6.12 Расположение сегмента (продолжение)

Просматривая листинг ROM BIOS, мы находим переменную KB_FLAG со

смещением 17H в сегменте DATA. Оператор ORG 17H данной программы

задает смещение этой переменной в оттранслированной программе.

Наконец, смысл оператора EQU, определяющего константу CAPS_STATE

следует непосредственно из листинга BIOS ПЗУ. Заданный этой

константой бит указывает текущее состояние переключателя CAPS LOCK.

В приведенной на Фиг. 6.12 программе имеется еще один оператор

SEGMENT. Он определяет сегмент VIDEO с адресом 0B800H. Это

сегментный адрес буфера для адаптера цветного- графического

дисплея. Этот адрес нужен для вывода состояния индикатора на экран

дисплея. Если мы хотим поместить символ в правый верхний угол

экрана, при условии, что строка на экране содержит 80 символов, то

смещение соответствующей ячейки должно быть равно 158 в десятичном

представлении. Программируемые характеристики оборудвания ПК

описываются в гл.8, а пока вы можете принять сказанное на веру.

Первая часть программы устанавливает необходимую адресацию

сегментов. Регистр DS указывает на сегмент DATA, а регистр ES - на

сегмент VIDEO. Хотя в программе эти сегменты объявлены директивой

AT абсолютными, ассемблер все же обозначает их значком "R", как

перемещаемые. Программа LINK, тем не менее, подставляет в

соответствующие поля данных правильные значения.

Программа тестирует переменную KB_FLAG, а ассемблер в

результате генерирует правильное смещение, равное 17H. В данном

примере символ стрелка вниз используется для обозначения обычного

режима, а стрелка вверх обозначает режим CAPS LOCK. Введенные с

клавиатуры символы считываются программой с помощью функции DOS,

выводящей эим символя на дисплей. В данном примере для выхода из

программы был произвольно выбран символ %. Если пользователь вводит

любой другой символ, то программа выводит его на дисплей и

возвращается к ожиданию ввода следующих.

Если ввести и запустить данную программу, то вы увидите в

верхнем правом углу цветного графического дисплея направленную вниз

или вверх стрелку. Если для цветного дисплея установлен режим 40

символов в строке, при выполнении данной программы

стрелка-индикатор будет выводиться во второй сверху строке. Если

нужно использовать эту программу с адаптером монохромного дисплея,

то измените адрес сегмента VIDEO на адрес 0B000H, соответственно

местоположению буфера монохромного дисплея.

При выполнении данной программы с адаптером цветного

графического дисплея в режиме 80 символов в строке вы увидите на

экране сильную помеху, "снег". Эта интерференция на экране

происходит из-за прямой передачи данных из программы в буфер

дисплея. В случае монохромного адаптера или цветного-графического

дисплея в режиме 40 символов в строке этой помехи не будет. О

причинах этого эффекта и о том, как его избежать, мы узнаем при

рассмотрении аппаратного обеспечения IBM PC.

Существуют и другие применения нескольких операторов SEGMENT в

одной программе. Если программе требуется область данных объмом

более 64 кбайт, то она должна организовать доступ к этим данным.

Как правило, вы воспользуетесь для обращения к этой области данных

некоторой схемой управления памятью. В такой ситуации вам будет

доступна вся эта область данных (за исключением некоторых

фиксированных участков) косвенную адресацию.

В качестве примера рассмотрим, как интерпретатор команд DOS

загружает программы. DOS загружает транзитную программу на границу

параграфа сразу за резидентной частью DOS. Размер этой резидентной

части может варьироваться в зависимости от числа дисководов в

системе. Кроме того, этот размер может существенно возрастать при

использовании в DOS прерывания INT 27H, которое заканчивает

выполнение программы, но оставляет ее резидентной в памяти. При

этом программный загрузчик DOS должен адресоваться к сегментному

префиксу PSP той программы, которую он загружает. Проще всего

задать эту структуру данных с помощью отдельного оператора SEGMENT.

На Фиг. 6.13 показано объявление сегмента, которое можно

использовать в двух различных местах. Если бы можно было посмотреть

текст исходной программы для загрузчика DOS, то мы бы обнаружили

там подобное объявление. В случае программы, использующей структуру

.EXE, такая сегментация могла бы обеспечить доступ к переменным в

сегментном префиксе PSP. В приведенном на Фиг. 5.6 примере

программы с применением функций DOS, использовалась структура файла

типа .COM. Это позволяло нам обращаться к различным ячейкам

сегмента PSP через смещение относительно блока PSP. Задача весьма

облегчалась тем, что DOS загружала программу в тот сегмент, который

содержал PSP.

В случае .EXE-файла блок PSP находится не в том же сегменте,

что и команды программы. Так как при передаче управления программе

типа .EXE DOS устанавливает регистры DS и ES на сегмент PSP, то

имеет смысл обращаться с PSP как с отдельным сегментом. Приведенный

на Фиг. 6.13 фрагмент программы из сегмента CODE, показывает, как

можно обращаться к данным в блоке PSP.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:31

Фиг. 6.13 Структура Программного Префикса Page 1-1

PAGE ,132

TITLE Фиг. 6.13 Структура Программного Префикса

0000 PROGRAM_SEGMENT_PREFIX SEGMENT

0000 0002[ INT_20 DB 2 DUP (?)

??

]

0002 ???? MEMORY_SIZE DW ?

0004 0005[ LONG_CALL DB 5 DUP (?)

??

]

0009 ???????? TERMINATE_ADDR DD ?

000D ???????? CTRL_BREAK DD ?

005C ORG 05CH

005C 0010[ FCB1 DB 16 DUP (?)

??

]

006C ORG 06CH

006C 0010[ FCB2 DB 16 DUP (?)

??

]

0080 ORG 080H

0080 0080[ DTA DB 128 DUP (?)

??

]

0100 PROGRAM_SEGMENT_PREFIX ENDS

0000 CODE SEGMENT

ASSUME CS:CODE,DS:PROGRAM_SEGMENT_PREFIX

0000 A1 0002 R MOV AX,MEMORY_SIZE

0003 CODE ENDS

END

Фиг. 6.13 Префикс программного сегмента

Обратите внимание, что сегмент PSP на Фиг. 6.13 на самом деле

не содержит никаких значений для переменных. Например, мы знаем,

что в первых двух байтах PSP содержится код прерывания INT 20H.

Однако мы решили показать, что в этом месте находится поле длиной 2

байта без каких-либо указаний о содержащихся там значениях. Мы

должны делать именно так, чтобы в результате нашего описания

сегмента ни редактор связей, ни загрузчик не попытались записать в

память каких-либо данных. Фактически, мы используем этот сегмент

как средство для объявления данных. Оператор SEGMENT объявляет

структуру данных, которую мы называем префиксом программного

сегмента. Ее местоположение в памяти не фиксировано, а определяется

одним из сегментных регистров. В нашем примере на Фиг. 6.13 это

местоположение определяется регистром DS.

Точно такой же способ можно использовать для обозначения любой

структуры данных, которая может быть расположена в произвольном

месте памяти микропроцессора 8088. Эта структура данных может быть,

например, болком управления для операционной системы, либо строкой

текста для текстового редактора, или даже блоком параметров

конкретной подпрограммы. Каждый объект структуры данных

располагается в своем отдельном сегменте. Таким образом, при

обращении программы к каждому элементу структуры данных сегментный

регистр указывает на начало (или на близкую к началу точку) этого

элемента. Программа не обращается к двум различным элементам с

одним и тем же значением сегментного регистра. Для каждого элемента

всегда устанавливается свой адрес сегмента.

Здесь следует немного остановиться на том, какие вообще есть

методы распределения памяти микропроцессора 8088. IBM PC с

микропроцессором 8088 может адресовать до 1Мбайт оперативной

памяти, но один сегмент может охватывать не более 64 кбайт. Даже с

четырьмя сегментными регистрами программа не имеет возможности

охватить всю память, не используя некоторых способов сегментации.

Если все данные помещаются в 64К, то нет нужды волноваться:

просто поместите все данные в один сегмент. Если же мы полагаем,

что программе требуется область данных, превышающая 64К, то нам

придется решать задачу распределения памяти. При этом возможны две

стратегии. В обоих случаях мы будем предполагать, что вся

совокупность данных может быть разбита на меньшие блоки (такие как

отдельные переменные, строки текста, управляющие блоки или

массивы), объемом не более 64К каждый.

Первый метод распределения памяти применяется в ситуации, когда

вашей главной заботой является экономия памяти. При этом методе вы

располагаете объекты данных в первых же свободных участках памяти.

Программа, управляющая доступом к областям данных, должна при этом

для каждой переменной использовать четырехбайтовый указатель. Из

них два байта используется для смещения и еще два байта для значе-

ния сегмента. Когда программе нужно полусить доступ к данным, она

извлекает адрес из области хранения адресов с помощью команд LDS

или LES. Если вам требуется еще большая экономия памяти, то вы

фактически можете хранить указатель в трехбайтовом поле. Два байта

содержат адрес сегмента данных, а оставшийся байт содержит смещение

данного объекта внутри сегмента. Начальное смещение всегда будет

иметь значение от 0 до 15, так как значение сегмента всегда кратно

16.

Хотя описанный метод наиболее эффективен в отношении объема

памяти, занимаемой данными, у него имеются пара недостатков.

Максимальная длина объекта данных немного меньше, чем 64 кбайт. В

рамках данной стратегии наихудшим окажется случай, когда абсолютный

адрес объекта данных кончается на 0FH. Так как максимальное

значение смещения в любом сегменте равно 0FFFFH, то максимальная

длина переменной будет 64К - 15, или 65521 байт. Второй недостаток

этого метода связан с затратами памяти для хранения указателей к

объектам данных. При большом числе объектов для хранения наряду с

ними всех четырехбайтовых (или трехбайтовых) указателей потребуется

много памяти.

Примером использования описанного метода распределения памяти

может служить блок управления файлом FCB. В последнем примере

работающей с DOS программы мы располагали блок FCB в произвольном

месте программы. Какого-либо выравнивания местоположения этой

структуры данных не производилось. Затем при обращении к DOS для

выполнения файловой операции программе понадобился четырехбайтовый

указатель. Идентификация блока FCB для DOS осуществлялось парой

регистров DS:DX.

При втором методе распределиня памяти все объекты данных

располагаются на границах параграфов. Это сразу же упрощает

указатель, определяющий объект данных. Этот указатель состоит

только из двух байтов, которые определяют местонахождение сегмента

с этими данными. Так как распределение памяти всегда начинается с

границы параграфа, то начальное смещение данных будет всегда равно

нулю. Однако при таком методе, расходуется дополнительная память.

Каждый раз, когда вы располагаете в памяти новый объект, возможна

потеря до 15 байт памяти. Это происходит, если последний байт

предыдущего объекта попадает точно на границу параграфа. Так как

граница следующего параграфа будет через 15 байт, то эти 15 байт в

промежутке теряются. Кроме того, при такой стратегии минимальная

длина объекта равна 16 байт. Даже если данные будут занимать меньше

места, оставшиеся байты все равно не могут быть использованы.

Как было отмечено, второй метод распределения памяти

используется загрузчиком DOS при запуске программ. DOS загружает

программу на ближайшую границу параграфа. Так как DOS исходит из

того, что в памяти располагается мало больших по размерам объектов,

то при данном методе издержки памяти будут невелики. Однако, если

ваша прикладная программа использует много небольших объектов, то

выравнивание по параграфам может оказаться слишком дорогим.

Второй метод распределения памяти, использующий выравнивание по

параграфам, позволяет определять области данных с помощью структуры

SEGMENT. Если же хотите использовать первый метод распределения

памяти, то вам потребуется другой способ определения структур

данных. Такой способ объявления данных как раз рассматривается в

следующем разделе.


Untitled

Структуры

Структура данных - это организация данных, которая имеет для

программиста определенный смысл. Как показывает опыт, мы определяем

структуры данных когда одна и та же совокупность данных

используется более чем одной программой или программистами.

Благодаря определению, обе стороны имеют четкий образ этих данных.

Если программа A передает некотрые данные программе B, то

определение структуры данных гарантирует, что каждая из программ

ищет данные в одном и том же месте.

У нас уже был хороший пример структуры данных. Блок управления

файлом FCB является структурой данных. Блок FCB используется

программами для обмена информацией о файле с DOS. В блоке FCB

содержатся такие важные данные об обрабатываемом файле, как номер

текущей записи, длина файла и т.д. Кроме того, в блоке FCB имеется

зарезервированное поле, которое содержит информацию, используемую

только DOS. В блоке FCB находится вся информация, необходимая для

DOS и прикладных программ. Эта структура данных служит для передачи

параметров файла между DOS и прикладной программой.

Теперь нужно найти такой способ определения структур данных,

чтобы программа могла с удобством ими пользоваться. В

Макроассемблере фирмы IBM имеется оператор STRUC, позволяющий

определять структуру данных. С точки зрения программиста структура

данных выглядит как еще один сегмент. Определение данных

ассемблируется так же, как и обычные операторы данных, и описание

структуры, как и описание сегмента, заканчивается оператором ENDS.

Однако в действительности структура не генерирует данные. Оператор

STRUC определяет структуру данных для ассемблера. В дальнейшем имя

этой структуры данных используется в ассемблируемой программе для

генерации соответствующей области данных.

Если рассматривать оператор STRUC описанным выше образом, то он

больше похож на оператор MACRO. Программа определяет структуру

данных в одном месте, а ее вызов осуществляет позднее. Фактическая

генерация данных происходит при вызове структуры. Фиг. 6.14 поможет

понять работу оператора STRUC.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:36

Фиг. 6.14 Структуры Page 1-1

PAGE ,132

TITLE Фиг. 6.14 Структуры

FCB STRUC

0000 00 DRIVE DB 0 ; Номер устройства

0001 20 20 20 20 20 20 20 FILE_NAME DB ' ' ; Имя файла

20

0009 20 20 20 FILE_EXT DB ' ' ; Тип файла

000C 0000 CURRENT_BLOCK DW 0 ; Номер текущего блока

000E 0080 RECORD_SIZE DW 80H ; Логический размер записи

0010 00000000 FILE_SIZE DD 0 ; Размер файла в байтах

0014 0000 DATE DW 0 ; Дата последнего изменения

Фиг. 6.14 Структуры (начало)

0016 000A[ RESERVED DB 10 DUP (?) ; Зарезервировано ДОС

??

]

0020 00 SEQ_NUMBER DB 0 ; Номер текущей записи

0021 00000000 RANDOM_NUMBER DD 0 ; Номер записи при прямом

; доступе

0025 FCB ENDS

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 01 INPUT FCB <1,'FIG6-14','INP'>

0001 464947362D313420

0009 494E50

000C 0000

000E 0080

0010 00000000

0014 0000

0016 000A[

??

]

0020 00

0021 00000000

0025 02 OUTPUT FCB <2,'EXAMPLE','TST'>

0026 4558414D504C4520

002E 545354

0031 0000

0033 0080

0035 00000000

0039 0000

003B 000A[

??

]

0045 00

0046 00000000

004A STRUCTURES PROC FAR

004A 1E PUSH DS ; Установка адреса возврата

004B B8 0000 MOV AX,0

004E 50 PUSH AX

004F 0E PUSH CS ; Установка DS на сегмент CODE

0050 1F POP DS

ASSUME DS:CODE

0051 8D 16 0000 R LEA DX,INPUT ; Открытие вводимого файл

Фиг. 6.14 Структуры (продолжение)

0055 B4 0F MOV AH,0FH

0057 CD 21 INT 21H

0059 8D 16 0025 R LEA DX,OUTPUT ; Создание выводимого файла

005D B4 16 MOV AH,16H

005F CD 21 INT 21H

0061 8D 1E 0000 R LEA BX,INPUT

0065 C7 47 0E 0010 MOV [BX].RECORD_SIZE,16 ; Установка размера записи для ввода

006A C6 47 20 01 MOV [BX].SEQ_NUMBER,1 ; Пропуск первой записи

006E C7 06 0033 R 0010 MOV OUTPUT.RECORD_SIZE,16 ; Установка размера записи для

; вывода

0074 8D 16 0000 R LEA DX,INPUT ; Чтение второй записи из файла

0078 B4 14 MOV AH,14H

007A CD 21 INT 21H

007C 8D 16 0025 R LEA DX,OUTPUT ; Вывод введенной записи

0080 B4 15 MOV AH,15H

0082 CD 21 INT 21H

0084 B4 10 MOV AH,10H ; Закрытие выводимого файла

0086 CD 21 INT 21H

0088 CB RET

0089 STRUCTURES ENDP

0089 CODE ENDS

END

Microsoft (R) Macro Assembler Version 4.00 4/16/89 23:15:19

Фиг. 6.14 Структуры Symbols-1

Structures and Records:

N a m e Width # fields

Shift Width Mask Initial

FCB . . . . . . . . . . . . . . 0025 000A

DRIVE . . . . . . . . . . . . 0000

FILE_NAME . . . . . . . . . . 0001

FILE_EXT . . . . . . . . . . . 0009

CURRENT_BLOCK . . . . . . . . 000C

RECORD_SIZE . . . . . . . . . 000E

FILE_SIZE . . . . . . . . . . 0010

DATE . . . . . . . . . . . . . 0014

RESERVED . . . . . . . . . . . 0016

SEQ_NUMBER . . . . . . . . . . 0020

RANDOM_NUMBER . . . . . . . . 0021

Фиг. 6.14 Структуры (продолжение)

На Фиг. 6.14 приведена очень простая программа, которая

использует файлы системы DOS. Эта программа открывает файл DOS на

носителе в дисководе A:, считывает вторую запись этого файла и

записывает ее в файл на носителе в дисководе B:. Маловероятно,

чтобы вы когда-нибудь применили эту программу чтобы сделать

что-либо существенное, но сейчас она дает нам возможность

использовать структуру данных для блока FCB.

Первая часть программы на Фиг. 6.14 определяет структуру данных

FCB. Оператор языка ассемблера STRUC отмечает начало определения

структуры. Метка FCB является здесь именем данной конкретной

структуры. В примере определяется каждое поле структуры данных FCB.

Обратите внимание, что в столбцах слева ассемблер генерирует

объектный код данной структуры. Однако, при редактировании связей

ассемблированного объектного кода, область данных в программе

отсутствует. Ассемблер распечаьываеь данную структуру данных в

оттранслированном виде исключительно для вашего сведения.

Точно так же, как и вслучае макрокоманды, имя FCB становится

как бы новым оператором языка ассемблера. Первым оператором в

сегменте CODE является вызов структуры данных FCB. В примере этой

структуре данных присваивается имя INPUT. Данная структура FCB

идентифицирует входной набор данных. Заметьте, что в этом операторе

FCB имеются операнды. Они заменяют или перекрывают значения,

которые были включены в исходное определение структуры данных.

Если мы сравним объектный код определения структуры FCB с

объектным кодом структуры INPUT, то увидим, что они различаются по

значениям первых трех полей. В определении структуры данных поле

DRIVE равно 0, в структуре INPUT - 1. Первый операнд в угловых

скобках определения структуры INPUT равен 1. Это значение заменяет

исходно определенное значение 0. Аналогично в данном примере

изменяются значения второго и третьего полей, относящихся к имени

файла. Закрывающая угловая скобка в определении структуры INPUT

завершает процедуру замены значений полей структуры данных.

Оставшаяся часть структуры INPUT идентична определеной в структуре

данных FCB.

Программа может изменять любой из полей структуры FCB, если

определение этого поля содержит только один элемент. В

рассматриваемом примере программа может изменять любое поле

структруыр FCB, за исключением поля RESERVED. Мы определили это

поле поле как 10 отдельных элементов, и оно не может быть изменено.

Аналогично, если поле определено оператором

DB 10,20

то его нельзя перекрыть. При вызове структуры можно изменять только

поля, состоящие из единственного элемента. Символьная строка,

включающая несколько символов, рассматривается ассемблером как один

элемент. В данном примере поле FILE_NAME содержит несколько

символов, но является одним элементом, значение которого можно

изменить.

Операнды, перечисленные в угловых скобках, заменяют операнды,

входящие в определение, по позиционному принципу, как и в

макрокоманде. Если вы не хотите модифицировать заданное в

определении значение, но желаете изменить следующее за ним поле, то

в список значений нужно включить пустой параметр. Например, для

модификации полей FILE_NAME и CURRENT_BLOCK, оставляя в то же время

по умолчанию заданные значения полей DRIVE и FILE_EXT, структуру

FCB следует вызвать оператором:

EXAMPLE FCB <,NEWNAME,,12>

Первый параметр пуст, так что ассемблер использует для него

значение по умолчанию. В следующем поле NEWNAME заменяет строку

пробелов. Для значения поля FILE_EXT по умолчанию используется

строка пробелов, и наконец, нулевое значение поля CURRENT_BLOCK

заменяется на 12.

В следующей исходной программной строке на Фиг. 6.14 программа

определяет структуру FCB под именем OUTPUT для выходного файла.

Здесь снова модифицируются первые три поля определения данных путем

включения новых значений в поля операндов оператора FCB.

Выигрыш от использования структур данных проявляется в

действительных командах программы. Программа может ссылаться на

имена INPUT и OUTPUT так же, как и на любые другие метки в

программе. Вы можете видеть это, в том участке программы, где

открывается входной файл INPUT и оператор

LEA DX,INPUT

используется для установки адреса входной структуры данных FCB.

В программе можно использовать каждое из указанных полей

структуры данных. Значению каждого имени соответствует смещение

какого-либо поля в структуре данных. Например, программа помещает в

регистр BX адрес FCB INPUT. После этого программа обращается к

полям RECORD_SIZE и SEQ_NUMBER в режиме адресации по базе. Так как

регистр BX уже указывает на структуру данных FCB, то нужно задать

смещение относительно этой базы. Способ адресации

[BX].RECORD_SIZ

указывает ассемблеру, что в команде, которую он должен

сгенерировать, смещение поля RECORD_SIZE складывается со значением

базы, хранящимся в регистре BX. Если вы рассмотрите соответствующие

команды на машинном языке, то увидите, что в них присутствуют

смещения для полей RECORD_SIZE (0EH) и SEQ_NUMBER (20H). Символ "."

идентифицирует имена полей как смещения в структуре данных.

Помимо режимов адресации по базе и индексу можно использовать

структуру данных и для прямой адресации. Следующий участок

программы непосредственно изменяет поле RECORD_SIZE в FCB OUTPUT.

Программа именует это поле OUTPUT.RECORD_SIZE. Имя OUTPUT

определяет конкретную структуру данных, а RECORD_SIZE - имя поля в

этой структуре данных.

Прежде, чем покончить с этим примером, посмотрим, какой

информацией о структуре данных располагает ассемблер. Фиг. 6.14

включает в себя фрагмент таблицы символических имен. Ассемблер

выделяет один из разделов этой таблицы для структур и записей.

Ассемблер показывает вам всю информацию о структуре данных, которой

располагает. Этот раздел имеет заголовок "Structures and records"

("Структуры и записи"). В первой строке этого раздела для структуры

FCB из нашего примера указано, что эта структура имеет длину 25H

байт и содержит 0AH полей. Далее ассемблер перечисляет все эти

поля, печатая их с отступом по отношению к имени структуры. Для

каждого из полей приводится значение соответствующего смещения. Для

структур ассемблер использует два столбца, обозначеные "width"

(ширина) и "#dfields" (число полей). Вторая строка в меток колонок

используется для записей. Структуры данных, которые ассемблер

интерпретирует как записи, будут рассмотрены в следующем разделе.

Программа, приведенная на Фиг. 6.14, не делает ничего

полезного. Кроме того, в ней нет никакой обработки ошибок. Однако

она хорошо иллюстрирует применение оператора STRUC. Этот способ

определения данных особенно подходит для часто используемых

структур данных. Использование имен полей в качестве значений

смещений очень удобно при модификации структур данных на этапе

разработки программы. Если вы внесете изменения в структуру данных,

то ассемблер автоматически изменит значения смещений при повторном

ассемблировании программы. Кроме того, использование структур

данных делает программу на языке ассемблера более читабельной и

понятной.


Записи

Рассмотренные в предыдущем разделе структуры предназначены для

многобайтовых данных. Но в некоторых случаях требуется побитовое

определение объектов данных. Для таких случаев в Макроассемблере

имеется механизм описания данных, который называют записью

(RECORD). Действие оператора RECORD аналогичны действиям операторов

STRUC и MACRO. Оператор RECORD задает определенную конфигурацию

данных. Присвоенное записи имя становится для ассемблера еще одним

оператором. Вы можете использовать это имя записи для задания

специальных конфигураций данных. От оператора STRUC оператор RECORD

отличается тем, что он определяет объекты на уровне отдельных

битов. Каждому из полей оператор RECORD присваивает имя и указывает

его ширину в битах. Оператор RECORD можно использовать для

формирования битовых полей длиной до 16 бит.

Здесь мы опять воспользуемся примером. На Фиг. 6.15 приведена

еще одна ничего не делающая программа, связанная с установоением

даты изменения файла. В определении Блока управления файлом

содержится 16-битовое поле, в котором содержится дата формирования

или последнего изменения данного файла операционной системой. При

открыти файла DOS заполняет это поле в блоке FCB на основе

информации из каталога дискеты. В 16-ти батих поля даты

закодированы год, месяц и число. Из приведенного на Фиг. 6.15

оператора RECORD видно строение этого слова данных.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:43

Фиг. 6.15 Записи Page 1-1

PAGE ,132

TITLE Фиг. 6.15 Записи

DATE_WORD RECORD YEAR:7,MONTH:4,DAY:5

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 FCB LABEL BYTE

0000 01 DRIVE DB 1 ; Номер устройства

0001 46 49 47 36 2D 31 35 FILE_NAME DB 'FIG6-15 ' ; Имя файла

20

0009 41 53 4D FILE_EXT DB 'ASM' ; Тип файла

000C 0000 CURRENT_BLOCK DW 0 ; Номер текущего блока

000E 0080 RECORD_SIZE DW 80H ; Логический размер записи

0010 00000000 FILE_SIZE DD 0 ; Размер файла в байтах

0014 0000 DATE DATE_WORD <> ; Дата последнего изменения

0016 000A[ RESERVED DB 10 DUP (?) ; Зарезервировано ДОС

??

]

0020 00 SEQ_NUMBER DB 0 ; Номер текущей записи

0021 00000000 RANDOM_NUMBER DD 0 ; Номер записи при прямом

0025 RECORDS PROC FAR

0025 1E PUSH DS ; Адрес возврата

0026 B8 0000 MOV AX,0

0029 50 PUSH AX

002A 0E PUSH CS ; Установка DS на сегмент CODE

002B 1F POP DS

ASSUME DS:CODE

002C 8D 16 0000 R LEA DX,FCB ; Открытие файла

0030 B4 0F MOV AH,0FH

0032 CD 21 INT 21H

0034 A1 0014 R MOV AX,DATE

0037 25 FE00 AND AX,MASK YEAR ; Выделение года

003A B1 09 MOV CL,YEAR ; Сдвиг вправо

003C D3 E8 SHR AX,CL

003E 8A F8 MOV BH,AL ; Сохранение года в регистре BH

0040 A1 0014 R MOV AX,DATE

0043 25 01E0 AND AX,MASK MONTH

0046 B1 05 MOV CL,MONTH

0048 D3 E8 SHR AX,CL

004A 8A D8 MOV BL,AL ; Сохранение месяца в регистре BL

Фиг. 6.15 Записи (начало)

004C A1 0014 R MOV AX,DATE

004F 25 001F AND AX,MASK DAY ; Сохранение дня в регистре AL

0052 CB RET

0053 RECORDS ENDP

0053 CODE ENDS

END RECORDS

Фиг. 6.15 Записи (продолжение)

В примере на Фиг. 6.15 записи присваивается имя DATE_WORD. В

поле операндов оператора RECORD видно, что в записи DATE_WORD

имеется три поля. Первые 7 бит содержат год (YEAR), следующие 4

бита - месяц (MONTH) и последние 5 бит - число (DAY). Так же, как и

в случае оператора MACRO, данная исходная программа просто

определяет тип конкретной записи, названной DATE_WORD. Оператор

RECORD не формирует соответствующих данных в памяти до тех пор,

пока вы не используете имя записи в качестве ассемблерного

оператора.

В рассматриваемом примере запись DATE формируется в блоке FCB с

использованием определения записи DATE_WORD. Метка DATE обозначает,

а запись DATE_WORD формирует эту 16-битовую область данных. Как мы

увидим ниже, существуют способы определения для полей записей

значений по умолчанию и их изменения. В данном примере мы ничего не

делали для изменения стандартных нулевых значений каждого из

полей.

Приведенная на Фиг. 6.15 программа иллюстрирует не только

структуру оператора RECORD, но и некоторые операции, которые

упрощаются благодаря этому оператору. В первой части программы

открывается файл, имя которго указано в блоке FCB. Остальная часть

программы извлекает из FCB информацию о дате и перебрасывает

отдельные поля в регистры микропроцессора 8088.

Во-первых, программа выделяет в дате из блока FCB значение

года: переслав слово данных DATE в регистр AX, программа обнуляет в

нем командой AND значения, относящиеся к месяцу и числу. Обратите

здесь внимание на непосредственный операнд MASK YEAR. Так как YEAR

- это поле записи, то оператор MASK возвращает значение, которое

выделяет в данном слове поле YEAR. В данном случае это значение

равно 0FE00H. Первые 7 бит в нем - единицы, а остальные биты

обнулены. Это значение маски соответстувет битам, которые формируют

значения YEAR в слове данных. В результате логического умножения

данного значения и всей остальной записи остается только поле

YEAR.

Следующими командами программа перемещает поле YEAR в правый

конец слова. Полю YEAR соответствует значение, равное сдвигу,

который необходим для перемещения данного поля до правой границы

слова. В данном случае это значение равно девяти. Сдвиг вправо на 9

бит обращает значение года в число в регистре AL. (Следует помнить,

что DOS кодирует год в виде числа в перделах от 0 до 119. Эти

значения соответствуют годам от 1980 до 2099).

В последующей группе команд программа выделяет из записи поле

MONTH. Здесь также используются оператор MASK и значения сдвигов,

из записи DATE_WORD. Подобным образом программа выбирает из записи

значение поля DAY.

В данном примере не выполняется никакой полезной работы, так

как записанные в регистры значения оказываются потерянными при

возвращении управления DOS. Однако вы можете запустить программу

через отладчик и установить точку прерывания на команде возврата.

Отладчик выводит на дисплей содержимое регистров BH, BL и AL, так

что вы можете увидеть дату. Более практичная программа после

считывания значений, относящихся к дате, преобразовывала бы их в

код ASCII для вывода на экран. Либо вы могли бы оформить нашу

программу как процедуру, которая вырабатывает информацию о дате для

другой программу.

Имеется еще несколько особенностей операции RECORD, которые

следует рассмотреть. На Фиг. 6.15 приведен фрагмент таблицы

символических имен из ассемблерного листинга. В этой таблице

содержится информация, которая имеется у ассемблера о каждом из

полей записи. В этой таблице нас будет интересовать второй ряд

заголовков: "Shift Width Mask Initial" (Сдвиг Длина Маска Начальное

значение"). Как видно из таблицы символических имен, запись

DATE_WORD имеет длину 16 бит и состоит из трех полей. Каждое поле

имеет четые атрибута. Значение сдвига равно числу битов в записи,

остающихся справа от поля. Это значение указывает ассемблеру,

насколько нужно сдвинуть данное поле, чтобы выровнять его на правый

край. Значение маски служит для выделения поля в записи. Цифра 1 в

слове маски показывает, что соответствующая позиция относится к

данному полю.

Ассемблер может оперировать значением длины любого поля записи.

Вы можете задать длину поля при ассемблировании с помощью оператора

WIDTH. Например, команда

MOV AL,WIDTH YEAR

в нашем примере помещает в регистр AL значение, равное семи.

Столбец начальных значений в таблице символических имен

показывает, какие значения ассемблер вставляет при формировании

записи. Можно задавать записи с начальными значениями, отличными от

нуля. Имеется также возможность изменить эти значения при генерации

области данных. Для задания начальных значений вы после указания в

операторе RECORD каждого из полей ставите знак равенства и

соответствующее значение. Запись DATE_WORD с начальными значениями,

соответствующими 1 января 1983 года, будет иметь вид:

DATE_WORD RECORD YEAR:7=3, MONTH:4=1, DAY:5=1

Эти значения можно переназаначить точно таким же способом, как

и в случае структур. Когда вы формируете запись, в угловых скобках

содержатся конкретные значения для данной генерации. Если нужно,

чтобы дата была 5 января 1984 года, то вы можете сгенерировать

запись следующим образом:

DATE DATE_WORD <4,,5>

Как и в случае макрокоманд или структур, параметры здесь

являются позиционно-зависимыми. Так как параметр, соответствующий

месяцу, не указан, то ассемблер воспользуется для него начальным

значением, указанным в операторе RECORD.

Обратите внимание, что программа на Фиг. 6.15 определяет блок

FCB, не полбзуясь оператором STRUC, рассмотренным в предыдущем

разделе. Мы не могли воспользоваться оператором STRUC, потому что

данные каждого поля структуры должны определяться одним из

операторов типа DEFINE. Мы не можем использовать имя записи в

качестве одного из полей структуры. Тот способ, который мы

применили на Фиг. 6.15, - один из позволяющих обойти эту

трудность.

Можно решить эту же задачу другим способом. Хотя ассемблер и не

генерирует область данных, пока имя записи не использовано в

программе в качестве оператора, но он хранит описания полей,

указанных в операторе RECORD. Это позволяет определить в программе

запись DATE_WORD, не используя ее для задания поля DATE структуры

данных. Это все равно, что определить макрокоманду, но не вызывать

ее. Остальная часть программы остается без изменений. Имена

различных полей записи DATE_WORD имеют в программе смысл и могут

использоваться как параметры при сдвиге и маскировании.

То же самое верно и для оператора STRUC. Определение структуры

задает для ассемблера значения смещений, даже если вы не вызываете

затем эту структуру для формирования области данных. Вы можете

воспользоваться этим для размещения блока FCB по умолчанию по

адресу 05CH в префиксе программного сегмента. Так как блок FCB

существует всегда, то нет необходимости использовать эту структуру

для генерации соответствующей области данных. Программы приведенные

на Фиг. 6.16 и 6.15, почти идентичны, за исключением некотрых

деталей. В программе на Фиг. 6.16 блок FCB определяется не

последовательностью команд типа DEFINE, а оператором STRUC.

Обратите внимание, что при ассемблировании данной программы не

называется ни запись DATE_WORD, ни структура FCB: они служат лишь

для задания смещений в области данных.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:49

Фиг. 6.16 Структуры и записи Page 1-1

PAGE ,132

TITLE Фиг. 6.16 Структуры и записи

DATE_WORD RECORD YEAR:7,MONTH:4,DAY:5

FCB STRUC

0000 00 DRIVE DB 0 ; Номер устройства

0001 20 20 20 20 20 20 20 FILE_NAME DB ' ' ; Имя файла

Фиг. 6.16 Структуры и записи (начало)

20

0009 20 20 20 FILE_EXT DB ' ' ; Тип файла

000C 0000 CURRENT_BLOCK DW 0 ; Номер текущего блока

000E 0080 RECORD_SIZE DW 80H ; Логический размер записи

0010 00000000 FILE_SIZE DD 0 ; Размер файла в байтах

0014 0000 DATE DW 0 ; Дата последнего изменения файла

0016 000A[ RESERVED DB 10 DUP (?) ; Зарезервировано ДОС

??

]

0020 00 SEQ_NUMBER DB 0 ; Номер записи в блоке

0021 00000000 RANDOM_NUMBER DD 0 ; Номер записи в файле

0025 FCB ENDS

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 RECORDS PROC FAR

0000 1E PUSH DS ; Занесение в стек адреса возврата

0001 B8 0000 MOV AX,0

0004 50 PUSH AX

ASSUME DS:CODE ; DS файтически указывает на PSP

0005 BA 005C MOV DX,05CH ; Адрес FCB в PSP

0008 B4 0F MOV AH,0FH ; Открыть файл

000A CD 21 INT 21H

000C BB 005C MOV BX,05CH ; Адрес FCB

000F 8B 47 14 MOV AX,[BX].DATE

0012 25 FE00 AND AX,MASK YEAR ; Выделение года из полной даты

0015 B9 0009 MOV CX,YEAR ; Выравнивание вправо

0018 D3 E8 SHR AX,CL

001A 8A F0 MOV DH,AL ; Сохранение года в регистре DH

001C 8B 47 14 MOV AX,[BX].DATE

001F 25 01E0 AND AX,MASK MONTH

0022 B9 0005 MOV CX,MONTH

0025 D3 E8 SHR AX,CL

0027 8A D0 MOV DL,AL ; Сохранение месяца в регистре DL

0029 8B 47 14 MOV AX,[BX].DATE

002C 25 001F AND AX,MASK DAY ; Сохранение дня в регистре AL

002F CB RET

0030 RECORDS ENDP

0030 CODE ENDS

END RECORDS

Фиг. 6.16 Структуры и записи (продолжение)

И последнее замечание об использовании записей и структур. Мы

применяем эти средства, потому что они позволяют писать программы

не вникая в конкретные детали структуры данных. Используя для

описания данных оператор STRUC, мы можем рассматривать ссылки на

каждое из полей как смещение внутри структуры данных. При этом

программисту не нужно знать фактическое смещение этого поля. То же

самое верно и для записи из битов. Если программа использует

операторы MASK и сдвига, то распределение бит этого поля достаточно

задать только в соответствующем операторе RECORD.

Выигрыш от применения этих программных средств становится

очевидным при разработке больших систем, с которыми связано большое

число программистов или программ. В этом случае вы наверняка будуте

модифицировать структуры данных по мере разработки программ. Если

при написании программ пользоваться описанными структурными

моделями, то изменить их и связанные с ними программы будет

несложно: вы модифицируете определенную структуру данных, а затем

вновь транслируете все программы с использовнием этой структуры.

Сами же программы в изменениях не нуждаются. Можно поступить еще

проще, если хранить структуру данных, как отдельный файл, который

будет включаться в каждую ассемблируемую программу оператором

INCLUDE: таким образом вы будете иметь только одну версию структуры

данных. Описанные средства упрощают процесс создания большой

программы со всеми изменениями, возникающими при ее разработке.


Untitled

Глава 7

Математический сопроцессор 8087

Конструкторы микропроцессора Intel 8088 предусмотрели для него

уникальную возможность, характерную лишь для семейства

микропроцессоров 8086/8088. Конструкции микропроцессора позволяет

иметь в системе сопроцессор. Сопроцессор - это устройство,

расширяющее возможности центрального процессора. Арифметический

сопроцессор 8087 является сопроцессором центрального

микропроцессора 8088, добавляющий команды числовой обработки и

регистры с плавающей точкой. Эти дополнительные арифметические

возможности расширяют набор команд микропроцессора 8088, и

значительно увеличивают вычислительную мощность в тех случаях,

когда программа выполняет операции с плавающей точкой и повышенной

точности.

Фирма сконструировала персональную ЭВМ так, чтобы была

возможность установить в нее сопроцессор. На системной плате рядом

с панелью микросхемы 8088 существует пустая 40- штырьковая панель.

Фирма IBM смонтировала эту панель для того, чтобы можно было

установить любой сопроцессор, архитектурно совместимый с

микропроцессором 8088. Арифметический сопроцессор 8087 отвечает

этим требованиям; он совместим и на уровне архитектуры, и по

расположению выводов. Для того, чтобы использовать его

вычислительные возможности, вам нужно всего лишь вставить

микросхему 8087 в пустую панель. Затем нужно установить

перключатель на системной плате в положение, показывающее что

сопроцессор существует; это делается только для того, чтобы

сопроцессор воспринимал от микросхамы 8087 прерывания по особой

ситуации. А собственно работа микросхамы 8087 не зависит ни от

одного из переключателей.


Untitled

Работа 8087

Сопроцессор 8087 обрабатывает команды с плавающей точкой,

контролируя команды, выполняемые процессором 8088. Арифметический

сопроцессор "смотрит" на команды, выполняемые процессором 8088.

Когда сопроцессор 8087 "видит" команду, котрую он должен выполнить,

он начинает ее обработку. Микросхема 8087 выполняет свои числовые

операции параллельно с микропроцессором 8088. То есть, пока

микросхема 8087 выполняет арифметическую команду, микропроцессор

8088 может продолжать выполнять свои команды. Тем самым возникает

истинная параллельность работы; микропроцесоор 8088 может выполнять

команду, пока сопроцессор 8087 выполняет другую команду. В

частности, это имеет значение в случае, когда команда сопроцессора

8087 занимает много времени, что характерно для некоторых команд с

плавающей точкой.

Поскольку два процессора могут работать параллельно, необходима

некоторая синхронизация межлу ними. Эту работу выполняет команда

WAIT микропроцессора 8088. Микросхема 8087 подключена к

микропроцессору 8088 таким образом, что когда процессор числовой

обработки занят (выполняет команду плавающей точки), вход TEST

микропроцессора 8088 неактивен. Команда WAIT останавливает

обработку команд в микропроцессоре 8088 до тех пор, пока вход TEST

не станет активен, сигнализуя о завершении команды сопроцессора

8087. Таким образом, при работе микропроцессора 8088 можно

гарантировать, что микросхема 8087 завершит свою работу до того,

как микропроцессор 8088 выберет на выполнение следующую команду.

Это также предотвращает выборку данных до завершения исполнения

команды.

Процессор и сопроцессор связаны только по внешним линиям

управления, таким как вход TEST. Микропроцессор 8088 не может

читать внутренние регистры микросхемы 8087, и наоборот. Все

данные, передаваемые между ними, должны быть помещены в память, к

которой оба процессора имеют доступ. Но из-за того, что регистры

адресации находятся в микропроцессоре 8088, микросхеме 8087 трудно

эффективно адресовать память, используя те же способы адресации,

которые используют микропроцессор 8088. Чтобы позволить микросхеме

8087 адресовать память с помощью способов адресации микропроцессора

8088, существует взаимодействие двух процессоров при выполнении

команд плавающей точки.

Набор команд микропроцессора 8088 содержит команду ESC, которая

сама по себе не выполняется в микропроцессоре 8088. В системах без

сопроцессора 8087 команда ESC идентична команде NOP - за

исключением того что для ее выполнения требуется большн времени.

Все команды ESC имеют встроенную адресную информацию, а именно, для

вычисления адреса, они используют байт mod=r/m. Несмотря на то,

что команда ESC действует как команда NOP, микропроцессор 8088

выполняет вычисление исполнительного адреса, а заетм выполняет

чтение памяти по результирующему адресу, хотя и не производит

никаких действий с данными. Если байт mod=r/m определяет регистр,

а не память микропроцессора 8088, никакого чтения памяти не

происходит.

Тем временем микросхема 8087 "наблюдает" за последовательностью

команд, выполняемых микропроцессором 8088. Когда микропроцессор

выполняет команду ESC, микросхема 8087 распознает эту команду, как

свою собственную. Затем микросхема 8087 ждет, пока микропроцесоор

8088 выполнить фиктивное чтение памяти. Когда адрес памяти

оказывается на системной шине, микросхема 8087 "захватывает" его, и

начиная с этого момента знает, где находятся данные в памяти, не

вычисляя при этом адреса. Микропроцессор 8088 вычисляет адрес, а

микросхема 8087 выполняет остальную часть команд. Теперь

микросхема 8087 может "похищать" некоторые циклы памяти для чтения

или записи данных, а микропроцесоор 8088 в это время продолжает

работу.

Микросхема 8087 добавляет арифметические возможности в систему,

но не замещает ни одну команду микропроцессора 8088. Команды ADD,

SUB, MUL и DIV, описанные в гл.4, выполняются микропроцессором

8088, а арифметический сопро- цессор 8087 выполняет дополнительные,

более эффективные команды арифметической обработки. С точки зрения

программиста, система с установленной в ней микросхемой 8087 должна

выглядеть, как единый процессор с большим набором команд, чем

простой микропроцессор 8088. Лишь в немногих местах надо помнить,

какой процессор какую команду выполняет. Только тогда, когда

микропроцессор 8088 должен непосредственно использовать результат

работы микросхемы 8087 и требуется синхронизирующая команда WAIT,

различать процессоры становится необходимо.

Когда вы используете Макроассемблер фирмы IBM, возникает

проблема при написании программ, использующих набор команд

микросхемы 8087. Чтобы использовать эту микросхему, нужно

сформировать ее команды, используя коды операций WAIT и ESC.

Наилучший способ сделать это - написать набор макрокоманд, которые

позволяют писать команды микросхемы 8087. В гл.6 были приведены

некоторые макрокоманды, необходимые для написания команд

сопроцессора 8087. Если вы собираетесь программировать микросхему

8087, вам нужно дописать примеры гл.6 для полного набора команд

сопроцессора 8087.


Untitled

Типы данных 8087

Микросхема 8087 имеет расширенный набор типов данных,

необходимых для поддержки ее расширенных арифметических

возможностей. В то время как микропроцессор 8088 может

непосредственно работать только с байтами и словами, микросхема

8087 имеет семь типов данных. Шесть из них присущи лишь микросхеме

8087. На Фиг. 7.1 показаны все семь типов данных, с которыми

работает микросхема 8087. Четыре формата представляют целые числа,

а три формата - вещественные, или числа с плавающей точкой. Один

формат представляет упакованные десятичные числа.

Рисунок 7.2 иллюстрирует способы, которыми микросхема хранит

эти числа в памяти. Как и в случае данных микропроцессора 8088,

все данные хранятся с младшей частью операнда, записанной в младших

адресах. Бит знака всегда оказывается в байте по старшему адресу

памяти. Мы будем обсуждать назначение различных полей по мере

того, как будем рассматривать разные типы данных.

Сопроцессор 8087 работает с тремя типами целых чисел: словом,

которое имеет длину 16 бит и идентично слову микропроцессора 8088;

коротким целым числом, имеющим длину 32 бита; длинным целым числом,

64-значением. Все эти числа являются числами, представленными в

двоичном дополнительном коде.

В программе целое слово определяется с помощью оператора DW.

Такое целое слово может иметь значение в диапазоне от - 32768 до

32767. Этот целый формат уже использовался в наборе команд

микропроцессора 8088. Это - единственный формат данных, общий для

микропроцессора 8088 и арифметического сопроцессора 8087. Короткий

целый формат требуется в операторе описания данных длиной 32 бита.

Такое описание двойного слова выполняет оператор DD, определяющий

целые числа, лежащие в диапазоне от -232 до 232-1. Напоминаем, что

с помощью оператора DD можно также определить пару

СЕГМЕНТ:СМЕЩЕНИЕ. Ассемблер решает сам, какую именно форму

сгенерировать, основываясь на операнде. Если операнд - адрес,

порождается пара СЕГМЕНТ:СМЕЩЕНИЕ; если же операнд - просто число,

соответственно порождается длинное целое число. Для описания

длинных 64-битовых целых чисел используется оператор определения

счетверенного слова DQ. Эта директива вынуждает ассемблер

сформировать поле данных, состоящее их четырех слов (восьми байт).

Такой тип целого может иметь значения в диапазоне от -264 до 264-1.

Этот оператор ассемблера, так же как и операторы DB, DW и DD, может

определить константу, неопределенное поле (если задан операнд "?"),

<ДДДД ВОЗРАСТАНИЕ ЗНАЧЕНИЯ

ЪДВДДДДДДДДДДДДДДДї

СЛОВА ЦЕЛЫЕ іSі ВЕЛИЧИНА і(ДВОИЧНОЕ

АДБДДДДДДДДДДДДДДДЩ ДОПОЛНЕНИЕ)

15 0

ЪДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

КОРОТКОЕ ЦЕЛОЕ іSі ВЕЛИЧИНА і(ДВОИЧНОЕ

АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ДОПОЛНЕНИЕ)

31 0

ЪДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

ДЛИННОЕ ЦЕЛОЕ іSі ВЕЛИЧИНА і

АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

63

ЪДВДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

УПАКОВАННОЕ ДЕСЯТИЧНОЕ іSі X і ВЕЛИЧИНА і

АДБДДДДДДДБd17Бd16Бd15Бd14Бd13Бd12Бd11Бd10Бd9ДБd8ДБd7ДБd6ДБd5ДБd4ДБd3ДБd2ДБd1ДБd0ДЩ

79 72 0

ЪДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДї

КОРОТКОЕ ВЕЩЕСТВЕННОЕ іSіПОРЯДОК і МАНТИССА і

АДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ

31 23 0

ЪДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

ДЛИННОЕ ВЕЩЕСТВЕННОЕ іSі ПОРЯДОК і МАНТИССА і

АДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

63 52 0

ЪДВДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

ВРЕМЕННОЕ ВЕЩЕСТВЕННОЕ іSі ПОРЯДОК ГДДї МАНТИССА і

АДБДДДДДДДДДДДДДДДДДДБДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

79 64 63 0

КОММЕНТАРИИ:

S - бит знака (0 - плюс, 1 - минус)

dn - Десятичная цифра (по две на бит)

X - Незначащие биты; 8087 их игнорирует при загрузке и обнуляет при сохранении

- Позиция подразумеваемой двомичной точки

I - Целый бит мантиссы; сохраняется для временных действительных, подразумевается - для коротких и длинных

ПОРЯДОК (нормализованные значения):

Короткие действительные: 127 (7FH)

Длинные действительные: 1023 (3FFH)

Временные действительные: 16383 (3FFFH) 

Фиг. 7.1 Форматы данных 8087 (Copyright Intel 1980)

а также несколько восьмибайтовых полей с помощью команды DUP.

Оставшийся целый тип данных - упакованный десятичный формат.

Этот тип данных представляет целое число в упакованном десяточном

формате. Такие данные занимают десять байт. Один байт

резервируется для знака, а оставшиеся девять байт содержат 18

десяточных цифр. Такое упакованное представление десятичных чисел

идентично представлению десятичных операндов микропроцессора 8088,

но при этом представлении одновременно обрабатывается 18 цифр.

Команды десятичной коррекции упакованных десятичных чисел

микропроцессора 8088 допускают одновременно только две десятичные

цифры. Кроме того, упакованные десятичгые числа микропроцессора

8088 требуют, чтобы программист определил метод обработки знака

числа, если используются отрицательные числа. Упакованные

десятичные числа сопроцессора 8087 имеют бит знака в старешм байте.

Упакованное десятичное число хранится в десятичном коде, причем

старший бит 10-байтного поля содержит знак (0 - положительно, 1 -

отрицательно).

Для описания упакованного десяточного числа используется

оператор определения десятибайтового поля DT. Чтобы задать

уракованное десятичное число в этом поле, необходимо использовать

шестнадцатеричную запись. Если в поле операнда окажется целое

число, ассемблер преобразует его в дополнительный двоичный код, а

не в упакованное десятичное число. К счастью, преобразовать

десятичное число в необходимую шестнадцатеричную форму легко.

A

ЪДВДВДДДДДДДДДДДї

і |M| і

+3іS|S| і

і |B| і

ГДБДБДДДДДДДДДДДґ

і і

+2і і

і і

ЪДВДВДДДДДДДДДДДї ГДДДДДДДДДДДДДДДґ ЪДВДВДДДДДДДДДДДї

і |M| і і і і |M| і

+1іS|S| і +1і і +3іS|S| і

і |B| і і і і |E| і

ГДБДБДДДДДДДДДВДґ ГДДДДДДДДДДДДДВДґ ГДЕДЕДДДДДДДДДДДґ

і |Lі і |Lі іL|M| і

+0і |Sі +0і |Sі +2іS|S| і

і |Bі і |Bі іE|F| і

АДДДДДДДДДДДДДБДЩ АДДДДДДДДДДДДДБДЩ ГДБДБДДДДДДДДДДДґ

7 0 7 0 і і

ЦЕЛЫЕ СЛОВА КОРОТКИЕ ЦЕЛЫЕ +1і і

і і

і ЪДВДДДДДДДДДДДДДї ГДДДДДДДДДДДДДВДґ ЪДВДВДДДДДДДДДДДї

і і | і і |Lі і |M| і

і +9іS| (X) і +0і |Sі +9іS|S| і

і і | і і |Fі і |E| і

і ГДБДДДДДВДДДДДДДґ АДДДДДДДДДДДДДБДЩ ГДБДБДДДДДДДДДВДґ

і і | і 7 0 і |Lі

Р +8і | і КОРОТКИЕ ВЕЩЕСТВ. +8і |Sі

О і | і і |Eі

ЪДВДВДДДДДДДДДДДї С ГДДДДДДДЕДДДДДДДґ ЪДВДВДДДДДДДДДДДї ГДВДВДДДДДДДДДБДґ

і |M| і Т і | і і |M| і і і |M| і

+7іS|S| і +7і | і +7іS|S| і і +7іI|S| і

і |B| і А і | і і |E| і і і |F| і

ГДБДБДДДДДДДДДДДґ Д ГДДДДДДДЕДДДДДДДґ ГДБДБДДВДВДВДДДДґ і ГДБДБДДДДДДДДДДДґ

і і Р і | і і |L|M| і і і і

+6і і Е +6і | і +6і |S|S| і і +6і і

і і С і | і і |E|F| і Р і і

ГДДДДДДДДДДДДДДДґ О ГДДДДДДДЕДДДДДДДґ ГДДДДДДБДБДБДДДДґ О ГДДДДДДДДДДДДДДДґ

і і В і | і і і С і і

+5і і +5і | і +5і і Т +5і і

і і і | і і і і і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ А ГДДДДДДДДДДДДДДДґ

і і і | і і і Д і і

+4і і +4і | і +4і і Р +4і і

і і і | і і і Е і і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ С ГДДДДДДДДДДДДДДДґ

і і і | і і і О і і

+3і і +3і | і +3і і В +3і і

і і і | і і і і і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДВДґ

і і і | і і і і | і

+2і і +2і | і +2і і +2і | і

і і і | і і і і | і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДґ

і і і | і і і і і

+1і і +1і | і +1і і +1і і

і і і | і і і і і

ГДДДДДДДДДДДДДВДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДВДґ ГДДДДДДДДДДДДДВДґ

і |Lі і | і і |Lі і |Lі

+0і |Sі +0і | і +0і |Sі +0і |Sі

і |Bі і | і і |Fі і |Fі

АДДДДДДДДДДДДДБДЩ АДДДДДДДБДДДДДДДЩ АДДДДДДДДДДДДДБДЩ АДДДДДДДДДДДДДБДЩ

7 0 7 0 7 0 7 0

ДЛИННЫЕ ЦЕЛЫЕ УПАКОВАННЫЕ ДЕС. ДЛИННЫЕ ВЕЩЕСТВ. ВРЕМЕННЫЕ ВЕЩЕСТВ.

S: Бит знака S: Бит знака

MSB/LSB: Старший/младший бит MSE/LSE: Старший/младший бит порядка

MSD/LSD: Старшая/младшая десятичная цифра MSF/LSF: Старший/младший бит дробной части

(X): Биты не имеют значения I: Целый бит мантиссы

A

Фиг. 7.2 Структура хранения данных в 8087

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:56

Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087 Page 1-1

PAGE ,132

TITLE Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087

0000 CODE SEGMENT

0000 04D2 WORD DW 1234

7_3.ASM(6): warning A4016: Reserved word used as symbol: WORD

0002 FB2E DW -1234

0004 40E20100 SHORT_INTEGER DD 123456

0008 C01DFEFF DD -123456

000C D202964900000000 LONG_INTEGER DQ 1234567890

0014 2EFD69B6FFFFFFFF DQ -1234567890

001C ???????????????? DQ ?

0024 78563412907856341200 PACKED_BCD DT 00123456789012345678H

002E 78563412907856341280 DT 80123456789012345678H ; Отрицательное от предыдущего

0038 0002[ DT 2 DUP (?)

???????????????

?????

]

004C CODE ENDS

END

Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087

Просто запишите нужное число в десятичной форме, а затем добавьте

букву H, показывающую, что это - шестнадцатеричное число.

Изобразить отрицательное число труднее. Если вы напишите перед

десятичным числом знак "-", ассемблер преобразует его в двоичный

дополнительный код, даже если есть буква H. Поэтому в этом случае

нужно подсчитать десятичные цифры и удлинить число до 20 цифр.

первые две цифры должны быть 80, чтобы показать, что число

отрицательно. То есть, чтобы изобразить -1234 в упакованном

десятичном формате, надо записать:

DT 80000000000000001234H

На Фиг. 7.3 изображен листинг ассемблера, иллюстрирующий

сформированные ассемблером значения в случае четырех типов целых

чисел.


Untitled

Представление данных с плавающей точкой

Для изображения чисел с плавающей точкой в процессоре 8087

существует три формата данных. Два из них совпадают с предложенным

ИИЭР стандартом для таких чисел. Короткий формат имеет 32 бита, а

длинный - 64 бита. Третий формат определяет 80-битовые числа, и не

совпадает со стандартом ИИЭР. Сопроцессор 8087 использует такой

формат "промежуточного действительного числа", чтобы обеспечить

очень высокую точность для промежуточных результатов вычислений.

Оставшаяся часть этого раздела предназначена для тех, кто не

имел дела с вещественными числами в ЭВМ. Вы можете пропустить этот

раздел, если понимаете о общих чертах способы представления

вещественных чисел - как на бумаге, так и в ЭВМ; а в следующем

разделе мы рассмотрим конкретный способ, используемый в

сопроцессоре 8087.

Целые числа - лучший способ представления многих величин.

Целые чила просто понимать и использовать, а также легко

преобразовывать в двоичное представление. Однако с целыми числами

плохо работать в случае очень больших значений. Очень большое

целое число обычно оканчивается длинной строкой нулей. Например:

Солнце находится на расстоянии около 93000000 миль от Земли. Целые

числа, кроме того, не способны представить значение, содержащее

дробную часть, то есть ЭВМ не может запомнить число 1/2 в целом

представлении. Любые другие дроби, меньшие 1, также невозможно

представить, используя целые числа.

Ученые и математики давным-давно разработали способ

представления этих чисел в достаточно удобном виде. На первом

этапе вводится десяточная точка. Этот символ, "." показывает

границу между целой и дробной частью числа. В случае целого числа

позиция, представляющая единицы, всегда находится на рпавом краю

числа; в случае, когда используется десятичная точка, цифры справа

от нее представляют значения, меньшие единицы.

У целых чисел каждая позиция числа соответствует степени 10.

То есть число 1234 есть

1234=1000+200+30+4=1*10**3+2*10**2+3*10**1+4*10**0

Десятичная точка показывает границу между позицией,

соответствующей 100, и дробной частью. В дробной части позиции

снова являются сиепенями 10, но теперь эти степени 10 -

отрицательные. Поэтому число 1.234 есть

1.234=1+0.2+0.03+0.004=

1*10**0+2*10**-1+3*10**-2+4*10**-3

Десятичная точка позволяет записывать дроби. Число 1/2 теперь

выглядит как 1.5, а 1/4 выглядит как 0.25, и 1/5 выглядит как 0.2.

Поскольку каждая позиция десятичного числа отличается от

соседней на степень 10, умножение числа на 10 эквивалентно сдвигу

десятичной точки на одну позицию вправо. Аналогично деление на 10

сдвигает десятичную точку на позицию влево. Это свойсто можно

использовать для сдвига десятичной точки на соответствующее место:

мы сдвигаем десятичную точку и одновременно корректируем число на

же степень 10. Такое представление чисел называется представление

"с плавающей точкой", поскольку десятичная точка "плавает" в числе;

она больше не помечает абсолютное место между целой и дробной

частями. Положение десятичной точки можно выбрать из соображений

удобства, а затем умножить число на нужную степень 10, чтобы

получить правильное значение.

Например, Солнце находится на расстоянии примерно 93000000 миль

от Земли. С такой формой записи тяжело работать из-за обилия

нулей. Можно записать это число в формате с плавающей точкой:

9.3*107. То есть 93000000 эквивалентно 9.3, умноженному на 10 в

седьмой степени. Фактически,

93000000=9.3*10**7=93*10**6=930*10**5

и так далее. Мы можем двигать десятичную точку куда угодно,

меняя степень 10.

Десятичное число с плавающей точкой состоит из двух частей.

Значащая часть числа называется мантиссой. В предыдущем примере

число 9.3 есть мантисса. На практике мантисса обычно находится в

пределах 1<=мантисса<10; т.е. она может быть равна 1.3, 7.6 или

9.97. Другая часть числа с плавающей точкой - это порядок,

степень, в которую нужно возвести 10 перед умножением его на

мантиссу. То есть 9.3*107 имеет мантиссу 9.3 и порядок 7. Если

основание системы счисления определено раз и навсегда, в нашем

случае это 10, то для восстановления первоначального числа должны

быть заданы только два числа - мантисса и порядок.

Представление с плавающей точкой позволяет записявать в

компактном виде как очень большие (например, 1.234*1085) так и

очень малые (1.234*10-85) числа. Чтобы записать те же числа без

использования степени десяти, потребовались бы длинные строки,

состоящие из нулей.

Двоичные числа с плавающей точкой изображаются аналогично

десятичным; отличие заключается в том, что основание системы

счисления здесь 2, а не 10. Мантисса имеет значение 1<=мантисса<2,

а порядок показывает степень 2. То есть число 1.101*10100 в

двоичной форме означает, что мантисса 1.101 умножается на 24, или

16. Значение мантиссы определяется таким же позиционным способом,

как и для десятичного числа, за исключением того, что основание

теперь равно 2. Позиции, находящиеся справа от двоичной точки,

представляют отрицательные степени двойки. Таблица на Фиг. 7.4

показывает значения первых пяти позиций.

Теперь мы можем вычислить десятичное значение числа из примера:

1.101b=1+1/2+1/8=15/8=1.625

10**100b=2**4=16

1.101*10**100B = (1+5/8)*16 = 26

Кроме того, мы могли бы вычислить это значение точно так же,

как в случае десятичных чисел. Значение порядка показывает, на

сколько позиций надо сдвинуть двоичную точку. В этом случае,

поскольку значение порядка равно 4, двоичную точку надо сдвинуть на

четыре позиции вправо. Поэтому

1.101*10**100B=11010B=26

Оба метода правильные и дают идентичные результаты. В первом

случае вычисляется мантисса, затем она умножается на основание,

возведенное в степень порядка; во втором случае сначала выполняется

умножение, а затем вычисляется мантисса.

Двоичное значение Десятичное значение

-------------------------------------------

0.1 1/2

0.01 1/4

0.001 1/8

0.0001 1/16

0.00001 1/32

----------------------------------------- Фиг. 7.4 Отрицательные

степени двух

Как же эти числа представляются в ЭВМ? Место, отводимое для

числа с плавающей точкой, делится на два поля. Одно поле содержит

знак и значение мантиссы, а другое содержит знак и значение

порядка. Размер поля мантиссы определяет точность представления

числа. Чем большее число бит отводится в ЭВМ под поле мантиссы,

тем выше точность. Например, десятичная мантисса 1.234 более

точная, чем мантисса 1.2. Две дополнительные цифры мантиссы дают

возможность более точно передавать значение числа.

Для того, чтобы сохранить максимальную точность, вычислительные

машины почти всегда хранят мантиссу в нормализованном виде. Это

означает, что мантисса есть число, лежащее между 1 и 2

(1<=мантисса<2). Два соображения говорят в пользу нормализации.

Во-первых, ни один незначащий нуль не дает никакого вклада в

точность числа. (Это несправедливо для нулей, лежащих в конце

числа; мы считаем, что число 1.000 более точно, чем число 1.0).

Если в мантиссе с плавающей точкой появились незначащие нули,

точность числа падает. Во-вторых, способ хранения мантиссы с

плавающей точкой подразумевает, что двоичная точка находится на

фиксированном месте. Фактически подразумевается, что двоичная

точка следует после первой двоичной цифры. То есть нормализация

мантиссы делает единичным первый бит, помещая тем самым значение

мантиссы между 1 и 2. Для выполнения нормализации ЭВМ корректирует

порядок числа на соответствующее значение.

При нормализации встречаются исключения. Наиболее очквидное

исключение - нуль. В этом случае вся мантисса нулевая. Второе

исключение не так очевидно, и возникает в случае приближения числа

к нижней границе своего диапазона. Давайте разберемся, что это

означает.

Точно так же, как длина мантиссы числа определяет его точность,

длина поля порядка определяет диапазон числа. Поле порядка

содержит степень двойки, и чем больше бит в этом поле, тем большее

число может быть представлено.

Если под поле порядка (представленного в дополнительном коде)

отведено три двоичных цмфры, наибольшее представимое число есть

1.111 ... * 10011B. Мантисса - это число, чуть меньше двух, а

множитель мантиссы равен 23, или 8. Поэтому максимальное число

чуть меньше 16. Наименьшее ненулевое положительное число есть

1.000 ... * 10100B, или 1*2-4, или 1/16. Поскольку в числах с

плавающей точкой для представления мантиссы не используется

дополнительный код, диапазон отрицательных чисел такой же. В

случае 3-битового порядка диапазон положительных чисел от 1/16 до

16, а отрицательных - от -16 до -1/16.

Если для поля порядка используется 4 бита, наибольшее число

равно 1.111... *100111B<2*27=256. Наименьшее ненулевое

положительное число 1.000... *101000B=1*2-8=1/256. Таким образом

четыре бита порядка допускают диапазон изменения чисел от 1/256 до

256. Чем большее число бит в поле порядка, тем шире диапазон

представимых чисел.

Важно отметить, что хотя диапазон и расширяется с увеличением

числа бит порядка, точность не увеличивается. В только что

рассмотренных примерах предполагалось, что имеется 4 бита мантиссы.

Если выбрать конкретное число, попадающее в диапазон обоих

примеров, то оно будут иметь одну и ту же точность независимо от

диапазона порядка. Например, число 1.010*10001B=21/2. Увеличение

количества бит порядка не увеличивает точность числа. Размер

мантиссы есть единственное ограничение точности данного числа в

пределах системы.

Значение порядка хранится не как число, представленное в

дополнительном коде. Для упрощения вычислений значение порядка в

ЭВМ хранится в виде смещенного числа. Это означает, что к

действительному значению порядка прибавляется смещение перед

записью его в память. Смещенным значение порядка делается для

того, чтобы можно было сравнивать значения порядка с помощью

обычной операции сравнения чисел с фиксированной точкой без знака.

В частности, это полезно при сравнении двух чисел с плавающей

точкой. Значения порядка и мантиссы записываются в одном элементе

данных, причем порядок записывается перед мантиссой. В случае

смещенного значения порыдка программа может сравнивать числа

побитно, начиная со старших позиций. Первое же неравенство

показывает соотношение чисел, и больше не нужно учитывать никакие

части чисел. Значение смещения определяется размером поля порядка.

Давайте это рассмотрим на примере форматов данных сопроцессора

8087.

В сопроцессоре 8087 формат короткого действительного числа

использует 32 бита, восемь из которых содержат значение порядка;

таким образом, оно может находиться в пределах от - 128 до 127.

Однако значение -128 зарезервировано для обозначения некоторого

специального, а именно, неопределенного числа, так что значения

порядка находятся в диапазоне от -127 до 127. Значение порядка -

это не число, представленное в дополнительном коде. Сопроцессор

8087 прибавляет значение смещения 127 к значению порядка

действительного числа перед тем, как его запомнить.

Значение порядка Хранимое значение

---------------------------------------

-127 000H

-1 07EH

0 07FH

1 080H

127 0FEH

------------------------------------- Фиг. 7.5 Хранение порядка

В таблице на Фиг. 7.5 показаны некоторые возможные значения

порядка и его запоминаемое значение. Как видно из рисунка,

смещение меняет содержимое поля порядка так, чтобы наименьшему

значению порядка соответствовало наименьшее действительное число;

аналогично, наибольшему значению порядка соответствовало наибольшее

число. Если два числа с плавающей точкой различаются значением

порядка, простое сравнение полей порядка упорядочивает оюа числа.

Поскольку такое сравнение можно продолжать и дальше с полями

мантисс, программа может легко сравнить два числа с плавающей

точкой. И вся эта простота - только благодаря смещению порядка.


Форматы действительных чисел

Существует три формата чисел с плавающей точкой, поддерживаемых

сопроцессором 8087. Эти форматы показаны нп Фиг. 7.1 и 7.2. На

Фиг. 7.1 показана логическая структура чисел, а на Фиг. 7.2 показано

расположение отдельных частей числа, когда оно записывается в

память.

Короткие и длинные форматы данных соответствуют предложенному

ИИЭР стандарту для представления чисел с плавающей точкой.

Короткое действительное число занимает 32 бита, или четыре байта

памяти. Этот формат часто называют числом с плавающей тоской

обычной точности. Его мантисса содержит 23 бита, что по точности

приблизительно соответствует шести - семи десятичным цифрам. То

есть семизначное десятичное число по точности примерно

соответствует 23-битовому двоичному числу. Восьмибитовое поле

порыдка имеет значение смещения 127, или 07FH. Значение порядка

лежит в диапазоне от -127 до 127, что приблизительно соответствует

диапазону от 10**-38 до 10**38. Оставшийся бит определяет знак

всего числа. Заметим, что внутри чисоа с плавающей точкой есть два

знаковых бита. Один из них - знак порядка, содержащийся внутри

поля порядка (модифицирующийся смещением). Другой знак показывает,

что отрицательно или положительно само число.

Длинный действительный формат числа занимает 64 бита, или

восемь байтов памяти. Это число удвоенной точности имет 52-

битовую мантиссу, что по точности соответствует примеоно 15- 16

десятичным цифрам. Одиннадцатибитовый порядок имеет диапазон от

2**-1023 до 2**1023 и значение смещения 1023, или 03FFH. В десятичной

форме это соответствует диапазону от 10**-307 до 10**307.

Сопроцессор 8087 всегда хранит в памяти длинные и короткие

действительные числа в нормализованном виде. В это означает, что

первый бит мантиссы всегда единичен; и поэтому хранить его

бессмысленно, но всегда подразумевается, что он присутствует, как

показано на Фиг. 7.2. Под изображением расположения данных длинных

и коротких действительных чисел показана 1 перед двоичной точкой.

Этот разряд отсутствует в памяти, но известно, что он на самом деле

существует.

Заметим теперь, что все шесть форматов данных сопроцессора

8087, которые были рассмотрены - четыре целых и два действительных

- существуют только за пределами процессора. То есть сопроцессор

8087 переводит данные в эти форматы только тогда, когда записывает

их в память; кроме того, он может читать данные, записанные в

память в этих форматах. В сопроцессоре 8087 данные всех типов

хранятся в седьмом формате - промежуточном, или временном

действительном. Это означает, что программа, в которой вы

работаете с сопроцессором 8087, использует преимущества

расширенного диапазона и точности временного действительного числа

только тогда, когда данные обрабатываются внутри процессора 8086.

Другие форматы данных существуют для удобства их хранения и

представления вне процессора.

Временное действительное число является наиболее точным и имеет

большой диапазон. Оно представляется 80 битами или 10 байтами.

Его мантисса имеет длину 64 бита, это дает точность, эквивалентную

примерно 19 десятичным цифрам. Мантисса в общем случае

нормализована, но в некоторых случаях может оказаться

денормализованной. По этой причине временный действительный формат

уже не пожразумевает, что старший бит мантиссы равен 1. На Фиг. 7.2

старшая нормализованная единица показана явно частью мантиссы, а не

чем-то подразумеваемым. Пятнадцатибитовое поле порядка смещается

значением 16383, или 03FFFH. Такой порядок допускает диапазон

изменения чисел от 2**-16383 до 2**16383, или примерно от 10**-4932 до

10**4932. Поскольку сопроцессор 8087 может денормализовать мантиссу

временного действительного числа, нижняя граница его расширяется

еще дальше. Так как число денормализовано, в мантиссе есть

незначащие нули, и это позволяет изобразить даже меньшее число, чем

допускает диапазон значений порядка. Например, давайте снова

рассмотрим простую форму числа с плавающей точкой, содержащую

3-битовое поле порядка и 4-битовую матиссу. Наименьшее

положительное число, которое здесь может быть представлено, есть

1.000*10000B=1*2**-3=1/8

В этом примере подразумевается: что значение порядка смещено

на значение 3. Если теперь мя денормализуем мантиссу, то сможем

представить даже меньшее число, например

0.100*10000B=1/2*2**-3=1/16

а наименьшее положительное число, которое можно представить

денормализацией мантиссы есть

0.001*10000B=1/8*2**-3=1/64

Денормализация мантиссы расширяет нижнюю границу диапазона

чисел.

Этот дополнительный диапазон дается не даром. Поскольку мы

ввели незначащие нули, точность мантиссы уменьшилась. Временный

действительный формат числа дает возможность пожертвовать точностью

ради расширения диапазона числа, когда это необходимо. Такая

возможность требуется в первую очередь на промежуточных шагах

длительных вычислений. Иногда прикладная программа вычитает два

примерно одинаковых числа перед выполнением другой операции, и

результат много меньше, чем любое из исходных чисел. Эта разность

может оказаться очень важной. В таких специальных случаях

вычисления можно продолжать, допуская расширение диапазона

представления числа. Единственной альтернативой здесь была бы

установка нулевого результата вычитания, а в этом случае всякая

значимость теряется.


Untitled

Определение действительных чисел

Ассемблер отдельно порождает поля данных дествительных чисел с

помощью четырех-, восьми- и десятибайтовых описаний. Короткому

действительному числу здесь соответствует оператор DD; длинному

действительному числу соответствует оператор DQ; а временному

действительному чисоу - оператор DT.

К сожалению, Макроассемблер фирмы IBM не обслуживает числа с

плавающей точкой процессора 8087. Если вы укажите действительное

число в качестве операнда оператора DD или DQ, то в качестве данных

получите запутанный набор нулей и единиц. Этот формат плавающих

чисел соответствует формату, используемому внутри интерпретатора

языка Бейсик, и не является форматом ИИЭР, используемым в

сопроцессоре 8087. Поэтому программа для сопроцессора 8087 должна

использовать несколько иной метод порождения действительных чисел.

Существует два метода, которые можно использовать для

построения действительных чисел. Первый метод - ручное

ассемблирование. Вы выписываете действительное число в праивльном

формате с помощью бумаги и ручки. Эта операция содержит:

тщательный анализ числа, преобразование десятичного числа в

двоичную форму, разделение порядка и мантиссы, смещение порядка, и

наконец, преобразование результата в двоичную или шестнадцатеричную

систему. Это реальный метод, но он требует изрядного опыта и

огромных затрат времени программиста. Второй метод порожления

действительных чисел - косвенный метод, требующий большого

количества машинного времени, но гораздо меньших затрат времени

программиста. Здесь вы записываете действительное число в виде

произведения ил частного двух или более целых. Ассемблер может

непосредственно порождать целые числа с помощью команд DW, DD или

DQ. Сразу же после инициализации микросхемы 8087 программа может

построить необходимые ей действительные числа, выполняя необходимые

умножения и деления. Например, в программе нужна константа с

плавающей точкой 1.234*105. Сформировать такую константу в целом

представлении программист сможет легко, используя следующий оператор

DD 123400

Заметим, что число слишком велико для представления оператором

DW, но удовлетворяет ограничениям 32-битовых целых чисел.

Но предположим, что нужно построить число 1.234*10**-5, которое

очень мало. В этом случае в программе потребуется два целых числа:

одно целое число соответствует значению мантиссы, а другое

соответствует десяти в степени порядка. Можно воспользоваться

следующими определениями

DW 1234

DD 100000000

чтобы построить необходимое число. Программист делит дробную

часть числа 1234 на значение 10**8 и получает нужное число.

При использовании такого подхода возникают сложности в работе с

очень большими или малыми числами. Эти числа требуют представления

степеней десяти, которые по значению много больше любых

представимых целых чисел. Далее приводится пример, который строит

действительный формат каждой третьей степени десяти и может помочь

в таком случае. Отложим рассмотрение этой программы до тех пор,

пока не изучим необходимые для нее команды. С помощью таких

действителтных представлений степеней 10 записать числа от 1036 до

10-24 будет достаточно легко. Программа может разбить число на

целую часть и степень десяти, а умножение и деление дадут верное

значение. Микросхема 8087 также допускает и другие способы

выполнения таких операций масштабирования, но методы, используюшие

целые числа и степени 10, наиболее просты, особенно для людей,

работающих с сопроцессором 8087 впервые.


Untitled

Модель программирования 8087

Хотя физически микросхема 8087 представляет собой отдельный

процессор, его нужно рассматривать, как расширитель микропроцессора

8088, добавляющий к нему новые возможности. Эти возможности

включают в сеьы дополнительные типы данных, дополнительные командв

и регистры. Рисунок 7.6 иллюстрирует программную модель

сопроцессора 8087. Его регистры показаны вместе с регистрами всей

системы программирования.

Регистровый стек

Сопроцессор 8087 имеет четыре регистра специального назначенмя

и восьмирегистровый стек для арифметических операндов. Этот

регистровый стек лежит в основе работы сопроцессора 8087, и поэтому

мы прежде всего рассмотрим именно его.

Регистровый стек процессора числовой обработки состоит из

восьми позиций, каждая из которых имеет ширину 80 бит. Сороцессор

8087 запоминает все объекты во временном действительном формате,

независимо от того, как они представлены в памяти системы;

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і ПОЛЕ ДАННЫХ ПОЛЕ і

і 79 78 64 63 0 1 0 і

іЪДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДїі

ііЗНАК і ПОРЯДОК і МАНТИССА і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі

іі і і і і іі

іАДДДДДДБДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ АДДДДДДЩі

і 15 0 і

і ЪДДДДДДДДДДДДДДДДДДДДДДї і

і іУПРАВЛЯЮЩИЙ РЕГИСТР і і

і ГДДДДДДДДДДДДДДДДДДДДДДґ і

і іРЕГИСТР СОСТОЯНИЯ і і

і ГДДДДДДДДДДДДДДДДДДДДДДґ і

і і і і

і ГД УКАЗАТЕЛЬ КОМАНД Дґ і

і і і і

і ГДДДДДДДДДДДДДДДДДДДДДДґ і

і і і і

і ГД УКАЗАТЕЛЬ ДАННЫХ ДДґ і

і і і і

і АДДДДДДДДДДДДДДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Фиг. 7.6 Регистровая труктура микросхемы INTEL 8087

сопроцессор 8087 преобразует целые и действительные числа во

внутренний формат. Как мы уже видели, такое представление

допускает очень высокую точность и большой диапазон. В случае

целых чисел сопроцессор 8087 получает точные результаты вплоть до

значения 2**64. Внутренний формат с плавающей точкой малоинтересен

для тех программистов, которые обрабатывают целые числа с помощью

микросхемы 8087.

Регистры сопроцессора 8087 функционируют как обычный стек,

такой же, как и стек микропроцессора 8088. Но у этого стека

имеется ограниченное число позиций - только вомесь. Процессор 8087

имеет еще один регистр, труднодоступный для программиста. Этот

регистр представляет собой слово, содержащее "этикетки" каждой

позиции стека. Такой регистр позволяет сопроцессору 8087

отслеживать, какие из позиций стека используются, а какие свободны.

Любая попытка помещения объекта в стек на уже занятую позицию

приводит к врзникновению особой ситуации - недействительной

операции. Ниже будут обсуждены особые ситуации, и то, как

программа может бороться с проблемой переполнения стека.

Программа может заносить данные в стек сопроцессора 8087 с

помощью команды загрузки, и все команды загрузки помешают данные в

вершину стека. Если число в главной памяти записано не во

временном действительном формате, микросхема 8087 преобразует его в

80-битовое представление во время выполнения команды загрузки.

Аналогично команды записи извлекают значение из стека микросхемы

8087 и помещают их в главную память, а если необходимо

преобразование формата данных, сопроцессор 8087 выполняет его как

часть операции записи. Некоторые формы операции записи оставляют

вершину стека нетронутой для дальнейших действий.

После того, как программа поместила данные в стек сопроцессора

8087, они могут быть использованы в любой вычислительной команде.

Вычислительные команды процессора 8087 допускают как действия между

регистрами, так и действия между памятью и регистрами. Точно так

же, как и в случае микропроцессора 8088, из любых двух операндов

арифметической операции один должен находиться в регистре. У

сопроцессора 8087 один из операндов должен быть всегда верхним

элементом стека, а другой операнд может быть взят их памяти, либо

из стека регистров. Стек регистров сопроцессора 8087 всегда должен

быть приемником результата любой арифметической операции.

Непосредственно записать результат в память той же командой,

которая выполнила вычисления, процессор числовой обработки не

может. Чтобы переслать операнд обратно в память, необходима

отдельная команда записи (или команда извлечения из стека с записью

в память). Некоторые арифметические команды извлекают верхний

элемент стека и сбрасывают его, не записывая в память.


Untitled

Управляющее слово

Сопроцессор 8087 содержит два управляющих регистра. Один

регистр нужен для записи управляющего слова, другой - для

считывания слова состояния. Управляющий регистр позволяет

программисту задать режим рабоиы микросхемы 8087, далее о

возможностях некоторых из них будет лишь упомянуто; рассомтрение

всех возможных вариаций управляющего слова выходит за пределя этой

книги. Структура управляющего регистра показана на Фиг. 7.7.

Управляющее слово позволяет выбрать один из двух различных

типов бесконечности. По умолчанию способом замыкания числовой

системы является проективное замыкание, при котором сопроцессор

8087 трактует положительную, и отрицательную бесконечности как

единую бесконечность без знака. Другой способ - аффинное

замыкание, в которое входят и положительная, и отрицательная

бесконечности. Хотя может показаться, что проективное замыкание

теряет информацию, оно никогда не приводит к бессмысленным

результатам. Аффинное замыкание в программе необходимо

использовать только тогда, когда неоюходима дополнительная

информация и когда программа написана для того, чтобы иметь дело с

(возможно) неверными результатами.

Сопроцессор 8087 позволяет выбрать один из четырех способов

округления чисел. Округление возникает тогда, когда результат

15 7 0

ЪДДДДДДДДДДДВДДДВДДДДДДДВДДДДДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДДї

і іIC і RC і PC іIEMі іPM іUM іOM іZM іDM іIM і

АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДДЩ

ДДДДДВДДДДД і ДДДВДДД ДДВДД і і і і і і і і МАСКИ ИСКЛЮЧЕНИЯ (1=МАСКИРУЕТСЯ)

і і і і і і і і і і і АД НЕВЕРНАЯ ОПРЕАЦИЯ

і і і і і і і і і і АДДДДДД НЕНОРМАЛИЗОВАННЫЙ ОПЕРАНД

і і і і і і і і і АДДДДДДДДДД ДЕЛЕНИЕ НА НОЛЬ

і і і і і і і і АДДДДДДДДДДДДДД ПЕРЕПОЛНЕНИЕ

і і і і і і і АДДДДДДДДДДДДДДДДДД ИСЧЕЗАНИЕ ПОРЯДКА

і і і і і і АДДДДДДДДДДДДДДДДДДДДДД ТОЧНОСТЬ

і і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)

і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДМАСКА РАЗБЛОКИРОВКИ ПРЕРЫВАНИЙ (1)

і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ ТОЧНОСТЬЮ (2)

і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ ОКРУГЛЕНИЕМ (3)

і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ НЕОПРЕДЕЛЕННОСТЬЮ (4)

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)

(1) Маска разблоктровки прерываний:

0 = Разблокировка

1 = Прерывания заблокированы (маскированы)

(2) Управление точностью:

00 = 24 бита

01 = (зарезервировано)

10 = 53 бита

11 = 64 бита

(3) Управление округлением:

00 = Округление до ближайшего или четного

01 = Округление в меньшую сторону

10 = Округление в болюшую сторону

11 = Обрезание к нулю

(4) Управление неопределенностью:

0 = Прожективная

1 = Афинитивная

 Фиг. 7.7 Формат управляющего слова INTEL 8087

требует большей точности, чем позволяет представление чисел. Метод

округления определяет, какое число выбирается в качестве

результата. Методы округления включают: выбор следующего большего

числа в системе представления чисел, следующего меньшего числа,

округление в направлении нуля, и округление в направлении четного

числа. Сопроцессор 8087 также дает возможность программе не

использовать полностью 64-битовую точность временного

действительного формата ИИЭР. Эта возможность не уменьшает время

выполнения команд и не должна выполняться только для того, чтобы

сделать команды процессора 8087 совместимыми с некоторыми

существовавшими ранее процедурами обработки.

Процессор 8087 может регистрировать множество ситуаций

появления ошибки, известных как особые ситуации; он также может

возбуждать прерывания, чтобы сообщить об этих ситуациях. В

управляющем слове имеется группа бит, которая позволяет

программисту определить, какие особые ситуации будут приводить к

прерываниям, а какие будут обрабатываться другими способами. Эти

биты известны как маски прерываний, так как они могут маскировать,

или предотвращать, возникновение прерываний.

Необходимости разрешать прерывание в каждой из этих ситуаций

нет. Сопроцессор 8087 имеет широкие возможности по обработке

особых ситуаций, встроенных в него самого. Для любой возможной

ситуации в микросхеме 8087 имеется способ обработки ее по

умолчанию. Например, деление на нуль вызывает прерывание по особой

ситуации, если она разрешена (немаскирована). Однако если

программист не разрешил это прерывание, сопроцессор 8087 использует

значение бесконечности, как результат деления на нуль. Это

значение распространится через все оставшиеся вычисления, давая

результат, непосредственно показывающий, что во время работы

возникла ошибка некоторого вида.

Иногда в какой-то из ваших программ, использующих плавающую

точку, может оказаться необходимым обрабатывать особую ситуацию

сразу же, как она возникает; и тогда у вас возникает желание

заменить обработчик прерываний вашей собственной подпрограммой,

обрабатывающей особые ситуации. В IBM PC вывод прерывания по

особой ситуации от микросхемы 8087 подключен ко входу

немаскируемого прерывания NMI (Non Maskable Interrupt). Это то же

самое прерывание, которое сигнализирует об ошибке четности.

В качестве примера особой ситуации рассмотрим упомянутое выше

переполнение стека. Если программа помещает в стек девятый объект,

сопроцессор 8087 отвечает прерыванием по особой ситуации. Если это

прерывание не разрешено, сопроцессор 8087 отмечает операцию как

недействительную и дает результат со специальным значением,

известным как NAN (Not A Number, не число). Если вы собираетесь

выполнять сложные вычисления, требующие более восьми позиций стека,

то можете выгодно использовать прерывание по этой особой ситуации.

Когда возникает переполнение стека, обработчик этого прерывания

может удалить нижние элементы стека, используя команды записи.

Затем программа освобождает эти позиции стека для дальнейшего

использования. Существует также и соответствующее прерывание по

исчерпыванию стека, которое возникает при использовании пустой

позиции стека; подпрограмма обработки прерывания может обрабатывать

и эту ситуацию: она может восстановить прежнее состояние стека из

области сохранения.

Далее мы увидим, что микросхема 8087 содержит информацию о

своем состоянии, что делает возможным обработать особые ситуации в

обработчике прерываний. Микросхема 8087 имеет исчерпывающую

информацию о команде, которая вызвала особую ситуацию.


Untitled

Слово состояния

Слово состояния микросхемы 8087 содержит текущее состояние

процессора. Расположение отдельных битов слова состояния показано

на Фиг. 7.8. В слове состояния имеются биты, показывающие особые

ситуации, так что обработчик прерывания сможет определить суть

возникшей ситуации. Также в слове состояния имеется бит,

показывающий, занят сопроцессор или нет. Это тот же самый бит,

который выведен наружу для синхронизации с микропроцессором 8088.

Слово состояния содержит и указатель на текущую вершину стека

внутри набора регистров процессора 8087.

15 7 0

ЪДДДВДДДВДДДДДДДДДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї

і B іC3 і ST іC2 і C1іC0 іIR і іPE іUE іOE іZE іDE іIE і

АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ

і і ДДДДДВДДДДД ДДДДДВДДДДД і і і і і і і і ФЛАГИ ИСКЛЮЧИТЕЛЬНЫХ СОСТОЯНИЙ (1=ЕСТЬ)

і і і і і і і і і і і АД НЕВЕРНАЯ ОПЕРАЦИЯ

і і і і і і і і і і АДДДДДД НЕНОРМАЛИЗОВАННЫЙ ОПЕРАНД

і і і і і і і і і АДДДДДДДДДД ДЕЛЕНИЕ НА НОЛЬ

і і і і і і і і АДДДДДДДДДДДДДД ПЕРЕПОЛНЕНИЕ

і і і і і і і АДДДДДДДДДДДДДДДДДД ИСЧЕЗАНИЕ ПОРЯДКА

і і і і і і АДДДДДДДДДДДДДДДДДДДДДД ТОЧНОСТЬ

і і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)

і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЗАПРОС НА ПРЕРЫВАНИЕ

і АДДДДДДДЕДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДСОД УСЛОВИЯ (1)

і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУКАЗАТЕЛЬ ВЕРШИНЫ СТЕКА (2)

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЗАНЯТО

(1) См. описание команды сравнения, тестирования, опроса, и остатка в разделе

S.7 для прерывания по коду состояния

(2) Значения ST:

000 = Вершина стека - регистр 0

001 = Вершина стека - регистр 1

*

*

111 = Вершина стека - регистр 7

 Фиг. 7.8 Формат слова состояния INTEL 8087

Вероятно, наиболее используемая часть слова состояния

микросхемы 8087 - это поле кода условия. В слове состояния есть

четыре бита, которые могут устанавливаться командами сопроцессора

8087. Два из этих битов кода условия непосредственно соответствуют

флагам переноса и нуля микропроцессора 8088; фактически, они

расположены на тех же позициях в старшем байте слова состояния

процессора 8087. Преимущества такого расположения этих бит можно

использовать, записав слово состояния в память; затем загрузить

старший байт слова состояния в регистр AH, и с помощью команды SAHF

устанавливаете флаги переноса и нуля в соответствии с результатом

операции сравнения в сопроцессоре 8087. Так как все числа в

сопроцессоре 8087 - действительные со знаком, этих двух флагов

достаточно для сравнения любых двух чисел. Далее мы будем иметь

дело с примерами, которые используют слово состояния для сравнения

чисел. Оставшиеся два бита регистра кода условия используются

специальной командой процессора 8087 для проверки появления любого

из чисел, соответствующих специальным условиям, которые известны

сопроцессору 8087. Поскольку многие из этих чисел требуют

индивидуальных способов обработки, регистр кода условия

предоставляет способ распознавания таких чисел.


Untitled

Набор команд сопроцессора 8087

Команды сопроцессора 8087 должны рассматриваться как расширение

набора команд микропроцессора 8088, т.е. микросхема 8087 добавляет

команды к общему набору. Адресация памяти выполняется так же, как

и в микропроцессоре 8088. Как мы уже видели ранее, так происходит

потому, что в действительности микропроцессор 8088 сам порождает

адреса памяти, а сопроцессор 8087 только выполняет требуемые

опреции над данными.

Сороцессор 8087 имеет набор регистров для чисел с плавающей

точкой, организованный в виде стека. Вершину стека микросхема 8087

локализует с помощью 3=битового указателя, содержащегося в слове

состояния. Прочитав слово состояния, вы сможете узнать, какой из

восьми регистров является текущей вершиной стека; но, как будет

видно далее, эта информация в программе требуется редко.

Вся адресация регистрового стека микросхемы 8087 делается

относительно текущей вершины стека. Мнемонически на ассемблере

вершина стека называется ST(0) или просто ST. Элемент, лежащий

прямо под вершиной стека, называется ST(1), второй элемент - ST(2),

и так далее, вплоть до последнего элемента, ST(7). Для простоты

использования мы будем опускать скобки в обозначениях элементов

стека, используя ST0, ST1, ST2 и так далее.

А теперь давайте посмотрим, как программа может добраться к

элементам стека. Команда

FADD ST0, ST3

складывает вершину стека и четвертый его элемент, помещая

результат в вершину стека. На Фиг. 7.9а показаны действия этой

команды. Ссылка на регистры ST0 и ST3 жестко связана с указателем

стека. На рисунке складываются значения A и D, а результат A + D

замещает значение A, так как оно лежит в вершине стека. На

Фиг. 7.9б до выполнения той же самой команды в стек был помещен

элемент E. Команда по=прежнему складывает содержимое вершины стека

с его четвертым элементом; но, как показано на Фиг. 7.9б, она теперь

складывает значения E и C, замещая результатом E + C зеачение E. В

результате помещения элемента E в стек перераспределения операндов

внутри сопроцессора 8087 не произошло, но произошло их перемещение

по отношению к вершине стека. То есть элемент ST3 всегда является

четвертым элементом стека, независимо от того, где расположен

указатель стека.

Мы можем разбить команды сопроцессора 8087 на три широкие

группы. В первой группе находятся пересылки данных - загрузки и

записи данных в сопроцессор 8087 и из него. Вторая группа -

управление сопроцессором, команды, которые служат для нужд

внутреннего сервиса 8087. В третьей группе команд сосредоточены

все возможности сопроцессора 8087, это команды числовой обработки.

Далее мы рассмотрим каждую группу детальнее. Этот текст не

содержит исчерпывающего описания команд микросхемы 8087. Мы будем

использовать примеры, чтобы с их помощью сообщить достаточное

количество информации о значении той или иной команды, а

рассмотрение всех вычислительных возможностей сопроцессора 8087

выходит за рамки данной книги.

ЪДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДДДДї

і і і і

і ЪДДДДДДДДДДДДї і і ЪДДДДДДДДДДДДї і

АДДґ A ГДДДД + АДДґ E ГДДДД +

ГДДДДДДДДДДДДґ і ГДДДДДДДДДДДДґ і

і B і і і A і і

ГДДДДДДДДДДДДґ і ГДДДДДДДДДДДДґ і

і C і і і B і і

ГДДДДДДДДДДДДґ і ГДДДДДДДДДДДДґ і

і D ГДДДДДЩ і C ГДДДДДЩ

ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ

і і і D і

ГДДДДДДДДДДДДґ

і і

(a) (b)

Фиг. 7.9 Действие FADD ST0,ST3


Команды пересылки данных

В группе команд пересылки данных сопроцессора 8087 имеется

всего три основных команды. Команда загрузки помещает данные в

регистровый стек 8087. Обычно эти данные читаются из памяти

системы, но команда загрузки может также извлечь число из самого

стека и заменить им вершину стека. Команда записи берет данные из

вершины стека и помещает их в память ЭВМ. Команда замены

обменивает два числа в регистровом стеке сопроцессора 8087.

На Фиг. 7.10 показан листинг ассемблера команд пересылки

данных. В текст в самом начале помещен набор макрокоманд

процессора 8087 с помощью фрагмента:

IF1

INCLUDE 87MAC.LIB

ENDIF

Эта последовательность команд помещает в текст программы

макрокоманды определения команд сопроцессора 8087 во время первого

прохода ассемблера, когда должны обрабатываться макрорасширения.

Ассемблер не читает файл макрокоманд во время второго прохода, так

как этот файл больше не нужен. В листинге ассемблера появляется

только команда ENDIF.

Первая команда пересылки данных, которую мы рассмотрим -

команда загрузки. Название всех команд сопроцессора 8087

начинается с буквы "F". Так что, чтобы загрузить число в

микросхему 8087, используется команда FLD (Floating LoaD, плавающая

загрузка). В отличие от команд микропроцессора 8088, где команда

MOV обслуживает все форматы данных, здесь существует разная

Microsoft (R) Macro Assembler Version 5.00 1/1/80 01:21:45

Фиг. 7.10 Команды пересылки сопроцессора 8087 Page 1-1

PAGE ,132

TITLE Фиг. 7.10 Команды пересылки сопроцессора 8087

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 WORD_INTEGER LABEL WORD

0000 SHORT_INTEGER LABEL DWORD

0000 LONG_INTEGER LABEL QWORD

0000 BCD_INTEGER LABEL TBYTE

0000 SHORT_REAL LABEL DWORD

0000 LONG_REAL LABEL QWORD

0000 TEMPORARY_REAL LABEL TBYTE

0000 9B DF 06 0000 R FILD WORD_INTEGER

0005 9B DB 06 0000 R FILD SHORT_INTEGER

000A 9B DF 2E 0000 R FILD LONG_INTEGER

000F 9B DF 26 0000 R FBLD BCD_INTEGER

0014 9B D9 06 0000 R FLD SHORT_REAL

0019 9B DD 06 0000 R FLD LONG_REAL

001E 9B DB 2E 0000 R FLD TEMPORARY_REAL

0023 9B D9 C2 FLD ST(2)

0026 9B DF 16 0000 R FIST WORD_INTEGER

002B 9B DB 16 0000 R FIST SHORT_INTEGER

0030 9B D9 16 0000 R FST SHORT_REAL

0035 9B DD 16 0000 R FST LONG_REAL

003A 9B DD D2 FST ST(2)

003D 9B DF 1E 0000 R FISTP WORD_INTEGER

0042 9B DB 1E 0000 R FISTP SHORT_INTEGER

0047 9B DF 3E 0000 R FISTP LONG_INTEGER

004C 9B DF 36 0000 R FBSTP BCD_INTEGER

0051 9B D9 1E 0000 R FSTP SHORT_REAL

0056 9B DD 1E 0000 R FSTP LONG_REAL

005B 9B DB 3E 0000 R FSTP TEMPORARY_REAL

0060 9B DD DA FSTP ST(2)

0063 9B D9 CA FXCH ST(2)

0066 9B D9 EE FLDZ

0069 9B D9 E8 FLD1

006C 9B D9 EB FLDPI

006F 9B D9 E9 FLDL2T

0072 9B D9 EA FLDL2E

0075 9B D9 EC FLDLG2

0078 9B D9 ED FLDLN2

007B CODE ENDS

END

Фиг. 7.10 Команды пересылки сопроцессора 8087

мнемоника для разных типов данных. Так получилось потому, что

ассемблер может различать четырехбайтовые и восьмибайтовые

операнды, но не может знать, является ли операнд действительным или

целым числом.

Всякий раз, когда операнд - целое, используется команда FILD.

Итак, FILD загружает слово (16 бит), короткое целое число (32 бита)

или длинное целое число (64 бита). Чтобы загрузить упакованное

десятичное число (80 бит), используется команда FBLD. Буква B

указывает десятичные числа. Наконец, команда FLD загружает

действительные числа. Ассемблер определяет, какой вид целого или

действительного числа вы желаете использовать.

В ассемблере для имен команд сопроцессора 8087, ссылающихся к

памяти, используется соглашение о том, что в случае целых чисел

вслед за буквой F следует буква I, в случае десятичных чисел -

буква B, и никакой буквы не следует в случае действительных чисел.

Мы увидим, что то же соглашение используется и в командах записи, и

в арифметических командах, которые указывают операнд в памяти.

Как видно из Фиг. 7.10, для каждого из семи обслуживаемых

сопроцессором 8087 типов данных существует команда загрузки.

Команда загрузки указывает поле данных в памяти, микросхема 8087

преобразует данные из их внешнего представления во временный

действительный формат. Преобразованное число помещается в стек,

увеличивая его объем на единицу. Если вы попытаетесь поместить

число в стек, который уже содержит восемь чисел, сопроцессор 8087

сообщит об особой ситуации - переполнении стека. Если программа не

обрабатывает особую ситуацию в своей собственной подпрограмме,

встроенный обработчик особой ситуации пометит загруженное значение,

как "неопределенное". Это означает, что дальнейшие действия с этим

числом дадут неопределенные результаты. Если вы сделаете ошибку,

сопроцессор 8087 проследит за тем, чтобы она не осталась

незамеченной.

Оставшаяся разновидность команды загрузки берет один из

элементов стека и помещает его в стек. Например, команда

FLD ST0

дублирует вершину стека. После нее два верхних элемента имеют

одинаковые значения. Команда

FLD ST3

помещает копию четвертого элемента стека в стек. Заметим, что

число, которое было раньше ST3, стало теперь ST4.

Давайте посмотрим на машинный язык, в действительности

формируемый этими командами. Поскольку этот текст порождает

команды процессора 8087 с помощью макрокоманд, сравнительно легко

увидеть, откуда появляются различные части команд. Во=первых,

каждая команда начинается с байта 09BH. Это - команда WAIT. Как

вы помните, сопроцессор 8087 должен быть синхронизирован с работой

микропроцессора 8088. Если микропроцессор 8088 попытается

выполнить следующую команду сопроцессора 8087 до того, как

сопроцессор 8087 завершит текущую команду, действия микросхемы 8087

дадут неверный результат. Фактически все макрокоманды 8087

содержат команду WAIT для обеспечения синхронизации. (Команды без

синхронизации сопроцессора 8087 - это все команды управления,

обычно не требеющие ожидания результата. Эти команды можно легко

отличить так как они все начинаются с FN, где буква N означает

отсутствие синхронизации).

По макрорасширениям также можно видеть, что команды процессора

8087 формируются командами ESC. Чтобы указать адрес памяти,

команда ESC имеет два операнда. Первый определяет, какая это

команда ESC, а второй ссылается на ячейку памяти. Команда ESC

может иметь длину два, три или четыре байта, в зависимости от

размера поля индексного смещения, сопровождающего байт mod=r/m. В

комбинации с командой WAIT максимальная длина команды сопроцессора

8087 достигает пяти байт.

Команда записи имеет два варианта. Первый вариант этой команды

извлекает число с вершины стека и записывает ее в поименованную

ячейку памяти. Выполняя эту команду, сопроцессор 8087 делает

преобразование данных из временного действительного формата в

желаемую внешнюю форму. Эта команда имеет коды операций FST и

FIST. (Заметим, что здесь продолжают свое действие соглашения об

именах команд). Этой же командой вы можете занести вершину стека в

любое место внутри стека.

Вероятно вы заметили, что команда FST не допускает запись всех

возможных внешних типов данных. Допустимы лишь типы из "большой

четверки" - целое слово, короткое целое, короткое и длинное

действительные. Эта команда не поддерживает все внешниие типы

данных, потому что создатели процессора 8087 понимали, что это не

обязательно из=за свойств следующей команды.

Второй вариант команды записи, кроме записи данных, также

изменяет положение указателя стека. Команды FSTP (а также команды

FISTP и FBSTP) выполняют ту же операцию записи данных из

сопроцессора 8087 в память, но они также извлекают число из стека.

Эта разновидность команд поддерживает все внешние типы данных.

Конструкторы микросхемы 8087 кое=где экономили на командах, и

поэтому только команды FLD и FSTP поддерживают все внешние типы

данных. Все остальные команды сохранения данных в памяти работают

только с "большой четверкой" типов данных. Конструкторы понимали,

что эти четыре типа будут преобладать над всеми, и использование

других форматов может быть реализовано только командами FLD и FSTP.

Команда замены FXCH - следующая команда в группе команд

пересылки данных. Команда FXCH меняет местами содержимое вершины

стека с содержимым другого регистра стека. Эта команда может

использовать в качестве операнда только другой элемент стека.

Нельзя одной командой поменять местами содержимое вершины стека и

ячейки памяти. Эта процедура потребует несколько команд и рабочее

поле где=то в памяти. В отличие от микропроцесоора 8088,

сопроцессор 8087 может в одной команде выполнить чтение из памяти

или запись в память, но не то или другое одновременно.

Команда Константа

--------------------------

FLDZ 0

FLD1 1

FLDPI PI

FLDL2T LOG2(10)

FLDL2E LOG2(e)

FLDG2 LOG10(2)

FLDLN2 LOGe(2)

-------------------------- Фиг. 7.11 Константы 8087

Остальные команды группы команд персылки данных обслуживают

константы. Они загружают в стек заранее известные значения. Эти

константы описывают набор величин, необходимых программам при

вычислениях, и были выбраны из соображений упрощения счета

трансцендентных и тригонометрических функций. Мы используем

некоторые из этих констант в демонстрационных программах. Таблица

на Фиг. 7.11 показывает, какое именно значение загружается в элемент

ST0 в случае каждой команды. В каждом случае мнемоника команды

выбрана так, чтобы отражать значение константы.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:07

Фиг. 7.12 Команды управления сопроцессора 8087 Page 1-1

PAGE ,132

TITLE Фиг. 7.12 Команды управления сопроцессора 8087

0000 CODE SEGMENT

ASSUME CS:CODE

0000 STATUS_WORD LABEL WORD

0000 CONTROL_WORD LABEL WORD

0000 ENVIRONMENT LABEL BYTE ; Область размером 14 байт

0000 STATE LABEL BYTE ; Область размером 94 байта

0000 9B DB E3 FINIT

0003 9B DB E0 FENI

0006 9B DB E1 FDISI

0009 9B 2E: D9 2E 0000 R FLDCW CONTROL_WORD

000F 9B 2E: D9 3E 0000 R FSTCW CONTROL_WORD

0015 9B DB E2 FCLEX

0018 9B 2E: D9 36 0000 R FSTENV ENVIRONMENT

001E 9B 2E: D9 26 0000 R FLDENV ENVIRONMENT

0024 9B 2E: DD 36 0000 R FSAVE STATE

002A 9B 2E: DD 26 0000 R FRSTOR STATE

0030 9B D9 F7 FINCSTP

0033 9B D9 F6 FDECSTP

0036 9B DD C2 FFREE ST(2)

0039 9B D9 D0 FNOP

003C 9B FWAIT

003D CODE ENDS

END

Фиг. 7.12 Команды управления сопроцессора 8087


Untitled

Команды управления

Команды управления микросхемы 8087 ничего не вычисляют, однако

они необходимы для управления ее работой. На Фиг. 7.12 показан

литсинг ассемблера команд управления сопроцессора 8087.

Многие из команд управления могут выполняться без синхронизации

с микропроцессором 8088. На Фиг. 7.12 показаны эти команды в

форме, ассемблированной с использованием команды WAIT, а код

операции в поле комментариев изображает команды без команды WAIT.

Ассемблеру сообщается о том, что операция выполняется без ожидания,

с помощью символов FN в качестве первых двух символов мнемоники

команды.

На Фиг. 7.13 перечислены действия всех команд управления

сопроцессором 8087. Команды FENI, FDISI, FCLEX обслуживают особые

ситуации в сопроцессоре 8087. В управляющем регистре содержится

маска прерываний, которая выделяет те ситуации, которые могут

вызвать прерывание. Маской прерываний сопроцессора 8087 в целом

управляют команды FENI и FDISI; эти команды аналогичны

соответственно командам STI и CLI микропроцессора 8088, за

исключением того, что они управляют прерываниями только от

сопроцессора 8087. Команда FCLEX сбрасывает биты особых ситуаций

регистра состояния. Микросхема 8087 помнит все особые ситуации,

так что если последовательность команд возбудила более одного типа

ошибки, все эти ошибки будут отмечены в регистре состояния. И

команда FCLEX - единственный способ сброса этих флагов.

Команда Действие

--------------------------------------------------------

FINIT Инициализация 8087. Переустановка программ

FENI Освобождение прерываний по исключительным состояниям

FDISI Блокирование прерываний по исключительным состояниям

FLDCW Загрузка управляющего регистра 8087 из памяти

FSTCW Сохранение управляющего регистра 8087 в память

FSTSW Сохранение регистра состояния 8087 в память

FCLEX Очистка индикаторов исключительных состояний

FSTENV Сохранить оборудование 8087 в память

FLDENV Загрузить оборудование 8087 из памяти

FSAVE Сохранить состояние 8087 в память

FRSTOR Загрузить состояние 8087 из памяти

FINCSTP Увеличеть указатель вершины стека

FDECSTP Уменьшить указатель вершины стека

FFREE Освободить регистр стека

FNOP Ничего не делать

FWAIT Идентично WAIT

--------------------------------------------------------

Фиг. 7.13 Управляющие действия

Мы уже рассмотрели управляющее слово и слово состояния в

составе программной модели сопроцессора 8087. Команды управления

FLDCW, FSTCW и FSTSW загружают и сохраняют эти регистры.

Рабочая среда сопроцессора 8087 содержит все регистры

микросхемы, за исключением стека данных; рабочая среда состоит из

14 байт. Рисунок 7.14 показывает структуру рабочей среды после

того, как сопроцессор 8087 записал ее в память. Запись рабочей

среды - это обычное действие при обработке особой ситуации в

сопроцессоре 8087, так как рабочая среда содержит все данные об

особых ситуациях. Один 20=битовый адрес указывает на последнюю

команду, которую выполнил сопроцессор 8087. Другой адрес указывает

последнюю из вызывавшихся ячеек данных. Код последней

выполнявшейся сопроцессором 8087 команды тоже входит в рабочую

среду.

ВОЗРАСТАНИ АДРЕСОВ

ДДДДДДДДДДДДДДДДДДДДДї

15 0 і

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і

і УПРАВЛЯЮЩЕЕ СЛОВО і+0 і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і СЛОВО СОСТОЯНИЯ і+2 і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і СЛОВО ПРИЗНАКА і+4 і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і IP15-0 і+6

УКАЗАТЕЛЬ ГДДДДДДДДДДДВДВДДДДДДДДДДДДДДДДДґ

КОМАНДЫ і IP19-16 і0і КОД ОПЕРАЦИИ і+8

ГДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДґ

і OP15-0 і+10

УКАЗАТЕЛЬ ГДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДґ

ОПЕРАНДА і OP19-16 і 0 і+12

АДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ

Фиг. 7.14 Рабочая среда 8087

Состояние микросхемы 8087 - это рабочая среда вместе с

регистрами данных. Так как в сопроцессоре 8087 имеется восемь

10=байтовых регистров, состояние содержит 94 байта. Рисунок 7.15

иллюстрирует структуру состояния сопроцессора 8087, записанного в

память. Структура состояния идентична рабочей среде с добавленным

в конце регистровым стеком. Программа может записать состояние

сопроцессора 8087, когда происходит переключение задач, или если в

обработчике прерываний потребуется использование сопроцессора 8087.

Когда ранее выполнявшаяся задача снова получает управление,

состояние может быть программно восстановлено.

Две команды - FINCSTP и FDECSTP - взаимодействуют с указателем

стека; они перемещают его. Все данные в регистрах остаются на

местах, т.е. слово "этикеток" не изменяется. Увеличение указателя

стека не эквивалентно извлечению данных из стека, так как

"этикетка" для этих данных все же показывает, что в регистре есть

данные. Попытка загрузить какое=либо число в такой стек приведет к

появлению особой ситуации - переполнению стека. Команда FFREE

освобождает ячейку стека, устанавливая "этикетку" для этой ячейки

так, что она показывает отсутствие данных в ячейке. Но команда

FFREE не меняет указатель стека, и если вам просто нужно выбросить

верхний элемент из стека, то в большинстве случаев это легче

сделать с помощью арифметической команды.

A

ВОЗРАСТАНИЕ АДРЕСОВ

ДДДДДДДДДДДДДДДДДДДДДї

15 0 і

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і

і УПРАВЛЯЮЩЕЕ СЛОВО і+0 і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і СЛОВО СОСТОЯНИЯ і+2 і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і СЛОВО ПРИЗНАКА і+4 і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і IP15-0 і+6

УКАЗАТЕЛЬ ГДДДДДДДДДДДВДВДДДДДДДДДДДДДДДДДґ

КОМАНДЫ і IP19-16 і0і КОД ОПЕРАЦИИ і+8

ГДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДґ

і OP15-0 і+10

УКАЗАТЕЛЬ ГДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДґ

ОПЕРАНДА і OP19-16 і 0 і+12

ГДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 15-0 і+14

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 31-16 і+16

ЭЛЕМЕНТ ВЕРШИ-ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

НЫ СТЕКА:ST і МАНТИССА 47-32 і+18

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 63-48 і+20

ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

іsі ПОРЯДОК 14-0 і+22

ГДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 15-0 і+24

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 31-16 і+26

СЛЕДУЮЩИЙ ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

ЭЛЕМЕНТ СТЕКА і МАНТИССА 47-32 і+28

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 63-48 і+30

ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

іsі ПОРЯДОК 14-0 і+32

ГДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

... . . . . . . . ...

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 15-0 і+84

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 31-16 і+86

ПОСЛЕДНИЙ ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

ЭЛЕМЕНТ СТЕКАі МАНТИССА 47-32 і+88

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МАНТИССА 63-48 і+90

ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

іsі ПОРЯДОК 14-0 і+92

АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

ПРИМЕЧАНИЯ:

S = Знак

Бит 0 каждого поля - самый правый младший бит соответствующего

поля регистра

бит 63 мантиссы - целый бит (подразумевая двоичная точка

находится рядом с ним, справа)

Фиг. 7.15 Состояние 8087


Untitled

Арифметические команды

Настоящим "сердцем" микросхемы 8087 является блок выполнения

арифметических команд. Сопроцессор 8087 быстро и точно выполняет

Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:07:21

Фиг. 7.16 Арифметические команды сопроцессора 8087 Page 1-1

PAGE ,132

TITLE Фиг. 7.16 Арифметические команды сопроцессора 8087

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 WORD_INTEGER LABEL WORD

0000 SHORT_INTEGER LABEL DWORD

0000 SHORT_REAL LABEL DWORD

0000 LONG_REAL LABEL QWORD

0000 9B D8 C1 FADD st(0),ST(1)

0003 9B D8 C2 FADD st(0),ST(2)

0006 9B D8 C2 FADD ST(0),ST(2)

0009 9B DC C2 FADD ST(2),ST(0)

000C 9B DE 06 0000 R FIADD WORD_INTEGER

0011 9B DA 06 0000 R FIADD SHORT_INTEGER

0016 9B D8 06 0000 R FADD SHORT_REAL

001B 9B DC 06 0000 R FADD LONG_REAL

0020 9B DE C2 FADDP ST(2),ST(0)

0023 9B D8 E2 FSUB st(0),ST(2)

0026 9B DE 26 0000 R FISUB WORD_INTEGER

002B 9B DE EA FSUBP ST(2),ST(0)

002E 9B DC E2 FSUBR ST(2),ST(0)

0031 9B DA 2E 0000 R FISUBR SHORT_INTEGER

0036 9B DE E2 FSUBRP ST(2),ST(0)

0039 9B D8 0E 0000 R FMUL SHORT_REAL

003E 9B DE 0E 0000 R FIMUL WORD_INTEGER

0043 9B DE CA FMULP ST(2),ST(0)

0046 9B D8 F2 FDIV ST(0),ST(2)

0049 9B DA 36 0000 R FIDIV SHORT_INTEGER

004E 9B DE FA FDIVP ST(2),ST(0)

0051 9B D8 FA FDIVR st(0),ST(2)

0054 9B DE 3E 0000 R FIDIVR WORD_INTEGER

0059 9B DE F2 FDIVRP ST(2),ST(0)

005C CODE ENDS

END

Фиг. 7.16 Арифметические команды сопроцессора 8087

вычислительные операции, и не только четыре основных действия -

сложение, вычитание, умножение и деление, но также трансцендентные

и тригонометрические функции.

На Фиг. 7.16 показан ассемблерный листинг программы, состоящей

из некоторых команд для выполнения основных четырех действий.

Данный пример иллюстрирует только работу команды FADD, во всех

возможных комбинациях. Прежде чем рассматривать команды, разберем

возможные варианты их работы. Как видно из Фиг. 7.17, существует

пять различных методов выполнения арифметических команд. На

Фиг. 7.17а показаны пять способов использования данных в

арифметических командах. В случае 1 указывается только код

операции команды. В операции принимают участие вершина стека и

элемент ST1, а результат замещает вершину стека. Заметим, что на

Фиг. 7.17а приведены примеры для каждого случая с использованием

команды сложения. На рисунке также показаны схемы выполнения

вычислительных операций.

Случай 2 иллюстрирует операцию, выполняемую с двумя регистрами

стека сопроцессора 8087. Одним из этих регистров должна быть

вершина стека. Если вершина стека - приемник результата, ее можно

не упоминать, указав только регистр источника. Если же приемником

результата служит какой=то другой регистр, нужно указывать и

источник, и приемник.

Форма команды Пример FADD Действие

-----------------------------------------------------------------

1. Fop FADD ST0<-ST0+ST1

2. Fop STi FADD ST2 ST0<-ST0+STi

Fop ST0,STi FADD ST0,ST2 ST0<-ST0+STi

Fop STi,ST0 FADD ST2,ST0 ST2<-ST0+ST2

3. FopP STi,ST0 FADDP ST2,ST0 ST2<-ST0+ST2,Извлечь из стека

4. Fop вещ.пам. FADD ВЕЩЕСТ ST0<-ST0+ВЕЩЕСТ

5. Flop целоеПам FIADD ЦЕЛОЕ ST0<-ST0+ЦЕЛОЕ

(a)

Операция Действие

----------------------------------------------------------

ADD назначение <- назначение + источник

SUB назначение <- назначение - источник

SUBR назначение <- источник - назначение

MUL назначение <- назначение * источник

DIV назначение <- назначение / источник

DIVR назначение <- источник / назначение

----------------------------------------------------------

(b)

Фиг. 7.17 Арифметические операции (a) Комбинации данных

(b) Арифметические функции для NDP 8087

Случай 3 представляет собой стековую операцию. Команда такого

формата выполняет операцию с двумя стековыми операндами, а затем

уничтожает вершину стека. Так как данные из вершины стека

извлекаются из него после операции, она не может быть приемником

результата этой операции. Если приемник результата - элемент ST1,

то операция становится классической стековой. Классическая

стековая операция удаляет из вершины стека два верхних элемента,

использует их в требуемой операции, а затем помещает результат

назад в стек. Конечно в качестве приемника результата этой

операции может быть указан любой из регистров.

Два оставшихся варианта используют операнды в памяти. В случае

4 операнд в памяти - это короткое или длинное действительное число.

В случае 5 операнд - это короткое целое или целое слово.

Вернувшись к Фиг. 7.16 можно заметить, что в команде FADD

определены только четыре операнда в памяти. Это два целых числа -

короткое и слово, и два действительных числа - короткое и длинное.

Арифметические команды не могут непосредственно работать с

десятичным, длинным целым и временным действительным форматами

чисел, и перед использованием их в счете программа должна загрузить

такие числа в регистр.

На Фиг. 7.17 показаны шесть существующих арифметических команд.

В процессоре 8087 стандартные четыре арифметические операции

дополнены обратными операциями деления и вычитания. Поскольку

сложение и умножение коммутативны, обратных операций для них

вводить не надо; порядок же операндов у вычитания и деления

критичен. И иногда бывает, что число, находящееся в регистре

источника - не то число, которое нужно вычитать из

регистра=приемника; в этом случае вычитание правильно выполнит

обратная операция.


Команды сравнения

Как и в наборе команд микропроцессора 8088, у сопроцессора 8087

есть команды, сравнивающие два числа. Сопроцессор 8087 отбрасывает

результат сравнения, но устанавливает в соответствии с ним флаги

состояния. Перед тем как опросить флаги состояния, программа

должна считать слово состояния в память. Далее проще всего

загрузить флаги состояния в регистр AH, а затем, для легкости

проверки условия, - в регистр флагов макропроцессора 8088.

На Фиг. 7.18 показан листинг ассемблера команд сравнения

сопроцессора 8087. Так как в операции всегда участвует вершина

стека, в программе надо указать только один регистр или операнд в

памяти. После сравнения в слове состояния содержится информация о

соотношении двух чисел. Таблица на Фиг. 7.19 показывает возможные

варианты соотношений. Для отражения результата сравнения

необходимы только два бита состояния - C3 и C0; расположение C3 и

C0 в слове состояния показано на Фиг. 7.8.

На Фиг. 7.20 приведен фрагмент программы, которая сравнивает

содержимое вершины стека и слово в памяти. Переходы в этой

программе выполняются на основе результата сравнения. В одном из

четырех случаев числа нельзя сравнить. Это происходит тогда, когда

одно из чисел есть NAN (не число) или одна из форм бесконечности.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:18

Фиг. 7.18 Команды сравнения сопроцессора 8087 Page 1-1

PAGE ,132

TITLE Фиг. 7.18 Команды сравнения сопроцессора 8087

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 WORD_INTEGER LABEL WORD

0000 SHORT_INTEGER LABEL DWORD

0000 SHORT_REAL LABEL DWORD

0000 LONG_REAL LABEL QWORD

0000 9B D8 D1 FCOM

0003 9B D8 D2 FCOM ST(2)

0006 9B DE 16 0000 R FICOM WORD_INTEGER

000B 9B D8 16 0000 R FCOM SHORT_REAL

0010 9B D8 D9 FCOMP

0013 9B DA 1E 0000 R FICOMP SHORT_INTEGER

0018 9B DC 1E 0000 R FCOMP LONG_REAL

001D 9B DE D9 FCOMPP

0020 9B D9 E4 FTST

0023 9B D9 E5 FXAM

0026 CODE ENDS

END

Фиг. 7.18 Команды сравнения сопроцессора 8087

Сопроцессор 8087 помещает флаги состояния C3 и C0 в точности на те

же места, которые в регистре флагов микропроцессора 8088 занимают

флаги нуля C3 и переноса C0. Как показано на рисунке, если

программа переслала старший байт слова состояния сопроцессора 8087

в регистр флагов микропроцессора 8088, она может выполнить условный

переход с помощью непосредственной проверки состояния флагов с

использованием команды условного перехода, и не нужно выделять и

проверять отдельные биты слова состояния.

С3 С0 Порядок

--------------------------------------------------------------

0 0 ST > источник

0 1 ST < источник

1 0 ST = источник

1 1 ST и источник несравнимы

--------------------------------------------------------------

Фиг. 7.19 Порядок сравнения

На Фиг. 7.18 демонстрируется, что существуют и другие варианты

команды FCOM, в том числе и то, что для команды FCOM существует

версия FICOM необходимая для ставнения целых чисел. Команды FCOMP

и FICOMP идентичны командам FCOM и FICOM, за исключением того, что

сопроцессор 8087 извлекает из стека содержимое вершины после

выполнения операции. Это позволяет сравнить два числа с помощью

сопроцессора 8087, не беспокоясь о том, что нужно удалить из стека

первый операнд после операции.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:23

Фиг. 7.20 Условный переход Page 1-1

PAGE ,132

TITLE Фиг. 7.20 Условный переход

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 WORD_INTEGER LABEL WORD

0000 ???? STATUS_WORD DW ?

;----- Сравнивается WORD_PTR со словом на вершине стека сопроцессора 8087

0002 9B DE 16 0000 R FICOM WORD_INTEGER ; Сравнение слова с ST

0007 9B DD 3E 0000 R FSTSW STATUS_WORD ; Сохранение слова состояния 8087

000C 9B FWAIT ; Синхронизация процессора с сопроцессором

000D 8A 26 0001 R MOV AH,BYTE PTR STATUS_WORD+1

0011 9E SAHF ; Занесение флагов (C0=CF,C3=ZF)

0012 72 02 JB CONTINUE ; Проверка флага C0,переход если 0

0014 75 00 JNE ST_GREATER ; Проверка флага C3

0016 ST_EQUAL: ; Попадаем сюда,если C3=1,C0=0 -

; ... ; значения равны

0016 ST_GREATER: ; Попадаем сюда,если C3=0,C0=0 - число

; ... ; на вершине стека 8087 больше WORD_PTR

0016 CONTINUE:

0016 75 00 JNE ST_LESS ; Проверка флага C3

0018 UNORDERED: ; Попадаем сюда,если C3=1,C0=1 - невоз-

; ... ; можно определить какое из чисел больше

0018 ST_LESS: ; Попадаем сюда,если C3=0,C0=1 - число

; ... ; на вершине стека 8087 меньше WORD_PTR

0018 CODE ENDS

END

Фиг. 7.20 Условный переход

У команды FCOMPP программист не указывает никаких операндов.

Эта команда всегда сравнивает верхние два элемента стека. После

сравнения они оба исчезают из стека.

Команды сравнения с извлечением из стека обеспечивают удобный

способ очистки стека. Поскольку у сопроцессора 8087 нет команды,

удобно извлекающей операнд из стека, вместо нее можно использовать

команды сравнения с извлечением из стека. Эти команды также

изменяют и регистр состояния, и их нельзя использовать, если биты

состояния имеют значение для дальнейшей работы, но в большинстве

случаев эти команды позволяют быстро извлечь из стека один или два

операнда. Так как сопроцессор 8087 регистрирует ошибку при

переполнении стека, необходимо удалить все операнды из стека при

окончании вычислений.

Существуют две специальные команды сравнения. Команда

сравнения содержимого вершины стека с нулем FTST, с помощью которой

можно быстро определить знак содержимого вершины стека. (Результат

сравнения иллюстрируется таблицей на Фиг. 7.19, нужно только всюду в

таблице слово "источник" заменить на слово "нуль").

Команда FXAM, строго говоря, не является командой сравнения.

Хотя она и работает с содержимым вершины стека, но не сравнивает

содержимое вершины ни с одним другим числом. Скорее команда FXAM

устанавливает все четыре флага регистра состояния (от C3 до C0

включительно), показывая, какого типа число находится в вершине

стека. Так как сопроцессор 8087 может обрабатывать любые формы

чисел, а не только нормализованные числа с плавающей точкой,

команда FXAM определит, что же находится в вершине стека. На

Фиг. 7.21 показаны значения битов состояния в каждом случае.

Если при арифметической обработке вы не делаете что=либо из

ряда вон выходящее и не работаете на пределе разрядной сетки

сопроцессора 8087, то не нужно рассматривать никакие из приведенных

выше результатов команды FXAM; вы должны ожидать увидеть лишь

положительные либо отрицательные нормализованные числа или нули.

Если выясняется, что вершина стека пустая, то обычно выдается

ошибка. Программа может сделать такую проверку для контроля

параметра, переданного в вершине стека.

Остальные значения - это реакция микросхемы 8087 на ошибку.

Когда сопроцессор 8087 обнаруживает ошибку, он пытается возбудить

прерывание по особой ситуации, устанавливая соответствующие биты в

слове состояния. Однако если ситуация замаскирована с помощью

управляющего слова, сопроцессор 8087 сам реагирует на нее. Он

решает, какая реакция соответствует данной ошибке и возбуждает

появление специфического числа в регистре. Например, результат NAN

возникает, если операция некорректна, как в случае извлечения

квадратного корня из отрицательного числа. Бесконечность

появляется, если результат операции слишком велик для представления

с плавающей точкой.

C3 C2 C1 C0 Значение С3 С2 С1 С0 Значение

---------------------------------------------------------------------------------------------------

0 0 0 0 + Ненормально 1 0 0 0 + 0

0 0 0 1 + NAN 1 0 0 1 Пусто

0 0 1 0 - Ненормально 1 0 1 0 - 0

0 0 1 1 - NAN 1 0 1 1 Пусто

0 1 0 0 + Нормально 1 1 0 0 + Ненормальное

0 1 0 1 + Бесконечность 1 1 0 1 Пусто

0 1 1 0 - Нормально 1 1 1 0 - Ненормальное

0 1 1 1 - Бесконечность 1 1 1 1 Пусто

---------------------------------------------------------------------------------------------------

Фиг. 7.21 Пример интерпретации кодов состояний

Денормализованные и ненормализованные числа возникают при

достижении другого предела представления чисел с плавающей точкой.

Когда число становится настолько маленьким, что у него уже нет

смещенного порядка, оно становится денормализованным. Вместо того,

чтобы заменить число нулем, процессор 8087 записывает наименьшее

значение порядка и ненормализованную мантиссу. Это означает, что

число теряет точность, так как у его мантиссы появляются незначащие

нули. Но денормализованный результат все же точнее, чем просто

нуль. И это тот случай, при котором сопроцессор 8087, продолжая

работу, наилучшим образом реагирует на плохие условия. Пометка

числа, как денормализованного, должна насторожить вас. При этом

также устанавливается бит денормализации в регистре особых

ситуаций, и остается установленным до тех пор, пока команда FCLEX

не сбросит биты особых ситуаций. С помощью анализа флагов

программа может зарегистрировать возникновение ошибки и отнестись к

результатам с подозрением.


Untitled

Степенные и тригонометрические функции

Оставшаяся группа команд сопроцессора 8087 выполняет вычисления

сложных математических функций. Эти команды позволяют микросхеме

8087 вычислять арифметические выражения, требующие логарифмов,

экспонент и тригонометрических функций. На Фиг. 7.22 изображен

список этих команд. В этом списке нет тех операций, которые есть у

карманного калькулятора. Конструкторы процессора 8087 не смогли

реализовать все нужные функции на одном кристале, так как это

оказалось слишком сложным. Вместо этого они создали набор функций

более низкого уровня, из которых можно программно построить функции

калькулятора. Например, таких тригонометрических функций как синус

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:28

Фиг. 7.22 Арифметические команды над вершиной стека 8087 Page 1-1

PAGE ,132

TITLE Фиг. 7.22 Арифметические команды над вершиной стека 8087

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE

0000 9B D9 FA FSQRT

0003 9B D9 FD FSCALE

0006 9B D9 F8 FPREM

0009 9B D9 FC FRNDINT

000C 9B D9 F4 FXTRACT

000F 9B D9 E1 FABS

0012 9B D9 E0 FCHS

0015 9B D9 F2 FPTAN

0018 9B D9 F3 FPATAN

001B 9B D9 F0 F2XM1

001E 9B D9 F1 FYL2X

0021 9B D9 F9 FYL2XP1

0024 CODE ENDS

END

Фиг. 7.22 Арифметические команды над вершиной стека 8087

и косинус нет; вместо этих функций есть функция частичного

тангенса. Эта функция вычисляет отношение, равное тангенсу угла.

Из этого отношения программа может определить синус, косинус,

тангенс и любую другую тригонометрическую функцию. Из обратных

тригонометрических функций есть операция частичного арктангенса,

которая берет значения отношения чисел и вычисляет угол этого

тангенса. Эта частичная функция позволяет сконструировать

арксинус, арккосинус и другие функции, не имея их в явном виде.

Ниже приведен список команд этой группы с коротким комментарием

работы каждой команды. Ни у одной из этих команд не пишутся

операнды, так как все они работают с вершиной стека, и возможно,

также с элементом ST1.

FSQRT (квадратный корень)

ST квадратный корень из (ST)

ST должно быть неотрицательно.

FSCALE (масштабирование) ST <- ST * 2ST1

Эта команда необходима для возведения в степень. Другая, и

притом единственная, функция возведения в степень имеет ограничения

на значение показателя. Эта команда возводит 2 в степень, равную

целому числу. Далее будет приведен пример возведения 10 в

произвольную степень.

FPREM (частичный остаток)

ST <- ST mod ST1 (частичный)

Полностью операцию деления по заданному модулю команда FPREM не

выполняет. Эта команда за один раз уменьшает содержимое вершины

стека максимум на 264. Команда выполняет нахождение истинного

остатка и требует очень много времени для уменьшения большого числа

по очень маленькому основанию. Максимально уменьшая число в

течение каждого выполнения команды, программист может разрешить

прерывания в течение всей операции поиска остатка. Если функция не

завершена, команда FPREM устанавливает флаг C2 равным 1, а когда

завершает вычисление остатка, то устанавливает другие три флага C3,

C1 и C0 равными трем младшим битам частного. Когда команда FPREM

используется в тригонометрических примитивах для ограничения

величины угла, это оказывается необходимым для определения октанта

первоначального угла. Чтобы продемонстрировать работу этой

команды, мы далее приведем пример тригонометрических вычислений.

FRNDIN T (округление до целого)

ST <- Integer(ST)

Эта команда округляет текущее содержимое вершины стека до

целого числа. Текущее управляющее слово определяет направление

округления.

FXTRACT (извлечение)

ST <- дробная часть ST

ST1 показатель степени двойки ST

Эта команда разбивает текущую вершину стека на компоненты.

Аргументом этой функции является вершина стека. Значение

показателя степени замещает содержимое вершины стека, а затем

дробная часть аргумента помещается в стек и становится новой

вершиной стека. Действия этой команды FXTRACT обратны действиям

команды FSCALE. Если в вершине стека находится некоторое число, то

выполнение последовательно команд FXTRACT и FSCALE оставляет в

вершине стека то же число. Но команда FSCALE не удаляет из стека

показатель степени, так что теперь в стеке дополнительно окажется

еще одно число.

FABS (абсолютная величина)

ST <- абсолютное значение ST

Эта команда устанавливает у числа в вершине стека знаковый

разряю в нуль (что соответствует положительному значению).

FCHS (смена знака)

ST <- -ST

Эта команда изменяет знак у вершины стека.

Следующие команды выполняют также трансцендентные функции, как

тригонометрические, а также логарифмические и возведение в степень.

FPTAN (частичный тангенс)

ST <- X

ST1 <- Y, где Y/X = TAN(угол)

Эта команда позволяет вычислить все тригонометрические функции.

Исходное число - угол, выраженный в радианах, значение которого

должно быть в интервале 0 < угол < PI/4, - помещается в вершину

стека. Уменьшить угол до правильного значения можно с помощью

команды FPREM. Результатом является отношение Y/X, которое равно

тангенсу угла; Y замещает вершину стека, а затем в стек помещается

X. Другие тригонометрические функции можно вычислить с

использованием этих значений; например, косинус COS(угол) =

X/SQRT(X2 + Y2).

FPATAN (частичный арктангенс)

ST <- Arctan(Y/X) = Arctan(ST1/ST)

Эта функция дополняет предыдущую, FPTAN. Команда FPATAN

вычисляет угол в соответствии с отношением чисел ST1 и ST0. Она

извлекает из стека число X, а затем записывает результирующий угол

вместо числа Y в качестве новой вершины стека. Исходные значения

должны подчиняться неравенству

0 < Y < X < бесконечность

F2XM1 (два в степени X минус 1)

ST <- 2ST - 1

Эта функция выполняет возведение в степень; она возводит 2 в

степень, указанную в вершине стека. Исходное число должно

находиться в диапазоне 0 <= ST <=0.5, и чтобы вычислить два в

степени, большей 0.5, эту команду нужно использовать вместе с

командой FSCALF. С помощью команд FLD, загружающих специальные

константы, программа может возвести в заданную степень также числа,

отличные от 2, для этого используются следующие формулы:

10**X = 2**(X*Log2(10))

e**X = 2**(X*Log2(e))

Y**X = 2**(X*Log2(Y))

Далее приводится пример возведения 10 в произвольную степень.

FYL2X (Y умножить на логарифм по основанию 2 от X)

ST <- Y*Log2(X) = ST1*Log2(ST)

Эта функция выполняет операцию логарифмирования. Она берет

логарифм по основанию 2 от содержимого вершины стека и затем

умножает его на элемент ST1. Команда FYL2X извлекает из стека

число X и замещает результат числом Y. Исходные числа должны

удовлетворять следующим соотношениям:

0 < X < бесконечности и - бесконечность < Y < бесконечности.

Эта функция позволяет вычислять логарифмы и по основаниям, отличным

от 2. Следующая формула:

Logn(2) * Log2(X)

вычисляет логарифм числа X по основанию n; значение Logn2

вычисляется как 1/(Log2n).

FYL2XP1 (Y умножить на логарифм по основанию 2 от X+1)

ST <- Y*Log2(X+1) = ST1*Log2(ST+1)

Эта функция идентична функции FYL2X за исключением того, что к

X прибавляется 1. Функция FYL2XP1 накладывает более жесткие

ограничения на X, и предназначена для вычисления логарифмов чисел,

значения которых очень близки к 1. Эта функция дает наиболее

высокую точность, если

0 < ABS(X) < 1 - (корень из 2 )/2


Untitled

Примеры

Этот раздел главы с помощью примеров исполльзования наиболее

распространенных операций научит вас применять сопроцессор 8087,

предполагая, что вы этого совсем не умеете. Все примеры просты,

т.е. в них не делается попыток обработки всех возможных ошибок или

реакции на какие=либо специальные числа, с которыми может работать

микросхема 8087. Сопроцессор 8087, конечно же, имеет такие

возможности, но эти действия относятся к более сложным. Когда вы

освоите основы техники, вы сможете добавить к этим примерам

некоторые фрагменты, которые превратят их в настоящие подпрограммы

общего назначения.

Степени десяти

Первый пример - исходный текст программы на Фиг. 7.23. Эта

программа распечатывает короткое действительное представление

степеней 10 от 103 до 1039. Как мы уже видели в разделе,

посвященном представлению данных, Макроассемблер фирмы IBM не имеет

возможностей непосредственно генерировать действительные числа.

Наличие такой таблицы чисел облегчит вам представление в виде

констант степеней 10. По этой таблице вы сможете определить

шестнадцатеричное представление числа, которое нужно использовать в

программе.

Программа вычисляет только каждую третью степень 10, и

использует короткий действительный формат. Если вы работаете с

много большими числами, или вам нужна большая точность, тогда нужно

выполнить этот пример, используя длинный действительный формат

чисел, а также построить каждую степень 10. Выполните это в

качестве упражнения.

Первоочередная цель этого примера - введение в программирование

и работу на сопроцессоре 8087. Это отдельная самостоятельная

программа, предназначенная для выполнения в виде файла типа .EXE.

Прежде чем разобраться в самой программе, заметим, что в нее входит

сегмент STACK, необходимый в файле типа .EXE. Сначала в сегменте

CODE записаны поля данных, а выполнение программы начинается с

метки CALCULATE_POWER. Заглянув вперед, на оператор END, вы

увидите, что первой выполняемой командой будет команда, помеченная

CALCULATE_POWER, так как она указана в операторе END.

Первая часть программы выполняет инициализацию. Перед

загрузкой указателя на сегмент CODE в регистр DS программа помещает

в стек адрес возврата из файла типа .EXE. Затем с помощью команды

FINIT инициализируется сопроцессор 8087, что аналогично аппаратному

сбросу. Тем самым сопроцессор 8087 оказывается настроенным на

обработку особых ситуаций по умолчанию, что наилучшим образом

подходит для примеров этой книги. Команда FINIT также сбрасывает

регистровый стек сопроцессора 8087, освобождая все его восемь

позиций. Программа должна использовать команду FINIT только в

момент запуска. Команда FINIT никогда не должна быть использована

внутри подпрограммы для сопроцессора 8087.

Следующие команды загружают число 1000 в регистр ST1 и число 1

в регистр ST0. Все следующие команды сопроцессора 8087 используют

эти два регистра стека. В регистре ST0 находится текущая степень

десяти, а в регистре ST1 находится значение 103. Мы будем

использовать число в регистре ST1 для увеличения числа в регистре

ST0 после каждой итерации программы. Целая переменная POWER

содержит текущую степень 10, находящуюся в регистре ST0.

После метки POWER_LOOP элемент ST0 умножается на элемент ST1,

(в котором содержится число 1000), чтобы увеличить содержимое

регистра ST0 в 103 раз. Команда FST записывает результат в память.

Оставшаяся часть программы после метки POWER_LOOP печатает

результаты вычислений. В подпрограмме TRANSLATE шестнадцатеричный

байт преобразуется в двухбайтовую строку в коде ASCII так, что

программа может его распечатать. Текущее значение POWER (степень

десяти), а также шестнадцатеричная строка, записанная процессором

8087, преобразуются в код ASCII. Затем функция DOS печатает строку

на дисплее. Цикл POWER_LOOP продолжается до тех пор, пока

последнее напечатанное значение не станет больше 1038. Это

значение выбрано потому, что 1038 - это максимальное число, которое

может быть представлено в коротком действительном формате. Если бы

использовался длинный действительный формат чисел, это значение

было бы равно 10308. Заключительная часть Фиг. 7.23 показывает,

как выглядит результат работы этой программы на дисплее.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:33

Фиг. 7.23 Степени 10 Page 1-1

PAGE ,132

TITLE Фиг. 7.23 Степени 10

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 ???????? POWER_OF_TEN DD ? ; Область данных для 10**x,

; короткое плавающее

0004 0002[ OUTPUT_POWER DB 2 DUP (' ') ; Текстовый буфер для значения

20

]

0006 48 20 20 20 20 DB 'H ' ; степени

000B 0008[ OUTPUT_STRING DB 8 DUP (' ') ; Текстовый буфер для результата -

20 20 20 20 20

20 20 20 20

]

0053 48 0D 0A 24 DB 'H',13,10,'$' ; Конец строки

0057 00 POWER DB 0 ; Текущая степень 10

0058 03E8 THOUSAND DW 1000 ; Константа

005A 03BF CONTROL_87 DW 03BFH

005C CALCULATE_POWER PROC FAR

005C 1E PUSH DS ; Занесение в стек адреса возврата

005D B8 0000 MOV AX, 0

0060 50 PUSH AX

0061 0E PUSH CS

0062 1F POP DS

ASSUME DS:CODE ; Адресация области данных

0063 9B DB E3 FINIT ; Инициализация сопроцессора 8087

Фиг. 7.23 (а) (начало)

0066 9B DF 06 0058 R FILD THOUSAND ; Загрузка 10**3 в стек сопроцессора 8087

006B 9B D9 E8 FLD1 ; Загрузка начального значения в стек 8087

006E POWER_LOOP:

006E 9B DC 8E 0000 FMUL ST(1) ; Умножение ST(0) на ST(1)

0073 9B D9 16 0000 R FST POWER_OF_TEN ; Сохранение в памяти результата

0078 80 06 0057 R 03 ADD POWER, 3 ; Увеличение показателя степени

007D A0 0057 R MOV AL, POWER ; Выборка показателя степени

0080 8D 1E 0004 R LEA BX, OUTPUT_POWER

0084 E8 00AC R CALL TRANSLATE

0087 B9 0004 MOV CX, 4

008A 8D 1E 000B R LEA BX, OUTPUT_STRING

008E 8D 36 0003 R LEA SI, POWER_OF_TEN+3

0092 FD STD ; Установка пересылки с уменьшением адреса

0093 VALUE_OUTPUT:

0093 AC LODSB ; Выборка байта результата

0094 E8 00AC R CALL TRANSLATE ; Занесение символа в выводимую строку

0097 E2 FA LOOP VALUE_OUTPUT ; Цикл по всем байтам результата

0099 8D 16 0004 R LEA DX, OUTPUT_POWER

009D B4 09 MOV AH, 9H

009F CD 21 INT 21H

00A1 80 3E 0057 R 26 CMP POWER, 38

00A6 72 C6 JB POWER_LOOP

00A8 9B DE D9 FCOMPP ; Удаление из стека двух чисел

00AB CB RET

00AC CALCULATE_POWER ENDP

00AC TRANSLATE PROC NEAR

00AC 50 PUSH AX ; Сохранение исходного значения

00AD 51 PUSH CX

00AE B1 04 MOV CL, 4 ; Сдвиг старшей цифры выводимого числа

00B0 D2 E8 SHR AL, CL ; на место младшей цифры

00B2 59 POP CX

00B3 E8 00CB R CALL XLAT_OUTPUT ; Вывод старшей цифры выводимого числа

00B6 58 POP AX ; Восстановление младшей цифры

00B7 E8 00CB R CALL XLAT_OUTPUT ; Вывод младшей цифры выводимого числа

00BA C3 RET

00BB TRANSLATE ENDP

00BB 30 31 32 33 34 35 36 ASCII_TABLE DB '0123456789ABCDEF'

37 38 39 41 42 43 44

45 46

00CB XLAT_OUTPUT PROC NEAR

00CB 24 0F AND AL, 0FH ; Выделение младшей цифры

00CD 53 PUSH BX

00CE 8D 1E 00BB R LEA BX, ASCII_TABLE ; Адрес таблицы трансляции

00D2 D7 XLAT ASCII_TABLE ; Преобразование в символьную форму

00D3 5B POP BX

00D4 88 07 MOV [BX], AL ; Сохранение очередного символа результата

00D6 43 INC BX ; Переключение на следующий символ

00D7 C3 RET

00D8 XLAT_OUTPUT ENDP

00D8 CODE ENDS

END CALCULATE_POWER

Фиг. 7.23 (а) (продолжение)

A>PRINT10

03H 447A0000H

06H 49742400H

09H 4E6E6B28H

0CH 5368D4A5H

0FH 58635FA9H

12H 5D5E0B6BH

15H 6258D727H

18H 6753C21CH

1BH 6C4ECB8FH

1EH 7149F2CAH

21H 76453719H

24H 7B4097CEH

27H 7F800000H

Фиг. 7.23 (b)

Фиг. 7.23 (a) Степени 10; (b) Вывод процедуры степеней 10

Вам нужно посмотреть часть программы TRANSLATE, несмотря на то,

что она не использует ни одной команды сопроцессора 8087. Эта

часть - пример подготовки чисел к печати. В частности, команда

XLAT преобразует для печати шестнадцатеричную тетраду (значение от

0 до 0FH) в правильный символ кода ASCII (от 0 до F). Просто

прибавлять значение тетрады к значению 0 нельзя, так как в коде

ASCII символы от A до F не следуют непосредственно за символами от

0 до 9; преобразование прекрасно выполняет команда перекодировки.

Мы используем аналогичный метод, когда преобразуем число с

плавающей точкой в пригодную для печати десятичную форму.


Untitled

Десять в степени X

Второй пример использования сопроцессора 8087 гораздо глубже

раскрывает перед нами его работу. Этот пример - подпрограмма,

которая будет использоваться в дальнейшем. Подпрограмма

предполагает, что исходное число находится в вершине стека; после

возврата из подпрограммы в вершине стека находится число, равное

десяти в степени X. Исходный текст этой подпрограммы приведен на

Фиг. 7.24.

Сопроцессор 8087 не имеет команды возведения 10 в произвольную

степень, но мы можем возводить в любую степень двойки. Поэтому

нужжно пользоваться формулой

10**X = 2**(X*Log2(10))

Первые две команды программы формируют показатель степени двух.

Программа загружает константу Log210, а затем умножает ее на

исходное число X, давая необходимую степень 2, называемую здесь E.

Поле комментариев в примере используется для иллюстрации элементов

стека сопроцессора 8087. Символ "?" означает, что значение

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:40

Фиг. 7.24 Вычисление 10**ST Page 1-1

PAGE ,132

TITLE Фиг. 7.24 Вычисление 10**ST

0000 CODE SEGMENT PUBLIC

ASSUME CS:CODE,DS:CODE

PUBLIC TEN_TO_X

0000 ???? OLD_CW DW ?

0002 ???? NEW_CW DW ?

;--------------------------------------------

; Эта программа извлекает число с вершины стека

; сопроцессора 8087 и возводит 10 в эту степень.

; Параметры: X в ST(0)

; Результат: 10**X в ST(0)

; Эта программа использует две ячейки в стеке 8087

; плюс параметр - всего три ячейки.

;--------------------------------------------

0004 TEN_TO_X PROC NEAR

;----ST(0)------;-----ST(1)-----;--ST(2)--

; X ; ? ; ?

0004 9B D9 E9 FLDL2T ; LOG2(10) ; X ; ?

0007 9B DE C9 FMULP ST(1),ST(0) ; X*LOG2(10)=E ; ? ; ?

000A D9 3E 0000 R FNSTCW OLD_CW ;---------------;---------------;--------

000E 9B FWAIT ; Выборка текущего слова состояния

000F A1 0000 R MOV AX,OLD_CW ; Сохранение слова сотояния

0012 25 F3FF AND AX,NOT 0C00H ; Установка способа округления к минус

0015 0D 0400 OR AX,0400H ; бесконечности

0018 A3 0002 R MOV NEW_CW,AX

001B 9B D9 2E 0002 R FLDCW NEW_CW ;---------------;---------------;--------

0020 9B D9 E8 FLD1 ; 1 ; E ; ?

0023 9B D9 E0 FCHS ; -1 ; E ; ?

0026 9B D9 C1 FLD ST(1) ; E ; -1 ; E

0029 9B D9 FC FRNDINT ; INT(E) = I ; -1 ; E

002C 9B D9 2E 0000 R FLDCW OLD_CW ; ; ;

0031 9B D9 CA FXCH ST(2) ; E ; -1 ; I

0034 9B D8 E2 FSUB ST(0),ST(2) ; E - I = F ; -1 ; I

0037 9B D9 FD FSCALE ; F*2**-1 = F/2 ; -1 ; I

003A 9B D9 F0 F2XM1 ; (2**F/2)-1 ; -1 ; I

003D 9B DE E1 FSUBRP ST(1),ST(0) ; 2**F/2 ; I ; ?

0040 9B D8 8E 0000 U FMUL ST(0) ; 2**F ; I ; ?

0045 9B D9 FD FSCALE ; (2**F)*(2**I) ; I ; ?

0048 9B D9 C9 FXCH ST(1) ; I ; 2**(I+F) ; ?

004B 9B D8 D9 FCOMP ; 2**(I+F) ; ? ; ?

004E C3 RET ; 10**X ; ? ; ?

004F TEN_TO_X ENDP

004F CODE ENDS

END

Фиг. 7.24 Вычисление 10**ST

соответствующего элемента стека неопределено. В подпрограмме

используется всего три элемента стека, так что в поле комментариев

есть три колонки.

Хотя теперь мы имеем нужную степень двух, у сопроцессора 8087

отсутствует команда, завершившая бы всю работу за один шаг.

Команда F2XM1 возводит 2 в степень X, но только если X меньше или

равен 1/2. Это означает, что степень E мы должны разделить на

целую и дробную части; затем команда FSCALE сможет возвести 2 в

целую степень, а команда F2XM1 обработает дробную часть.

Перед разделением E на две части программа выполняет некоторые

вспомогательные действия. Эти действия - команды сопроцессора

8087, которые читают управляющее слово и устанавливают режим

округления в направлении меньшего числа. Теперь, когда мы возьмем

целую часть показателя степени, его значение будет округляться

влево, в направлении минус бесконечности. Дробная часть показателя

будет положительным числом, что также требуется для команды F2XM1.

Обратите внимание на использование команды FWAIT после команды

FNSTCW. Ожидать окончания выполнения умножения перед записью

управляющего слова не надо, так как умножение не меняет код в

управляющем слове. Но перед чтением управляющего слова из главной

памяти и модификацией его нужна гарантия, что сопроцессор 8087 уже

завершил запись. Значит, нужно выполнить команду ожидания FWAIT

перед чтением.

После установки способа округления команда FRNDINT округляет

показатель степени E до целого значения. Так как мы также

запомнили и исходное значение E в стеке, можно вычесть целую часть

из E и получить дробную часть показателя степени. То есть теперь E

= I + F, и можно записать

2**E = 2**I*2**F

Но перед тем обратим внимание на одну маленькую деталь.

Дробная часть F может оказаться значением большим 1/2, и поэтому не

может быть аргументом команды F2XM1. Сейчас мы используем число

-1, ранее помещенное в стек, чтобы разделить F на 2, получив при

этом F/2. Чтобы это сделать, воспользуемся командой FSCALE, так

как эта команда умножает содержимое ST0 на 2 в степени,

содержащейся в ST1. Поскольку в элементе ST1 содержится -1,

результирующим эффектом будет умножение на 1/2. Теперь можно

утверждать, что содержимое регистра ST0 меньше 1/2.

Далее команда F2XM1 возводит 2 в степень F/2, а -1 в стеке

помогает ликвидировать -1, порождаемую в результате работы команды

F2XM1. Обратное вычитание с извлечением из стека избавляется и от

-1 в стеке. Затем 2F/2 умножается само на себя, в элементе ST0

получается число 2F. Так как целая часть показателя степени теперь

переместилась в элементе ST1, команда FSCALE умножает 2I на число

2F, которое уже находится в элементе ST0, давая искомый результат.

Команда FCOMP удаляет из стека число I перед возвратом из

подпрограммы.


Изображение чисел с плавающей точкой

Следующая подпрограмма берет число из вершины стека и

преобразует его в печатную строку символов. Фактически,

подпрограмма извлекает число с вершины стека и посылает его на

дисплей. Далее эта подпрограмма будет использована в двух примерах

для вывода их результатов. Исходный текст программы показан на

Фиг. 7.25.

Эта подпрограмма достаточно просто строит выводимую символьную

строку. Если исходное число NAN, либо бесконечность, или другое

специальное число сопроцессора 8087, результат будет показан

неверно. Первая часть программы - удобное место для использования

команды FXAM, которая определила бы тип числа в вершине стека. Но

в данном примере эта команда не используется, так как

предполагается, что исходное число имеет нужный тип.

Эта программа не приспособлена для оформления формата

выводимого числа. Результат всегда содержит знак (либо пробел,

либо знак "-") и целую часть, состоящую из одной цифры. После

десятичной точки расположены восемь десятичных позиций, а затем

буква E и три позиции цифр для степени 10. Результат работы этой

программы не так хорош, как можно было желать, но он позволяет

видеть результат работы программы в читабельной форме. Более

красивое преобразование числа потребовало бы значительно больше

команд, и лишь малая часть из них помогла бы пониманию работы

сопроцессора 8087.

Программа преобразования работает следующим образом. Сначала

она определяет порядок числа. Например, число 1234 имеет порядок

3; это означает, что оно находится между значениями 103 и 104.

Найдя правильный порядок числа, программа сохраняет его значение

(показатель степени результата) и делит исходное число на 10 в этой

степени. Теперь число находится в интервале от 1 до 10. Затем

программа умножает число на 108. Запись этого числа в десятичной

форме дает девять десятичных цифр; старшая цифра - целая часть,

младшие восемь цифр - дробные разряды.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:46

Фиг. 7.25 Преобразование плавающего формата в текстовый Page 1-1

PAGE ,132

TITLE Фиг. 7.25 Преобразование плавающего формата в текстовый

0000 CODE SEGMENT PUBLIC

ASSUME CS:CODE,DS:CODE,ES:CODE

EXTRN TEN_TO_X:NEAR

0000 ???? OLD_CW DW ?

0002 ???? NEW_CW DW ?

0004 ???? EXPONENT DW ?

0006 ??????????????????? BCD_RESULT DT ?

?

0010 ??????????????????? BCD_EXPONENT DT ?

?

001A 00E1F505 TEN8 DD 100000000

001E 20 20 20 20 20 20 20 PRINT_STRING DB ' E ',10,13,'$'

20 20 45 20 20 20 20

0A 0D 24

Фиг. 7.25 Преобразование плавающего формата в текстовый (начало)

PUBLIC FLOAT_ASCII

;--------------------------------------------

; Эта программа извлекает из вершины стека

; сопроцессора 8087 число и выводит его на

; экран в плавающем формате.

; Параметры: число в ST(0)

; Результат: изображение числа на экране;

; число извлечено из стека сопроцессора 8087

;--------------------------------------------

002F FLOAT_ASCII PROC NEAR

;-----ST(0)-----;-----ST(1)-----;--ST(2)--

; X ; ? ; ?

002F 9B D9 C0 FLD ST(0) ; X ; X ; ?

0032 9B D9 E1 FABS ; |X| ; X ; ?

0035 9B D9 E8 FLD1 ; 1 ; |X| ; X

0038 9B D9 C9 FXCH ST(1) ; |X| ; 1 ; X

003B 9B D9 F1 FYL2X ; LOG2(X) ; X ; ?

003E 9B D9 E9 FLDL2T ; LOG2(10) ; LOG2(X) ; X

0041 9B DE F1 FDIVRP ST(1),ST(0) ; E=LOGX/LOG10 ; X ; ?

0044 D9 3E 0000 R FNSTCW OLD_CW ; ; ;

0048 9B FWAIT ; ; ;

0049 A1 0000 R MOV AX,OLD_CW ; ; ;

004C 25 F3FF AND AX,NOT 0C00H ; ; ;

004F 0D 0400 OR AX,0400H ; ; ;

0052 A3 0002 R MOV NEW_CW,AX ; ; ;

0055 9B D9 2E 0002 R FLDCW NEW_CW ; ; ;

005A 9B D9 FC FRNDINT ; I = INT(E) ; X ; ?

005D 9B D9 2E 0000 R FLDCW OLD_CW ; ; ;

0062 9B DF 16 0004 R FIST EXPONENT ; I ; X ; ?

0067 9B D9 E0 FCHS ; -I ; X ; ?

006A E8 0000 E CALL TEN_TO_X ; 10 ** (-I) ; X ; ?

006D 9B DE C9 FMULP ST(1),ST(0) ; X/10**I ; ? ; ?

0070 9B DA 0E 001A R FIMUL TEN8 ; Мантисса ; ? ; ?

0075 9B DF 36 0006 R FBSTP BCD_RESULT ; ? ; ? ; ?

007A 9B DF 06 0004 R FILD EXPONENT ; I ; ? ; ?

007F 9B DF 36 0010 R FBSTP BCD_EXPONENT ; ? ; ? ; ?

;----- Вывод на экран значений,запомненных как упакованные

; целые двоично-десятичные числа

0084 FC CLD

0085 8D 3E 001E R LEA DI,PRINT_STRING ; Указатель на выводимую

; строку

0089 A0 000F R MOV AL,BYTE PTR BCD_RESULT+9

008C E8 00C3 R CALL PRINT_SIGN ; Печать знака

008F A0 000A R MOV AL,BYTE PTR BCD_RESULT+4

0092 E8 00DF R CALL PRINT_NYBBLE ; Печать первой цифры

0095 B0 2E MOV AL,'.' ; Десятичная точка

0097 AA STOSB

0098 8D 1E 0009 R LEA BX,BCD_RESULT+3

009C B9 0004 MOV CX,4 ; Печать 8 байт (16 цифр)

009F DO_BYTE: ; после десятичной точки

009F E8 00CD R CALL PRINT_BYTE

00A2 E2 FB LOOP DO_BYTE

00A4 B0 45 MOV AL,'E' ; Символ экспоненты

00A6 AA STOSB

Фиг. 7.25 Преобразование плавающего формата в текстовый (продолжение)

00A7 A0 0019 R MOV AL,BYTE PTR BCD_EXPONENT+9

00AA E8 00C3 R CALL PRINT_SIGN ; Печать знака экспоненты

00AD A0 0011 R MOV AL,BYTE PTR BCD_EXPONENT+1

00B0 E8 00DF R CALL PRINT_NYBBLE ; Первая цифра экспоненты

00B3 8D 1E 0010 R LEA BX,BCD_EXPONENT

00B7 E8 00CD R CALL PRINT_BYTE ; Оставшиеся цифры

00BA 8D 16 001E R LEA DX,PRINT_STRING

00BE B4 09 MOV AH,9H

00C0 CD 21 INT 21H ; Вывод всей строки на экран

00C2 C3 RET

00C3 FLOAT_ASCII ENDP

;----- Эта подпрограмма выводит ' ' или '+'

00C3 PRINT_SIGN PROC NEAR

00C3 3C 00 CMP AL,0 ; Проверка на знак

00C5 B0 20 MOV AL,' ' ; Занесение положительного знака

00C7 74 02 JZ POSITIVE

00C9 B0 2D MOV AL,'-' ; Занесение минуса

00CB POSITIVE:

00CB AA STOSB ; Сохранение в выводимой строке

00CC C3 RET

00CD PRINT_SIGN ENDP

;----- Эта программа печатает две десятичные цифры,

; находящиеся в памяти по адресу [BX]

00CD PRINT_BYTE PROC NEAR

00CD 8A 07 MOV AL,[BX] ; Выборка байта

00CF 51 PUSH CX

00D0 B1 04 MOV CL,4

00D2 D2 E8 SHR AL,CL ; Сдвиг старшей цифры

00D4 59 POP CX

00D5 E8 00DF R CALL PRINT_NYBBLE ; Печать старшей цифры

00D8 8A 07 MOV AL,[BX] ; Выборка младшей цифры

00DA E8 00DF R CALL PRINT_NYBBLE ; Печать младшей цифры

00DD 4B DEC BX ; Переход на следующую пару цифр

00DE C3 RET

00DF PRINT_BYTE ENDP

;----- Печать одной десятичной цифры из регистра AL

00DF PRINT_NYBBLE PROC NEAR

00DF 24 0F AND AL,0FH ; Выделение младшей цифры

00E1 04 30 ADD AL,'0' ; Преобразование в символ

00E3 AA STOSB ; Сохранение в выводимой строке

00E4 C3 RET

00E5 PRINT_NYBBLE ENDP

00E5 CODE ENDS

END

Фиг. 7.25 Преобразование плавающего формата в текстовый (продолжение)

Первая часть программы определяет правильный порядок исходного

числа. В программе логарифм числа по основанию 10 находится с

помощью формулы

Log10(X) = Log2(X)/Log2(10)

Затем округляется порядок в направлении минус бесконечности,

опять используя управление округлением. В предыдущем примере,

вычисляя 10X, мы пользовались умножением для переноса исходного

числа в нужный диапазон. Теперь мы используем константу TENB

(которая содержит целое число 108) для того, чтобы вернуть число в

нужный диапазон. Наконец, команда FBSTP дважды преобразует числа в

десятичное представление; сначала она дает нам девять цифр мантиссы

числа, а затем - три цифры порядка.

Остальная часть программы выполняет символьную обработку,

необходимую для преобразования десятичного представления в строки

символов. Программа определяет и показывает знаки числа и порядка.

Она распаковывает десятичные байты и преобразует их в символы;

подпрограмма PRINT_BYTE делает распаковку, а подпрограмма

PRINT_NYBBLE выполняте преобразование в символы. Заметим, что в

этом случае не нужна команда XLAT, так как все цифры имеют значения

между 0 и 9. (Но если исходное число - одно из неопределенных

чисел, символьная строка будет содержать некоторые непонятные

символы).

Эта программа верно печатает любое число, лежащее в диапазоне

длинных действительных чисел. Любое число, выходящее за пределы

возможностей этого представления (например 101234) имеет поле

порядка, сокращенное до трех цифр. Конечно, вы можете изменить

программу так, чтобы она обрабатывала четыре цифры поля порядка,

если вы этого желаете. Но существует все же число, которое

программой обрабатывается верно, но вы, возможно, пожелаетет

изменить его изображение. Если исходное число 0, результат

печатается в виде 0.00000000E-932. Так происходит потому, что поле

порядка имеет смещение; процессор 8087 представляет число 0 с

минимально возможным порядком (-4932) и с нулевой мантиссой. Когда

программа преобразует число в код ASCII, она верно печатает

мантиссу и порядок (за исключением того, что ей приходится усекать

порядок до трех цифр). Если вы захотите обрабатывать такой порядок

отдельно, то измените программу, вставив в нее проверку на нуль

(чаще всего, с помощью команды FTST) в самом начале, рассматривая

это, как специальный случай.


Untitled

Квадратное уравнение

Теперь приведем два примера, использующих программу индикации

чисел с плавающей точкой. Первый пример - решение квадратного

уравнения. Найдем корни уравнения, задаваемого следующей формулой

0 = A*X**2 + B*X + C

Из школьного курса математики известно, что решение этого

уравнения

X = ( -B +- SQR( B**2 - 4*A*C))/(2*A)

Программа решения этого уравнения очевидна и показана на

Фиг. 7.26. В ней предполагается, что все три параметра A, B и C

записаны в виде целых чисел. Конечно, если вы будете использоваь

программу не только как пример, нужно организовать процедуру ввода

различных коэффициентов.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:54

Фиг. 7.26 Вычисление корней квадратного уравнения Page 1-1

PAGE ,132

TITLE Фиг. 7.26 Вычисление корней квадратного уравнения

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,ES:CODE

EXTRN FLOAT_ASCII:NEAR

0000 0001 A DW 1

0002 FFFB B DW -5

0004 0006 C DW 6

0006 ???? STATUS DW ?

0008 0004 FOUR DW 4

000A 0002 TWO DW 2

000C 8C AD A8 AC EB A5 20 ERROR_MSG DB 'Мнимые корни',10,13,'$'

AA AE E0 AD A8 0A 0D

24

001B QUADRATIC PROC FAR

001B 1E PUSH DS ; Сохранение адреса возврата

001C 2B C0 SUB AX,AX

001E 50 PUSH AX

001F 8C C8 MOV AX,CS

0021 8E D8 MOV DS,AX

0023 8E C0 MOV ES,AX

0025 9B DB E3 FINIT ;-----ST(0)-----;-----ST(1)------

0028 9B DF 06 0002 R FILD B ; B ; ?

002D 9B D8 8E 0000 FMUL ST(0) ; B**2 ; ?

0032 9B DF 06 0000 R FILD A ; A ; B**2

0037 9B DE 0E 0008 R FIMUL FOUR ; 4*A ; B**2

003C 9B DE 0E 0004 R FIMUL C ; 4*A*C ; B**2

0041 9B DE E1 FSUBRP ST(1),ST(0) ; D=B**2-4*A*C ; ?

0044 9B D9 E4 FTST

0047 9B DD 3E 0006 R FSTSW STATUS

004C 9B FWAIT

004D 8A 26 0007 R MOV AH,BYTE PTR STATUS+1

0051 9E SAHF

0052 72 37 JB IMAGINARY

0054 9B D9 FA FSQRT ; SQR(D) ;

Фиг. 7.26 Вычисление корней квадратного уравнения (начало)

0057 9B D9 C0 FLD ST(0) ; SQR(D) ; SQR(D)

005A 9B D9 E0 FCHS ; -SQR(D) ; SQR(D)

005D 9B DE 06 0002 R FIADD B ; B-SQR(D) ; SQR(D)

0062 9B D9 E0 FCHS ; -B+SQR(D) ; SQR(D)

0065 9B D9 C9 FXCH ST(1) ; SQR(D) ; -B+SQR(D)

0068 9B DE 06 0002 R FIADD B ; B+SQR(D) ; -B+SQR(D)

006D 9B D9 E0 FCHS ; N1=-B-SQR(D) ; N2=-B+SQR(D)

0070 9B DE 36 0000 R FIDIV A ; N1/A ; N2

0075 9B DE 36 000A R FIDIV TWO ; ROOT1=N1/2*A ; N2

007A E8 0000 E CALL FLOAT_ASCII ; N2 ; ?

007D 9B DE 36 0000 R FIDIV A ; N2/A ; ?

0082 9B DE 36 000A R FIDIV TWO ; ROOT2=N2/2*A ; ?

0087 E8 0000 E CALL FLOAT_ASCII ; ? ; ?

008A CB RET

008B IMAGINARY:

008B 8D 16 000C R LEA DX,ERROR_MSG

008F B4 09 MOV AH,9H

0091 CD 21 INT 21H ; Вывод сообщения об ошибке

0093 CB RET

0094 QUADRATIC ENDP

0094 CODE ENDS

END QUADRATIC

Фиг. 7.26 Вычисление корней квадратного уравнения (продолжение)

В примере отсутствует обработка комплексных чисел, но имеется

проверка дискриминанта (B**2 - 4*A*C) на отрицательность, и если это

число отрицательно, программа завешается с сообщением об ошибке.

Можно было бы ввести в программу комплексную арифметику, тем не

менее, ее нет в данном примере. Необходимо помнить, что

сопроцессор 8087 не обрабатывает автоматически комплексные или

мнимые числа, и нужно писать программу раздельной обработки

действительной и мнимой частей комплексного числа.

Команда FTST проверяет дискриминант на отрицательность; она

подобна сравнению с встроенным нулевым операндом источника.

Программа записывает слово состояния в память, а затем загружает

его в регистр флагов микропроцессора 8088. После этого делается

проверка JB (переход, если меньше), определяющая, меньше ли нуля

дискриминант. Оставшаяся часть программы проделывает работу по

вычислению двух корней уравнения, и здесь используется то

преимущество, что коэффициенты находятся в памяти. Такой подход

минимизирует объем используемого в сопроцессоре 8087 стека. Но

если вы переделаете эту программу так, что она будет подпрограммой,

вызываемой из другой программы, вам, вероятно, захочется передавать

параметры ей с помощью стека сопроцессора 8087. В этом случае

потребуется другой способ для загрузки подпрограммой некоторых

величин.


Untitled

Синус угла

Последний пример использования сопроцессора 8087 - вычисление

синуса угла. У сопроцессора 8087 нет команды вычисления функции

SIN; самое большее, что он может - это выполнить команду FPTAN,

нахождение частичного тангенса. Чтобы выполнить операцию SIN,

воспользуемся этой командой, а также командой FPREM (частичный

остаток).

Программа, вычисляющая SIN, показана на Фиг. 7.27. Эта

программа вычисляет и печатает синусы углов от 1/2 до 6 с шагом 1/2

радиана. Выдача программы аналогична выдаче следующей программы на

языке Бейсик:

10 FOR X = .5 TO 6.0 STEP .5

20 PRINT SIN(X)

30 NEXT X

Для печати результатов используется подпрограмма на Фиг. 7.25.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:01

Фиг. 7.27 Вычисление синуса угла Page 1-1

PAGE ,132

TITLE Фиг. 7.27 Вычисление синуса угла

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE,DS:CODE,ES:CODE

EXTRN FLOAT_ASCII:NEAR

0000 0001 NUM_ANGLE DW 1

0002 0002 DEN_ANGLE DW 2

0004 ???? STATUS DW ?

0006 0004 FOUR DW 4

= 0040 C3 EQU 40H

= 0004 C2 EQU 04H

= 0002 C1 EQU 02H

= 0001 C0 EQU 01H

0008 93 A3 AE AB 20 E1 AB ERROR_MSG DB 'Угол слишком большой', 10, 13, '$'

A8 E8 AA AE AC 20 A1

AE AB EC E8 AE A9 0A

0D 24

001F SIN PROC FAR

001F 1E PUSH DS

0020 2B C0 SUB AX, AX

0022 50 PUSH AX

0023 8C C8 MOV AX, CS

0025 8E D8 MOV DS, AX

0027 8E C0 MOV ES, AX

0029 DO_AGAIN:

0029 9B DB E3 FINIT ;-----ST(0)-----;-----ST(1)------

002C 9B DF 06 0000 R FILD NUM_ANGLE ; ;

0031 9B DE 36 0002 R FIDIV DEN_ANGLE ; X = Угол ;

Фиг. 7.27 (a) Процедура SIN (начало)

0036 9B D9 EB FLDPI ; PI ; X

0039 9B DE 36 0006 R FIDIV FOUR ; PI/4 ; X

003E 9B D9 C9 FXCH ; X ; PI/4

0041 9B D9 F8 FPREM ; R ; PI/4

0044 9B DD 3E 0004 R FSTSW STATUS

0049 9B FWAIT

004A 8A 26 0005 R MOV AH, BYTE PTR STATUS+1

004E F6 C4 04 TEST AH, C2

0051 75 55 JNZ BIG_ANGLE

0053 F6 C4 02 TEST AH, C1 ; Определяется, необходимо ли вычитание PI/4

0056 74 05 JZ DO_R ; Если 0, то не необходимо вычитание PI/4

0058 9B DE E1 FSUBRP ST(1), ST(0) ; A = PI/4-R ; ?

005B EB 06 JMP SHORT DO_FPTAN

005D DO_R:

005D 9B D9 C9 FXCH ; PI/4 ; R

0060 9B D8 D9 FCOMP ; R ; ?

0063 DO_FPTAN:

0063 9B D9 F2 FPTAN ; OPP ; ADJ Где OPP/ADJ=Tan(A)

;----- Опеределение того, что нужно - синус или косинус

0066 F6 C4 42 TEST AH, C3 or C1

0069 7A 03 JPE DO_SINE

006B 9B D9 C9 FXCH ; ADJ ; OPP

006E DO_SINE: ; D ; N

;----- Вычисление N/SQR(N**2 + D**2)

006E 9B D8 8E 0000 U FMUL ST(0) ; D**2 ; N

0073 9B D9 C9 FXCH ST(1) ; N ; D**2

0076 9B D9 C0 FLD ST(0) ; N ; N ; D**2

0079 9B D8 8E 0000 U FMUL ST(0) ; N**2 ; N ; D**2

007E 9B DC 06 0000 U FADD ST(2) ; N**2 + D**2 ; N ; D**2

0083 9B D9 FA FSQRT ; SQR(N2 + D2) ; N ; D**2

0086 9B DE F1 FDIVRP ST(1) ; SIN(X) ; D**2

0089 9B D9 C9 FXCH ST(1) ; D**2 ; SIN(X)

008C 9B D8 D9 FCOMP ; SIN(X) ; ?

008F F6 C4 01 TEST AH, C0

0092 74 03 JZ SIGN_OK

0094 9B D9 E0 FCHS

0097 SIGN_OK:

0097 E8 0000 E CALL FLOAT_ASCII

009A FF 06 0000 R INC NUM_ANGLE

009E 83 3E 0000 R 0D CMP NUM_ANGLE, 13

00A3 77 02 JA RETURN_INST

00A5 EB 82 JMP DO_AGAIN

00A7 RETURN_INST:

00A7 CB RET

00A8 BIG_ANGLE:

00A8 8D 16 0008 R LEA DX, ERROR_MSG

00AC B4 09 MOV AH, 9H

00AE CD 21 INT 21H

00B0 CB RET

00B1 SIN ENDP

00B1 CODE ENDS

END SIN

Фиг. 7.27 (a) Процедура SIN (продолжение)

A>SIN

4.79425539E-001

8.41470985E-001

9.97494987E-001

5.98472144E-001

1.41120008E-001

-3.50783228E-001

-7.56802495E-001

-9.77530118E-001

-9.58924275E-001

-7.05540326E-001

-2.79415498E-001

2.15119988E-001

Фиг. 7.27 (b) Вывод процедуры SIN

Фиг. 7.27 Вычисление синуса угла

В первой части программы происходит ее инициализация для работы

в качестве файла типа .EXE. Затем сопроцессор 8087 загружает два

целых числа и делит их, формируя исходный угол. Это - пример

использования двух целых чисел для порождения числа с плавающей

точкой (в данном случае 1/2), что нельзя сделать непосредственно с

помощью ассемблера.

Как вы помните из тригонометрии, синус - периодическая функция.

То есть функция дает один и тот же результат в случае исходных

чисел, различающихся ровно на 2*PI. Поэтому первой задачей

подпрограммы SIN является замена исходного угла соответствующим

значением, лежащим в диапазоне

0 <= X < 2*PI

В команде FPTAN требуется, чтобы угол находился в диапазоне

0 <= X < PI/4

Это означает, что даже если угол и меньше 2*PI, мы должны

уменьшить его еще, чтобы он удовлетворял ограничениям команды

FPTAN. К счастью, если исходный угол уменьшен до значения,

меньшего PI/4, все еще можно определить верное значение

тригонометрических функций. Чтобы это сделать, надо знать, в каком

месте исходного диапазона от 0 до 2*PI находился исходный угол.

Нужное уменьшение угла выполняет команда FPREM. Она не только

вычисляет остаток, но и три младших бита частного, определяемого в

течение процесса поиска остатка. Эти три бита команда записывает в

слово состояния. Следовательно, хотя мы и уменьшили угол до

значения одной восьмой исходного диапазона, все же можно определить

октант, в который попадет угол. Зная его, можно найти формулу

вычисления синуса с помощью тригонометрических преобразований.

Таблица на Фиг. 7.28 показывает связь между исходным октантом и

методом вычисления синуса угла. В таблице предполагается, что

число R - это остаток от уменьшения исходного угла до значения

меньше PI/4. Номер октанта появляется в разрядах C3 = C1 = C0

после выполнения команды FPREM.

С помощью этой таблицы мы можем определить формулу вычислений,

применяемую в каждом случае выполнения программы. После загрузки

значения угла в радианах программа загружает число и делит его на

4, чтобы использовать в команде FPREM. В этот момент

"захватывается" слово состояния. Если процесс поиска остатка не

завершился на этом единственном шаге, это означает, что исходный

угол был больше 2**64. Следовательно, его значение настолько больше

максимально возможного при вычислениях тригонометрических функций,

что мы отбрасываем это число, как слишком большое. Этого не

происходит со значениями, выбранными в примере, но здесь для

иллюстрации введена такая проверка.

Октанты

C0 C3 C1 Диапазон SIN(X) = :

-----------------------------------------------------

0 0 0 0 PI/4 SIN(R)

0 0 1 PI/4 PI/2 COS(PI/4-R)

0 1 0 PI/2 3*PI/4 COS(R)

0 1 1 3*PI/4 PI SIN(PI/4-R)

1 0 0 PI 5*PI/4 - SIN(R)

1 0 1 5*PI/4 3*PI/2 - COS(PI4-R)

1 1 0 3*PI/2 7*PI/4 - COS(R)

1 1 1 7*PI/4 2*PI - SIN(PI/4-R)

(R - остаток, 0<R<PI/4)

-----------------------------------------------------

Фиг. 7.28 SIN(X) в восьми секторах

Программа проверяет разряд C1 в регистре состояния, чтобы

определить, должна ли она использовать остаток R, или его надо

вычесть из PI/4. Так как PI/4 еще находится в одном из регистров,

это сделать просто. Если вычитание не требуется, команда FCOMP

удаляет из стека ненужное значение PI/4.

Затем команда FPTAN вычисляет частичный тангенс. Результат

работы команды показан, как OPP/ADJ (сокращения от английских слов

Opposite (противоположный) и Adjacent (соседний)), что равно

тангенсу угла R или PI/4-R, в зависимости от того, что было

выбрано. С помощью этих двух чисел теперь можно опеределить синус

или косинус угла. Например, синус, заданный парой чисел OPP/ADJ,

можно вычислить по формуле

SIN(X) = OPP/SQR(OPP**2+ADJ**2), где TAN(X) = OPP/ADJ

Чтобы вычислить косинус, нужно числитель заменить на ADJ. Мы

решаем, нужен ли синус или косинус, анализируя запомненные

описатели октанта, т.е. проверяя значения разрядов C3 и C1.

Команда TEST выделяет эти значения, а команда JPE делает переход,

если они оба нулевые или оба единичные. В этом случае мы вычисляем

синус; если же они различны, мы вычисляем косинус, что достигается

заменой местами значений OPP и ADJ в стеке регистров.

Далее следующие команды сопроцессора 8087 вычисляют значение

синуса (или косинуса) по значению частичного тангенса.

Единственный шаг, который еще надо выполнить - это определение

окончательного знака результата. В случае синуса результат

отрицателен, если угол находится в октантах от четвертого до

седьмого. Проверка разряда C0 определяет верный знак результата.

Затем программа FLOAT_ASCII, показанная на Фиг. 7.25, печатает

число в плавающем формате. Управление возвращается назад, к началу

цикла, если еще не пройдены все октанты. Нижняя часть Фиг. 7.27

иллюстрирует результат выполнения этой программы.


Отладка программ с использованием 8087

Перед тем, как мы окончим обсуждение процессора 8087, хотелось

бы немного поговорить об отладке написанных для него программ.

Проблема, с которой мы сталкиваемся здесь, заключается в том, что

утилита DEBUG (отладчик) в DOS не поддерживает процессор 8087.

Это означает, что при обнаружении отладчиком контрольной точки он

не отображает на экране содержимого регистров сопроцессора 8087.

Это сильно затрудняет отладку программы, изменяющей регистры

сопроцессора 8087.

В книге предлагается метод, который можно применять для отладки

программ сопроцессора 8087, используя отладчик DOS. Этот метод

может быть и не наилучший, но он использовался при отладке программ

приведенных в этой главе.

Основное препятствие заключается в неспособности программы

DEBUG показывать содержимое регистрового стека сопроцессора 8087.

Без переписывания заново программы DEBUG этот метод дает

существенную иинформацию, необходимую при отладке программы

сопроцессора 8087. Этот метод отладки требует, чтобы программа

была написана, как отдельно выполняемая, либо как файл типа .EXE,

либа типа .COM. Даже если вы пишете подпрограмму, отладьте ее

сначала, как головную программу. Одной из первых команд программы

должна быть команда FINIT, сбрасывающая процессор 8087 в состояние,

в которое он попадает при включении питания. Это надо сделать,

чтобы можно было прогонять программу снова и снова, всегда начиная

сначала. Рассматриваемый здесь метод отладки не позволит вам

остановиться, проанализироваь регистры сопроцессора 8087, а затем

продолжить программу с этого же места. Этот метод основывается на

возможности начинать все сначала после каждой контрольной точки.

Вы должны организовать все параметры подпрограммы в виде ячеек

памяти, и программа должна загружать все эти числа в

соответствующие регистры вслед за командой FINIT. Это необходимо,

даже если идет работа с программой, принимающей параметры,

переданные через стековые регистры. Сначала отлаживаемая программа

работает с параметрами, лежащими в памяти. После того, как

арифметика и логика программы будет отлажена, можно будет изменить

программу так, чтобы она принимала параметры из регистрового стека.

Цель всех этих действий - позволить программе выполняться без

внешнего вмешательства. Это означает, что можно запустить

программу сначала и выполнять ее до некоторой команды, и перезапуск

программы приведет к точно такому же ее выполнению. Такое свойство

необходимо, так как предлагаемый метод индикации регистров

разрушает содержимое стека процессора 8087, и когда это произошло,

продолжать выполнение программы с этого же места уже нельзя.

Программу надо перезапустить с самого начала и остановить ее уже в

другом месте, а это возможно благодаря принятым мерам. Последние

два примера, квадратное уравнение и функция синуса, устроены именно

таким образом: их параметры находятся в памяти, и программы

начинаются с команды FINIT.

Следующий этап процедуры отладки требует размещения

специального программного фрагмента в заранее фиксированном месте

вашей программы. Для отладки примеров был выбран адрес 200, так

как ни один из этих примеров не занимает более 500 байт. Этот

программный фрагмент предназначен только для отладки, и вы удалите

его перед получением окончательной версии программы. Такой

фрагмент показан на Фиг. 7.29. Как вы видите, он очень короток и

содержит только три команды и два поля данных. Первое поле данных

содержит константу, в данном случае 106, или 1000000. Выбор этого

значения остается за вами; другое значение может оказаться

подходящим, если ваша программа работает с числами, меньшими 10-6,

или большими 1012.

Смысл этого программного фрагмента заключается в том, что он

преобразует содержимое вершины стека в число, которое вы сможете

увидеть. Этот фрагмент умножает содержимое текущей вершины стека

на число с массой нулей, а это эквивалентно сдвигу десятичной точки

вправо. В данном случае, если вершина стека содержит 1/2,

умножение преобразует ее в 500000.

После того, как число преобразовано в большое целое (вместо

дробного), команда FBSTP записывает его в упакованной десятичной

форме в поле, также находящееся в этом специальном программном

фрагменте. Затем команда INT 3 возвращает управление программе

DEBUG. Теперь можно использовать команду Display программы DEBUG,

чтобы посмотреть на 10 байт, записанных командой FBSTP. Конечно,

читать показанное значение нужно наоборот, так как это - способ,

которым сопроцессор 8087 записывает десятичные числа. Также надо

учесть модификацию десятичнлй точки, которую выполнило умножение.

------------------------------

TEN6 DD 1000000

ORG 200H

BCD_TEMP DT ?

ORG 210H

FIMUL TEN6

FBSTR BCD_TEMP

INT 3

--------------------------- Фиг. 7.29 Отладка процедуры

для числового сопроцессора

Отладка программы для сопроцессора 8087 осуществляется

следующим образом. Как только вы решили, что программа работает

неверно, вы находите место контрольной точки по листингу программы.

Использование команды Unassemble многого не даст, так как все

команды сопроцессора 8087 дезассемблируются как команды ESC. Так

что использование литсинга программы существенно.

Теперь вы выполняете программу с начала до контрольной точки;

именно для этого вы сконструировали программу так, чтобы ее можно

было перезапустить с начала баз какой=либо подготовки. Всякий раз,

когда вы вновь устанавливаете контрольную точку, нужно выполнять

программу с начала.

Когда программа попадает на контрольную точку, управление

передается в отладчик. Теперь вы можете выполнить тот специальный

фрагмент кода, который помещен в программу. Команда INT 3 в конце

этого фрагмента возвращает управление в программу DEBUG, так что вы

можете увидеть, что за число находилось в вершине стека, когда

выполнялась исходная контрольная точка. Так как была использована

команда FBSTP, она извлекла число из вершины стека, записав аго в

память. Поэтому, чтобы увидеть второе число стека, ST1, вы можете

еще раз выполнить отладочный фрагмент; все это можно повторять

столько раз, сколько вы хотите. Когда по этому методу будет

получено десятичное число, содержащее значение 0FFH как в старшем,

так и в знаковом байтах, знайте, что из стека извлеклось пустое

значение. Далее в программе можно установить новую контрольную

точку, и снова выполнить программу сначала. Таким образом вы

можете пройти путь по всей программе, пока не найдется место

ошибки. Как только ошибка найдена, можно либо исправить ее на

месте ("залатать" ошибку), или выйти назад в DOS, чтобы

отредактировать и заново ассемблировать программу. Когда, наконец,

программа выполняется верно, и вам больше уже не нужна программа

DEBUG, можно удалить отладочный фрагмент из программы. В этот же

момент вы можете изменить программу так, чтобы принимать параметры

через регистры стека, а не через память.


Untitled

Глава 8

Персональный компьютер IBM

Эта глава описывает устройство персонального компьютера фирмы

IBM (IBM PC). Поскольку данная книга посвящена программированию на

языке ассемблера для IBM PC, вполне уместно рассмотреть аппаратное

обеспечение машины. Эта глава не предназначена для инженеров и

техников; она скорее для тех, кто пишет на языке ассемблера

программы, которые выполняются на IBM PC.

Выше говорилось, что язык ассемблера не всегда самый подходящий

язык программирования. Умение программировать непосредственно на

машинном уровне позволяет программисту иметь много больше

возможностей для управления машиной. Но в случае большого проекта

подавляющее количество подробностей, которое требуется языком

ассемблера, затрудняет сосредоточие усилий на главной цели. Поэтому

лучше всего использовать ассемблер только по мере необходмости.

Желание использовать преимущества непосредственного управления

аппаратурой - одна из причин, по которой спользуются языком

ассемблера. Чтобы корректно решить эту задачу, вам необхдимо знать,

что из себя представляет аппаратура и какие у нее возможности. Вот

зачем нужна эта глава. Информация в данной главе адресуется

программисту, а не инженеру. Мы рассмотрим здесь различные узлы

аппаратуры, и как ими можно управлять.

Эта глава - дополнение к Техническому Описанию Персонального

Компьютера. Вы будете также пользоваться им для уточнения

специфических аспектов программирования аппаратуры. Кроме тогно

дополнительную информацию можно получить из справочных листков к

отдельным микросхемам. В этой главе данные из этих источников не

повторяются. Всюду где это нужно, в тексте воспроизводятся

некоторые данные аппаратуры, иллюстрирующие отдельные программные

возможности. Будут, конечно, приводиться и примеры работы

аппаратуры.

Описание базовой системы ввода-вывода BIOS (Basic Input/Output

System), которая находится в постоянном запоминающем устройстве

(ПЗУ) ЭВМ, будет отложено до следующей главы. Программы BIOS

выполняют управление на аппаратном уровне устройствами IBM PC. В

этой главе объясняется, что может делать аппаратура; следующая

глава детализирует, что происходит с аппаратным обеспечением в

процессе работы, а последняя глава поможет добиться от него того,

что не реализовано в ПЗУ с программой BIOS.


Untitled

Системное оборудование

В каждом пункте этой главы мы будем обсуждать свои аспекты

системы ввода-вывода IBM PC. В этом пункте речь пойдет о

стандартных компонентах аппаратуры - о тех, которые находятся на

процессорной плате системы. Другие части будут посвящены отдельным

платам адаптеров ввода-вывода, которые по необходимости можно

установить в систему.

Центральным процессором IBM PC является Intel 8088, тот самый,

естественно, который рассматривался на протяжении первых глав

книги. У нас, как будто еще есть что о нем рассказать. Рядом с

микросхемой 8088 на системной плате имеется пустое гнездо, в

которую вставляется арифметический сопроцессор Intel 8087; он был

рассмотрен в гл.7 и теперь уже должен быть знаком вам.

Остальные компоненты системной платы выполняют функции, которые

и превращают микропроцессор в ЭВМ. На системной плате находится до

64K байт оперативной памяти, а также 40K байт памяти только для

чтения (ПЗУ, постоянное запоминающее устройство). Это ПЗУ содержит

интерпретатор языка Бейсик, а также систему BIOS, которую мы

обсудим в следующей главе.

На системной плате есть много компонент, необходимых для работы

IBM PC. Мы рассмотрим только программируемые или полезные для

программирования компоненты. На системной плате этими компонентами

являются микросхемы программируемого периферийного интерфейса 8255,

таймера-счетчика 8253, контроллера прерываний 8259, и контроллера

прямого доступа в память 8237. Остальные микросхемы выполняют

аппаратные функции, которые нельзя изменять с помощью

программирования. Подробно, на элементном уровне, микросхемы

рассматриваться не будут; если вас это интересует, обратитесь к

каталлогу фирмы Intel, либо к другим справочным материалам. Вместо

этого мы рассмотрим функции ввода-вывода, реализованные на

системной плате IBM PC. Управляя этим средством доступа, мы сможем

пльзоваться всеми перечисленными выше компонентами.


Untitled

Динамик

Внутри корпуса IBM PC есть маленький динамик. Программа может

управлять звуками, генерируемыми этими динамиком. Для этого нужно

управлять некоторыми выходными битами микросхемы 8255 и генератора

тона в микросхеме 8253.

На Фиг. 8.1 показана программа, которая управляет динамиком

двумя разными способами. Первый способ, помеченный в листинге

меткой DIRECT, непосредственно управляет динамиком. Бит 1 выводного

порта 61H подключен к динамику. Всякий раз, когда программа меняет

значение этого бита, диффузор динамика двигается либо наружу, либо

внутрь. Быстро меняя значение этого бита, программа генерирует

звук. Это иллюстрирует первая часть программы на Фиг. 8.1, она

меняет значение бита 1, порождая высокочастотный тон. Скорость, с

которой программа меняет бит 1, определяет частоту тона.

Взяв на себя непосредственное управление динамиком, вы должны

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:08

Фиг. 8.1 Управление динамиком Page 1-1

PAGE ,132

TITLE Фиг. 8.1 Управление динамиком

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 SPEAKER PROC FAR

0000 1E PUSH DS ; Адрес возврата

0001 B8 0000 MOV AX, 0

0004 50 PUSH AX

;----- Задание режима работы динамика

0005 2B C9 SUB CX, CX ; Счетчик цикла

0007 E4 61 IN AL, 61H

0009 24 FE AND AL, 0FEH

000B E6 61 OUT 61H, AL ; Установка разряда 0 порта 61H в 0 для

; задания прямого управления динамиком

000D DIRECT:

000D 0C 02 OR AL, 2

000F E6 61 OUT 61H, AL ; Включить динамик

0011 24 FD AND AL, 0FDH

0013 E6 61 OUT 61H, AL ; Выключить динамик

0015 E2 F6 LOOP DIRECT

;----- Управление высотой звука динамика

0017 B0 B6 MOV AL, 10110110b

0019 E6 43 OUT 43H, AL ; Установка режима для 2-го канала

001B B8 03E8 MOV AX, 1000 ; Выбор высоты звука

001E E6 42 OUT 42H, AL

0020 8A C4 MOV AL, AH

0022 E6 42 OUT 42H, AL ; Занесение высоты звука в порт динамика

0024 E4 61 IN AL, 61H

0026 8A E0 MOV AH, AL

0028 0C 03 OR AL, 3

002A E6 61 OUT 61H, AL ; Выбор режима управления динамикаом

002C 2B C9 SUB CX, CX

002E KILL_TIME:

002E E2 FE LOOP KILL_TIME ; Цикл ожидания, во время которого

; работает динамик

0030 8A C4 MOV AL, AH

0032 E6 61 OUT 61H, AL ; Выключение динамика

0034 CB RET

0035 SPEAKER ENDP

0035 CODE ENDS

END

Фиг. 8.1 Управление динамиком

прежде всего работать с выводным портом микросхемы 8255 системной

платы. Программируемый периферийный

интерфейс микросхемы 8255 (PPI) имеет всего три входных или

выходных, порта. IBM PC инициализирует микросхему 8255 так, чтобы получилось два входных

порта - 60H и 62H - и один порт вывода, 61H. Порт 60H в первую

очередь вводит значения с клавиатуры. Его можно

также использовать для чтения положения переключателей на системной

плате. Обычно состояния этих переключателей читаются только один

раз, во время инициализации при включении питания системы.

Результат программа BIOS записывает в память для дальнейшего

использования. Поэтому с точки зрения наших целей можно считать,

что порт 60H непосредственно обслуживает ввод с клавиатуры. Вообще

входной порт выполняет важную функцию. Он служит буфером между

микропроцессором и устройством ввода-вывода; он передает данные

микропроцессору только тогда, когда последний запрашивает их

командой IN. Все остальное время вводной порт задерживает данные и

не допускает, чтобы они повлияли на работу микропроцессора.

Другой порт ввода микросхемы 8255, порт 62H, обслуживает другие

входы. Четыре его бита непосредственно соответствуют

переключателям, показывающим объем памяти, подсоединенной к

системному каналу ввода-вывода. Другие четыре бита имеют

индивидуальное назначение. Два из них показывают тип системной

ошибки. Программа обслуживания немаскируемого прерывания NMI

использует эти биты для определения причины системной аварии. Бит 5

порта 62H используется для обратной связи с одним из каналов

таймера-счетчика. Этот бит служит индикатором текущего выхода

второго канала микросхемы 8253. Бит 4 порта 62H отражает текущее

состояние ввода с кассетного магнитофона. Сзади у IBM PC, рядом с

разъемом для подключения клавиатуры, имеется разъем для подключения

кассетного магнитофона. При чтении данных с кассеты, этот бит

используется для определения текущего значения, вводимого с

кассеты.

Порт 61H - это порт вывода микросхемы 8255 в машине фирмы IBM.

Всякий выходной порт захватывает (временно запоминает) данные,

выводимые программой. Если бы аппаратура не запоминала данные, они

бы пропали в течение микросекунды или около этого. Такое

запоминание данных позволяет сохранять их значение в порте до тех

пор, пока они снова не будут изменены программой. То есть, когда мы

выводим значение, меняющее положение диффузора динамика, оно

остается неизменными до тех пор, пока его не изменит программа.

На Фиг.8.2 показано значение битов порта 61H. Эти данные взяты

из из технического описания.

При изучении управления динамиком имеют значение только биты 0

и 1. Из всех других - только бит 3 - управление двигателем

кассетного магнитофона - и бит 7 - сброс ввода с клавиатуры - имеют

какое-то значение для наших программ. Остальные биты предназначены

только для инициализации и диагностики. Чтобы полностью разобраться

в них, нужно детально изучить принципиальные схемы системы.

Возвращаясь к обсуждению управления динамиком, мы можем

заметить, что биты 0 и 1 используются для непосредственного

Бит Значение

-----------------------------------------------------

0 Порт 2 таймера (упраление динамиком)

1 Прямое управление динамиком

2 Мультиплексный порт 62H

3 Управление мотором кассетного магнитофона

4 Включение контроля доступа на системной

плате памяти

5 Включение контроля доступа в памяти

каналов ввода-вывода

6 Временной контроль клавиатуры

7 Мультиплексный/сброса ввода с клавиатуры

порт 60H

-----------------------------------------------------

Фиг. 8.2 Значение битов порта 61H

управления. Как показано на Фиг. 8.1, установка бита 0 в положение

0 включает прямое управление динамиком, блокируя механизм генерации

звука микросхемой 8253. Этот метод используется во второй части

програмы.

Обратите внимание на то, как программа сбрасывает бит 0.

Команда OUT включает все 8 бит порта 61H. Способа изменить только

бит 0, оставив остальные биты нетронутыми, не существует. Если в

программе нужно изменить только бит 0, она должна считать из порта

текущее значение других разрядов. К счастью, микросхема 8255

допускает прямое программное чтение выводных портов.

Последовательность команд

IN AL , 61H

AND AL , 0FFH

OUT 61H, AL

читает текущий код из выводного порта, затем команда AND сбрасывает

младший бит, а команда OUT посылает результат в выводной порт. Если

бы программа вывела в порт просто число 0, динамик работал бы

верно, но клавиатура была бы выключена. Работая с любым портом

вывода побитовой настройки, стройте программу так, чтобы она не

влияла ни на один из других бит, если только вы не собираететсь

изменять и их.

Оставшаяся часть первой программы на Фиг.8.1 изменяет значение

бита 1 выходного порта. Исходное значение порта 61H находится уже в

регистре AL, так что программе не нужно читать его при каждом

выполнении цикла. Регистр CX используется таким образом, чтобы

выполнить цикл 64K раз. При выполнении программы вам, возможно, не

удастся услышать звук, генерируемый программой. В этом случае

попытайтесь вставить несколько добавочных команд NOP в цикл DIRECT.

Это снизит частоту тона.

Вторая часть программы на Фиг. 8.1 для генерации тональности

использует таймер-счетчик 8253. Прежде чем двигаться дальше,

обсудим функционирование микросхемы 8253, чтобы понять, как она

используется в системе. Микросхема 8253 фирмы Intel содержит три

16-битовых счетчика, которые могут быть использованы в системе для

счета или задания временных интервалов. В один из счетчиков

программа загружает 16-битовое значение. Содержимое счетчика

уменьшается на единицу по каждому импульсу от таймера; частота

импульсов, подводимых с таймера ко всем трем каналам, равна

1.19МГц. Это означает, что содержимое счетчика уменьшается на

единицу каждые 840 наносекунд. Каждый из трех каналов имеет выход.

Строка контроля выхода изменяется всякий раз, когда содержимое

счетчика достигает нуля. Командами управления определяют способ,

которым микросхема 8253 ведет счет.

Выходы этих трех каналов счетчика-таймера подключаются к

различным узлам системной платы. Канал 0 подключается к контроллеру

прерываний 8259. Система использует этот канал для порождения

прерывания времени суток. Канал 1 соединен с контроллером прямого

доступа к памяти (ПДП или DMA) 8237, и использовать этот канал

схемы 8253 нельзя, так как смена кода в этом счетчике с большой

вероятностью уничтожит вашу программу и все другие данные в памяти

системы. Канал 2 подключен к динамику для генерации звука.

Позже мы вернемся к каналу 0 микросхемы 8253. Канал 2 Дает

выход на динамик. Для установки канала таймера программа посылает

код 0B6H в порт 43H, управляющий порт микросхемы 8253. Тем самым

канал 2 таймера-счетчика настраивается на работу в качестве

делителя частоты. Таймер делит исходную частоту - в данном случае

1.19МГц - на 16-битовое число, которое программа загружает в

регистр канала 2. Регистр канала 2 расположен по адресу порта 42H

(канал 0 - это порт 40H и, поскольку вы никогда не должны изменять

содержимое канала 1, задачу определения адреса его порта мы

оставляем вам). Программа в примере загружает в регистр канала

число 1000. Это означает, что на выходе вы услышите частоту 1190Гц.

На самом деле, вы услышите основную частоту 1190 Гц плюс обертоны,

вызванные прямоугольной формой сигнала таймера.

Заметим, что число 1000 - 16-битовое, в то время как порт 42H -

8-битовый. Команда установки режима работы, которую мы послали в

порт 43H, сообщила микросхеме 8253, что в нее будет выводиться

16-битовое число в виде двух 8-битовых. Сначала посылается младший

значащий байт, а за ним следует старший. Такая двухшаговая

процедура загружает в канальный регистр требуемое значение.

Далее программа должна дать управляющему порту 61H такую

установку, чтобы он пропускал сигнал на динамик. Для этого

программа устанавливает равными 1 биты 0 и 1 управляющего порта.

Заметим, что программа в начале сохраняет первоначальное значение

кода из управляющего порта и восстанавливает его в конце. Это

отключает динамик по окончании звука. Если этот способ

недостаточен, - например, если программа генерирует звук тогда,

когда не совсем ясно, был ли выключен динамик - можно выключить

его, сбросив в нуль бит 1 порта 61H.

Эти два метода управления динамиком наиболее прямолинейны. Эти

методы можно пытаться комбинировать в поисках интересных эффектов.

После установки на вывод звука при помощи микросхемы 8253 можно

модулировать выходной сигнал посредством битов 1, 0 или обоих,

порта 61H, а также менять число в канальном счетчике при включенном

динамике. Программу на Фиг. 8.1 можно изменить так, чтобы она

выводила значение регистра CX при каждой итерации цикла. Это

приведет к тому, что частота сигнала из динамика будет расти от

очень низкого к очень высокому тону. Работая с этими тремя

управляющими значениями, вы сможете создать множество интересных

эффектов.


Клавиатура

Следующее устройство ввода-вывода, которое мы рассмотрим, -

клавиатура. Клавиатура отделена от ЭВМ и подключено к системе

четырехжильным проводом. Хотя внутри клавиатуры и находится

отдельный микропроцессор, для его программирования нет простого

способа, так что мы оставим эту задачу инженерам фирмы IBM. Мы же

можем заняться информацией, которую клавиатура посылает в систему.

Цепь обслуживания клавиатуры на системной плате подключена к

системе прерываний. Кадый раз, как эта цепь регистрирует нажатие

клавиши, она возбуждает прерывание в системе. Это прерывание

передает управление обработчику прерываний от клавиатуры. Эта

процедура получает от клавиатуры данные и сохраняет их для

дальнейшего использования. Обработчик прерываний клавиатуры

обслуживает также специальные случаи, например, перезагрузка

системы (CTL-ALT-DEL) и снятие програмы (CTL-BREAK). Но мы не будем

рассказывать, как это делается, до следующей главы, поскольку все

это обслуживает встроенная система программ BIOS. Пока же осмотрим,

как можно управлять аппаратурой обслуживания клавиатуры.

Когда клавиатура посылает сигнал прерывания, он, прежде, чем

попадет в микропроцессор 8088, проходит через контроллер прерываний

8259. Этот контроллер обслуживает систему прерываний IBM PC почти

во всех ее аспектах.

Микросхема 8259 может обслужить до восьми прерывающих

устройств. К линиям прерываний в IBM PC подключен системный таймер,

клавиатура, адаптер асинхронной связи, фиксированный диск

(винчестер), накопитель на гибких магнитных дисках и печатающее

устройство. Остальные уровни прерываний доступны другим устройствам

ввода-вывода, подключенным к системному каналу ввода-вывода.

Конструктивно каждое из прерывающих устройств назначено к своему

входу прерывания микросхемы 8259. Вход прерывания, к которому

подключено прерывающее устройство, называется уровнем прерывания

этого устройства. Как мы сейчас увидим, микросхема 8259

упорядочивает приоритеты прерываний в соответствии с уровнем. На

Фиг. 8.3 показаны уровни прерываний каждого устройства ЭВМ.

Прерывание от каждого устройства можно по-отдельности

заблокировать или разблокировать. Микросхема 8259 работает точно

так же, как микропроцессор 8088, который имеет флаг прерываний,

разрешающий или запрещающий прерывания по командам STI и CLI. Но

8259 имеет восемь флагов прерываний - по одному на каждое возможное

прерывающее устройство. Эти флаги содержит регистр маски прерываний

IMR (Interrupt Mask Register), расположенный в порту по адресу 21H.

Бит 7 соответствует прерыванию 7, бит 6 - прерыванию 6, и так

далее. Если вы установите бит в 1, устройство не может вызвать

прерывание; если же бит сброшен в 0, контроллер прерываний передает

прерывание дальше, микропроцессору 8088. Разумеется, даже если

какое-либо прерывание разблокировано в регистре IMR, прежде чем это

прерывание сможет произойти, необходимо также сбросить флаг

прерываний в микропроцессоре 8088.

В зависимости от уровня прерывания контроллер назначает

приоритеты каждому прерывающему устройству. Уровень прерывания

определяется аппаратным подключением и не может быть изменен

программным путем. Прерывание номер 0 имеет высший приоритет,

прерывание номер 7 - низший. Если любые два устройства пытаются

одновременно возбудить прерывание, обслуживается устройство более

высокого приоритета. Обслуживание устройства более низкого

приоритета откладывается до тех пор, пока не будет обслужено

устройство с более высоким приоритетом. Микросхема 8259

автоматически реализует такое разбиение по приоритетам, но в

Уровень Устройство

--------------------------------------------------

0 Канал таймера 0

1 Клавиатура

2 -

3 Асинхронные коммуникации

4 Упорядоченные асинхронные коммуникации

5 Фиксированный диск

6 Дискета

7 Принтер

--------------------------------------------------

Фиг. 8.3 Уровни прерываний для микросхемы 8259

управлении прерываниями должна принимать участие и ваша программа;

именно, она должна сообщить контроллеру 8259 о том, что она

закончила работу с текущим прервавшим устройством. Такая команда

называется EOI (конец прерывания), она сообщает микросхеме 8259,

что обработка прерывания от устройства с наивысшим приоритетом

закончилась, и теперь вызвать прерывание в системе может устройство

с более низким приоритетом.

Контроллер 8259 также предотвращает прерывание микропроцессора

устройством с более низким приоритетом, если система в этот момент

обслуживает прерывание с более высоким приоритетом. Микросхема 8259

запоминает текущее активное прерывание, и считает, что

микропроцессор обслуживает это прерывание до тех пор, пока не

получит команду EOI. Микросхема 8259 делает это с помощью двух

внутренних регистров. Первый из них идентифицирует устройства,

которые к настоящему моменту выдали запрос на прерывание; этот

регистр называется регистром запросов прерываний IRR. Другой

регистр следит за текущими обслуживаемыми прерываниями, и

называется регистром обслуживаемых прерываний ISR. Если вы забыли

выполнить команду EOI, микропроцессор сможет прерывать только

программы обработки прерываний от устройств с боле высоким

приоритетом. Если вы не выполнили команду EOI после прерывания от

таймера, система попадает в останов. Так как таймер имеет наивысший

приоритет, отсутствие завершения его прерывания блокирует

возбуждение всех других прерываний.

Контроллер 8259, кроме того, упорядочивает все прерывания. Это

означает, что контроллер заставляет микропроцессор правильно

выбирать обработчик прерываний. Когда контроллер распознает

прерывание, он вынуждвет микропроцессор 8088 начинать работу по

адресу, указанному в одном из 256 векторов прерываний в первом

килобайте памяти. IBM PC отображает восемь уровней прерывания

контроллера 8259 на вектора прерываний от 8 до 0FH включительно.

Так, если поступает прерывание от клавиатуры (уровень 1),

начинается выполнение программы по адресу CS:IP, определенному

двойным словом в векторе прерываний 9, лежащему по адресам от 24H

до 27H.

Давайте посмотрим, как все это вместе происходит в IBM PC.

Сначала процедура самопроверки при включении питания (POST)

инициализирует контроллер 8259 с соответствующей управляющей и

упорядочивающей информацией. В это же время процедура POST снимает

маску прерываний от таймера, клавиатуры, а также прерываний от

дискет в регистре маски прерываний микросхемы 8259. После

завершения процедуры POST включается система прерываний по команде

STI. Теперь система готова к приему прерываний.

Вы нажимаете клавишу на клавиатуре. Клавиатура посылает символ

в системную плату, где он записывается в регистр и возбуждает

прерывание уровня 1. Далее начинает работу микросхема 8259. Она

устанавливает бит 1 в регистре IRR, отмечая запрос на прерывание.

Если не установлены ни нулевой, ни первый биты регистра ISR,

показывающие обслуживание прерывания с более высоким приоритетом,

контроллер возбуждает вход прерывания микропроцессора 8088. Когда

микропроцессор окажется готов к приему прерывания, он выполнит цикл

подтверждения прерывания. Микропроцессор 8088 поместит в стек

текущее содержимое регистра флагов, CS и IP. Контроллер 8259

отвечает на цикл подтверждения прерывания номером прерывания, в

данном случае это 9; затем контроллер установит равным единице бит

1 в регистре ISR, показывая, что прерывание 1 обслуживается. Тем

временем микропроцессор 8088 загружает пару регистров CS:IP из

ячеек 24H- 27H и начинает работу с указанного в них адреса.

Затем микропроцессор выполняет программу обработки прерываний

от клавиатуры. На Фиг. 8.4 показана схема обработчика прерывания от

клавиатуры. Обратите внимание, что первая команда, которую должна

выполнить эта программа, сохраняет содержимое регистра AX в стеке.

Это делается потому, что программа изменяет содержимое регистра AX.

Если обработчик прерываний не восстановит первоначальное значение

этого регистра перед возвратом в прерванную программу, возникнет

ошибка выполнения. Это ведь очень трудно - писать верно работающие

программы, если во время их выполнения содержимое регистров будет

произвольно изменяться другими программами. Если обработчику

прерывания потребуется больше регистров, то он должен все их

сохранить, а затем восстановить.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:14

Фиг. 8.4 Управление клавиатурой Page 1-1

PAGE ,132

TITLE Фиг. 8.4 Управление клавиатурой

0000 CODE SEGMENT

ASSUME CS:CODE

0000 KEYBOARD_INTERRUPT PROC FAR

0000 50 PUSH AX ; Сохранение регистра AX

0001 E4 60 IN AL, 60H ; Выборка номера клавиши

0003 50 PUSH AX ; Сохранение номера клавиши

0004 E4 61 IN AL, 61H

0006 8A E0 MOV AH, AL ; Сохранение текущего значения регистра AL

0008 0C 80 OR AL, 80H

000A E6 61 OUT 61H, AL ; Сигнал об успешном получении символа

000C 8A C4 MOV AL, AH

000E E6 61 OUT 61H, AL ; Возврат Клавиатуры в нормальный режим

0010 58 POP AX

; ...

0011 B0 20 MOV AL, 20H

0013 E6 20 OUT 20H, AL ; Сигнал об окончании прерывания

0015 58 POP AX ; Восстановление регистра AX

0016 CF IRET ; Возвращение в прерванную программу

0017 KEYBOARD_INTERRUPT ENDP

0017 CODE ENDS

END

Фиг. 8.4 Контроль клавиатуры

Обработчик прерывания читает код клавиши, так называемый код

сканирования, из порта 60H. Программа доложна "убрать" прерывание,

сообщив устройству ввода-вывода, что оно должно снять запрос на

прерывание. Программа сбрасывает прерывание от клавиатуры,

устанавливая бит 7 порта 61H. Это не только отменяет запрос на

прерывание, но и сообщает клавиатуре, что она может присылать

следующий символ в микропроцессор 8088. Теперь программа прерывания

может обработать код от клавиши.

Когда обслуживание прерывания завершено, обработчик прерывания

посылает в микросхему 8259 команду EOI. Она сбрасывает в 0 бит 1 в

регистре ISR, и теперь могут произойти любые ожидающие обслуживания

прерывания с меньшими приоритетами. Выполнение команды EOI состоит

в посылке в порт 20H кода 20H. Программа обработки прерывания

восстанавливает содержимое регистра AX, команда IRET

восстанавливает регистры флагов IP, CS, а также приводит флаги к их

первоначальному виду до прерывания.

Точно такая же последовательность событий происходит в случае

любого прерывания, исходящего от микросхемы 8259 и обслуживаемого

микропроцессором 8088. Единственная разница заключается в том,

какое именно прерывание возникает. От этого изменяется установка

битов в регистрах IRR и ISR, и номер вектора прерывания,

выдаваемого микропроцессору 8088. Обработчик прерывания должен все

также сохранять состояние микропроцессора в момент прерывания, а

программа должна выполнить команду EOI в конце обработки

прерывания, или когда определит, что выполняемая программа может

принять прерывание меньшего приоритета. И еще одно предостережение.

Ваша программа обработки прерывания не должена посылать две команды

EOI. Если выполняющаяся программа обработки прерывания прервала

работу обработчика с меньшим приоритетом, вторая команда EOI

поступит вместо этой команды от обработчика с меньшим приоритетом.

Это может привести к неприемлимым последствиям, почему вам и не

следует допускать такого наложения.

Итак, мы обработали прерывание от клавиатуры, правильно

установили все биты и выполнили все команды. Что же мы получили?

Клавиатура посылает в микропроцессор "коды сканирования". Их

значения отражают расположение клавиш на клавиатуре и не имеют

никакого отношения к символу, изображенному на клавише. Например,

клавише ESC возвращает код сканирования 1, клавиша "1" - код 2, и

так далее. Клавише DEL соответствует код сканирования 83. Каждая

клавиша имеет свой собственный уникальный код сканирования. Так что

данные из порта 60H не могут рассматриваться как символ кода ASCII.

Код сканирования нужно еще перекодировать в правильный код

символа.

Клавиатура передает несколько больше, чем только эти 83 кода

сканирования. Первые коды, от 1 до 83, известные как "коды нажатия"

клавиатура посылает, при нажатии клавиши. Когда клавиша

отпускается, клавиатура посылает другой код сканирования, "код

отпускания". Код отпускания формируется прибавлением числа 128 к

коду нажатия. То есть коды отпускания попадают в диапазон от 129 до

211; их легко распознать по 7-му биту, равному 1.

Поскольку клавиатура посылает различные коды для каждого

нажатия и отпускания клавиши, программа может отследить состояние

каждой клавиши. Вы узнаете о нажатии клавиши, получив от клавиатуры

код нажатия. Клавиша нажата, пока вы не получите код отпускания,

сообщающий, что клавиша отпущена. Для большинства клавиш эта

информация не важна, но для регистровых клавиш (например, клавиши

верхнего регистра) она решающая. Например, большие буквы

формируются только тогда, когда клавиша верхнего регистра нажата.

Так как такая информация доступна от любой клавиши клавиатуры IBM,

работа с регистровыми клавишами не составляет труда. Если вы

пожелаете изменить кодировку клавиатуры, устроив свое собственное

расположение клавиш, любая клавиша может трактоваться вами как

регистровая.

И наконец, каждая клавиша клавиатуры IBM имеет встроенный

механизм автоповторения. Если вы держите любую клавишу нажатой

более 1/2 секунды, клавиатура начинает посылать коды нажатия со

скоростью 10 раз в секунду. Это удобно для обычных клавиш, особенно

для клавиш управления курсором. Вы просто держите их нажатыми, и

курсор движется в нужное место. Но если у вас есть клавиши,

выполняющие некоторую работу только в момент первого нажатия (и не

может использовать автоповторение), вы опять-таки должны

отслеживать нажатия и отпускания этой клавиши. Только первый код

нажатия должен вызывать ее срабатывание, а остальные коды нажатия

вы должны игнорировать.


Untitled

Время суток

Канал 0 таймера 8253 имеет специальное назначение в IBM PC. Выход

этого канала таймера подключен к уровню прерывания 0 микросхемы

8259. Это означает, что всякий раз, когда выход канала 0 имеет

активный уровень сигнала, возникает прерывание (при условии, что

все остальное установлено корректно). Процедура самопроверки при

включении питания инициализирует канал 0 таймера, загружая в него

число 0. Это дает наибольшее (не наименьшее) значение счетчика,

которое может записать в него программа. Имея на входе частоту 1.19

МГц, счетчик считает обратно к нулю чуть быстрее, чем за 55

миллисекунд. Программа инициализации устанавливает таймер таким

образом, что он считает непрерывно. Это означает, что прерывание 0

возникает 18.2 раза в секунду.

Как мы увидим в следующей главе, встроенная система программ

BIOS использует это постоянное прерывание от таймера, чтобы следить

за текущим временем. BIOS продвигает часы текущего времени вперед

всякий раз, когда возникает прерывание. Затем, с помощью

соответствующих вычислений, Вы можете преобразовать число циклов

таймера в часы, минуты и секунды.

Почему же выбрано значение 18.2? Почему счетчик не

программируется так, чтобы давать прерывание 20 раз в секунду, или

другое "хорошее" число раз? Это объясняет следующий пример.

Системный таймер может выполнять функцию измерения времени

отличного от времени дня. Время дня прекрасно подходит для

определения интервалов времени, измеряемых в секундах или минутах.

Но в некоторых ситуациях, возникающих при управлении

вводом-выводом, нужно определять интервалы времени порядка одной -

двух миллисекунд. Обычно программы отсчитывают такие интервалы с

помощью временного цикла. Программа для такого цикла выглядит

примерно так:

MOV CX, LOOP_VALUE

HERE:LOOP HERE

Вы выбираете константу LOOP_VALUE так, что цикл выполняется в

точности нужное число раз. Это очень хороший метод, если вам нужна

задержка на определенное время. В выше приведенном примере

начальное значение константы LOOP_VALUE, равное 0FFFFH, дает время

выполнения около 250-миллисекунд.

Но предположим, что вы хотите понаблюдать за внешним событием,

и определить, сколко времени займет его наступление. Можно использовать

вариант временного цикла такого, например, вида:

MOV CX, 0

HERE:

; --- проверка возникновения события

IN AL, DX

TEST AL, MASK_BIT

LOOPNE HERE

DONE:

; --- CX содержит число итераций цикла

Таким способом вы считаете число итераций цикла, чтобы

вычислить затраченное на него время. Этот метод предполагает, что

событие возникнет до того, как содержимое регистра CX второй раз

достигнет 0. Но если вам нужно измерить что-то с точностью до

микросекунд, этот метод не удобен, так как каждая итерация цикла

требует от 10 до 20 микросекунд. Системный таймер дает лучшее

решение. Поскольку он изменяет свое значение каждые 840 наносекунд,

вы сможете определить длительность события с точностью до

микросекунды.

На Фиг. 8.5 показан пример программы, вычисляющей время события

с помощью системного таймера. В этом примере в качестве

регистрируемого события используется канал 2 таймера. В первой

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:19

Фиг. 8.5 Управление системным таймером Page 1-1

PAGE ,132

TITLE Фиг. 8.5 Управление системным таймером

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 TIMER PROC FAR

0000 1E PUSH DS ; Занесение адреса возврата

0001 B8 0000 MOV AX, 0

0004 50 PUSH AX

0005 B0 B6 MOV AL, 10110110B ; Выборка таймера 2

0007 E6 43 OUT 43H, AL

0009 B8 0500 MOV AX, 500H

000C E6 42 OUT 42H, AL ; Таймер 2 установлен на 500 отсчетов

000E 8A C4 MOV AL, AH

0010 E6 42 OUT 42H, AL

0012 E8 001D R CALL LOW_TO_HIGH ; Выборка времени первого перехода с 0 на 1

0015 8B D8 MOV BX, AX ; Сохранение значения в регистре BX

0017 E8 001D R CALL LOW_TO_HIGH ; Выборка времени второго перехода с 0 на 1

001A 2B D8 SUB BX, AX ; Вычитая получаем длину цикла

001C CB RET

001D TIMER ENDP

Фиг. 8.5 Управление системным таймером (начало)

;--------------------------------------------

; Эта подпрограмма ждет перехода с нижнего уровня

; сигнала на верхний (с 0 на 1) в таймере 2

; и возвращает в регистре AX значение счетчика таймера 0

;--------------------------------------------

001D LOW_TO_HIGH PROC NEAR

001D E4 62 IN AL, 62H ; Проверка разряда таймера 2

001F A8 20 TEST AL, 20H

0021 75 FA JNZ LOW_TO_HIGH ; Цикл: сигнал таймера 2 на

; нижнем уровне

0023 WAIT_HIGH:

0023 E4 62 IN AL, 62H ; Проверка разряда таймера 2

0025 A8 20 TEST AL, 20H

0027 74 FA JZ WAIT_HIGH ; Цикл: сигнал таймера 2 на

; верхнем уровне

0029 B0 00 MOV AL, 0 ; Послать команду в регистр управления тай-

002B E6 43 OUT 43H, AL ; мером 2, которая "замораживает" таймер 2

002D 90 NOP

002E 90 NOP ; Задержка, необходимая для 8253

002F E4 40 IN AL, 40H ; Чтение младшего байта счетчика

0031 8A E0 MOV AH, AL

0033 90 NOP

0034 E4 40 IN AL, 40H ; Чтение старшего байта счетчика

0036 86 E0 XCHG AH, AL

0038 C3 RET ; Возвращение значения в AX

0039 LOW_TO_HIGH ENDP

0039 CODE ENDS

END

Фиг. 8.5 Системный таймер (продолжение)

части программы этот канал таймера загружается известным значением.

Здесь мы произвольно выбрали число 500H. Обратите внимание, что эта

часть программы идентична способу генерации звуков с помощью втого

канала таймера.

Наша программа вызывает подпрограмму LOW_TO_HIGH, которая

возвращает значение таймера в тот момент, когда на выходе канала 2

таймера отмечается переход от низкого к высокому уровню. Программа

рследит именно за переходом; если бы она регистрировала только

высокий уровень, было бы неизвестно, стал ли сигнал высоким только

что или уже готов стать низким. Подпрограмма посылает нуль в

управляющий регистр таймера (порт 43H), чтобы "заморозить" текущее

значение канала 0. Это позволяет ей прочитать текущее значение

таймера, продолжающего счет. Если бы программа временно не

зафиксировала таймер, она не смогла бы прочитать без ошибки его

16-битовое значение.

Обратим внимание на то, что подпрограмма на Фиг. 8.5 содержит

несколько команд NOP. Эти команды записаны в программе для выдержки

временных соотношений. Если очень внимательно прочитать инструкции

по микросхеме 8253, мы заметим, что между командами IN и OUT,

выполняемыми этой микросхемой, проходит не меньше 1 микросекунды.

Команда NOP занимает как раз достаточно времени, чтобы исключить

нарушение требований микросхемы по времени.

После возврата из подпрограммы программа сохраняет в регистре

BX значение счетчика таймера во время первого перехода с низкого

уровня на высокий. Затем программа снова вызывает подпрограмму,

чтобы зарегистрировать следующий переход с низкого уровня на

высокий на выходе канала 2 таймера. Потом она вычитает одно число

из другого, чтобы определить время цикла канала 2.

Мы уже говорили о том, что загрузка регистра 0 таймера

значением счета 0 - очень полезна. Данная программа подтверждает

это, так как она вычитает два значения таймера, не обращая внимания

на то, какое из них больше, а какое меньше. Так как канал 0 таймера

работает асинхронно по отношению к этой программе, нет никакой

гарантии, что первое читаемое из него число больше второго.

Например, предположим, что первый переход с низкого на высокий

уровень происходит, когда таймер 0 имеет значение 100H. После 500H

циклов значение числа в таймере будет 0FC00H. Счетчик таймера 0

автоматически "проскочил" от значения 0 к значению 0FFFFH, и

значение прочитанное вторым оказалось численно больше первого. Но

из-за того, что регистр таймера снова начинает счет со значения

0FFFFH, мы всегда можем вычитать эти два числа. При этом иногда

будет появляться перенос, иногда нет, но разность этих двух чисел

всегда будет равна числу отсчетов.

Чтобы убедить вас в правильности этого положения, рассмотрим

случай, когда счетчик загружается числом 8000H. Если первый переход

возникает при значении 6000H, второй появится при значении 5B00H, и

разница между ними составляет 500H. Но если первый переход

возникает при значении 100H, второй возникает при значении 7C00H, и

разница станет равна 8500H. Чтобы правильно отреагировать на эту

ситуацию, программа должна была бы проверить, не произошло ли

переполнение счетчика за время отсчета.

При выполнении этой программы вы обнаружите, что значение в

регистре BX составляет около 0A00H, и не равно ожидаемому значению

500H. Так происходит потому, что таймер работает в режиме

уменьшения содержимого счетчика на два по каждому временному

импульсу. Чтобы разобраться в работе микросхемы 8253, нужно

ознакомиться с инструкцией по ее программированию.

Фиг. 8.6 дает сводку для управляющего слова микросхемы 8253.

Для настройки одного из каналов на конкретный режим работы вы

выводите это управляющее слово в порт 43H. Мы уже встречались с

выводом некотрых значений в порт 43H. Чтобы "заморозить" счетчик,

мы послали в этот порт нуль, а для настройки генератора тональности

- код 0B6H. Посмотрим, откуда берутся эти значения.

Два старших бита управляющего слова определяют канал таймера.

Следующие два бита - выполняемую операцию. Когда мы выводим

значение 0, выбирается таймер 0 и запирание данных в счетчике.

Следующие 3 бита задают режим работы выбранного таймера. Эти биты

не играют роли, когда счетчик заперт, но нужны при инициализации

таймера. Оставшийся бит определяет, будет ли счетчик работать как

16-битовое двоичное число или как четырехзначное десятичное в

двоичном представлении.

A

Формат управляющего слова

D7 D6 D5 D4 D3 D2 D1 D0

ЪДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДї

і SC1і SC0і RL1і RL0і M2 і M1 і M0 і BCDі

АДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДЩ

Определение управления

SC - выбор счетчика:

SC1 SC0

ЪДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДї

і 0 і 0 і задать счетчик 0 і

ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 0 і 1 і задать счетчик 1 і

ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 1 і 0 і задать счетчик 2 і

ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 1 і 1 і неопределено і

АДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДЩ

RL - чтение/загрузка:

RL1 RL0

ЪДДДДВДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і 0 і 0 і Операция запирания счетчика (см. і

і і і раздел процедуры READ/WRITE) і

ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 1 і 0 і Считать/загрузить старший байт і

ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 0 і 1 і Считать/загрузить младший байт і

ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 1 і 1 і Считать/загрузить младший байт, і

і і і затем - старший і

АДДДДБДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

M - режим:

M2 M1 M0

ЪДДДВДДДВДДДВДДДДДДДДДДДї

і 0 і 0 і 0 і Режим 0 і

ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ

і 0 і 0 і 0 і Режим 1 і

ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ

і X і 1 і 0 і Режим 2 і

ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ

і X і 1 і 1 і Режим 3 і

ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ

і 1 і 0 і 0 і Режим 4 і

ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ

і 1 і 0 і 1 і Режим 5 і

АДДДБДДДБДДДБДДДДДДДДДДДЩ

BCD:

ЪДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і 0 і 16-битовый двоичный счетчик і

ГДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 1 і десятичный в двоичном представлении і

і і (BCD) счетчик (4-х разрядный) і

АДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Фиг. 8.6 Программирование таймера/счетчика

(с разрешения фирмы Intel; приоритет Intel 1981г.)A

Управляющий код 0B6H, который использовался для генерации

тональности, можно расписать таким образом:

0B6H = 10110110B = 01 11 011 0

Это управляющее слово выбирает канал 2. Счетчик этого канала

работает с 16-битовым значением, младший байт которого загружается

в счетчик первым. Последный бит показывает, что счет будет

двоичным. Можно видеть также, что выбирается третий режим работы.

Микросхема 8253 может работать в шести разных режимах. Но для

наших целей, в конфигурации системы IBM, удобны только два из них.

Режим 3 устанавливается по умолчанию для всех трех каналов таймера.

Это режим работы генератора прямоугольных импульсов: половину

периода выход канала дает нижний уровень, а другую половину периода

- верхний. Счетчик работает в этом режиме, вычитая из своего

значения по два, а не по одному. Во время первого обратного счета

выход низкий, а затем, во время второго счета - высокий. Так как

счетчик считает по два, выход имеет высокий уровень точно половину

заданного времени, и низкий тоже точно половину. Из-за того, что

канал 0 таймера обычно работает в режиме 3, пример на Фиг. 8.5

заканчивает счет со значением счетчика 0A00H, а не 500H, как

ожидалось. Каждый отсчет таймера уменьшает содержимое счетчика на

два.

Так как счетчик считает двойками, он переполняется каждые 27

микросекунд. Если вы хотите измерять события более длительные, то

вам придется воспользоваться другим способом измерения времени.

Другой режим таймера, который используется в IBM PC - это режим

0. Этот режим называют прерыванием по завершению счета. В этом

режиме таймер не работает непрерывно. После его установки таймер до

тех пор не начинает считать (единицами), пока в него полностью не

будет загружено число. Затем счетчик считает в сторону уменьшения с

частотой синхроимпульсов, пока не достигнет нуля. В этот момент его

выход становится высоким. Поскольку выход канала 0 таймера

подключен к прерыванию 0 контроллера 8259, в системе возникает

прерывание.

Режим прерывания по завершению счета полезен, если вы хотите в

определенный момент подать программе сигнал с помощью прерывания.

Так как счетчик ограничен шестнадцатью битами, максимальный

отсчитываемый интервал времени составляет 55 миллисекунд. Если этот

интервал слишком мал, нужен другой метод измерения времени.

Если вы хотите измерять интервал времени в секундах, нужно

оставить таймер в его обычном режиме работы. Система BIOS позволяет

захватывать управление системой каждые 55 миллисекунд, и в каждый

такой момент вы можете решить, не исчерпался ли нужный промежуток

времени.

Если время нужной вам задержки находится между 55

миллисекундами и 5 секундами, можно использовать метод без

использования программ BIOS. Например, вам хочется сделать задержку

на 150 миллисекунд. Используя режим прерывания по завершению счета,

вы настраиваете таймер на прерывание через 50 миллисекунд (этому

соответствует значенние счетчика около 59500). Обработчик

прерывания программируется так, чтобы, получая управление первые

два раза, он заново устанавливал таймер на 50 миллисекунд. По

третьему прерыванию от таймера, когда 150 миллисекунд исчерпаны,

можно предпринять нужные действия.

При организации задержек через таймер всегда нужна некоторая

осторожность. Как упоминалось выше, канал 1 таймера выполняет одну

важную аппаратную функцию. Если вы модифицируете число в канале 1,

ваша программа может немедленно разрушиться. Использование канала 2

таймера безопасно. Этот канал подключен только к динамику и выходу

кассетного магнитофона. Отсюда, очевидно, следует, что нельзя

использовать канал 2 таймера для отсчета промежутков времени в одно

время с попытками воспроизводить мелодии через динамик. И наконец,

BIOS пользуется услугами таймера 0 для различных системных функций.

При обсуждении BIOS будет видно, что прерывание по времени суток

управляет не только текущим временем, но также обслуживает и

двигатель накопителя на дискетах. Перед тем, как изменять настройку

канала 0 таймера для любых целей, нужно понять, какие существующие

функции вы можете при этом изменить.


Untitled

Возможности системы

Эта часть главы описывает программные аспекты некоторых

системных возможностей. Не каждая система обладает теми

возможностями, которые мы опишем. Мы обсудим те из них, которые вы

сможете обнаружить в большинстве систем. Обсуждение коснется двух

видео адаптеров, адаптера дисковода, адаптера печатающего

устройства, адаптера асинхронных коммуникаций и игрового

адаптера.


Untitled

Видеоадаптеры

Система IBM PC, которая попадет к вам в руки, может быть

снабжена одним из двух различных дасплейных адаптеров. Один из них

- адаптер монохромного дисплея и принтера (мы будем называть его

монохромным адаптером, или иногда - черно-белой платой). Такой

видео адаптер лучше всего подходит для деловых приложений.

Черно-белая плата управляет монохромным дисплеем фирмы IBM.

Монохромный адаптер работает в одном режиме, и может показывать на

экране текст только одного цвета. Этот адаптер также содержит

аппаратные узлы, необходимые для управления печатающим устройством.

Такая комбинированная плата удобнее всего для деловых приложений.

Другой видео адаптер - адаптер цветного графического

монитора (мы будем называть его цветной платой). Цветная плата

работает с дисплеями, использующими нормальные телевизионные

частоты. Этот адаптер дает цветные изображения, а не только

одноцветные, как монохромный. У него есть несколько режимов работы,

включая такие, в которых можно управлять отдельными точками

экрана.


Untitled

Адаптер монохромного дисплея и принтера

Сначала поговорим о монохромном дисплее, он - простейший из

двух адаптеров. Мы отложим описание той части платы, которая

работает с печатающим устройством, пока не дойдем до адаптера

печатающего устройства. Схема адаптера печатающего устройства на

монохромной плате идентична схеме отдельного адаптера печатающего

устройства, поэтому способы программирования их также одинаковы.

Черно-белая плата работает в одном режиме. Этот видео адаптер

предназначен для вывода 25 строк символов по 80 символов в строке.

Такое построение изображения называют режимом 80*25. Вы помещаете

символы на экран, записывая коды ASCII в буфер дисплея. Буфер

дисплея - это специальная область памяти, расположенная в адресном

пространстве по адресу 0B0000H. Эта память - часть платы адаптера,

а не системной памяти. Всякий раз, когда вы записываете символ в

коде ASCII в буфер дисплея, он появляется в соответствующем месте

экрана. Преобразование символа из кода ASCII в точки на экране

выполняется аппаратно.

Каждый символ на дисплее имеет атрибут. Атрибуты символов

определяют вид, в которым адаптер выводит символы на экран. На Фиг.

8.7 показаны атрибуты символов и их значения. Вам нужно знать эти

значения, так как их тоже надо помещать в буфер дисплея. Позиция

каждого символа в буфере дисплея занимает два байта. Четный байт

пары содержит код символа, а нечетный - значение атрибута. По Фиг.

8.7 можно определить значение атрибута, с которым выводить на экран

символ. Обычно выводится белый (на самом деле зеленый) символ на

черном фоне. Для этого случая значение атрибута равно 07H. Чтобы

сделать изображение негативным, нужно изменить значение атрибута на

70H. Атрибут 00H делает символ невидимым. Хотя код ASCII символа и

записан в байт символа, значение атрибута не дает символу появиться

на экране.

Дисплейный буфер черно-белой платы содержит 4K байта памяти.

Этого достаточно, чтобы иметь байты символов и атрибутов для каждой

из 2000 позиций экрана. Первый символ буфера дисплея появляется в

верхнем левом углу экрана, следующие два байта изображают следующий

символ справа, и так далее. Первый символ второй строки находится в

байтах 160 и 161. Теперь можно определить адрес любого символа на

экране. Сначала определим позицию в верхнем левом углу экрана, как

находящуюся в строке 0 и колонке 0; нижний правый угол находится в

строке 24 и колонке 79. Тогда формулой вычисления адреса для

произвольной строки и позиции будет

адрес = 2*(строка*80 + колонка) + 0B0000H

Умножение на 2 выравнивает адрес к двум байтам на позицию.

Прибавление значения 0B0000H отражает начальный адрес буфера

дисплея. Обычно в программе либо регистр DS, либо регистр ES

загружается значением 0B0000H, и в остальном программа работает со

смещениями в буфере дисплея.

Значение Атрибут

----------------------------------------------------------

00H ничего не выводится

01H подчеркнутые символы

07H белый символ на черном фоне

0FH ярко-белый символ, черный фон

70H черный символ, белый фон

80H при добавлении к любому другому символ

мигает

----------------------------------------------------------

Фиг. 8.7 Символьные атрибуты для монохромного адаптера

На Фиг. 8.8 в качестве примера для монохромного дисплея

приведена программа, которая перемещает содержимое экрана на одну

позицию вправо. Она выбрасывает крайнюю справа колонку, а слева

помещает колонку из пробелов. Если вы хотите испытать эту

программу, но имеете в своей системе только цветную плату, то

программа работает при установке сегмента DISPLAY на адрес 0B800H.

Оба видео адаптера очень похожи при работе в текстовом режиме, и

различаются только адресами памяти и ввода-вывода.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:26

Фиг. 8.8 Горизонтальный сдвиг вправо Page 1-1

PAGE ,132

TITLE Фиг. 8.8 Горизонтальный сдвиг вправо

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 DISPLAY SEGMENT AT 0B800H

0000 DISPLAY_START LABEL WORD

0FA0 ORG 4000

0FA0 DISPLAY_END LABEL WORD

0FA0 DISPLAY ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 SIDE_SCROLL PROC FAR

0000 1E PUSH DS

0001 B8 0000 MOV AX, 0

0004 50 PUSH AX

0005 B8 ---- R MOV AX, DISPLAY

0008 8E D8 MOV DS, AX

000A 8E C0 MOV ES, AX

ASSUME DS:DISPLAY, ES:DISPLAY

;----- Заполнение столбца 79 пробелами

Фиг. 8.8 Горизонтальный сдвиг вправо

000C B9 0019 MOV CX, 25

000F 8D 3E 004E R LEA DI, DISPLAY_START+78

0013 B8 0720 MOV AX, 720H

0016 BLANK:

0016 89 05 MOV [DI], AX

0018 81 C7 00A0 ADD DI, 160

001C E2 F8 LOOP BLANK

;----- Сдвиг вправо

001E B9 07D0 MOV CX, 2000

0021 8D 36 0F9E R LEA SI, DISPLAY_END-2

0025 8D 3E 0FA0 R LEA DI, DISPLAY_END

0029 FD STD

002A F3/ A5 REP MOVSW

002C A3 0000 R MOV DISPLAY_START, AX

002F CB RET

0030 SIDE_SCROLL ENDP

0030 CODE ENDS

END

Фиг. 8.8 Горизонтальный сдвиг направо

Программа на Фиг. 8.8 делает этот горизонтальный сдвиг очень

простым способом. Правая колонка заполняется пробелами в результате

записи 25 пробелов через каждые 160 байт памяти. Затем программа

сдвигает весь дисплейный буфер по памяти на один байт вверх.

Поскольку буфер дисплея непрерывно продолжается строка за строкой,

символ, который был в колонке 79 строки 0, после сдвига оказывается

в колонке 0 строки 1. Наконец, программа заменяет первый символ

буфера пробелом.

Монохромный адаптер имеет также несколько портов ввода-вывода,

которые можно использовать при программаровании дисплея, но они

будут рассмотрены очень бегло. Конструкция монохромного адаптера

использует эти порты ввода-вывода в первую очередь для удобства

аппаратной организации. Кроме того, программирование черно-белой

платы в общих чертах аналогично цветной плате. Поскольку цветная

плата имеет намного больше специальных возможностей в использовании

портов ввода-вывода, мы поговорим о них в основном в следующем

разделе.

Монохромный адаптер порождает сигналы вертикальной и

горизонтальной синхронизации, необходимые для нормального

изображения, с помощью контроллера электронно-лучевой трубки 6845

фирмы Motorola. Эта микрсхема содержит два порта ввода-вывода,

имеющих адреса 3B4H и 3B5H. Адаптер также имеет управляющий порт

3B8H и порт состояния 3BAH. После инициализации адаптера

модифицировать коды в этих портах вряд ли стоит. Существуют и

другие режимы работы, возможные для монохромной платы, но мы

сконцентрируем наше внимание на цветной плате. Полное, детальное

описание портов ввода-вывода монохромного адаптера можно найти в

техническом описании.


Untitled

Адаптер цветного графического монитора

Другой видеоадаптер, который может быть установлен на IBM PC -

адаптер цветного графического монитора. Цветная плата разработана в

фирме IBM для подключения к устройствам подобным телевизору.

Черно-белый адаптер работает только с монохромным дисплеем фирмы

IBM, а цветную плату можно подключить к любому цветному или

черно-белому монитору, использующему стандартные телевизионные

сигналы. Или, вы можете отдельно приобрести радиочастотный

модулятор, который позволит вам подключить выход цветной платы к

вашему домашнему телевизору. Цветная плата может быть подключена к

разнообразным мониторам, так как она работает на телевизионных

частотах.

Цветная плата имеет много режимов работы, и программы фирмы IBM

обслуживают некоторые из этих режимов. Обслуживание других режимов

оставлено вам. Цветная плата дает намного больше гибкости, чем

монохромная.

Для цветного графического адаптера возможны два текстовых

режима. Один текстовый режим формирует экран, имеющий 25 строк по

80 символов, идентичный монохромному адаптеру. Другой текстовый

режим уменьшает количество символов в строке до 40. Этот режим с

меньшим разрешением необходим, если вы хотите использовать цветную

плату или с монитором, имеющим меньшее разрашение, или с домашним

телевизором, так как 80-символьное изображение может оказаться

нечитабельным при некоторых изображениях. Чтобы использовать все

возможности цветной платы, вы должны работать с высококачественным

монитором. Монитор с высокой разрешающей способностью позволит вам

без искажений выводить полные 80-символьные строки.

Цветная плата имеет также два графических режима. В этих

режимах вы можете управлять каждой отдельной точкой экрана. В

текстовых режимах независима каждая символьная позиция, а в

графических режимах независимо управление каждой точкой.

Графический режим среднего разрашения дает 200 строк растра, по 320

точек в строке. Каждая из этих точек может иметь один из четырех

возможных цветов. Это означает, что любая точка управляется двумя

битами. Графический режим высокого разрешения дает 200 строк

растра, каждая из которых состоит из 640 точек; в этом режиме

возможны только два цвета, черный и белый.


Текстовый режим

Два текстовых режима цветного графического адаптера дают

простой способ вывода символов на экран. В текстовых режимах

цветная плата аналогична монохромному адаптеру, но она позволяет

указать цвет каждого символа. Так же, как и в монохромной плате,

каждая символьная позиция цветной платы имеет байт атрибутов. Этот

Номер бита 7 6 5 4 3 2 1 0

ЪДДВДДДДДДДДВДДВДДДДДДДДї

і Мі Ф О Ні Яі СИМВОЛ і Фиг. 8.9 Байт атрибута

АДДБДДБДДБДДБДДБДДБДДБДДЩ для ЦГА

- 289 -

байт атрибутов определяет цвета символов и фона каждой позиции.

На Фиг.8.9 показана структура байта атрибутов в цветном

текстовом режиме. Три бита, помеченных Ф О Н, определяют один из

восьми возможных цветов фона. Три бита СИМВОЛ определяют цвет

символа. Бит Я также влияет на цвет символа: его установка делает

цвет символа более ярким. Тем самым обеспечивается выбор одного из

16 цветов символа. Байт атрибута управляет единственным символом,

позволяя выбрать любую комбинацию цветов фона и символа каждой

позиции. Старший бит, помеченный буквой М, обычно вызывает мигание

символа. Установка бита мигания равным 1 заставляет контроллер

дисплея переключать цвет символа между его цветом и цветом фона со

скоростью примерно четыре раза в секунду. Так как символ,

изображенный в цвете фона, невидим, то получается эффект мигания.

Можно заменить бит мигания четвертым битом цвета фона, разрешив при

этом 16 цветов фона и 16 цветов символа. Это делает один из битов

регистра выбора цветов. Еще надо обратить внимание на то, что

одинаковая установка цветов символа и фона означает, что вы не

видите символ. Символ есть, но это все равно что пытаться увидеть

северного медведя в пургу - все одного цвета. На Фиг. 8.10 показаны

все 16 цветов, возможных в текстовом режиме.

I R G B Цвет

-------------------------------------

0 0 0 0 черный

0 0 0 1 синий

0 0 1 0 зеленый

0 0 1 1 морской волны

0 1 0 0 красный

0 1 0 1 магента

0 1 1 0 коричневый

0 1 1 1 светло-серый

1 0 0 0 темно-серый

1 0 0 1 голубой

1 0 1 0 светло-зеленый

1 0 1 1 светлый морской

1 1 0 0 розовый

1 1 0 1 светлый магента

1 1 1 0 желтый

1 1 1 1 белый

------------------------------------------- Фиг. 8.10 Цвета

(Фирма IBM, приоритет 1981 г.)

Если вы сравните байт атрибута для ЦГА с атрибутами для

монохромного адаптера на Фиг. 8.7, то увидите, что они аналогичны.

Конечно, вы не можете указать цвета монхромному адаптеру, но все

остальное совпадает. Поскольку цветная плата не поддерживает

атрибут подчеркивания, то установка монохромного атрибута

подчеркивания соответствует голубому символу на черном фоне.

Такая организация байта атрибутов является попыткой сделать два

видео адаптера по-возможности совпадающими. Каждый символ в буфере

дисплея находится по четному адресу, а байт атрибутов - по

нечетному. Память цветного дисплея находится на видео адаптере, но

по другому адресу: у монохромного дисплея она имеет адрес 0B0000H,

а у цветного - 0B8000H. О сходстве дисплеев говорит то, что если

при модификации программы для монохромного дисплея на Фиг. 8.8,

изменить содержимое указателя AT сегмента на значение 0B800H,

программа будет верно работать и на цветной плате. То есть одна и

та же программа работает на любом видео адаптере при минимуме

изменений.

Для управления адаптером на цветной плате также используется

контроллер ЭЛТ 6845 фирмы Motorola. Два порта ввода-вывода этого

контроллера имеют адреса ввода-вывода 3D4H и 3D5H. На самом деле

контроллер 6845 имеет 18 внутренних регистров. Доступ ко всем

регистрам осуществляется с помощью двух портов ввода-вывода и

косвенной адресации. Для обращения к регистру микросхемы 6845,

нужно сначала загрузить индекс регистра в порт 3D4H, а затем читать

этот регистр или записать в него данные через порт 3D5H.

Приведем пример, чтобы пояснить, как работает контроллер. На

Фиг. 8.11 перечислены все 18 регистров микросхемы 6845. В примере

мы используем только регистры R10 и R11. Эти регистры определяют

начальную и конечную строки растра одного знакоместа для курсора.

Каждый символ, порождаемый цветной платой, состоит из восьми строк

растра, имеющих номера от 0 до 7. Вы можете поместить курсор в

любых из этих восьми строк. Регистр R10 сообщает микросхеме 6845,

на какой строке начинается курсор, а регистр R11 определяет

последнюю строку курсора. ROM BIOS инициализирует курсор,

находящийся на строках 6 и 7; это делается загрузкой числа 6 в R10

и числа 7 в R11.

Прогамма на Фиг.8.12(а) модифицирует курсор цветной графической

платы. Она смещает курсор так, что он оказывается на верхних пяти

строках растра вместо нижних двух. Сначала программа загружает в

индексный регистр контроллера 6845 (3D4H) число 10, а затем

записывает номер начальной строки, равный 0, в регистр данных

(3D5H). Затем, поместив 11 в индексный регистр, она устанавливает

номер конечной строки равным 4. Теперь курсор имеет вид мигающего

блока в верхней части позиции символа, а не митающего

подчеркивания. Подобный этому способ модификации курсора

используется несколькими редактирующими программами (редакторами)

персональной ЭВМ, включая встроенный редактор интерпретатор языка

Бейсик. При установке режима вставки во время редактирования,

курсор становится жирнее; интерпретатор Бейсика делает это, меняя

параметры в микросхеме 6845.

Вернувшись к Фиг. 8.11, можно увидеть, что в микросхеме 6845

есть и другие регистры. Большинство из них управляет сигналами

горизонтальной и вертикальной синхронизации телевизионного растра.

Вы можете модифицировать коды в этих регистрах для каких-либо

действий дисплеем. Например, команда MODE системы DOS, которая

может сдвигать катринку на экране влево и вправо, модифицирует

регистр R2, задающий положение строчного синхроимпульса.

Если вы захотите поэкспериментировать с этими регистрами, вам

придется писать короткие программы, делающие требуемые изменения.

ЪДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДВДДДДДДДДВДДДДДДДДї

іРегистр і і Объект в і і і

і # і Регистр хранит і программе і чтение і запись і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R0 і Гориз. сумма і Символ і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R1 і Выводимая горизонталь і Символ і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R2 і Гориз. поз. синхронизации і Символ і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R3 і Гориз. ширина синхронизации і Символ і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R4 і Вертикальный итог і Симв. ряд і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R5 і Общая вертикальн.коррекция іСкан.строкиі Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R6 і Выводимая вертикаль і Симв. ряд і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R7 і Поз. вертикальной синхрон. і Симв. ряд і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R8 і Режим совмещения і - і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R9 і Макс.адрес скан. строки іСкан.строкиі Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R10 і Начало курсора іСкан.строкиі Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R11 і Конец курсора іСкан.строкиі Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R12 і Начальный адрес (гориз.) і - і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R13 і Начальный адрес (строка) і - і Нет і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R14 і Курсор (Гориз.) і - і Да і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R15 і Курсор (Строка) і - і Да і Да і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R16 і Световое перо (гориз.) і - і Да і Нет і

ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ

і R17 і Световое перо(строка) і - і Да і Нет і

АДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДДБДДДДДДДДЩ

Фиг. 8.11 Регистры микросхемы 6845

(с разрешения фирмы Motorola)

Если попытаться изменять регистры с помощью утилиты DEBUG, то это

может не сработать. Регистры R14 и R15 управляют позицией курсора,

и если утилита DEBUG переместит курсор между вашими обращениями к

индексному регистру и к регистру данных, изменения не произойдет.

Так получилось потому, что утилита DEBUG изменила содержимое

индексного регистра микросхемы 6845, и он уже больше не указывает

на тот внутренний регистр, который вы хотели изменить.

Другая интересная регистровая пара микросхемы 6845 - пара

регистров начального адреса R12 и R13. Адаптер цветного

графического дисплея имеет 16K байт памяти, а монохромная плата 4K

байта. Дополнительная память в цветной плате используется для

графических режимов, но она целиком в вашем распоряжении и в

текстовых режимах. Для текстового режима 80*25 требуется 4K байта

памяти, так что в буфере есть место для четырех разных страниц

памяти. Сдвигать данные на экране можно переносом данных с одного

места на другое, как это делалось на Фиг. 8.8. В случае же

вертикального сдвига нужно лишь изменить начальный адрес в

микросхеме 6845. Обычно начальный адрес равен 0. Если вы измените

его на 80 (число символов в строке), отображение будет начинаться

со второй строки. Это даст немедленный эффект сдвига всех данных

экрана вверх на одну строку.

На самом деле вы не изменяли данные, а сдвинули начало

сканирования памяти дисплея. Можно рассматривать дисплей как окно

80*25, через которое мы смотрим в 8192-символьный буфер дисплея.

При использовании начального адреса для сдвига данных возникают

сложности, когда вы приближаетесь к границе 16K байт. В этой точке

отображение "заворачивается". Верхние строки извлекаются из конца

буфера, а нижние строки - из его начала. Вы, конечно, сможете

решить эту проблему, но для этого потребуются некоторые размышления

и эксперименты.

[Прим. перев.: стр. 252 текста оригинала в моей копии отсутствует.

Очевидно, на ней находится только начало Фиг. 8.12]

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:31

Фиг. 8.12 Программа управления цветным дисплеем Page 1-1

PAGE ,132

TITLE Фиг. 8.12 Программа управления цветным дисплеем

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 DISPLAY_BUFFER SEGMENT AT 0B800H

0000 DISPLAY_START LABEL WORD

0000 DISPLAY_BUFFER ENDS

= 03D4 CRT_INDEX EQU 03D4H

= 03D5 CRT_DATA EQU 03D5H

= 03DA CRT_STATUS EQU 03DAH

= 000A CURSOR_START EQU 10 ; Регистры управления курсором

= 000B CURSOR_END EQU 11 ; в контроллере дисплея 6845

0000 CODE SEGMENT

ASSUME CS:CODE

0000 COLOR_GRAPHICS PROC FAR

0000 1E PUSH DS ; Адрес возврата в ДОС

0001 2B C0 SUB AX, AX

0003 50 PUSH AX

Фиг. 8.12 Программа управления цветным дисплеем (начало)

;----- Фиг. 8.12 (а) Изменение курсора

0004 BA 03D4 MOV DX, CRT_INDEX

0007 B0 0A MOV AL, CURSOR_START ; Установка индексного регистра

0009 EE OUT DX, AL ; 6845 на регистр начала курсора

000A 42 INC DX

000B B0 00 MOV AL, 0

000D EE OUT DX, AL ; Установка начала курсора

000E 4A DEC DX

000F B0 0B MOV AL, CURSOR_END

0011 EE OUT DX, AL ; Выбор регистра конца курсора

0012 42 INC DX

0013 B0 04 MOV AL, 4

0015 EE OUT DX, AL ; Установка конца курсора

;----- Фиг. 8.12 (б) Использование регистра состояния 6845

0016 B8 0002 MOV AX, 2

0019 CD 10 INT 10H ; Выбор символьного режима 80*25

001B B8 ---- R MOV AX, DISPLAY_BUFFER

001E 8E C0 MOV ES, AX ; Адрес буфера дисплейной памяти

0020 B8 0720 MOV AX, 0720H ; Вывод начинается с пробела

0023 NEXT_CHAR:

0023 BF 0000 MOV DI, 0

0026 B9 0050 MOV CX, 80

0029 F3/ AB REP STOSW ; Вывод строки из 80 символов

002B FE C0 INC AL ; Следующий символ

002D 75 F4 JNZ NEXT_CHAR

002F BB 0720 MOV BX, 0720H

0032 NEXT_CHAR_1:

0032 B9 0050 MOV CX, 80

0035 BF 0000 MOV DI, 0

0038 BA 03DA MOV DX, CRT_STATUS ; Порт состояния для адаптера

; цветного дисплея

003B WAIT_NO_RETRACE:

003B EC IN AL, DX ; Проверка на обратный ход луча

003C A8 01 TEST AL, 1

003E 75 FB JNZ WAIT_NO_RETRACE ; Запрещение прерываний

0040 FA CLI

0041 WAIT_RETRACE:

0041 EC IN AL, DX

0042 A8 01 TEST AL, 1

0044 74 FB JZ WAIT_RETRACE ; Ожидание обратного хода луча

0046 8B C3 MOV AX, BX ; Выборка символа

0048 AB STOSW ; Занесение в дисплейный буфер

0049 FB STI ; Разрешение прерываний

004A E2 EF LOOP WAIT_NO_RETRACE

004C FE C3 INC BL

004E 75 E2 JNZ NEXT_CHAR_1

;----- Фиг. 8.12 (в) Вывод линии по диагонали

Фиг. 8.12 Программа управления цветным дисплеем (продолжение)

0050 B8 0004 MOV AX, 4

0053 CD 10 INT 10H ; Установка графического

; режима 320*200

0055 06 PUSH ES

0056 1F POP DS

0057 B3 32 MOV BL, 50 ; Число групп строк

0059 B1 02 MOV CL, 2 ; Счетчик сдвига

005B BF 0000 MOV DI, 0

005E DOT_LOOP:

005E B0 C0 MOV AL, 0C0H ; В AL маска первой точки в байте

0060 88 05 MOV [DI], AL

0062 81 C7 2000 ADD DI, 2000H ; Переключение на нечетную строку

0066 D2 E8 SHR AL, CL ; Сдвиг до следующей точки в байте

0068 88 05 MOV [DI], AL

006A 81 EF 1FB0 SUB DI, 2000H-80 ; Переключение на четную строку

006E D2 E8 SHR AL, CL

0070 88 05 MOV [DI], AL

0072 81 C7 2000 ADD DI, 2000H ; Переключение на нечетную строку

0076 D2 E8 SHR AL, CL

0078 88 05 MOV [DI], AL

007A 81 EF 1FAF SUB DI, 2000H-81 ; Переключение на четную строку,

007E FE CB DEC BL ; следующий байт

0080 75 DC JNZ DOT_LOOP

;----- Возврат к символьному режиму 80*25

0082 B8 0002 MOV AX, 2

0085 CD 10 INT 10H

0087 CB RET

0088 COLOR_GRAPHICS ENDP

0088 CODE ENDS

END

Фиг. 8.12 Программа управления цветным дисплеем (продолжение)

Цветной графический адаптер имеет еще три регистра ввода-

вывода. Регистр режима находится по адресу ввода-вывода 03D8H. Этот

регистр устанавливает аппаратные переключатели различных режимов

отображения. Например, вы можете установить бит 5 этого регистра в

"0" и получить 16 цветов фона в текстовых режимах, а установка бита

5 в "1" переключает 7-й бит атрибута на функцию мигания. Полное

описание структуры этого регистра, и всех других, дано в

техническом описании.

Регистр выбора цвета находится по адресу ввода-вывода 03D8H.

Этот регистр устанавливает цвет окаймления в текстовых режимах.

Поскольку сам по себе текст не занимает весь экран, текст можно

окружить каким-либо выбираемым цветом. Кроме того, регистр выбора

цвета может управлять выбором палитр в графических режимов, как мы

увидим в следующем разделе. Младшие 4 бита этого регистра

устанавливают для окаймления один из 16 цветов, перечисленных на

Фиг. 8.10.

И наконец, регистр состояния по адресу ввода-вывода 3DAH

организует информационную обратную связь между цветной платой и

программой. Например, программа может использовать этот регистр для

определения текущего состояния светового пера, если оно подключено

к системе. Более важным является то, что регистр состояния

сообщает, когда безопасно читать или записывать данные в дисплейный

буфер.

Не вдаваясь в технические детали, заметим, что чтение или

запись данных в буфер дисплея цветной платы может вызвать "снег" на

экране, если вы будете это делать в неподходящие моменты времени.

Это случается только в текстовом режиме с высоким разрешением

80*25. Вспомните, что ранее приводился пример на Фиг. 8.12, в

котором постоянно записывались данные в буфер дисплея. Если для

этого примера использовать цветную плату, на экране появится масса

помех. Чтобы избежать этого, можно использовать регистр

состояния.

Существуют моменты, когда можно безопасно работать с данными в

буфере дисплея. В такие моменты бит 0 регистра состояния равен 1.

Нужно подождать, когда бит 0 станет равным 1, а затем читать или

записывать данные. На Фиг. 8.12б изображена программа, которая

записывает символ на экран дисплея. Первые строки этой программы

записывают данные в буфер, не используя регистр состояния. Эта

часть программы 224 раза записывает 80 симвлов в первую строку

буфера, что эквивалентно заполнению экрана около девяти раз. Когда

вы запустите программу, то увидите короткую вспышку помех в тот

момент, когда будут записываться данные.

Вторая часть программы на Фиг. 8.12б повторяет те же действия,

но на этот раз проверяя бит состояния. Обратите внимание, что эта

часть программы проверяет бит состояния двумя различными способами.

Сначала она ждет, пока бит состояния не станет равным 0. Затем, как

только он станет равен 1, программа записывает данные. Так сделано

потому, что интервал, в течение которого можно записывать данные,

очень мал, и если бы программа делала проверку только на равенство

1, она могла бы захватить бит состояния непосредственно перед его

обращением в 0. В этом случае, чтобы избежать помех, микропроцессор

уже не может записывать данные достаточно быстро, но такая

организация цикла гарантирует, что бит состояния захватывается в

первый же момент, когда он становится равным 1.

Используя бит состояния, вы уже не увидите на экране никаких

помех, но и заметите, что выполнение программы стало гораздо

медленнее. Выполняться медленнее программу заставляет

дополнительное время, затрачиваемое на ожидание бита состояния.

Если вам нужно записать на экране много данных - например,

организовать горизонтальный сдвиг, как на Фиг. 8.8, то потребуется

другой способ. Простейший способ - сбросить в нуль разряд

отображения в регистре режима. Бит 3 регистра режима (3D8H)

управляет отображением на дисплее. Если сбросить этот бит в 0,

экран погаснет. Чтобы переслать много данных в буфер цветного

дисплея, вы можете выключить отображение и переслать данные без

проверки бита состояния. Поскольку отображение выключено, на экране

не возникнут помехи. Когда пересылка блока закончится, включите

отображение. Вы заметите короткое мигание изображения в момент его

выключения и включения. Фирма IBM использует этот метод для

обслуживания вертикального сдвига цветного дисплея.


Untitled

Графический режим

Цветной графический адаптер имеет два режима отображения, в которых

можно управлять отдельными точками экрана. Эти режимы называются

режимами Полной Адресации точек (APA: All Points Addressable),

поскольку с помощью этих режимов можно адресовать и изменять все

точки. В действительности цветная плата допускает более двух

режимов APA, но мы рассмотрим только те два, которые поддерживаются

системой программирования. Используя информацию из технического

описания, вы можете попробовать и другие режимы.

Графический режим среднего разрешения обеспечивает вывод

изображения из 320 точек и 200 строк растра. Каждая из этих точек

может иметь один из четырех цветов. Это означает, что для каждой

точки требуется для представления цвета 2 бита. В каждом байте

графической памяти помещается четыре точки или пиксела (от англ.

picture element - графический элемент, которые называют также

"пелами"). Если вы умножите горизонтальный размер на вертикальный,

а затем разделите на 4 - число точек в байте, то увидите, что в

режиме среднего разрешения требуется 16000 байт. Именно поэтому

цветная плата имеет память объемом 16K.

На Фиг. 8.13 показано расположение элементов в каждом байте.

Пара бит с номерами 6 и 7 отображается первой точкой (0), а биты 0

и 1 представляют последнюю точку (3), отображаемую из этого байта.

В первых 80 байтах графической памяти содержится 320 точек

строки растра 0, т.е. первой строки экрана.

7 6 5 4 3 2 1 0

ЪДДДДДДДДДВДДДДДДДДДВДДДДДДДДДВДДДДДДДДДї

і і і і і

і точка 0 і точка 1 і точка 2 і точка 3 і

і і і і і і і і і

АДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДЩ

Фиг. 8.13 Битовая структура для графики 320*200

Но байт 80 уже не содержит первые четыре точки строки 1. По

причинам, которые объясняются конструкцией аппаратуры, четные и

нечетные строки хранятся в разных концах графической памяти. Все

нечетные строки имеют смещение 2000H байт от соответствующих четных

строк. Поэтому строка 1 лежит в байтах от 2000H до 204FH. Строка 2

- четная и находится по адресам от байта 50H до 9FH; строка 3

находится по адресам от 2050H до 209FH, и так далее.

На Фиг. 8.12в изображена программа, которая иллюстрирует

структуру графической памяти. Эта программа рисует диагональную

линию на дисплее 320*200. Линия начинается в точке (0, 0), верхнем

левом углу, и идет до точки (199, 199), в самый низ и слегка правее

центра. Первая часть программы вводит дисплей в режим APA среднего

разрешения с помощью вызова программы BIOS. В следующей главе

рассматривается, как это делается.

В программе регистр DI используется в качестве указателя

нужного байта в буфере дисплея. В целом программа выводит 200

точек. В регистр BL загружается число 50, так как внутренний цикл

пишет по четыре точки за одну итерацию. В регистр CL заносится 2 -

значение счетчика переключений. Цикл заносит в регистр AL код 0C0H,

что устанавливает старшие два бита на цвет 3; остальные три точки

байта имеют цвет 0, т.е. цвет фона. После записи этого байта

регистр AL сдвигается вправо на 2 бита, и теперь активизируется

вторая точка байта. Прибавление значения 2000H к содержимому

регистра DI смещает указатель на нечетную строку. Третья точка

снова получается сдвигом регистра AL и вычитанием числа (2000H -

80) из содержимого регистра DI. Это возвращает регистр DI назад в

четную строку, но уже на следующие 80 байт. Наконец, после записи

четвертого байта, регистр DI снова возвращается в четное поле, но

еще и увеличивается, смещаясь тем самым на следующий байт строки

растра. В примере цикл работает с четырьмя байтами, так как на

каждый байт приходится четыре элемента.


Untitled

Цвета в режиме APA 320*200

А теперь рассмотрим цвета, которые можно получить в графическом

режиме среднего разрешения. Так как точке отведено два бита, для

нее можно указать один из четырех цветов. Цвет 0 (00B) - это цвет

фона. В качестве этого цвета можно выбрать любой из 16 цветов,

показанных на Фиг. 8.10, записав соответствующее 4-битовое значение

в регистр выбора цвета (3D9H). Остальные три цвета определены

фирмой IBM: вы не можете выбирать произвольно цвета 1, 2 и 3. Фирма

IBM определила две различные палитры цветов, они показаны на Фиг.

8.14. Палитру можно выбрать, устанавливая бит 5 регистра выбора

цвета.

Как показано на Фиг. 8.14, если сбросить бит 5 в 0, получатся

цвета зеленый, красный и желтый вместе с выбранным цветом фона.

Установка бита 5 равным 1 дает голубой, пурпурный и белый. Вы также

можете модифицировать палитру и другим битом регистра выбора цвета.

Установка бита 4 равным 1 делает цвета палитры более яркими.

Программы инициализации BIOS обычно загружают в регистр выбора

цвета значение 30H. Это соостветствует черному цвету фона (0) и и

яркой палитре 1.

Значение цвета Палитра 0 цвета Палитра 1 цвета

--------------------------------------------------

1 (01B) Зеленый Голубой

2 (10B) Красный Фиолетовый

3 (11B) Желтый Белый

бит 5 = 0 бит 5 = 1

--------------------------------------------------

Фиг. 8.14 Цветные палитры

для графики 320*200


Untitled

Графика высокого разрешения

Другой режим APA дает 640 точек в строке растра и 200 строк. В этом

режиме каждая точка соответствует одному биту. Если этот бит

содержит 0, точка черная. Если этот бит содержит 1, точка цветная;

цвет выбирается с помощью регистра выбора цвета. Обычно этот

регистр содержит значение 1111B (белый), давая черно-белую

картинку. Вы можете выбрать любой другой цвет.

Отображение элементов почти идентично графическому режиму

нормального разрешения. Отличие заключается в том, что в байте

содержится восемь точек. Старший значащий бит (бит 7) выводится

первым, а бит 0 - последним. Здесь также строка содержит 80 байт;

так же, как и при нормальном разрешении, четные строки находятся в

начеле буфера дисплея, а нечетные - по адресу 2000H.


Untitled

Адаптер параллельного принтера

Чтобы подключить печатающее устройство фирмы IBM или любое другое,

которое подключается через параллельный интерфейса, вам нужен

адаптер параллельного печатающего устройства (принтера). Этот

адаптер встроен в адаптер монохромного дисплея и принтера. Если вы

используете адаптер цветного графического монитора, то нужен

отдельный адаптер принтера. С точки зрения интерфейса с печатающим

устройством, эти два адаптера идентичны, за исключением адресов

ввода-вывода. Порты принтера на монохромной плате имеют адреса от

3BCH до 3BEH, а отдельная плата принтера имеет адреса от 378H до

37AH.

Адаптер принтера имеет два выводных порта и один порт ввода.

Этот адаптер очень похож на микросхему 8255, используемую для

интерфейса клавиатуры. Фактически, сначала в конструкции платы

печатающего устройства использовалась микросхема 8255. Но фирма IBM

решила лучше делать адаптеры с раздельными компонентами.

Выводной 8-битовый порт данных по адресу 3BCH или 378H передает

данные принтеру. Адаптер посылает символьный код ASCII, помещаемый

в этот порт, прямо в принтер. Второй порт вывода, расположенный

по адресам 3BEH или 37AH, имеет 5 выводных бит. В нем содержатся

управляющие сигналы для принтера; эти линии управляют его работой и

инициализацией. В частности, бит 0 инициирует передачу данных в

принтер. Простая запись данных в порт вывода данных не означает

пересылку символа на принтер. Для того, чтобы в него поступил

символ, нужно установить бит строба (бит 0 порта 3BEH или 37AH)

равным 1, а затем снова сбросить на 0. На Фиг. 8.15 показана

короткая программа, передающая печатающему устройству строку

символов. Подпрограмма с именем PRINT обеспечивает сам процесс

передачи данных в принтер.

Обратите внимание, что процедура PRINT читает код из вводного

порта (3BCH или 379H). Этот порт возвращает информацию состояния

печатающего устройства программе. В данном примере программа

проверяет состояние, чтобы выводить следующий символ именно тогда,

когда принтер готов его принять. Бит 7 состояния порта ввода

показывает занятость принтера. Если этот бит содержит 1, печатающее

устройство готово принимать следующий символ для печати. В

противном случае программа должна подождать. Остальные 4 вводных

бита этого порта отражают возможные ошибки на печатающем

устройстве, например, отсутствие бумаги. Наш пример не контролирует

эти ситуации. Техническое описание содержит структуры вводных и

выводных портов платы адаптера печатающего устройства.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:39

Фиг. 8.15 Вывод на принтер Page 1-1

PAGE ,132

TITLE Фиг. 8.15 Вывод на принтер

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

= 0378 BASE EQU 378H

0000 CODE SEGMENT

ASSUME CS:CODE

0000 94 A8 A3 2E 20 38 2E MSG DB 'Фиг. 8.15', 13, 10, '$'

31 35 0D 0A 24

000C MAIN PROC FAR

000C 1E PUSH DS ; Адрес возврата

000D 2B C0 SUB AX, AX

000F 50 PUSH AX

0010 8D 1E 0000 R LEA BX, MSG

0014 PRINT_LOOP:

0014 2E: 8A 07 MOV AL, CS:[BX] ; Выбор символа из строки для вывода

0017 3C 24 CMP AL, '$' ; Конец строки?

0019 74 06 JE MAIN_RETURN

001B E8 0022 R CALL PRINT ; Печать символа

001E 43 INC BX

001F EB F3 JMP PRINT_LOOP ; Переход к следующему символу

0021 MAIN_RETURN:

0021 CB RET

0022 MAIN ENDP

;----- Эта подпрограмма печатает символ в регистр AL

0022 PRINT PROC NEAR

0022 BA 0378 MOV DX, BASE ; Порт вывода данных на принтер

0025 EE OUT DX, AL ; Занесение символа в порт вывода на принтер

0026 42 INC DX ; Адрес порта состояния принтера

0027 WAIT_BUSY:

0027 EC IN AL, DX ; Опрос состояния принтера

0028 A8 80 TEST AL, 80H ; Проверка разряда занятости принтера

002A 74 FB JZ WAIT_BUSY ; Цикл до освобождения принтера

002C 42 INC DX

002D B0 0D MOV AL, 0DH ; Установка разряда готовности данных

002F EE OUT DX, AL

0030 B0 0C MOV AL, 0CH ; Сброс разряда готовности данных

0032 EE OUT DX, AL

0033 C3 RET

0034 PRINT ENDP

0034 CODE ENDS

END MAIN

Фиг. 8.15 Вывод на принтер

Один из управляющих битов порта 3BEH (или 37AH) управляет

линией прерывания от печатающего устройства. Для того, чтобы

печатающее устройство могло посылать свой сигнал прерывания в

контроллер 8259, этот бит нужно установить равным 1. Однако адаптер

печатающего устройства выдает неверный сигнал прерывания, т.е.

выбранный для этой цели сигнал не вызывает правильного прерывания.

Поэтому не стоит и пытаться писать программу, которая бы

использовала возможности прерывания от адаптера печатающего

устройства (если вы не захотите физически изменить плату

печатающего устройства). Далее мы приведем пример, который обходит

эту проблему с помощью системного таймера.


Адаптер синхронных коммуникаций

Адаптер асинхронных коммуникаций дает возможность связываться с

IBM PC по последовательному интерфейсу. Этот адаптер дает

возможность связываться с другими ЭВМ, службами баз данных, а также

с другими источниками информации. Мы не будем обсуждать принципы

работы этого канала, а поговорим о методах программирования этого

конкретного адаптера IBM PC.

Интегральная микросхема коммуникаций проделывает всю работу по

приему и передаче символов по асинхронной линии. Элемент

асинхронной связи ACE (Asynchronous Communication Element) 8250

можно запрограммировать для управления самыми различными аспектами

связи. При инициализации элемента ACE под пограммным контролем

оказываются размер символа, частота передачи, символы останова и

биты четности. Адаптер также позволяет проверять и задавать

стандартные сигналы управления модемом (модулятора -

демодулятора).

С помощью элемента ACE символ передается просто посредством его

записи в регистр передачи. Микросхема далее выполняет все, что

соответствует кодам, которые вы передали ей при инициализации.

Чтобы принять символ, вы просто читаете его из буфера приема.

Существует регистр состояния, называемый регистром состояния линии,

который показывает, когда буфер передачи пуст и может принять

другой символ. Другой бит регистра состояния сообщает, когда

элемент ACE уже принял символ из другой системы.

В техническом описании приводятся и другие регистры, входящие в

элемент ACE 8250. Эти регистры дают возможность управления модемом

и определения его состояния. Вы также можете разрешить выработку

прерывания при возникновении в элементе ACE различных условий. Это

позволяет вашей программе быстро реагировать на любую смену внешних

условий. east-font-family:"MS Mincho"'>

Программа на Фиг. 8.16 демонстрирует основные механизмы,

необходимые для инициирования элемента ACE, посылки и приема

символа. Базовый адрес ввода-вывода платы адаптера равен 3F8H, так

что регистры элемента ACE расположены по адресам от 3F8H до 3FEH.

Можно также модифицировать адаптер асинхронной связи фирмы IBM так,

чтобы его регистры соответствовали адресам ввода-вывода от 2F8H до

2FEH. С помощью такой модификации можно установить в персональную

ЭВМ второй адаптер и связаться с двумя различными внешними

устройствами. Фактически, можно подключить печатающее устройство к

системе с помощью последовательного, а не параллельного сопряжения.

В этом случае нужны два адаптера: один из них работает с печатающим

устройством, а другой обслуживает внешние связи.

Один из портов ввода-вывода элемента ACE выполняет несколько

функций. Оба буфера, передачи и приема, находятся по адресу 3F8H,

так что когда что-либо записывается по этому адресу, информация

попадает в буфер передачи, но при чтении по этому адресу, вы

получаете последний символ, принятый микросхемой ACE. Этот же порт

ввода-вывода выполняет и третью функцию. Значение делителя,

определяющее скорость работы адаптера, записывается в этот порт

ввода-вывода. Микросхема ACE делит входную частоту на число,

помещенное в регистр делителя, позволяя тем самым выбрать скорость

от 50 до 9600 бод. Режим использования порта 3F8H задает один из

битов управляющего регистра.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:45

Фиг. 8.16 Управление последовательным каналом Page 1-1

PAGE ,132

TITLE Фиг. 8.16 Управление последовательным каналом

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

= 03F8 SERIAL EQU 03F8H

0000 CODE SEGMENT

ASSUME CS:CODE

0000 ASYNC PROC FAR

0000 1E PUSH DS ; Адрес возврата в ДОС

0001 2B C0 SUB AX,AX

0003 50 PUSH AX

0004 BA 03FB MOV DX,SERIAL+3 ; Управляющий регистр

0007 B0 80 MOV AL,80H

0009 EE OUT DX,AL ; Настройка на установку скорости

000A B8 0180 MOV AX,384 ; Делитель частоты для скорости 300 бод

000D BA 03F8 MOV DX,SERIAL

0010 EE OUT DX,AL ; Младшая часть делителя

0011 8A C4 MOV AL,AH

Фиг. 8.16 Управление последовательным каналом (начало)

0013 42 INC DX

0014 EE OUT DX,AL ; Старшая часть делителя

0015 BA 03FB MOV DX,SERIAL+3 ; Управляющий регистр

0018 B0 03 MOV AL,00000011b ; Режим без проверки на четность,8 бит

001A EE OUT DX,AL

;----- Вывод символа в канал

001B BA 03FD MOV DX,SERIAL+5 ; Регистр состояния канала

001E SEND:

001E EC IN AL,DX

001F A8 20 TEST AL,20H

0021 74 FB JZ SEND

0023 B0 41 MOV AL,'A'

0025 BA 03F8 MOV DX,SERIAL

0028 EE OUT DX,AL

;----- Прием символа

0029 BA 03FD MOV DX,SERIAL+5 ; Регистр состояния канала

002C RECV:

002C EC IN AL,DX

002D A8 02 TEST AL,2

002F 74 FB JZ RECV

0031 BA 03F8 MOV DX,SERIAL

0034 EC IN AL,DX

0035 CB RET

0036 ASYNC ENDP

0036 CODE ENDS

END ASYNC

Фиг.8.16 Установка, пересылка и получение данных по

асинхронному каналу (продолжение)

Первая часть программы примера инициализирует микросхему ACE

8250. Первым делом программа настраивает скорость работы адаптера.

Значение делителя, равное 384, устанавливает скорость 300 бод.

Обратите внимание, что перед записью значения делителя программа

заносит 1 в бит 7 управляющего регистра по адресу 3FBH.

Окончательный вывод в порт 3FBH задает характеристики линии. В этом

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:50

Фиг. 8.17 Обработка прерываний от последовательного канала Page 1-1

PAGE ,132

TITLE Фиг. 8.17 Обработка прерываний от последовательного канала

0000 ABS0 SEGMENT AT 0

002C ORG 0BH*4

002C ASYNC_INTERRUPT LABEL WORD

002C ABS0 ENDS

0000 STACK SEGMENT STACK

Фиг. 8.17 Обработка прерываний от последовательного канала (начало)

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 0049 R BUFFER_POINTER DW BUFFER

0002 SET_INTERRUPT PROC FAR

0002 2B C0 SUB AX,AX

0004 8E D8 MOV DS,AX

ASSUME DS:ABS0 ; Адресация по сегментному регистру DS

; в область векторов прерываний

;----- Установка прерывания

0006 C7 06 002C R 0024 R MOV ASYNC_INTERRUPT,offset INT_HANDLER

000C 8C 0E 002C R MOV ASYNC_INTERRUPT,CS ; Занесение вектора прерывания

0010 BA 03F9 MOV DX,03F9H ; Регистр разрешения прерываний

0013 B0 04 MOV AL,04H ; Прерывание по приему из канала

0015 EE OUT DX,AL

0016 E4 21 IN AL,21H ; Регистр маски прерываний 8259

0018 24 F7 AND AL,0F7H ; Занесение 0 в разряд 3

001A E6 21 OUT 21H,AL ; Прерывание не маскируется

001C BA 03FC MOV DX,3FCH ; Регистр управления модемом

001F B0 08 MOV AL,08H ; разряд OUT2

0021 EE OUT DX,AL

0022 EB FE HERE: JMP HERE ; Конец задания режима работы последователь-

0024 SET_INTERRUPT ENDP ; ного канала,ожидание прерывания

;----- Программа обработки прерываний от последовательного канала по приему

0024 INT_HANDLER PROC FAR

0024 50 PUSH AX ; Сохрание используемых регистров

0025 53 PUSH BX

0026 52 PUSH DX

0027 BA 03FD MOV DX,3FDH ; Регистр состояния канала

002A EC IN AL,DX

002B A8 01 TEST AL,01H ; Был ли получен символ?

002D 74 12 JZ INT_RETURN ; Нет,возврат из прерывания

002F BA 03F8 MOV DX,3F8H ; Регистр приема данных

0032 EC IN AL,DX ; Выбор символа из канала

0033 2E: 8B 1E 0000 R MOV BX,BUFFER_POINTER

0038 2E: 88 07 MOV CS:[BX],AL ; Сохранение в буфере

003B 43 INC BX

003C 2E: 89 1E 0000 R MOV BUFFER_POINTER,BX

0041 INT_RETURN:

0041 5A POP DX ; Восстановление регистров

0042 5B POP BX

0043 B0 20 MOV AL,20H ; Сброс контроллера прерываний

Фиг. 8.17 Обработка прерываний от последовательного канала (продолжение)

0045 E6 20 OUT 20H,AL

0047 58 POP AX

0048 CF IRET ; Возврат из прерывания

0049 INT_HANDLER ENDP

0049 0080[ BUFFER DB 128 DUP (?)

??

]

00C9 CODE ENDS

END SET_INTERRUPT

Фиг. 8.17 Асинхронные прерывания (окончание)

Оставшиеся две части примера посылают и принимают символ. В

регистре состояния линии по адресу ввода-вывода 3FDH есть биты

состояния буферов передачи и приема. Посылать символ до тех пор,

пока буфер передачи не опустеет, нельзя; и естественно, нельзя

читать символ до того, как он принят.

Адаптер асинхронной связи также работает с прерываниями. Сигнал

OUT2 в регистре управления модемом передает сигнал прерывания от

микросхемы ACE системе. Регистр разрешения прерываний в микросхеме

ACE выбирает те возможные изменения состояний, которые приведут к

возбуждению внешнего прерывания. Адаптер асинхронной связи

возбуждает прерывание уровня 3 контроллера прерываний 8259.

Давайте посмотрим, как можно использовать прерывание от

асинхронной платы для того, чтобы принимать символы. На Фиг. 8.17

показана последовательность событий, необходимых для включения

системы прерываний. В случае аппаратного прерывания, программа

устанавливает вектор прерывания, соответствующий уровню 3

контроллера 8259 (прерывание 0BH по адресу 58H), на адрес процедуры

обслуживания прерывания. Затем она сбрасывает бит регистра маски,

соответствующий прерыванию от платы связи. В микросхеме ACE 8250

программа загружает регистр разрешения прерывания так, чтобы

разрешить прерывания по состоянию приемной линии. И наконец,

программа включает линию OUT2, чтобы в систему поступали

прерывания. Когда все это работает, не возникает никаких проблем о

бработке символов по мере их получения системой. Программа на Фиг.

8.17 помещает эти символы в буфер, где их может не торопясь

просматривать другая программа.


Untitled

Адаптер управления играми

Адаптер управления играми подключает к системе джойстики и другие

органы управления игрой. Это все аналоговые устройства - т.е., они

не могут работать с нулями и единицами. Их входные значения - это

сопротивление, которое ЭВМ не может прочитать непосредственно.

Адаптер управления играми преобразует значение сопротивления в

нечто такое, с чем ЭВМ может иметь дело.

Адаптер управления играми не преобразует значение сопротивления

непосредственно в двоичное число. Вместо этого адаптер преобразует

значение сопротивления во временную задержку. Чем больше

сопротивление, тем больше временная задержка. Эту задержку

компьютер может измерить. Преобразовать задержку в число,

соответствующее положению джойстика, можно программным способом.

Нашей задачей является написание программы, которая преобразует

временную задержку в число.

Адаптер управления играми обслуживает до четырех входов

сопротивления. Механизм временной задержки по каждому из этих

входов подключен к отдельному биту порта ввода-вывода 201H. Когда

вы выводите любое число в порт 201H, четыре его младших бита

сбрасываются в 0. Биты возвращаются в состояние 1 после некоторго

интервала времени. Этот интервал времени определяется значением

сопротивления, подключенного к адаптеру. Программа на Фиг. 8.18 -

это пример определения значения сопротивления двух из четырех

входных портов. Эта программа использует простой метод. Вместо

определения временной задержки всех четырех портов одновременно,

она обслуживает их последовательно. Количество времени, нужного для

определения одной задержки, невелико. В результате, поочередная

обработка этих задержек вместо одновременной выполняется без

проблем.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:57

Фиг. 8.18 Адаптер управления играми Page 1-1

PAGE ,132

TITLE Фиг. 8.18 Адаптер управления играми

= 0201 GAME_PORT EQU 201H

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 GAME_CONTROL PROC FAR

0000 1E PUSH DS ; Адрес возврата

0001 2B C0 SUB AX, AX

0003 50 PUSH AX

0004 BA 0201 MOV DX, GAME_PORT

0007 B8 B000 MOV AX, 0B000H ; Сегмент дисплейной памяти

000A 8E D8 MOV DS, AX

000C B5 21 MOV CH, 21H ; Символ, который будет записываться в буфер

000E B1 00 MOV CL, 0

0010 WRITE_LOOP:

0010 B4 01 MOV AH, 1 ; Выбор координаты X

0012 E8 0042 R CALL POSITION

0015 8B D8 MOV BX, AX ; Сохранение координаты X в буфере

0017 D1 EB SHR BX, 1

0019 D1 EB SHR BX, 1 ; Деление на 4

Фиг. 8.18 Адаптер управления играми (начало)

001B B4 02 MOV AH, 2 ; Выбор координаты Y

001D E8 0042 R CALL POSITION

0020 D0 E8 SHR AL, 1

0022 D0 E8 SHR AL, 1

0024 D0 E8 SHR AL, 1

0026 D0 E8 SHR AL, 1 ; Деление на 16

0028 B4 A0 MOV AH, 160

002A F6 E4 MUL AH ; Преобразование в смещение в буфере

002C 03 D8 ADD BX, AX

002E 88 2F MOV [BX], CH ; Сохранение символа

0030 EC IN AL, DX

0031 24 10 AND AL, 10H

0033 3A C1 CMP AL, CL

0035 74 D9 JE WRITE_LOOP

0037 8A C8 MOV CL, AL

0039 80 F9 10 CMP CL, 10H

003C 75 D2 JNE WRITE_LOOP

003E FE C5 INC CH ; Следующий символ

0040 EB CE JMP WRITE_LOOP

0042 GAME_CONTROL ENDP

;----- В AH бит маски

0042 POSITION PROC NEAR

0042 51 PUSH CX

0043 2B C9 SUB CX, CX ; Начальное значение для цикла ввода

0045 EE OUT DX, AL ; Запуск таймера

0046 POS_LOOP:

0046 EC IN AL, DX

0047 84 C4 TEST AL, AH

0049 E0 FB LOOPNE POS_LOOP ; Цикл пока 1 - таймер не закончил отсчет

004B B8 0000 MOV AX, 0

004E 2B C1 SUB AX, CX ; Определение значения счетчика

0050 59 POP CX ; в диапазоне 0-255

0051 C3 RET

0052 POSITION ENDP

0052 CODE ENDS

END

Фиг. 8.18 Адаптер управления играми (продолжение)

Последняя часть программы на Фиг. 8.18 берет позицию X-Y,

определенную по входу от джойстика, и записывает символ в

соответствующую позицию дисплея. Адаптер управления играми также

допускает четыре переключающих ввода, значения которых можно

прочитать в старших четырех битах порта 201H. Программа в примере

опрашивает один из этих битов переключения, чтобы перейти от одного

символа к другому и вывести его на экран.


Untitled

Адаптер дисковода

Адаптер дисковода является интерфейсом между микропроцессором и

накопителем на гибких магнитных дисках. Схемы, смонтированные на

плате этого адаптера, обеспечивают все функции, необходимые для

ввода и вывода данных на гибкий диск (дискету). Адаптер

обеспечивает также физическое кодирование и декодирование данных,

необходимое для использования дискет.

Центральной частью адаптера дисковода фирмы IBM является

микросхема контроллера гибкого диска FDC (Floppy Disk Controller)

мPD765 фирмы NEC. Эта компонента также производится фирмой Intel

как микросхема 8272. Микросхема FDC управляет потоком данных на

дискету и с нее. Микросхема FDC имеет два порта ввода-вывода, один

для данных, а другой для отражения состояния. Порт данных находится

по адресу 3F4H, а порт состояния по адресу 3F5H. Порт данных -

двунаправленный, т.е. в различные моменты времени можно и читать

данные из этого порта, и записывать их в порт. Регистр состояния

можно только читать в любой момент времени. Он сообщает, как в

данный момент нужно работать с регистром данных.

В регистре состояния есть 2 бита, которые используются во время

работы с дисководом. Бит 6 - это признак ввода-вывода данных (DIO -

Data Input/Output). Этот бит сообщает, каких действий в отношении

регистра данных ожидает от вас контроллер. Если признак DIO равен

1, микросхема FDC ждет от вас чтения регистра данных. Если DIO

равен 0, FDC ждет записи в регистр данных. Бит 7 порта состояния -

это бит запроса устройства (RQM - Request For Master). Он

аналогичен биту "занято" печатающего устройства. Когда бит RQM

содержит 1, микросхема FDC готова к тому, чтобы читался или

записывался регистр данных. Если вы не уделите внимание биту RQM,

то введете микросхему FDC в заблуждение, и после этого ничего не

будет работать.

Регистр данных в действительности не является единственным.

Подобно контроллеру 6845, порт данных фактически состоит из группы

регистров. Но в отличие от микросхемы 6845, регистра индекса

регистров данных здесь нет. Данные, которые вы посылаете в

контроллер, должны поступать в определенном порядке. Аналогично, в

определенном порядке данные поступают из порта, когда вы их

читаете.

В техническом описании содержится диаграмма, показывающая

входные и выходные комбинации для всех операций дисковода.

Рассмотрим простейшую команду микросхемы FDC, опрос состояния

механизма. Вы выполняете эту операцию тогда, когда хотите что-либо

узнать о текущем состоянии дисковода. На Фиг. 8.19 показаны данные

для команды опроса состояния, а на Фиг. 8.20 показана программа,

выполняющая операцию опроса состояния дисковода.

----------------------------------

Команда...................04H

Модификатор команды.......00H

статус возврата...........ST3 Фиг. 8.19 Команда опроса

---------------------------------- состояния

A
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:03

Фиг. 8.20 Проверка состояния дисковода Page 1-1

PAGE ,132

TITLE Фиг. 8.20 Проверка состояния дисковода

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

FDC_STATUS RECORD RQM:1, DIO:1, OTHER:6

0000 CODE SEGMENT

ASSUME CS:CODE

0000 SENSE PROC FAR

0000 1E PUSH DS ; Адрес возврата

0001 2B C0 SUB AX, AX

0003 50 PUSH AX

0004 BA 03F4 MOV DX, 3F4H ; Порт состояния контроллера дисков

0007 B4 04 MOV AH, 04H ; Команда состояния устройства

0009 E8 001E R CALL OUTPUT ; Передача в контроллер

000C B4 00 MOV AH, 0 ; Второй байт команды

000E E8 001E R CALL OUTPUT

;----- Чтение состояния из контроллера дисков

0011 IN_DIO:

0011 EC IN AL, DX ; Ждать, пока флаг DIO разрешит ввод

0012 A8 80 TEST AL, MASK RQM ; из контроллера

0014 74 FB JZ IN_DIO

0016 IN_RQM:

0016 EC IN AL, DX ; Ждать, пока флаг RQM покажет, что

0017 A8 80 TEST AL, MASK RQM ; контроллер готов

0019 74 FB JZ IN_RQM

001B 42 INC DX ; Установка на порт данных

001C EC IN AL, DX ; Чтение состояния контроллера

001D CB RET ; Конец примера

001E SENSE ENDP

;----- Подпрограмма посылки байта в контроллер

001E OUTPUT PROC NEAR

001E EC IN AL, DX ; Ожидание пока DIO разрешит чтение

001F A8 40 TEST AL, MASK DIO ; из контроллера из контроллера

0021 75 FB JNZ OUTPUT

0023 OUT_RQM:

0023 EC IN AL, DX ; Ожидание пока RQM покажет, что

0024 A8 80 TEST AL, MASK RQM ; контроллер готов

0026 74 FB JZ OUT_RQM

0028 42 INC DX ; Установка на порт данных

0029 8A C4 MOV AL, AH ; Посылаемые данные

002B EE OUT DX, AL ; Вывод в порт

002C 4A DEC DX ; Установка на порт состояния

002D C3 RET

002E OUTPUT ENDP

002E CODE ENDS

END SENSEA

Фиг. 8.20 Проверка состояния дисковода

Каждое действие, выполняемое контроллером дисковода, состоит из

трех фаз: команды, выполнения и результата. В фазе команды

микросхема FDC ожидает данные, и это отражает бит DIO. Когда

микросхема FDC устанавливает бит RQM, чтобы он указывал на

готовность принять данные, программа может посылать команду в

контроллер. В случае опроса состояния дисковода, она выводит в

микросхему FDC два байта команды. Первый байт, 04H, является кодом

операции этой команды. Второй байт сообщает, какой из механизмов

опросить. В течение командной фазы признак DIO всегда показывает,

что микросхема FDC ждет данные, и программа использует бит RQM для

определения момента, когда можно посылать очередной байт данных.

Теперь контроллер переходит в фазу выполнения. В течение этой

фазы контроллер выполняет команду. В данном случае он опрашивает

состояние дисковода. В течение этого времени бит RQM сообщает

программе, чтобы она не использовала порт данных. После завершения

операции признак DIO переключается на 1, сообщая программе, что она

может читать регистр данных. По разрешению RQM программа может

прочитать единственный байт состояния от этой операции. Как только

программа прочитает всю информацию состояния признак DIO снова

принимает значение 0, ожидая ввода следующей команды.

Как видно из таблицы в техническом описании, команда опроса

состояния дисковода - одна из самых простых. Команде чтения данных

требуется девять байт данных во время командной фазы. Когда

операция завершится, программа должна прочитать семь байт состояния

из контроллера. Выполнение не начинается до тех пор, пока не

присылается шестой командный байт, и вы не можете начать другую

операцию, пока не будут прочитаны все семь байт состояния.

По адресу ввода-вывода 3F2H находится цифровой выводной регистр

контроллера дисковода. Этот выводной порт выполняет некоторые

дополнительные операции управления дисководом. Основное назначение

этого порта - управление двигателями дисковода. У механизмов

дисководов 5 1/4 дюйма, используемых в IBM PC, двигатели работают

не непрерывно. Программа должна включить двигатель перед чтением

или записью на дискету - и выключить его после. Если вы оставите

двигатель включенным на все время, это приведет к быстрому износу

дискеты. Когда двигатель работает, на передней панели дисковода

горит красная лампочка.

Адаптер использует цифровой выводной регистр и для других

целей. Два бита выбирают необходимый дисковод. Этот регистр также

используется для сброса микросхемы FDC, так как существуют

ошибочные ситуации, которые вводят контроллер в неопределенное

состояние. В таких случаях единственный выход - сбросить контроллер

и попробовать снова.


Прямой доступ у памяти

Плата адаптера дисковода устроена фирмой IBM так, что она

использует возможность прямого доступа в память системы (ПДП).

Прямой доступ в память позволяет устройству ввода-вывода передавать

данные непосредственно в память или из нее. При этом микропроцессор

не "касается" данных. Принтер, например, требует передачи каждого

печатаемого символа самим микропроцессором. В случае же обмена с

дискетой микропроцессор был бы тяжелой обузой для достаточно

быстрой передачи данных. Программа микропроцессора для передачи

данных дисковода была бы очень похожа на программу Фиг. 8.15, где

символы посылались в принтер. То есть программа должна была бы

читать бит RQM, в цикле, чтобы проверить наличие очередного байта

данных. Тем не менее, если микропроцессор не ответит дисководу

достаточно быстро, то данные будут потеряны.

В случае передачи данных с помощью ПДП микропроцессор должен

только инициировать операцию. Все остальное выполняет контроллер

ПДП 8237 фирмы Intel, расположенный на системной плате. В случае

чтения с дискеты программа инициализирует ПДП для обслуживания

передачи данных. Затем программа посылает команду в контроллер

дисковода, чтобы он выполнил чтение. Во время выполнения программа

не должна передавать данные, так как эту работу выполняет

контроллер ПДП. Когда операция завершается, программа выполняет

фазу обработки результата, как и раньше.

Давайте посмотрим, как нужно настраивать ПДП на операцию чтения

с дискеты. На Фиг. 8.21 показана программа, предназначенная для

этой цели. ПДП имеет четыре канала. Дисковод подключен к каналу 2

ПДП. Каналы 1 и 3 доступны через системный канал ввода-вывода для

других устройств ввода-вывода, а канал 0 используется для очень

важной аппаратной функции - поддерживания регенерации памяти. Если

вы вмешаетесь в работу канала 0 ПДП, содержимое всей памяти

системы, вероятнее всего, изменится.

Каждый канал ПДП имеет два регистра: адресный регистр и регистр

счетчика. Регистр адреса задает область памяти, куда передаются

данные. В нашем случае операции чтения, значение в регистре адреса

указывает начало буфера данных. Когда контроллер дисковода читает

байт с дискеты, контроллер ПДП помещает этот байт в память по

адресу, определяемому регистром адреса. После каждого байта

контроллер ПДП увеличивает адресный регистр так, что он указывает

на следующий байт буфера.

На Фиг. 8.21 BUFFER - имя области данных. Программа определяет

абсолютный адрес буфера BUFFER в системе. Для этого она прибавляет

смещение BUFFER к сдвинутому (умноженному на 16) значению регистра

CS, который содержит значение сегмента буфера. Затем программа

помещает младшие 16 бит адреса в регистр адреса ПДП канала 2.

Старшие 4 бита адреса помещаются в специальный регистр "страницы".

В действительности контроллер ПДП 8237 работает только с

16-битовым адресом. В IBM PC этот регистр страницы добавлен для

того, чтобы программа могла читать данные в любое место памяти.

Имеется три регистра страницы, по одному для каналов 1, 2 и 3.

Регистр страницы имеет размер всего 4 бита, и поэтому старшие биты

регистра AL не играют роли при формировании физического адреса

буфера данных.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:09

Фиг. 8.21 Настройка прямого доступа в память Page 1-1

PAGE ,132

TITLE Фиг. 8.21 Настройка прямого доступа в память

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

= 0000 DMA EQU 0 ; Адрес порта DMA

0000 CODE SEGMENT

ASSUME CS:CODE

0000 DMA_SET PROC FAR

0000 1E PUSH DS ; Адрес возврата

0001 2B C0 SUB AX, AX

0003 50 PUSH AX

0004 B0 46 MOV AL, 46H ; Установка DMA в режим чтения с дискеты

0006 E6 0B OUT DMA+11, AL ; в память

0008 E6 0C OUT DMA+12, AL

000A 8C C8 MOV AX, CS ; Текущий адрес сегмента

000C B1 04 MOV CL, 4

000E D3 C0 ROL AX, CL ; Умножение на 16

0010 8A E8 MOV CH, AL ; Старшие 4 разряда в регистре CH

0012 24 F0 AND AL, 0F0H ; Очистка младших разрядов

0014 05 0032 R ADD AX, offset BUFFER ; Прибавление адреса буфера

0017 80 D5 00 ADC CH, 0

001A E6 04 OUT DMA+4, AL ; Вывод младшего байта адреса

001C 8A C4 MOV AL, AH

001E E6 04 OUT DMA+4, AL ; Вывод старшего байта адреса

0020 8A C5 MOV AL, CH

0022 E6 81 OUT 081H, AL ; Установка регистра страницы

0024 B8 01FF MOV AX, 511 ; Счетчик на один сектор

0027 E6 05 OUT DMA+5, AL ; Младший байт счетчика

0029 8A C4 MOV AL, AH

002B E6 05 OUT DMA+5, AL ; Старший байт счетчика

002D B0 02 MOV AL, 2 ; Открыть для прямого доступа канал 2

002F E6 0A OUT DMA+10, AL

0031 CB RET

0032 DMA_SET ENDP

0032 0200[ BUFFER DB 512 DUP (?) ; Буфер для чтения с диска

??

]

0232 CODE ENDS

END

Фиг. 8.21 Настройка прямого доступа в память

Программа также посылает в контроллер ПДП число передаваемых

байт данных. Контроллер дисковода использует это значение,

записанное в регистр счетчика канала 2, для завершения операции

чтения данных. ПДП посылает устройству специальный управляющий

сигнал, называемый завершением счета, когда оно записывает в память

последний байт. Последняя команда, выдаваемая ПДП - разрешение

работы канала 2. Теперь программа может войти в командную фазу

контроллера дисковода.

Адаптер дисковода соединяет множество компонентов программного

и аппаратного обеспечение компьютера. Адаптер дисковода использует

и ПДП, и прерывания для обслуживания работы дисковода. Сам по себе

контроллер дисковода - сложное, "интеллектуальное" устройство

управления, требующее получения "программы" перед началом работы. В

следующей главе при обсуждении управления механизмом дисковода с

помощью программы BIOS все это будет рассмотрено в комплексе.


Untitled

Глава 9

Базовая система ввода/вывода

В предыдущей главе изучалась аппаратура IBM PC. Фирма IBM

поставляет стандартные управляющие программы для рассмотренных выше

аппаратных компонент. Эти программы находятся в ПЗУ на системной

плате и носят название BIOS (Basic Input/Output System, базовая

система ввода-вывода). В этой главе объясняются функции,

обеспечиваемые BIOS. Материалом этой главы нужно пользоваться

вместе с гл.3 и приложением A технического описания IBM PC. В гл.3

описана базовая системы ввода-вывода, в частности некоторые ее

функции, а в приложении A приведен полный ассемблерный листинг BIOS

IBM PC.

В данной книге программы BIOS изучаются по двум причинам: их

использование дает пример техники программирования на языке

ассемблера, особенно в случае управления системной аппаратурой, и

еще существенней то, что программы BIOS играют важную роль в

разработке программ на языке ассемблера для IBM PC. В предыдущих

главах уже использовались некоторые функции программы BIOS; и,

поскольку фирма IBM внесла эти функции в BIOS, дублировать их не

стоит. Их надо использовать всюду, где это возможно.

Вторая причина использования BIOS - это транспорта- бельность.

Фирма IBM, поставляя BIOS, диктует уровень системного интерфейса

программисту, работающему на языке ассемблера. Когда фирма IBM

разрабатывала свою персональную ЭВМ, системные конструкторы знали,

что не важно, насколько хорошо делается их работа первый раз, так

как ее всегда можно сделать лучше. Время идет, и эволюция

технологии позволяет делать ту же работу лучше.

По мере того, как разрабатывается новая аппаратура для

персональной ЭВМ, появляются и новые программные интерфейсы для

нее. И если вы пишите программы, используя непосредственно

аппаратуру, вам придется корректировать программы каждый раз, когда

изменяется системная аппаратура. Это не имеет особого значения в

случае индивидуального использования ваших программ, но важно, если

вы пишите программы, которые будут использоваться многими. Или,

если вы собираетесь тиражировать вашу программу, вам бы хотелось,

чтобы она использовалась длительное время; т.е. вы не захотите

поставлять новые версии программы всякий раз, когда кто-то создает

новую системную аппаратуру.

Интерфейс с BIOS - попытка разрешить эту проблему. Фирма IBM

определила интерфейс с различными компонентами системы.

Изготовители стараются поддерживать такой интерфейс для своей новой

аппаратуры, а это означает, что даже если аппаратура и изменится,

интерфейс с BIOS останется прежним. Ваша программа не потребует

изменений.

По мере того, как будут появляться новые аппаратные функции,

вам нужно будет изменять программу для того, чтобы воспользоваться

ими. В этом случае, вероятно, фирма IBM расщирит интерфейсы с BIOS.

Но ваши старые программы, которые хорошо работали на старых

машинах, будут продолжать работать и на новых машинах. Конечно, они

не будут использовать новые функции, но если раньше ваша программа

была полезной, весьма вероятно, что будет использоваться и далее.

Предположим теперь, что фирма IBM решила модифицировать плату

контроллера дисковода. Это могло бы потребоваться по разным

причинам. Или фирма захотела уменьшить стоимость изготовления

платы, или расширить возможности платы контроллера. В любом случае

программный интерфейс с платой, который мы рассматривали в

предыдущей главе, может оказаться другим. Но ПЗУ с BIOS, которое

фирма IBM будет поставлять с таким новым контроллером, будет иметь

тот же самый интерфейс. Это означает, что вызывающая

последовательность и передача параметров через интерфейс останутся

теми же. Если вы написали программу, использующую дискеты, и

обращаетесь к BIOS при всех видах доступа к дискете, ваша программа

будет выполняться правильно. Если же вы написали эту программу так,

что она непосредственно работает с контроллером дисковода, скорее

всего, она не будет работать с новым контроллером.

BIOS состоит из нескольких различных частей. Первая часть -

процедура самопроверки при включении питания POST. Эта программа

выполняется всякий раз, когда включается питание, или когда

делается системный сброс (одновременное нажатие клавиш

CTL-ALT-DEL). Программа POST проверяет аппаратуру системы и

инициализирует для нормальной работы некоторые узлы.

BIOS также содержит драйверы устройств. Эти программы управляют

работой устройств. Фирма IBM поставляет программы управления для

всех часто используемых устройств. Но вообще для всех устройств

фирмы IBM драйверы не поставляются. Кроме того, не все желательные

функции доступны. Просто в ПЗУ нет места, чтобы реализовать все

нужные вам функции.

И наконец, BIOS содержит ряд программ системного сервиса. Эти

программы непосредственно не управляют ни одним устройством, но они

многое делают для работы системы.

Эта глава описывает различные компоненты BIOS. Мы начнем с

программы POST, поскольку она - первая в листинге ассемблера и

первой выполняется при включении питания. Программы системного

сервиса все связаны с системной платой, и поэтому они будут

рассматриваться далее. И в конце мы рассмотрим, как фирма IBM

обеспечивает работу различных функций ввода-вывода системы.


Untitled

Замечания по листингу ROM BIOS

Листинг BIOS дан в приложении A технического описания IBM PC. Этот

листинг - описание модуля ПЗУ объемом 8K, расположенного по

адресам, начиная с 0FE000H в адресном пространстве микропроцессора

8088. Этот модуль ПЗУ - один из пяти модулей, размещенных на

системной плате фирмой IBM. Другие четыре модуля ПЗУ содержат в

себе интерпретатор с языка Бейсик. Исходные тексты программ

Бейсика, так же, как и DOS, являются частной собственностью фирмы и

не печатаются в техническом описании. Но тексты программ входящих

в BIOS фирма IBM опубликовала, так что каждый может изучить

интерфейсы с BIOS.

Листинг ассемблера в приложении A - это полный листинг

содержимого ПЗУ. Это не листинг Макроассемблера фирмы IBM потому,

что в то время, когда фирма IBM разрабатывала BIOS, Макроассемблер

не существовал. Для разработки BIOS использовался Макроассемблер

фирмы Intel, с помощью которого получился данный листинг. Ассемблер

фирмы Intel идентичен ассемблеру фирмы IBM в использовании и

синтаксисе. Как вы видите, ассемблер фирмы Intel не печатает в том

же виде адресное поле, а также отличается некоторыми

псевдооперациями ассемблера. Но с этими небольшими отличиями

работать будет не сложно.

В листинге программы BIOS описаны шесть различных сегментов. Из

них по-настоящему интересны для нас лишь три. Сегмент ABS0,

расположенный по адресу 0, содержит векторы прерываний, с которыми

имеет дело BIOS и процедура POST. В этом сегменте нет никаких

определений данных; он просто отводит место векторам. Эта область

попадает в ОЗУ, и поэтому программа BIOS должна инициализировать ее

после включения питания. Сегмент DATA, расположенный в параграфе

40H или по абсолютному адресу 400H, определяет все поля данных,

используемых в BIOS. Аналогично этот сегмент определяет места

переменных, но не их начальные значения. Наконец, сегмент CODE

начинается в параграфе 0F000H. В первых 56K байтах этого сегмента

ничего нет, первый байт сегмента CODE имеет абсолютный адрес

0FE000H или смещение 0E000H в сегменте. Эти данные, от 0FE000H до

0FFFFFH, представляют содержимое модуля ПЗУ с BIOS объемом 8K. Эти

программы вместе с ПЗУ с Бейсиком - единственные, которые

содержатся в машине в момент начала работы.

Еще одно замечание. Фирма IBM не разрабатывала BIOS в виде

одного большого исходного текста. Каждая функция была разработана в

виде отдельного модуля, а затем модули были связаны вместе,

сформировав BIOS. Возможно вы увидите некоторые связки в программе.

Для публикации фирма IBM скомбинировала все исходные тексты в один

большой исходный текст и ассемблировала его. Такое единое

ассемблирование позволило показать абсолютные адреса каждой функции

BIOS.


Untitled

Самотестирование при включении питания

IBM PC выполняет процедуру самопроверки после каждого сброса

системы, включая момент, когда появляется питание. Такая проверка

преследует две цели: она выполняет быструю проверку основных

элементов системы и инициализирует основные аппаратные компоненты.

Как системный тест, процедура POST образует первую часть

трехуровнего диагностического пакета фирмы IBM для ее персональной

ЭВМ. Процедура POST выполняется всякий раз при включении системы.

Эта быстрая проверка тестирует работу системы и обнаруживает ошибку

до того, как она повлияет на выполнение программы. Программы,

обеспечивающие второй уровень диагностики приходит вместе с каждой

машиной в составе руководства по работе на IBM PC; эта дискета (или

кассета) содержит программу диагностики, она проверяет оьдельно

каждый компонент машины. Эта диагностическая программа определяет,

какую часть машины покуптель должен вернуть для обслуживания, если

найдена ошибка. Наконец, фирма IBM поставляет усовершенствованное

диагностическое средство. Это средство, доступное за дополнительную

плату, определяет, какой из сменных узлов не работает. Это

диагностическое средство создано для обслуживающего персонала,

работающего на таких машинах. Правда вы можете купить для себя и

усовершенствованные диагностические тесты.

Начало процедуры POST, возможно, будет трудно найти из-за того,

что оно находится в самом конце листинга. Когда микропроцессор 8088

сбрасывается (или когда включается питание), он начинает работу с

адреса 0FFFF:0000H. Эта ячейка находится всего в 16 байтах от

самого конца адресного пространства микропроцессора 8088. Этого

места хватает для команды перехода на настоящую программу POST. Как

вы видите, команда FAR JMP передает управление на метку START,

которая и есть начало процедуры POST. Фирма IBM использует

оставшиеся байты в конце памяти под дату; эта дата - момент выпуска

ПЗУ в серию.

Способ запуска процедуры POST также объясняет, почему фирма IBM

расположила ПЗУ в верхних адресах памяти. Именно здесь

микропроцессор начинает выполнение программы после сброса.

Системное ПЗУ, содержащее программу инициализации машины, должно

иметь некоторую информацию по адресу 0FFFFH:0000H. Так что есть

смысл помещать все ПЗУ в конце памяти. Также, есть смысл поместить

ОЗУ в нижних адресах, оставив в нем векторы прерываний. Возможность

модификации кодов в этих векторах в большой степени увеличивает

универсальность программы BIOS.

Вообще процедура POST состоит из неинтересных кодов. Многие

последовательности команд не имеют никакого смысла. Если вы

посмотрите на начальную последовательность команд, то увидите, что

они не делают ничего - если с микропроцессором ничего не случилось.

Если вас не интересует написание диагностических программ нет

смысла изучать технику процедуры POST. Отметим некоторые действия

процедуры POST, чтобы показать область проверки ошибок. Процедура

POST проверяет все ПЗУ системной платы, считая контрольную сумму.

Этот тест складывает все байты модулей ПЗУ. При сложении перенос из

8-битового результата игнорируется. Если окончательный результат

нулевой, ПЗУ прошло проверку. Конечно, перед тем, как запустить ПЗУ

в серию, фирма IBM обеспечила, чтобы сумма каждого ПЗУ была равна

нулю. Если ПЗУ плохое, этот тест находит ошибку.

Процедура POST также проверяет всю оперативную память в

системе. Переключатели не системной плате сообщают процедуре POST,

сколько у системы есть памяти в наличии. Каждый бит памяти

проверяется, может ли он быть установлен в единицу и сброшен в

нуль. По окончании теста процедура POST записывает нули по всем

адресам памяти. Это означает, что если вы написали программу,

которая будет работать сразу же после процедуры POST, содержимое

всей памяти окажется нулевым. Но надеяться, что некоторая другая

программа инициализирует поля данных вашей программы - плохой стиль

программирования. чтобы быть в этом уверенным, лучше делать это

самому.

Последнее, что можно сказать про процедуру POST - то, что она

Аппаратные прерывания

---------------------------------------------------------

Номер

прерывания Использование в ROM BIOS

---------------------------------------------------------

2 02H Ошибка четности в памяти

5 05H Печать экрана

8 08H Текущее время

9 09H Клавиатура

14 OEH Дискета

Драйверы BIOS

---------------------------------------------------------

Номер

прерывания Использование в ROM BIOS

---------------------------------------------------------

16 10H Видео

17 11H Проверка оборудования

18 12H Размер памяти

19 13H Дискета

20 14H Асинхронные

21 15H Кассета стриммера

22 16H Клавиатура

23 17H Принтер

24 18H Точка входа Бэйсика для кассеты

25 19H Точка входа в процедуру первичной

загрузки

26 1AH Текущее время

---------------------------------------------------------

Процедуры управляемые пользователем

---------------------------------------------------------

Номер

прерывания Использование в ROM BIOS

---------------------------------------------------------

27 1BH Прерывание клавиатуры

28 1CH Квантование времени

Блоки параметров BIOS

---------------------------------------------------------

Номер

прерывания Использование в ROM BIOS

---------------------------------------------------------

29 1DH Параметры видеомонитора

30 1EH Параметры дисковода

31 1FH Графические символы видеомонитора

---------------------------------------------------------

Фиг. 9.1 Векторы прерываний, используемые

ROM BIOS

инициализирует векторы прерываний для BIOS. Программы получают

доступ к BIOS с помощью векторов прерывания. Сами по себе эти

подпрограммы находятся в модуле ПЗУ, в том же, что и процедура

POST. Перед тем, как процедура POST передаст управление

операционной системе, она делает так, чтобы каждая входная точка

BIOS была записана в соответствующий вектор прерывания. BIOS

использует векторы прерываний для прерываний от 2 до 01FH.

Техническое описание содержит листинг векторов прерывания, в

котором показаны номера прерываний и первоначальное содержимое

векторов. На Фиг.9.1 изображена часть этой таблицы, которая будет

использоваться в описании BIOS.


Untitled

Прерывания ROM BIOS

Как показано в таблице на Фиг.9.1, BIOS использует векторы

прерываний микропроцессора 8088. Эти векторы служат для нескольких

различных целей. Первый блок векторов имеет дело непосредственно с

аппаратными прерываниями. Программы обслуживающие эти прерывания

получают управление всегда, когда возникает аппаратное прерывание.

Например, прерывание от клавиатуры использует вектор прерывания 9,

расположенный по адресу 9*4 или 24H. BIOS обслуживает не все

прерывания инициируемые контроллером 8259. Некоторые прерывания

зарезервированы за устройствами фирмы IBM, а другие вы можете

использовать для своих целей. И даже в том случае, если фирма IBM

зарезервировала какой-то вектор прерывания, вы все равно можете

использовать его по-своему. Но если вы хотите тиражировать

программу, нужно помнить, что у других пользователей компьютеры

имеют не совсем такие же конфигурации, как у вас.


Untitled

Драйверы доступа

Драйверы устройств - это сердце BIOS. Эти программы дают

возможность программисту, работающему на языке ассемблера, работать

с устройствами IBM PC. Любая программа может управлять устройствами

с помощью соответствующих последовательностей команд на аппаратном

уровне. Тем не менее во многих случаях вы можете работать с

устройствами стандартным образом, не вникая в их специфические

особенности. Например, только небольшое число программ делают с

дискетой что-либо еще кроме чтения и записи. В гл.8 была написана

программа для чтения состояния дисковода. Если нужно прочитать

некоторый сектор дискеты, можно использовать для этой цели BIOS и

не переписывать эти программы заново. Программист, работающий на

языке ассемблера, должен рассматривать BIOS как инструмент,

сокращающий его работу.

Функции BIOS вызываются с помощью программного прерывания.

Параметры для функции передаются через регистры. Например,

следующие команды опрашивают текущий режим дисплея

MOV AH, 15

INT 10H

Команда INT 10H вызывет драйыер обслуживания дисплея, входящий

в BIOS. Драйвер дисплея имеет большие возможности. Установка в

регистре AH числа 15 сообщает ему, что программист хочет знать

текущее состояние дисплея. BIOS возвращает информацию о состоянии

через регистр AL.

Каждый драйвер устройства входящий в BIOS имеет свои

собственные входные и выходные параметры. В общем случае регистр AH

определяет функцию для данного драйвера. Остальные регистры BIOS

использует для любых других принимаемых или возвращаемых

параметров. Каждая функция кратко описана в техническом описании

IBM PC, а также снабжена другими важными замечаниями. При разборе

драйверов мы вернемся к этим листингам, а перед этим рассмотрим

другие векторы прерываний.


Untitled

Процедуры пользователя

Существуют некоторые системные функции, которые требуют

непосредственного вмешательства пользовательской программы. Для

этого предназначены два прерывания. Первое, с вектором 1BH -

прекращение выполнения программы с по команде с клавиатуры. Чтобы

прервать выполняемую программу, пользователь системы нажимает

клавиши CTL-BREAK. В обычных случаях это возвращает управление в

текущую программную систему - DOS или Бейсик. Если пишется

программа, которая самостоятельно должна обрабатывать подобное

вмешательство пользователя, надо постоянно проверять, не нажал ли

пользователь соответствующие клавиши на клавиатуре. Или можно

использовать прерывание по отпусканию. Программа обслуживания

клавиатуры BIOS всякий раз, когда возникает нажатие клавиши

CTL-BREAK, дает програмное прерывани 1BH. Обычно это прерывание

указывает на возврат - команду IRET - так что ничего не происходит.

Если мы хотим сразу знать о нажатии клавиши CTL-BREAK, тогда нужно

сделать так, чтобы прерывание 1BH указывало на специальную

подпрограмму нашей программы. Эта подпрограмма сразу узнает о том,

что пользователь захотел выйти из программы и сможет предпринять

некоторые действия.

Аналогично, можно написать программу, которой нужно

периодическое прерывание. Например, игровая программа должна

постоянно знать положение клавиши управления игрой. BIOS выдает

прерывание 1CH всякий раз, когда срабатывает таймер. Как мы уже

видели, это случается примерно 18.2 раз в секунду, или раз в 55

миллисекунд. Можно написать программу, которая проверяет положение

клавиш управления игрой каждый восемнадцатый раз возникновения

прерывания, позволяя тем самым корректировать информацию об их

положении примерно раз в секунду. Этот метод дает нам возможность

периодического входа в заданную подпрограмму.


Untitled

Блоки параметров

Блоки параметров придают гибкость аппаратным программам ПЗУ.

Векторы прерываний блоков параметров указывают на таблицы,

используемые BIOS. Блок параметров дисковода содержит данные,

которые BIOS использует при управлении дисководом. Поскольку

различные типы дисководов имеют разные характеристики, BIOS имеет

таблицу для всех типов дисководов поставляемых фирмой IBM. Если вы

захотели использовать другой тип дисковода вы можете модифицировать

таблицу параметров и использовать это устройство.

Существует также таблица параметров для задания режима работы

дисплея. Если ваш дисплей нуждается в некоторых других временных

соотношениях, можно модифицировать эту таблицу. Например, многие

телевизоры не могут показывать полную ширину 40-символьного

изображения. Один из параметров видеотаблицы управляет сдвигом

изображения на экране дисплея по-горизонтальным. Команда MODE в DOS

может сдвигать изображение на дисплее, модифицируя таблицу

параметров.

Последний блок параметров, использующий вектор прерываний BIOS,

на самом деле является таблицей образов символов. BIOS обеспечивает

вывод символов на дисплей во всех режимах работы дисплея. В

графических режимах BIOS изображает символы, составляя их из

соответствующих конфигураций точек. Таблица для первых 128 символов

находится в ПЗУ, она расположена, начиная со смещения 0FA6EH в

сегменте CODE. Вектор прерывания 01FH указывет на таблицу,

используемую для оставщихся 128 символов. В ПЗУ для этой таблицы не

нашлось места, так что ее должен организовать пользователь. Это

позволяет вам заменить набор символов, выбранный фирмой IBM для

старших 128 символов, своим собственным набором. Для этого нужно

только сконструировать точечные образы символов, загрузить

указатель на эту таблицу в вектор прерывания 01FH, и использовать

для вывода символов графический режим работы дисплея. Эта

возможность может оказаться очень полезной, так как позволяет

конструировать и использовать свой собственный набор символов.

Для того, чтобы изменить параметры, вам нужно изменить только

вектор прерывания указывающий на блок параметров. Где-нибудь в

своей программе вы составляете таблицу параметров, а затем

модифицируете вектор прерывания так, чтобы он указывал на эту

таблицу. Когда вы используете BIOS и ей требуется параметр, она

обращается в вашу, а не в стандартную таблицу параметров,

организованную в ПЗУ. Такие таблицы параметров делают BIOS очень

гибкой. Хотя команды и находятся в ПЗУ, вы можете изменить действие

BIOS, не заменяя ПЗУ на новое и не подменивая ни одной из программ,

вхолдящих в BIOS.


Untitled

Область данных ROM BIOS

Сегмент DATA, расположенный по адресу 40H, содержит переменные,

используемые в BIOS. Мы не будем здесь перечислять все переменные

и их функции. Все они указаны в описании соответствующего драйвера

устройства.

Фирма IBM не меняет ни одну из этих ячеек без особой

необходимости. Некоторые из драйверов, входящих в BIOS не только

читают эти ячейки. Но для программы может оказаться довольно важным

просматривать содержимое этих ячеек. В следующей главе приводится

пример, в котором требуется изменить одну из переменных,

поддерживаемых BIOS. Замена этого числа дает дополнительные

возможности при использовании системы.

Так как, скорее всего, фирма IBM не изменит местоположение ни

одной из этих ячеек, разумнее использовать эти данные

непосредственно, и это вполне возможно. Некоторые из переменных

могут потерять смысл по мере того, как люди будут разрабатывать

новые версии аппаратуры. Например, если фирма IBM разработает

систему, у которой не будет никакой памяти (что совершенно

невероятно), исчезнет необходимость в ячейках, которые содержат

текущий объем памяти. В этом случае фирма IBM могла бы найти другой

вид использования этой ячейки. Но если найдется функция, имеющая

форму, сходную с текущей, то вероятнее всего переменная будет

использоваться тем же способом.


Untitled

Прогарммы драйверов устройств

Теперь мы обсудим один за другим драйверы устройств. Вместо того,

чтобы рассматривать их в порядке номеров, давайте рассмотрим их в

порядке роста сложности. Простейшие из них - программы системного

сервиса, с них мы и начнем.


Untitled

Системный сервис

Два драйвера в BIOS дают самый простой системный сервис. Они

предназначены для определения объема памяти ЭВМ и конфигурации

внешних устройств.

Программа определения объема памяти не имеет параметров. BIOS

возвращает в регистре AX объем памяти системы, измеренный в

килобайтах (1024 байт). Если система имеет память 64K байт, в

регистре AX возвратится число 64. Любая программа, использующая всю

память системы, должна запрашивать у BIOS объем памяти, чтобы

определить, где находится ее конец. Программа могла бы определить

объем памяти, записав и прочитав подряд ячейки памяти, сравнивая

записанное и прочитанное значение. Но, как покажет пример в

следующей главе, важно писать все прикладные программы так, чтобы

они использовали для определения объема памяти подпрограмму,

возвращающую этот объем. Изменяя значение верхнего предела памяти,

можно зарезервировать участок в верхних адресах памяти. После того,

как программа изменит значение общего объема памяти, корректно

написанная прикладная программа не нарушит границу памяти.

Программа проверки конфигурации внешних устройств не имеет

входных параметров. Эта программа возвращает в регистре AX

16-битовый код, показывающий, какие устройства подключены к

конкретной системе. В прологе распечатки этой программы в

техническом руководстве по IBM PC указывается, что означает каждый

бит. Эта функция BIOS - простейший способ определения, существует

ли конкретное устройство в системе, или нет.

Последняя системная сервисная программа проверяет время суток.

У этой программы есть две функции: чтение времени и установка

времени. Время измеряется в квантах таймера, начиная с того

момента, когда машина включается, и отсчитывается от полуночи. BIOS

не преобразует это значение в часы, минуты и секунды. Но в листинге

BIOS показаны нужные для преобразования константы. Чтобы определить

время в часах, разделите 24-битовое значение таймера на 65543,

число квантов таймера в часе. Чтобы определить минуты, разделите

остаток от предыдущего деления на 1092, количество квантов в

минуте, и так далее.

Если точность преобразования значения времени не очень критична

для вас, можно воспользоваться более простым методом. Так как

количество квантов, соответствующее 24 часам не помещается в одно

слово, значение таймера представляется трехбайтовым целым числом.

Значение старшего байта отличается не более, чем на 1% от времени в

часах. Младшее слово можно разделить на 1092, чтобы определить

число минут, а деление остатка на 18 дает число секунд.

Функция времени дня использует аппаратное прерывание,

прерывание по кванту таймера. Это прерывание имеет уровень 0 в

контроллере прерываний 8259, и имеет вектор прерывания 8 в

микропроцессоре 8088. Эта программа получает управление каждые 55

миллисекунд. Основное назначение этой программы - увеличение

счетчика квантов таймера программы времени дня. Если программа

выключит прерывания на значительный промежуток времени, то весьма

вероятно, что время суток перестанет быть правильным.

Прерывание от таймера используется также программой

обслуживания дисковода. Двигатели дисковода включены не постоянно;

BIOS включает двигатели только на время доступа к дискете. Но BIOS

не выключает двигатель сразу же после выполнения операции. Есть

некоторый интервал времени между включением двигателя и тем

моментом, когда он разгонится и будет вращаться достаточно быстро,

для того, чтобы можно было читать данные. Если программа обращается

к дисководу почти сразу после предыдущего обращения, лучше оставить

двигатель включенным, а не выключать и включать его. Программа

обработки аппаратного прерывания от таймера учитывает это.

Обработчик дискового прерывания загружает число в переменную,

которая называется MOTOR_COUNT, когда завершается операция обмена с

дискеттой. Прерывание от таймера уменьшает значение этого счетчика.

Когда значение переменной MOTOR_COUNT достигает 0, выключается

двигатель дисковода. Программа обслуживания дисковода проверяет

этот счетчик, перед обращением к дискете. Если двигатель еще не

включен, нужна задержка, пока двигатель не разгонится. Обычно

двигатель дисковода продолжает работать две секунды после

завершения предыдущей операции. Это время - один из параметров

дисковода, и вы можете изменить его значение. Выбор этого значения

поддерживает балланс между повышением производительности и

снижением износа поверхности дискеты.

Все эти три сервисные программы BIOS передают числа из ячеек

памяти в вызывающую программу. Можно избежать использования BIOS

путем непосредственного чтения этих ячеек. Но зачастую проще

вызвать BIOS, чем организовывать адресацию к сегменту DATA

используемому в BIOS. С "наивной" точки зрения, проще использовать

программу BIOS.


Принтер и асинхронные коммуникации

Программы входящие в BIOS обслуживающие печатающее устройство и

последовательный канал очень похожи. Основная разница -

возможность чтения символов из асинхронного адаптера. Обе

программы имеют функции инициализации адаптера, вывода символа, и

чтения состояния адаптера. На Фиг.9.2 приведен список функций,

реализуемых этими программами BIOS.

Как видно из рисунка, эти две программы BIOS не совпадают.

Значение регистра AH, необходимое для задания конкретного действия,

разное для обеих программ и нам приходится с этим мириться.

Эти программы BIOS могут обслуживать более одного адаптера. В

поле данных BIOS по адресу 40:0H имеется областть из восьми слов.

BIOS использует эту область для хранения адресов адаптеров

печатающих устройств и последовательных каналов. Четыре слова со

смещением 0, которые помечены RS_232_BASE, являются местом для

хранения адресов портов четырех адаптеров последовательных каналов.

По смещению 8, помеченного PRINT_BASE, находится соответствующая

область для адаптеров печатающих устройств. Процедура POST

инициализирует эту область данных в зависимости от того, какие

устройства она обнаружит в системе. При поиске печатающих устройств

процедура POST сначала ищет черно-белую плату, затем адаптер

печатающего устройства по адресу ввода-вывода 378H и, наконец

адаптер печатающего устройства по адресу 278H. Если процедура POST

находит адаптер печатающего устройства по любому из этих адресов,

она помещает значение его базового адреса в область данных.

Аналогичную работу процедура POST делает с адаптерами

последовательного канала, сначала она ищет плату по адресу

ввода-вывода 3F8H, а затем по адресу 2F8H.

Программы BIOS написаны независимо от адресов ввода-вывода

конкретных плат адаптеров. В регистр DX помещается входной

параметр, указывающий, какую из имеющихся плат должна использовать

программа входящая в BIOS. Например, если у вас есть монохромная

плата, имеющая порт печатающего устройства по адресу ввода-вывода

3BCH, то этот адрес появится первым в таблице PRINT_BASE. Если вы

вызовете программу печати BIOS и загрузите в регистр DX 0,

программа BIOS направит ввод-вывод в этот адаптер. Если вы также

имеете отдельный адаптер печатающего устройства, расположенный по

адресу ввода-вывода 378H, установка регистра DX в 1 позволит

программисту работать с этим адаптером. Посмотрев в текст программы

обслуживания печатающего устройства и последовательного канала,

можно увидеть, что она использует регистр DX для выбора

соответствующего значения из таблицы базовых адресов в сегменте

DATA используемого BIOS. После того, как BIOS определит это

значение, весь ввод-вывод она будет делать, используя модификации

этого базового адреса. В программах есть некоторое количество

команд увеличения или уменьшения, работающих с регистром DX. Это

позволяет BIOS работать с разными регистрами адаптера ввода-вывода

Иден.(Значение AH) Функция печати Функция коммуникаций

-------------------------------------------------------------

0 Печать символа Инициализация адаптера

1 Инициализация Посылка символа

2 Чтение состояния Получение символа

3 - Чтение состояния

-------------------------------------------------------------

Фиг. 9.2 Печать и асинхронные коммуникации

без использования абсолютных значений. Все ссылки ввода-вывода

делаются относительно первоначального адреса, взятого из таблицы

базовых адресов.

Функции инициализации печати не требуется от пользователя

никаких входных параметров. Программа инициализации сбрасывает

печатающее устройство и подготавливает порт управления адаптера

печатающего устройства для дальнейшей работы. Но с другой стороны,

инициализация интерфейса RS232 требует от пользователя информации о

параметрах линии связи. Детали кода инициализации, передаваемого

программе с помощью регистра AL, показаны в прологе к программе

обслуживания последовательного канала.

Другие функции BIOS поддержки печати и последовательного канала

дают возможность записывать (для последовательной связи также

читать) данные в устройство. Особенно важно то, что ввод-вывод

делается синхронно. Это означает, что когда программа передает

управление BIOS, чтобы она выполнила нужную функцию, управление не

возвращается до тех пор, пока работа не будет завершена. Когда

символ печатается, управление остается в программе печати до тех

пор, пока она не передаст символ в устройство печати. Если

печатающее устройство занято, BIOS образует цикл, ожидая конца

работы печатающего устройства. Когда символ передается по каналу

асинхронной связи, программа BIOS ждет, пока аппаратура разрешит

передачу следующего символа. Аналогично программа приема

последовательного канала ждет до тех пор, пока адаптер не принял

символ. Если внешнее устройство никогда не пришлет символ,

программа, вызвавшая функцию BIOS, никогда не получит управление

назад.

По этой причине обе программы содержат функцию состояния. Она

позволяет программе решить, может ли BIOS выполнить операцию в

текущий момент времени. Функция состояния печати сообщает, занято

ли печатающее устройство в данный момент. Программа состояния

последовательного канала показывает, может ли символ быть передан

или принят в данный момент. Программа может использовать эти

программы состояния, чтобы определить, можно ли непосредственно

выполнить операцию. Вы можете решить сделать в вашей программе

что-либо еще в то время, пока операция ввода-вывода не может

выполняться. Если вы проверяете появление некоторого внешнего

события, например приема символа адаптером, программа состояния

позволит не останавливать программу до тех пор, пока символ не

принят. Проверка появления символа позволит вам продолжить работу с

ним при условии, что до этого программа выполняла другие действия.

Важно отметить также способ обработки ошибок программ BIOS.

Используя программы BIOS, очень трудно "подвесить" систему. За

исключеннием того случая, когда вы ожидаете символ из

последовательного канала, BIOS всегда возвращают управление

вызвавшей программе, даже если возникла ошибка внешнего устройства.

В каждом цикле, который ожидает выполнения некоторого действия

внешним устройством, BIOS использует счетчик. Например, когда

программа печати ожидает завершения работы печатающего устройства,

в регистрах BL и CX находится счетчик. Если значение счетчика в

этих регистрах становится нулевым до того, как печатающее

устройство освободится, BIOS возвращает управление с ошибкой

исчерпания времени. Это означает, что выключение печатающего

устройства до того, как оно закончило печать, не вызовет

"зависания" системы. BIOS в конце концов вернет управление

программе, указывая, что произошла ошибка устройства печати.

В BIOS возникают небольшие трудности в связи с исчерпанием

времени. Когда в печатающее устройство попадает символ перевода

страницы 0CH, бумага пропускается до начала очередной страницы.

Если на текущей странице более 51 строки, печатающее устройство

будет двигать бумагу долго, и возникнет ошибка по исчерпанию

времени. То есть можно получить индикацию ошибки даже тогда, когда

печать работает правильно. Величина, задающая интервал времени, в

течение которого контролируется печатающее устройство,

скорректировано во второй версии программы BIOS и устраняет эту

проблему. Если вы имеете первую версию, вы можете заново повторить

операцию печати, вызвавшую ошибку исчерпания времени. Получение

ошибки вновь гарантирует, что это не ошибка программы BIOS.


Untitled

Клавиатура

Программы BIOS, обслуживающие клавиатуру, дают примеры различной

программной техники. Прежде всего программы обслуживания

клавиатуры BIOS имеют большой обработчик прерываний, который

преобразует коды сканирования клавиатуры в коды ASCII. Обработчик

прерываний обслуживает обычные и регистровые клавиши. Программа

обслуживания клавиатуры также поддерживает кольцевой буфер длиной

15 символов, позволяя вам печатать быстрее, чем программа может

обрабатывать нажатия клавиш.

Данные клавиатуры

Поле данных программ клавиатуры начинается у смещения 17H в

сегменте DATA. Две флаговых переменных, KB_FLAG и KB_FLAG_1, имеют

битовое значение и отслеживают текущее положение регистровых

клавиш. За определением этих переменных следуют выражения,

показывающие назначение их битов. Например, бит 3 переменной

KB_FLAG следит за состоянием клавиши ALT. Если она нажата, этот

бит равен 1. Если клавиша ALT не нажата, бит равен 0. Биты

переменной KB_FLAG определяют текущее состояние всех регистровых

клавиш, обычных и триггерных. Триггерные клавиши управления

регистрами используют биты переменной KB_FLAG_1. Эти клавиши

изменяют состояние клавиатуры всякий раз, когда нажимаются.

Например, клавиша CAPS LOCK переключает клавиатуру с больших букв

на маленькие и наоборот каждый раз, когда нажимается.

BIOS использует биты переменной KB_FLAG_1 для того, чтобы

отслеживать, нажата ли в текущий момент клавиша CAPS LOCK (а также

другие триггерные клавиши). BIOS должна отслеживать их из-за того,

что все клавиши клавиатуры имеют встроенную функцию автоповторения

по прошествии некоторого времени. Если бы BIOS переключала бит

CAPS_STATE каждый раз, когда получала код "нажатия" от клавиши CAPS

LOCK, автоповторение клавиши сделало бы невозможным для оператора

определение текущего состояния клавиатуры. BIOS переключает бит

клавиши CAPS_STATE, когда поступает первый код нажатия. Затем

программа BIOS игнорирует все коды нажатия до тех пор, пока он не

получит код отпускания от клавиши CAPS LOCK, означающий, что

клавиша отпущена.

BIOS использует переменную ALT_INPUT для обеспечения

специального режима ввода в альтернативном регистре. Когда нажата

клавиша альтернативного регистра, можно ввести десятичное число с

помощью цифровой клавиатуры. Когда клавиша альтернативного регистра

отпускается, программа клавиатуры возвращает символ кода ASCII,

соответствующий этому десятичному числу. Такая техника позволяет

оператору вводить в IBM PC любые символы, даже если их нет на

клавиатуре. Например, нажмите клавишу ALT, затем напечатайте с

помощью цифровой клавиатуры 1, 1, 1 и отпустите клавишу ALT.

Появится символ "o". Этот символ имеет код со значением 111 в коде

ASCII.

Переменная ALT_INPUT хранит текущее значение кода, который

вводится в альтернативном регистре. Когда на цифровой клавиатуре

печатается цифра, и состояние клавиатуры показывает, что включен

альтернативный регистр, программа BIOS умножает текущее значение

переменной ALT_INPUT на 10, и прибавляет к нему новое число. Когда

клавиша альтернативного регистра отпускается, в переменной

ALT_INPUT содержится введенный символ. Обычно переменная ALT_INPUT

устанавливается равной нулю, и программа BIOS не считает нулевое

значение правильным результатом ввода в альтернативном регистре.

Это позволяет оператору использовать клавишу альтернативного

регистра вместе с другими клавишами, нажимая и отпуская ее (как это

делается в интерпретаторе языка Бейсик, где ALT-A порождает строку

символов AUTO и не вводя при этом кода 0 в момент отпускания этой

клавиши.

Остальные переменные поддерживают буфер клавиатуры. По мере

того, как на клавиатуре печатаются символы, возникают прерывания.

Программа KB_INT, входящая в BIOS принимает прерывание от

клавиатуры, читает код сканирования из порта 60H, и определяет код

ASCII этой клавиши. Затем программа BIOS записывает это значение в

буфер клавиатуры KB_BUFFER. В этом буфере есть 16 слов - каждая

клавиша записывается в виде слова. Первый байт - это код ASCII

клавиши, второй байт - код сканирования или расширенный код

сканирования клавиши. Использование расширенного кода сканирования

позволяет передавать в прикладную программу символы, которые не

имеют кода ASCII.

У буфера есть два указателя. Переменная BUFFER_HEAD содержит

смещение первого символа в буфере в сегменте DATA, соответствующего

клавише, нажатой раньше других. Переменная BUFFER_TAIL указывает на

символ, соответствующий самой последней нажатой клавише. Если

указатели имеют одинаковые значения, буфер пуст.

Когда обработчик прерывания BIOS реагирует на очередное нажатие

клавиши, при котором должен быть сгенерирован символ, он помещает

этот символ в буфер. Если буфер не заполнен, обработчик прерывания

помещает символ по адресу, на который указывает переменная

BUFFER_TAIL. Затем он увеличивает переменную BUFFER_TAIL на два,

чтобы указывать на следующую ячейку буфера. Если увеличение

указателя перемещает его за пределы буфера, указатель перемещается

на начало буфера. Это означает, что буфер клавиатуры "закольцован".

После шестнадцатого нажатия очередной символ попадает в начало

буфера. Такая организация буфера также называется циклической, так

как позиции буфера образуют замкнутый цикл, а не расположены в ряд.

Программа клавиатуры BIOS, вызываемая по команде INT 16H, имеет

три функции. Одна из них удаляет символ из буфера. Указатель

BUFFER_HEAD указывает на первый символ буфера. Если буфер не пуст,

(что получается, если указатель начала равен указателю конца), BIOS

удаляет слово по указателю BUFFER_HEAD и увеличивает указатель

начала на два. Если указатель превышает значение указателя

BUFFER_END, он перемещается назад в начало буфера. Подпрограмма K4

в программе клавиатуры BIOS обеспечивает сдвиг указателя и, если

необходимо, его перенос в начало.

Другая функция клавиатуры возвращает текущее состояние буфера

клавиатуры. Она сообщает вызвавшей программе, есть ли в буфере

символ или нет. Программа может использовать эту информацию для

того, чтобы избежать ожидания нажатия клавиши, если у нее есть чем

заняться в это время. Такой вызов функции чтения состояния можно

использовать для определения момента выхода из цикла. Во время

каждого прохода по циклу можно проверить, введен ли уже символ с

клавиатуры. Если нет, цикл продолжается. Если символ введен, цикл

завершается. Тело цикла не может выполняться в случае использования

циклящей программой функции чтения символа.

На Фиг. 9.3 показан листинг программы на языке ассемблера,

которая прибавляет единицу к четырехбайтовому целому числу всякий

раз, когда она проходит через цикл. Когда оператор нажимает клавишу

пробела, происходит выход из программы. Эта программа использует

две функции BIOS. Когда регистр AH установлен в 1, BIOS возвращает

состояние буфера клавиатуры. Если установлен флаг нуля, символа

нет. Если символ есть, программа должна также прочитать этот

символ, иначе он останется в буфере до тех пор, пока следующая

программа (или эта же, но позднее) не запросит символ. В этом

примере символ из буфера извлекается вызовом функции программы

обслуживания клавиатуры BIOS с нулевым значением в регистре AH.

BIOS возвращает символ в регистре AL, и программа сравнивает

значение символа с пробелом. Этот пример показывает, как можно

организовать проверку определенного символа в каждом проходе цикла.


Untitled

Процедура BIOS клавиатуры изнутри

Мы не собираемся построчно анализировать программу клавиатуры BIOS.

Но в ней, однако, есть интересные места. Некоторые из них мы

упомянули раньше, например подпрограмму K4, которая сдвигает

указатель буфера.

Программа KB_INT использует несколько таблиц значений клавиш.

Если вы посмотрите программу, то увидите, что эти таблицы

используются различными способами. Таблицы, содержащие значения

кодов сканирования, используются для поиска шаблонов. BIOS

сравнивает код сканирования клавиатуры со значениями в таблице.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:15

Фиг. 9.3 Состояние клавиатуры Page 1-1

PAGE ,132

TITLE Фиг. 9.3 Состояние клавиатуры

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE

0000 0000 LITTLE DW 0

0002 0000 BIG DW 0

0004 COUNT PROC FAR

0004 1E PUSH DS ; Адрес возврата в ДОС

0005 2B C0 SUB AX, AX

0007 50 PUSH AX

0008 ADD_ONE:

0008 2E: FF 06 0000 R INC LITTLE

000D 75 05 JNZ STILL_LOW

000F 2E: FF 06 0002 R INC BIG

0014 STILL_LOW:

0014 B4 01 MOV AH, 1 ; Программа опроса статуса клавиатуры

0016 CD 16 INT 16H

0018 74 EE JZ ADD_ONE ; Переход, если нет символа в буфере ввода

001A B4 00 MOV AH, 0

001C CD 16 INT 16H ; Чтение символа

001E 3C 20 CMP AL, ' ' ; Сравнение с пробелом

0020 75 E6 JNZ ADD_ONE ; Переход, если не пробел

0022 CB RET ; Возврат в ДОС

0023 COUNT ENDP

0023 CODE ENDS

END COUNT

Фиг. 9.3 Состояние клавиатуры

Команда REPNE SCASB, используемая после метки K16, позволяет BIOS

просмотреть таблицу в поисках соответствия с одной из регистровых

клавиш. Когда BIOS находит соответствие в таблице кодов

сканирования, она использует смещение в таблице для получения

значения маски, используемого вместе с переменной KB_FLAG. Так как

все регистровые клавиши представлены битами в переменных флагов,

единая программа, пользуясь этими таблицами, может управлять

регистровыми клавишами.

BIOS использует также другие таблицы для перекодировки кодов

сканирования в коды ASCII. Определив текущее состояние регистров,

BIOS загружает в регистр BX указатель на нужную таблицу кодов

ASCII. Затем программа преобразует код сканирования в правильное

начальное значение выбранной таблицы (вычитая начальный адрес

таблицы). Команда XLAT переводит код сканирования в правильный код

ASCII. Этот прием используется там, где BIOS порождает коды

псевдосканирования цифровой клавиатуры в режиме использования

регистра клавиатуры CONTROL (метка K63).

Подпрограмма ERROR_BEEP - пример управления динамиком, которое

мы разбирали в предыдущей главе; она порождает сигнал, который BIOS

посылает всегда, когда оператор вводит символ, а буфер полон. Так

как этот сигнал может возникнуть всякий раз, когда система

обслуживает прерывание от клавиатуры, было бы неразумно менять

значение счетчика в канале таймера, управляя динамиком. Для этой

цели BIOS использует непосредственное управление динамиком. Если

уже генерируется какой-либо звук, он обрывается и появляется сигнал

о переполнении клавиатуры. Если вы внимательно послушаете сигнал

переполнения, то заметите, что он слегка дрожит. Возникающее 18 раз

в секунду прерывание таймера меняет тон, прерывая цикл прямого

управления динамиком. Как было предложено в предыдущей главе, вы

можете исследовать последствия использования различных временных

циклов таймера на выходную тональность динамика.


Untitled

Кассета

Программа управления кассетным магнитофоном в BIOS - это пример

работы с последовательным устройством с помощью временных циклов.

Но из-за отличий временных параметров команд, программа

обслуживания кассетного магнитофона BIOS во всех критических

случаях использует таймер-счетчик 8253. Здесь будут рассмотрены

только две программы использующие таймер - READ_HALF_BIT и

WRITE_BIT.

В техническом описании содержится вся информация о методе

кодировки данных, записываемых на кассету. Программа WRITE_BIT

записывает на ленту один бит данных. Выход канала 2

таймера/счетчика непосредственно подключается к выводному порту

кассетного адаптера. Поэтому запись бита данных заключается в

установке правильной частоты канала 2 таймера и ожидании одного

полного цикла. Программа WRITE_BIT делает именно это, но в обратном

порядке. Когда программа WRITE_BIT получает управление, предыдущий

бит еще находится в процессе записи. Два цикла ожидания в программе

WRITE_BIT обеспечивают задержку на пол-цикла, необходимую для

завершения записи предыдущего бита. Когда запись бита завершена,

BIOS заносит новое значение частоты в канал 2 таймера. Программа

WRITE_BIT возвращает управление вызвавшей программе тогда, когда

новая частота начала выдаваться на ленту. Программа управления

кассетным магнитофоном достаточно быстрая (или скорость выдачи бит

в кассету достаточно медленная - это зависит от вашей точки

зрения), чтобы программа WRITE_BIT вызвалась снова до того, как

таймер завершит первые полцикла записи бита.

Программа READ_HALF_BIT выполняет противоположную работу. Эта

программа ждет до тех пор, пока бит ввода с кассетного механизма

(бит 4 порта 62H) не изменит состояние. Каждая смена состояния

этого бита соответствует чтению половины бита. Программа кассетного

механизма вычитает текущее значение таймера из его значения при

предыдущей смене бита. Это число соответствует времени, которое

потребовалось сигналу кассеты, чтобы перейти из одного состояния в

другое. Сложение двух полубитовых переходов дает общую длительность

цикла этого бита. Так как времена циклов у нулей и единиц разные,

программа READ_BYTE может определить значение текущего бита. Из

восьми прочтенных битов она формирует байт.

Программа READ_HALF_BIT иллюстрирует использование канала 0

таймера для целей измерения времени. BIOS замораживает значение

счетчика таймера, а затем читает его в регистр AX. Использование

значения 0, загружаемого в счетчик 0 таймера позволяет вычитать

любые два значения таймера, не анализируя, какое из них больше; в

любом случае получится верная разность.

Программа управления кассетным магнитофоном BIOS содержит в

себе подпрограммы, выполняющие четыре функции. Две из них - блочные

операции ввода-вывода, чтение блока и запись блока. Для

эффективного использования ленты данные записываются на нее блоками

по 256 байт. BIOS проверяет правильность ввода этих блоков с

помощью циклического избыточного кода CRC (Cyclic Redundacy Check).

Проверка ошибок с помощью CRC выявляет почти все ошибки, которые

могут возникнуть на ленте. Это позволяет IBM PC использовать

кассеты в качестве средства памяти с уверенностью, что вновь

читаемые с них данные правильны. Кроме того, BIOS помещает данные в

блоки в связи с несовершенством механизма кассетного магнитофона,

проявляющемся при записи блоков любого размера. Программа обязана

ждать до тех пор, пока двигатель кассетного магнитофона не

включится и разгонится до нужной скорости. Программа также должна

записывать на ленту синхронизирующие импульсы для того, чтобы

микропроцессор вошел в синхронизацию с данными тогда, когда они

будут читаться. Наконец, BIOS записывает слово CRC и конечный байт

в конце каждого блока. Вся эта дополнительная работа происходит с

любым блоком данных, независимо от того, один это байт или 10000

байт. Фирма IBM выбрала размер блока, равный 256, как компромисс

между слижком большим размером блока и нерациональным

использованием ленты.

Другие две функции программы управления кассетным магнитофоном

BIOS - просто включают двигатель ммагнитофона и выключают его. Если

вы думаете о разработке простого способа подсоединения вашей

аппаратуры к IBM PC, имейте в виду, что кассетный порт очень удобен

для этой цели. С помощью разъема кассетного магнитофона,

расположенного сзади корпуса машины, вы можете подключиться к

последовательной линии ввода-вывода. Существует также реле, которое

позволяет управлять низковольтным слаботочным двигателем. Но есть

одна вещь, о которой надо помнить. Выходной бит подключается прямо

к входному биту, когда реле двигателя включено. Такое соединение

позволяет диагностическим программам фирмы IBM проверять входные и

выходные цепи кассетного механизма без записи и чтения данных. Если

вы отдельно используете последовательные вход и выход, нужно

включить реле двигателя - даже если двигателя нет.


Untitled

Дискета

Программа обслуживания дисковода BIOS выполняет блочные операции с

адаптером дисковода. BIOS позволяет вызывающей программе указывать

адреса дорожки и сектора для команды чтения или записи. BIOS

управляет адаптером и самим дисководом, настраивая их на операцию,

а затем пересылает данные из буфера или в буфер пользователя.

После операции BIOS собирает результаты и передает их как часть

выходных параметров программы обслуживания дисков BIOS. Вызывающей

программе не нужно обслуживать различные функции контроллера дисков

и беспокоиться о временных соотношениях.

Области данных драйвера BIOS дискеты

Области данных дискового драйвера BIOS начинаются у смещения 3EH в

сегменте DATA. Первые четыре байта поля данных хранят информацию

состояния дисководов между операциями. Семибайтовый буфер с именем

NEC_STATUS хранит информацию о состоянии контроллера, возвращаемую

контроллером дисководов фирмы NEC после операций чтения и записи.

Как видно из управляющих программ, этот буфер позволяет BIOS

расшифровывать любую ошибку и предоставлять все ошибки в виде

простого набора кодов ошибок. Эти коды ошибок программа BIOS

помещает в байт с именем DISKETTE_STATUS, одновременно возвращая

его в вызывающую программу в регистре AH после выполнения операций

ввода-вывода. Операторы ассемблера после имени DISKETTE_STATUS

перечисляют все коды ошибок, которые может получить вызывающая

программа.

Контроллер дисковода фирмы NEC знает положение головки

чтения-записи в каждом из четырех дисководов, которые он может

обслуживать. Но для этого контроллер должен войти в синхронизацию с

этими механизмами до того, как он начнет точно отслеживать текущее

положение головок; синхронизация нужна потому, что после включения

питания или после сброса контроллер фирмы NEC не знает, где

находятся головки. Байт SEEK_STATUS использует младшие 4 бита, по

одному биту на механизм, чтобы указать, известно ли контроллеру

текущее положение головок или нет. Когда BIOS посылает сигнал

сброса в контроллер, он заносит нуль в этот байт. Перед каждой

операцией обмена данными с дисководами BIOS проверяет этот байт

установки. Если содержимое бита, соответствующего механизму, с

которым идет работа, равно 0, BIOS посылает команду рекалибровки

перед командой установки. На этапе рекалибровки головка

чтения-записи устанавливается на дорожку 0, и теперь контроллер и

механизм согласованы в смысле положения головки. Все последующие

операции установки делаются без предварительной рекалибровки.

Операция рекалибровки играет важную роль и при обычной работе

для устранения условий появления ошибки. После любой ошибки

рекомендуется сбросить контроллер. Этим обеспечивается сброс

условия появления ошибки в контроллере. Байт SEEK_STATUS при этом

устанавливается равным нулю. Поэтому перед тем, как прикладная

программа повторит операцию (неудачную операцию нужно повторять по

крайней мере три раза, так как большинство таких ошибок устранимы и

не повторяются), выполнится рекалибровка механизма. Это

автоматически устраняет ошибку, вызванную неправильной установкой,

когда головка не попала на нужную дорожку. В обычных случаях

рекалибровка и повторная установка на эту же дорожку устраняет

ошибку.

Байты MOTOR_STATUS и MOTOR_COUNT управляют двигателем

дисковода. Адаптер дисковода имеет управляющий регистр, который

позволяет выбрать двигатель дисковода, который включается в двнное

время. В этот управляющий регистр можно только записывать данные,

читать из него нельзя, и байт MOTOR_STATUS является его образом в

памяти. Перед тем, как выполнить операцию записи-чтения на дискете,

вы должны дать возможность двигателю разогнаться до необходимой

скорости, подождав некоторое время - около половины секунды. Если

двигатель уже работает, ждать не нужно. Чтение конкретного бита в

этом байте состояния позволяет определить, нужно ли ожидание.

Далее после выполнения операции важно оставить двигатель

включенным. Есть вероятность, что доступ к дискете вскоре

повторится; однако здесь нужно выбирать между износом дискеты и

увеличением времени доступа. BIOS не допускает непрерывного

вращения двигателей дисководов, для этого в ячейке MOTOR_COUNT

находится уменьшающийся счетчик. При каждом прерывании от таймера

этот счетчик уменьшается, и когда счетчик достигает нуля, все

двигатели выключаются. Обычно временные параметры установлены так,

что двигатель работет примерно две секунды после завершения

операции.

В тексте программы обслуживания дисководов BIOS можно увидеть,

что величина MOTOR_COUNT на одном из первых шагов устанавливается

равной 255. Тем самым гарантируется, что прерывание от таймера не

выключит двигатель дисковода в течение операции. Текущее значение,

представляющее две секунды, записывается в байт счетчика перед

возвратом из BIOS в вызывающую программу.

На Фиг.9.4 приведены команды управления дисководом. Команда

сброса пересылает в контроллер параметры механизма, такие как режим

работы ПДП и скорость предачи. Команда сброса также выполняет

аппаратный сброс контроллера. Фирма IBM рекомендует выполнять это

действие после любой ошибки. Это необходимо, так как некоторые

ошибки (в частности, ошибка по исчерпыванию времени операции,

получающаяся, если в дисководе нет дискеты) ставят контроллер в

затруднительное положение. После таких ошибок вернуть контроллер к

нормальной работе можно только с помощью сброса.


Untitled

Команды чтения и записи

Команды чтения и записи испоьзуют регистры микропроцессора 8088 как

входные параметры. Эти параметры указывают дорожку, сектор,

головку и дисковод, на котором должна выполняться операция. Парой

регистров ES:BX вызывающая программа указывает буфер, и драйвер

дисковода запускает операцию ПДП в этом буфере. Подпрограмма

DMA_SETUP вычисляет физический адрес буфера. Эта программа также

вычисляет общее число пересылаемых байт, используя входной

параметр, задающий количество секторов, а также табличный параметр

- размер сектора. Затем эта программа посылает значение счетчика и

адрес в контроллер ПДП. Заметим, что эта программа определяет

перекрытие буфером границы области размером 64K. Поскольку

четырехбитовый регистр страницы не изменяется при передаче данных,

то если адрес ПДП переходит через значение 0FFFFH, передача данных

будет неверной. Эта программа сигнализирует об ошибке, не допуская

выполнения ошибочной операции ввода-вывода.

AH Функция

-------------------------------------------------

0 Инициализация адаптера дисковода

1 Читать состояние от последней операции

2 Чтение с диска в память

3 Запись из памяти на диск

4 Проверка дискеты

5 Форматирование дорожки дискеты

-------------------------------------------------

Фиг. 9.4 Функции BIOS для дисковода


Untitled

Команда проверки

Команда проверки аналогична команде чтения, за исключением того,

что данные не записываются в память, а просто отбрасываются. У

контроллера ПДП есть специальная команда, называемая командой

проверки, выполняя которую ПДП отвечает на запросы контроллера

дисковода и переключает шину ЭВМ в необходимый режим. Но в этом

случае ПДП не выполняет другую часть своего цикла, в которой данные

записываются в память. Команда проверки используется для проверки

того, правильно ли записались данные на дискету. Операционная

система использует операцию проверки во время выполнения команды

FORMAT. Проверка в этом случае ищет дефектные места на дискете, и

когда выявляет эти дефектные области, операционная система помещает

их за пределами справочника дискеты. Это позволяет использовать

дефектную дискету, а не выбрасывать ее.

Команда проверки, хотя и может определить, что в данных на

дискете есть ошибка, не гарантирует, что данные записались

правильно. Предположим, что в цепи записи на дискету есть

неисправность. Такая ошибка не вызовет появления ошибочной ситуации

во время записи, но на дискету данные не запишутся. Если вы

проверите область данных, команда проверки прочитает ранее

записанные данные (которые вы хотели модифицировать) без ошибки, и

вы будете считать, что все прошло нормально. Если вы хотите

удостовериться в том, что данные записаны верно, вы должны

прочитать данные назад в другой буфер после записи и затем сравнить

оба буфера. Это гарантирует, что данные записались верно.


Команда форматирования

Команда форматирования инициализирует новую дискету. Когда вы

инициализируете дискету, происходит запись на нее маркеров

идентификации секторов. Эти поля контроллер использует при

операциях чтения и записи для опознавания секторов. Например, во

время операции чтения BIOS посылает четыре байта идентификации

сектора в контроллер дисковода. Эти четыре байта обычно

соответствуют номеру дорожки, номеру головки, номеру сектора и

размеру сектора, и называются номером цилиндра-головки-записи CHRN.

Контроллер использует значение номера CHRN сравнивая его со

значениями, записанными в поля идентификации секторов во время

форматирования.

Это означает, что контроллер не обращает внимания на то, что

записано в поле номера CHRN на дискете, т.е. сектора могут

пронумерованы в произвольном порядке, не от первого до восьмого на

каждой дорожке. Как только контроллер находит сектор, у которого

поле номера CHRN совпадает с заданным, он читает сектор. Значения

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:20

Фиг. 9.5 Форматирование дискеты Page 1-1

PAGE ,132

TITLE Фиг. 9.5 Форматирование дискеты

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 CODE SEGMENT

ASSUME CS:CODE,ES:CODE

0000 00 00 01 02 00 00 02 ID_BUFFER DB 0, 0, 1, 2, 0, 0, 2, 2

02

0008 00 00 03 02 00 00 04 DB 0, 0, 3, 2, 0, 0, 4, 2

02

0010 00 00 05 02 00 00 06 DB 0, 0, 5, 2, 0, 0, 6, 2

02

0018 00 00 07 02 00 00 08 DB 0, 0, 7, 2, 0, 0, 8, 2

02

0020 FORMAT PROC FAR

0020 1E PUSH DS ; Адрес возврата в ДОС

0021 2B C0 SUB AX, AX

0023 50 PUSH AX

0024 8D 1E 0000 R LEA BX, ID_BUFFER ; Занесение адреса буфера в ES:BX

0028 0E PUSH CS

0029 07 POP ES

002A B9 0001 MOV CX, 1 ; Трек 0, сектор 1

002D BA 0000 MOV DX, 0 ; Дисковод 0, сторона 0

0030 TRACK_LOOP:

0030 8D 3E 0000 R LEA DI, ID_BUFFER ; Необходимо для занесения номера

0034 B0 08 MOV AL, 8 ; трека в буфер форматирования

0036 ID_SETUP:

0036 26: 88 2D MOV ES:[DI], CH ; Занесение номера трека (цилиндра)

0039 83 C7 04 ADD DI, 4 ; Переход на следующее поле

003C FE C8 DEC AL

003E 75 F6 JNZ ID_SETUP ; Цикл по полям в буфере

0040 B8 0501 MOV AX, 501H ; Форматирование

0043 CD 13 INT 13H

0045 FE C5 INC CH ; Переход на следующий трек

0047 80 FD 40 CMP CH, 40H ; Все сформатировано?

004A 75 E4 JNE TRACK_LOOP ; Цикл по трекам

004C CB RET ; Возврат в ДОС

004D FORMAT ENDP

004D CODE ENDS

END FORMAT

Фиг. 9.5 Форматирование дискеты

номера CHRN контроллер помещает на дискету во время операции

форматирования. Вы имеете возможность записать в качестве значений

номера CHRN любые значения, которые выберете. Буфер данных для

команды форматирования содержит байты номера CHRN для каждого

сектора дискеты. Это означает, что буфер данных может содержать

например такие значения:

DB 10,0,1,2,10,0,2,2

DB 10,0,3,2,10,0,4,2

для дорожки 10 стороны 0 дискеты. Это пример поля данных,

которое использует команда FORMAT операционной системы PC DOS или

MS DOS. На Фиг. 9.5 показана программа, которая форматирует

одностороннюю дискету с обычными значениями номера CHRN. Заменять

этой программой команду FORMAT операционной системы PC DOS нельзя,

так как система PC DOS также проверяет дискету и записывает на

дискету справочник и таблицу расположения файлов. Еще вы можете

заметить, что эта программа сразу же после запуска начинает

форматировать дискету в дисководе A:. Вы должны быть готовы к

этому, если собираетесь выполнить эту программу.

Вы можете использовать команду форматирования в том случае,

если хотите защитить дискету от копирования. Защита от копирования

означает, что дискета шифруется таким образом, что ее становится

трудно скопировать. Так как утилита DISKCOPY предполагает, что

идентификаторы секторов записаны обычным образом, она не может

копировать дискету с не стандартными номерами секторов. Записав на

дискету идентификатор сектора, отличный от нормального, вы защитите

ее от копирования.

В качестве примера давайте защитим дискету от копирования,

записав не стандартный номер сектора на дорожку 10. Пример,

приведенный выше, показывает обычные номера секторов. Если вместо

них буфер данных будет содержать значения DB 10, 0, 10, 2, 10, 0,

2, 2 DB 10, 0, 3, 2, 10, 0, 4, 2

дорожка 10 не будет иметь сектора 1. Вместо него на ней

появится сектор 10, которого не бывает на нормальной дискете

системы PC DOS. Программа DISKCOPY не может скопировать дорожку 10

правильно. Если теперь данная программа проверит (с помощью команды

проверки) наличие сектора 10 на дорожке 10 дискеты, отсутствие

ошибки будет означать, что дискета оригинальная, а не копия.

Этот способ защиты от копирования не совсем надежен. Каждый

опытный пользователь (и даже некоторые программы копирования) могут

обнаружить защиту такого типа и обойти ее. Но модификация

идентификаторов секторов не может производиться произвольно. Для

определения адреса установки головок BIOS использует номер дорожки

из поле CHRN, так что номер цилиндра должен соответствовать номеру

цилиндра, на котором находится сектор. Код в байте номера головки

определяет установку электронного переключателя, выбирающего

головку, поэтому это значение должно быть задано корректно. Длина

поля берется из таблицы параметров, а не из регистров при вызове,

так что ее изменить трудно. К тому же, это число использует и BIOS,

и контроллер, определяя длину сектора, так что изменить его вы

сможете только после тщательной подготовки. Свободно изменяемым

остается только номер сектора. Перед тем, как вы начнете изменять

номера секторов, запомните, что если при этом вы собираетесь еще

использовать эту дискету в рамках DOS, система будет пытаться

использовать сектор, который вы заменили сектором со своим

нестандартным номером, если вы не модифицируете таблицу

расположения файлов дискеты так, чтобы зарезервировать этот сектор.

Если вам нужно считывать по нескольку секторов (что позволяет

драйвер дисковода BIOS), номера у секторов должны быть

последовательными, но не обязательно начинаться с первого.

В общем, команда форматирования дает некоторое средство защиты

от копирования. Однако абсолютно надежный метод защиты еще не

найден. Только хороший выбор техники шифрования поможет оставить

честных людей честными.


Untitled

Дисплей

Видеопрограмма BIOS управляет работой двух дисплейных адаптеров,

которые могут быть установлены в IBM PC. Мы оставили ее для

рассмотрения в последнюю очередь, так как она самая большая и

сложная из всех программ, входящих в базовую систему ввода-вывода.

Области данных дисплея

Секция поля данных BIOS, озаглавленная VIDEO DISPLAY DATA AREA и

начинающаяся со смещения 49H, содержит переменные, используемые

видеопрограммой. Все эти ячейки данных содержат значения,

используемые для работы с дисплейным адаптером в текущий момент

времени. Многие из этих значений копируют данные, находящихся в

регистрах дисплейных адаптеров, из которых запрещено чтение.

Видеопрограмма BIOS должна знать текущее значение таких переменных,

как CRT_MODE_SET и CRT_PALETTE при модификации регистров. В

отличие от порта вывода на системной плате (порт 61H), BIOS не

может прочитать эти регистры перед их изменением. Это означает,

что BIOS должна поддерживать в памяти образ регистра.

Все поля данных имеют содержательные комментарии, которые

поясняют назначение данных во время вашей работы с текстом BIOS.

Отдельного комментария заслуживает поле CURSOR_POSN. Так как

цветной графический адаптер может поддерживать более одной страницы

изображения в текстовом режиме, на каждой странице имеется свое

место для курсора. Контроллер CRT 6845 обслуживает только курсор

текущей страницы. Когда BIOS переключается со страницы на страницу,

контроллер запоминает положение курсора на соответствующей

странице. Так как цветная плата может содержать максимум восемь

страниц в 40-символьном режиме, имеется восемь ячеек для хранения

текущего положения курсора на каждой странице.


Untitled

Функции ввода-вывода дисплея

Программа дисплея BIOS имеет много функций, все они перечислены на

Фиг.9.6. В связи с тем, что видеопрограмма имеет так много

функций, она использует таблицу переходов к этим функциям. Эта

таблица названа M1 и содержит смещения каждой точки входа программы

дисплея BIOS. Первая часть программы VIDEO_IO извлекает код из

регистра AH и преобразует его в адрес перехода. Первая часть

программы выполняет еще и некоторые другие действия, включая

проверку поля EQUIP_FLAG.

Фирма IBM написала видеопрограмму BIOS так, чтобы она могла

работать с двумя дисплейными адаптерами, как с цветным графическим,

так и с монохромным. Но BIOS также подразумевает, что из них

активен только один. Это означает, что вы не сможете использовать

BIOS для того, чтобы записать символ в цветной дисплей, а затем

сразу использовать BIOS для записи символа в монохромный дисплей.

Видеопрограмма BIOS может иметь дело только с одним дисплейным

адаптером.

AH Функция

-------------------------------------------------

0 Инициализация адаптера дисплея

1 Установка размера и формы курсора

2 Установка позиции курсора

3 Чтение позиции курсора

4 Чтение позиции светового пера

5 Назначение текущей страницы

6 Сдвиг вверх

7 Сдвиг вниз

8 Чтение символа

9 Запись символа и атрибута

10 Запись одного символа

11 Выбор палитры

12 Запись точки

13 Чтение точки

14 Запись на телетайп

-------------------------------------------------

Фиг. 9.4 Функции BIOS для видеомонитора

Всякий раз, когда программа вызывает видеопрограмму BIOS, она

определяет, какой дисплейный адаптер имеется в системе с помощью

проверки битов поля EQUIP_FLAG, которые соответствуют текущему

дисплею. Если биты 5 и 4 оба равны 1, то в системе присутствует

монохромный адаптер. Любая другая установка бит говорит о том, что

в системе работает цветной адаптер. Фирма IBM написала эту

программу таким способом, исходя из того, что система может иметь

только один дисплейный адаптер. Перед первым включением машины вы

должны установить переключатели на системной плате в положение,

показывающее, какой адаптер дисплея используется.

Информация флагов оборудования в поле EQUIP_FLAG определяет,

какой из адресов буфера будет использовать видеопрограмма BIOS. Для

монохромной платы BIOS загружает в регистр ES значение 0B000H, а

для цветной платы - значение 0B800H. Это позволяет остальным

программам дисплея BIOS работать без использования информации о

том, какой адаптер работает в системе. Все ссылки к буферу делаются

относительно регистра ES.

Вы можете решить, что, поскольку поле EQUIP_FLAG показывает,

какой адаптер используется, можно переключаться от одного адаптера

к другому просто изменяя биты в слове флагов. К сожалению, это не

так, Адрес ввода-вывода контроллера 6845 отличается для двух

адаптеров, и BIOS записывает этот базовый адрес в свою область

данных. Видеопрограмма BIOS заносит в переменную ADDR_6845 этот

адрес только при инициализации адаптера (команда AH = 0). Поэтому

переключение от одного дисплея к другому также требует

корректировки этой переменной.

Даже если переменная CURSOR_POSN содержит восемь позиций, она

не может обслуживать переключение на другой дисплей. Вы должны

сбрасывать положение курсора в области данных BIOS всякий раз при

переключении с одного адаптера на другой. Если вы не сделаете

этого, изображение курсора не будет соответствовать его положению,

записанному в области данных, и символ на экране будет записываться

в неверную позицию.

Фирма IBM опубликовала методы смены одного дисплея другим, как

с помощью программы на языке ассемблера, так и с помощью программы

на Бейсике. В этих методах требуется для указания адаптера, который

вы хотите использовать, изменить переменную EQUIP_FLAG, а затем

использовать видеопрерывание INT 10H при AH = 0. Эта функция

инициализирует адаптер и обеспечивает правильную установку всех

полей данных программы BIOS. После этого BIOS может работать с тем

дисплейным адаптером, который указан. При этом картинка на другом

дисплее остается видимой. Кроме того, дисплейный буфер того

адаптера продолжает отображать любые изменения текста и графики,

занесенной в него. Так что вы можете измекнять содержимое

дисплейного буфера с помощью вашей программы (а не с помощью BIOS),

чтобы скорректировать информацию, находящуюся на экране, с которого

вы только что переключились.

Давайте рассмотрим простой пример. Вы имеете IBM PC с двумя

адаптерами - цветным графическим и монохромным, и к каждому

адаптеру подключен дисплей. Когда вы сначала включаете машину,

система использует монохромный дисплей. Именно с учетом этого вы и

должны установить переключатели на системной плате, так как

монохромный дисплей может быть поврежден, если не будет

инициализирован сразу же после включения питания. Инструкция по

работе рекомендует, чтобы вы установили переключатели в положение,

показывающее, что в системе присутствует монохромный адаптер.

После этого вы можете использовать видеопрограмму BIOS с

монохромным дисплеем. Чтобы перейти на цветной адаптер, можно

выполнить программу на Фиг.П9.7. Эта программа включает цветной

адаптер в 80-символьном текстовом режиме. Символы, которые были

высвечены на монохромном дисплее, на нем и остаются, а вы теперь

можете использовать видеопрограмму BIOS для работы с цветным

графическим дисплеем. Но если вам потребуется изменить содержимое

монохромного дисплея, вы сможете сделать это, записав новые символы

или атрибуты в буфер дисплея по адресу 0B000H. Это не изменит

положения курсора, но изменит картинку. В этом случае если вы

хотите изменять текст одновременно на цветном и монохромном

дисплеях, вы должны написать собственную программу обслуживания

монохромного дисплея. Или вы можете выяснить, какие значения надо

изменить в поле данных BIOS, чтобы курсор перешел назад, и

выполнить нужные действия, не инициализируя адаптер каждый раз.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:26

Фиг. 9.7 Переключение на цветной дисплей Page 1-1

PAGE ,132

TITLE Фиг. 9.7 Переключение на цветной дисплей

0000 STACK SEGMENT STACK

0000 0040[ DW 64 DUP (?)

????

]

0080 STACK ENDS

0000 ABS0 SEGMENT AT 0

0410 ORG 410H

0410 EQUIP_FLAG LABEL BYTE ; Будет изменяться только младший

0410 ABS0 ENDS ; байт поля флагов

0000 CODE SEGMENT

ASSUME CS:CODE,DS:ABS0

COLOR PROC FAR

0000 1E PUSH DS ; Адрес возврата в ДОС

0001 2B C0 SUB AX, AX

0003 50 PUSH AX

0004 8E D8 MOV DS, AX ; Загрузка адреса сегмента ABS0 в регистр DS

0006 80 26 0410 R CF AND EQUIP_FLAG, 11001111b ; Указание на цветной дисплей как

000B 80 0E 0410 R 20 OR EQUIP_FLAG, 00100000b ; на основной (режим 80*25)

0010 B8 0003 MOV AX, 3

0013 CD 10 INT 10H ; Сброс дисплея

0015 C3 RET ; Возврат в ДОС

COLOR ENDP

0016 CODE ENDS

END

Фиг. 9.7 Переключение на цветной дисплей


Untitled

Установка режима

Когда программа выполняет прерывание INT 10H при AH = 0, она

вызывает функцию установки режима видеопрограммы BIOS. Если в

переменной EQUIP_FLAG отмечено, что в системе есть монохромная

плата, не важно, какое значение оказалось в регистре AL. В этом

случае программа BIOS настраивает монохромный адаптер на режим 7,

что означает текстовый режим 80*50, поддерживаемый черно-белой

платой.

В случае цветной графической платы значение в регистре AL

определяет, в какой из двух графических или двух текстовых режимов

введет программа BIOS дисплейный адаптер. Вы видите, что имеются

черно-белые режимы наряду с цветными для текста, а также графика

320*200. Эти черно-белые режимы на самом деле не выключают цвета,

они только выключают сигнал цветности, который используется в

телевизоре для определения цвета каждой точки. Если вы используете

RGB-монитор, цвета останутся на местах. Если же вы используете

цветной (или черно-белый) видеомонитор или телевизор, установка

черно-белого режима цветной платы выключит цвета и даст более

четкое изображение на экране. Если вы занимаетесь задачей, в

которой цвета не нужны, то получите несколько лучшее изображение,

выбрав один из черно-белых режимов вместо цветного.

Когда выполняется программа установки режима, она настраивает

адаптер и поля данных видеопрограммы BIOS на обслуживание нужного

режима работы. Программа установки режима заполняет пробелами буфер

дисплея и помещает курсор в верхнем левом углу экрана. До тех пор,

пока вы не будете достаточно знакомы с устройством дисплея, вы

должны использовать программу установки режима BIOS. Хотя и нет

ничего плохого в смене кодов дисплея на ваши собственные, очень

трудно отлаживать программу, которая модифицирует дисплей. Если вы

сделаете что-либо неверно, дисплей станет неработоспособным, и

исчезнет возможность определить, что же неверно.

Функции видеопрограммы, соответствующие номерам от 1 до 5

служат для работы с регистрами микросхемы 6845. Как вы помните из

описания системной аппаратуры, микросхема 6845 содержит регистры,

управляющие формой и положением курсора, а также временными

характеристиками отображения. Эти подпрограммы видеопрограммы BIOS

позволяют модифицировать отображение, не зная базового адреса

микросхемы 6845. Эти подпрограммы входят в число сервисных программ

BIOS.


Untitled

Сдвиг изображения

Программы сдвига перемещают текстовую информацию либо вверх, либо

вниз, в зависимости от вызванной функции. Программы сдвига также

обеспечивают некоторые средства организации окон (фрагментов

экрана) на экране дисплея - т.е. BIOS может сдвигать только часть

содержимого экрана. Входные параметры программы сдвига определяют

прямоугольник, расположенный на экране. Он задает верхний левый и

правый нижний углы области сдвига. Видеопрограмма BIOS сдвигает

данные только в этой области. Остальная часть экрана не меняется.

Мы уже видели пользу сдвига окон при изучении операционной

системы и языка Бейсик, которые использовали для сдвига BIOS. Если

работает 80-символьный дисплей, DOS устанавливает верхний левый

угол окна сдвига равным (0, 0), а нижний правый угол (24, 79). Так

сдвигается весь экран. Но Бейсик использует двадцать пятую строку

для индикации состояния дисплея, и только 24 строки отводит под

изображение программы. Когда Бейсик сдвигает экран с помощью

базовой системы ввода-вывода, он устанавливает верхний левый угол в

точке (0, 0), а правый нижний угол - в точке (23, 79). Из-за того,

что последняя строка остается за пределами сдвига, она не

перемещается во время сдвига. В следующей главе приведен пример

сдвига окна, вызываемого из программы на Бейсике.

Видеопрограмма BIOS выполняет сдвиги с помощью переноса

символов и атрибутов в дисплейном буфере. Программа сдвига не

меняет стартового адреса дисплейного буфера; такой метод сдвига был

бы быстрее, но не позволяет прикладной программе определять, куда

должны попасть отдельные символы. Способ сдвига, реализуемый в

BIOS, подходит для обычной работы экрана. Заметим также, что

программа сдвигает изображение при необходимости более чем на одну

строку. Обычно программа сдвигает изображение на одну строку.

Функция сдвига видеопрограммы BIOS позволяет сдвинуть содержимое

экрана на несколько строк. Если же количество строк сдвига равно

нулю, программа BIOS очищает экран. Это - быстрый способ очистки

всего экрана или его части.

Когда программа работает в 80-символьном режиме на цветном

графическом адаптере, она не может записывать или читать его текст

в произвольные моменты времени. Если программа будет модифицировать

буфер не в строго определенные моменты времени, на экране появятся

помехи. Так как программа сдвига читает и записывает большие

количества данных, она должна быть написана с учетом проблемы

помех. Если посмотреть текст программы обслуживания операции

сдвига, то можно увидеть, что BIOS обслуживает режим 80*25 цветной

платы (переменная CRT_MODE равна 2 или 3), как специальный случай.

В случае операций сдвига эта подпрограмма BIOS ждет до тех пор,

пока не возникнет вертикальный обратный ход луча дисплея. Это

означает, что аппаратура адаптера выдала на экран все содержимое

буфера и готова начать новый кадр. (Аппаратура адаптера повторяет

этот процесс регенерации экрана 60 раз в секунду). При появлении

вертикального обратного хода программа BIOS выключает дисплей и

выполняет сдвиг. Когда программа сдвига переместит все символы, она

снова включает дисплей. Это вызывает короткое мигание дисплея. Если

вы внимательно посмотрите на экран во время сдвига, то заметите,

что верхние его шесть строк несколько темнее остальных. Это

происходит из-за того, что операция сдвига занимает несколько

большее время, чем один период регенерации экрана. Поэтому верхние

шесть строк выключаются на два интервала регенерации, а весь

остальной экран - только на один интервал. Метод, допускающий

появление помех на экране, настолько непригляден, что описанный

метод предпочтительнее. Вы можете написать несколько программ,

чтобы попробовать другие методы.

Когда экран находится в графическом режиме, начинает работать

другая часть программы сдвига. Хотя это и существенная часть

программы BIOS, ее обсуждение отложим до тех пор, пока не

рассмотрим чтение и запись символов на экран.


Untitled

Чтение и запись символов

Подпрограммы видеопрограммы BIOS, соответствующие номерам от 8 до

10, обрабатывают символы на дисплее. Все эти три программы

работают с текущим положением курсора. Чтобы записать конкретный

символ на экран, программа должна прежде всего задать положение

курсора с помощью видеопрограммы BIOS при AH-2. После вывода

символа курсор не продвигается автоматически после того, как символ

записан на экране. Если программе нужно записать более одного

символа, она дожна переместить курсор на следующую позицию перед

записью символа. То же самое справедливо и для чтения символов с

дисплея. Так как все символьные операции происходят там, где

расположен курсор, программа, использующая видеопрограмму BIOS, не

сможет определить, по какому адресу расположен символ в буфере.

Программы чтения и записи символа определяют, где он расположен, а

вызывающая программа должна знать только строку и колонку символа.

Имеются две формы программы записи символа. Одна из этих

программ требует указания как символа, так и атрибутов (мигание,

яркость, цвета и так далее), чтобы поместить символ по текущему

адресу курсора. Другая программа записи символа записывает только

символ и не меняет атрибуты этой позиции. Эти две различные формы

программ записи символа позволяют вызывающей программе принимать

текущие атрибуты позиции символа, не выясняя, какие они. Функция

чтения символа возвращает и символ, и его атрибуты по адресу

курсора. Иметь здесь две разные функции не надо - информация

доступна, и если нужна вам, используйте ее.

В связи с проблемой помех цветной графической платы в программы

чтения и записи встроена проверка горизонтального обратного хода.

Эта проверка нужна для того, чтобы на экране не появился "снег" во

время работы микропроцессора. Эта проверка выполняется всегда,

независимо от того, в каком режиме находится дисплей; она

производится даже тогда, когда используется монохромная плата.

Максимальное время ожидания сигнала горизонтального обратного хода

составляет примерно 63 микросекунды, или 63 миллионных долей

секунды. Это ожидание - минимальное в процессе выдачи символа на

экран. У фирмы IBM не оказалось достаточно места в ПЗУ, чтобы

написать подпрограммы, проверяющие специальные случаи. Так что BIOS

всегда ждет сигнала отсутствия помех перед чтением и записью.


Untitled

Текст в графических режимах

Одной из важных возможностей программы BIOS фирмы IBM является

способность показывать на экране текст даже тогда, когда цветной

графический адаптер работает в графическом режиме. Это

осуществляется с помощью таблицы образов символов по адресу

0FFFFH:0FA6EH. Эта таблица содержит образы символов для первых 128

символов. Если нужно, пользователь может загрузить в вектор

прерывания 01FH указатель на таблицу образов оставшихся 128

символов.

Как видно в листинге BIOS, когда цветная графическая плата

находится в графическом режиме, программа записи символа переходит

к специальной части под названием GRAPHICS_WRITE. Эта часть

программы извлекает образ символа из таблицы в ПЗУ или из таблицы

пользователя и помещает точки в соответствующие места памяти

дисплея. В этой программе есть несколько интересных мест. В режиме

среднего разрешения программа BIOS расширяет 8-битовый по ширине

образ символа в 16-битовый. Подпрограмма S21 (EXPAND_BYTE) помещает

в регистр AL строку образа символа и расширяет ее до полного

слова, возвращаемого в регистре AX.

Программа записи символов должна также учитывать адресацию

четных и нечетных полей графической платы. В подпрограмме

GRAPHICS_WRITE происходит запись различных строк образов в байты,

расположенные на расстоянии 2000H друг от друга. Это лучше всего

видно в подпрограмме записи для режима с высоким разрешением. В

этом режиме BIOS может записывать символы со строками образа прямо

в буфер дисплея. Но вместо использования команды REP MOVSB для

пересылки восьми байт используется цикл для обслуживания четных и

нечетных полей. Сначала BIOS записывает четное поле с помощью

команды STOSB. Затем записывается нечетное поле с помощью команды

MOV по адресу [DI+2000H-1].

Другая возможность программ записи символов - запись символов

на экран с помощью функции "исключающее или". Это обычный способ

выдачи на экран символов, которые затем надо удалить. Когда

программа BIOS записывает символ на дисплей при включенном бите

XOR, он берет функцию "исключающее или" от содержимого буфера и

образа символа. Обычно это дает символ в читабельной форме, но его

фактический вид зависит от фона, на котором он записывается. Но

когда программа BIOS снова записывает символ на то же место, опять

используя функцию XOR, символ исчезает, и экран возвращается к

своему первоначальному виду. Этот метод предпочтительнее того, при

котором записывается символ, а затем поверх него выдается пробел.

Запись пробела не восстановит экран к виду, который он имел до

записи символа. Вы можете очень эффективно использовать эту

возможность записи со стиранием в случаях, когда вам надо выдать

временное сообщение на экран.

Программа чтения символа работает аналогично, когда дисплей

находится в графическом режиме; BIOS извлекает образ символа из

дисплейного буфера, а затем сравнивает этот образ с образами

таблицы символов. Когда она находит символ, это отвечает, что по

данному адресу находится найденный символ. Эта программа работает

только в случае точного соответствия, так что если часть другой

графической картинки вторгнется в позицию символа, BIOS не сможет

распознать символ. Более того, эта программа позволяет программисту

считать графический режим идентичным текстовому режиму. Пока

программа использует для взаимодействия с дисплеем BIOS, она может

работать с текстом независимо от режима работы дисплея.

Вспомним теперь, что и программы сдвига изображения тоже имеют

специальные части для обслуживания графических режимов. Если вы

вернетесь к соответствующей части BIOS, то обнаружите, что она

определяет окна сдвига в графическом дисплее и выполняет сдвиг

способом, который идентичен сдвигу в текстовом режиме. Сдвиг в

графических режимах происходит несколько медленне, чем в текстовом

режиме, в первую очередь из-за того, что программа должна переслать

все 16000 байт, а не 2000 или 4000 байт, нужных в текстовом режиме.

Это дает 4- - 8-кратное увеличение времени сдвига, и оно становится

заметно больше.

Способность BIOS обрабатывать символы в графических режимах

дает большие возможности. Становится достаточно простым нарисовать

график или картинку, а затем использовать символы для пометки

отдельных частей рисунка. Кроме того, вы можете отвести часть

экрана под графическое изображение, а другое окно использовать для

текста. В это окно вы можете заносить обычный текст с помощью

программ символьного вывода, а также сдвигать его. В других случаях

вы можете записывать символы на дисплей, не обращая внимания на его

текущий режим. BIOS сама определит, в каком режиме находится

дисплей, и правильно запишет символы.


Untitled

Графика

Видеопрограмма BIOS имеет несколько функций, которые обслуживают

графику на IBM PC. Устанавливая регистр AH равным 11, программа

может сделать выбор цвета в графическом режиме. Эта подпрограмма

устроена так, что она работает с истинной палитрой, а не с

предопределенными палитрами цветной платы. Если графический режим

320*200 имеет истинную палитру, то это означает, что должно

существовать отображение в четыре цвета, возможных для каждой

точки, из четырех цветов, которые хотела бы иметь программа. В

настоящее время это делается для цвета фона в режиме нормального

разрешения. Вы можете выбрать любой цвет в качестве цвета 0, цвета

фона. Интерфейс этой программы был разработан для того, чтобы

позволять определение истинной палитры цветов, если фирма IBM

когда-либо модифицирует аппаратуру так, чтобы позволять это.

Чтобы достичь этой цели, значение точки определяется в регистре

BH. В регистре BL определяется цвет, который присвоит адаптер этой

точке. Например, если содержимое регистра BH равно 0; регистр BL

содержит значение цвета фона. BIOS обслуживает значение регистра

BH, равное только 0 или 1 , так как можно выбрать только цвет фона

и одну из двух предопределенных палитр. Пролог BIOS определяет

палитру. С помощью этой функции можно также определить цвет

окаймления в текстовых режимах.

Две другие графические подпрограммы позволяют читать либо

записывать конкретные точки графического экрана. В простейших

случаях эта функция BIOS позволяет вам определить строку и колонку

без определения их отображения в буфер дисплея. При построении

больших картин, а также при выполнении любой графической работы эти

функции требуют очень много времени. Программа должна вызывать BIOS

при выдаче каждой точки на экран. В случае режима высокого

разрешения программа должна вызвать BIOS 128000 раз, чтобы

изобразить каждую точку правильно. Хотя программы, входящие в BIOS

выполняется очень быстро, она должна вычислять адрес буфера всякий

раз, когда получает информацию о строке и колонке. Для этого

действия требуется умножение и несколько сложений, и следовательно,

требуется некоторое время. В общем случае программа записывает

график с помощью стартового положения точки и смещений относительно

этого положения. Это означает, что программа вычисляет положение

первой точки с помощью алгоритма отображения в буфер, а положения

остальных точек отсчитывает от текущего адреса буфера.


Выдача на телетайп

Функция выдачи на телетайп видеопрограммы BIOS предназначена для

тех программ, которые хотят использовать дисплей в простейшей

форме. Эта функция представляет дисплей в виде телетайпа. Данная

программа выполняет обслуживание установки курсора и выдачи

символа. После того, как BIOS заносит символ в текущую позицию, он

перемещает курсор в следующую позицию. Если курсор попал за

пределы строки, программа BIOS сдвигает дисплей вверх на одну

строку, и помещает курсор в первую позицию следующей строки.

Кроме того, что программа выдачи на телетайп дает удобный метод

выдачи символов на экран, она служит хорошим примером использования

видеопрограммы BIOS для обслуживания символов. Эта программа

записывает символы, перемещает курсор, а также сдвигает экран,

когда это небходимо, а также реагирует на некоторые служебные

символы. Символ возврата на шаг назад перемещает курсор на одну

позицию назад, возврат каретки перемещает курсор в начало строки, а

перевод строки переносит курсор на следующую строку, если надо

сдвигая экран. И наконец, символ звукового сигнала (код 7) вызывает

генерацию звука с помощью динамика. ОС использует эту функцию BIOS

для поддержки большинства своих дисплейных функций.


Untitled

Глава 10

Раширения системы и подпрограммы на языке Ассемблера

В этой главе рассказывается о способе использования программ на

языке ассемблера в больших программах. Приведенные ранее примеры

были автономными программами на языке ассемблера. Ни один из других

языков программирования не позволяет так, как язык ассемблера,

управлять техническими средствами. Однако во многих случаях выбор

языка ассемблера в качестве языка программирования может оказаться

неправильным. Часто лучше всего бывает применять язык высокого

уровня в сочетании с подпрограммами на языке ассемблера.

В настоящей главе рассматриваются две области применения

программ на языке ассемблера. В первом случае мы напишем на языке

ассемблера программы для расширения встроенной базовой системы

ввода-вывода (ROM BIOS). Эти программы добавляют новые функции для

технического обеспечения. Перед использованием такие программы

необходимо поместить в память на постоянное хранение. Затем ваша

программа привлекает эти новые функции как расширение стандартного

набора функций BIOS. Будут приведены два примера для двух различных

способов загрузки программ в память.

Вторая область - использование процедур на языке ассемблера

в программах, написанных на языке высокого уровня. Здесь

подпрограмма на машинном языке выполняет ту функцию, которую было

бы сложно или невозможно реализовать на языке высокого уровня.

Будет приведен ряд примеров, иллюстрирующих различные способы

применения таких подпрограмм. Примеры относятся в основном к

дополнению функций технического обеспечения, однако те же принципы

можно использовать для любой программы на машинном языке.


Untitled

Расширение системы BIOS

Некоторех программы - драйверы устройств мы хотим загружать в

память так, чтобы они становились постоянным расширением системы.

Хорошим примером программы такого типа является BIOS. Поместив

драйверы устройств в ПЗУ, фирама IBM сделала их постоянной частью

системы. Эти процедуры доступны для всех программ, запускаемых на

IBM PC, поскольку BIOS всегда находится в памяти.

Однако большинство из нас не может позволить себе роскошь

помещать свои программы в ПЗУ. Если программа не предназначена для

широкого тиражирования, то нет никакого смысла тратить тысячи

долларов на производство единичного модуля ПЗУ. Однако, существует

более дешевая альтернатива. Разработаны ПЗУ специальных типов,

которые пользователь может программировать самостоятельно.

Некоторые компании поставляют программаторы ППЗУ (программируемые

ПЗУ - PROM в англоязычной аббревиатуре), которые позволяют вам

помещать свои программы в постоянную память. Специальное

техническое обеспечение для программирования ППЗУ стоит несколько

сотен долларов, а отдельные модули ППЗУ - от 10 до 50 долларов.

Для некоторых программ вам может понадобиться этот способ. В

IBM PC имеется свободное гнездо для подключения модуля ПЗУ. К этому

гнезду можно подключить стандартное ПЗУ на 8 кбайт или ППЗУ. Это

сделает программу постоянной частью ЭВМ. Мы не будем более подробно

обсуждать установку ПЗУ и ППЗУ. Для этого требуется специальное

техническое обеспечение, и эта процедура различна для разных

пользователей.

Вместо этого будут рассмотрены способы загрузки программы в

оперативную память таким образом, чтобы она стала постоянной частью

системы. Программа будет находиться в памяти вплоть до выключения

машины. Преимуществом в данном случае является то, что такая

функция встроена в ЭВМ не навсегда. Ее можно изменять не разбирая

машину. Если вы обнаружите в программе какие-либо недостатки, то ее

можно модифицировать без повторения всех манипуляций с ПЗУ.

Расширение системы BIOS

Некоторех программы - драйверы устройств мы хотим загружать в

память так, чтобы они становились постоянным расширением системы.

Хорошим примером программы такого типа является BIOS. Поместив

драйверы устройств в ПЗУ, фирама IBM сделала их постоянной частью

системы. Эти процедуры доступны для всех программ, запускаемых на

IBM PC, поскольку BIOS всегда находится в памяти.

Однако большинство из нас не может позволить себе роскошь

помещать свои программы в ПЗУ. Если программа не предназначена для

широкого тиражирования, то нет никакого смысла тратить тысячи

долларов на производство единичного модуля ПЗУ. Однако, существует

более дешевая альтернатива. Разработаны ПЗУ специальных типов,

которые пользователь может программировать самостоятельно.

Некоторые компании поставляют программаторы ППЗУ (программируемые

ПЗУ - PROM в англоязычной аббревиатуре), которые позволяют вам

помещать свои программы в постоянную память. Специальное

техническое обеспечение для программирования ППЗУ стоит несколько

сотен долларов, а отдельные модули ППЗУ - от 10 до 50 долларов.

Для некоторых программ вам может понадобиться этот способ. В

IBM PC имеется свободное гнездо для подключения модуля ПЗУ. К этому

гнезду можно подключить стандартное ПЗУ на 8 кбайт или ППЗУ. Это

сделает программу постоянной частью ЭВМ. Мы не будем более подробно

обсуждать установку ПЗУ и ППЗУ. Для этого требуется специальное

техническое обеспечение, и эта процедура различна для разных

пользователей.

Вместо этого будут рассмотрены способы загрузки программы в

оперативную память таким образом, чтобы она стала постоянной частью

системы. Программа будет находиться в памяти вплоть до выключения

машины. Преимуществом в данном случае является то, что такая

функция встроена в ЭВМ не навсегда. Ее можно изменять не разбирая

машину. Если вы обнаружите в программе какие-либо недостатки, то ее

можно модифицировать без повторения всех манипуляций с ПЗУ.


Untitled

Возврат программы в DOS с сохранением ее резидентности

Первый способ написания и загрузки постоянной функции в DOS состоит

в том, чтобы, возвращая управление DOS, программа оставалась в

памяти резидентной. Такую функцию существляет прерывание INT 27H.

Обычно для выхода в DOS используется прерывание INT 20H, либо

программа производит переход по адресу 0 программного префикса, как

мы делали в программах типа .EXE. В результате управление

возвращается DOS. Операционная система освобождает память,

предоставленную этой программе. Следующую программу, которая

загружается после прерывания INT 20H, DOS помещает в ту же область

памяти, которая использовалась для предыдущей.

Выход в DOS через прерывание INT 27H отличается от

рассмотренного. Управление возвращается в DOS точно так же, как и в

случае прерывания INT 20H, но часть памяти, занимаемая программой,

не возвращается для дальнейшего использования. В регистре DX

указывает на адрес первой свободной ячейки после той области

памяти, котрую вы хотите зарезервировать. DOS резервирует эту

область памяти, как часть системы. Это означает, что ваша программа

становится частью DOS. Такую программу можно удалить из памяти

только перезагрузив DOS и начав все сначала.

Если выход в PC DOS осуществляется при помощи прерывания INT

27H, то в регистре CS должен находиться адрес программного

префикса. Легче всего это сделать, если писать использующую INT 21H

программу как .COM программу. Написать программу типа .EXE,

оставляющую при выходе содержимое регистров CS и DX корректным,

довольно трудно. Поскольку создание программ типа .COM было

рассмотрено в гл.5, будем считать, что все наши остающиеся

резидентными программы имеют тип .COM.

Рассматриваемый для прерывания DOS INT 27H пример довольно

сложен. Он иллюстрирует не только использование INT 27H, но и

способы замены существующей BIOS другой версией. В этом примере мы

даже применим несколько трюков с таймером для увеличения скорости

обработки.

Пример представлен на Фиг. 10.1. Приведенная здесь программа

предназначена для обслуживания буфера печати. Обычно при выдаче на

печать символа программа обращается к прерыванию INT 17H - драйверу

печати BIOS. Эта функция выдает символ на принтер после

проверки ошибок и ожидания готовности принтера. Как правило, при

этом обеспечивается достаточная производительность. Но допустим,

что вы пишете несколько программ и хотите вывести их на принтер.

Если вы попытаетесь сделать это, то не сможете обратиться к системе

до тех пор, пока принтер не закончит работу. Чтобы

продолжить редактирование или ассемблирование другой части

программы, вам придется ждать завершения печати.

A
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:27

Фиг. 10.1 Буфер для печати Page 1-1

PAGE ,132

TITLE Фиг. 10.1 Буфер для печати

0000 ABS0 SEGMENT AT 0

0020 ORG 4*8H

0020 ???????? TIMER_INT DD ? ; Аппратное прерывание от таймера

005C ORG 4*17H

005C ???????? PRINTER_INT DD ? ; Прерывание к BIOS для печати

0408 ORG 408H

0408 ???? PRINTER_BASE DW ? ; Базовый адрес адаптера принтера

040A ABS0 ENDS

0000 CODE SEGMENT

0100 ORG 100H

ASSUME CS:CODE,DS:CODE,ES:CODE

0100 EB 09 90 JMP START

0103 ???????? PRINT_VECTOR DD ? ; Место для хранения исходного вектора 17h

0107 ???????? TIMER_VECTOR DD ? ; Место для хранения исходного вектора 9h

010B START:

010B 2B C0 SUB AX,AX ; Установка регистра ES на сегмент ABS0

010D 8E C0 MOV ES,AX

ASSUME ES:ABS0

010F 26: A1 005C R MOV AX,WORD PTR PRINTER_INT

0113 26: 8B 1E 005E R MOV BX,WORD PTR PRINTER_INT+2

0118 26: 8B 0E 0020 R MOV CX,WORD PTR TIMER_INT

011D 26: 8B 16 0022 R MOV DX,WORD PTR TIMER_INT+2

0122 A3 0103 R MOV WORD PTR PRINT_VECTOR,AX

0125 89 1E 0105 R MOV WORD PTR PRINT_VECTOR+2,BX

0129 89 0E 0107 R MOV WORD PTR TIMER_VECTOR,CX

012D 89 16 0109 R MOV WORD PTR TIMER_VECTOR+2,DX

;----- Во время занесения векторов прерываний прерывания запрещены

0131 FA CLI

Фиг. 10.1 Буфер печати (начало)

0132 26: C7 06 005C R 0162 MOV WORD PTR PRINTER_INT,offset PRINT_HANDLER

R

0139 26: 8C 0E 005E R MOV WORD PTR PRINTER_INT+2,CS

013E 26: C7 06 0020 R 0196 MOV WORD PTR TIMER_INT,offset TIMER_HANDLER

R

0145 26: 8C 0E 0022 R MOV WORD PTR TIMER_INT+2,CS

014A B0 36 MOV AL,00110110b

014C E6 43 OUT 43H,AL

014E B0 00 MOV AL,0 ; Увеличение скорости работы таймера в 256 раз

0150 E6 40 OUT 40H,AL

0152 B0 01 MOV AL,1

0154 E6 40 OUT 40H,AL

0156 FB STI

0157 8D 16 28FE R LEA DX,BUFFER_END ; Занесение адреса конца программы

015B CD 27 INT 27H ; Выход с сохранением программы в памяти

015D 00 TIMER_COUNT DB 0

015E 01EE R BUFFER_HEAD DW BUFFER_START

0160 01EE R BUFFER_TAIL DW BUFFER_START

;----- Эта подпрограмма управляет вызовом прерывания 17h

0162 PRINT_HANDLER PROC FAR

ASSUME CS:CODE,DS:nothing,ES:nothing

0162 0A E4 OR AH,AH

0164 74 05 JZ BUFFER_CHARACTER ; Проверка на функцию вывода символа

0166 2E: FF 2E 0103 R JMP PRINT_VECTOR ; Переход на стандартный обработчик

; прерывания 17h

016B BUFFER_CHARACTER:

016B FB STI

016C 53 PUSH BX

016D 51 PUSH CX

016E 56 PUSH SI

016F 2B C9 SUB CX,CX ; Счетчик отсчетов таймера

0171 PRINT_LOOP:

0171 2E: 8B 1E 0160 R MOV BX,BUFFER_TAIL ; Выборка адреса конца буфера

0176 8B F3 MOV SI,BX

0178 E8 01E2 R CALL ADVANCE_POINTER ; Перемещение указателя на следующий байт

017B 2E: 3B 1E 015E R CMP BX,BUFFER_HEAD ; Проверка на наличие места в буфере

0180 74 0E JE BUFFER_FULL ; Нет места,ожидается пока оно появится

0182 2E: 88 04 MOV CS:[SI],AL ; Вывод символа в буфер

0185 2E: 89 1E 0160 R MOV BUFFER_TAIL,BX ; Занесение нового адреса конца буфера

018A B4 00 MOV AH,0 ; Код возврата из прерывания 17h

018C PRINT_RETURN:

018C 5E POP SI

018D 59 POP CX

018E 5B POP BX

018F CF IRET

0190 BUFFER_FULL:

0190 E2 DF LOOP PRINT_LOOP ; Повторить цикл проверки занятости буфера

0192 B4 01 MOV AH,1 ; Буфер занят слишком долго,ошибка

0194 EB F6 JMP PRINT_RETURN

0196 PRINT_HANDLER ENDP

Фиг. 10.1 Буфер печати (продолжение)

;----- Эта программа вызывает 4660 раз в секунду

0196 TIMER_HANDLER PROC FAR

ASSUME CS:CODE,DS:nothing,ES:nothing

0196 50 PUSH AX

0197 53 PUSH BX

0198 2E: 8B 1E 015E R MOV BX,BUFFER_HEAD

019D 2E: 3B 1E 0160 R CMP BX,BUFFER_TAIL ; Есть ли что-нибудь в буфере?

01A2 75 14 JNZ TEST_READY ; Переход,если буфер не пуст

;----- Эта подпрограмма управляет таймером в скоростном режиме

01A4 TIMER_RETURN:

01A4 5B POP BX

01A5 2E: FE 06 015D R INC TIMER_COUNT ; Увеличение счетчика делителя таймера

01AA 75 06 JNZ SKIP_NORMAL

01AC 58 POP AX ; Это выполняется один раз на 256 прерываний

01AD 2E: FF 2E 0107 R JMP TIMER_VECTOR ; Переход на стандартную программу обработки

; прерывания от таймера

01B2 SKIP_NORMAL:

01B2 B0 20 MOV AL,20H

01B4 E6 20 OUT 20H,AL ; Конец прерывания

01B6 58 POP AX

01B7 CF IRET

;----- Символ в буфере,производится попытка напечатать его

01B8 TEST_READY:

01B8 52 PUSH DX

01B9 1E PUSH DS

01BA 2B D2 SUB DX,DX

01BC 8E DA MOV DS,DX ; Установка регистра DS на сегмент ABS0

ASSUME DS:ABS0

01BE 8B 16 0408 R MOV DX,PRINTER_BASE

01C2 42 INC DX ; Установка на порт состояния

01C3 EC IN AL,DX

01C4 A8 80 TEST AL,80H ; Проверка готовности принтера

01C6 74 16 JZ NO_PRINT

01C8 4A DEC DX ; Установка на порт данных

01C9 2E: 8A 07 MOV AL,CS:[BX] ; Выбрка выводимого символа

01CC E8 01E2 R CALL ADVANCE_POINTER

01CF 2E: 89 1E 015E R MOV BUFFER_HEAD,BX

01D4 EE OUT DX,AL ; Вывод символа в порт принтера

01D5 83 C2 02 ADD DX,2 ; Установка на порт управления

01D8 B0 0D MOV AL,0DH

01DA EE OUT DX,AL ; Передача символа из порта в принтер

01DB B0 0C MOV AL,0CH

01DD EE OUT DX,AL

01DE NO_PRINT:

01DE 1F POP DS

01DF 5A POP DX

01E0 EB C2 JMP TIMER_RETURN ; Возврат через подпрограмму управления

01E2 TIMER_HANDLER ENDP ; таймером

01E2 ADVANCE_POINTER PROC NEAR

01E2 43 INC BX ; Сдвиг указателя

Фиг. 10.1 Буфер печати (продолжение)

01E3 81 FB 28FE R CMP BX,offset BUFFER_END

01E7 75 04 JNE ADVANCE_RETURN ; Проверка на конец циклического буфера

01E9 8D 1E 01EE R LEA BX,BUFFER_START ; Установка указателя на начало буфера

01ED ADVANCE_RETURN:

01ED C3 RET

01EE ADVANCE_POINTER ENDP

01EE BUFFER_START LABEL BYTE

01EE 2710[ DB 10000 DUP (?)

??

]

28FE BUFFER_END LABEL BYTE

28FE CODE ENDS

END

Фиг. 10.1 Буфер печати (продолжение)

Приведенная в примере программа может облегчить решение задачи.

Конечно, это не обойдется вам даром. Программа отводит под буфер

печати некоторую область памяти, котрая будет постоянно за ним

закреплена. DOS изымает эту область из общего объема памяти,

предоставляемой пользователю. Например, если в системе 96K байт

памяти, а 10 кбайт отводится под буфер печати, то пользоваться

Макроассемблером уже не удастся. Для макроассемблера требуется 96

кбайт, а после создания буфера печати останется лишь 86 кбайт.

Поэтому, прежде чем организовать буферизацию печати, убедитесь, что

в системе останется еще достаточный объем памяти.

Буферизация печати осуществляется примерно так. Стандартная

команда PRINT (INT 17H) заменяется процедурой, которая помещает

символы в буфер вместо того, чтобы посылать их на принтер. Эта

часть программы и называется буферизацией печати. Отдельная часть

программы, называемая выводом на печать, извлекает символы из

буфера печати и пересылает их на принтер.

Основным моментом в данном примере является замена прерывания

INT 17H базовой системы ввода-вывода. Почти все прикладные

программы для вывода на печать используют именно это прерывание, а

это означает, что теперь все обычные операции печати будут

приводить к пересылке символов в подпрограмму буферизации печати, а

не на принтер. В частности, в нашем примере, мы можем

листинг ассемблирования вывести на принтер, нажав клавиши

Ctrl-PrtSc, служащие для пересылки символов с экрана на печать.

Когда мы выводим листинг ассемблирования с программой

буферизации печати в памяти, символы поступают в буфер в памяти, а

не на принтер. Буферизация очень незначительно

увеличивает время просмотра. Когда файл выведен на экран (и в буфер

печати), управление возвращается DOS. Вы можете прекратить

пересылку символов на принтер, снова нажав клавиши Ctrl-PrtSc.

Листинговый файл находится в буфере, и DOS готова продолжить

выполнение других заданий, например, редактирование или

ассемблирование.

Затем начинает выполняться вторая часть программы. Эта

процедура извлекает символы из буфера и пересылает их на принтер.

Она управляется прерыванием от таймера. При каждом прерывании от

таймера процедура вывода на печать также получает управление. Если

в буфере имеется символ, и если устройство печати находится в

состоянии "готово", то подпрограмма пересылает этот символ на

принтер. Таким образом, символы извлекаются из буфера и

пересылаются на принтер со скоростью работы этого устройства.

Поскольку программа вывода на печать работает в фоновом режиме,

одновременно могут выполняться другие задания, например,

редактирование или ассемблирование.

Обратимся к программе, представленной на Фиг. 10.1, и

рассмотрим, как взаимодействуют ее компоненты. Во-первых, в ней

описан сегмент ABS0, содержащий вектор прерываний, с которым

программа имеет дело. Приведенная в примере программа заменяет как

прерывание вывода на печать INT 17H, так и прерывание от таймера

INT 8. Заметим также, что в сегменте ABS0 определяется адрес

PRINTER_BASE. В этой ячейке находится базовый адрес для устройства

печати 0. В данном примере предполагается, что все операции печати

производятся на системном устройстве печати.

Сегмент CODE - это та секция программы, которая остается

резидентной. При помощи команды ORG 100H мы составили эту программу

как файл типа .COM. Это означает, что для создания из выходного

файла редактора связей файла типа .COM, необходимо выполнить

описанную в гл.5 последовательность действий. Для хранения исходных

значений вектора печати и вектора таймера в программе используются

области памяти PRINT_VECTOR и TIMER_VECTOR. Хотя программа заменяет

значения этих векторов, при выводе на печать в ней должны быть

известны их исходные значения.

Первая часть сегмента CODE, начиная с метки START, является

инициализирующей частью программы. В ней считываются исходные

значения векторов прерываний и сохраняются в области данных

сегмента CODE. В процедуре инициализации векторы прерываний в

нижних адресах памяти заменяются новыми, используемыми в процедурах

буферизации и вывода на печать. Обратите внимание на команду CLI,

которая блокирует прерывания перед выполнением этой операции.

Поскольку программа изменяет прерывание таймера, она не может

допустить обработку его шага в этот момент времени. Если бы

прерывание от таймера произошло в тот момент, когда программа

изменила только одно из двух слов вектора прерываний от таймера, то

микропроцессор продолжил бы выполнение с непредсказуемого адреса

памяти. Разумнее запретить прерывания, чем допустить возможность

перехода по неизвестному адресу.

Прежде чем разблокировать прерывания, программа изменяет

текущее значение счетчика таймера. Обычно прерывания от таймера

происходят примерно 18 раз в секунду. Устройство печати может

печатать по 80 символов в секунду. Если бы процедура вывода на

печать выдавала по одному символу при каждом прерывании от таймера,

то максимальная скорость печати составила бы 18 символов в секунду.

Если ускорить таймер, прерывания от таймера будут происходить чаще.

Это позволит программе выдавать на печать все 80 символов в

секунду. В приведенном примере в таймер загружается значение

счетчика 256, оно в 256 раз меньше стандартного значения.

Компенсируется это увеличение скорости при помощи процедуры

TIMER_HANDLER.

Процедура инициализации возвращает управление в DOS при помощи

прерывания INT 27H. Перед выходом из процедуры в регистр DX

загружается указатель на байт, сразу следующий за последнм байтом

всей программы. Заметим, что все процедуры и буфер печати мы

расположили в пределах этой области памяти. В соответствии с

правилами действия прерывания INT 27H DOS не затронет эту

область.

Приведенная программа зря расходует часть памяти.

Инициализирующая ее часть выполняется только один раз, поэтому нет

смысла оставлять ее в памяти. Можно оптимизировать программу

поместив часть кода от команды START до INT 27H после метки

BUFFER_END. В этом случае при прерывании INT 27H инициализирующая

часть программы оказалась бы за пределами защищаемой области

памяти, и следующая загружаемая DOS программа перекрыла бы

процедуру инициализации. Экономия около 90 байт из более чем 10000

байт в нашем примере не впечетляет, но она вполне доступна в случае

необходимости.

Далее следует процедура PRINT_HANDLER. Эта подпрограмма

вместо базовой системы ввода-вывода осуществляет управление

принтером при каждом обращении программ к прерыванию INT 17H для

вывода данных на печать. Первые три команды управляют перехватом

управления у BIOS. Наша процедура работает только тогда, когда

должен быть напечатан символ (AH = 0). При любом другом коде

функции работу выполняет BIOS, поэтому программа производит

проверку, не равен ли регистр AH нулю. Если нет, то производится

косвенный переход с использованием сохраненного значения исходного

вектора печати. В результате управление передается процедуре

входящей в BIOS, которая выполняет требуемую функцию. Сказанное

означает, что в нашей процедуре обработки прерывания достаточно

написать только поддержку сделанных изменений.

Относительно рассмотренного способа управления печатью следует

сделать два замечания. Во-первых, передача дальше всех функций

печати кроме случая AH = 0 - не блестящая идея. Если какая-либо

программа инициализирует принтер (AH = 2) во время работы механизма

буферизации, то BIOS берет управление на себя и выдает на принтер

команду RESET. Эта команда обрывает ту строку, которая в это время

выводится на печать, что в большинстве случаев приводит к потере

одного или нескольких символов. Если вы хотите сделать эту

программу более защищенной от ошибок, то вам придется рассмотреть

вопрос об управлении всеми функциями печати.

Второе, на что следует обратить внимание - это использование

сохраненного вектора прерываний печати. Можно было бы обратиться к

листингу BIOS, приведенному в техническом справочнике, и найти

начальный адрес процедуры печати. Затем включить этот адрес

непосредственно в код программы так же, как это делается для других

абсолютных адресов. Однако в результате программа оказалась бы

жестко к этому адресу в системе BIOS. Если фирма IBM изменит

процедуры BIOS и, таким образом, - адрес процедуры печати, то

рассмотренная программа не сможет больше работать. Конечно, если

пишите эту программу для своей собственной машины, а покупать новую

или продавать свою программу не собираетесь, то указанных проблем

не возникнет. Однако в общем случае надо избегать использования

абсолютных адресов, если есть выбор. В приведенном примере

процедура инициализации легко может использовать вектор

прерываний печати для определения адреса процедуры печати

BIOS в ПЗУ.

В оставшейся части процедуры PRINT_HANDLER символ помещается в

буфер печати. Перед тем, как поместить символ программа проверяет,

есть ли в буфере место. Если буфер полон, программа ждет, пока

освободится место. Это ожидание не вызовет проблем, поскольку и

стандартная процедура BIOS ждет, чтобы принтер был готов принять

символ. Из соображений безопасности в регистре CX накапливается

число проходов по ветви "занято". Если это число становится равным

64K, а буфер по-прежнему полон, то это может означать какой-то

сбой. В этом случае процедура PRINT_HANDLER так же, как и BIOS,

выдает сообщение о превышении допустимого времени ожидания.

В приведенном примере процедура печати использует также

внутреннюю процедуру ADVANCE_POINTER. Эта несложная процедура

делает буфер печати циклическим. Если указатель сдвигается за

пределы буфера, подпрограмма переносит его на начало буфера. Она

аналогична процедуре BIOS для буфера клавиатуры. Только в данном

случае в буфер помещается 10000 символов, а не 16.

Интересно рассмотреть работу процедуры TIMER_HANDLER из

приведенного примера. Инициализирующая процедура связывает эту

подпрограмму с аппаратным прерыванием от таймера, поэтому на каждом

цикле таймера она получает управление. Помимо пересылки кодов на

принтер, эта процедура должна обеспечивать, через компенсацию

ускоения таймера, выполнение его обычных функций, таких как

ведение времени дня.

Сначала процедура работы с таймером проверяет, имеются ли

предназначенные для вывода на печать символы. Нет смысла пытаться

переслать символы на принтер, если пересылать нечего. Если в буфере

нет символов, процедура проходит на метку TIMER_RETURN. Этот

фрагмент процедуры обслуживает ускорение таймера.

Метка TIMER_RETURN указывает часть программы, обеспечивающую

нормальное функционирование таймера. При каждом прерывании от

таймера значение байта TIMER_COUNT увеличивается на единицу. Если

этот байт не нулевой, то процедура выходит из прерывания после

выдачи сигнала о завершении прерывания на контроллер прерываний.

Если этот байт равен нулю, то выход из программы осуществляется

посредством косвенного перехода по сохраненному вектору прерывания

от таймера TIMER_VECTOR. При этом управление передается процедуре

BIOS для определения текущего времени и выключения дисковода.

Дублировать эти операции в нашей программе не требуется. Переход в

BIOS происходит только один из 256 раз выполнения подпрограммы

работы с таймером. Но поскольку скорость таймера была увеличена в

256 раз, процедура реакции на прерывание от таймера базовой системы

ввода-вывода по-прежнему будет получать управление 18,2 раза в

секунду. Это означает, что текущее время будет поддерживаться

правильно, и мотор дисковода будет выключен вовремя. Именно поэтому

и было выбрано ускорение таймера в 256 раз, хотя и ускорения в 5

раз было бы достаточно, чтобы обеспечить работу устройства печати с

максимальной скоростью.

Ускорение таймера в 256 раз было выбрано потому, что это было

просто сделать. Однако если брать в расчет производительность, то

лучше было бы ускорить работу таймера в 5 раз, поскольку на

обработку каждого прерывания от таймера тратится по меньшей мере 10

микросекунд, и даже больше, если в буфере печати есть символы.

Время, затраченное на обработку прерываний, идет в ущерб выполнению

системой других заданий, например ассемблирования. При такой

частоте прерываний от таймера, становится заметным замедление

работы. Для оптимизации производительности следует ускорять таймер

менее, чем в 256 раз.

Что же происходит в процедуре работы с таймером, когда в буфере

есть символы, предназначенные для печати? Программа считывает порт

состояния, чтобы определить, готов ли принтер к приему символа.

Поскольку в процедуре используется базовый адрес из области данных

BIOS, то наша подпрограмма будет работать и с автономным адаптером

устройства печати, и с портом адаптера монохромного дисплея. Если

устройство печати не готово, процедура возвращает управление на

метку TIMER_RETURN, где в случае необходимости поддерживаются

стандартные функции таймера. Процедура вывода на печать не ждет,

когда устройство печати освободится, если оно занято. Мы знаем, что

прерывание от таймера очень скоро повторится, тогда мы и повторим

попытку вывода. Ожидание готовности устройства печати здесь

связывало бы бы всю систему. Результат был бы таким же, как и в

случае отсутствия буферизации печати.

Если принтер готов, программа извлекает символ из буфера и

передает его на принтер. И в данном случае программа вновь не

делает всего, что следовало бы. Подпрограмма, входящая в BIOS,

делает проверку на ситуацию ошибки при передаче каждого символа. То

же самое следовало бы делать и в нашей процедуре. Но что же

произойдет в случае сбоя? Если процедура вывода обнаружила ошибку,

то как она сможет сообщить программе, что это произошло во время

печати? В некоторых случаях к этому моменту программа передававшая

даные для печати уже завершила свою работу. Наилучший выход может

состоять в проверке ошибок при каждой пересылке символа на принтер

процедурой работы с таймером. При обнаружении ошибки процедура

PRINT_HANDLER должна выдать сообщение об ошибке, что далее все

программы будут производить вывод на печать через прерывание INT

17H. Возможно, это не идеальный вариант, но, вероятно, лучший.

Прежде чем закончить рассмотрение примера, следует обратить

внимание еще на одну проблему. Существуют и другие процедуры,

изменяющие частоту прерываний от таймера. BASICA - расширенная

версия интерпретатора Бейсика, для ускорения таймера используется

прием, во многом аналогичный приведенному. При вызове программы

BASICA после установки буферизованной печати, процедура

TIMER_HANDLER получает прерывания уже не с той частотой, которая

предполагается. Поскольку процедура TIMER_HANDLER ограничивает

передачу управления прерыванием от таймера процедуре BIOS, текущее

время замедлится в 256 раз. BASICA осуществляет также инициализацию

устройства печати, что, как мы уже видели, мешает выводу на печать.

Это означает, что программа буферизации печати будет работать не

для всех приложений. Однако она иллюстрирует использование

прерывания INT 27H для создания постоянной системной функции.

Приведенный пример иллюстрирует также метод переопределения

векторов BIOS для подцепления новой функции к уже имеющимся

программам.


Загрузка в верхнюю часть памяти

Применение прерывания DOS INT 27H является предпочтительным

способом включения в систему постоянных функций типа драйверов

устройств. Это - удобный способ сделать программу постоянной частью

системы. Пользователь может включить программу в файл AUTOEXEC.BAT,

тогда она будет загружаться автоматически. Такую автоматическую

загрузку можно использовать, когда в вашей системе имеется

специальное устройство ввода-вывода. DOS будет загружать драйвер

этого устройства при каждой загрузке системы. Вы можете даже

предпочесть собственную версию процедуры буферизации печати,

поскольку вы хотите, чтобы она постоянно загружалась в систему.

Однако выход в DOS с фиксацией программы в ОЗУ работает не

всегда. Фирма IBM предлагает три операционные системы для

персональных ЭВМ: DOS, которая и рассматривается в данной книге,

CP/M-86 фирмы Digital Research и UCSD p-System фирмы SofTech

Microsystems. Кроме указанных систем, предлагаемых фирмой IBM,

несколько независимых разработчиков распространяют свои системы.

Чтобы создать драйвер устройства, который работал бы со всеми этими

системами, нужно использовать нечто отличного от метода,

применяемого для DOS.

Допустим, у вас имеется специализированное устройство печати,

которое вы хотите продавать как приспособление к IBM PC. Поскольку

ваш принтер - отноительно дешевое устройство, для него потребуется

больше управления со стороны BIOS, чем для принтера фирмы IBM. Вы

конструируете принтер и устройство подсоединения и пишете BIOS

программу для поддержки его работы. Если вы пользуетесь

прерыванием INT 27H, то ваше устройство можно передавать только

пользователям, имеющим на своей персональной ЭВМ DOS. Необходим

такой способ загрузки драйвера устройства, который бы работал во

всех операционных системах.

Способ загрузки, годный не только для DOS, называется загрузкой

в верхние адреса оперативной памяти. При этом управление системой

перехватывается непосредственно после процедуры самоконтроля при

включении питания. Это может быть реализовано при помощи

специальной дискеты загрузки. Программа будет записана на дискету,

которая вставляется в дисковод перед включением питания.

Подпрограмма загрузки, входящая в BIOS, загружает драйвер

устройства с дискеты в верхнюю часть оперативной памяти. Затем

можно изменить размер области данных сообщаемый BIOS в соответствии

с имеющимся объемом оперативной памяти. При загрузке программы в

верхние адреса размер доступной оперативной памяти уменьшается.

Если после этого загрузить стандартную операционную систему, будет

восстановлено нормальное функционирование ЭВМ. Все операционные

системы фирмы IBM учитывают объем памяти BIOS при определении

границ оперативной памяти. Указанные системы не затрагивают

программ, загруженных в верхние адреса. Если система удовлетворяет

указанным требованиям, то можно пользоваться загрузкой в верхние

адреса оперативной памяти.

Приведем пример для иллюстрации описанного приема. На Фиг. 10.2

представлен листинг ассемблирования двух подпрограмм. Первая

подпрограмма осуществляет инициализацию и загрузку драйвера

устройства. Вторая подпрограмма является собственно драйвером

устройства. Позже станет ясным, почему удобнее было разделить эту

программу на две части.

A
Microsoft (R) Macro Assembler Version 5.00 1/1/80 01:21:50

Фиг. 10.2(а) Загрузчик для создания псевдо-диска Page 1-1

PAGE ,132

TITLE Фиг. 10.2(а) Загрузчик для создания псевдо-диска

0000 NEW_DISK SEGMENT

0000 DISK_BIOS LABEL FAR

0003 ORG 3

0003 OLD_VECTOR LABEL WORD

0003 NEW_DISK ENDS

0000 ABS0 SEGMENT AT 0

004C ORG 13H*4

004C DISK_VECTOR LABEL WORD

0410 ORG 410H

0410 EQUIPMENT LABEL WORD

0413 ORG 413H

0413 MEMORY_SIZE LABEL WORD

= 00A0 DISK_SIZE EQU 160

7C00 ORG 7C00H ; Место,в которое заносится загрузчик ДОС

7C00 BOOT_RECORD LABEL FAR

7C00 ABS0 ENDS

0000 CODE SEGMENT

ASSUME CS:CODE,DS:ABS0

7C00 ORG 7C00H

7C00 8C C8 MOV AX,CS

7C02 8E D8 MOV DS,AX

7C04 8E C0 MOV ES,AX

7C06 8D 36 7C00 R LEA SI,BOOT_RECORD

7C0A 8D 3E 7A00 R LEA DI,BOOT_RECORD-200H ; Место,на которое переносится

7C0E B9 0200 MOV CX,512 ; загрузчик ДОС

7C11 F3/ A4 REP MOVSB ; Перенесение загрузчика

7C13 E9 7A16 R JMP NEXT_LOCATION-200H

7C16 NEXT_LOCATION:

Фиг. 10.2 программа создания псевдо-диска (начало)

7C16 83 06 0410 R 40 ADD EQUIPMENT,40H ; Увеличение числа дисководов

7C1B A1 0413 R MOV AX,MEMORY_SIZE

7C1E 2D 00A0 SUB AX,DISK_SIZE

7C21 A3 0413 R MOV MEMORY_SIZE,AX ; Уменьшение доступной ДОС памяти,необхо-

7C24 B1 06 MOV CL,6 ; димое для размещения псевдо-диска

7C26 D3 E0 SHL AX,CL ; Умножение на 1024/16

7C28 8E C0 MOV ES,AX ; Сегментная часть адреса нового диска

7C2A B8 0201 MOV AX,201H ; Чтение сектора в эту область

7C2D BB 0000 MOV BX,0

7C30 B9 0002 MOV CX,2

7C33 BA 0000 MOV DX,0

7C36 CD 13 INT 13H

7C38 72 1A JC BOOT_ERROR

ASSUME ES:NEW_DISK

7C3A A1 004C R MOV AX,DISK_VECTOR

7C3D 26: A3 0003 R MOV OLD_VECTOR,AX

7C41 A1 004E R MOV AX,DISK_VECTOR+2 ; Сохранение старого вектора пре-

7C44 26: A3 0005 R MOV OLD_VECTOR+2,AX ; рывания 13h

7C48 C7 06 004C R 0000 MOV DISK_VECTOR,0 ; Установка вектора прерывания 17h

7C4E 8C 06 004E R MOV DISK_VECTOR+2,ES ; на новое место

7C52 EB 07 JMP SHORT REBOOT ; Чтение загрузчика с другой дискеты

7C54 BOOT_ERROR:

7C54 8D 36 7A93 R LEA SI,ERROR_MSG-200H ; Печать сообщения об ошибке

7C58 E8 7C81 R CALL PRINT_MSG

7C5B REBOOT:

7C5B 8D 36 7AA5 R LEA SI,BOOT_MSG-200H ; Печать сообщения о загрузке ДОС

7C5F E8 7C81 R CALL PRINT_MSG

7C62 WAIT_BOOT:

7C62 B4 00 MOV AH,0

7C64 CD 16 INT 16H ; Ожидание ввода с клавиатуры

7C66 3C 20 CMP AL,' ' ; Ожидается ввод пробела

7C68 75 F8 JNE WAIT_BOOT

7C6A B8 0201 MOV AX,201H

7C6D BB 7C00 MOV BX,7C00H

7C70 B9 0001 MOV CX,1

7C73 BA 0000 MOV DX,0

7C76 8E C2 MOV ES,DX ; Ввод на стандартное место загрузчика

7C78 CD 13 INT 13H

7C7A 72 D8 JC BOOT_ERROR

7C7C EA 7C00 ---- R JMP BOOT_RECORD

7C81 PRINT_MSG PROC NEAR

7C81 2E: 8A 04 MOV AL,CS:[SI] ; Взять символ для печати

7C84 46 INC SI

7C85 3C 24 CMP AL,'$' ; Проверка на символ конца вывода

7C87 75 01 JNE OUTPUT

7C89 C3 RET

7C8A OUTPUT:

7C8A B4 0E MOV AH,14

7C8C BB 0000 MOV BX,0

7C8F CD 10 INT 10H ; Вывод на дисплей через BIOS

7C91 EB EE JMP PRINT_MSG

7C93 8E E8 A8 A1 AA A0 20 ERROR_MSG DB 'Ошибка загрузки',13,10,'$'

A7 A0 A3 E0 E3 A7 AA

A8 0D 0A 24

Фиг. 10.2 программа создания псевдо-диска (продолжение)

7CA5 82 E1 E2 A0 A2 EC E2 BOOT_MSG DB 'Вставьте новую дискету с ДОС',13,10

A5 20 AD AE A2 E3 EE

20 A4 A8 E1 AA A5 E2

E3 20 E1 20 84 8E 91

0D 0A

7CC3 A8 20 AD A0 A6 AC A8 DB 'и нажмите на пробел',10,13,'$'

E2 A5 20 AD A0 20 AF

E0 AE A1 A5 AB 0A 0D

24

7CD9 PRINT_MSG ENDP

7CD9 CODE ENDS

END

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:49

Фиг. 10.2(б) Программа обслуживания псевдо-диска Page 1-1

PAGE ,132

TITLE Фиг. 10.2(б) Программа обслуживания псевдо-диска

0000 CODE SEGMENT

ASSUME CS:CODE

;--------------------------------------------

; Эта программа находится в секторе 1 трека 0

; псевдо-диска. Чтение и запись на устройство 2

; переадресуется на эту программу

;--------------------------------------------

0000 DISK PROC FAR

= 0140 DISK_SIZE EQU 320 ; Размер псевдо-диска в сектрах

0000 EB 05 90 JMP START_BIOS

0003 ???????? ORIGINAL_VECTOR DD ?

0007 START_BIOS:

0007 80 FA 02 CMP DL, 2 ; Программа обрабатывает только обращения

000A 74 05 JE L1 ; к устройству (дисководу) 2

000C OLD_BIOS:

000C 2E: FF 2E 0003 R JMP ORIGINAL_VECTOR ; Переход на стандартную программу

0011 L1:

0011 3C 01 CMP AL, 1

0013 76 F7 JBE OLD_BIOS

0015 80 FC 04 CMP AH, 4

0018 72 06 JB READ_WRITE ; Обрабатываются только команду чтения и

; записи

001A OK_RETURN:

001A B4 00 MOV AH, 0 ; Код возврата - 0

001C F8 CLC ; Сброс C-флага - нет ошибки

001D CA 0002 RET 2

0020 READ_WRITE:

0020 53 PUSH BX ; Сохранение регистров

0021 51 PUSH CX

0022 52 PUSH DX

0023 56 PUSH SI

0024 57 PUSH DI

0025 1E PUSH DS

0026 06 PUSH ES

Фиг. 10.2 программа создания псевдо-диска (продолжение)

;----- Вычисление адреса расположения требуемой записи в псевдо-диске

0027 50 PUSH AX ; Сохранение кода требуемой операции

0028 B0 08 MOV AL, 8 ; Число секторов на треке

002A F6 E5 MUL CH

002C B5 00 MOV CH, 0

002E 03 C1 ADD AX, CX ; Прибавление номера сектора

0030 80 FE 00 CMP DH, 0 ; Проверка на номера стороны

0033 74 03 JE HEAD_0

0035 05 0140 ADD AX, 320 ; Переключение на второю сторону

0038 HEAD_0:

0038 48 DEC AX

0039 3D 0140 CMP AX, DISK_SIZE ; Вычисленное значение правильно?

003C 76 0E JBE DISK_OK

003E RECORD_NOT_FOUND:

003E 58 POP AX ; Восстановление регистров

003F 07 POP ES

0040 1F POP DS

0041 5F POP DI

0042 5E POP SI

0043 5A POP DX

0044 59 POP CX

0045 5B POP BX

0046 B4 04 MOV AH, 4 ; Ошибка: сектор не найден

0048 F9 STC

0049 CA 0002 RET 2 ; Возврат с указанием об ошибке

004C DISK_OK:

004C B1 05 MOV CL, 5

004E D3 E0 SHL AX, CL ; Определение расположения данных на

0050 8C C9 MOV CX, CS ; псевдо-диске

0052 03 C8 ADD CX, AX ; В регистре CX сегментная часть адреса

; данных на диске

0054 51 PUSH CX

0055 8B D3 MOV DX, BX ; В регистре DX адрес передачи

0057 B1 04 MOV CL, 4

0059 D3 EA SHR DX, CL

005B 8C C1 MOV CX, ES

005D 03 D1 ADD DX, CX ; В регистре DX сегментная часть адреса

; передаваемых данных

005F 59 POP CX

0060 83 E3 0F AND BX, 0Fh ; Выделение младших 4 разрядов

0063 58 POP AX ; Восстановление код требуемой операции

0064 80 FC 02 CMP AH, 2

0067 74 11 JE READ_OPN

0069 WRITE_OPN:

0069 8C CE MOV SI, CS

006B 3B CE CMP CX, SI ; Проверка на запись поверх этой программы

006D 74 1B JE ALL_DONE

006F 8E C1 MOV ES, CX

0071 BF 0000 MOV DI, 0

0074 8E DA MOV DS, DX

0076 8B F3 MOV SI, BX ; Установка параметров передачи

0078 EB 09 JMP SHORT DO_MOVE

007A READ_OPN:

007A 8E D9 MOV DS, CX

Фиг. 10.2 программа создания псевдо-диска (продолжение)

007C BE 0000 MOV SI, 0

007F 8E C2 MOV ES, DX

0081 8B FB MOV DI, BX

0083 DO_MOVE:

0083 8A E8 MOV CH, AL ; Число слов в секторе

0085 B1 00 MOV CL, 0

0087 FC CLD

0088 F3/ A5 REP MOVSW ; Пересылка данных

008A ALL_DONE:

008A 07 POP ES ; Восстановление регистров

008B 1F POP DS

008C 5F POP DI

008D 5E POP SI

008E 5A POP DX

008F 59 POP CX

0090 5B POP BX

0091 B4 00 MOV AH, 0 ; Нормальное окончание

0093 F8 CLC

0094 CA 0002 RET 2

0097 DISK ENDP

0097 CODE ENDS

END

Фиг. 10.2 (а) Процедура загрузки для виртуального диска;

(b) Программа драйвера виртуального диска.

Драйвер устройства, приведенный в рассматриваемом примере,

реализует модель диска в оперативной памяти. Мы возьмем 160К

памяти системы и будем исполльзовать ее не как оперативную

память, а как дискету. Мы выбрали именно 160К потому, что это

минимальный объем дискеты фирмы IBM. Очевидно, при большем объеме

оперативной памяти можно моделировать дискету большего объема.

Подпрограмму псевдо-диска можно использовать для повышения

производительности программ, производящих интенсивный обмен с

диском. Например, если поместить на псевдо-диск ассемблер и

исходный код программы, ассемблирование будет произведено не за

минуты, а за секунды. Производительность некоторых программ может

быть повышена более чем на порядок. Платой за такое повышение

производительности являются 160K байт оперативной памяти, отводимые

под псевдо-диск. Если в системе, которая в основном используется

для редактирования и ассемблирования, имеется 256 кбайт памяти, то

в действительности для ассемблера достаточно всего лишь 96 кбайт.

Оставшиеся 160 кбайт можно использовать для моделирования диска в

оперативной памяти. Следует помнить, что содержимое такого диска

теряется при отключении питания, поэтому, прежде чем окончить

работу, убедитесь, что информация скопирована на настоящую

дискету.

Первая подпрограмма на Фиг. 10.2 - процедура загрузки. Ее

код находится в секторе 1 дорожки 0 загрузочной дискеты. Как

поместить программу туда, будет объяснено позже. Подпрограмма POST

при завершении считывает содержимое сетора 1 дорожки 0 в память,

по адресу 0:7C00H. Затем POST передает управление по первому

адресу этой записи. Таким образом система фирмы IBM загружает в

память DOS или любую другую операционную систему. А мы как раз и

собираемся, загружать свою собственную простую операционную

систему.

Сегмент NEW_DISK определяет адрес подпрограммы-драйвера

устройства, также представленной на втором листинге (см. Фиг.

10.2). Поскольку наши подпрограммы ассемблируются отдельно, этот

сегмент для связи процедуры загрузки и драйвера устройства во время

выполнения. Сегмент ABS0 локализует векторы прерываний, заменяемые

в процедуре загрузки. В сегменте CODE, содержатся команды,

загружаемые с дискеты. Сегмент CODE - единственная часть

приведенной программы, находящаяся на загрузочной дискете.

Первое, что делает программа инициализации - пересылает себя по

адресу 0:7A00H. Затем, в процессе инициализации, процедура

перезагружает систему, чтобы загрузить настоящую операционную

систему. Эта загрузка производится по адресу 0:7C00H. Если бы

процедура инициализации не переносила себя на другое место, она бы

считывала следующую запись загрузки в ту область памяти, где

находится сама.

С адреса NEXT_LOCATION процедура инициализации инсталирует

драйвер устройства. Она изменяют флаги оборудования для указания на

наличие дополнительного дисковода по сравнению с установкой внешних

переключателей. Это "убеждает" операционную систему, что диск в

оперативной памяти является частью технического обеспечения. При

инициализации значение MEMORY_SIZE уменьшается на 160 кбайт,

которые резервируются для моделирования диска. Это предотвращает

использование предназначенной для него памяти. Кроме того,

программа подсчитывает значение сегмента для этой области в 160

кбайт, чтобы знать, куда загружать драйвер устройства. Когда это

выполнено, подпрограмма инициализации загружает в зарезервиро-

ванную память содержимое сектора 2 дорожки 0 загрузочной дискеты.

Как поместить драйвер устройства в сектор 2 будет описано при

размещении программы загрузки в секторе 1.

После чтения процедуры драйвера устройства, подпрограмма

инициализации изменяет вектор прерывания BIOS дискеты BIOS (INT

13H), чтобы он указывал на новый драйвер устройства. Как и в

предыдущем примере, эта процедура сохраняет старый вектор. Новому

драйверу этот вектор нужен чтобы при необходимости считывать данные

с настоящей дискеты, а не с ее модели. Наконец, наша программа

загружает систему. Она предлагает пользователю вставить системную

дискету, ждет утвердительного ответа и считывает запись загрузки.

(Если бы процедура предварительно не произвела пересылку программы,

то сейчас она была бы испорчена). Если все идет нормально, то

процедура осуществляет переход по первому адресу записи загрузки, в

результате чего управление получает стандартная операционная

система.

Прежде чем двинуться дальше, рассмотрим, как поместить

процедуру загрузки на новую загрузочную дискету. Во-первых,

необходима пустая отформатированная дискета. Она и станет

загрузочной. Листинг на Фиг. 10.3 показывает, что ассемблирование и

редактирование связей процедуры загрузки происходят, как обычно.

Вызовите программу DOS DEBUG и загрузите процедуру инициации.

Она загружается со смещением 7C00H, установленным программой DEBUG.

Регистры устанавливаются таким образом, чтобы использовать BIOS для

записи одного сектора дискеты. Это выполняет трехбайтовая

программа, находящаяся по адресу 200H. Если после записи нет

состояния ошибки, то запись инициализации уже на дискете.

Для записи драйвера устройства в сектор 2 выполните следующие

шаги, показанные на Фиг. 10.3. С помощью программы DEBUG мы

загружаем в память драйвер псевдодиска. Команда записи программы

DEBUG помещает код драйвера в сектор с относительным номером 1

(сектор 2 дорожки 0) дискеты, находящейся на дисководе A:.

Аналогичный способ можно было бы применить и для занесения на

дискету записи инициализации.

Такой способ формирования вызова BIOS в программе DEBUG

для записи на дискету может использоваться почти для всех функций

BIOS. Проследить, что именно происходит при вызове BIOS, можно с

помощью программы DEBUG. Можно установить регистры для вызова и

написать несложную трехбайтовую программу, осуществляющую

программное прерывание и производящую возврат в DEBUG. Этот прием

удобен также для тестирования собственного драйвера устройства.

Вернемся к процедуре драйвера псевдо-диска во второй части Фиг.

10.2. Заметим, что процедура загрузки сохранила исходный вектор

дискеты (INT 13H) в этом сегменте со смещением 3. Подпрограммы-

драйвера используют этот вектор для реализации всех функций

дискеты, которые не реализуются псевдо-диском. В приведенной

подпрограмме предполагается, что псевдо-диск находится на дисководе

2. На запрос любого другого дисковода процедура передает управление

BIOS, используя приэтом сохраненный в ORIGINAL_VECTOR исходный

вектор. Аналогично и запрос на смену дискеты передается BIOS. Если

функция, запрашиваемая для псевдо-дисковода, не считывание и не

запись, то драйвер псевдо-диска не производит никаких действий, и

происходит возврат с нормальным кодом завершения. Псевдо-диск

не требует форматирования, а поскольку у нас нет контроля ошибок,

то не остается ничего проверять.

Если запрашиваемой операцией является считывание или запись,

драйвер вычисляет адрес соответствующего псевдо-сектора в памяти.

При обращении за границу диска поцедура возвращает запись об ошибке

отсуствия адреса. Код драйвера устанавливает регистры источника и

назначения в соответствии с направлением операции. Наконец,

команда REP MOVSW передает данные между псевдо-диском и буфером

пользователя. Рассматриваемая программа всегда устанавливает

нормальный код завершения и производит возврат в вызывающую

программу.

Данный пример показывает, как реализовать моделирование диска,

однако он не готов для продуктивного использования. Для того, чтобы

стать утилитой общего назначения, эта программа должна быть

преобразована для обеспечения работы с любым прсевдоустройством, а

не только со вторым. Программу можно было бы изменить для работы с

сектором любой длины, хотя обычно этого не требуется. Фактически,

если моделирование диска применяется только при работе с DOS,

процедура инициализации должна форматировать дискету, записав

A

A>MASM BOOT,,,;

The IBM Personal Computer MACRO Assembler

Version 1.00 (C)Copyroght IBM Corp 1981

Warning Severe

Errors Errors

0 0

A>B:LINK BOOT,,,;

IBM Personal Computer Linker

Version 1.00 (C)Copyroght IBM Corp 1981

Warning: No STACK segment

Therhe was 1 error detected

A>MASM DISK,,,;

The IBM Personal Computer MACRO Assembler

Version 1.00 (C)Copyroght IBM Corp 1981

Warning Severe

Errors Errors

0 0

A>B:LINK DISK,,,;

IBM Personal Computer Linker

Version 1.00 (C)Copyroght IBM Corp 1981

Warning: No STACK segment

Therhe was 1 error detected

A>DEBUG BOOT.EXE

-R

      AX=0000
BX=0000  CX=7CD3  DX=0000
SP=0000  BP=0000  SI=0000
DI=0000

      DS=06D7
ES=06D7  SS=06E7  CS=06E7
IP=0000   NV  UP DI PL NZ NA PO NC

06E7:0000 0000 ADD [BX+SI],AL DS:0000=CD

-U7C00 7C05

06E7:7C00 8CC8 MOV AX,CS

06E7:7C00 8CD8 MOV DS,AX

06E7:7C00 8CC0 MOV ES,AX

-RAX

AX 0000

:301

-RBX

BX 0000

:7C00

-RCX

CX 7CD3

:1

-RDX

DX 0000

:

-RES

ES 06D7

:6E7

-E200

O6D7:0200 OO.CD 00.13 00.CC ;*** Здесь вставьте загрузочную дискету

-g=100

      AX=0000
BX=7C00  CX=0001  DX=0000
SP=0000  BP=0000  SI=0000
DI=0000

      DS=06D7
ES=06D7  SS=06E7  CS=06E7
IP=0102   NV  UP EI PL NZ NA PE NC

06E7:0102 CC INT 3

-NDISK.EXE ;*** Здесь вставьте программную дискету

-L

-UD 10

06E7:0000 EB05 JMPS 0007

06E7:0002 90 NOP

06E7:0003 0000 ADD [BX+SI],AL

06E7:0005 0000 ADD [BX+SI],AL

06E7:0007 80FA02 CMP DL,02

06E7:000A 7405 CMP 0011

06E7:000C 2E SEG CS

06E7:000D FF2E0300 JMP L,[0003] ;*** Здесь вставьте загрузочную дискету

-W0 0 1 1

-Q

A>

A

Фиг. 10.3 Шаги подготовки загрузки в верхние

адреса памяти

справочник и таблицу размещения файлов FAT. При нынешнем виде этой

процедуры после загрузки DOS вы должны "форматировать" диск C:. Для

псевдо-диска не требуеися физического форматирования, но утилита

FORMAT записывает таблицу FAT и каталог, необходимые для

функционирования DOS.

Эта процедура обеспечивает также сохранение процедуры-драйвера

устройства в псевдо-секторе 1 на дорожке 0. Система DOS не

использует указанный сектор дисковода C:, однако другие системы

могут это делать. Вы вооозможно, заметили, что программа псевдо-

диска предотвращает запись в смоделированный сектор дорожки 0, так

что программа по крайней мере не уничтожит саму себя.

Вообще говоря, метод загрузки в верхние адреса оперативной

памяти довольно сложен. Необходима загрузка с двух дискет, что

требует от оператора дополнительных манипуляций. Если не

предполагается использование программы в каких-либо других

системах, кроме DOS, то гораздо удобнее использовать прерывание INT

27H. В противном случае загрузка в верхние адреса оперативной

памяти может оказаться единственно возможным способом.


Untitled

Процедуры на языке Ассемблера

Помимо постоянно находящихся в памяти драйверов и автономных

программ, язык ассемблера используется и для подпрограмм в больших

программах, написанных, как правило, на языке высокого уровня.

Такие языки высокого уровня как Бейсик или Паскаль позволяют быстро

и ясно писать большие программы. Однако эти языки позволяют делать

не все, что может понадобиться. Сказанное особенно справедливо для

персональных ЭВМ, поскольку хорошая прикладная программа здесь

требует использования всех возможностей машины. Достигнуть этого на

языке высокого уровня удается не всегда. Либо язык высокого уровня

не позволяет реализовать требуемую функцию (например, вызов

процедуры BIOS), либо накладные расходы языка делают прикладную

программу слишком медленной (например, за счет операторов Бейсика

PEEK и POKE для считывания конкретных ячеек памяти).

К счастью, в языках высокого уровня имеется механизм,

позволяющий вызывать подпрограммы, написанные на языке ассембоера.

Требуемую функцию можно выполнить быстро и эффективно на машинном

языке, а затем вернуться к языку высокого уровня для выполнения

остальной работы. В этом разделе будут приведены примеры,

иллюстрирующие два способа включения процедуры ассемблера в

программу, написанную на языке высокого уровня.


Процедура Бэйсика BLOAD

При первом способе подпрограмма на машинном языке добавляется к

программе для интерпретатора Бэйсика. Пожалуй, использование

интерпретатора Бейсика является наиболее распространенным методом

написания программ для персональной ЭВМ. Подпрограмма на языке

ассемблера, которую предполагается включить в разрабатываемую

программу, довольно велика - более 100 байт. Ассемблерную процедуру

такой длины трудно вставить в текст программы на языке Бейсик,

способ, позволяющий сделать это будет рассмотрен в следующем

примере.

Функция, которую мы добавляем к программе на языке Бейсик

позволяет выводить на принтер графические изображения. IBM PC

снабжена графическими средствами. Графические команды позволяют

программно управлять отдельными точками, выводимыми на принтер, во

многом подобно тому, как в графическом режиме адаптер цветного

дисплея дает программисту возможность управлять отдельными точками

растра. На Фиг. 10.4 представлены графические команды, которые

потребуются в рассматриваемом примере. Практически, графические

функции реализуются на принтере через управляющие последователь-

ности символов. Вместо символа в коде ASCII программа выдает на

принтер служебный символ (27 в коде ASCII). Следующие за ним

символы задают уже не символы для вывода на печать, а определенные

действия принтера. Как видно из Фиг. 10.4, существуют команды для

вывода на принтер изображения точки, в результате выполнения

которых печатается определенная точка изображения.

Команда Действие

---------------------------------------------------------

ESC + "3" + n Установка промежутка между

строками n/216

Esc + "K" +n1+n2+v1...vk Печатать образы точек v1...vk

(k = n1 + 256*n2) как 480 точек поперек страницы

---------------------------------------------------------

Фиг.10.4 Графические команды для принтера

Приведенная на Фиг. 10.5 программа использует указанные команды

для вывода на принтер образа экрана графического дисплея размером

320*200 точек. Каждая точка растра передается на принтер. Если

точка на экране имеет цвет фона, то соответствующая точка на печать

не выдается. Если точка окрашена в один из трех основных цветов, то

программа выводит на печать черную точку. Эта программа не

масштабирует изображение, поэтому окружность на экране может

отобразиться в эллипс на принтере. Между тремя основными цветами не

проводится различий. Цветное изображение превращается в

черно-белое.

Подпрограмма PRINT_SCREEN является процедурой типа FAR. Вызов

ее из языка Бейсик является вызовом типа FAR, поэтому и возврат в

программу должен быть соответствующего типа. Последовательность ESC

+ "3" + 24 устанавливает такой интервал между строками печати, что

один ряд точек вплотную примыкает к другому. В печатающей головке

имеется восемь иголок, расстояние между которыми равно 1/72 дюйма.

Если сделать интервал между строками равным 8/72 дюйма (или 24/216

дюйма), то ряды точек соединятся. Приведенный фрагмент программы

показывает способ пересылки на устройство печати последовательности

служебных символов. Управляющая последовательность символов и чисел

пересылается на принтер как обычные символы. Остальное обеспечивает

устройство печати.

При каждом проходе печатающей головки на бумаге остается по

восемь рядов точек (по одному на каждую иголку печатающей головки)

в каждой из 320 колонок. От метки NEXT_ROW в программе

последовательность ESC + "K" + 64 + 1 пересылается на принтер. Это

означает, что последующие 320 байт (64 + 1*256) являются образами

точек для получения графического изображения на принтере. В

графическом режиме "K" на принтере можно получить изображение

шириной до 480 точек.

Для считывания точек с дисплея программа использует видео

функцию BIOS . Эта функция считывает восемь рядов точек текущнго

столбца и собирает их в один байт: "1" означает, что точка имеет

основной цвет и должна появиться на бумаге. Цикл продолжается через

метку NEXT_COLUMN - до тех пор, пока все 320 столбцов (что

соответствует 320 байтам) не будут переданы на принтер . После

перехода принтера на новую строку при помощи служебных символов

"возврат каретки" и "перевод строки" (13 и 10 в коде ASCII),

программа пересылает следующую группу из восьми рядов. За 25

проходов печатающей головки выводятся все 200 рядов. Возврат в

интерпретатор Бейсика производится при помощи возврата типа FAR.

В рассматриваемой программе было бы удобно использовать

процедуру PRINT. Эта процедура выдает один байт на принтер при

помощи функции печати BIOS. Функция помещает необходиые для базовой

системы ввода-вывода установки регистров в определенное место. Если

не использовать указанную функцию, то программа сама должна была бы

устанавливать регистры AH и DX равными нулю перед каждым вызовом

процедуры PRINT.

A
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:57

Фиг. 10.5 Печать графической копии дисплея Page 1-1

PAGE ,132

TITLE Фиг. 10.5 Печать графической копии дисплея

= 001B ESC EQU 27 ; Символ Escape

0000 CODE SEGMENT

ASSUME CS:CODE

0000 PRINT_SCREEN PROC FAR

0000 B0 1B MOV AL, ESC ; Установка перевода строки на 1/8 дюйма

0002 E8 0060 R CALL PRINT

0005 B0 33 MOV AL, '3'

0007 E8 0060 R CALL PRINT

000A B0 18 MOV AL, 24 ; 1/8 = 24/216 дюйма

000C E8 0060 R CALL PRINT

000F BA 0000 MOV DX, 0 ; Номер строки

0012 NEXT_ROW:

0012 B0 1B MOV AL, ESC

0014 E8 0060 R CALL PRINT

0017 B0 4B MOV AL, 'K'

0019 E8 0060 R CALL PRINT

001C B0 40 MOV AL, 320-256

001E E8 0060 R CALL PRINT

0021 B0 01 MOV AL, 1

0023 E8 0060 R CALL PRINT

0026 B9 0000 MOV CX, 0 ; Номер столбца

0029 NEXT_COLUMN:

0029 52 PUSH DX ; Сохранение номера строки

002A BB 0008 MOV BX, 8 ; Число одновлеменно обрабатываемых точек

002D NEXT_DOT:

002D D0 E7 SHL BH, 1 ; Освобождение младшего разряда

002F B4 0D MOV AH, 13 ; Чтение цвета точки из памяти дисплея

0031 CD 10 INT 10h

0033 0A C0 OR AL, AL

0035 74 03 JZ BACKGROUND ; Проверка на цвет фона

0037 80 CF 01 OR BH, 1 ; Не фон, необходимо вывести точку на печать

003A BACKGROUND:

003A 42 INC DX ; Переключение на следующую строку

003B FE CB DEC BL ; Уменьшение счетчика строк в данном проходе

003D 75 EE JNZ NEXT_DOT

003F 8A C7 MOV AL, BH ; Печать 8-ми точек

0041 E8 0060 R CALL PRINT ; Вывод на печать

0044 5A POP DX ; Восстановление номера строки начала прохода

0045 41 INC CX ; Переключение на следуюий столбец

0046 81 F9 0140 CMP CX, 320 ; Все столбцы выведены?

004A 75 DD JNZ NEXT_COLUMN

004C B0 0D MOV AL, 13 ; Переход на следующую строку на принтере

004E E8 0060 R CALL PRINT

0051 B0 0A MOV AL, 10

0053 E8 0060 R CALL PRINT

0056 83 C2 08 ADD DX, 8 ; Переключение на слдующую группу из 8 строк

0059 81 FA 00C8 CMP DX, 200 ; Все строки выведены?

005D 72 B3 JB NEXT_ROW

005F CB RET ; Возврат в BASIC

Фиг. 10.5 Печать графического экрана (начало)

0060 PRINT_SCREEN ENDP

0060 PRINT PROC NEAR

0060 52 PUSH DX

0061 B4 00 MOV AH, 0 ; Печать символа, находящегося в регистре AL

0063 BA 0000 MOV DX, 0

0066 CD 17 INT 17h

0068 5A POP DX

0069 C3 RET

006A PRINT ENDP

006A CODE ENDS

ENDA

Фиг. 10.5 Печать графического экрана (продолжение)

Как же обратиться к этой процедуре из программы, написанной на

языке Бейсик? В языке Бейсик существуют два способа подключения

подпрограмм. Во время работы интерпретатор Бейсика использует

оставшуюся память системы (до 64 кбайт) в качестве рабочей области.

Если в системе более 96 кбайт памяти, часть памяти будет не

доступна для интерпретатора Бейсика. Лучше всего поместить нашу

процедуру в эту область. Если свободной области памяти нет, то

можно специально выделить некоторый объем памяти из рабочей области

интерпретатора Бейсика для хранения подпрограммы. В данном примере

подпрограмма будет храниться вне рабочей области интерпретатора

Бейсика. В следующем примере будет показано, как включить процедуру

в контролируемую интерпретатором Бейсика область памяти.

На Фиг. 10.6 показана последовательность действий для

подготовки подпрограммы к дальнейшему использованию.

Соответствующая информация приведена в приложении C справочника по

языку Бейсик. Программа (на Фиг. 10.6) предназначена для машины с

оперативной памятью 96 кбайт и более. Программа ассемблируется

обычным образом. При редактировании связей задается опция /H.

Редактор связей создает файл типа .EXE таким образом, что программа

загружается в верхние адреса оперативной памяти, а не с самого

низкого из доступных адресов.

Чтобы подключить процедуру к программе, написанной на языке

Бейсик, нам потребуется программа DEBUG. После загрузки программы

на языке Бейсик во время работы программы DEBUG и уточнения

значений регистров, загружаем процедуру на языке ассемблера.

Приведенный пример реализован на машине с памятью 128 кбайт.

Значение регистра CS, равное 1FF9H, указывает на то, что программа

помещена в 70H байт от конца оперативной памяти. Заметим, что

рассматриваемая программа имеет объем около 6AH байт, так что

редактор связей разместил программу с самого старшего адреса

памяти, допускающего выравнивание по границе параграфа. Следует

также заметить, что эта программа является сегментно -

перемещаемой. Это означает, что ее можно перемещать в памяти,

поскольку первая ее команда имеет смещение 0 относительно текущего

сегмента кода. При переносе этой программы на машину с большим или

меньшим объемом памяти эта особенность оказывается решающей.

A

B>A:MASM FIG10-5,,,;

The IBM Persona Computer MACRO Assembler

Version 1.00 (C)Copyright IBM Corp 1981

Warning Severe

Errors Errors

0 0

B>A:LINK FIG10-5,,,/H;

IBM Personal Computer Linker

Version 1.10 (C)Copyright IBM Corp 1982

Warning: No STACK segment

There was 1 error detected

B>A:DEBUG A:BASIC.COM

-R

AX=0000 BX=0000 CX=2B80 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000

DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI PL NZ NA PO NC

04B5:0100 E91329 JMP 2A16

-NFIG10-5.EXE

-L

-R

AX=0000 BX=0000 CX=006A DX=0000 SP=0000 BP=0000 SI=0000 DI=0000

DS=04B5 ES=04B5 SS=1FF9 CS=1FF9 IP=0000 NV UP DI PL NZ NA PO NC

1FF9:0000 B01B MOV AL,1B

-RSS

SS 1FF9

:4B5

-RCS

CS 1FF9

:4B5

-RIP

IP 0000

:100

-G

---- В интерпретаторе Бэйсика введите команды

DEF SEG = &H1FF9

BSAVE "FIG10-5",0,&H70

Фиг. 10.6 (а) Создание подпрограммы для Бэйсика

B>A:MASM FIG10-5,,,;

The IBM Persona Computer MACRO Assembler

Version 1.00 (C)Copyright IBM Corp 1981

Warning Severe

Errors Errors

0 0

B>A:LINK FIG10-5,,,/H;

IBM Personal Computer Linker

Version 1.10 (C)Copyright IBM Corp 1982

Warning: No STACK segment

There was 1 error detected

B>A:DEBUG A:BASIC.COM /M:&H8000

-R

AX=0000 BX=0000 CX=2B80 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000

DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI PL NZ NA PO NC

04B5:0100 E91329 JMP 2A16

-NFIG10-5.EXE

-L

-R

AX=0000 BX=0000 CX=006A DX=0000 SP=0000 BP=0000 SI=0000 DI=0000

DS=04B5 ES=04B5 SS=0FF9 CS=0FF9 IP=0000 NV UP DI PL NZ NA PO NC

0FF9:0000 B01B MOV AL,1B

-RSS

SS 0FF9

:4B5

-RCS

CS 1FF9

:4B5

-RIP

IP 0000

:100

-G

---- В интерпретаторе Бэйсика; введите команды

DEF SEG = &H0FF9

BSAVE "FIG10-5",0,&H70

(b)

A

Фиг. 10.6 (а) Создание подпрограммы для Бэйсика; (b) Создание

подпрограммы для Бэйсика на машине с 64K

Теперь мы имеем дело с интерпретатором Бейсика. Нам необходимо

восстановить содержимое регистров таким, каким оно было после

загрузки Бейсика. После того, как интерпретатор Бейсика запущен,

для локализации подпрограммы используется команда DEF SEG. Команда

BSAVE помещает обратно на дискету объектный код, готовый к новой

загрузке из Бейсика при помощи команды BLOAD.

В части (b) Фиг. 10.6, повторяются действия из части (а), но

для машины с объемом памяти 64 кбайт. Различие здесь состоит в том,

что интерпретатор Бейсика не может использовать всю память под

рабочую область. Опция /M в командной строке Бейсика ограничивает

рабочую область Бейсика и оставляет место для подпрограммы.

Аналогичная команда потребуется и при запуске программы.

Программой DEBUG можно воспользоваться для вашей ассемблерной

процедуры и при работе интерпретатора Бейсика . Включите в команду

G при запуске программы Бейсика установку контрольной точки в

подпрограмме. По достижении контрольной точки работа

интерпретатора Бейсика приостанавливается, и производится обычная

для программы DEBUG выдача содержимого регистров.

Теперь все готово для выполнения подпрограммы на языке

ассемблера как части программы на языке Бейсик. Снова предположим,

что система располагает 128 кбайтами памяти, и выполним следующую

последовательность действий:

введите "BASIC" на уровне команд DOS;

введите "SCREEN1" во время работы интерпретатора Бейсика.

Фиг. 10.7 Печатная копия экрана

Эти действия вводят нас в интерпретатор и переводят режим

изображения на размер 320*200 точек. На Фиг. 10.7. показаны

оставшиеся события этой истории. Команда BLOAD загружает процедуру

в ту же область памяти, из которой она была сохранена. При желании

в команде BLOAD можно задать параметры, обеспечивающие загрузку

программы в другую область памяти. Оператор LINE дает процедуре

графической распечатки экрана информацию для печати. Для вызова

этой процедуры мы используем команду DEF SEG, чтобы установить

значение регистра CS на процедуру. Значение регистра IP для

процедуры помещается в простую переменную. Оператор CALL

осуществляет дальний вызов по заданному адресу. Фиг. 10.7

представляет собой копию реальной распечатки, полученной при

выполнении приведенной программы.

Если в системе 64Кбайт памяти, то программа будет отлична в

двух аспектах. Для вызова интерпретатора Бейсика используется

команда BASIC/M:&H8000, резервирующая верхнюю часть памяти для

нашей ассемблерной процедуры, а команда DEF SEG задает адрес

подпрограммы, как это было сделано в части (b) Фиг. 10.6.


Untitled

Вставка короткой программы

В предыдущем примере рассматривалась довольно большая программа

на языке ассемблера, хранящаяся в собственном объектном файле и

загружаемая в память интерпретатором Бейсика. А как в случае очень

маленькой программы. Представляется, что для такой программы

тратилось бы слишком много усилий на одну только загрузку ее из

собственного файйла. В приложении C справочника по языку Бейсик

приведен способ "упаковки" программы на машинном языке в область

памяти за пределами рабочей области интерпретатора. Приведем пример

применения другого способа.

На Фиг. 10.8 показана программа, написанная на языке

ассемблера, которой мы воспользуемся. Эта программа обращается к

BIOS для сдвига изображения на экране. Рассмотрев параметры,

хранящиеся в регистрах CX и DX, можно увидеть, что сдвигаемое окно

отображает лишь часть экрана. Мы будем исползовать приведенную

программу для разбиения экрана на несколько окон, в каждом из

которых сдвиг может производиться независимо. Поскольку средства

реализации этого в языке Бейсик отсутствуют, понадобится процедура

на языке ассемблера.

Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:07:03

Фиг. 10.8 Программа прокрутки окон на дисплее Page 1-1

PAGE ,132

TITLE Фиг. 10.8 Программа прокрутки окон на дисплее

0000 CODE SEGMENT

ASSUME CS:CODE

0000 SCROLL PROC FAR

0000 55 PUSH BP

0001 8B EC MOV BP, SP

0003 8B 76 06 MOV SI, [BP+6] ; Загрузка адреса параметра

0006 8B 0C MOV CX, [SI] ; Загрузка параметра

0008 0A C0 OR AL, AL

000A B7 07 MOV BH, 7

000C B8 0601 MOV AX, 601h

000F 75 0C JNZ WINDOW1 ; Определение требуемого окна

0011 B9 0200 MOV CX, 200h ; Окно 1

0014 BA 1010 MOV DX, 1010h

0017 DO_SCROLL:

0017 CD 10 INT 10h

0019 5D POP BP

001A CA 0002 RET 2

001D WINDOW1:

001D B9 0514 MOV CX, 514h ; Окно 2

0020 BA 1224 MOV DX, 1224h

0023 EB F2 JMP DO_SCROLL

0025 SCROLL ENDP

0025 CODE ENDS

END

Фиг. 10.8 Процедура сдвига изображения для Бэйсика

Как можно увидеть на листинге ассемблирования на Фиг. 10.8, для

определения, в каком из двух окон должен производиться сдвиг,

используется входной параметр. Программа, написанная на языке

Бейсик, передает этот параметр в ассемблерную подпрограмму

оператором CALL. На Фиг.10.9(а) показано содержимое стека в момент

вызова в Бэйсике процедуры SCROLL. Оператор CALL помещает в стек

адрес параметра перед выполнением дальнего вызова (FAR CALL)

подпрограммы на машинном языке. Адрес в стеке является смещением

параметра относительно регистра DS. Первые команды процедуры SCROLL

извлекают этот адрес из регистра SI для того, чтобы загрузить

истинное значение в регистр CX. На Фиг.10.9(b) показано содержимое

стека после того, как процедура SCROLL поместила содержимое

регистра BP в стек, а затем переслала содержимое регистра SP в

регистр BP. Обратите внимание, что параметр находится в шести

байтах от вершины стека. Если бы программа на языке Бейсик

передавала более одного параметра, перед вызовом они были бы

аналогичным образом помещены в стек. Забегая вперед, заметим, что

перед возвратом процедура, используя команду RET 2, извлекает

параметры из стека. Интерпретатор Бейсика предполагает, что перед

возвратом подпрограмма удаляет параметры из стека.

ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ

SPДДДДД>і Смещение і SPДДДД>і Старое зна-і

і возврата і і чение BP і<ДДДДBP

ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ

і Сегмент і і Смещение і [BP+2]

і возврата і і возврата і

ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ

і Смещение і і Сегмент і [BP+4]

і аргумента і і возврата і

ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ

і Смещение і [BP+6]

і аргумента і

ГДДДДДДДДДДДДґ

(a) (b)

Фиг. 10.9 Стек при вызове процедуры

Подпрограмма SCROLL в зависимости от значения параметра

обрабатывает одно из двух окон экрана. Если параметр равен нулю,

то изображение в окне, заданном координатами (2, 0) и (16, 16)

сдвигается вверх на одну строку. Если параметр не равен нулю, то

на одну строку вверх сдвигается изображение в окне (5, 20), (18,

36). Перемещается текст только в заданном окне, остальной текст

или данные на экране остаются неподвижными. Реализация такого

оконного режима входит в функцию сдвига BIOS. Для ее использования

требуется лишь вызвать BIOS с правильно заданными параметрами.

На Фиг. 10.10 представлена программа на языке Бейсик,

обращающаяся к процедуре SCROLL. В этом простом примере в каждое

окно записывается строка символов, а затем вызывается процедура

сдвига текста вверх. Эта Бэйсик-программа не более чем

иллюстрирует использование сдвига окон.

Первое, на что следует обратить внимание, это - способ загрузки

программы на машинном языке в систему. Программа содержится в

символьной строке P$. Каждый символ в строке соответствует одному

байту объектного кода из Фиг. 10.8. В программу на Бэйсике эта

программа вводится с клавиатуры по листингу ассемблирования. Это -

одна из причин, по которой применение рассмотренного способа

ограничено лишь короткими программами. При вводе программы таким

способом очень легко сделать ошибки.

A

1 CLS

5 DEFINT A-Z

10 P$=CHR$(&H55)+CHR$(&H8B)+CHR$(&HEC)+CHR$(&H8B)+CHR$(&H76)+CHR$(&H6)

20 P$=P$+CHR$(&H8B)+CHR$(&HC)+CHR$(&HA)+CHR$(&HC9)+CHR$(&HB7)+CHR$(&H7)

30 P$=P$+CHR$(&HB8)+CHR$(&H1)+CHR$(&H6)+CHR$(&H75)+CHR$(&HC)+CHR$(&HB9)

40 P$=P$+CHR$(&H0)+CHR$(&H2)+CHR$(&HBA)+CHR$(&H10)+CHR$(&H10)+CHR$(&HCD)

50 P$=P$+CHR$(&H10)+CHR$(&H5D)+CHR$(&HCA)+CHR$(&H2)

60 P$=P$+CHR$(&H0)+CHR$(&HB9)+CHR$(&H14)

70 P$=P$+CHR$(&H5)+CHR$(&HBA)+CHR$(&H24)+CHR$(&H12)+CHR$(&HEB)+CHR$(&HF2)

100 ENTRY!=(PEEK(VARPTR(P$)+1))+(PEEK(VARPTR(P$)+2))*256

110 IF ENTRY!>32768! THEN ENTRY%=ENTRY!-65536! ELSE ENTRY%=ENTRY!

120 A$="АБВГДЕЖЗИК"

130 L=0:R=1

140 LOCATE 1,1:PRINT "Пример сдвига окна . . .э

200 LOCATE 15,1:PRINT A$;

210 CALL ENTRY%(L)

220 LOCATE 18,21:PRINT A$;

230 CALL ENTRY%(R)

240 A$=RIGHT$(A$,9)+LEFT$(A$,1)

250 GOTO 200

Фиг. 10.10 Бэйсик-программа для сдвига окон

Поскольку программа на машинном языке задана в строке P$, то

для определения адреса этой строки программа на языке Бейсик

использует функцию VARPTR. Для оператора CALL необходим адрес

подпрограммы, поэтому для его нахождения и используется функция

VARPTR. Воспользовавшись информацией из приложения C справочника

по Бейсику, можно найти адрес строки во втором и третьем байтах

дескриптора строки. Возвращаемое функцей VARPTR значение является

адресом дескриптора строки для P$. Программа извлекает адрес

строки из дескриптора и присваивает его значение переменной ENTRY!.

Поскольку это значение может находиться в диапазоне от 0 до 65536,

подпрограмма должна преобразовать его в целое значение длиной в

одно слово, со значением от от -32768 до 32767. Это слово

помещается в переменную ENTRY%. В остальных строках программы в

сдвигаемые окна записывается символьная строка, а затем для

перемещения текста вызывается подпрограмма SCROLL.

При запуске этой программы вы увидите, что данные в двух окнах

перемещаются независимо. Такой прием позволяет задать два

различных окна на экране и перемещать в них текст независимо друг

от друга. Если написать немного более длинную программу, можно

было бы ограничить каждое окно рамкой, чтобы деиствительно отделить

их друг от друга. Применение подобных методов построения окон

позволяет писать довольно симпатичные программы с одновременным

выводом на экран наскольких фрагментов текста.

Прежде чем покончить с этой программой, давайте просмотрим

через отладчик часть программы, написанную на машинном языке. Для

этого надо иметь готовую к выполнению программу ДОС DEBUG. Это

достигается следующим образом: сначала загружается программа

DEBUG, а затем загружается BASIC.COM (или BASICA.COM, если

используется расширенный Бейсик). После загрузки программы Бейсик

замените первый символ в P$ (и соответственно, первый байт

программы на машинном языке), на CHR$($HCC). Это - код для

прерывания INT 3 прерывания по точке прерывания. Теперь, когда во

время выполнения программы на языке Бейсик она вызывает

подпрограмму на машинном языке, управление получает программа

DEBUG. Теперь можно вновь заменить код 0CCH на исходное значение

(в данном случае 055H). Программу DEBUG можно использовать для

трассировки программы на машинном языке. Конечно, если программа

на языке ассемблера хорошо написана и коротка, то такая отладка не

так необходима. На самом же деле вы, вероятно, заметите, что в

большинстве случаев из-за ошибок при вводе с клавиатуры программы

на машинном языке в строку интерпретатора Бейсик возникает

множество проблем.


Untitled

Компилируемые языки высокого уровня

В предыдущих примерах рассматривалась программа на языке

ассемблера, используемая совместно с интерпретатором Бейсика.

Версия языка Бейсик, входящая в поставку IBM PC, является

интерпретируемым языком. Это означает, что программа хранится в

ЭВМ в виде, очень похожем на исходный текст. Интерпретатор не

преобразует операторы языка Бейсик в команды машинного языка.

Интерпретатор Бейсика во время выполнения просматривает каждый

оператор программы и делает все, что необходимо для выполнения

этого оператора.

По-другому работает компилятор. Он преобразует операторы языка

высокого уровня в команды машинного языка. Фирма IBM предлагает

компиляторы для персональной ЭВМ с языков Бейсик, Паскаль, Фортран

и Кобол. Выходом компилятора является программа на машинном языке

(файл *.OBJ), т.е. он во многом аналогичен выходу ассемблера.

Запуск программы, написанной на компилируемом языке высокого уровня

состоит из двух этапов. Сначала программа должна быть

скомпилирована, и должны быть отредактированы связи. Затем она

может быть выполнена. Интерпретируемая программа может выполняться

непосредственно, минуя этап компиляции.

Компилируемые языки на персональной ЭВМ аналогичны языку Бейсик

в том смысле, что не дают возможности делать с техническим

обеспечением все, что вздумается. На самом деле интерпретатор

Бейсика еще позволяет программисту при помощи операторов программы

считывать и записывать информацию с портов ввода-вывода и ячеек

памяти. Другие языки не всегда предоставляют даже эту возможность.

Поэтому применение подпрограмм на языке ассемблера в программе на

Паскале или Фортране может оказаться даже более необходимым.

Возможно, вам придется заняться этим, если вы захотите

воспользоваться всеми возможностями технического обеспечения.

К счастью, включить процедуру на языке ассемблера в программу

на компилируемом языке высокого уровня довольно просто, так как

выходом компилятора является объектный файл, готовый к

редактированию связей. Выход ассемблера - тоже объектный файл.

Следовательно, достаточно лишь связать программу на языке высокого

уровня и программу на языке ассемблера при помощи редактора связей

DOS. Нет необходимости соединять программы в процессе выполнения,

как это делалось для интерпретатора Бейсика.

Построим пример на языке Фортран (Фиг. 10.11). Для языка

Паскаль все очень похоже. Подобный пример приведен в приложении D

справочника к компилятору Фортрана. В примере головная программа,

написанная на Фортране, объединена с программой на языке

ассемблера, которая считывает текущее время, используя программное

прерывание базовой системы ввода-вывода. Подпрограмма на языке

ассемблера обращается к BIOS для определения текущего времени и

возвращает соответствующее значение в программу на Фортране.

Головная программа преобразует кванты таймера, в текущее время,

выраженное в часах, минутах и секундах.

На Фиг. 10.11 представлена головная программа на Фортране.

Эта программа вызывает внешнюю процедуру TIMER, имеющую один

параметр A - четырехбайтовое целое значение. Возрващаемое

процедурой TIMER значение представляет собой текущее время,

выраженное в квантах таймера и отсчитываемое от полуночи.

Программа на Фортране по полученному из процедуры TIMER значению

вычисляет время в часах(HOURS), минутах(MINS), секундах(SECS) и

сотых долях секунды(HSECS). Отметим, насколько проще реализовать

умножение и деление на языке Фортран, чем на языке ассемблера.

Можно убедиться, что выполнение всех подобных операций на Фортране

существенно упрощает программирование. Чрезвычайно удобен и способ

преобразования целых переменных в выдаваемые на печать символы при

помощи операторов Фортрана WRITE и FORMAT. На языке ассемблера для

выполнения тех же самых действий потребовалось бы несколько сот

строк. Вспомним пример для сопроцессора 8087, где программа

преобразовывала число с плавающей точкой в код ASCII. В этой

программе содержалось значительное число команд, и, кроме того,

использовался сопроцессор 8087.

$STORAGE=4

INTEGER A,HOURS,MINS,SECS,HSECS

CALL TIMER(A)

HOURS=A/65543

A=A-HOURS*65543

MINS=A/1092

A=A-MINS*1092

SECS=A/18

HSECS=(100*(A-SECS*18))/18

WRITE(*,10)HOURS,MINS,SECS,HSECS

10 FORMAT(1X,'THE TIME IS: ',I2,':',I2,':',I2,'.',I2)

END

Фиг. 10.11 Программа определения времени дня на Фортране

Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:07:35

Фиг. 10.12 Подпрограмма для программы на ФОРТРАНе Page 1-1

PAGE ,132

TITLE Фиг. 10.12 Подпрограмма для программы на ФОРТРАНе

FRAME STRUC

0000 ???? SAVEBP DW ?

0002 ???????? SAVERET DD ?

0006 ???????? A DD ? ; Указатель на параметр

000A FRAME ENDS

0000 CODE SEGMENT 'CODE'

DGROUP GROUP DATA

ASSUME CS:CODE,DS:DGROUP,ES:DGROUP,SS:DGROUP

0000 TIMER PROC FAR

PUBLIC TIMER ; Указание программе LINK на расположение

; программы TIMER

0000 55 PUSH BP

0001 8B EC MOV BP,SP ; Загрузка адреса стека

0003 B4 00 MOV AH,0

0005 CD 1A INT 1Ah ; Вызов BIOS для получения даты и времени

0007 C4 5E 06 LES BX,[BP].A ; Загрузка адреса поля параметров

000A 26: 89 17 MOV ES:[BX],DX ; Сохранение младшей части времени

000D 26: 89 4F 02 MOV ES:[BX+2],CX ; Сохранение старшей части времени

0011 5D POP BP

0012 CA 0004 RET 4 ; Возврат с удалением параметров из стека

0015 TIMER ENDP

0015 CODE ENDS

END

Фиг. 10.12 Ассемблерная процедура для программы на Фортране

На Фиг. 10.12 представлена подпрограмма на языке ассемблера -

процедура TIMER. В этой несложной программе для считывания

текущего времени и сохранения полученного значения в двойном слове

используется обращение к BIOS. Здесь нам необходимо рассмотреть

способ передачи параметров из программы на Фортране в подпрограмму

на языке ассемблера.

На Фиг.10.13 показано содержимое стека в начальный момент

выполнения подпрограммы на языке ассемблера. Точно так же, как

интерпретатор Бейсика, программа на Фортране помещает адрес

параметра в стек. Однако компиляторы Фортрана и Паскаля передают

указатель длиной в два слова, а не одно только смещение параметра.

Это означает, что программа на языке ассемблера, прежде чем

получить доступ к параметру, должна установить как сегментный

регистр, так и адрес смещения. Если бы параметров было более

одного, то программа на Фортране перед вызовом поместила бы в стек

значения адресов и остальных параметров.

ГДДДДДДДДДДДДґ

SPДДДД>і Смещение і

і возврата і

ГДДДДДДДДДДДДґ

і Сегмент і

і возврата і

ГДДДДДДДДДДДДґ

і Смещение і

і аргумента і

ГДДДДДДДДДДДДґ

і Сегмент і

і аргумента і

ГДДДДДДДДДДДДґ

Фиг. 10.13 Стек для вызова процедуры в Фортране

Подпрограмма TIMER на Фиг. 10.12 адресует стек, помещая в него

регистр BP и устанавливая его на вершину стека.Структура FRAME

помогает идентифицировать разные значения в стеке после того как

программа сохранит в нем значение BP. Команда LES BX,[BP]+A

помещает адрес параметра в пару регистров ES:BX. Используя этот

адрес, программа помещает четырехбайтовое значение текущего времени

в четырехбайтовую целую переменную.

Заметим, что процедура TIMER извлекает адрес параметра из стека

при выполнении команды возврата точно так же, как это делалось в

программах на языке Бейсик. Заметим также, что в этой ассемблерной

программе для идентификации имени TIMER используется оператор

PUBLIC. Делается это для того, чтобы редактор связей мог найти

подпрограмму и правильно связать ее с программой на Фортране. Для

интерпретатора Бейсика такой необходимости не было, поскольку

программа на Бейсике не редактировалась совместно с программой на

языке ассемблера.


Заключение

Язык ассемблера - мощное средство программирования. Он позволяет

программисту осуществлять всестороннее управление аппаратными

средствами ЭВМ. Однако такое управление заставляет программиста

вникать в детали, далекие от основного содержания программы. Все

преимущества языка ассемблера оборачиваются подчас пустой тратой

времени на многочисленные детали.

В настоящей главе было рассмотрено несколько способов

использования возможностей программирования на языке ассемблера в

сочетании удобcтвами языков высокого уровня. Разумно распределяя

функции по выполнению работы, умелый программист предоставит

отработку бесчисленных деталей программирования языку высокого

уровня, а сам сосредоточится на реализации основной функции

программы. Затем, когда потребуется повышение производительности

программы или более точное управление аппаратными средствами,

программист может переключиться на язык ассемблера. Язык

ассемблера позволяет программисту выполнять действия, которые либо

вообще нельзя реализовать на языке высокого уровня, либо выполнение

которых займет слишком много машинного времени в случае привлечения

дорогих средств языка высокого уровня.

Существует два способа распределения работ между программами на

языке ассемблера и языке высокого уровня. В первом случае можно

ввести новый драйвер устройства, который позволит программисту

использовать стандартные методы доступа к некоторому нестандартному

драйверу устройства. Здесь были приведены примеры, в которых

осуществлялась буферизация печати, а в оперативной памяти

создавалась модель диска. При втором подходе подпрограмма на языке

ассемблера становится частью программы, написанной на языке

высокого уровня, с явным обращением к этой подпрограмме по мере

необходимости. В любом из этих вариантов данная глава соединяла

все принципы языка ассемблера, изложенные в этой книге.


Untitled

Содержание
  • Предисловие
Глава 1 Введение
  • Программирование на языке Ассемблера
  • Персональный компьютер фирмы IBM
  • Эта книга
Глава 2 Основы компьютерных вычислений
  • Двоичная арифметика
  • Двоичное дополнение
  • Шестнадцатерчное представление
  • Язык машины и язык ассемблера
  • Синтаксис языка ассемблера
  • Принципы работы ассемблера
  • Биты, байты и слова
  • Нумерация бит
  • Набор символов
  • Принципы работы компьютерa
  • Процедуры
  • Стек
  • Прерывания
Глава 3 Микропроцессор 8088
  • Модель программирования 8088
  • Регистры общего назначения
  • Регистры адресации
  • Прямая адресация
  • Вычисление адресов
  • Адресация через Базу + смещение
  • База + индекс + смещение
  • Мод-р/м байт
  • Физическая адресация
  • Регистры сегмента
  • Переназначение сегмента
  • оператор SEGMENT
  • оператор ASSSUME
  • Управляющие регистры
    • Указатель инструкций (IP),
    • Указатель стека (SP),
    • Регистр флагов,
    • Флаг Знака,
    • Флаг нуля,
    • Флаг четности,
    • Флаг переноса,
    • Дополнительный флаг переноса,
    • Флаг переполнения,
    • Флаг захвата,
    • Флаг прерывания,
    • Флаг направления,
  • Векторы прерываний
Глава 4 Команды процессора 8088
  • Пересылка данных
    • Пересылка,
    • Обмен,
    • Ввод и вывод,
    • Загрузка исполнительного адреса,
    • Загрузка указателя,
    • Передача флагов,
    • Перевод,
  • Операции со стеком
  • Передача параметров
  • Арифметические команды
    • Сложение,
    • Вычитание,
    • Однооперандная арифметика,
    • Сравнение,
    • Десятичная коppекция,
    • Символьная коppекция: сложение и вычитание,
    • Умножение,
    • Символьная коppекция: умножение,
    • Деление,
    • Символьная коppекция: деление,
    • Преобразование,
  • Арифммметический пример
  • Логические операции
  • Операции сдвига и вращения
  • Операции на строках
    • Загрузить и сохранить,
    • REP префикс,
    • Пересылка строк,
    • Сканирование и сравнение,
  • Инструкции передачи управления
    • Близкая и далекая передача управления,
    • Адресация переходов
    • Безусловный переход
    • Переходы по условию
    • Проверки кодов условий
    • Управление циклами
  • Управляющие инструкции
    • Установка флагов
    • Специальые команды
Глава 5 Использование ДОС и Ассемблера
  • Дисковая операционная система
  • Система файлов
  • Директории
  • Командный процессор
  • Функции ДОС
  • Блок управления файла (FCB)
  • Фалы .COM и .EXE
  • Создание программы на языке ассемблера
  • Ассемблер и макроассемблер
    • Таблица символических имен
    • Перекрестные ссылки
  • Редактор
    • Могомодульность
    • EXTRN и PUBLIC
    • Операция слияния (редактирования)
    • Карта слияния
  • DEBUG
  • Перевод из .EXE формы в .COM форму
Глава 6 Свойства Макроассемблера
  • Макроопределение
  • Аргументы макроса
  • Ассемблирование по условию
  • Повторение макроса
  • Макрооператоры
  • Команда INCLUDE
  • Сегменты
  • Структуры
  • Записи
Глава 7 Числовой процессор 8087
  • Операции 8087
  • Типы данных 8087
  • Представление в виде числа с плавающей точкой
  • Форматы действительных чисел
  • Определение действительных чисел
  • Модель программирования 8087
    • Стек - регистр
    • Управляющее слово
    • Слово cостояния
  • Набор инструкций 8087
    • Инструкции пересылки данных
    • Инструкции управления
    • Арифметические инструкции
    • Инструкции сравнения
    • Степенные и тригонометрические функции
  • Примеры
    • Степени десяти
    • Десять в степени X
    • Изображение чисел с десятичной точкой
    • Квадратное уравнение
    • Синус угла
    • Отладка программ с использованием 8087
Глава 8 Персональный компьютер IBM
  • Системное оборудование
  • Динамик
  • Клавиатура
  • Реальное время
  • Возможности cистемы
  • Видеоадаптеры
    • Адаптеры монохромного дисплея и принтера
    • Адаптер цветного графического монитора
    • Текстовый режим
    • Графический режим
    • Цветовой режим в 320*200 APA
    • Графика высокого разрешения
  • Адаптер паралельного принтера
  • Адаптер асинхронных коммуникаций
  • Адаптер управления играми
  • Адаптер дайвера дискеты
  • Память прямого доступа
Глава 9 ROM BIOS
  • Некоторые замечания по содержаию ROM BIOS
  • Самотестирование при включении питания
  • Прерывания ROM BIOS
  • Драйверы доступа
    • Процедуры пользователя
    • Блоки параметров
  • Область данных ROM BIOS
  • Программы драйверов доступа
  • Системный сервис
  • Принтер и асинхронные коммуникации
  • Клавиатура
    • Данные клавиатуры
    • Процедура BIOS клавиатуры изнутри
  • Кассета
  • Дискета
    • Области данных дискеты
    • Команды чтения и записи
    • Команда проверки
    • Команда форматирования
  • Дисплей
    • Области данных дисплея
    • Функции ввода-вывода дисплея
    • Установка режима
    • Движение изоражения
    • Запись и чтение символов
    • Текст в графических режимах
    • Графика
    • Запись на телетайп
Глава 10 Расширения и подпрограммы языка ассемблера
  • Расширения системы ввода-вывода
  • Выход в ДОС и сохранение резидента
  • Загрузка в верхие области памяти
  • Подпрограммы на языке ассемблера
  • Применение команды Бэйсика BLOAD к процедуре ассемблера
  • Короткая встроенная программа
  • Скомпилированные программы языков высокого уровня
Заключение

Предисловие
Эта книга научит вас писать программы на языке ассемблера для персонального компьютера фирмы IBM. Она также покажет как использовать ПК IBM для написания этих программ. Наконец, здесь приведены способы сопряжения программ языка ассемблера с остальными компонентами системы. Эта книга для тех, кто начинает программировать на языке ассемблера. Для пользования ею желательно наличие некоторого предварительного опыта в программировании на языках высокого уров ня. В ней не обсуждается формирование алгоритмов и техника программирования. Пользование этой книгой может оказаться затруднительным для вас, если вы не располагаете опытом написания программ на языках Бэйсик, Паскаль или подобных. Тем не менее, эта книга представляет материал в такой форме, которая позволит вам начать программировать на языке ассемблера даже если вы ничего не знаете о внутренней организации компьютера.

Первый раздел книги, состоящий из глав 1 и 2, посвящен принципам работы ЭВМ. Особое внимание в нем уделяется тем функциям компьютера, которые обычно не заметны пользователям языков высокого уровня. Сюда относятся двоичная арифметика и представление данных. В первом разделе также рассматриваются основные операции ассемблера. Этот раздел может быть пропущен программистами с опытом работы на языке ассемблера.

Вторая часть книги, включающий главы 3,4 и 7, является описанием процессора, импользуемого персональным в IBM - Intel 8088. Она включает в себя описание микропроцессора 8088 (в дальнейшем слово "микропроцессор" будет опускаться везде, где его нет в оригинале - прим. перев.), его регистров и способов адресации. Книга представляет набор команд 8088 вместе с примерами, показывающими ипользование большинства из этих команд. Глава 7 отведена числовому сопроцессору 8087. В ней описываются дополнительные типы данных и команды, реализуемые 8087. В этой главе gриведено несколько примеров для демонстрации работы числового сопроцессора.

Если предыдущая часть книги достаточно обширна чтобы охватить почти все системы использующие семейство процессоров 8086/8088, то заключительные разделы непосредственно связаны с персональным компьютером IBM. Главы 5 и 6 посвящены созданию и использованибю программ на IBM PC. Вы научитесь пользоваться "инструментарием" языка ассемблера, который включает в себя строковый редактор, ассемблер, редактор связей, а также общее описание Дисковой Операционой Системы, которая описывается здесь как программная среда. В главе 6 описаны некоторые специальные средства доступные как элементы макро-ассемблера. Сюда входят не только макрокоманды , но и специальные средства определения данных присущие программированию на языке ассемблера.

Главы 8,9 и 10 охватывают аппаратное оборудование и "микрокопрограммную" составлящую IBM PC (персонального компьютера IBM). Здесь основное внимание уделяется специфическим аспектам IBM PC и применению на нем программирования на языке ассемблера. Особенно интересна глава 10, в которой описывается техника связи программ на языке асемблера с другими программами или системами. Эта глава содержит несколько различных методов приспособления ассемблерной программы к другим программам или превращения этой программы в постоянное дополнение к системе.

Автор был членом группы, которая разработала и построила IBM PC. Я хочу поблагодарить всех сотрудников фирмы, которые помогали мне в подготовке этого текста. Особая благодарность Дэйву О'Коннору и Джуд Маккарти, моим менеджерам в этот период. Но более всего я признателен моей жене, Синтии (Cynthia) за ее помощь и поддержку.

Дэвид Дж. Брэдли


ASSEMBLER & WIN32

Программирование на ассемблере под Win32 воспринимается весьма не однозначно. Считается, что написание приложений слишком сложно для применения ассемблера. Собственно обсуждению того насколько оправдана такая точка зрения и посвящена данная статья. Она не ставит своей целью обучение программированию под Win32 или обучение ассемблеру, я подразумеваю, что читатели имеют определённые знания в этих областях.

В отличие от программирования под DOS, где программы написанные на языках высокого уровня (ЯВУ) были мало похожи на свои аналоги, написанные на ассемблере, приложения под Win32 имеют гораздо больше общего. В первую очередь, это связано с тем, что обращение к сервису операционной системы в Windows осуществляется посредством вызова функций, а не прерываний, что было характерно для DOS. Здесь нет передачи параметров в регистрах при обращении к сервисным функциям и, соответственно, нет и множества результирующих значений возвращаемых в регистрах общего назначения и регистре флагов. Следовательно проще запомнить и использовать протоколы вызова функций системного сервиса. С другой стороны, в Win32 нельзя непосредственно работать с аппаратным уровнем, чем “грешили” программы для DOS. Вообще написание программ под Win32 стало значительно проще и это обусловлено следующими факторами:

  • отсутствие startup кода, характерного для приложений и динамических библиотек написанных под Windows 3.x;
  • гибкая система адресации к памяти: возможность обращаться к памяти через любой регистр общего назначения; “отсутствие” сегментных регистров;
  • доступность больших объёмов виртуальной памяти;
  • развитый сервис операционной системы, обилие функций, облегчающих разработку приложений;
  • многообразие и доступность средств создания интерфейса с пользователем (диалоги, меню и т.п.).

Современный ассемблер, к которому относится и TASM 5.0 фирмы Borland International Inc., в свою очередь, развивал средства, которые ранее были характерны только для ЯВУ. К таким средствам можно отнести макроопределение вызова процедур, возможность введения шаблонов процедур (описание прототипов) и даже объектно-ориентированные расширения. Однако, ассемблер сохранил и такой прекрасный инструмент, как макроопределения вводимые пользователем, полноценного аналога которому нет ни в одном ЯВУ.

Все эти факторы позволяют рассматривать ассемблер, как самостоятельный инструмент для написания приложений под платформы Win32 (Windows NT и Windows 95). Как иллюстрацию данного положения, рассмотрим простой пример приложения, работающего с диалоговым окном. Пример 1. Программа работы с диалогом Файл, содержащий текст приложения, dlg.asm

IDEAL
P586
RADIX   16
MODEL   FLAT
%NOINCL
%NOLIST
include "winconst.inc"          ; API Win32 consts
include "winptype.inc"          ; API Win32 functions prototype
include "winprocs.inc"          ; API Win32 function
include "resource.inc"          ; resource consts
MAX_USER_NAME   =       20
DataSeg
szAppName       db      'Demo 1', 0
szHello         db      'Hello, '
szUser          db      MAX_USER_NAME dup (0)
CodeSeg
Start:          call    GetModuleHandleA,       0
                call    DialogBoxParamA,        eax, IDD_DIALOG, 0, offset DlgProc, 0
                cmp     eax,IDOK
                jne     bye
                call    MessageBoxA,            0, offset szHello,      \
                                                offset szAppName,       \
                                                MB_OK or MB_ICONINFORMATION
bye:            call    ExitProcess,            0
public  stdcall DlgProc
proc    DlgProc stdcall
arg     @@hDlg  :dword, @@iMsg  :dword, @@wPar  :dword, @@lPar  :dword
                mov     eax,[@@iMsg]
                cmp     eax,WM_INITDIALOG
                je      @@init
                cmp     eax,WM_COMMAND
                jne     @@ret_false
                mov     eax,[@@wPar]
                cmp     eax,IDCANCEL
                je      @@cancel
                cmp     eax,IDOK
                jne     @@ret_false
                call    GetDlgItemTextA,        [@@hDlg[, IDR_NAME,     \
                                                offset szUser, MAX_USER_NAME
                mov     eax,IDOK
@@cancel:       call    EndDialog,              [@@hDlg[, eax
@@ret_false:    xor     eax,eax
                ret
@@init:         call    GetDlgItem,             [@@hDlg], IDR_NAME
                call    SetFocus,               eax
                jmp     @@ret_false
endp    DlgProc
end     Start

Файл ресурсов dlg.rc

#include "resource.h"
IDD_DIALOG DIALOGEX 0, 0, 187, 95
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_CLIENTEDGE
CAPTION "Dialog"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,134,76,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,73,76,50,14
    LTEXT           "Type your name",IDC_STATIC,4,36,52,8
    EDITTEXT        IDR_NAME,72,32,112,14,ES_AUTOHSCROLL
END

Остальные файлы из данного примера, приведены в приложении 1.

Сразу после метки Start, программа обращается к функции API Win32 GetModuleHandle для получения handle данного модуля (данный параметр чаще именуют как handle of instance). Получив handle, мы вызываем диалог, созданный либо вручную, либо с помощью какой-либо программы построителя ресурсов. Далее программа проверяет результат работы диалогового окна. Если пользователь вышел из диалога посредством нажатия клавиши OK, то приложение запускает MessageBox с текстом приветствия.

Диалоговая процедура обрабатывает следующие сообщения. При инициализации диалога (WM_INITDIALOG) она просит Windows установить фокус на поле ввода имени пользователя. Сообщение WM_COMMAND обрабатывается в таком порядке: делается проверка на код нажатия клавиши. Если была нажата клавиша OK, то пользовательский ввод копируется в переменную szValue, если же была нажата клавиша Cancel, то копирования не производится. Но и в том и другом случае вызывается функция

окончания диалога: EndDialog. Остальные сообщения в группе WM_COMMAND просто игнорируются, предоставляя Windows действовать по умолчанию.

Вы можете сравнить приведённую программу с аналогичной программой, написанной на ЯВУ, разница в написании будет незначительна. Очевидно те, кто писал приложения на ассемблере под Windows 3.x, отметят тот факт, что исчезла необходимость в сложном и громоздком startup коде. Теперь приложение выглядит более просто и естественно.

Пример 2. Динамическая библиотека

Написание динамических библиотек под Win32 также значительно упростилось, по сравнению с тем, как это делалось под Windows 3.x. Исчезла необходимость вставлять startup код, а использование четырёх событий инициализации/деинициализации на уровне процессов и потоков, кажется логичным.

Рассмотрим простой пример динамической библиотеки, в которой всего одна функция, преобразования целого числа в строку в шестнадцатеричной системе счисления. Файл mylib.asm

Ideal
P586
Radix   16
Model   flat
DLL_PROCESS_ATTACH      = 1

extrn   GetVersion:     proc

DataSeg
hInst           dd      0
OSVer           dw      0

CodeSeg
proc    libEntry        stdcall
arg     @@hInst :dword, @@rsn   :dword, @@rsrv  :dword
                cmp     [@@rsn],DLL_PROCESS_ATTACH
                jne     @@1
                call    GetVersion
                mov     [OSVer],ax
                mov     eax,[@@hInst]
                mov     [hInst],eax
@@1:            mov     eax,1
                ret
endP    libEntry

public  stdcall Hex2Str
proc    Hex2Str stdcall
arg     @@num   :dword, @@str   :dword
uses    ebx
                mov     eax,[@@num]
                mov     ebx,[@@str]
                mov     ecx,7
@@1:            mov     edx,eax
                shr     eax,4
                and     edx,0F
                cmp     edx,0A
                jae     @@2
                add     edx,'0'
                jmp     @@3
@@2:            add     edx,'A' - 0A
@@3:            mov     [byte ebx + ecx],dl
                dec     ecx
                jns     @@1
                mov     [byte ebx + 8],0
                ret
endp    Hex2Str

end     libEntry

Остальные файлы, которые необходимы для данного примера, можно найти в приложении 2.

Краткие комментарии к динамической библиотеке

Процедура libEntry является точкой входа в динамическую библиотеку, её не надо объявлять как экспортируемую, загрузчик сам определяет её местонахождение. LibEntry может вызываться в четырёх случаях:

  • при проецировании библиотеки в адресное пространство процесса (DLL_PROCESS_ATTACH);
  • при первом вызове библиотеки из потока (DLL_THREAD_ATTACH), например, с помощью функции LoadLibrary;
  • при выгрузке библиотеки потоком (DLL_THREAD_DETACH);
  • при выгрузке библиотеки из адресного пространства процесса (DLL_PROCESS_DETACH).

В нашем примере обрабатывается только первое из событий DLL_PROCESS_ATTACH. При обработке данного события библиотека запрашивает версию OS сохраняет её, а также свой handle of instance.

Библиотека содержит только одну экспортируемую функцию, которая собственно не требует пояснений. Вы, пожалуй, можете обратить внимание на то, как производится запись преобразованных значений. Интересна система адресации посредством двух регистров общего назначения: ebx + ecx, она позволяет нам использовать регистр ecx одновременно и как счётчик и как составную часть адреса.

Пример 3. Оконное приложение

Файл dmenu.asm

Ideal
P586
Radix   16
Model   flat

struc   WndClassEx
        cbSize          dd      0
        style           dd      0
        lpfnWndProc     dd      0
        cbClsExtra      dd      0
        cbWndExtra      dd      0
        hInstance       dd      0
        hIcon           dd      0
        hCursor         dd      0
        hbrBackground   dd      0
        lpszMenuName    dd      0
        lpszClassName   dd      0
        hIconSm         dd      0
ends    WndClassEx

struc   Point
        x               dd      0
        y               dd      0
ends    Point

struc   msgStruc
        hwnd            dd      0
        message         dd      0
        wParam          dd      0
        lParam          dd      0
        time            dd      0
        pnt             Point   <>
ends    msgStruc

MyMenu                  = 0065
ID_OPEN                 = 9C41
ID_SAVE                 = 9C42
ID_EXIT                 = 9C43

CS_HREDRAW              = 0001
CS_VREDRAW              = 0002
IDI_APPLICATION         = 7F00
IDC_ARROW               = 00007F00
COLOR_WINDOW            = 5
WS_EX_WINDOWEDGE        = 00000100
WS_EX_CLIENTEDGE        = 00000200
WS_EX_OVERLAPPEDWINDOW  = WS_EX_WINDOWEDGE OR WS_EX_CLIENTEDGE
WS_OVERLAPPED           = 00000000
WS_CAPTION              = 00C00000
WS_SYSMENU              = 00080000
WS_THICKFRAME           = 00040000
WS_MINIMIZEBOX          = 00020000
WS_MAXIMIZEBOX          = 00010000
WS_OVERLAPPEDWINDOW     = WS_OVERLAPPED         OR      WS_CAPTION        OR \
                          WS_SYSMENU            OR      WS_THICKFRAME     OR \
                          WS_MINIMIZEBOX        OR      WS_MAXIMIZEBOX
CW_USEDEFAULT           = 80000000
SW_SHOW                 = 5
WM_COMMAND              = 0111
WM_DESTROY              = 0002
WM_CLOSE                = 0010
MB_OK                   = 0

PROCTYPE        ptGetModuleHandle       stdcall \
                        lpModuleName    :dword

PROCTYPE        ptLoadIcon              stdcall \
                        hInstance       :dword, \
                        lpIconName      :dword

PROCTYPE        ptLoadCursor            stdcall \
                        hInstance       :dword, \
                        lpCursorName    :dword

PROCTYPE        ptLoadMenu              stdcall \
                        hInstance       :dword, \
                        lpMenuName      :dword

PROCTYPE        ptRegisterClassEx       stdcall \
                        lpwcx           :dword

PROCTYPE        ptCreateWindowEx        stdcall \
                        dwExStyle       :dword, \
                        lpClassName     :dword, \
                        lpWindowName    :dword, \
                        dwStyle         :dword, \
                        x               :dword, \
                        y               :dword, \
                        nWidth          :dword, \
                        nHeight         :dword, \
                        hWndParent      :dword, \
                        hMenu           :dword, \
                        hInstance       :dword, \
                        lpParam         :dword

PROCTYPE        ptShowWindow            stdcall \
                        hWnd            :dword, \
                        nCmdShow        :dword

PROCTYPE        ptUpdateWindow          stdcall \
                        hWnd            :dword

PROCTYPE        ptGetMessage            stdcall \
                        pMsg            :dword, \
                        hWnd            :dword, \
                        wMsgFilterMin   :dword, \
                        wMsgFilterMax   :dword

PROCTYPE        ptTranslateMessage      stdcall \
                        lpMsg           :dword

PROCTYPE        ptDispatchMessage       stdcall \
                        pmsg            :dword

PROCTYPE        ptSetMenu               stdcall \
                        hWnd            :dword, \
                        hMenu           :dword

PROCTYPE        ptPostQuitMessage       stdcall \
                        nExitCode       :dword

PROCTYPE        ptDefWindowProc         stdcall \
                        hWnd            :dword, \
                        Msg             :dword, \
                        wParam          :dword, \
                        lParam          :dword

PROCTYPE        ptSendMessage           stdcall \
                        hWnd            :dword, \
                        Msg             :dword, \
                        wParam          :dword, \
                        lParam          :dword

PROCTYPE        ptMessageBox            stdcall \
                        hWnd            :dword, \
                        lpText          :dword, \
                        lpCaption       :dword, \
                        uType           :dword

PROCTYPE        ptExitProcess           stdcall \
                        exitCode        :dword

extrn           GetModuleHandleA        :ptGetModuleHandle
extrn           LoadIconA               :ptLoadIcon
extrn           LoadCursorA             :ptLoadCursor
extrn           RegisterClassExA        :ptRegisterClassEx
extrn           LoadMenuA               :ptLoadMenu
extrn           CreateWindowExA         :ptCreateWindowEx
extrn           ShowWindow              :ptShowWindow
extrn           UpdateWindow            :ptUpdateWindow
extrn           GetMessageA             :ptGetMessage
extrn           TranslateMessage        :ptTranslateMessage
extrn           DispatchMessageA        :ptDispatchMessage
extrn           SetMenu                 :ptSetMenu
extrn           PostQuitMessage         :ptPostQuitMessage
extrn           DefWindowProcA          :ptDefWindowProc
extrn           SendMessageA            :ptSendMessage
extrn           MessageBoxA             :ptMessageBox
extrn           ExitProcess             :ptExitProcess

UDataSeg
hInst           dd              ?
hWnd            dd              ?

IFNDEF  VER1
hMenu           dd              ?
ENDIF

DataSeg
msg             msgStruc        <>
classTitle      db      'Menu demo', 0
wndTitle        db      'Demo program', 0
msg_open_txt    db      'You selected open', 0
msg_open_tlt    db      'Open box', 0
msg_save_txt    db      'You selected save', 0
msg_save_tlt    db      'Save box', 0

CodeSeg
Start:  call    GetModuleHandleA,       0               ; получаем hInstance
        mov     [hInst],eax

        sub     esp,SIZE WndClassEx                     ; выделяем место в стеке
                                                        ; заполняем структуру WndClassEx
        mov     [(WndClassEx esp).cbSize],SIZE WndClassEx
        mov     [(WndClassEx esp).style],CS_HREDRAW or CS_VREDRAW
        mov     [(WndClassEx esp).lpfnWndProc],offset WndProc
        mov     [(WndClassEx esp).cbWndExtra],0
        mov     [(WndClassEx esp).cbClsExtra],0
        mov     [(WndClassEx esp).hInstance],eax
        call    LoadIconA,              0, IDI_APPLICATION
        mov     [(WndClassEx esp).hIcon],eax
        call    LoadCursorA,            0, IDC_ARROW
        mov     [(WndClassEx esp).hCursor],eax
        mov     [(WndClassEx esp).hbrBackground],COLOR_WINDOW
IFDEF   VER1
        mov     [(WndClassEx esp).lpszMenuName],MyMenu
ELSE
        mov     [(WndClassEx esp).lpszMenuName],0
ENDIF
        mov     [(WndClassEx esp).lpszClassName],offset classTitle
        mov     [(WndClassEx esp).hIconSm],0
        call    RegisterClassExA,       esp             ; регистрируем окно

        add     esp,SIZE WndClassEx                     ; восстановим стек
                                                        ; создадим окно

IFNDEF  VER2
        call    CreateWindowExA,        WS_EX_OVERLAPPEDWINDOW, \ extended window style
                                        offset classTitle,      \ pointer to registered class name
                                        offset wndTitle,        \ pointer to window name
                                        WS_OVERLAPPEDWINDOW,    \ window style
                                        CW_USEDEFAULT,          \ horizontal position of window
                                        CW_USEDEFAULT,          \ vertical position of window
                                        CW_USEDEFAULT,          \ window width
                                        CW_USEDEFAULT,          \ window height
                                        0,                      \ handle to parent or owner window
                                        0,                      \ handle to menu, or child-window identifier
                                        [hInst],                \ handle to application instance
                                        0                       ; pointer to window-creation data
ELSE
        call    LoadMenu,               hInst, MyMenu
        mov     [hMenu],eax
        call    CreateWindowExA,        WS_EX_OVERLAPPEDWINDOW, \ extended window style
                                        offset classTitle,      \ pointer to registered class name
                                        offset wndTitle,        \ pointer to window name
                                        WS_OVERLAPPEDWINDOW,    \ window style
                                        CW_USEDEFAULT,          \ horizontal position of window
                                        CW_USEDEFAULT,          \ vertical position of window
                                        CW_USEDEFAULT,          \ window width
                                        CW_USEDEFAULT,          \ window height
                                        0,                      \ handle to parent or owner window
                                        eax,                    \ handle to menu, or child-window identifier
                                        [hInst],                \ handle to application instance
                                        0                       ; pointer to window-creation data
ENDIF
        mov     [hWnd],eax
        call    ShowWindow,             eax, SW_SHOW            ; show window
        call    UpdateWindow,           [hWnd]                  ; redraw window

IFDEF   VER3
        call    LoadMenuA,              [hInst], MyMenu
        mov     [hMenu],eax
        call    SetMenu,                [hWnd], eax
ENDIF

msg_loop:
        call    GetMessageA,            offset msg, 0, 0, 0
        or      ax,ax
        jz      exit
        call    TranslateMessage,       offset msg
        call    DispatchMessageA,       offset msg
        jmp     msg_loop
exit:   call    ExitProcess,            0

public  stdcall WndProc
proc    WndProc stdcall
arg     @@hwnd: dword,  @@msg:  dword,  @@wPar: dword,  @@lPar: dword
        mov     eax,[@@msg]
        cmp     eax,WM_COMMAND
        je      @@command
        cmp     eax,WM_DESTROY
        jne     @@default
        call    PostQuitMessage,        0
        xor     eax,eax
        jmp     @@ret
@@default:
        call    DefWindowProcA,         [@@hwnd], [@@msg], [@@wPar], [@@lPar]
@@ret:  ret
@@command:
        mov     eax,[@@wPar]
        cmp     eax,ID_OPEN
        je      @@open
        cmp     eax,ID_SAVE
        je      @@save
        call    SendMessageA,           [@@hwnd], WM_CLOSE, 0, 0
        xor     eax,eax
        jmp     @@ret
@@open: mov     eax, offset msg_open_txt
        mov     edx, offset msg_open_tlt
        jmp     @@mess
@@save: mov     eax, offset msg_save_txt
        mov     edx, offset msg_save_tlt
@@mess: call    MessageBoxA,            0, eax, edx, MB_OK
        xor     eax,eax
        jmp     @@ret
endp    WndProc
end     Start

Комментарии к программе

Здесь мне хотелось в первую очередь продемонстрировать использование прототипов функций API Win32. Конечно их (а также описание констант и структур из API Win32) следует вынести в отдельные подключаемые файлы, поскольку, скорее всего Вы будете использовать их и в других программах. Описание прототипов функций обеспечивает строгий контроль со стороны компилятора за количеством и типом параметров, передаваемых в функции. Это существенно облегчает жизнь программисту, позволяя избежать ошибок времени исполнения, тем более, что число параметров в некоторых функциях API Win32 весьма значительно.

Существо данной программы заключается в демонстрации вариантов работы с оконным меню. Программу можно откомпилировать в трёх вариантах (версиях), указывая компилятору ключи VER2 или VER3 (по умолчанию используется ключ VER1). В первом варианте программы меню определяется на уровне класса окна и все окна данного класса будут иметь аналогичное меню. Во втором варианте, меню определяется при создании окна, как параметр функции CreateWindowEx. Класс окна не имеет меню и в данном случае, каждое окно этого класса может иметь своё собственное меню. Наконец, в третьем варианте, меню загружается после создания окна. Данный вариант показывает, как можно связать меню с уже созданным окном.

Директивы условной компиляции позволяют включить все варианты в текст одной и той же программы. Подобная техника удобна не только для демонстрации, но и для отладки. Например, когда Вам требуется включить в программу новый фрагмент кода, то Вы можете применить данную технику, дабы не потерять функционирующий модуль. Ну, и конечно, применение директив условной компиляции – наиболее удобное средство тестирования различных решений (алгоритмов) на одном модуле.

Представляет определённый интерес использование стековых фреймов и заполнение структур в стеке посредством регистра указателя стека (esp). Именно это продемонстрировано при заполнении структуры WndClassEx. Выделение места в стеке (фрейма) делается простым перемещением esp:

sub esp,SIZE WndClassEx

Теперь мы можем обращаться к выделенной памяти используя всё тот же регистр указатель стека. При создании 16-битных приложений такой возможностью мы не обладали. Данный приём можно использовать внутри любой процедуры или даже произвольном месте программы. Накладные расходы на подобное выделение памяти минимальны, однако, следует учитывать, что размер стека ограничен и размещать большие объёмы данных в стеке вряд ли целесообразно. Для этих целей лучше использовать “кучи” (heap) или виртуальную память (virtual memory).

Остальная часть программы достаточно тривиальна и не требует каких-либо пояснений. Возможно более интересным покажется тема использования макроопределений.

Макроопределения

Мне достаточно редко приходилось серьёзно заниматься разработкой макроопределений при программировании под DOS. В Win32 ситуация принципиально иная. Здесь грамотно написанные макроопределения способны не только облегчить чтение и восприятие программ, но и реально облегчить жизнь программистов. Дело в том, что в Win32 фрагменты кода часто повторяются, имея при этом не принципиальные отличия. Наиболее показательна, в этом смысле, оконная и/или диалоговая процедура. И в том и другом случае мы определяем вид сообщения и передаём управление тому участку кода, который отвечает за обработку полученного сообщения. Если в программе активно используются диалоговые окна, то аналогичные фрагменты кода сильно перегрузят программу, сделав её малопригодной для восприятия. Применение макроопределений в таких ситуациях более чем оправдано. В качестве основы для макроопределения, занимающегося диспетчеризацией поступаю щих сообщений на обработчиков, может послужить следующее описание.

Пример макроопределений

macro   MessageVector   message1, message2:REST
        IFNB
                dd      message1
                dd      offset @@&message1

                @@VecCount = @@VecCount + 1
                MessageVector   message2
        ENDIF
endm    MessageVector

macro   WndMessages     VecName, message1, message2:REST
        @@VecCount      = 0
DataSeg
label   @@&VecName      dword
        MessageVector   message1, message2
        @@&VecName&Cnt  = @@VecCount
CodeSeg
                mov     ecx,@@&VecName&Cnt

                mov     eax,[@@msg]
@@&VecName&_1:  dec     ecx
                js      @@default
                cmp     eax,[dword ecx * 8 + offset @@&VecName]
                jne     @@&VecName&_1
                jmp     [dword ecx + offset @@&VecName + 4]

@@default:      call    DefWindowProcA, [@@hWnd], [@@msg], [@@wPar], [@@lPar]
@@ret:          ret
@@ret_false:    xor     eax,eax
                jmp     @@ret
@@ret_true:     mov     eax,-1
                dec     eax
                jmp     @@ret
endm    WndMessage

Комментарии к макроопределениям

При написании процедуры окна Вы можете использовать макроопределение WndMessages, указав в списке параметров те сообщения, обработку которых намерены осуществить. Тогда процедура окна примет вид:

proc    WndProc stdcall
arg     @@hWnd: dword,  @@msg:  dword,  @@wPar: dword,  @@lPar: dword
WndMessages     WndVector,      WM_CREATE, WM_SIZE, WM_PAINT, WM_CLOSE, WM_DESTROY

@@WM_CREATE:
        ; здесь обрабатываем сообщение WM_CREATE
@@WM_SIZE:
        ; здесь обрабатываем сообщение WM_SIZE
@@WM_PAINT:
        ; здесь обрабатываем сообщение WM_PAINT
@@WM_CLOSE:
        ; здесь обрабатываем сообщение WM_CLOSE
@@WM_DESTROY:
        ; здесь обрабатываем сообщение WM_DESTROY
...
endp    WndProc

Обработку каждого сообщения можно завершить тремя способами:

  • вернуть значение TRUE, для этого необходимо использовать переход на метку @@ret_true;
  • вернуть значение FALSE, для этого необходимо использовать переход на метку @@ret_false;
  • перейти на обработку по умолчанию, для этого необходимо сделать переход на метку @@default.

Отметьте, что все перечисленные метки определены в макро WndMessages и Вам не следует определять их заново в теле процедуры.

Теперь давайте разберёмся, что происходит при вызове макроопределения WndMessages. Вначале производится обнуление счётчика параметров самого макроопределения (число этих параметров может быть произвольным). Теперь в сегменте данных создадим метку с тем именем, которое передано в макроопределение в качестве первого параметра. Имя метки формируется путём конкатенации символов @@ и названия вектора. Достигается это за счёт использования оператора &. Например, если передать имя TestLabel, то название метки примет вид: @@TestLabel. Сразу за объявлением метки вызывается другое макроопределение MessageVector, в которое передаются все остальные параметры, которые должны быть ничем иным, как списком сообщений, подлежащих обработке в процедуре окна. Структура макроопределения MessageVector проста и бесхитростна. Она извлекает первый параметр и в ячейку памяти формата dword заносит код сообщения. В следующую ячейку памяти формата dword записывается а дрес метки обработчика, имя которой формируется по описанному выше правилу. Счётчик сообщений увеличивается на единицу. Далее следует рекурсивный вызов с передачей ещё не зарегистрированных сообщений, и так продолжается до тех пор, пока список сообщений не будет исчерпан.

Сейчас в макроопределении WndMessage можно начинать обработку. Теперь существо обработки скорее всего будет понятно без дополнительных пояснений.

Обработка сообщений в Windows не является линейной, а, как правило, представляет собой иерархию. Например, сообщение WM_COMMAND может заключать в себе множество сообщений поступающих от меню и/или других управляющих элементов. Следовательно, данную методику можно с успехом применить и для других уровней каскада и даже несколько упростить её. Действительно, не в наших силах исправить код сообщений, поступающих в процедуру окна или диалога, но выбор последовательности констант, назначаемых пунктам меню или управляющим элементам (controls) остаётся за нами. В этом случае нет нужды в дополнительном поле, которое сохраняет код сообщения. Тогда каждый элемент вектора будет содержать только адрес обработчика, а найти нужный элемент весьма просто. Из полученной константы, пришедшей в сообщении, вычитается идентификатор первого пункта меню или первого управляющего элемента, это и будет номер нужного элемента вектора. Остаётся только сделать переход на обработчик.

Вообще тема макроопределений весьма поучительна и обширна. Мне редко доводится видеть грамотное использование макросов и это досадно, поскольку с их помощью можно сделать работу в ассемблере значительно проще и приятнее.

Резюме

Для того, чтобы писать полноценные приложения под Win32 требуется не так много:

  • собственно компилятор и компоновщик (я использую связку TASM32 и TLINK32 из пакета TASM 5.0). Перед использованием рекомендую “наложить” patch, на данный пакет. Patch можно взять на site http://www.borland.com/ или на нашем ftp сервере ftp.uralmet.ru.
  • редактор и компилятор ресурсов (я использую Developer Studio и brcc32.exe);
  • выполнить перетрансляцию header файлов с описаниями процедур, структур и констант API Win32 из нотации принятой в языке Си, в нотацию выбранного режима ассемблера: Ideal или MASM.

В результате у Вас появится возможность писать лёгкие и изящные приложения под Win32, с помощью которых Вы сможете создавать и визуальные формы, и работать с базами данных, и обслуживать коммуникации, и работать multimedia инструментами. Как и при написании программ под DOS, у Вас сохраняется возможность наиболее полного использования ресурсов процессора, но при этом сложность написания приложений значительно снижается за счёт более мощного сервиса операционной системы, использования более удобной системы адресации и весьма простого оформления программ.

Приложение 1. Файлы, необходимые для первого примера

Файл констант ресурсов resource.inc

IDD_DIALOG      =       65      ; 101
IDR_NAME        =       3E8     ; 1000
IDC_STATIC      =       -1

Файл определений dlg.def

NAME            TEST
DESCRIPTION     'Demo dialog'
EXETYPE         WINDOWS
EXPORTS         DlgProc         @1

Файл компиляции makefile

#   Make file for Demo dialog
#   make –B
#   make –B –DDEBUG for debug information

NAME    = dlg
OBJS    = $(NAME).obj
DEF     = $(NAME).def
RES     = $(NAME).res

TASMOPT=/m3 /mx /z /q /DWINVER=0400 /D_WIN32_WINNT=0400

!if $d(DEBUG)
TASMDEBUG=/zi
LINKDEBUG=/v
!else
TASMDEBUG=/l
LINKDEBUG=
!endif

!if $d(MAKEDIR)
IMPORT=$(MAKEDIR)\..\lib\import32
!else
IMPORT=import32
!endif

$(NAME).EXE: $(OBJS) $(DEF) $(RES)
        tlink32 /Tpe /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF), $(RES)

.asm.obj:
        tasm32 $(TASMDEBUG) $(TASMOPT) $&.asm

$(RES): $(NAME).RC
        BRCC32 -32 $(NAME).RC

Файл заголовков resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dlg.rc
//
#define IDD_DIALOG                      101
#define IDR_NAME                        1000
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Приложение 2. Файлы, необходимые для второго примера

Файл описания mylib.def

LIBRARY         MYLIB
DESCRIPTION     'DLL EXAMPLE, 1997'
EXPORTS         Hex2Str         @1

Файл компиляции makefile

#   Make file for Demo DLL
#   make –B
#   make –B –DDEBUG for debug information

NAME    = mylib
OBJS    = $(NAME).obj
DEF     = $(NAME).def
RES     = $(NAME).res

TASMOPT=/m3 /mx /z /q /DWINVER=0400 /D_WIN32_WINNT=0400

!if $d(DEBUG)
TASMDEBUG=/zi
LINKDEBUG=/v
!else
TASMDEBUG=/l
LINKDEBUG=
!endif

!if $d(MAKEDIR)
IMPORT=$(MAKEDIR)\..\lib\import32
!else
IMPORT=import32
!endif

$(NAME).EXE: $(OBJS) $(DEF)
        tlink32 /Tpd /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF)

.asm.obj:
        tasm32 $(TASMDEBUG) $(TASMOPT) $&.asm

$(RES): $(NAME).RC
        BRCC32 -32 $(NAME).RC

Приложение 3. Файлы, необходимые для третьего примера

Файл описания dmenu.def

NAME            TEST
DESCRIPTION     'Demo menu'
EXETYPE         WINDOWS
EXPORTS         WndProc                 @1

Файл ресурсов dmenu.rc

#include "resource.h"
MyMenu MENU DISCARDABLE
BEGIN    POPUP "Files"
   BEGIN
        MENUITEM "Open",                ID_OPEN
        MENUITEM "Save",                ID_SAVE
        MENUITEM SEPARATOR
        MENUITEM "Exit",                        ID_EXIT
    END
    MENUITEM "Other",                   65535
END

Файл заголовков resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dmenu.rc
//
#define MyMenu                          101
#define ID_OPEN                         40001
#define ID_SAVE                         40002
#define ID_EXIT                         40003
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40004
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Файл компиляции makefile

#   Make file for Turbo Assembler Demo menu
#       make -B
#       make -B -DDEBUG -DVERN  for debug information and version
NAME    = dmenu
OBJS    = $(NAME).obj
DEF     = $(NAME).def
RES     = $(NAME).res
!if $d(DEBUG)TASMDEBUG=/zi
LINKDEBUG=/v
!else
TASMDEBUG=/l
LINKDEBUG=
!endif

!if $d(VER2)
TASMVER=/dVER2
!elseif $d(VER3)
TASMVER=/dVER3
!else
TASMVER=/dVER1
!endif

!if $d(MAKEDIR)
IMPORT=$(MAKEDIR)\..\lib\import32
!else
IMPORT=import32
!endif

$(NAME).EXE: $(OBJS) $(DEF) $(RES)
        tlink32 /Tpe /aa /c $(LINKDEBUG) $(OBJS),$(NAME),, $(IMPORT), $(DEF), $(RES)

.asm.obj:
        tasm32 $(TASMDEBUG) $(TASMVER) /m /mx /z /zd $&.asm

$(RES): $(NAME).RC
        BRCC32 -32 $(NAME).RC


Настройка Windows для программирования на ассемблере

После долгого изучения особенностей программирования на ассемблере для Windows, я пришел к выводу, что все-таки это сложно и неудобно если начинать с нуля. Однако быстро и практично, когда у программиста есть свои заготовки. В этой статье я расскажу о заготовках которые использую сам и которые, на мой взгляд, очень удобны.

1. Ассоциация файлов ".ASM" c Notepad.

Вы скажете, что это можно запросто сделать и через Explorer. Я отвечу - да, однако удобнее иметь у себя ".reg" файл, который можно в любой момент запустить и получить эту возможность.

REGEDIT4
[HKEY_CLASSES_ROOT\.asm]
"Content Type"="text/plain"
@="txtfile"
[HKEY_CLASSES_ROOT\.asm\ShellNew]
"NullFile"=""

HKEY_CLASSES_ROOT содержит в себе всю информацию о типах файлов и программах, ассоциированных с ними. А тип "txtfile" ассоциирован с Notepad.exe (посмотрите HKEY_CLASSES_ROOT\txtfile].

2. Создание пункта меню "Новый ASM файл".

На мой взгляд, очень удобно нажать правую кнопку мыши на рабочем столе и выбрать в меню "New..." пункт "Новый ASM файл". К тому же здесь можно настроить шаблон на все создаваемые файлы. Создайте этот .reg файл и попробуйте.

REGEDIT4
[HKEY_CLASSES_ROOT\asmfile]
@="Программа на ассемблере"
[HKEY_CLASSES_ROOT\asmfile\DefaultIcon]
@="C:\\WINDOWS\\SYSTEM\\shell32.dll,-152"
[HKEY_CLASSES_ROOT\asmfile\shell]
[HKEY_CLASSES_ROOT\asmfile\shell\open]
[HKEY_CLASSES_ROOT\asmfile\shell\open\command]
@="C:\\WINDOWS\\NOTEPAD.EXE %1"
[HKEY_CLASSES_ROOT\asmfile\shell\print]
[HKEY_CLASSES_ROOT\asmfile\shell\print\command]
@="C:\\WINDOWS\\NOTEPAD.EXE /p %1"
[HKEY_CLASSES_ROOT\.asm]
"Content Type"="text/plain"
@="asmfile"
[HKEY_CLASSES_ROOT\.asm\ShellNew]
"NullFile"="asm.asm"


Начало программирования на ассемблере для Windows.

Создание полноценного приложения для Windows, использующего все возможности данной системы,- довольно сложная задача. В то же время возможно написание каких-то небольших приложений или подпрограмм на ассемблере.

Создание приложение для Windows в общем случае имеет больше стадий чем для Dos. Это объясняется тем, что при компоновке Windows-программы в нее включается не только код исходных модулей, но и ресурсы. Итак создание программы для Windows должно содержать следующие этапы :

  • разработка исходного текста программы
  • создание ресурсов, которые используются программой
  • создание текстового файла описания используемых ресурсов
  • создание текстового файла описания программного модуля
  • компиляция и компоновка программы с использованием файла описания программного модуля
  • компиляция файла ресурсов и включение его в готовый исполняемый файл

В данной статье будет использоваться компилятор и компоновщик фирмы Borland - Turbo Assembler и Turbo Link. Конечно возможно некоторым покажеться странным использование данного продукта, но могу вас уверить на мой взгляд удобнее инструмента для программирования на ассемблере для Windows вы не найдете. Я использую версию Turbo Assembler 5.0.

В этой статье я также не буду рассказывать о создании ресурсов и присоеденении их к исполняемому файлу, мы рассмотрим только простой пример программы, и попробуем найти какие-то сходства с программированием под Dos.

Итак маленький пример:

Файл hello.asm

.model large, WINDOWS PASCAL
; Подключаем файл где описаны константы (типа "MB_OK", "MB_ICONEXCLAMATION")
include windows.inc
; Говорим что будем использовать функцию API MessageBox
extrn MESSAGEBOX:proc
; Сегмент данных
.data
; Пустое место для информации Program Manager'a
freespace db 16 dup(0)
; Заголовок диалогового окна
lpszTitle db 'Generic Sample Assembly Application',0
; Текст диалогового окна
lpszText db 'Hello World !',0
; Сегмент кода
.code
; Наш старый "добрый" start (На самом деле WinMain)
start:
; Инициализируем задачу и получаем входные параметры
call INITTASK
or ax,ax
; Если инициализация прошла успешно
jnz @@OK
; Если ошибка
jmp @@Fail
@@OK:
; Сохраняем HINSTANCE
mov hInstance,di
; Инициализируем приложение
call INITAPP,hInstance
or ax,ax
jnz @@InitOK
@@Fail:
; Если инициализация завершилась неудачно
mov ax, 4CFFh
int 21h
@@InitOK:
; Выводим на экран диалоговое окно
call MESSAGEBOX,0,ds offset lpszText,ds offset lpszTitle,MB_OK+MB_ICONEXCLAMATION
; Хе-хе а вот и выход
mov ax,4c00h
int 21h
end start

Как видите разработка простых приложений для Windows практически не отличается от программирования для Dos. Однако есть маленькие особенности которые мы сейчас рассмотрим:

.model large, WINDOWS PASCAL

Про эту строчку много говорить и не надо, но она имеет одну особенность. Система Windows и ее функции используют алгоритм "прямой" передачи параметров через стэк, как в языке Pascal. Поэтому мы и вводим этот модификатор в директиву modal.

include windows.inc

Подключение файлов определений тоже не представляет ничего нового, но если вы не хотите самостоятельно перекапывать файлы Windows и выяснять какие константы имеют какие значение просто включите эту строчку в вашу программу.

extrn MESSAGEBOX:proc

Наверно одна из самых существенных строк этого примера. Она говорит компилятору, что мы будем использовать внешнюю функцию MessageBox. Я думаю у вас не возникает никаких сомнений - существование этой функции в Windows API.

freespace db 16 dup(0)

Зачем в начале сегмента данных стоят 16 байт с нулями ? Для приложений Win16 эти 16 байт являются обязательным условием нормальной работы программы. При загрузке Вашего приложения Windows заменяет эти 16 байт своей информацией. Для более подробного разъяснения этой проблеммы обратитесь к книге Мэта Патрика (Matt Patrick) "Внутри Windows" ("Windows Internals"), 1993, Addison Wesley.

Всем известно, что каждое приложение для Windows начинается с WinMain. Через параметры переданные этой функции можно было получить командную строку, идентификатор предидущего экземпляра программы и так далее и тому подобнее. Немного сложнее процедура инициализации приложения происходит в Assembler.

Первым шагом в инициализации приложения под Windows будем считать вызов функции INITTASK. После выполнения эта функция возвращает:

  • При успешном завершении AX=1
  • DX содержит nCmdShow, то есть параметр, указывающий на стиль просмотра окна
  • ES:BX содержит адрес командной строки
  • SI содержит идентификатор ранее загруженной программы hPrevInstance
  • DI содержит идентификатор загруженной программы hInstance

После обработки входных параметров нужно инициализировать приложение, используя функцию INITAPP. Единственным параметром этой функции служит идентификатор приложения hInstance.Функция INITAPP возвращает в AX=1 если приложение успешно проинициализировано.

Для того, что бы посмотреть, что же эта программа умеет делать вы должны создать еще один файл .def

Файл hello.def

NAME HELLO
EXETYPE WINDOWS

CODE MOVABLE DISCARDABLE
DATA MOVABLE MULTIPLE DISCARDABLE
STACKSIZE 5120
HEAPSIZE 4096
DESCRIPTION 'Copyright(C) 1999 by BlackWolf'

Содержимое этого файла мы рассмотрим подробнее :

NAME HELLO

Имя получаемого модуля "HELLO".

EXETYPE WINDOWS

Тип получаемого модуля - исполняемый файл Windows.

CODE MOVABLE DISCARDABLE

Ключевым словом CODE мы можем выставлять параметры нашего кодового сегмента. О возможных параметрах сегмента смотрите ниже.

DATA MOVABLE MULTIPLE DISCARDABLE

Тоже самое только для сегмента данных.

STACKSIZE 5120

Устанавливаем размер стэка равным 5120.

HEAPSIZE 4096

Устанавливаем размер "кучи" равным 4096.

DESCRIPTION 'Copyright(C) 1999 by BlackWolf'

Директива DESCRIPTION вставляет в файл кода указанный текст и обычно применяется для примечания о авторских правах.

Теперь рассмотрим параметры сегментов. Они могут быть различных типов и в принципе формат инструкций CODE и DATA выглядит таким образом:

CODE [FIXED|MOVABLE] [DISCARDABLE|NONDISCARDABLE] [PRELOAD|LOADONCALL]

Скобки в данном случае показывают, что элемент не обязателен. Вертикальная черта означает "ИЛИ". Опции директивы CODE означают:

  • FIXED - Сегмент имеет фиксированный адрес.
  • MOVABLE - Сегмент может быть перемещен, что бы освободить пространство в памяти.
  • DISCARDABLE - Сегмент может быть выгружен, чтобы освободить пространство.
  • NONDISCARDABLE - Сегмент не может быть выгружен.
  • PRELOAD - Сегмент загружается в память при запуске приложения.
  • LOADONCALL - Сегмент загружается в память только когда происходит обращение к некоторому его элементу.

DATA [NONE|SINGLE|MULTIPLE] [READONLY|READWRITE] [PRELOAD|LOADONCALL] [SHARED|NONSHARED]

Скобки в данном случае показывают, что элемент не обязателен. Вертикальная черта означает "ИЛИ". Опции директивы DATA означают:

  • NONE - Cегмент данных отсутствует (применяется только для DLL).
  • SINGLE - Единственный сегмент данных, разделяемый всеми процессами (применяется по умолчанию для DLL).
  • MULTIPLE - Несколько сегментов данных (применяется по умолчанию для исполняемых файлов).
  • READONLY - Данные в сегменте можно только читать, но не изменять.
  • READWRITE - Данные в сегменте можно как читать, так и изменять.
  • PRELOAD - Сегмент заранее автоматически загружается в память.
  • LOADONCALL - Сегмент загружается в память при обращении к нему.
  • SHARED - Одна копия сегмента данных разделяется между всеми процессами (применяется по умолчанию для 16-битных DLL)
  • NONSHARED - Отдельная копия сегмента данных загружается для каждого процесса (применяется по умолчанию для приложений и 32-битных DLL).

Существует также еще одна очень интересная директива - STUB. Она вставляет в файл .EXE кода для Windows программу Dos. Эта директива никогда не применяется для библиотек. Если вы не указываете директиву STUB Turbo Assembler вставит в ваш исполняемый файл программу WINSTUB.EXE. Эта программа является заглушкой для запуска Windows приложений под Dos. При запуске вашего приложения под Dos вы получите примерно такое сообщение :

This program must be run under Microsoft Windows.

Используя данную директиву вы можете написать программу которая будет прекрасно работать как под Dos так и под Windows.

После написания файлов hello.asm и hello.def вы должны скомпилировать и скомпоновать программу используя такие параметры для Turbo Assembler:

tasm hello.asm

После компиляции вы должны получить .OBJ файл.

tlink hello,hello,,import.lib(*),hello

После компоновки вы получите готовый исполняемы файл Windows. :)

(*) Если вы используете импорт функций API или импорт функций из других DLL, вы обязательно должны включить в компоновку библиотеку import.lib для 16-битных приложений, или библиотеку import32.lib для 32-битных приложений.

На этом я закончу первую вводную статью о программировании на ассемблере для Windows.


Windows Assembler

Системное программирование на Assembler'е
Одним из самых интересных видов программирования является системное программирование. Системное программирование имеет множество синонимов, одни называют его "Reverse Ingeneering", другие "Исследование программм", но смысл остался тем же. Окунитесь в мир системы, в которой вы работаете, и уже ничто Вас не сможет оторвать от этого занятия.

Программирование на языке Ассемблера для Win16.

"Начало программирования на Ассемблере для Windows".
"Первое окно".
"Ресурсы и меню".
"Настройка Windows для программирования на ассемблере".

Программирование на языке Ассемблера для Win32 .

"Ассемблер и Win32"
"Введение в программирование для Win32".


Введение

Введение в программирование для Win32

Программирование на ассемблере всегда отличалось от программирования на языках высокого уровня и требовало от программиста дополнительных знаний, дополнительной практики и большего проффесионализма. Поэтому прежде чем начать создавать приложения для Win32 мы должны рассмотреть некоторые особенности системы Win32, которые были введены фирмой Microsoft.

1. Передача параметрам функциям Windows API реализована по прямому алгоритму. То есть если на C Вы вызывали фнукциию так:

FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName)

то на ассемблере вызов подобной функции будет выглядеть так :

...
mov eax,offset lpProcName
mov ebx,hModule
push eax
push ebx
call GetProcAddress
...

В то время как в TASM :

.model FLAT,STDCALL
...
call GetProcAddress, [hModule], offset lpProcName
...

В инструкции компилятору ".model" мы передали в параметре, что будем использовать модель памяти FLAT и осуществлять стандартную передачу параметров типа STDCALL.

STDCALL использует соглашения языка C для процедур с переменными аргументами и соглашения языка Pascal для процедур с постоянными аргументами. Для всех остальных идентификаторов применяются соглашения языка C.

Так например некоторые функции Windows API используют способ перевернутой передачи параметров в стек. Так называемый (метод языка С или С-метод). К таким функциям относят функции который могут получать переменное число параметров. Примером такой функции может послужить функция wsprintf(LPTSTR lpOut, LPCTSTR lpFmt, ... ). Для вызова таких функций нужно воспользоваться модификатором вызова, что в TASM делается так :

call _wsprintfA C, offset lpOut, offset lpFmt, offset lpFirstParameter, offset lpSecondParameter

в то время как другие функции с постоянным числом параметров можно вызывать без всяких модификаторов ( если вы конечно воспользовались директивой ".model FLAT, STDCALL").

call GetProcAddressA, [hModule], offset lpProcName

2. Все функции API подразделены на две группы: функции которые поддерживают UNICODE, и функции которые поддерживают ANSI. Функции, поддерживающие UNICODE имеют в конце названия букву "W", а функции, поддерживающие ANSI имеют в конце названия букву "A". Таким образом :

GetProcAddressA - прототип функции GetProcAddress для ANSI.
GetProcAddressW - прототип функции GetProcAddress для UNICODE.

Различие UNICODE от ANSI заключается в том, что в ANSI для передачи одной буквы используется один байт, в зависимости от текущей кодовой страницы в различных кодировках, а в UNICODE использьзуется два байта (так называемый WIDE символ). Поэтому при использовании UNICODE все ссылки на строки включают в себя букву W. Так например :

LPCTSTR (ANSI) = LPCWSTR (UNICODE)
LPTSTR (ANSI) = LPWSTR (UNICODE)

3. Все функции Windows API теперь зависят от регистра букв. То есть если если функция API называется GetProcAddressA то и объявление то же должно быть GetProcAddressA. Например :

...
EXTRN GetProcAddressA: PROC
...
call GetProcAddressA,[hModule], offset lpProcName
...

4. Рассмотрим теперь оссобенности компиляции и компоновки программ. Для компиляции под Win32 Вам понадобяться следующие программы:

tasm32.exe - Borland Turbo Assembler
tlink32.exe - Borland Turbo Link
brcc32.exe - Borland Resource Compiler

Для запуска этих программ понадобяться еще несколько библиотек.

Итак для того, что бы откомпилировать нашу программу используя TASM надо передать компилятору такие параметры:

tasm32.exe /ml <имя файла>

Опция /ml в данном случае говорит о том, что в тексте программы во всех именах следует различать регистр букв, то есть CreateWindow будет отличаться от CREATEWINDOW.

Теперь скомпонуем нашу программу :

tlink32.exe -c -aa -Tpe <имя файла>

Опция -с аналогична опции /ml, она говорит компоновщику о том, что в файле должны различаться регистры букв у имен переменных и функций.

Опция -aa указывает компилятору что нужно создать обычное приложение Windows использующее GDI. Если вы укажите опцию -ap то компоновщик сгенерирует консольное приложение Windows (примером консольного приложения может послужить FAR). Написание консольных приложений я рассмотрю в одной из своих следующих статей.

Последней опцией -Tpe мы сообщаем компоновщику, что нужно сгенерировать EXE файл. Если мы укажем опцию -Tpd то на выходе компоновщика получим DLL файл.

Компиляция ресурсов в Win32 происходит по тому-же алгоритму, что и в Win16. Например для компиляции ресурсов, находящихся в файле myres.rc нужно запустить brcc32.exe с следующимим параметрами.

brcc32.exe myres.rc

Ну вот и все об основных различиях программирования для Win32.


Ресурсы и меню

Я думаю не вызывает сомнений утверждение, что написание программ для Windows, не содержащих ресурсы и меню, связано не только с огромными трудностями, но и с тем, что мы не используем одно из основных удобств Windows.

Ресурсы, представляют собой двоичные данные, хранящиеся внутри EXE или DLL файла программы. Команды меню, например, организованы как ресурсы, которые программа загружает во время выполнения. Ресурсы экономят время, сокращая объем подготовительных операций, выполняемых программой, и экономят память, так как двоичные ресурсы остаются на диске до тех пор, пока они не потребуются.

С помощью редактора ресурсов, такого как Borland Resource Workshop, вы разрабатываете ресурсы вашей программы так, как если бы вы рисовали картинку, редактировали текстовый документ или чертили диаграмму. Borland Resource Workshop – интерактивный редактор ресурсов; то, что вы видите на экране, выглядит точно так же, как будут, в конечном счете, выглядеть ресурсы в законченной программе.

В этой статье я не буду рассматривать процесс работы в Resource Workshop. Мы рассмотрим только некоторые моменты, которые нам понадобятся в дальнейшем. В Resource Workshop вы должны создать файл сценария ресурсов. Этот файл представляет собой обыкновенный текстовый документ, содержащий операторы исходного кода ресурсов приложения. Файлы сценария ресурсов имеют расширения .RC.

Пример файла сценария ресурсов (menures.rc)

#include "menures.rh"

MENU_1 MENU
{
POPUP "&File"
{
MENUITEM "&First item", CM_FIRSTMENU
MENUITEM "&Second item", CM_SECONDMENU
MENUITEM SEPARATOR
MENUITEM "&Third Menu", CM_THIRDMENU
}
POPUP "&Exit"
{
MENUITEM "&Exit NOW", CM_EXIT
}
}

После того как ресурсы созданы, мы должны связать их с нашим приложением. Для этого Resource Workshop создает файлы идентификаторов ресурсов. Эти файлы имеют расширение .RH.

Пример файлы идентификаторов ресурсов (menures.rh)

#define ICON_1 1
#define MENU_1 1
#define CM_EXIT 104
#define CM_THIRDMENU 103
#define CM_SECONDMENU
102
#define CM_FIRSTMENU 101

Теперь, когда вы понимаете, что создает Resource Workshop, мы приступим к созданию файла ресурсов. Файл ресурсов представляет собой бинарный файл и присоединяется к нашему приложению в процессе компоновки. Файлы ресурсов имеют расширение .RES.

Для получения файла ресурсов вы можете использовать две программы:

  • Resource Workshop.
    Borland Resource Compiler.

Для создания файла ресурсов в Resource Workshop вы должны выбрать в меню File пункт Save As и сохранить ваши ресурсы как .RES файл.

Для создания файла ресурсов с помощью Borland Resource Compiler вы должны просто запустить программу brcc.exe и как параметр передать ей путь и имя файла сценария ресурсов.

Когда файл ресурсов создан, можно перейти к вопросу программирования. Для того, что бы Turbo Assembler понял файл идентификаторов ресурсов его надо немного переделать. Описания всех идентификаторов начинается с директивы #define, так как подобной директивы в Turbo Assembler нет, мы должны подправить все объявления идентификаторов на идентичную директиве #define директиву equ.

Например: #define CM_EXIT 1 нужно заменить CM_EXIT equ 1

После того как вы преобразовали файл идентификаторов ресурсов, его надо подключить к основной программе. Для этого используйте директиву include.

ВНИМАНИЕ: Не все ресурсы могут иметь одинаковые идентификаторы. Однако только ресурсы типов DIALOG и MENU можно проигнорировать, так как их использование не связано с идентификатором, ресурсы остальных типов должны иметь уникальные идентификаторы.

Пример программы (menures.asm)

locals
jumps
.model large, WINDOWS PASCAL
; Подключаем файл описания констант и структур API
include windows.inc
; Подключаем файл описания идентификаторов ресурсов
include windows.ri
; Описываем используемые функции
extrn BEGINPAINT:PROC
extrn CREATEWINDOW:PROC
extrn DEFWINDOWPROC:PROC
extrn DISPATCHMESSAGE:PROC
extrn ENDPAINT:PROC
extrn GETMESSAGE:PROC
extrn GETSTOCKOBJECT:PROC
extrn INITAPP:PROC
extrn INITTASK:PROC
extrn LOADCURSOR:PROC
extrn MESSAGEBOX:PROC
extrn POSTQUITMESSAGE:PROC
extrn REGISTERCLASS:PROC
extrn SHOWWINDOW:PROC
extrn TEXTOUT:PROC
extrn TRANSLATEMESSAGE:PROC
extrn UPDATEWINDOW:PROC
extrn WAITEVENT:PROC

.data

; Для Windows Task Manager'a
freespace db 16 dup(0)
; HINSTANCE нашего приложения
hInstance dw ?
; Параметр просмотра окна
cmdShow dw ?
; Структура PAINTSTRUCT
lppaint PAINTSTRUCT <0>
; Структура сообщения MSGSTRUCT
msg MSGSTRUCT <0>
; Структура описания окна
wc WNDCLASS <0>
; Заголовок окна
lpszTitleName db 'Menu and Resources Window',0
; Название класса окна
lpszClassName db 'ASMCLASS',0
; Выводимая в окно строка
lpszText db 'Hello World'
; Длина строки lpszText
lpszTextLength equ $-lpszText
; Заголовок диалогового окна
lpszMenuCaption db 'Menu Event',0
; Идентификатор меню
MenuString db 'MENU_1',0
; Сообщения диалогового окна
lpszMenu1Msg db 'First menu item selected',0
lpszMenu2Msg db 'Second menu item selected',0
lpszMenu3Msg db 'Third menu item selected',0

.code
.286

; Как говоритья в WarLords "Lets The War Begins"
start:
; Инициализируем сегмент данных
mov ax, @data
mov ds, ax
; Инициализируем задачу и получаем входные параметры
call INITTASK
or ax,ax
; Если инициализация прошла успешно
jnz @@OK
; Если ошибка
jmp @@Fail
@@OK:
; Сохраняем HINSTANCE
mov [hInstance],di
; Сохраняем параметр просмотра окна
mov [cmdShow],dx
; Инициализируем приложение
call INITAPP,hInstance
or ax,ax
jnz @@InitOK
@@Fail:
; Если инициализация завершилась неудачно
mov ax, 4CFFh
int 21h
@@InitOK:
; Заполняем структуру описания окна
mov [wc.clsStyle],0
mov word ptr [wc.clsLpfnWndProc],offset WndProc
mov word ptr [wc.clsLpfnWndProc+2],seg WndProc
mov [wc.clsCbClsExtra],0
mov [wc.clsCbWndExtra],0
mov ax,[hInstance]
mov [wc.clsHInstance],ax
; Загружаем пиктограмму ICON_1 и вствляем его в структуру
call LOADICON,ax,ICON_1
mov [wc.clsHIcon],ax
; Загружаем курсор IDC_ARROW и вставляем его в структуру
xor ax,ax
call LOADCURSOR,ax IDC_ARROW
mov [wc.clsHCursor],ax
; Загружаем цвет белого фона и вставляем его в структуру
call GETSTOCKOBJECT,WHITE_BRUSH
mov [wc.clsHbrBackground],ax
mov word ptr [wc.clsLpszMenuName],offset MenuString
mov word ptr [wc.clsLpszMenuName+2],ds
mov word ptr [wc.clsLpszClassName],offset lpszClassName
mov word ptr [wc.clsLpszClassName+2],ds
; Регистрируем структуру описания окна
call REGISTERCLASS,ds offset wc
; Создаем окно
xor ax,ax
mov bx,CW_USEDEFAULT
call CREATEWINDOW,ds offset lpszClassName,ds offset lpszTitleName\ ,WS_OVERLAPPEDWINDOW,ax,bx,bx,bx,bx,ax,ax,\
[hInstance],ax,ax
; Показываем окно
push ax
call SHOWWINDOW,ax,cmdShow
; Обновляем окно
pop ax
call UPDATEWINDOW,ax
; Цикл обработки сообщений
msg_loop:
; Получаем сообщение
call GETMESSAGE,ds offset msg,0,0,0
; Проверяем на наличие сообщения WM_QUIT (AX=0)
cmp ax,0
je end_loop
; Обрабатываем сообщение
call TRANSLATEMESSAGE,ds offset msg
call DISPATCHMESSAGE,ds offset msg
; Обрабатываем следующее сообщение
jmp msg_loop
end_loop:
; Выходим из программы
mov ax,[msg.msWPARAM]
mov ah,4Ch
int 21h
;
; Процедура обработки сообщений
;
WndProc PROC
ARG hwnd:WORD,wmsg:WORD,wparam:WORD,lparam:DWORD

cmp [wmsg],WM_DESTROY
; Если получили сообщение WM_DESTROY (получено сообщение "на выход")
je wmdestroy
cmp [wmsg],WM_CREATE
; Если получили сообщение WM_CREATE (получено сообщение "создать окно")
je wmcreate
cmp [wmsg],WM_PAINT
; Если получили сообщение WM_PAINT (получено сообщение "нарисуйся")
je wmpaint
cmp [wmsg],WM_COMMAND
; Если получили сообщение WM_COMMAND (получено сообщение от меню)
je wmcommand
; Если мы не обрабатываем ни одно из вышеперечисленных сообщений передаем
; управление стандартному обработчику
jmp defwndproc
;
; Обработка сообщения WM_PAINT
;
wmpaint:
; Начинаем рисование и получаем указатель на текущий DC
call BEGINPAINT,hwnd,ds offset lppaint
; AX содержит контекст устройства вывода DC, полученный после вызова BEGINPAINT
; Вызываем TEXTOUT для вывода строки lpszText
call TEXTOUT,ax,5,5,ds offset lpszText,lpszTextLength
; Заканчиваем рисование
call ENDPAINT,hwnd,ds offset lppaint
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_CREATE
;
wmcreate:
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Вызов стандартного обработчика сообщений
;
defwndproc:
call DEFWINDOWPROC,hwnd,wmsg,wparam,lparam
jmp finish
;
; Обработка сообщения WM_DESTROY
;
wmdestroy:
; Вызываем POSTQUITMESSAGE
call POSTQUITMESSAGE,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_COMMAND
;
wmcommand:
; Если выбран первый пункт меню File
cmp [wparam],CM_FIRSTMENU
jne wmcommand1
; Выводим на экран диалоговое окно
call MESSAGEBOX,0,ds offset lpszMenu1Msg,ds offset lpszMenuCaption,0
; Выход из обработчика WM_COMMAND
jmp exit_wmcommand
wmcommand1:
; Если выбран второй пункт меню File
cmp [wparam],CM_SECONDMENU
jne wmcommand2
; Выводим на экран диалоговое окно
call MESSAGEBOX,0,ds offset lpszMenu2Msg,ds offset lpszMenuCaption,0
; Выход из обработчика WM_COMMAND
jmp exit_wmcommand
wmcommand2:
; Если выбран третий пункт меню File
cmp [wparam],CM_THIRDMENU
jne wmcommand3
; Выводим на экран диалоговое окно
call MESSAGEBOX,0,ds offset lpszMenu3Msg,ds offset lpszMenuCaption,0
; Выход из обработчика WM_COMMAND
jmp exit_wmcommand
wmcommand3:
; Если выбран пункт Exit NOW меню Exit
cmp [wparam],CM_EXIT
; Вызываем POSTQUITMESSAGE
call POSTQUITMESSAGE,0
; Выход из обработчика WM_COMMAND
jmp exit_wmcommand
; Выход из обработчкиа WM_COMMAND
exit_wmcommand:
; Выходим из обработчика WM_COMMAND
xor ax,ax
jmp finish
;
; "Финишная прямая"
;
finish:
; Обнуляем DX и на выход
xor dx,dx
ret
WndProc ENDP

end start

В этой программе я использовал два типа ресурсов MENU и ICON. Подключение ресурса ICON выглядит очень наглядно, и объяснять его я не буду. Однако подключение ресурса MENU осуществляется немного по-другому.

Ресурсы типа DIALOG и MENU подключаются не через идентификаторы ресурсов, а через их текстовые имена, которые они имели при создании в Resource Workshop. В нашем случае ресурс MENU имеет имя "MENU_1". Вы просто должны объявить строку, содержащую имя ресурса и заканчивающуюся символом 0.

Например:

lpszMenu db 'MENU_1',0

Все остальные типы ресурсов (кроме MENU и DIALOG) подключаются по методу подключения ресурсы ICON.

Теперь рассмотрим подробнее функцию WndProc. По сравнению с предыдущей статьей она стала немного сложнее. Это связано с тем, что теперь наше приложение обрабатывает все пункты меню. Когда вы нажимаете на какой-либо пункт меню, Windows посылает программе сообщение WM_COMMAND. Через параметр wParam передается идентификатор нажатого пункта меню. Так на нашем примере, если вы нажимаете на кнопку Second Item в меню файл, то приложению передается сообщение WM_COMMAND с wParam=CM_SECONDITEM. Т.е. со значением которые вы присвоили данному пункту меню во время создания ресурса MENU в Resource Workshop.

Теперь опишем еще раз процесс создания приложения, содержащего ресурсы:

  • Создаем файл сценария ресурсов (.RC) и файл идентификатора ресурсов (.RH) в Resource Workshop.
    menures.rc, menures.rh
    Преобразуем файл сценария ресурсов (.RC) в файл ресурсов (.RES), с помощью Resource Workshop или программы brcc.exe.
    brcc.exe menures.rc
    Преобразуем файл идентификаторов ресурсов (.RH) в язык, понятный Turbo Assembler. (.RI) Компилируем приложение
    tasm.exe menures.asm
    Компонуем приложение
    tlink.exe menures.obj,menures.exe,menures.map,import.lib,menures.def,menures.res.

Нажмите на ссылку, что бы скачать файлы статьи

menures.asm
menures.def
menures.rc
menures.rh
menures.ri
menures.res

Ну вот и все про ресурсы и меню.


Первое окно

Теперь, когда вы уже не сомневаетесь в своих силах мы приступим к более сложной задаче - создании окна.

Создание окна, обработка событий и реакция на события - вот три основных вопроса, которые мы рассмотрим в этой статье. В дальнейшем мы всегда будем ссылаться на эту статью, ибо она описывает ту основу, на которой держится все система GUI Windows. Постарайтесь понять эту статью и если это у вас получиться, то я думаю, что дальше ничего сложного для вас не будет.

Всем известно, что каждое приложение для Windows начинается с WinMain. Через параметры, переданные этой функции можно было получить командную строку, идентификатор предыдущего экземпляра программы и так далее и тому подобнее. Немного сложнее процедура инициализации приложения происходит в Assembler.

Первым шагом в инициализации приложения под Windows будем считать вызов функции INITTASK. После выполнения эта функция возвращает:

  • При успешном завершении AX=1

  • DX содержит nCmdShow, то есть параметр, указывающий на стиль просмотра окна

  • ES:BX содержит адрес командной строки

  • SI содержит идентификатор ранее загруженной программы hPrevInstance

  • DI содержит идентификатор загруженной программы hInstance

После обработки входных параметров нужно инициализировать приложение, используя функцию INITAPP. Единственным параметром этой функции служит идентификатор приложения hInstance.Функция INITAPP возвращает в AX=1, если приложение успешно проинициализировано.

Теперь, когда все сложности по инициализации закончились, мы наконец-то сможем создать наше первое окно на Assembler.

Процесс создания окна можно разделить на несколько этапов :

  • Заполнение структуры WNDCLASS.

  • Регистрация класса окна с помощью функции RegisterClass.

  • Создание окна с помощью функции CreateWindow.

  • Вывод окна на экран, используя функцию ShowWindow.

  • Обновление окна с помощью функции UpdateWindow.

Рассмотрим эти этапы подробнее.

Структура WNDCLASS является одним из кирпичей фундамента, на котором построена Windows. В этой структуре содержится вся информация о создаваемом окне. Описание этой структуры находится в файле WINDOWS.INC.

Структура WNDCLASS

  • clsStyleclsLpfnWndProc : DWORD
  • clsCbClsExtra : WORD
  • clsCbWndExtra : WORD
  • clsHInstance : WORD
  • clsHIcon : WORD
  • clsHCursor : WORD
  • clsHbrBackground : WORD
  • clsLpszMenuName : DWORD
  • clsLpszClassName : DWORD
  • clsStyle : WORD

Элемент clsStyleclsLpfnWndProc определяет адрес функции окна, которая обрабатывает сообщения для окон данного класса.

Элемент clsCbClsExtra задает число байт, которое необходимо дополнительно запросить у Windows под эту структуру, для хранения собственных данных, присоединенных классу.

Элемент clsCbWndExtra задает число байт, которое необходимо дополнительно запросить у Windows для размещения всех структур, создаваемых совместно с данным классом, для хранения собственных данных, присоединенных к окну.

Элемент clsHInstance сообщает Windows о том, кто создает определение класса. Когда завершается последний экземпляр программы, Windows удаляет все связанные с ним определения классов.

Элемент clsHIcon определяет пиктограмму, которая будет использоваться для изображения приложения, например на панели задач. Вы можете создать пиктограмму сами(*) или воспользоваться одной из определенных:

  • IDI_APPLICATION - стандартная пиктограмма для приложения
  • IDI_HAND - "стоп"
  • IDI_QUESTION - "вопросительный знак"
  • IDI_EXCLAMATION - "восклицательный знак"
  • IDI_ASTERISK - "информация"

Элемент clsHCursor идентифицирует курсор мыши, используемый в данном окне по умолчанию. Если вы не хотите создавать свой курсор(*), то можно воспользоваться одним, из представляемых Windows:

  • IDC_ARROW - стрелка
  • IDC_IBEAM - вертикальная черта
  • IDC_WAIT - "песочные часы"
  • IDC_CROSS - крестик
  • IDC_UPARROW - вертикальная черта
  • IDC_SIZE - четыре стрелки, указывающие в разные стороны
  • IDC_ICON - курсор, используемый при drag&drop.
  • IDC_SIZENWSE - двунаправленная стрелка "северо-запад/юго-восток"
  • IDC_SIZENESW - двунаправленная стрелка "северо-восток/юго-запад"
  • IDC_SIZEWE - двунаправленная стрелка "восток-запад"
  • IDC_SIZENS - двунаправленная стрелка "север-юг"

Элемент clsHbrBackground задает цвет фона для окна. Для его определения можно воспользоваться любой из 20 системных констант цвета :

  • COLOR_SCROLLBAR - цвет полосы прокрутки
  • COLOR_BACKGROUND - цвет фона окна
  • COLOR_ACTIVECAPTION - цвет заголовка активного окна
  • COLOR_INACTIVECAPTION - цвет заголовка неактивного окна
  • COLOR_MENU - цвет меню
  • COLOR_WINDOW - цвет окна
  • COLOR_WINDOWFRAME - цвет обрамления окна
  • COLOR_MENUTEXT - цвет текста меню
  • COLOR_WINDOWTEXT - цвет текста окна
  • COLOR_CAPTIONTEXT - цвет текста в заголовке окна
  • COLOR_ACTIVEBORDER - цвет активной границы окна
  • COLOR_INACTIVEBORDER - цвет неактивной границы окна
  • COLOR_APPWORKSPACE - цвет рабочего места окна
  • COLOR_HIGHLIGHT - цвет подсветки
  • COLOR_HIGHLIGHTTEXT - цвет подсвеченного текста
  • COLOR_BTNFACE - цвет передней части кнопки
  • COLOR_BTNSHADOW - цвет тени кнопки
  • COLOR_GRAYTEXT - цвет "серого" текста"
  • COLOR_BTNTEXT - цвет текста в кнопке
  • COLOR_INACTIVECAPTIONTEXT - цвет текста в заголовке неактивного окна
  • COLOR_BTNHILIGHT - цвет текущей кнопки

ВНИМАНИЕ : При использовании этих констант к их значению обязательно нужно прибавлять единицу, т.к. первое значение для них равно нулю, а ноль является недействительным значением для логического номера кисти.

Элемент clsLpszMenuName - содержит пару сегмент:смещение на имя меню окна, определенное в файле ресурсов(*).

Элемент clsLpszClassName - содержит пару сегмент:смещение на имя класса. Поскольку после регистрации класс становиться доступен всем другим приложениям Windows, имя класса должно быть уникальным.

Элемент clsStyle определяет свойства окна, которые могут комбинироваться при помощи сложения. Если этому параметру присвоить ноль, то Windows автоматически установит значение clsStyle автоматически. Опишем существующие стили окна :

  • CS_BYTEALIGNCLIENT - оказывает влияние на ширину окна, задавая выравнивание клиентской области на границе байтов
  • CS_BYTEALIGNWINDOW - устанавливает выравнивание окна по границе байтов
  • CS_CLASSDC - обеспечивает контекст устройства, используемый совместно всеми окнами класса.
  • CS_DBLCLKS - служит для установки таймера после получения первого сообщения о нажатии кнопки на мыши
  • CS_GLOBALCLASS - позволяет приложению создавать окно этого класса, не считаясь со значением параметра clsHInstance
  • CS_HREDRAW - определяет, что окно будет перерисовываться, как только измениться его горизонтальный размер
  • CS_VREDRAW - определяет, что окно будет перерисовываться, как только измениться его вертикальный размер
  • CS_NOCLOSE - запрещает пункт "Close" системного меню
  • CS_OWNDC - предоставляет частный контекст устройства для каждого окна в данном классе
  • CS_PARENTDC - устанавливает, что дочерние окна не могут рисовать в родительском окне
  • CS_SAVEBITS - предписывает системе сохранять копию части экрана, поврежденной окном

Теперь, когда вы ознакомились с полями структуры WNDCLASS можно их мысленно запомнить и, передав адрес структуры в качестве параметра в функцию REGISTERCLASS, произвести регистрацию окна.

После успешной регистрации класса окна мы можем создавать окна этого класса. Для создания окон используется функция CREATEWINDOW. Эта функция очень громоздка, так как содержит большое число параметров, поэтому постараемся объяснить их назначение.

Параметры функции CREATEWINDOW :

  • lpszClassName : DWORD
  • lpszWindowName : DWORD
  • dwStyle : DWORD
  • x : WORD
  • y : WORD
  • nWidth : WORD
  • nHeight : WORD
  • hWndParent : WORD
  • hMenu : WORD
  • hInstance : WORD
  • lpParam : DWORD

Параметр lpszClassName содержит адрес строки названия класса окна. В данном случае мы будем использовать только, что зарегистрированный класс окна

Параметр lpszWindowName содержит адрес строки, которая будет идентифицировать окно в панели задач, а также будет видна в заголовке окна.

Параметр dwStyle содержит двойное слово параметров (стилей) окна.

Параметры x,y,nWidth,nHeight содержат геометрические координаты местоположения окна на экране.

Параметр hWndParent содержит идентификатор родительского окна. Если это окно основное, то значение этого параметра должно быть равно 0.

Параметр hMenu содержит идентификатор меню для создаваемого окна.

Параметр hInstance содержит идентификатор приложения, которое создает это окно.

Параметр lpParam содержит дополнительную информацию об окне.

После создания окна его нужно вывести на экран и вставить в очередь обработки сообщений. Используйте функцию SHOWWINDOW, передайте ей в параметрах - идентификатор вашего окна, и стиль просмотра окна. Для вставки окна в очередь обработки сообщений используется функция UPDATEWINDOW с параметром - идентификатор окна.

Теперь, когда наше первое окно создано и видно на экране, надо попробовать поработать с ним. В данный момент наше окно, и приложение, в том числе представляют собой два мертвых груза, и для того, что бы наша программа ожила нужно начать обрабатывать сообщения. Прежде чем привести пример надо разобраться с еще одной немаловажной структурой - MSGSTRUCT. Эта структура описывает формат сообщения Windows. Мы рассмотрим только часть ее элементов, ибо в большинстве случаев остальные совсем не используются.

  • msHWND : WORD - идентификатор окна HWND, которому было передано сообщение
  • msMESSAGE : WORD - идентификатор сообщения
  • msWPARAM : WORD - параметр 1
  • msLPARAM : DWORD - параметр 2

Как видите структура сообщения Windows проста, однако, процедура ее обработки не очень :

msg MSGSTRUCT <0>

msg_loop:
; Получаем сообщение
call GETMESSAGE,ds offset msg,0,0,0
; Проверяем на наличие сообщения WM_QUIT (AX=0)
cmp ax,0
je end_loop
; Обрабатываем сообщение
call TRANSLATEMESSAGE,ds offset msg
call DISPATCHMESSAGE,ds offset msg
; Обрабатываем следующее сообщение
jmp msg_loop
end_loop:
; Выходим из программы
mov ax,[msg.msWPARAM]
mov ah,4Ch
int 21h

Этот кусок кода должен начать работать стразу после создания главного окна. Он отвечает за получение, перевод и передачу сообщения вашему окну. Если этот обработчик получает сообщение WM_QUIT, то он завершает работу программы. Этот участок вашей программы только передает сообщения вашему окну, но не обрабатывает их, для этого существует функция WndProc, адрес которой мы передали при заполнении структуры WNDCLASS. Именно эта функция разбирает по кусочкам сообщения и передает работу вашим обработчикам.

WndProc PROC
ARG hwnd:WORD,wmsg:WORD,wparam:WORD,lparam:DWORD

cmp [wmsg],WM_DESTROY
; Если получили сообщение WM_DESTROY (получено сообщение "на выход")
je wmdestroy
cmp [wmsg],WM_LBUTTONDOWN
; Если получили сообщение WM_LBUTTONDOWN (нажата левая кнопка мыши)
je wmlbuttondown
cmp [wmsg],WM_CREATE
; Если получили сообщение WM_CREATE (получено сообщение "создать окно")
je wmcreate
cmp [wmsg],WM_RBUTTONDOWN
; Если получили сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
je wmrbuttondown
cmp [wmsg],WM_PAINT
; Если получили сообщение WM_PAINT (получено сообщение "нарисуйся")
je wmpaint
; Если мы не обрабатываем ни одно из вышеперечисленных сообщений передаем
; управление стандартному обработчику
jmp defwndproc

Процедура обработки сообщения должна обнулять регистровую пару DX:AX перед выходом из обработчика. Эти регистры используется Windows для получения результатов вызова обработчика.

ВНИМАНИЕ: Если переданное сообщение не обрабатывается обработчиком, обязательно нужно передать управление стандартному обработчику. Для того, что бы это сделать нужно вызвать функция DEFWINDOWPROC, передав ей в параметрах : идентификатор окна, идентификатор сообщения и параметры.

Теперь когда, все рассказано и показано приведем пример рабочей программы :

Файл window.asm

locals
jumps
.model large, WINDOWS PASCAL
; Подключаем файл описания констант и структур API
include windows.inc
; Описываем используемые функции
extrn BEGINPAINT:PROC
extrn CREATEWINDOW:PROC
extrn DEFWINDOWPROC:PROC
extrn DISPATCHMESSAGE:PROC
extrn ENDPAINT:PROC
extrn GETMESSAGE:PROC
extrn GETSTOCKOBJECT:PROC
extrn INITAPP:PROC
extrn INITTASK:PROC
extrn LOADCURSOR:PROC
extrn MESSAGEBOX:PROC
extrn POSTQUITMESSAGE:PROC
extrn REGISTERCLASS:PROC
extrn SHOWWINDOW:PROC
extrn TEXTOUT:PROC
extrn TRANSLATEMESSAGE:PROC
extrn UPDATEWINDOW:PROC
extrn WAITEVENT:PROC

.data

; Для Windows Task Manager'a
freespace db 16 dup(0)
; HINSTANCE нашего приложения
hInstance dw ?
; Параметр просмотра окна
cmdShow dw ?
; Структура PAINTSTRUCT
lppaint PAINTSTRUCT <0>
; Структура сообщения MSGSTRUCT
msg MSGSTRUCT <0>
; Структура описания окна
wc WNDCLASS <0>
; Заголовок окна
lpszTitleName db 'First Window',0
; Название класса окна
lpszClassName db 'ASMCLASS',0
; Выводимая в окно строка
lpszText db 'Hello World'
; Длина строки lpszText
lpszTextLength equ $-lpszText
; Заголовок диалогового окна
lpszCaption db 'Mouse Event',0
; Сообщения диалогового окна
lpszLeftMsg db 'Left button pressed',0
lpszRightMsg db 'Right button pressed',0

.code
.286

; Как говоритья в WarLords "Lets The War Begins"
start:
; Инициализируем сегмент данных
mov ax, @data
mov ds, ax
; Инициализируем задачу и получаем входные параметры
call INITTASK
or ax,ax
; Если инициализация прошла успешно
jnz @@OK
; Если ошибка
jmp @@Fail
@@OK:
; Сохраняем HINSTANCE
mov [hInstance],di
; Сохраняем параметр просмотра окна
mov [cmdShow],dx
; Инициализируем приложение
call INITAPP,hInstance
or ax,ax
jnz @@InitOK
@@Fail:
; Если инициализация завершилась неудачно
mov ax, 4CFFh
int 21h
@@InitOK:
; Заполняем структуру описания окна
mov [wc.clsStyle],0
mov word ptr [wc.clsLpfnWndProc],offset WndProc
mov word ptr [wc.clsLpfnWndProc+2],seg WndProc
mov [wc.clsCbClsExtra],0
mov [wc.clsCbWndExtra],0
mov ax,[hInstance]
mov [wc.clsHInstance],ax
mov [wc.clsHIcon],0
; Загружаем курсор IDC_ARROW и вставляем его в структуру
xor ax,ax
call LOADCURSOR,ax IDC_ARROW
mov [wc.clsHCursor],ax
; Загружаем цвет белого фона и вставляем его в структуру
call GETSTOCKOBJECT,WHITE_BRUSH
mov [wc.clsHbrBackground],ax
mov word ptr [wc.clsLpszMenuName],0
mov word ptr [wc.clsLpszMenuName+2],0
mov word ptr [wc.clsLpszClassName],offset lpszClassName
mov word ptr [wc.clsLpszClassName+2],ds
; Регистрируем структуру описания окна
call REGISTERCLASS,ds offset wc
; Создаем окно
xor ax,ax
mov bx,CW_USEDEFAULT
call CREATEWINDOW,ds offset lpszClassName,ds offset lpszTitleName\ ,WS_OVERLAPPEDWINDOW,ax,bx,bx,bx,bx,ax,ax,\
[hInstance],ax,ax
; Показываем окно
push ax
call SHOWWINDOW,ax,cmdShow
; Обновляем окно
pop ax
call UPDATEWINDOW,ax
; Цикл обработки сообщений
msg_loop:
; Получаем сообщение
call GETMESSAGE,ds offset msg,0,0,0
; Проверяем на наличие сообщения WM_QUIT (AX=0)
cmp ax,0
je end_loop
; Обрабатываем сообщение
call TRANSLATEMESSAGE,ds offset msg
call DISPATCHMESSAGE,ds offset msg
; Обрабатываем следующее сообщение
jmp msg_loop
end_loop:
; Выходим из программы
mov ax,[msg.msWPARAM]
mov ah,4Ch
int 21h
;
; Процедура обработки сообщений
;
WndProc PROC
ARG hwnd:WORD,wmsg:WORD,wparam:WORD,lparam:DWORD

cmp [wmsg],WM_DESTROY
; Если получили сообщение WM_DESTROY (получено сообщение "на выход")
je wmdestroy
cmp [wmsg],WM_LBUTTONDOWN
; Если получили сообщение WM_LBUTTONDOWN (нажата левая кнопка мыши)
je wmlbuttondown
cmp [wmsg],WM_CREATE
; Если получили сообщение WM_CREATE (получено сообщение "создать окно")
je wmcreate
cmp [wmsg],WM_RBUTTONDOWN
; Если получили сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
je wmrbuttondown
cmp [wmsg],WM_PAINT
; Если получили сообщение WM_PAINT (получено сообщение "нарисуйся")
je wmpaint
; Если мы не обрабатываем ни одно из вышеперечисленных сообщений передаем
; управление стандартному обработчику
jmp defwndproc
;
; Обработка сообщения WM_PAINT
;
wmpaint:
; Начинаем рисование и получаем указатель на текущий DC
call BEGINPAINT,hwnd,ds offset lppaint
; AX содержит контекст устройства DC, полученный после вызова BEGINPAINT
; Вызываем TEXTOUT для вывода строки lpszText
call TEXTOUT,ax,5,5,ds offset lpszText,lpszTextLength
; Заканчиваем рисование
call ENDPAINT,hwnd,ds offset lppaint
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_CREATE
;
wmcreate:
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Вызов стандартного обработчика сообщений
;
defwndproc:
call DEFWINDOWPROC,hwnd,wmsg,wparam,lparam
jmp finish
;
; Обработка сообщения WM_DESTROY
;
wmdestroy:
; Вызываем POSTQUITMESSAGE
call POSTQUITMESSAGE,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_LBUTTONDOWN
;
wmlbuttondown:
; Выводим на экран MESSAGEBOX с надписью "Left button pressed"
call MESSAGEBOX,0,ds offset lpszLeftMsg,ds offset lpszCaption,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_RBUTTONDOWN
;
wmrbuttondown:
; Выводим на экран MESSAGEBOX с надписью "Right button pressed"
call MESSAGEBOX,0,ds offset lpszRightMsg,ds offset lpszCaption,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; "Финишная прямая"
;
finish:
; Обнуляем DX и на выход
xor dx,dx
ret
WndProc ENDP

end start

Файл window.def

NAME WINDOW
EXETYPE WINDOWS
CODE MOVABLE DISCARDABLE
DATA MOVABLE MULTIPLE DISCARDABLE
STACKSIZE 5120
HEAPSIZE 4096
DESCRIPTION 'Copyright(C) 1999 BlackWolf'

Ну, вот и все про окна и сообщения.


Контекст устройства

С точки зрения программиста Windows является системой, не зависящей от устройств (device independent). Эту независимость со стороны Windows обеспечивает библиотека GDI32.dll, а со стороны устройства - драйвер этого устройства. С точки зрения программы связующим звеном между программой и устройством является контекст устройства (Device Context - DC). Если программе нужно осуществить обмен с внешним устройством, программа должна оповесить GDI о необходимости подготовить устройство для операции ввода-вывода. После того, как устройство подготовленно, программа получает хэндл контекста устройства, т.е. хэндл структуры, содержащей набор характеристик этого устройства. В этот набор входят:
bitmap(битовая карта, изображение), отображаемый в окне,
перо для прорисовки линий,
кисть,
палитра,
шрифт
и т.д. Программа никогда напрямую не обращается к контексту устройства(кстати, эта структура не документирована Microsoft), она обращается к нему опосредствованно, через определенные функции. После того, как все действия произведены, и необходимость в использовании устройства отпала, программа должна освободить контекст устройства, чтобы не занимать память. Есть еще одна причина, из-за которой необходимо освобождать контекст устройства. В системе может существовать одновременно только ограниченное число контекстов устройств. Если контекст устройства не будет освобождаться после операций вывода, то через несколько перерисовок окна система может повиснуть. Так что не забывайте освобождать контексты устройств!

Когда программа требует контекст устройства, она получает его уже заполненным значениями по умолчанию. Объект в составе контекста называется текущим объектом. Само слово - текущий - говорит о том, что контекст устройства можно изменить. Программа может создать новый объект, скажем, bitmap или шрифт, и сделать его текущем. Замещенный объект автоматически из памяти не удаляется, его необходимо позже удалить отдельно. Само собой разумеется, что программа может получить характеристики текущего устройства. А вот изменить эти характеристики, увы, можно только через замену объекта (впрочем, это и так понятно).


Типы Контекста Устройства

В Windows поддерживаются следующие типы контекстов устройств:
контекст дисплея(обеспечивает работу с дисплеем);
контекст принтера(обеспечивает работу с принтером);
контекст в памяти(моделирует в памяти устройство вывода);
информационный контекст(служит для получения данных от устройства).


Контекст Дисплея

Windows поддерживает три типа контекста дисплея - контекст класса, приватный контекст и общий контекст. Первые два типа используются в приложениях, которые выводят на экран большое количество информации. Ярким примером такого рода приложений являются настольные издательские системы, графические пакеты и т.д.

Приложения, которые не очень интенсивно работают с экраном, испольуют общий контекст. Контекст класса является устаревшим и поддерживается только для обеспечения совместимости с предыдущими версиями Windows. Microsoft не рекомендует использовать его при разработке новых приложений и рекомендует использовать только приватный контекст.

Контексты устройств хранятся в кэше, управляемом системой. Хэндл общего контекста программа получает с помощью функций GetDC(), GetDCEx() или BeginPaint(). После того, как программа отработает с дисплеем, она должна освободить контекст, вызвав функцию ReleaseDC() или EndPaint() (в случае, если контекст получался с помощью BeginPaint()). После того, как контекст дисплея освобожден, все изменения, внесенные в него программой, теряются и при повторном получении контекста все действия по изменению контекста необходимо повторять заново.

Приватный контекст отличается от общего тем, что сохраняет изменения даже после того, как прикладная программа освободила его. Приватный контекст не хранится в кэше, поэтому прикладная программа может не освобождать его. Естественно, что в этом случае за счет использования большего объема памяти достигается более высокая скорость работы с дисплеем.

Для работы с приватным контекстом необходимо при регистрации класса окна указать стиль CS_OWNDC. После этого программа может получать хэндл контекста устройства точно так же, как и в случае общего контекста. Система удаляет приватный контекст в том случае, когда удаляется окно.

При работе с контекстами необходимо запомнить, что хэндлы контекста устройства с помощью функции BeginPaint() необходимо получать только в случае обработки сообщения WM_PAINT. Во всех остальных случаях необходимо использовать функции GetDC() или GetDCEx().


Контекста Принтера

При необходимости вывода на принтер программа должна создать контекст устройства с помощью функции CreateDC(). Аргументы этой функции определяют имя драйвера устройства, тип устройства и инициализационные данные для устройства. Используя эти данные, система может подготовить принтер и распечатать требуемые данные. После распечатки прикладная программа должна удалить контекст принтера с помощью функции DeleteDC() (а не ReleaseDC()).


Информационный Контекст

Информационный контекст фактически не является контекстом устройства и служит только для получения информации о действительном контексте устройства. К примеру, для того, чтобы получить характеристики принтера, программа создает информационный контекст, используя для этого функцию CreateIC(), а затем из него выбирает требующиеся характеристики. Естественный вопрос: а для чего нужно использовать информационный контекст? Почему нельзя те же самые данные получить из действительно контекста? Дело в том, что этот тип контекста создается и работает намного быстрее, а также занимает меньше памяти по сравнению с действительным контекстом. После того, как надобность в информационном контексте миновала, программа должна удалить его с помощью функции DeleteDC().


Контекст в памяти

Этот контекст используется для хранения изображений, которые затем будут скопированны на устройство вывода. Сам по себе контекст в памяти не создается. Он обязательно создается как совместимый с тем устройством или окном, на которое предполагается копировать информацию (вот он - совместимый контекст - переходник между программой и драйвером устройства!). Алгоритм работы с контекстом в памяти состоит из нескольких шагов:
1. Получения хэндла контекста устройства (назовем его hDC - handle of Device Context) для окна, в которое будет осуществляться вывод изображения.
2. Получения хэндла bitmap'а, который будет отображаться в окне.
3. Получения совместимого с hDC контекста в памяти (для хранения изображения) с помощью функции CreateCompatibleDC() (обратите внимание на название функции - создать СОВМЕСТИМЫЙ контекст).
4. Выбора изображения (hBitmap) как текущего для контекста в памяти ( hCompatibleDC).
5. Копирования изображения контекста в памяти (hCompatibleDC) на контекст устройства (hDC).
6. Удаления совместимого контекста (hCompatibleDC).
7. Принятия мер для того, чтобы замещенный bitmap из контекста в памяти не остался в памяти.
8. Освобождения контекста устройства (hDC).

При необходимости шаги 6 и 7 можно поменять местами. Когда и как удалять замещенный bitmap, зависит от программиста и поставленной перед ним задачи.

Именно этот способ и используется в большинстве программ для копирования изображения.

Но, как известно, лучше один раз увидеть, чем сто раз услышать (по-английски это звучит еще более категорично - seeing is believing - увидеть, значит поверить).


Что такое ресурсы

Составной частью проекта, работа которого планируется в Windows, является файл определения ресурсов. Возникает вопрос: что же такое ресурсы, когда и в каких целях они используются?

У Windows есть некоторые предопределенные данные (предопределенные курсоры, иконки и кисти). Точно так же, почти в каждой программе для Windows есть некоторые данные, которые определяются еще до начала работы программы, особым образом добавляются в выполняемый файл и используются при работе программы. Яркими примерами таких данных являются иконки и курсоры мыши. Кроме них, к числу ресурсов относятся:
- используемые в программе изображения;
- строки символов;
- меню;
- ускорители клавиатуры;
- диалоговые окна;
- шрифты;
- ресурсы, определяемые пользователем;

Следует отметить, что выполняемым файлом может быть файл программы .exe, файл динамической библиотеки .dll и другие бинарные файлы. Для удобства буду их называть bin-файлами.

Помимо того, что ресурсы определяются до начала работы программы и добавляются в bin-файл, у них есть еще одна характерная черта. При загрузке bin-файла в память, РЕСУРСЫ В ПАМЯТЬ НЕ ЗАГРУЖАЮТСЯ. Только в случае, если тот или иной ресурс требуется для работы программы, программа сама загружает ресурс в память.

Возможность использования того или иного атрибута в качестве ресурса не означает, что программист не может создавать эти атрибуты в программе. Яркий пример тому можно найти в работе старого доброго Program Manager'а. При перетаскивании иконки с место на место курсор меняет свою форму и принимает форму, подобную перетаскиваемой иконке. Естественно, что в этом случае курсоры определяются программой. Помимо этого, вспомним drag-and-drop в Explorer'е и изменение формы курсора при этом.

Еще одним примером являются динамические меню, т.е. меню, которые изменяют свой вид и предоставляемые возможности в зависимости от обстоятельств.


Ресурсы стандартные и нестандартные

Все ресурсы, заранее определенные в Win32 API, называются стандартными. Для работы с ними существуют специальные функции. Но именно эта стандартность и ограничивает возможности программиста. Стандарт, он и есть стандарт.

Для того чтобы можно было преодолеть эти ограничения, был создан особый тип ресурсов - определяемые пользователем ресурсы. Используя именно этот тип, мы можем предоставить в распоряжение программы практически любые данные. Но, как известно, бесплатным бывает только сыр в мышеловке. В данном случае платой за универсальность является усложнение программы, так как забота о манипулировании данными из ресурсов лежит уже не на системе, а на программе, использующей эти ресурсы. Программа может только получить указатель на данные ресурсов, загруженные в память средствами Windows. Дальнейшая работа с ними ложится ИСКЛЮЧИТЕЛЬНО на плечи программы!


Подключение ресурсов к исполняемому файлу

Ресурсы создаются отдельно от файлов программы и добавляются в bin-файл при линковании программы. Подавляющее большинство ресурсов содержится в файлах ресурсов, имеющих расширение .RC. Имя файла ресурсов обычно совпадает с именем bin-файла программы. Так, если имя программы MYPROG.EXE, то имя файла ресурсов - MYPROG.RC.

Некоторые типы ресурсов (меню, например) можно описать на специальном языке и воспользоватся при этом обычным текстовым редактором, поддерживающим текст в формате ASCII. Другие ресурсы (иконки, курсоры, изображения) тоже описываются в текстовом виде, но часть их описания является последовательность шестнадцатиричных цифр описывающих изображения. Можно, конечно, попробовать написать эту последовательностьи в текстовом редакторе, но, наверное, в этом случае сложность создания ресурса приблизится к сложности написания программы, а возможно, и превысит ее. Обычно для создания ресурсов пользуются специальными средствами - редакторами ресурсов. Они позволяют создавать ресурсы, визуально контролировать правильность их создания, после чего сохранять их в файлах ресурсов.

Я часто использую "смешанный" способ редактирования ресурсов. Например, при визуальном редактировании диалоговых окон достаточно трудно точно установить элементы диалогового окна именно так, как хочется. Устанавливаю все элементы ПРИБЛИЗИТЕЛЬНО на те места, где они должны находиться, после чего сохраняю ресурсы в виде файла с расширением RC. Затем редактирую RC-файл как обычный текстовый файл, точно указывая при этом все размеры и позиции.

При создании RC-файлов программист может столкнуться с одной тонкостью. Некоторые ресурсы, такие, как иконки, курсоры, диалоговые окна, изображения (bitmap'ы) могут быть сохранены в отдельных файлах с расширениями .ico, .cur, .dlg, .bmp соответственно. В этом случае в RC-файлах делаются ссылки на упомянутые файлы.

Файл ресурсов создан - теперь его нужно откомпилировать. Компилируется он специальным компилятором ресурсов. Обычно имя компилятора ресурсов заканчивается на RC.EXE. В частности, в Borland 5.0 он называется BRC.EXE.

После компиляции файла ресурсов компилятором ресурсов создается новый файл, имеющий расширение .RES. Именно этот RES-файл используется линкером для добавления ресурсов в bin-файл. Следует отметить, что при необходимости RES-файлы могут создаваться и редакторами ресурсов. В каком формате создавать ресурсы и как присоединять их к исполняемому файлу, зависит от потребностей и привычек создающего ресурсы программиста.

Итак, постараемся подвести итог сказанному. Ресурсы создаются и включаются в bin-файл посредством выполнения следующих шагов (некоторые шаги могут быть опущены в зависимости от обстоятельств)

Последовательность создания файла ресурсов

Действие | Используемое средство
Создание RC-файла (при необходимости включающего ссылки на файлы с расширением .ico, .cur, .bmp, .dlg, .mnu и т.д.) | Редактор ресурсов (при необходимости может быть использован текстовый редактор и графические редакторы)
Редактирование RC-файла в текстовом виде | Текстовый редактор
Компиляция RC-файла - получение RES-файла | Компилятор ресурсов
Добавление ресурсов, содержащихся в RES-файле, в bin-файл | Линкер


Подключение меню к окну

Любой, кто хоть немного работал в Windows, знает, что меню располагаются сразу под заголовком окна и позволяют пользователю осуществить выбор возможностей, предоставляемых программой. Помимо этого, существуют также всплывающие меню, которые могут появлятся в любой точке экрана. Обычно их содержание зависит от того, на каком окне щелкнули клавишей мыши.

Давайте представим себе главное меню программы как древовидную структуру. Основная часть - корень нашего дерева - это непосредственно главное меню. Само по себе оно представляет только структуру в памяти, не отображается на экране, не содержит ни одного элемента, но хранит указатель на список структур, описывающих подключаемые к нему элементы и всплывающие (popup) меню. В свою очередь, popup-меню должно указать на список структур очередного, более низкого уровня и т.д. Конечные элементы меню никаких указателей на списки не имеют, но хранят некий идентификатор действия (назовем его идентификатором элемента меню), которое должна произвести программа при выборе данного элемента меню. Используя эти структуры, мы можем построить меню практически любой глубины и сложности.

Эта многоуровневая древовидная структура описывается в файле ресурсов. Описание меню имеет вид:

MenuName  MENU  [параметры] ; это - главное меню
{
  Описание всех popup-меню и элементов меню второго уровня
}
В данном случае MenuName - это имя создаваемого нами меню. Слово MENU обозначает начало определения меню.

В Win32 API для описания меню существуют два ключевых слова. Первое - POPUP - специфицирует всплывающее меню. Второе - MENUITEM - описывает обычный элемент меню.

Всплывающие меню описывается следующим образом:

POPUP  "Имя"  [,параметры]  ; описание popup-меню
{
  Описание всех popup-меню и элементов очередного уровня
}
У конечного элемента меню в его описании есть еще одна характеристика - тот самый идентификатор действия:
MENUITEM  "Имя",  MenuID  [,параметры]
В обоих случаях "Имя" - это тот текст, который будет выведен на экран при отображении меню (обратите внимание - при описании главного меню выводимого на экран текста нет!). В том случае, когда вместо имени окна записано слово SEPARATOP (без кавычек!), на месте элемента меню появляется горизонтальная линия. Обычно эти горизонтальные линии (сепораторы или разделители) используются для разделения элементов подменю на логические группы (логика определяется только программистом и никаких особенностей не содержит).

Если в имени меню встречается символ "&", то следующий за амперсандом символ на экране будет подчеркнут одинарной чертой. Этот элемент меню можно будет вызывать с клавиатуры посредством одновременного нажатия клавиши Alt и подчеркнутого символа.

MenuID - идентификатор действия. Он может быть передан функции окна, содержащего меню. Значение идентификатора определяется пользователем. Функция окна в зависимости от полученного MenuID производит определенные действия.

Параметры же описывают способ появления элемента на экране. Возможные значения параметров приведены в таблице:

Параметры, описывающие элемент меню в файле ресурсов

Флаг | Значение
CHECKED | Рядом с именем элемента может отображаться небольшой значек, говорящий о том, что соответствующий флаг установлен
ENABLED | Элемент меню доступен
DISABLED | Элемент меню недоступен, но отображается как обычный
GRAYED | Элемент меню недоступен и отображается серым цветом
MENUBREAK | Горизонтальные меню размещают следующие элементы в новой строке, а вертикальные - в новом столбце
MENUBARBREAK | То же, что и предыдущее, но в случае вертикального меню столбцы разделяются вертикальной линией

Попробуем создать описание небольшого меню. Горизонтальное меню (menubar) позволит выбирать подменю "File", "Examples" и конечный элемент "Help". Подменю "File" будет содержать элементы "Open" и "Exit", разделенные горизонтальной линией, а подменю "Examples" - несколько конечных элементов.

Ниже приведен текст скрипта для этого меню:

  MyMenu MENU
   {
     POPUP "&File"
      {
        MENUITEM "&Open", 101
        MENUITEM SEPARATOR
        MENUITEM "E&xit", 102
      }
     POPUP "&Examples"
      {
        POPUP "Example 1"
         {
           MENUITEM "1&1", 103
           MENUITEM "1&2", 104
         }
        POPUP "Example 2"
         {
           MENUITEM "2&1", 105
           MENUITEM "2&2", 106
         }
      }
     MENUITEM "&Help", 111
   }
Следует обратить внимание на то, что идентификаторы действия есть только у MENUITEM'ов. Popup-меню идентификаторов не содержит.


Диалоговые окна и их элементы

Основным средством "общения" пользователя с программой являются диалоговые окна (их также называют диалогами). В этом разделе мы рассмотрим работу диалоговых окон и их взаимодействие не только с пользователем и программой, но и окнами более низкого уровня, называемыми элементами управления, которые выполняют большую часть черновой работы, незаметной не только пользователям, но и программистам.

Диалоговые окна можно классифицировать по двум критериям. Первый критерий - это модальность диалогового окна. Второй критерий, не всегда заметный, заслуживает особого внимания. Второй критерий фактически определяет, с одной стороны, возможности диалогового окна, а с другой стороны, ответственного за обработку сообщений посылаемых диалоговому окну. В подавляющем большинстве случаев обработку сообщений, адресованных диалоговому окну, производит функция диалогового окна (о ней речь впереди), но иногда диалоговое окно своей функции не имеет (она запрятана в "глубинах" системы) и всю обработку производит система. Возможности таких окон очень ограничены, в основном они предназначены для выдачи сообщений пользователю. Об этом говорит даже их название - окна сообщений.


Модальные и немодальные диалоги

Диалоговые окна бывают модальными и немодальными.
Наиболее часто используются модальные окна. Эти окна не дают пользователю возможности работать с другими окнами, созданными приложением, породившим диалоговое окно, но разрешают переключаться на работу с другими приложениями. Для того чтобы пользователь мог продолжить работу с другими окнами своего приложения, необходимо завершить работу с диалоговым окном.

В особых случаях, представляющих угрозу системе и требующих немедленной реакции оператора, могут использоваться системные модальные окна. Эти окна не позволяют переключаться ни на какое другое окно. Естественно, что и применять системное модальное окно нужно с умом.

Немодальные диалоговые окна не требуют своего завершения для продолжения работы, и пользователь может во время работы с ними свободно переключаться на любое приложение.


Кнопки, Списки И Прочее...

Элемент управления - это ОКНА более низкого по отношению к диалоговому окну уровня. Предлагаю отметить то, что элементы управления никогда не могут использоваться как самостоятельные окна. Они всегда используются на фоне какого-то окна, которое является для них родительским окном. Элементы управления, таким образом, всегда являются дочерними окнами, другими словами, у них всегда присутствует стиль WM_CHILD.

Как и любые другие окна, элементы управления могут получать и выдавать сообщения. Правда, это относится не ко всем элементам управления, но... Стоп! Давайте прервемся на секунду.

Мне бы хотелось обратить внимание читателя на один интересный момент. Для посылки сообщения обычно используют функции SendMessage() и SendDlgItemMessage(). Дело в том, что значение, которое возвращают эти функции, зависит только от того сообщения, которое они отправили. Таким образом, если вам необходимо узнать по возвращенному значению, что произошло в результате обработки того или иного сообщения, ищите описание возвращаемых значений не в описаниях функций, а описаниях сообщений.

По умолчанию подавляющее большинство сообщений от элементов управления получает диалоговое окно, которому они принадлежат. Диалоговое окно должно каким-то образом их обрабатывать. Отсюда очередной вывод - у диалогового окна должна быть собственная оконная функция.

Каждому элементу управления присваивается идентификатор. При каком-либо воздействии на этот орган управления со стороны пользователя диалоговое окно получает сообщение, содержащее идентификаторы элемента и типа производимого пользователем действия. Диалоговое функция обрабатывает эти сообщения и выполняет соответствующие действия. Этот процесс происходит параллельно с обработкой сообщений в оконной функции. При этом нужно заметить, что в отличие от обычного окна, "нормальное" диалоговое окно не имеет своего цикла обработки сообщений. Цикл обработки сообщений запускается один раз при запуске программы.

Элементами управления могут быть кнопки (buttons), которые мы уже использовали в окнах сообщений, переключатели (check boxes), селекторы (radio buttons), списки (list boxes), комбинированные списки (combo boxes), линейки проктутки (scroll bars) и статические элементы (statics). Все элементы в этом перечне относятся к категории базовых, и все они присутствовали и в Windows 3.x. На их основе Microsoft разработала серию новых элементов управления (common controls), которые позволили расширить возможности интерфейса с пользователем и улучшить внешний вид приложений. Мы рассмотрим как базовые, так и новые общие (как еще можно перевести на русский язык название "common controls"?) элементы управления.

Я уже упоминал, что основную часть работы диалогового окна выполняют элементы управления. Поэтому рассмотрим сначала вопрос о том, как может быть создано диалоговое окно, а потом на примерах - работу каждого типа элементов управления.


Создание диалогового окна

Диалоговое окно, как и меню, может быть создано несколькими способами: во-первых, с помощью описания его в файле ресурсов и, во-вторых, во время выполнения программы. Наиболее часто используется описание диалога в файле ресурсов. Лучше всего при создании диалого воспользоваться редактором ресурсов, с помощью которого может быть создан текстовый файл, содержащий описание диалогового окна. Ресурсы диалога в этом текстовом файле задаются оператором DIALOG, который имеет следующий формат:

DialogName DIALOG [DISCARDABLE]    X,Y,Width,Height
CAPTION "Заголовок окна"
STYLE <Стили диалогового окна>
FONT n, <имя шрифта>
 {
   Описание элементов диалога
 }
В данном случае DialogName - это имя диалогового окна. Опция DISCARDABLE станет совершенно ясной при рассмотрении вопроса об организации памяти в Windows. Параметры X и Y - это координаты верхнего левого угла диалового окна, Width и Height - ширина и высота диалога. STYLE описывает стили окна. Здесь могут использоваться как стили, применяемые для описания обычных окон, так и стили, применяемые только в диалоговых окнах. Эти новые стили приведены в таблице:

Стили диалоговых окон

Стиль | Значение | Эффект
DS_ABSALIGN | 0x0001L | Положение диалогового окна исчисляется в экранных координатах
DS_SYSMODAL | 0x0002L | Создается системное модальное диалоговое окно
DS_3DLOOK | 0x0004L | Создается диалоговое окно, имеющее зрительную иллюзию трехмерности
DS_FIXEDSYS | 0x0008L | Вместо SYSTEM_FONT используется SYSTEM_FIXED_FONT
DS_NOFAILCREATE | 0x0010L | Диалоговое окно создается, несмотря на то, что при его создании произошли ошибки
DS_LOCALEDIT | 0x0020L | В 32-битных приложениях не используется
DS_SETFONT | 0x0040L | Определяет шрифт, который будет применятся в диалоговом окне
DS_MODALFRAME | 0x0080L | Создается модальное диалоговое окно
DS_NOIDLEMSG | 0x0100L |
DS_SETFOREGROUND | 0x0200L | Поместить диалоговое окно на передний план
DS_CONTROL | 0x0400L |
DS_CENTER | 0x0800L | Диалоговое окно помещается в центр рабочей области
DS_CENTERMOUSE | 0x1000L |
DS_CONTEXTHELP | 0x2000L |

Приведенных выше сведений вполне достаточно, чтобы написать заготовку диалогового окна в файле ресурсов. Но какой смысл описывать диалоговое окно, если в нем нет ни одного из элементов управления? Ведь даже закрыть такое диалоговое окно (если в нем, конечно, нет системного меню) невозможно! Значит, нам необходимо срочно научиться описывать эти элементы!

Я уже упоминал о том, что в "недрах" Win32 есть масса предопределенных объектов. В частности, там находятся и некоторые предопределенные классы окон. К таким классам относятся кнопки (класс "button"), списки (класс "listbox"), комбинированные списки (класс "combobox"), окна редактирования (класс "edit"), полосы прокрутки (класс "scrollbar"), статистические элементы (класс "static"). У каждого класса есть свой определенный набор стилей, которые определяют внешний вид и поведение элементов управления, относящихся к данному классу.

Управление окном каждого класса, а также получение информации от него производится с помощью обмена управляющими сообщениями. О действиях пользователей с ними элементы управления оповещают свои родительские окна через нотификационные сообщения.


Кнопки

Перед тем, как начать рассказ о кнопках, хочу предостеречь читателя. Дело в том, что можно использовать кнопки и в обычных окнах. Но они, как и большинство элементов управления, проектировались для использования именно в диалоговых окнах. Использование кнопок в обычных окнах не рекомендуется, ибо это увеличивает риск того, что программа будет работать неправильно.

Кнопка - это имитация на экране обычной кнопки или переключателя. В этом разделе под кнопками я также подразумеваю не только PushButons (обычные нажимаемые кнопки), но и Check Boxes (обычно это небольшие квадратики, в которых можно установить или не установить пометку) и Radio Buttons (небольшие кружочки, В ОДНОМ из которых стоит точка). Пользователь может установить курсор мыши на кнопку, щелкнуть клавишей мыши - и кнопка пошлет диалоговому окну сообщение WM_COMMAND. То же произойдет и в случае, если пользователь сначала выберет кнопку клавишей Tab, а потом нажмет Enter.

В параметрах сообщения WM_COMMAND содержится информация, которой достаточно, чтобы диалоговое окно узнало, от какой кнопки пришло сообщение, какое действие требуется выполнить, и таким образом пользователь инициировал выдачу сообщения.

При этом необходимо отметить, что обычная кнопка (ее называют PushButton) не помнит того, что с ней делали, т.е. она на короткое время становится нажатой, а затем возвращается в исходное состояние. Можно нажать кнопку десять раз подряд, и все десять раз она пошлет диалоговому окну одно и то же сообщение, если, конечно, кнопка не сделана запрещенной. CheckBox помнит о том, что он находится в одном из двух состояний - установленом или не установленном, некоторые CheckBox'ы могут находиться еще и в неопределенном состоянии.

Говорит о состоянии одной RadioButton бессмысленно. Дело в том, что RadioButton'ы предназначены для осуществления выбора одного из нескольких взаимоисключаемых вариантов, поэтому можно говорить о группе (иногда говорят кластере) RadioButton'ов. В частности, для объединения RadioButton'ов в кластеры служит такой элемент управления, как группа (GroupBox). Обычно группы используются для группирования органов управления только для улучшения дизайна диалоговых окон. Что же касается RadioButton'ов, то без обрамляющей группы существование кластера не имеет смысла.

Формат описания кнопок в окне диалога достаточно прост:

  CONTROL  "Заголовок", ButtonID, class, styles, X, Y, Width, Height
X, Y, Width, Height - это все ясно. Все то же самое, что и при описании непосредственно диалогового окна. "Заголовок" - надпись на кнопке или рядом с кнопкой. ButtonID - идентификатор кнопки, т.е. значение, которое посылается диалоговому окну при нажатии кнопки в качестве LOWORD(wParam). Через HIWORD(wParam) диалоговое окно получает код нотификации, т.е. код того действия, которое произвел пользователь. Примерами действия пользователя могут служить нажатие клавиши Enter, двойной щелчек правой или левой клавишей мыши и так далее. А источник, т.е. хэндл инициировавшего сообщение окна, сообщения содержится в lParam (я напомню, что если сообщение приходит от меню, то lParam всегда равен 0, а если от акселератора - 1). Все легко и просто. Сложности начинаются при рассмотрении класса, определяющегося полем class, типа кнопки и стиля кнопки, которые определяются параметром style.

Для кнопок, вне зависимости от того, PushButton ли это, RadioButton или CheckBox, класс всегда определяется как "button".

Читатель, наверное, уже привык к тому, что ответы на большинство вопросов можно найти в файлах заголовках и файлах помощи Win32. Поэтому и сейчас, как всегда, смотрим в заголовочный файл winuser.h, выбираем оттуда стили кнопок, которые начинаются с букв BS_, и сводим их в таблицу. Надоело изучать эти таблицы? Ничего, тяжело в учении - легко в бою! © А.В. Суворов, "Наука побеждать".

Стили кнопок

Флаг | Значение | Эффект
BS_PUSHBUTTON | 0x00000000L | Создается обычная кнопка
BS_DEFPUSHBUTTON | 0x00000001L | Создается обычная кнопка, которая срабатывает при нажатии "Enter" даже тогда, когда не выбрана
BS_CHECKBOX | 0x00000002L | Создается CheckBox, при нажатии состояние автоматически не изменяется, забота об этом ложится на программу
BS_AUTOCHECKBOX | 0x00000003L | Создается CheckBox, который автоматически меняет свое состояние при нажатии
BS_RADIOBUTTON | 0x00000004L | Создается Radio Button, автоматически состояние не меняется
BS_3STATE | 0x00000005L | То же, что и BS_CHECKBOX, но имеет три состояния - включенное, отключенное и неопределенное, автоматически состояние не меняет
BS_AUTO3STATE | 0x00000006L | То же, что и предыдущее, но состояние меняется автоматически
BS_GROUPBOX | 0x00000007L | Группа
BS_USERBUTTON | 0x00000008L | Устаревший стиль, необходимо использовать BS_OWNERDRAW
BS_AUTORADIOBUTTON | 0x00000009L | То же, что и RadioButton, но при нажатии состояние меняется автоматически
BS_OWNERDRAW | 0x0000000BL | За прорисовку кнопки отвечает программа, а не система
BS_LEFTTEXT | 0x00000020L | Текст помещается слева от RadioButton'а или CheckBox'а, то же, что и BS_RIGHTBUTTON
BS_TEXT | 0x00000000L | Внутри или рядом с кнопкой отображается текст
BS_ICON | 0x00000040L | Внутри кнопки или рядом с кнопкой отображается иконка
BS_BITMAP | 0x00000080L | Внутри кнопки или рядом с кнопкой отображается bitmap
BS_LEFT | 0x00000100L | Размещает текст у левого края прямоугольника, выделенного для размещения кнопки
BS_RIGHT | 0x00000200L | Размещает текст у правого края прямоугольника, выделенного для размещения кнопки
BS_CENTER | 0x00000300L | Размещает текст по горизонтали в центре прямоугольника, выделенного для размещения кнопки
BS_TOP | 0x00000400L | Размещает текст у верхнего края прямоугольника, выделенного для размещения кнопки
BS_BOTTOM | 0x00000800L | Размещает текст у нижнего края прямоугольника, выделенного для размещения кнопки
BS_VCENTER | 0x00000C00L | Размещает текст по вертикали в центре прямоугольника, выделенного для размещения кнопки
BS_PUSHLIKE | 0x00001000L | Делает CheckBox или RadioButton внешне похожими на PushButton
BS_MULTILINE | 0x00002000L | При необходимости текст разбивается на несколько строк
BS_NOTIFY | 0x00003000L | Разрешает посылку родительскому окну нотификационных сообщений BN_DBLCLK, BN_KILLFOCUS и BN_SETFOCUS
BS_FLAT | 0x00008000L | Не добавляется имитация трехмерности изображения элемента управления
BS_RIGHTBUTTON | 0x00000020L | RadioButton или CheckBox размещаются справа от надписи (то же, что и BS_LEFTTEXT)


Режимы отображения

Подавляющее большинство функций, работающих с оконными координатами, определяют координаты относительно начала рабочей области окна, т.е. от левого верхнего угла.

Таким образом, даже при перемещии окна координаты объектов внутри окна остаются неизменными. При этом единицы, в которых измеряются координаты, зависят от режима отображения (mapping mode), установленного для данного окна. Единицы измерения, зависящие от режима отображения, называют логическими единицами координатами.

При выводе информации на конкретное устройство единицы логических координат преобразуются в физические единицы, которыми являются пиксели.

Идентификаторы, применяемые для обозначения режимов отображения

Идентификатор | Значение | Эффект
MM_TEXT | 1 | Логическая единица равна пикселю, начало координат - левый верхний угол окна, положительное значение x - вправо, положительное значение y - вниз (обычный отсчет)
MM_LOMETRIC | 2 | Логическая единица равна 0.1 мм, отсчет координат - обычный
MM_HIMETRIC | 3 | Логическая единица равна 0,01 мм, отсчет координат - обычный
MM_LOENGLISH | 4 | Логическая единица равна 0,1 дюйма, отсчет координат - обычный
MM_HIENGLISH | 5 | Логическая единица равна 0,001 дюйма, отсчет координат - обычный
MM_TWIPS | 6 | Логическая единица равна 1/12 точки на принтере (~1/1440 дюйма - "твип"), отсчет координат - обычный
MM_ISOTROPIC | 7 | Логические единицы и направление осей определяются программистом с помощью функций SetWindowExtEx() и SetViewportExtEx(), единицы по осям имеют одинаковый размер
MM_ANISOTROPIC | 8 | Логические единицы и направления осей определяются так же, как и для MM_ISOTROPIC, но размеры единиц по осям различны
MM_MIN |  | MM_TEXT
MM_MAX |  | MM_ANISOTROPIC
MM_MAX_FIXEDSCALE |  | MM_TWIPS

Для установки текущего режима отображения используется функция SetMappingMode(), которая в файле wingdi.h описана следующим образом:

  WINGDIAPI int  WINAPI SetMapMode(HDC, inc)
Первый аргумент этой функции - хэндл контекста устройства, для которого устанавливается данный режим. Второй аргумент определяет задаваемый режим отображения. В том же файле wingdi.h можно найти и идентификаторы, использующиеся для обозначения режимов отображения(см. табл.). Надеюсь, что после того, как была просмотрена таблица, вопросов у читателя не возникло. Теперь ясно, что иногда для решения конкретных задач (например, построения графиков) можно использовать различные режимы отображения. При создании окна по умолчанию устанавливается режим MM_TEXT, т.е. все координаты исчисляются в пикселах.


Пишем программу

Наша программа будет отображать bitmap в окне и при необходимости производить его масштабирование:

.386
.model flat, stdcall
include win32.inc

extrn            BeginPaint:PROC
extrn            CreateCompatibleDC:PROC
extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DeleteDC:PROC
extrn            DeleteObject:PROC
extrn            DispatchMessageA:PROC
extrn            EndPaint:PROC
extrn            ExitProcess:PROC
extrn            GetClientRect:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            GetObjectA:PROC
extrn            GetStockObject:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            LoadImageA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            SelectObject:PROC
extrn            ShowWindow:PROC
extrn            StretchBlt:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

BITMAP struct
   bmType       dd ?
   bmWidth      dd ?
   bmHeight     dd ?
   bmWidthBytes dd ?
   bmPlanes     dw ?
   bmBitsPixel  dw ?
   bmBits       dd ?
BITMAP ends

LR_LOADFROMFILE  = 010h
SRCCOPY          = 00CC0020h
IMAGE_BITMAP     = 0

.data

newhwnd          dd 0
msg              MSGSTRUCT
wc               WNDCLASS

hDC              dd ?
hCompatibleDC    dd ?
PaintStruct      PAINTSTRUCT
hBitmap          dd ?
hOldBitmap       dd ?
Rect             RECT
Bitmap           BITMAP

hInstance        dd 0

szTitleName      db 'DCDemo', 0
szClassName      db 'ASMCLASS32',0
szImg            db 'IMG.BMP',0

.code
;-----------------------------------------------------------------------------
start:
        call    GetModuleHandleA, 0
        mov     [hInstance], eax

; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW
        mov     [wc.clsLpfnWndProc], offset DCDemoWndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInstance]
        mov     [wc.clsHInstance], eax

        call    LoadIconA, 0, IDI_APPLICATION
        mov     [wc.clsHIcon], eax

        call    LoadCursorA, 0 ,IDC_ARROW
        mov     [wc.clsHCursor], eax

        call    GetStockObject, WHITE_BRUSH
        mov     [wc.clsHbrBackground], eax

        mov     [wc.clsLpszMenuName], 0
        mov     [wc.clsLpszClassName], offset szClassName

        call    RegisterClassA, offset wc

        call    CreateWindowExA, 0,offset szClassName,offset szTitleName, \
                WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, \
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0, \
                [hInstance], 0

        mov     [newhwnd], eax

        call    ShowWindow, [newhwnd], SW_SHOWNORMAL
        call    UpdateWindow, [newhwnd]

msg_loop:
        call    GetMessageA, offset msg, 0, 0, 0
        .if     eax != 0
                call    TranslateMessage, offset msg
                call    DispatchMessageA, offset msg
                jmp     msg_loop
        .endif
        call    ExitProcess, [msg.msWPARAM]

;-----------------------------------------------------------------------------
DCDemoWndProc    proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_PAINT
        je      wmpaint
        cmp     [wmsg], WM_DESTROY
        je      wmdestroy

        call    DefWindowProcA, [hwnd],[wmsg],[wparam],[lparam]
        jmp     finish

wmpaint:
;=================================================================

        ; Получаем контекст устройства
        call    BeginPaint, [hwnd], offset PaintStruct
        mov     [hDC], eax

        ; Загружаем bitmap, который будет отображаться в окне, из файла
        call    LoadImageA, 0,offset szImg,IMAGE_BITMAP, \
                0,0,LR_LOADFROMFILE
        mov     [hBitmap], eax

        ; Получаем размерность загруженного bitmap'а
        call    GetObjectA, [hBitmap], size BITMAP, offset Bitmap

        ; Создаем совместимый с контекстом окна контекст в памяти
        call    CreateCompatibleDC, [hDC]
        mov     [hCompatibleDC], eax

        ; Делаем загруженный из файла bitmap текущим в совместимом контексте
        call    SelectObject, [hCompatibleDC], [hBitmap]
        mov     [hOldBitmap], eax

        ; Определяем размер рабочей области окна
        call    GetClientRect, [hwnd], offset Rect

        ; Копируем bitmap с совместимого на основной контекст
        ; с маштабированием
        call    StretchBlt, [hDC],0,0,[Rect.rcRight],[Rect.rcBottom], \
                [hCompatibleDC],0,0,[Bitmap.bmWidth],[Bitmap.bmHeight], \
                SRCCOPY

        ; Вновь делаем старый bitmap текущим
        call    SelectObject, [hCompatibleDC], [hOldBitmap]

        ; Удаляем загруженный с диска bitmap
        call    DeleteObject, [hBitmap]

        ; Удаляем совместимый контекст
        call    DeleteDC, [hCompatibleDC]

        ; Освобождаем основной контекст, завершая перерисовку
        ; рабочей области окна
        call    EndPaint, [hwnd], offset PaintStruct

        mov     eax, 0
        jmp     finish
;=================================================================

wmdestroy:
        push    0
        call    PostQuitMessage
        mov     eax, 0
finish:
        ret
DCDemoWndProc          endp
ends
end start
Возьмите файл dcdemo.zip и попробуйте запустить программу. Если все правильно, то bitmap точно впишется в окно. Попробуйте поизменять размеры окна. Bitmap постоянно будет точно вписываться в окно, при этом растягиваясь или сжимаясь. А теперь давайте разберем эту программу и сопоставим операторы программы с шагами алгоритма работы с контекстом в памяти, о котором я говорил ранее.

В оконной функции, которая называется DCDemoWndProc, интерес для нас представляет обработка сообщения WM_PAINT, которое мы и рассмотрим. Первый шаг алгоритма - получить хэндл контекста устройства - мы выполняем посредством вызова функции BeginPaint(hWnd, &PaintStruc). Аргумент hWnd очевиден - мы получаем контекст данного окна. Что же касается структуры PaintStruct...

Вид окна, отображаемого программой:
Окно DCDemo

Понятно, что окно далеко не всегда должно перерисоваться полностью. К примеру, только часть окна перекрывается другим окном. Естественно, что и перерисоваться должна только часть окна. В этом случае полная перерисовка всего окна будет только лишней тратой времени и ресурсов компьютера. Windows "знает" о том, какая часть окна перекрыта. При необходимости перерисовки (при вызове BeginPaint()) система заполняет структуру типа PAINTSTRUCT, адрес которой передается системе как второй аргумент BeginPaint(). Среди всех полей структуры типа PAINTSTRUCT основным (на мой взгдяд) является поле, содержащее координаты той области (clipping region), которая должна быть перерисована. В нашем примере информация, хранящаяся в этой структуре, но я прощу читателя обратить внимание на эту структуру и в дальнейшем использовать ее. Получив от функции BeginPaint() хэндл контекста устройства (hDC), будем считать первых шаг выполненным.

Второй шаг - получение хэндла bitmap'а, который будет отображаться в окне - мы делаем, вызывая функцию LoadImage(). Я не случайно воспользовался именно этой функцией. Во-первых, возможности этой функции достаточно широки. Она позволяет загружать графические образы как из ресурсов, записанных в исполняемом файле, так и из файлов, содержащих только изображения. Графическим образом может быть bitmap, курсор и иконка. Кроме этого, функция позволяет управлять параметрами отображения и загрузки образа. Во-вторых, подавляющее большинство функций работают с ресурсами, сохраненными в исполняемом файле, и у программистов, начинающих осваивать Win32, попытки загрузить что-либо из файла сопровождаются некоторыми трудностями.

В файле winuser.h эта функция описана следующим образом:

  WINUSERAPI HANDLE WINAPI LoadImageA(HINSTANCE,LPCSTR,UINT,int,int,UINT)
Первых, второй и последний аргументы этой функции работают в связке. Первый аргумент(hInst) - хэндл программы. Как читатель должен помнить, если вместо хэндла программы указан нуль, то объект является предопределенным, т.е. хранится в "глубинах" системы. В противном случае, объект загружается откуда-то снаружи. Второй аргумент - lpszName - определяет загружаемый объект. Последний аргумент - fuLoad - содержит флаги, определяющие режим загрузки объекта. Среди этих флагов есть флаг LR_LOADFROMFILE. Его название определяет его назначение - если этот флаг установлен, загрузка происходит из внешнего файла. От значения первого и последнего аргументов зависит, как будет интерпретирован второй аргумент. Взаимодействие этих трех аргументов объясняется в таблице:

Взаимодействие аргументов функции LoadImage()

fuLoad(флаг LR_LOADFROMFILE) | hInst | lpszName
Не установлен | NULL | Идентификатор предопределенного объекта
Не установлен | не NULL | Имя ресурса
Установлен | NULL | Имя файла, в котором содержится bitmap
Установлен | не NULL | Имя файла, в котором содержится bitmap

Третий аргумент - тип образа, он может принимать значения IMAGE_BITMAP, IMAGE_CURSOR, IMAGE_ICON и IMAGE_ENHMATEFILE. Здесь комметарии излишни. Четвертый и пятый аргументы указывают ширину и высоту иконки или курсора и в нашем примере не используются.

Последний аргумент - флаги, определяющие параметры загрузки образа в память. Их достаточно много, только в файле winuser.h я насчитал 12 возможных идентификаторов. Все они начинаются с букв LR. Ничего сложного в них нет, и читатель сам сможет изучить их. Краткое описание этих флагов приведено в таблице:

Флаги, определяющие параметры загрузки образа в память

Флаг | Значение | Эффект
LR_DEFAULTCOLOR | 0x0000 | Указывает, что загружаемый образ - не монохромный
LR_MONOCHROME | 0x0001 | Указывает, что загружаемый образ - черно-белый
LR_COLOR | 0x0002
LR_COPYRETURNORG | 0x0004
LR_COPYDELETEORG | 0x0008
LR_LOADFROMFILE | 0x0010 | Образ необходимо загружать из файла, а не из ресурсов
LR_LOADTRANSPARENT | 0x0020 | Все пиксели, цвет которых совпадает с цветом пикселя, расположенного в левом верхнем углу bitmap'а, отображаются как прозрачные
LR_DEFAULTSIZE | 0x0040 | Использовать ширину и высоту образа, определенные в системных метриках для иконки и курсора, если cxDesired или cyDesired равны 0. Если этот флаг не установлен, а cxDesired и/или cyDesired равны 0, используются размеры образа, указанные в ресурсе.
LR_LOADMAP3DCOLORS 0x1000 Заменить следующие оттенки серого цвета:
RGB(128,128,128)(DkGray) - на COLOR_3DSHADOW,
RGB(192,192,192)(Gray) - на COLOR_3DFACE,
RGB(223,223,223)(LtGray) - на COLOR_3DLIGHT
LR_CREATEDIBSECTION | 0x2000 | При загрузке bitmap'a возвращает оригинальные значения цветов, не преобразуя bitmap в совместимый с данным контекстом
LR_COPYFROMRESOURCE
LR_SHARED 		0x8000 Разделять хэндл загруженного изображения, в случае, если образ загружается несколько раз. Нежелательно применять к образам нестандартных размеров

Функция LoadImage() возвращает нам хэндл загруженного bitmap'а (hBitmap) (или NULL, если где-то что-то не так), после чего мы можем считать второй шаг нашего алгоритма пройденным.

Третий шаг - получение совместимого контекста в памяти - выполняется посредством вызова функции CreateCompatibleDC(). Единственный аргумент этой функции - хэндл контекста (hDC), для которого создается совместимый контекст.

Четвертый шаг мы реализуем вызовом функции SelectObject(). Первым аргументом указываем хэндл контекста, в котором замещается текущий элемент (в данном случае это хендл только что созданного контекста в памяти hCompatibleDC), а вторым - хэндл элемента, которым замещается текущий элемент (хэндл загруженного bitmap'а hBitmap). Немаловажно, что эта функция возвращает хэндл ЗАМЕЩЕННОГО элемента (hOldBitmap), т.е. впоследствии с этим элементом могут также производиться манипуляции.

А вот на пятом шаге происходит то, ради чего мы зыварили всю эту кашу с загрузкой bitmap'ов, совместимыми контекстами и прочим. Для копирования bitmap'а (с масштабированием) с одного контекста на другой, мы используем функцию StretchBlt(), одну из "могучих blt", по меткому выражению Чарльза Петцольда. К их числу, помимо StretchBlt(), относятся BiBlt() и PatBlt().

Наверное, StretchBlt() является самой "могучей" из них. И наверное, ее мощь и обусловила наличие у этой функции "всего-навсего" одиннадцати аргументов. В файле wingdi.h эта функция описана следующим образом:

  WINGDIAPI BOOL WINAPI StretchBlt(HDC,int,int,int,int,HDC,int,int,int,int,DWORD);
Первые пять аргументов описывают тот прямоугольник на экране, в который будет вписан bitmap (на рисунке он обозначен зеленым цветом). Ту часть bitmap'а, которая будет вписана в прямоугольник на экране (на рисунке - пересекающаяся часть зеленого и синего прямоугольников), описывают следующие пять аргументов. И последний, одиннадцатый аргумент, так называемый код растовой операции, описывает, каким образом пиксели одного bitmap'а будут взаимодействовать с пикселями другого bitmap'а. Для того чтобы лучше понять аргументы функции, обратимся к рисунку. Представим, что в окне, в регионе, обозначенном на рисунке зеленым цветом, нужно отобразить bitmap (обозначен на рисунке синим цветом) или часть bitmap'а, при необходимости сжав или растянув ее.

Взаимодействие аргументов функции StretchBlt
Взаимодействие аргументов функции StretchBlt

Итак, первый и шестой аргументы функции - хэндлы окна (hDC) и совместимого контекста (hCompatibleDC) соответственно. Второй (nXOriginDest) и третий (nYOriginDest) аргументы содержат смещение верхнего левого угла прямоугольника, в который будет вписываться bitmap, относительно левой и верхней сторон рабочей области окна (В каких единицах выражено смещение? Напомню, мы создали окно и не меняли режим отображения, т.е. текущий режим является установленным по умолчанию). Четвертый (nWidthDest) и пятый (nHeightDest) аргументы - ширина и высота этого прямоугольника. Седьмой (nXOriginSrc) и восьмой (nYOriginSrc) аргументы аналогичны второму и третьему аргументам. Девятый (nWidth) и десятый (hHeightSrc) аргументы содержат ширину и высоту отображаемой части bitmap'а . Не нужно обладать развитым пространственным воображением, чтобы понять, что, изменяя положения прямоугольников друг относительно друга, а также относительно окна, меняя их размеры, можно отобразить на экране любую часть или целый bitmap.

В примере мы хотим фактически совместить наш bitmap с рабочей областью окна, поэтому второй и третий аргументы вызываемой функции равны нулю. Четвертый и пятый аргументы равны ширине и высоте рабочей области (мы получили ширину и высоту рабочей области с помощью функции GetClientRect()). Седьмой и восьмой аргументы равны нулю (подумайте, почему?), а десятый содержат ширину и высоту bitmap'а, которые мы получили, обратившись к GetObject(). Вот, кажется, и все. Нет, не все.

Как работает одиннадцатый аргумент, определяющий взаимодействие двух bitmap'ов? Давайте закончим обсуждение нашего алгоритма, а затем вернемся к этому вопросу.

Мы прошли уже пять шагов алгоритма. Остались еще три шага - удаление совместимого контекста, объекта и контекста устройства - пусть читатель сам определит, какие операторы программы их реализуют.

Неужели мы добрались до конца нашего алгоритма? Мне он показался бесконечным! Давайте прежде чем рассматривать одиннадцатый аргумент упомянем об оставшихся "могучих Blt".

Функция BitBlt() тоже копирует bitmap с одного контекста на другой, но без масштабирования. Следовательно, ей не нужны девятый и десятый аргументы - ширина и высота отображаемой области - отображается все то, что поместится в отведенную для этого область (зеленый прямоугольник на рисунке).

Последняя из "могучих" - функция PatBlt - просто закрашивает прямоугольник на экране, используя для этого выбранную в контексте устройства кисть. Раз нет отображаемого объекта, то зачем нам его контекст и координаты? Отбрасываем аргументы с шестого по десятый включительно и получаем список аргументов PatBlt().

Наконец мы подошли к тому, чтобы уяснить, что же представляет собой одиннадцатый аргумент функции StretchBlt(). Сейчас мы поговорим о том, что же такое.


Коды растровых операций

Выше уже было сказано, что одиннадцатый аргумент функции StretchBlt() - это код растровой операции. Другими словами, это код, который определяет, как при операции будут взаимодействовать биты, определяющие заливку и изображение совместимого контекста с изображением на действительном контексте. Комбинируются биты на основе логических операций над ними. По укоренившейся в книге по программированию для Windows традиции, эти операции записываются в обратной польской нотации (не путать с венгерской, автор польской нотации не имеет к Microsoft ни малейшего отношения).

По той же традиции, биты, определяющие bitmap совместимого контекста, обозначают буквой S (source - источник, исходный), биты заливки - буквой P (pattern - образец), а биты, на которых будет прорисовываться изображение - буквой D (destination - назначение, место назначения). Операции обозначаются следующим образом: a - побитовое И (AND), n - побитовое НЕТ (NO), o - побитовое ИЛИ (OR), x - побитовое исключающее ИЛИ (XOR).

Несколько слов о польской нотации. В ней операции записываются слева направо. Знак операции следует за операндами. Появление знака операции означает, что нужно произвести следующие действия:
- взять два последних операнда - произвести с ними требующуюся операцию - записать результат на место последних двух операндов.

Фактически польская нотация описывает действия таким образом, словно операнды и операции находятся в стеке, для чего, собственно, эта польская нотация и была изобретена.

Обозначив знак операции как Op, в польской нотации действия с битами можно записать таким образом:

PSOp

Это говорит о необходимости взять пиксель патерны и прорисоваемого bitmap'а и произвести над ним операцию. Если в операции участвуют три операнда, то получим:

DPSOp1Op2

Что мы должны сделать в этом случае? Правильно, сначала произвести действие, определяемое Op1, с битами патерны и прорисовываемым bitmap'ом, после этого произвести Op2 с полученным результатом и битами действительного контекста. Ничего сложного здесь нет.

Каждый код растровой операции представляется 32-битным целым. Старшее слово кода представляет собой индекс битовой операции, младшее - код операции. Как определяется индекс операции?

Давайте представим, что нам необходимо определить индекс растровой операции, определяемой в польской нотации записью DPSxx. Попутно можно определить и индекс операции PSx. Запишем друг под другом ОПРЕДЕЛЕННЫЕ значения P,S и D, а под ними - результаты побитовых операций PSx и DPSxx:

P | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0
S | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0
D | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0

PSx | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0
DPSxx | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0

Итак, индекс операции PSx - 0x3C, а индекс операции DPSxx - 0x96.
Уважаемый читатель, я прошу обратить внимание на то, что друг под другом записываются не произвольные, а строго определенные значения. Эти значения позволяют перебрать все возможные комбинации патерны, исходного и целевого bitmap'ов. Теперь, когда все стало ясно, вы можете попробовать попрактиковаться в определении индексов дюбых операций. Несмотря на то, что существуют 256 индексов растровых операций, на практике используются только некоторые из них. В файле wingdi.h для наиболее часто используемых растровых операций определены идентификаторы, которые приведены в таблице:

Краткое описание кодов растровых операций

Наименование | Индекс операции | Польская запись | Эффект
BLACKNESS | 0x00 | 0 | Заполнение действительного контекста черным цветом
NOTSRCERASE | 0x11 | DSon |
NOTSRCCOPY | 0x33 | Sn | Прорисовываемый bitmap отображается в негативном виде
DSTINVERT | 0x55 | Dn | Изображение действительного контекста проявляется негативным
PATINVERT | 0x5A | DPx |
SRCINVERT | 0x66 | DSx |
SRCAND | 0x88 | DSa |
MERGEPAINT | 0xBB | DSno |
MERGECOPY | 0xC0 | PSa |
SRCCOPY | 0xCC | S | Копирование прорисовываемого bitmap'а на действительный контекст
SCRPAINT | 0xEE | DSo |
PATCOPY | 0xF0 | P | Копирование патерны на действительный контекст
PATPAINT | 0xFB | DPSnoo |
WHITENESS | 0xFF | 1 | Заполнение действительного контекста белым цветом

На основании данных таблицы я затрудняюсь объяснить, как изменяется изображение при использовании разных растровых операций. Рекомендую читателю запустить приведенную выше программу несколько раз, и каждый раз в функции StretchBlt() указывать новую растровую операцию.

Теперь и одиннадцатый аргумент PatBlt() стал ясным и понятным - я просто копирую bitmap в окно. Только и всего. Кстати, понимание логики работы с растровыми операциями может позволить избежать трудоемких преобразований bitmap'ов перед копированием.


Полосы прокрутки

Мне кажется, что у читателя может возникнуть вопрос: что делать в тех случаях, когда нам не нужно масшабировать bitmap, но нужно иметь возможность просматривать все части изображения? Ответ заключен в названии данного раздела - использование полос прокрутки.

Давайте, уважаемый читатель, чуть-чуть изменим предыдущую программу для того, чтобы продемонстрировать использование полос прокрутки, и посмотрим, как она будет работать. Текст программы с внесенными изменениями:

.386
.model flat, stdcall
include win32.inc

extrn            BeginPaint:PROC
extrn            BitBlt:PROC
extrn            CreateCompatibleDC:PROC
extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DeleteDC:PROC
extrn            DeleteObject:PROC
extrn            DispatchMessageA:PROC
extrn            EndPaint:PROC
extrn            ExitProcess:PROC
extrn            GetClientRect:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            GetObjectA:PROC
extrn            GetStockObject:PROC
extrn            InvalidateRect:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            LoadImageA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            SelectObject:PROC
extrn            SetScrollPos:PROC
extrn            SetScrollRange:PROC
extrn            ShowWindow:PROC
extrn            StretchBlt:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

BITMAP struct
   bmType       dd ?
   bmWidth      dd ?
   bmHeight     dd ?
   bmWidthBytes dd ?
   bmPlanes     dw ?
   bmBitsPixel  dw ?
   bmBits       dd ?
BITMAP ends

LR_LOADFROMFILE  = 010h
SRCCOPY          = 00CC0020h
IMAGE_BITMAP     = 0

TRUE             = 1
FALSE            = 0

; Scroll Bar Constants
SB_HORZ          = 0
SB_VERT          = 1
SB_CTL           = 2
SB_BOTH          = 3

; Scroll Bar Commands
SB_LINEUP        = 0
SB_LINELEFT      = 0
SB_LINEDOWN      = 1
SB_LINERIGHT     = 1
SB_PAGEUP        = 2
SB_PAGELEFT      = 2
SB_PAGEDOWN      = 3
SB_PAGERIGHT     = 3
SB_THUMBPOSITION = 4
SB_THUMBTRACK    = 5
SB_TOP           = 6
SB_LEFT          = 6
SB_BOTTOM        = 7
SB_RIGHT         = 7
SB_ENDSCROLL     = 8

.data

newhwnd          dd 0
msg              MSGSTRUCT
wc               WNDCLASS

hDC              dd ?
hCompatibleDC    dd ?
PaintStruct      PAINTSTRUCT
hBitmap          dd ?
hOldBitmap       dd ?
Rect             RECT
Bitmap           BITMAP
nHorizDifference dd 0
nVertDifference  dd 0
nHorizPosition   dd 0
nVertPosition    dd 0

hInstance        dd 0

szTitleName      db 'DCDemo', 0
szClassName      db 'ASMCLASS32',0
szImg            db 'IMG.BMP',0

.code
;-----------------------------------------------------------------------------
start:
        call    GetModuleHandleA, 0
        mov     [hInstance], eax

; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW
        mov     [wc.clsLpfnWndProc], offset DCDemoWndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInstance]
        mov     [wc.clsHInstance], eax

        call    LoadIconA, 0, IDI_APPLICATION
        mov     [wc.clsHIcon], eax

        call    LoadCursorA, 0 ,IDC_ARROW
        mov     [wc.clsHCursor], eax

        call    GetStockObject, WHITE_BRUSH
        mov     [wc.clsHbrBackground], eax

        mov     [wc.clsLpszMenuName], 0
        mov     [wc.clsLpszClassName], offset szClassName

        call    RegisterClassA, offset wc

        call    CreateWindowExA, 0,offset szClassName,offset szTitleName, \
                WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, \
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0, \
                [hInstance], 0

        mov     [newhwnd], eax

        call    ShowWindow, [newhwnd], SW_SHOWNORMAL
        call    UpdateWindow, [newhwnd]

msg_loop:
        call    GetMessageA, offset msg, 0, 0, 0
        .if     eax != 0
                call    TranslateMessage, offset msg
                call    DispatchMessageA, offset msg
                jmp     msg_loop
        .endif
        call    ExitProcess, [msg.msWPARAM]

;-----------------------------------------------------------------------------
DCDemoWndProc    proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_CREATE
        je      wmcreate
        cmp     [wmsg], WM_PAINT
        je      wmpaint
        cmp     [wmsg], WM_VSCROLL
        je      wmvscroll
        cmp     [wmsg], WM_HSCROLL
        je      wmhscroll
        cmp     [wmsg], WM_DESTROY
        je      wmdestroy

        call    DefWindowProcA, [hwnd],[wmsg],[wparam],[lparam]
        jmp     finish

wmcreate:
        call    LoadImageA, 0,offset szImg,IMAGE_BITMAP, \
                0,0,LR_LOADFROMFILE
        mov     [hBitmap], eax
        mov     eax, 0
        jmp     finish

wmpaint:
        call    BeginPaint, [hwnd], offset PaintStruct
        mov     [hDC], eax

        call    GetObjectA, [hBitmap], size BITMAP, offset Bitmap

        call    CreateCompatibleDC, [hDC]
        mov     [hCompatibleDC], eax

        call    SelectObject, [hCompatibleDC], [hBitmap]
        mov     [hOldBitmap], eax

        call    GetClientRect, [hwnd], offset Rect

        call    BitBlt, [hDC],0,0,[Rect.rcRight],[Rect.rcBottom], \
                [hCompatibleDC],[nHorizPosition],[nVertPosition], \
                SRCCOPY

        mov     eax, [Bitmap.bmWidth]
        sub     eax, [Rect.rcRight]
        mov     [nHorizDifference], eax

        .if     eax > 0
                call    SetScrollRange, [hwnd],SB_HORZ,0, \
                        [nHorizDifference],TRUE
        .else
                call    SetScrollRange, [hwnd],SB_HORZ,0,0,TRUE
        .endif

        mov     eax, [Bitmap.bmHeight]
        sub     eax, [Rect.rcBottom]
        mov     [nVertDifference], eax

        .if     eax > 0
                call    SetScrollRange, [hwnd],SB_VERT,0, \
                [nVertDifference],TRUE
        .else
                call    SetScrollRange, [hwnd],SB_VERT,0,0,TRUE
        .endif

        call    SelectObject, [hCompatibleDC], [hOldBitmap]
        call    DeleteDC, [hCompatibleDC]
        call    EndPaint, [hwnd], offset PaintStruct
        mov     eax, 0
        jmp     finish

wmvscroll:
        movzx   eax, [word ptr wparam]
        .if     eax == SB_LINEDOWN
                mov      eax, [nVertPosition]
                .if eax < [nVertDifference]
                    inc  [nVertPosition]
                .endif
        .elseif eax == SB_LINEUP
                .if [nVertPosition] > 0
                    dec  [nVertPosition]
                .endif
        .elseif eax == SB_THUMBTRACK
                 movzx   eax, [word ptr wparam+2]
                 mov     [nVertPosition], eax
        .endif
        call    SetScrollPos, [hwnd],SB_VERT,[nVertPosition],TRUE
        call    InvalidateRect, [hwnd],0,TRUE
        mov     eax, 0
        jmp     finish

wmhscroll:
        movzx   eax, [word ptr wparam]
        .if     eax == SB_LINEDOWN
                mov      eax, [nHorizPosition]
                .if eax < [nHorizDifference]
                    inc  [nHorizPosition]
                .endif
        .elseif eax == SB_LINEUP
                .if [nHorizPosition] > 0
                    dec  [nHorizPosition]
                .endif
        .elseif eax == SB_THUMBTRACK
                 movzx   eax, [word ptr wparam+2]
                 mov     [nHorizPosition], eax
        .endif
        call    SetScrollPos, [hwnd],SB_HORZ,[nHorizPosition],TRUE
        call    InvalidateRect, [hwnd],0,TRUE
        mov     eax, 0
        jmp     finish

wmdestroy:
        call    DeleteObject, [hBitmap]
        call    PostQuitMessage, 0
        mov     eax, 0
finish:
        ret
DCDemoWndProc          endp
ends
end start

Если в окне, создаваемом программой, не отображаются горизонтальная и вертикальная полосы прокрутки, необходимо уменьшить размеры окна по горизонтали и вертикали.

Появились полосы прокрутки? Давайте разберемся, благодаря чему это произошло.

Во-первых, если сравнить вызовы функций CreateWindowEx() в этой и предыдущей программах, то можно увидеть, что у окна появились два новых стиля - WS_HSCROLL и WS_VSCROLL. Эти стили определяют наличие у окна горизотнальной и вертикальной полос прокрутки соответственно. Первый шаг сделан. Этот шаг можно было бы сделать и по-другому, определив полосы прокрутки как дочерние окна, но о дочерних окнах мы будем говорить позже. Разница между полосами прокрутки, являющимися частью окна, и полосами прокрутки - дочерними окнами состоит в том, что дочерние окна имеют встроенный клавиатурный интерфейс, позволяющий воздействовать на полосу прокрутки с помощью клавиатуры. Встроенным полосам прокрутки, к сожалению, досталось только управление с помощью курсора мыши.

Теперь необходимо определить диапазон прокрутки, который определяет число шагов между крайними позициями бегунка (слайдера). По умолчанию для полос прокрутки, являющихся частью окна, этот диапазон определен от 0 до 100. Для того чтобы изменить диапазон прокрутки, необходимо вызвать функцию SetScrollRange(), которая в файле winuser.h определена следующим образом:

  WINUSERAPI BOOL WINAPI SetScrollRange(HWND hWnd, int nBar,
                                        int nMinPos, int nMaxPos,
                                        BOOL bRedraw);
Первый аргумент функции - хэндл окна, которому принадлежат полосы прокрутки. Второй аргумент определяет, для какой полосы прокрутки (вертикальной или горизонтальной) устанавливается диапозон. В данном случае этот аргумент может принимать значение SB_VERT или SB_HORZ, что определяет работу с вертикальной или горизонтальной полосой прокрутки. Третий и четвертый аргументы непосредственно указывают нижнюю и верхнюю границу диапозона прокрутки. Пятый аргумент представляет собой флаг, определяющий, нужно ли перерисовывать полосу прокрутки после определения диапозона. TRUE - полоса прокрутки перерисовывается, FALSE - перерисовка не нужна. Заметьте, что если диапазон прокрутки определен от 0 до 0, то полоса прокрутки становится невидимой. Это свойство используется и в приведенной выше программе. В том случае, когда размеры окна превышают размеры отображаемого bitmap'а, у полос прокрутки устанавливается диапазон от 0 до 0, следовательно, полоса прокрутки скрывается.

В данной случае с помощью функции SetScrollRange() диапазон прокрутки определен как разность между размером bitmap'а и размера окна по вертикали и по горизонтали, т.е. шаг полосы прокрутки соответствует одному пикселю.

Воздействовать на полосы прокрутки можно по-разному: во-первыхб можно щелкнуть клавишей мыши на стрелах, расположенных по краям полосы; во-вторых, можно щелкнуть на полосе выше или ниже слайдера. Наконец, можно перетащить слайдер на другое место. Все эти воздействия приводят к тому, что оконная функция окна, которому принадлежат полосы прокрутки, получает сообщение WM_VSCROLL (если действия производились вертикальной полосой) или WM_HSCROLL (реакция на воздействие на горизонтальную полосу).

Характер воздействия оконная функция может определить по параметрам сообщения. Младшее слово wParam, которое и определяет характер воздействия на полосу прокрутки, может принимать значения, прведенные в таблице:

Идентификаторы характеров воздействия на полосы прокрутки

Параметр | Значение | Описание
SB_LINEUP | 0 | Используется только с WM_VSCROLL, щелчок мышью на стрелке вверх, приводит к прокрутке на одну "строку" вверх
SB_LINELEFT | 0 | Используется только с WM_HSCROLL, щелчок мышью на стрелке влево, приводит к прокрутке на одну "колонку" влево
SB_LINEDOWN | 1 | Используется только с WM_VSCROLL, щелчок мышью на стрелке вниз, приводит к прокрутке на одну "строку" вниз
SB_LINERIGHT | 1 | Используется только с WM_HSCROLL, щелчок мышью на стрелке вправо, приводит к прокрутке на одну "колонку" вправо
SB_PAGEUP | 2 | Используется только с WM_VSCROLL, щелчок мышью на полосе прокрутки выше слайдера, приводит к прокрутке на одну "страницу" вверх
SB_PAGELEFT | 2 | Используется только с WM_HSCROLL, щелчок мышью на полосе прокрутки левее слайдера, приводит к прокрутке на одну "страницу" влево
SB_PAGEDOWN | 3 | Используется только с WM_VSCROLL, щелчок мышью на полосе прокрутки ниже слайдера, приводит к прокрутке на одну "страницу" вниз
SB_PAGERIGHT | 3 | Используется только с WM_HSCROLL, щелчок мышью на полосе прокрутки правее слайдера, приводит к прокрутке на одну "страницу" вправо
SB_THUMBPOSITION | 4 | Перетаскивание слайдера закончено, пользователь отжал клавишу мыши
SB_THUMBTRACK | 5 | Слайдер перетаскивается с помощью мыши, приводит к перемещению содержимого экрана
SB_TOP | 6 | Используется только с вертикальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавищу "Home"
SB_LEFT | 6 | Используется только с горизонтальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавишу "Home"
SB_BOTTOM | 7 | Используется только с вертикальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавищу "End"
SB_RIGHT | 7 | Используется только с горизонтальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавишу "End"
SB_ENDSCROLL | 8 | Пользователь отпустил клавишу мыши после удержания ее нажатой на стрелке или на полосе прокрутке

В таблице показано, что прокрутка при нажатии клавиши мыши в некоторых случаях производится на одну строку и одну страницу. В данном случае необходимо осознать, что понятие "строка" и "страница" ничего общего с текстовой и страницей не имеют. Этими понятиями я заменил условные единицы, на которые прокручивается изображение в окне. К примеру, в приведенной программе строка соответствует один пиксель, а понятие страницы вовсе не определено (что есть страница для картинки?).

Старшее слово wparam используется только в тех случаях, когда младшее слово wparam равно SB_THUMBPOSITION или SB_THUMBTRACK. В этих случаях оно хранит позицию слайдера. В остальных случаях это значение не используется.

В тех случаях, когда полосы прокрутки реализованы как дочерние окна, lparam содержит хэндл окна полосы прокрутки. Если полоса реализована как часть окна, этот параметр не используется.

После того, как мы зафиксировали факт произведенного с полосой прокрутки действия и характер действия, программа должна правильно отреагировать на него и при необходимости изменить позицию слайдера в соответствии с произведенным воздействием. Делается это с помощью обращения к функции SetScrollPos(), которая следующим образом описана в файле winuser.h:

   WINUSERAPI int WINAPI SetScrollPos(HWND hWnd, int nBar, int nPos, BOOL bRedraw);
Первый аргумент - это хэндл окна, содержащего полосу прокрутки (в этом случае, если полоса прокрутки реализована как часть окна), второй аргумент может принимать значение SB_VERT или SB_HORZ (об этих значениях говорилось выше), третий аргумент определяет, в какую позицию должен быть установлен слайдер. И наконец, четвертый аргумент определяет, нужно ли перерисовывать полосу прокрутки после установки слайдера. Если последний аргумент равен TRUE, то полоса прокрутки будет перерисована.

Для того чтобы в соответствии с новой позицией слайдера изменилось изображение в рабочей области, окну необходимо послать сообщение WM_PAINT, которое заставит окно перерисоваться. В программе, приведенной выше, сообщение WM_PAINT окну посылается с помощью вызова функции InvalidateRect(). Из этого следует, что код обработки сообщения WM_PAINT в оконной функции должен разрабатываться с учетом того, что содержимое окна может прокручиваться (скроллироваться).

И в заключение мне бы хотелось слегка посыпать голову пеплом. Любой хоть немного понимающий в программировании человек ужаснется, когда увидит, что я загружаю изображение из файла при каждой перерисовке окна (в программе, использующей функцию StretchBlt()). Это резко замедляет работу программы и занимает слишком много ресурсов. Но в данном случае целью было не написание программы, работающей оптимальным образом, а простая демонстрация того, что должна сделать программа для того, чтобы вывести на экран изображение.


Контекст Устройства и WM_PAINT

Я уже говорил, что в Windows окно само отвечает за перерисовку себя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT. Каким образом осуществляется перерисовка?

Обычно используют один из трех методов:

- Рабочая область может быть восстановлена, если ее содержимое формируется с помощью каких-либо вычислений;
- последовательность событий, формирующих рабочую область, может быть сохранена, а затем "проиграна" сколь угодно раз;
- можно создавать виртуальное окно и направлять весь вывод в виртуальное окно, а при получении основным окном сообщения WM_PAINT копировать содержимое виртуального окна в основное.

Думаю, что читатель догадался, что в качестве виртуального окна используется контекст в памяти. Как его копировать, мы уже знаем. Но как рисовать на нем?


Установка текущей позиции

Для установки текущей позиции используется функция MoveToEx(). В файле заголовков wingdi.h эта функция описывается следующим образом:

  WINGDIAPI BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT);
Первый аргумент - это контекст устройства, на котором мы будем рисовать, второй и третий - координаты точки, в которую мы устанавливаем текущую графическую позицию. Последний аргумент - указатель на структуру типа POINT, в которую функция запишет координаты старой текущей позиции. Структура типа POINT выглядит следующим образом:
  POINT struc
    ptX  dd ?
    ptY  dd ?
  POINT ends
Если при вызове функции указатель на структуру типа POINT равен NULL, то координаты старой текущей позиции не возвращаются.


Прорисовка одного пикселя

Прорисовать один пиксель в определенной позиции мы можем с помощью вызова функции SetPixel(), описанной в wingdi.h:

   WINGDIAPI COLORREF WINAPI SetPixel(HDC, int, int, COLORREF);
Первый три аргумента очевидны - контекст устройства вывода и координаты прорисовываемого пикселя. Но что такое COLORREF?

Здесь следует пояснить, что каждый пиксель на экране состоит из трех микроточек - красной, зеленой и синей. Каждая из этих микроточек может светится с интенсивностью от 0 (микроточка не светится) до 255 (максимальная яркость). Например, если светится только красная составляющая, то получаются цвета от темно-бордового (почти черного) до ярко красного. Комбинируя микроточки и их интенсивность, мы можем определить почти 17 миллионов цветов (будут ли они все поддерживаться на компьютере читателя, определяется видеоподсистемой компьютера). Обычно в таких случаях говорят об RGB-значениях цвета (red, green, blue - красный, зеленый, голубой).

Вернемся к COLORREF. Опять обратимся к заголовочному файлу, но на сей раз не к wingdi.h, а к windef.h:

  typedef DWORD   COLORREF;
Понятно, что COLORREF - это двойное слово. Оно кодируется следующим образом:
  0x00bbggrr
т.е. младший байт определяет интенсивность красного, второй - зеленого, третий - синего цвета.

При нормальном завершении функция возвращает предыдущее значение цвета пикселя. Если возвращаемое значение равно -1, то это говорит либо о возникновении ошибки, либо о том, что координаты пикселя вышли за пределы рабочей области окна.


Создание пера для рисования линий

Рисование графических примитивов производится с помощью перьев. В Windows'95 есть три предопределенных пера - черное (BLACK_PEN), белое (WHITE_PEN) и прозрачное (NULL_PEN). При создании окна по умолчанию ему присваивается черное перо. Хэндл каждого из них может быть получен с помощью функции GetStockObject(). Естественно, что программиста не может удовлетворитьь столь малое число перьев, поэтому для прорисовки линий можно воспользоваться пером, созданным в программе посредством вызова функции CreatePen(). Как всегда, обращаемся к файлам заголовков, в данном случае - к файлу wingdi.h:

  WINGDIAPI HPEN   WINAPI CreatePen(int, int, COLORREF);
Первый аргумент определяет стиль кисти. В wingdi.h эти стили описаны достаточно образно. Для того чтобы сохранить стиль этого описания (не путать со стилем кисти) я включил его третьим столбцом в таблице:

Возможные стили кисти

Стиль пера | Значение | Описание | Эффект
PS_SOLID | 0 | ______ | Сплошная линия
PS_DASH | 1 | ------ | Пунктирная линия
PS_DOT | 2 | ...... | Линия из точек
PS_DASHDOT | 3 | _._._._ | Штрих-пунктирная линия (тире-точка)
PS_DASHDOTDOT | 4 | _.._.._ | Штрих-пунктирная линия (тире-точка-точка)
PS_NULL | 5 |  | Прозрачное перо
PS_INSIDEFRAME | 6 |  | При рисовании замкнутой фигуры граница фигуры будет определятся по внешнему краю, а не по середине линии (если толщина пера более 1 пикселя)

Второй аргумент функции CreatePen() - толщина пера в логических единицах. Если этот аргумент равен 0, то толщина пера делается равной одному пикселю.

Третий аргумент - цвет чернил. Теперь для того, чтобы мы могли использовать наше перо, необходимо сделать его текущим в контексте устройства. Делается это уже давно знакомой нам функцией SelectObject(). После того, как мы отработаем с пером, необходимо удалить его, вызвав функцию DeleteObject().


Рисование линии

Нарисовать линию можно с помощью функции LineTo(). Она описана в файле wingdi.h:

  WINGDIAPI  BOOL WINAPI LineTo(HDC, int, int);
Первый аргумент - контекст устройства. Второй и третий аргументы - координаты точки, ДО КОТОРОЙ ОТ ТЕКУЩЕЙ ПОЗИЦИИ будет проведена линия. При успешной завершении функция возвращает TRUE.

Но здесь же возникает вопрос: где будет находиться текущая позиция после успешного выполнения функции? А будет она находиться там, где закончилась линия. Это сделано для того, чтобы легко можно было рисовать ломаные линии. В таком случае не нужно многократно вызывать функцию MoveToEx() для установления новой текущей позиции.


Рисование прямоугольника

Прямоугольник можно нарисовать, обратившись к функции Rectangle(). Ее описание содержится в файле wingdi.h:

  WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);
Аргумент первый понятен без объяснений - хэндл контекста устройства. Остальные аргументы - координаты верхнего левого и нижнего правого углов прямоугольника. TRUE возвращает при нормальном завершении операции. Прямоугольник автоматически заполняется цветом и способом, определяемым текущей кистью.


Рисование эллипса

Для рисования эллипса необходимо вызвать функцию Ellipse(), которая в wingdi.h описывается следующим образом:

  WINGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);
Первый аргумент - это, как всегда, контекст устройства. Для того чтобы понять, как определяется эллипс, предлагаю читателю обратиться к рисунку:

Определение аргумента функции Ellipse():
Ellipse

Как видно из рисунка, эллипс ограничен прямоугольником. Именно через координаты этого прямоугольника и определяется прорисовываемый эллипс. Второй и третий аргументы - координаты левого верхнего угла прямоугольника (на рисунке обозначены как UpX, UpY), четвертый и пятый аргументы - координаты нижнего правого угла (на рисунке обозначены как LowX, LowY).

Окружность является частным случаем эллипса. И в данном случае, если мы определим прямоугольник, у которого ширина равна высоте, т.е. квадрат, вместо эллипса получим окружность.

Как эллипс, так и окружность после прорисовки заполняются цветом и атрибутами текущей кисти.


Рисование прямоугольника с закругленными краями

Прямоугольник с закругленными краями рисуется с помощью функции RoundRect(). Из файла wingdi.h добываем ее описание.

  WINGDIAPI BOOL  WINAPI RoundRect(HDC, int, int, int, int, int, int);
Первые пять аргументов полностью идентичны аргументам функции Rectangle(). Последние два аргумента содержат ширину и высоту эллипса, определяющего дуги. После прорисовки прямоугольник закрашивается текущей кистью. В случае успешного завершения функция возвращает TRUE.


Рисование дуги и сектора эллипса

Возьмем из файла wingdi.h описание функции Arc(), которая используется для рисования дуги:

  WINGDIAPI BOOL  WINAPI Arc(HDC, int, int, int, int, int, int, int, int);
Первые пять аргументов полностью аналогичны аргументам функции Ellipse(). Непосредственно дуга определяется еще двумя точками. Первая - начало дуги - находится на пересечении эллипса, частью которого является дуга, и прямой, проходящей через центр прямоугольника и точку начала дуги. На рисунке начало дуги обозначено StartX, StartY. Вторая - конец дуги - определяется аналогично. Конец дуги обозначен EndX, EndY. Таким образом, для прорисовки дуги необходимо сначала определить точки StartX, StartY и EndX, EndY, после чего прорисовывать дугу. Дуга прорисовывается против часовой стрелки.

У функции Pie(), которая применяется для рисования сектора эллипса, набор аргументов и их обозначения абсолютно идентичны функции Arc().


Несколько слов о заполнении объектов

Как читатель уже знает, заполнение замкнутых графических объектов происходит с помощью текущей кисти. Программист может использовать предопределенную кисть, а может создать свою собственную, после чего сделать ее текущей с помощью функции SelectObject().

Простейшим видом кисти является так называемая сплошная кисть, которая создается с помощью функции CreateSolidBrush():

  WINGDIAPI HBRUSH  WINAPI CreateSolidBrush(COLORREF);
Единственный аргумент этой функции - цвет кисти (может, лучше сказать не кисти, а краски?).

Штриховая кисть создается с помощью функции CreateHatchBrush():

  WINGDIAPI HBRUSH  WINAPI CreateHatchBrush(int, COLORREF);
Первый аргумент этой функции - стиль штриховки. Возможные стили приведены в таблице.

Стили штриховки

Стиль штриховки | Значение | Описание | Эффект
HS_HORIZONTAL | 0 | ---- | Горизонтальная штриховка
HS_VERTICAL | 1 | ||||| | Вертикальная штриховка
HS_FDIAGONAL | 2 | \\\\\ | Наклонная слева направо штриховка
HS_BDIAGONAL | 3 | ///// | Наклонная справа налево штриховка
HS_CROSS | 4 | +++++ | Штриховка крестиком
HS_DIAGCROSS | 5 | xxxxx | Штриховка косым крестиком

Второй аргумент указывает цвет штриховки.

И наконец, с помощью функции CreatePatternBrush() мы можем создать кисть, которая при заполнении будет использовать bitmap. В wingdi.h она описана следующим образом:

  WINGDIAPI HBRUSH WINAPI CreatePatternBrush(HBITMAP);
Уже по типу аргумента видно, что единственным аргументом этой функции является хэндл bitmap'а.

Эти три функции при успешном завершении возвращают хэндл созданной кисти. В том случае, если произошла какая-то ошибка, возвращаемое значение равно NULL.


Демонстрационная программа

Текст программы:

.386
.model flat, stdcall
include win32.inc

extrn            BeginPaint:PROC
extrn            BitBlt:PROC
extrn            CreateCompatibleBitmap:PROC
extrn            CreateCompatibleDC:PROC
extrn            CreateHatchBrush:PROC
extrn            CreatePen:PROC
extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DeleteDC:PROC
extrn            DeleteObject:PROC
extrn            DispatchMessageA:PROC
extrn            Ellipse:PROC
extrn            EndPaint:PROC
extrn            ExitProcess:PROC
extrn            GetClientRect:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            GetStockObject:PROC
extrn            LineTo:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            MoveToEx:PROC
extrn            PatBlt:PROC
extrn            PostQuitMessage:PROC
extrn            Rectangle:PROC
extrn            RegisterClassA:PROC
extrn            SelectObject:PROC
extrn            SetPixel:PROC
extrn            ShowWindow:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

BITMAP struct
   bmType       dd ?
   bmWidth      dd ?
   bmHeight     dd ?
   bmWidthBytes dd ?
   bmPlanes     dw ?
   bmBitsPixel  dw ?
   bmBits       dd ?
BITMAP ends

SRCCOPY          = 00CC0020h
PATCOPY          = 00F00021h
TRUE             = 1
FALSE            = 0

.data

newhwnd          dd 0
msg              MSGSTRUCT
wc               WNDCLASS
Rect             RECT

hDC              dd ?
hCompatibleDC    dd ?
PaintStruct      PAINTSTRUCT
hCompatibleBitmap dd ?
hBitmap          dd ?
hOldBitmap       dd ?
hOldPen          dd ?
hOldBrush        dd ?

Pens             dd 2 dup(?)
Brushes          dd 2 dup(?)

hInstance        dd 0

szTitleName      db 'GraphDemo', 0
szClassName      db 'ASMCLASS32',0
x                dd ?
y                dd ?
p                dd ?

.code
;-----------------------------------------------------------------------------
start:
        call    GetModuleHandleA, 0
        mov     [hInstance], eax

; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW
        mov     [wc.clsLpfnWndProc], offset GraphDemoWndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInstance]
        mov     [wc.clsHInstance], eax

        call    LoadIconA, 0, IDI_APPLICATION
        mov     [wc.clsHIcon], eax

        call    LoadCursorA, 0 ,IDC_ARROW
        mov     [wc.clsHCursor], eax

        call    GetStockObject, WHITE_BRUSH
        mov     [wc.clsHbrBackground], eax

        mov     [wc.clsLpszMenuName], 0
        mov     [wc.clsLpszClassName], offset szClassName

        call    RegisterClassA, offset wc

        call    CreateWindowExA, 0,offset szClassName,offset szTitleName, \
                WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, \
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0, \
                [hInstance], 0

        mov     [newhwnd], eax

        call    ShowWindow, [newhwnd], SW_SHOWNORMAL
        call    UpdateWindow, [newhwnd]

msg_loop:
        call    GetMessageA, offset msg, 0, 0, 0
        .if     eax != 0
                call    TranslateMessage, offset msg
                call    DispatchMessageA, offset msg
                jmp     msg_loop
        .endif
        call    ExitProcess, [msg.msWPARAM]

;-----------------------------------------------------------------------------
GraphDemoWndProc    proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                    wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_PAINT
        je      wmpaint
        cmp     [wmsg], WM_DESTROY
        je      wmdestroy

        call    DefWindowProcA, [hwnd],[wmsg],[wparam],[lparam]
        jmp     finish

wmpaint:
        ;---------------------------------------
        call    CreatePen, 1,0,0FF0000h
        mov     [dword ptr Pens], eax
        call    CreatePen, 3,0,000000h
        mov     [dword ptr Pens+4], eax

        call    CreateHatchBrush, 1,00000FFh
        mov     [dword ptr Brushes], eax
        call    CreateHatchBrush, 2,0FF0080h
        mov     [dword ptr Brushes+4], eax
        ;---------------------------------------
        call    GetClientRect, [hwnd], offset Rect
        call    BeginPaint, [hwnd], offset PaintStruct
        mov     [hDC], eax
        call    CreateCompatibleDC, [hDC]
        mov     [hCompatibleDC], eax
        call    GetClientRect, [hwnd], offset Rect

        call    CreateCompatibleBitmap, [hDC],[Rect.rcRight],[Rect.rcBottom]
        mov     [hCompatibleBitmap], eax

        call    SelectObject, [hCompatibleDC],[hCompatibleBitmap]
        mov     [hOldBitmap], eax

        call    PatBlt, [hCompatibleDC],0,0,[Rect.rcRight], \
                [Rect.rcBottom], PATCOPY
        ;---------------------------------------
        mov     [x], 0
        mov     [y], 0
        mov     [p], 0
  @@lp2:
        call    SetPixel, [hCompatibleDC],[x],[y],00FF0000h
        .if     eax == -1
                mov   [x], 0
                add   [y], 10h
        .endif
        add     [x], 10h
        .if     eax == -1
                cmp   [p], -1
                jz    @@lp3
        .endif
        mov     [p], eax
        jmp  @@lp2
  @@lp3:
        ;---------------------------------------
        call    SelectObject, [hCompatibleDC],[dword ptr Pens]
        mov     [hOldPen], eax
        call    MoveToEx, [hCompatibleDC],[Rect.rcLeft],[Rect.rcTop],0
        call    LineTo, [hCompatibleDC],[Rect.rcRight],[Rect.rcBottom]

        call    SelectObject, [hCompatibleDC],[dword ptr Pens+4]
        call    MoveToEx, [hCompatibleDC],[Rect.rcRight],[Rect.rcTop],0
        call    LineTo, [hCompatibleDC],[Rect.rcLeft],[Rect.rcBottom]
        call    SelectObject, [hCompatibleDC],[hOldPen]
        ;=======================================
        call    SelectObject, [hCompatibleDC],[dword ptr Brushes]
        mov     [hOldBrush], eax
        call    Rectangle, [hCompatibleDC],100,75,450,200

        call    SelectObject, [hCompatibleDC],[dword ptr Brushes+4]
        call    Ellipse, [hCompatibleDC],50,50,200,100
        call    SelectObject, [hCompatibleDC],[hOldBrush]
        ;=======================================
        call    BitBlt, [hDC],[PaintStruct.PSrcPaint.rcLeft], \
                [PaintStruct.PSrcPaint.rcTop], \
                [PaintStruct.PSrcPaint.rcRight], \
                [PaintStruct.PSrcPaint.rcBottom], \
                [hCompatibleDC], \
                [PaintStruct.PSrcPaint.rcLeft], \
                [PaintStruct.PSrcPaint.rcTop], \
                SRCCOPY

        call    DeleteObject, [dword ptr Pens]
        call    DeleteObject, [dword ptr Pens+4]
        call    DeleteObject, [dword ptr Brushes]
        call    DeleteObject, [dword ptr Brushes+4]

        call    SelectObject, [hCompatibleDC], [hOldBitmap]
        call    DeleteObject, [hCompatibleBitmap]
        call    DeleteDC, [hCompatibleDC]
        call    EndPaint, [hwnd],offset PaintStruct
        mov     eax, 0
        jmp     finish

wmdestroy:
        call    DeleteObject, [hBitmap]
        call    PostQuitMessage, 0
        mov     eax, 0
finish:
        ret
GraphDemoWndProc          endp

ends
end start


Коротко О Win32

Приложения Win32 работают в защищенном режиме на процессорах 80386 и выше

Приложение может адресовать память вплоть до 4 Гбайт

Формат исполняемого файла - PE(Portable Executable)

Windows выполняет каждое 32-разрядное приложение в отдельном виртуальном пространстве.

Основные используемые библиотеки:
KERNEL32.DLL - содержит функции управления памятью, загрузки/удаления программ и обеспечивает непосредственную поддержку выполнения программ
USER32.DLL - сосредоточены функции управления окнами (создание окна, изменение его размеров, перемещение, удаление), другими элементами интерфейса, а также функции ввода с клавиатуры и работы с мышью
GDI32.DLL - обеспечивает взаимодействие с графическими устройствами (в том числе с экраном, принтерами и плоттерами)


Создаем окно

     Основные шаги при создании окна:
    1. Получить дискриптор Вашей программы(обязательно)
    2. Получить указатель на командную строку(не обязательно)
    3. Зарегистрировать класс окна(не требуется, если Вы используете
        встроенный тип окна, например MessageBox)
    4. Создать окно(обязательно)
    5. Показать окно(если хотите немедленно показать окно)
    6. Обновить окно
    7. Образовать бесконечный цикл, обрабатывающий сообщения окна
    8. Если есть сообщения, то обработать специализированной функцией
        ответственной за окно
    9. Выйти из программы, если пользователь закрыл окно

Разберем простую программу, которая выводит только окно.
Я взял пример программы Wap32.asm из пакета TASM и несколько упостил ее.

.386
.model flat, stdcall
include win32.inc

Файл win32.inc содержит некоторые нужные константы и структуры

extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DispatchMessageA:PROC
extrn            ExitProcess:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            ShowWindow:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

.data

newhwnd          dd 0
msg              MSGSTRUCT   <?>
wc               WNDCLASS    <?>
hInst            dd 0
szTitleName      db 'Win32 Assembly Program',0
szClassName      db 'ASMCLASS32',0

.code
start:

        push    0
        call    GetModuleHandleA
        mov     [hInst], eax

Получим дискриптор программы.
Далее инициализируем структуру WndClass для регистрации окна

        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS

clsStyle - определяет стиль класса

        mov     [wc.clsLpfnWndProc], offset WndProc

clsLpfnWndProc - указывает на процедуру окна

        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInst]
        mov     [wc.clsHInstance], eax

clsHInstance - содержит дискриптор программы

        push    IDI_APPLICATION
        push    0
        call    LoadIconA
        mov     [wc.clsHIcon], eax

        push    IDC_ARROW
        push    0
        call    LoadCursorA
        mov     [wc.clsHCursor], eax

        mov     [wc.clsHbrBackground], COLOR_WINDOW + 1
        mov     dword ptr [wc.clsLpszMenuName], 0
        mov     dword ptr [wc.clsLpszClassName], offset szClassName

clsLpszClassName - определяет имя класса окна

        push    offset wc
        call    RegisterClassA

Создаем окно:

        push    0
        push    [hInst]                  ; дискриптор окна
        push    0
        push    0
        push    CW_USEDEFAULT            ; высота
        push    CW_USEDEFAULT            ; ширина
        push    CW_USEDEFAULT            ; y
        push    CW_USEDEFAULT            ; x
        push    WS_OVERLAPPEDWINDOW      ; стиль
        push    offset szTitleName       ; заголовок окна
        push    offset szClassName       ; имя класса
        push    0                        ; дополнительный стиль

        call    CreateWindowExA

        mov     [newhwnd], eax

newhwnd - дискриптор окна
Покажем окно:

        push    SW_SHOWNORMAL
        push    [newhwnd]
        call    ShowWindow

Обновим окно:

        push    [newhwnd]
        call    UpdateWindow

Создаем цикл для обработки сообщений окна

msg_loop:
        push    0
        push    0
        push    0
        push    offset msg
        call    GetMessageA

        cmp     ax, 0
        je      end_loop

        push    offset msg
        call    TranslateMessage

        push    offset msg
        call    DispatchMessageA

        jmp     msg_loop

end_loop:

выход из программы:

        push    [msg.msWPARAM]
        call    ExitProcess

Процедура окна:

WndProc          proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                 wparam:DWORD, lparam:DWORD

Win32 требует, чтобы EBX, EDI, и ESI были сохранены

        cmp     [wmsg], WM_DESTROY
        je      wmdestroy

        push    [lparam]
        push    [wparam]
        push    [wmsg]
        push    [hwnd]
        call    DefWindowProcA
        jmp     finish

wmdestroy:
        push    0
        call    PostQuitMessage
        mov     eax, 0
finish:
        ret
WndProc          endp
ends
end start

Программу можно взять здесь

На первый взгляд кажется, что слишком много написано для простой программы. На самом же деле писать все полностью не нужно, достаточно написать файл один раз, а потом использовать его как шаблон для своих новых программ. Можно создать объектный файл и использовать его как загрузочный код, а писать только процедуру окна(WinProc).


Рисование графических примитивов

Контекст устройства и WM_PAINT
Установка текущей позиции
Прорисовка одного пикселя
Создание пера для рисования линий
Рисование линии
Рисование прямоугольника
Рисование эллипса
Рисование прямоугольника с закругленными краями
Рисование дуги и сектора эллипса
Несколько слов о заполнении объекта
Демонстрационная программа


Hello, world! ;)

Это, наверное, самая простая программа.
Используются всего две функции:
MessageBoxA из USER32.DLL и
ExitProcess из KERNEL32.DLL.
Суффикс A у MessageBox означает, что функция работает с однобайтовыми
ANSI строками. Для двубайтных Unicode строк - суффикс W, e.g. MessageBoxW.
Сама функция MessageBox выводит сообщение в отдельном оконе, ее прототип:

int MessageBox(
  HWND hWnd,
  LPCSTR lpText,
  LPCSTR lpCaption,
  UINT uType
);

Параметры:
hWnd - идентификатор окна, из которого вызывается MessageBox
lpText - указатель на null-terminated строку, содержущая текст сообщения
lpCaption - указатель на null-terminated строку, содержущая заголовок сообщения
uType - вид диалогового окна

Функция ExitProcess заканчивает процесс и все образованные процессы из этого процесса.

VOID ExitProcess(
  UINT uExitCode,
);

uExitCode - код выхода

Параметры функции передаются по следующей схеме:

    AnyFunction(first_param, second_param, third_param);

    push    [third_param]
    push    [second_param]
    push    [first_param]
    call    AnyFunction
или
    call    AnyFunction, [first_param], [second_param], [third_param]

Вот сама программа:

.386
.model flat

extrn            MessageBoxA:proc
extrn            ExitProcess:proc

.data

mb_text          db 'Hello, World!',0
mb_caption       db 'My first program',0

.code
start:
                 push   0
                 push   offset mb_caption
                 push   offset mb_text
                 push   0
                 call   MessageBoxA
                 push   0
                 call   ExitProcess

ends
end start

Программу можно взять здесь


Icon

Для использования собственной иконки нужно создать файл ресурса:

-->Begin icon.rc <--

  ICON_1 ICON 180.ico
  ICON_SM ICON 163.ico

-->End of icon.rc<--

Затем icon.rc откомпилировать с помощью какого-нибудь компилятора ресурсов(res-файл должен быть 32-bit Windows compatible). Возьмем brcc32.exe:

  brcc32 icon.rc
Получим icon.res - понадобится при линковании.

WNDCLASSEX struct
  clSize           dword    ?
  clStyle          dword    ?
  clLpfnWndProc    dword    ?
  clCbClsExtra     dword    ?
  clCbWndExtra     dword    ?
  clHInstance      dword    ?
  clHIcon          dword    ?
  clHCursor        dword    ?
  clHbrBackground  dword    ?
  clLpszMenuName   dword    ?
  clLpszClassName  dword    ?
  clHIconSm        dword    ?
WNDCLASSEX ends

clHIcon: Содержит дискриптор иконки получаемый после вызова LoadIcon.
clHIconSm: Тоже самое для маленькой иконки, если null то используется hIcon.

--------------------------------------------------
.data
  szIconName       db 'ICON_1', 0
  szIconSmName     db 'ICON_SM', 0
  ...
--------------------------------------------------
.code
  ...
  push    offset szIconName
  push    [hInst]
  call    LoadIconA
  mov     [wc.clHIcon], eax

  push    offset szIconSmName
  push    [hInst]
  call    LoadIconA
  mov     [wc.clHIconSm], eax
  ...
  регистируем класс, создаем окно...
---------------------------------------------------

При линковании нужно указать файл ресурса:

  tlink32 [options] my.obj,my.exe,,,,my.res

Полный текст программы: cr_icon.zip


Оглавление

  • Коротко о Win32
  • Структура программы
  • Hello, world!
  • Создаем окно
  • Контекст устройства
  • Полосы проктурки
  • Рисование графических примитивов
  • Ресурсы


Menu 1

Общий вид ресурса меню:

MyMenu  MENU
{
  [menu list]
}
или
MyMenu  MENU
BEGIN
  [menu list]
END
Поле [menu list] может быть MENUITEM или POPUP. Синтаксис MENUITEM:
  MENUITEM "&text", ID [,options]
&text - название пункта меню, амперсанд ставится перед подчеркиваемым символом.
ID - уникальный идентификатор, будет передоваться процедуре окна(wparam), если этот пункт меню выберут.
Параметры options:
GRAYED - Недоступный пункт меню, текст выводится серым цветом.
INACTIVE - Тоже недоступный пункт меню, но текст нормальный.
MENUBREAK - Этот пункт и следующие пункты появляются в новой строке меню.
HELP - Пункт меню и следующие пункты выводятся в правой части строки меню.

Синтаксис POPUP:
POPUP "&text" [,options]
{
  [menu list]
}
MENUITEM SEPARATOR - разделитель для отделения элементов меню.
Далее при регистрации класса в поле clsLpszMenuName указать на строку с названием ресурса меню.
Пример:
-- Begin menu.rc --
#define IDM_1 1
#define IDM_2 2
#define IDM_EXIT 3
#define IDM_ABOUT 4
MENU_1 MENU
    BEGIN
      POPUP "File"
        BEGIN
             MENUITEM "MenuItem &GRAYED",  IDM_1, GRAYED
             MENUITEM "MenuItem &INACTIVE",  IDM_2, INACTIVE
             MENUITEM SEPARATOR
             MENUITEM "&Exit",  IDM_EXIT
        END
      MENUITEM "&About",  IDM_ABOUT
    END
-- End of menu.rc --

-- Begin cr_menu1.asm --
.386
.model flat, stdcall
include win32.inc

extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DispatchMessageA:PROC
extrn            ExitProcess:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            MessageBoxA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            ShowWindow:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

IDM_1     EQU 1
IDM_2     EQU 2
IDM_EXIT  EQU 3
IDM_ABOUT EQU 4

.data

newhwnd          dd 0
msg              MSGSTRUCT
wc               WNDCLASS

hInst            dd 0

szMenuName       db 'MENU_1', 0  <<название ресурса>>
szTitleName      db 'Win32 Assembly Program', 0
szClassName      db 'ASMCLASS32',0
mb_message       db 'Win32 Assembler: Menu',0
mb_title         db 'About',0

.code
;----------------------------------------------------------------------
start:

        push    0
        call    GetModuleHandleA        ; get hmod (in eax)
        mov     [hInst], eax            ; hInstance is same as HMODULE
                                        ; in the Win32 world
; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
        mov     [wc.clsLpfnWndProc], offset WndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInst]
        mov     [wc.clsHInstance], eax

        push    IDI_APPLICATION
        push    0
        call    LoadIconA
        mov     [wc.clsHIcon], eax

        push    IDC_ARROW
        push    0
        call    LoadCursorA
        mov     [wc.clsHCursor], eax

        mov     [wc.clsHbrBackground], COLOR_WINDOW + 1
        mov     dword ptr [wc.clsLpszMenuName], offset szMenuName
        <<указываем смещение на строку с названием ресурса>>
        mov     dword ptr [wc.clsLpszClassName], offset szClassName

        push    offset wc
        call    RegisterClassA

        push    0                        ; lpParam
        push    [hInst]                  ; hInstance
        push    0                        ; menu
        push    0                        ; parent hwnd
        push    CW_USEDEFAULT            ; height
        push    CW_USEDEFAULT            ; width
        push    CW_USEDEFAULT            ; y
        push    CW_USEDEFAULT            ; x
        push    WS_OVERLAPPEDWINDOW      ; Style
        push    offset szTitleName       ; Title string
        push    offset szClassName       ; Class name
        push    0                        ; extra style

        call    CreateWindowExA

        mov     [newhwnd], eax

        push    SW_SHOWNORMAL
        push    [newhwnd]
        call    ShowWindow

        push    [newhwnd]
        call    UpdateWindow

msg_loop:
        push    0
        push    0
        push    0
        push    offset msg
        call    GetMessageA

        cmp     ax, 0
        je      end_loop

        push    offset msg
        call    TranslateMessage

        push    offset msg
        call    DispatchMessageA

        jmp     msg_loop

end_loop:
        push    [msg.msWPARAM]
        call    ExitProcess

;--------------------------------------------------------------
WndProc          proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_DESTROY
        je      wmdestroy
        cmp     [wmsg], WM_COMMAND
        je      wmcommand

        push    [lparam]
        push    [wparam]
        push    [wmsg]
        push    [hwnd]
        call    DefWindowProcA
        jmp     finish

wmdestroy:
        push    0
        call    PostQuitMessage
        mov     eax, 0
        jmp     finish

wmcommand:
        mov     eax, [wparam]
        cmp     eax, IDM_ABOUT
        jne     wmnext
        push   0
        push   offset mb_title
        push   offset mb_message
        push   0
        call   MessageBoxA
wmnext:
        cmp     eax, IDM_EXIT
        je      wmdestroy

finish:
        ret
WndProc          endp
ends
end start
-- End of cr_menu1.asm --
Текст программы: cr_menu1.zip


Menu 2

Меню можно добавить в процессе создания окна, использовав при этом аргумент hMenu - дискриптор меню. Его возращает функция LoadMenu.

==>Begin cr_menu2.asm<==
.386
.model flat, stdcall
include win32.inc

extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DispatchMessageA:PROC
extrn            ExitProcess:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            LoadMenuA:PROC
extrn            MessageBoxA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            ShowWindow:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

IDM_1     EQU 1
IDM_2     EQU 2
IDM_EXIT  EQU 3
IDM_ABOUT EQU 4

.data

newhwnd          dd 0
hMenu            dd ?
msg              MSGSTRUCT
wc               WNDCLASS

hInst            dd 0

szMenuName       db 'MENU_1', 0
szTitleName      db 'Win32 Assembly Program', 0
szClassName      db 'ASMCLASS32',0
mb_message       db 'Win32 Assembler: Menu',0
mb_title         db 'About',0

.code
;-----------------------------------------------------------------------------
start:

        push    0
        call    GetModuleHandleA        ; get hmod (in eax)
        mov     [hInst], eax            ; hInstance is same as HMODULE
                                        ; in the Win32 world
; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
        mov     [wc.clsLpfnWndProc], offset WndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInst]
        mov     [wc.clsHInstance], eax

        push    IDI_APPLICATION
        push    0
        call    LoadIconA
        mov     [wc.clsHIcon], eax

        push    IDC_ARROW
        push    0
        call    LoadCursorA
        mov     [wc.clsHCursor], eax

        push    offset szMenuName
        push    [hInst]
        call    LoadMenuA
        mov     [hMenu], eax

        mov     [wc.clsHbrBackground], COLOR_WINDOW + 1
        mov     dword ptr [wc.clsLpszMenuName], 0
        mov     dword ptr [wc.clsLpszClassName], offset szClassName

        push    offset wc
        call    RegisterClassA

        push    0                        ; lpParam
        push    [hInst]                  ; hInstance
        push    [hMenu]                  ; menu
        push    0                        ; parent hwnd
        push    CW_USEDEFAULT            ; height
        push    CW_USEDEFAULT            ; width
        push    CW_USEDEFAULT            ; y
        push    CW_USEDEFAULT            ; x
        push    WS_OVERLAPPEDWINDOW      ; Style
        push    offset szTitleName       ; Title string
        push    offset szClassName       ; Class name
        push    0                        ; extra style

        call    CreateWindowExA

        mov     [newhwnd], eax

        push    SW_SHOWNORMAL
        push    [newhwnd]
        call    ShowWindow

        push    [newhwnd]
        call    UpdateWindow

msg_loop:
        push    0
        push    0
        push    0
        push    offset msg
        call    GetMessageA

        cmp     ax, 0
        je      end_loop

        push    offset msg
        call    TranslateMessage

        push    offset msg
        call    DispatchMessageA

        jmp     msg_loop

end_loop:
        push    [msg.msWPARAM]
        call    ExitProcess

;-----------------------------------------------------------------------------
WndProc          proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_DESTROY
        je      wmdestroy
        cmp     [wmsg], WM_COMMAND
        je      wmcommand

        push    [lparam]
        push    [wparam]
        push    [wmsg]
        push    [hwnd]
        call    DefWindowProcA
        jmp     finish

wmdestroy:
        push    0
        call    PostQuitMessage
        mov     eax, 0
        jmp     finish

wmcommand:
        mov     eax, [wparam]
        cmp     eax, IDM_ABOUT
        jne     wmnext
        push   0
        push   offset mb_title
        push   offset mb_message
        push   0
        call   MessageBoxA
wmnext:
        cmp     eax, IDM_EXIT
        je      wmdestroy

finish:
        ret
WndProc          endp
ends
end start
==>End of cr_menu2.asm<==
Текст программы: cr_menu2.zip


Menu 3

Меню можно создать динамически, без создания ресурса.
Программа cr_menu3.asm имеет в памяти шаблон меню, потом из шаблона создаст "объект" меню с помощью LoadMenuIndirect, которая вернет дискриптор меню. Меню может затем быть добавлено при создании окна, устанавливая дискриптор меню в соответствующий аргумент CreateWindowEx.

Вы можете также создать окно без меню и потом дополнить используя SetMenu.
Эта функция API может также использоваться для изменения меню. Меню могут удалить используя NULL вместо дискриптора меню.

        push    offset MenuTemplate ;шаблон меню
        call    LoadMenuIndirectA
        mov     [hMenu], eax        ;hMenu - menu handle
Шаблон меню состоит из заголовка и списка пунктов меню.
MENUEX_TEMPLATE_HEADER struct
    wVersion       dw ?
    wOffset        dw ?
    dwHelpId       dd ?
MENUEX_TEMPLATE_HEADER ends
wVersion - версия шаблона, должна быть равна 1.
wOffset - смещение относительно конца этого поля. Если первое определение пункта меню следует за dwHelpId, то wOffset должен быть 4.
dwHelpId - идентификатор подсказки.

Заголовок должен выравниваться по DWORD границе.
Пункт меню:
 typedef struct {
     DWORD  dwType;
     DWORD  dwState;
     UINT   uId;
     WORD   bResInfo;
     WCHAR  szText[1];
     // DWORD dwHelpId;
 } MENUEX_TEMPLATE_ITEM;
dwType - тип пункта меню.
dwState - состояние пункта меню.
UINT - уникальный идентификатор.
bResInfo - величина введенная для обозначения последнего пункта меню, подменю...
szText - текст пункта меню. Это Unicode строка оканчивающаяся 0, выравнивается по WORD границе.
dwHelpId - идентификатор подсказки. Нужен только при создании POPUP меню.

Пример:
MenuTemplate      MENUEX_TEMPLATE_HEADER<1,4,0>

                  ; &File
                  dd MFT_STRING  ;     Type
                  dd MFS_ENABLED ;     State
                  dd 0           ;     Id
                  dw MFR_POPUP   ;     ResInfo
                  dw '&','F','i','l','e',0,0   ;     Text
                  dd 0           ;     HelpId

                  ; MenuItem &GRAYED
                  dd MFT_STRING  ;     Type
                  dd MFS_GRAYED  ;     State
                  dd IDM_1       ;     Id
                  dw 0           ;     ResInfo
                  dw 'M','e','n','u','I','t','e','m',' '   ; Text
                  dw '&','G','R','A','Y','E','D',0
                  ; dd 0 - HelpId only with popup menu

                  ; MenuItem &DISABLED
                  dd MFT_STRING  ;     Type
                  dd MF_DISABLED ;     State
                  dd IDM_2       ;     Id
                  dw 0           ;     ResInfo
                  dw 'M','e','n','u','I','t','e','m',' '   ; Text
                  dw '&','D','I','S','A','B','L','E','D',0
                  ; dd 0         ;     HelpId

                  ; ------------------
                  dd MFT_SEPARATOR,0,0
                  dw 0,0

                  ; E&xit
                  dd MFT_STRING  ;     Type
                  dd MFS_ENABLED ;     State
                  dd IDM_EXIT    ;     Id
                  dw MFR_END     ;     ResInfo
                  dw 'E','&','x','i','t',0,0    ; Text
                  ; dd 0         ;     HelpId

                  ; &About
                  dd MFT_STRING  ;     Type
                  dd MFS_ENABLED ;     State
                  dd IDM_ABOUT   ;     Id
                  dw MFR_END     ;     ResInfo
                  dw '&','A','b','o','u','t',0    ; Text
                  ;  dd 0        ;     HelpId

Полный текст: cr_menu3.zip


Menu 4

Установить новое меню можно с помощью функции SetMenu, указав ей дескриптор нового меню.
Вот пример:

  push    offset szMenuName2 ;новое меню
  push    [hInst]
  call    LoadMenuA ;получаем hMenu
;eax - hMenu
  push    eax
  push    [hwnd]    ; handle текущего окна
  call    SetMenu   ; устанавливаем меню
Для удаления меню нужно использовать DestroyMenu. В отличие от SetMenu с hMenu=NULL, DestroyMenu освобождает память.
  push    [hwnd]
  call    GetMenu   ; получим hMenu
;eax - hMenu
  push    eax
  call    DestroyMenu

  push    [hwnd]
  call    DrawMenuBar ;перерисуем меню, точнее очистим

Полный текст: cr_menu4.zip


Ресурсы

Что такое ресурсы
Ресурсы стандартные и нестандартные
Подключение ресурсов к исполняемому файлу
Подключение меню к окну
Диалоговые окна и их элементы
Модальные и немодальные диалоги
Кнопки, списки и прочее
Создание диалогового окна
Кнопки
Icon
Menu 1
Menu 2
Menu 3
Menu 4


Структура

Основная структура Win32 программы:

  .386
  .model flat
  .data
    ;<initialized data>
  .data?
    ;<uninitialized data>
  .code
  <label>:
    ;<code>
  end <label>

.386 означает, что набор инструкций для 80386 процессора, можно использовать и выше(.486 .586).
Модель памяти используется только FLAT.


Кирпичики

 Кирпичиками я называю небольшие исходники, которые, будучи однажды сделанными, кочуют из программы в программу. Таких кирпичиков скапливается немало у любого программиста.
Кирпичик 1 VB5.0 | Печатает в поле прописью сумму, набранную цифрами в другом поле. Задачка встречается при создании бухгалтерских программ. В приведенном конкретном исходнике сумма цифрами вводится в Text7. После ухода из этого поля по клавише Tab в Text4 появляется сумма прописью. Сумма может вводится в виде: 1457= или 1457-40
Кирпичик 2 VBA | Та же программка, с небольшими изменениями, успешно работает в VBA в среде приложения Excel 97. Там удобнее ее использовать не как макрос, а как новую функцию Converter, тогда данные будут изменяться автоматически, без вызова макроса вручную. Изменением типа данных в ячейке можно разрешать или запрещать вывод копеек в конкретную ячейку, без изменения программки. В этом варианте сумма может вводиться в виде 1457 или 1457,40
Кирпичик 3 VB5.0 | Перекодировкa текста DOS (866 кодовая таблица) в Windows (1251). Код можно вставить в любую программу для просмотра текстового файла DOS в NotePad.
Кирпичик 4 VB5.0 | Пример изъятия из текстового файла DOS нужных фрагментов и преобразования их в таблицу базы данных MS Access. Задачка часто встречается при использовании текстовых отчетов, созданных в DOS в качестве входных документов для приложения на VB. В этом примере кода входной файл, считываемый с дисковода A, представляет собой набор столбцов немного плавающих по ширине и содержащих цифровые значения. В отдельных строках располагаются названия объектов, которым соответствуют считываемые значения.
Кирпичик 5 VB5.0 | Простенький пример печати бланка отчета без применения Crystal Report. Такой способ вывода на печать более трудоемок, но зато результат в меньшей степени зависит от марки принтера. Mожно использовать даже Epson FX-1000, который обычно не справляется с текстами Word 97, если применены таблицы или иные графические элементы.
Кирпичик 6 VB5.0 | Еще один пример печати отчета без применения Crystal Report. Интересен тем, что во втором столбце печатаемой таблицы могут быть длинные строки, в которых надо переносить слова не разрезая, но нумерация в первом столбце должна появляться один раз. Поскольку отчеты могут быть различны по объему, организована постраничная печать с ориентацией на количество строк, выдаваемых не лучшим принтером. В отличие от предыдущего примера здесь применены относительные координаты позиций печати. Тема печати отчетов заслуживает внимания и в ближайшем будущем я подробнee остановлюсь на проблемах печати в отдельной статье.
Кирпичик 7 VB5.0 | Пример запроса SQL для вывода некоторых полей таблицы базы данных MS Access в DBGrid, по параметру, выбираемому в DBCombo.
Кирпичик 8 VB3.0-VB5.0 | Функция Delimit(No,Text,Delimiters) делит Text на элементы, разделенные строкой символов Delimiters (которая может быть любой длины). Если параметр No=0, функцией возвращается количество элементов (Integer), при других значениях - получившаяся строка, отделенная первым встретившимся элементом Delimiters (String). Здесь же некоторые примеры использования функции.
Кирпичик 9 VB3.0-VB5.0 | Функция Separate делает примерно тоже, но есть несколько отличий: 1. При любом No возвращается String 2. Разделитель всегда состоит из одного символа, но их может быть несколько, например если Delimiters=",;-/", то в качестве разделителей будет принят любой из символов ",", ";", "-", "/" (в Delimit разделителем была бы строка ",;-/").
Кирпичик 10 VB3.0-VB5.0 | Функция fmtCurrency преобразовывает число к денежному формату: разделитель - точка, два знака после точки.
Кирпичик 11 VB3.0-VB5.0 | Этот кирпичик аналогичен Кирпичику 1. Впрочем, у него есть более красивое решение. Функция IntTxt печатает в поле прописью сумму, набранную цифрами в другом поле без названия денежных единиц. Используется совместно с кирпичиком 12.
Кирпичик 12 VB3.0-VB5.0 | Функция LetterSum является дополнением к кирпичику 11. Печатает названия денежных единиц.
Кирпичик 13 VB3.0-VB5.0 | Функция HyphText, решающая задачу размещения многострочного текста в PictureBox с организацией переноса слов.
Кирпичик 14 VB5.0 | Еще один вариант процедуры "Сумма - прописью". Изящное и компактное решение - красивая работа !
Кирпичик 15 VB5.0 | Выводит список шрифтов, доступных объекту Printer на вашей машине.
Кирпичик 16 VB5.0 | Операции с файлами. Простые процедуры, которые могут быть полезны начинающим.
Кирпичик 17 VB5.0 | Модифицированная и подправленная процедура печати отчета из DBGrid (как ей пользоваться - см. статью о печати отчетов). Добавлен перенос слов в длинной строке. Добавлены более-менее приличные комментарии. По-видимому изменения будут продолжаться, поскольку подобные задачки постоянно встречаются мне в работе.
Кирпичик 18 VB5.0 | Процедура, решающая очень актуальную при выводе отчетов на принтер задачу. Определяет границы недоступных для печати зон по краям листа.
Кирпичик 19 VB5.0 | Пример отправки почты из VB-приложения без привлечения почтовых программ.
Кирпичик 20 VB5.0 | Пример отправки почты с аттачментом из VB-приложения без привлечения почтовых программ.
Кирпичик 21 VB5.0 | Модифицированная и подправленная процедура печати отчета из MSFlexGrid (как ей пользоваться - см. статью о печати отчетов).
Кирпичик 22 VB5-6 | Модуль, содержащий функции для работы с треем.
Кирпичик 23 VB5-6 | Универсальный класс, который позволяет изменять вид тулбара, читать данные из *.ini и записывать в него, проигрывать wav, запускать IE с web-адресом, отслеживать ввод с клавиатуры определенного слова. Для создания *.dll исходный код полностью вставляется в открытый проект ActiveX и компилируется.


Кирпичик 1

 Private Sub Text7_LostFocus()
    Dim n(12) As Byte
    Dim k As String, s As Byte, i As Byte
    On Error GoTo ErrorInput

    k = Text7.Text
    If InStr(k, "=") = Len(k) Then
        i = Len(k) - 1
        s = i
        If s <= 12 Then
            If Left(k, 1) > 0 Then GoTo 10
        End If
    End If
    If InStr(k, "-") = Len(k) - 2 Then
        i = Len(k) - 3
        s = i
        If s <= 12 Then
            If Left(k, 1) > 0 Then GoTo 10
            If Left(k, s) = 0 Then GoTo 10
        End If
    End If
    GoTo ErrorInput

10  For i = 1 To s
    n(i) = Mid(k, s - i + 1, 1)
    Next i
    i = s

    If i = 1 Then GoTo 50
    If i = 2 Then GoTo 60
    If i = 3 Then GoTo 70
    If i = 4 Then GoTo 80
    If i = 5 Then GoTo 60
    If i = 6 Then GoTo 70
    If i = 7 Then GoTo 50
    If i = 8 Then GoTo 60
    If i = 9 Then GoTo 70
    If i = 10 Then GoTo 50
    If i = 11 Then GoTo 60
    If i = 12 Then GoTo 70
    If i > 12 Then GoTo ErrorInput

20  If i
= 0 Then Exit Sub
If i = 1 Then GoTo 100
If i = 2 Then GoTo 200
If i = 3 Then GoTo 300
If i = 4 Then GoTo 400
If i = 5 Then GoTo 200
If i = 6 Then GoTo 300
If i = 7 Then GoTo 100
If i = 8 Then GoTo 200
If i = 9 Then GoTo 300
If i = 10 Then GoTo 100
If i = 11 Then GoTo 200

300 If Mid(k,
s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " сто"
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " двести"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " триста"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " четыреста"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " пятьсот"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " шестьсот"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " семьсот"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " восемьсот"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " девятьсот"

i = i - 1
GoTo 20

200 If Mid(k,
s - i + 1, 1) = 1 Then GoTo 500
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " двадцать"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " тридцать"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " сорок"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " пятьдесят"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " шестьдесят"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " семьдесят"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " восемьдесят"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " девяносто"
i = i - 1
GoTo 20

100 If Mid(k,
s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " один"
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " два"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " три"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " четыре"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " пять"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " шесть"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " семь"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " восемь"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " девять"
If i = 1 Then GoTo 701
If i = 7 Then GoTo 702
If i = 10 Then GoTo 703

400 If Mid(k,
s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " одна"
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " две"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " три"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " четыре"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " пять"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " шесть"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " семь"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " восемь"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " девять"
If i = 4 Then GoTo 704
GoTo 20

500 If Mid(k,
s - i + 1, 2) = 11 Then Text4.Text = Text4.Text + " одиннадцать"
If Mid(k, s - i + 1, 2) = 12 Then Text4.Text = Text4.Text + " двенадцать"
If Mid(k, s - i + 1, 2) = 13 Then Text4.Text = Text4.Text + " тринадцать"
If Mid(k, s - i + 1, 2) = 14 Then Text4.Text = Text4.Text + " четырнадцать"
If Mid(k, s - i + 1, 2) = 15 Then Text4.Text = Text4.Text + " пятнадцать"
If Mid(k, s - i + 1, 2) = 16 Then Text4.Text = Text4.Text + " шестнадцать"
If Mid(k, s - i + 1, 2) = 17 Then Text4.Text = Text4.Text + " семнадцать"
If Mid(k, s - i + 1, 2) = 18 Then Text4.Text = Text4.Text + " восемнадцать"
If Mid(k, s - i + 1, 2) = 19 Then Text4.Text = Text4.Text + " девятнадцать"
If Mid(k, s - i + 1, 2) = 10 Then Text4.Text = Text4.Text + " десять"
If i = 5 Then Text4.Text = Text4.Text + " тысяч"
If i = 8 Then Text4.Text = Text4.Text + " миллионов"
If i = 11 Then Text4.Text = Text4.Text + " миллиардов"
If i = 2 Then
Text4.Text = Text4.Text + " рублей"
If InStr(k, "-") > 0 Then GoTo 600
End If
i = i - 2
GoTo 20

70  If
Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " Сто"
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " Двести"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " Триста"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " Четыреста"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " Пятьсот"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " Шестьсот"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " Семьсот"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " Восемьсот"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " Девятьсот"
i = i - 1
GoTo 20

60  If
Mid(k, s - i + 1, 1) = 1 Then GoTo 90
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " Двадцать"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " Тридцать"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " Сорок"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " Пятьдесят"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " Шестьдесят"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " Семьдесят"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " Восемьдесят"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " Девяносто"
i = i - 1
GoTo 20

50  If
Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " Один"
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " Два"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " Три"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " Четыре"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " Пять"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " Шесть"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " Семь"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " Восемь"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " Девять"
If i = 1 Then GoTo 701
If i = 7 Then GoTo 702
If i = 10 Then GoTo 703

80  If
Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " Одна"
If Mid(k, s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " Две"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " Три"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " Четыре"
If Mid(k, s - i + 1, 1) = 5 Then Text4.Text = Text4.Text + " Пять"
If Mid(k, s - i + 1, 1) = 6 Then Text4.Text = Text4.Text + " Шесть"
If Mid(k, s - i + 1, 1) = 7 Then Text4.Text = Text4.Text + " Семь"
If Mid(k, s - i + 1, 1) = 8 Then Text4.Text = Text4.Text + " Восемь"
If Mid(k, s - i + 1, 1) = 9 Then Text4.Text = Text4.Text + " Девять"
If i = 4 Then GoTo 704
GoTo 20

90  If Mid(k,
s - i + 1, 2) = 11 Then Text4.Text = Text4.Text + " Одиннадцать"
If Mid(k, s - i + 1, 2) = 12 Then Text4.Text = Text4.Text + " Двенадцать"
If Mid(k, s - i + 1, 2) = 13 Then Text4.Text = Text4.Text + " Тринадцать"
If Mid(k, s - i + 1, 2) = 14 Then Text4.Text = Text4.Text + " Четырнадцать"
If Mid(k, s - i + 1, 2) = 15 Then Text4.Text = Text4.Text + " Пятнадцать"
If Mid(k, s - i + 1, 2) = 16 Then Text4.Text = Text4.Text + " Шестнадцать"
If Mid(k, s - i + 1, 2) = 17 Then Text4.Text = Text4.Text + " Семнадцать"
If Mid(k, s - i + 1, 2) = 18 Then Text4.Text = Text4.Text + " Восемнадцать"
If Mid(k, s - i + 1, 2) = 19 Then Text4.Text = Text4.Text + " Девятнадцать"
If Mid(k, s - i + 1, 2) = 10 Then Text4.Text = Text4.Text + " Десять"
If i = 5 Then Text4.Text = Text4.Text + " тысяч"
If i = 8 Then Text4.Text = Text4.Text + " миллионов"
If i = 11 Then Text4.Text = Text4.Text + " миллиардов"
If i = 2 Then
Text4.Text = Text4.Text + " рублей"
If InStr(k, "-") > 0 Then GoTo 600
End If
i = i - 2
GoTo 20

600 Text4.Text
= Text4.Text + " " + Right(k, 2)
If Right(k, 1) = 2 Then Text4.Text = Text4.Text + " копейки"
If Right(k, 1) = 3 Then Text4.Text = Text4.Text + " копейки"
If Right(k, 1) = 4 Then Text4.Text = Text4.Text + " копейки"
If Right(k, 1) = 1 Then Text4.Text = Text4.Text + " копейкa"
If Right(k, 1) = 0 Then Text4.Text = Text4.Text + " копеек"
If Right(k, 1) > 4 Then Text4.Text = Text4.Text + " копеек"

Exit Sub

701 If Mid(k,
s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " рубля"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " рубля"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " рубля"
If Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " рубль"
If Mid(k, s - i + 1, 1) > 4 Then Text4.Text = Text4.Text + " рублей"
If Left(k, s) = 0 Then
If InStr(k, "-") > 0 Then
Text4.Text = Text4.Text + "Ноль рублей"
GoTo 600
End If
If InStr(k, "-") = 0 Then Exit Sub
End If
If Mid(k, s - i + 1, 1) = 0 Then Text4.Text = Text4.Text + " рублей"
If InStr(k, "-") > 0 Then GoTo 600

Exit Sub

702 If Mid(k,
s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " миллиона"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " миллиона"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " миллиона"
If Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " миллион"
If Mid(k, s - i + 1, 1) > 4 Then Text4.Text = Text4.Text + " миллионов"
If Mid(k, s - i + 1, 1) = 0 Then
If n(7) + n(8) + n(9) > 0 Then Text4.Text = Text4.Text + " миллионов"
End If
i = i - 1
GoTo 20

703 If Mid(k,
s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " миллиарда"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " миллиарда"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " миллиарда"
If Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " миллиард"
If Mid(k, s - i + 1, 1) > 4 Then Text4.Text = Text4.Text + " миллиардов"
If Mid(k, s - i + 1, 1) = 0 Then
If n(10) + n(11) + n(12) > 0 Then Text4.Text = Text4.Text + " миллиардов"
End If
i = i - 1
GoTo 20

704 If Mid(k,
s - i + 1, 1) = 2 Then Text4.Text = Text4.Text + " тысячи"
If Mid(k, s - i + 1, 1) = 3 Then Text4.Text = Text4.Text + " тысячи"
If Mid(k, s - i + 1, 1) = 4 Then Text4.Text = Text4.Text + " тысячи"
If Mid(k, s - i + 1, 1) = 1 Then Text4.Text = Text4.Text + " тысяча"
If Mid(k, s - i + 1, 1) > 4 Then Text4.Text = Text4.Text + " тысяч"
If Mid(k, s - i + 1, 1) = 0 Then
If n(4) + n(5) + n(6) > 0 Then Text4.Text = Text4.Text + " тысяч"
End If
i = i - 1
GoTo 20

ErrorInput:

    If MsgBox("Неправильный формат введенной суммы", vbCritical, "Ошибка формата") = vbOK Then Exit Sub

End Sub


Кирпичик 2

 Public Function Converter(Conv1)
    Dim n(12) As Byte
    Dim k As String, s As Byte, i As Byte, z As String

    k = Conv1

    If InStr(k, ",") = 0 Then
        i = Len(k)
        s = i
        If s <= 12 Then
            If Left(k, 1) > 0 Then GoTo 10
        End If
    End If
    If InStr(k, ",") = Len(k) - 2 Then
        i = Len(k) - 3
        s = i
        If s <= 12 Then
            If Left(k, 1) > 0 Then GoTo 10
            If Left(k, s) = 0 Then GoTo 10
        End If
    End If
    GoTo 175

10  For i = 1 To s
    n(i) = Mid(k, s - i + 1, 1)
    Next i
    i = s

    If i = 1 Then GoTo 50
    If i = 2 Then GoTo 60
    If i = 3 Then GoTo 70
    If i = 4 Then GoTo 80
    If i = 5 Then GoTo 60
    If i = 6 Then GoTo 70
    If i = 7 Then GoTo 50
    If i = 8 Then GoTo 60
    If i = 9 Then GoTo 70
    If i = 10 Then GoTo 50
    If i = 11 Then GoTo 60
    If i = 12 Then GoTo 70
    If i > 12 Then GoTo ErrorInput

20  If i
= 0 Then
Converter = z
GoTo 175
End If
If i = 1 Then GoTo 100
If i = 2 Then GoTo 200
If i = 3 Then GoTo 300
If i = 4 Then GoTo 400
If i = 5 Then GoTo 200
If i = 6 Then GoTo 300
If i = 7 Then GoTo 100
If i = 8 Then GoTo 200
If i = 9 Then GoTo 300
If i = 10 Then GoTo 100
If i = 11 Then GoTo 200

300 If Mid(k,
s - i + 1, 1) = 1 Then z = z + " сто"
If Mid(k, s - i + 1, 1) = 2 Then z = z + " двести"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " триста"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " четыреста"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " пятьсот"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " шестьсот"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " семьсот"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " восемьсот"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " девятьсот"

i = i - 1
GoTo 20

200 If Mid(k,
s - i + 1, 1) = 1 Then GoTo 500
If Mid(k, s - i + 1, 1) = 2 Then z = z + " двадцать"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " тридцать"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " сорок"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " пятьдесят"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " шестьдесят"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " семьдесят"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " восемьдесят"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " девяносто"
i = i - 1
GoTo 20

100 If Mid(k,
s - i + 1, 1) = 1 Then z = z + " один"
If Mid(k, s - i + 1, 1) = 2 Then z = z + " два"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " три"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " четыре"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " пять"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " шесть"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " семь"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " восемь"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " девять"
If i = 1 Then GoTo 701
If i = 7 Then GoTo 702
If i = 10 Then GoTo 703

400 If Mid(k,
s - i + 1, 1) = 1 Then z = z + " одна"
If Mid(k, s - i + 1, 1) = 2 Then z = z + " две"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " три"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " четыре"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " пять"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " шесть"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " семь"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " восемь"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " девять"
If i = 4 Then GoTo 704
GoTo 20

500 If Mid(k,
s - i + 1, 2) = 11 Then z = z + " одиннадцать"
If Mid(k, s - i + 1, 2) = 12 Then z = z + " двенадцать"
If Mid(k, s - i + 1, 2) = 13 Then z = z + " тринадцать"
If Mid(k, s - i + 1, 2) = 14 Then z = z + " четырнадцать"
If Mid(k, s - i + 1, 2) = 15 Then z = z + " пятнадцать"
If Mid(k, s - i + 1, 2) = 16 Then z = z + " шестнадцать"
If Mid(k, s - i + 1, 2) = 17 Then z = z + " семнадцать"
If Mid(k, s - i + 1, 2) = 18 Then z = z + " восемнадцать"
If Mid(k, s - i + 1, 2) = 19 Then z = z + " девятнадцать"
If Mid(k, s - i + 1, 2) = 10 Then z = z + " десять"
If i = 5 Then z = z + " тысяч"
If i = 8 Then z = z + " миллионов"
If i = 11 Then z = z + " миллиардов"
If i = 2 Then
z = z + " рублей"
If InStr(k, ",") > 0 Then GoTo 600
End If
i = i - 2
GoTo 20

70  If
Mid(k, s - i + 1, 1) = 1 Then z = z + " Сто"
If Mid(k, s - i + 1, 1) = 2 Then z = z + " Двести"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " Триста"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " Четыреста"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " Пятьсот"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " Шестьсот"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " Семьсот"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " Восемьсот"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " Девятьсот"
i = i - 1
GoTo 20

60  If
Mid(k, s - i + 1, 1) = 1 Then GoTo 90
If Mid(k, s - i + 1, 1) = 2 Then z = z + " Двадцать"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " Тридцать"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " Сорок"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " Пятьдесят"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " Шестьдесят"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " Семьдесят"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " Восемьдесят"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " Девяносто"
i = i - 1
GoTo 20

50  If
Mid(k, s - i + 1, 1) = 1 Then z = z + " Один"
If Mid(k, s - i + 1, 1) = 2 Then z = z + " Два"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " Три"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " Четыре"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " Пять"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " Шесть"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " Семь"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " Восемь"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " Девять"
If i = 1 Then GoTo 701
If i = 7 Then GoTo 702
If i = 10 Then GoTo 703

80  If
Mid(k, s - i + 1, 1) = 1 Then z = z + " Одна"
If Mid(k, s - i + 1, 1) = 2 Then z = z + " Две"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " Три"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " Четыре"
If Mid(k, s - i + 1, 1) = 5 Then z = z + " Пять"
If Mid(k, s - i + 1, 1) = 6 Then z = z + " Шесть"
If Mid(k, s - i + 1, 1) = 7 Then z = z + " Семь"
If Mid(k, s - i + 1, 1) = 8 Then z = z + " Восемь"
If Mid(k, s - i + 1, 1) = 9 Then z = z + " Девять"
If i = 4 Then GoTo 704
GoTo 20

90  If Mid(k,
s - i + 1, 2) = 11 Then z = z + " Одиннадцать"
If Mid(k, s - i + 1, 2) = 12 Then z = z + " Двенадцать"
If Mid(k, s - i + 1, 2) = 13 Then z = z + " Тринадцать"
If Mid(k, s - i + 1, 2) = 14 Then z = z + " Четырнадцать"
If Mid(k, s - i + 1, 2) = 15 Then z = z + " Пятнадцать"
If Mid(k, s - i + 1, 2) = 16 Then z = z + " Шестнадцать"
If Mid(k, s - i + 1, 2) = 17 Then z = z + " Семнадцать"
If Mid(k, s - i + 1, 2) = 18 Then z = z + " Восемнадцать"
If Mid(k, s - i + 1, 2) = 19 Then z = z + " Девятнадцать"
If Mid(k, s - i + 1, 2) = 10 Then z = z + " Десять"
If i = 5 Then z = z + " тысяч"
If i = 8 Then z = z + " миллионов"
If i = 11 Then z = z + " миллиардов"
If i = 2 Then
z = z + " рублей"
If InStr(k, ",") > 0 Then GoTo 600
End If
i = i - 2
GoTo 20

600 z = z +
" " + Right(k, 2)
If Right(k, 1) = 2 Then z = z + " копейки"
If Right(k, 1) = 3 Then z = z + " копейки"
If Right(k, 1) = 4 Then z = z + " копейки"
If Right(k, 1) = 1 Then z = z + " копейкa"
If Right(k, 1) = 0 Then z = z + " копеек"
If Right(k, 1) > 4 Then z = z + " копеек"

Converter = z
Exit Function

701 If Mid(k,
s - i + 1, 1) = 2 Then z = z + " рубля"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " рубля"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " рубля"
If Mid(k, s - i + 1, 1) = 1 Then z = z + " рубль"
If Mid(k, s - i + 1, 1) > 4 Then z = z + " рублей"
If Left(k, s) = 0 Then
If InStr(k, ",") > 0 Then
z = z + "Ноль рублей"
GoTo 600
End If
If InStr(k, ",") = 0 Then Exit Function
End If
If Mid(k, s - i + 1, 1) = 0 Then z = z + " рублей"
If InStr(k, ",") > 0 Then GoTo 600

Converter = z
Exit Function

702 If Mid(k,
s - i + 1, 1) = 2 Then z = z + " миллиона"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " миллиона"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " миллиона"
If Mid(k, s - i + 1, 1) = 1 Then z = z + " миллион"
If Mid(k, s - i + 1, 1) > 4 Then z = z + " миллионов"
If Mid(k, s - i + 1, 1) = 0 Then
If n(7) + n(8) + n(9) > 0 Then z = z + " миллионов"
End If
i = i - 1
GoTo 20

703 If Mid(k,
s - i + 1, 1) = 2 Then z = z + " миллиарда"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " миллиарда"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " миллиарда"
If Mid(k, s - i + 1, 1) = 1 Then z = z + " миллиард"
If Mid(k, s - i + 1, 1) > 4 Then z = z + " миллиардов"
If Mid(k, s - i + 1, 1) = 0 Then
If n(10) + n(11) + n(12) > 0 Then z = z + " миллиардов"
End If
i = i - 1
GoTo 20

704 If Mid(k,
s - i + 1, 1) = 2 Then z = z + " тысячи"
If Mid(k, s - i + 1, 1) = 3 Then z = z + " тысячи"
If Mid(k, s - i + 1, 1) = 4 Then z = z + " тысячи"
If Mid(k, s - i + 1, 1) = 1 Then z = z + " тысяча"
If Mid(k, s - i + 1, 1) > 4 Then z = z + " тысяч"
If Mid(k, s - i + 1, 1) = 0 Then
If n(4) + n(5) + n(6) > 0 Then z = z + " тысяч"
End If
i = i - 1
GoTo 20

ErrorInput:

    If MsgBox("Неправильный формат введенной суммы", vbCritical, "Ошибка формата") = vbOK Then Exit Function

175 End Function


page8

 Private Sub mnuFileOpen_Click()
'---------------------------------------------------
'Открытие текстового файла DOS
'(866 кодовой таблицы) из выбираемой директории
'для просмотра в NotePad. Для случая с большим по
'размеру файлом (например потенциальной базы данных)
'системой выдается запрос на использование WordPad.
'---------------------------------------------------
'Эти строки - в General основной формы
Private Declare Function GetOEMCP Lib "kernel32" () As Long
Private Declare Function GetACP Lib "kernel32" () As Long
Private Declare Function OemToChar Lib "user32" Alias "OemToCharA" (ByVal lpszSrc As String, ByVal lpszDst As String) As Long
'-------------------------------------------------------
Dim sFile As String
Dim nShell As Integer
On Error GoTo InputError
With dlgCommonDialog
'To Do
'set the flags and attributes of the
'common dialog control
.Filter = "All Files (*.*)|*.*"
.ShowOpen
If Len(.filename) = 0 Then
Exit Sub
End If
sFile = .filename
End With
'To Do
'process the opened file
FilePath$ = dlgCommonDialog.filename
Open FilePath$ For Input As 1
InputStr$ = Input(LOF(1), 1)
OutputStr$ = Space$(Len(InputStr$))
Code& = OemToChar(InputStr$, OutputStr$)
Close 1
Open "c:/Program Files/Fuel/fueltemp.doc" For Output As 2
Print #2, OutputStr$
Close 2
nShell = Shell("Notepad " & "c:\Program Files\Fuel\fueltemp.doc", vbNormalFocus)
InputError:
Exit Sub
End Sub


page10

 Private Sub mnuDriveABaza_Click()
'---------------------------------------------------
'Пример преобразования текстового файла DOS в базу
'данных формата *.mdb (Access).Таблица в базе
'создана заранее и связана с Form4.DBGrid1 по Data1
'в дочерней форме Form4.
'В DBGrid1 и виден результат. Недостаток - невысокое
'быстродействие (у меня был файл ~ 5000 строк).
'---------------------------------------------------
'Эти строки - в General основной формы
Private Declare Function GetOEMCP Lib "kernel32" () As Long
Private Declare Function GetACP Lib "kernel32" () As Long
Private Declare Function OemToChar Lib "user32" Alias "OemToCharA" (ByVal lpszSrc As String, ByVal lpszDst As String) As Long
'-------------------------------------------------------
Dim sFile, sLine As String
Dim str, str0, str1, str2 As String

With dlgCommonDialog
'To Do
'set the flags and attributes of the
'common dialog control
.Filter = "All Files (*.*)|*.*"
.ShowOpen
If Len(.filename) = 0 Then
Exit Sub
End If
sFile = .filename
End With
'To Do
'process the opened file
FilePath$ = dlgCommonDialog.filename
Open FilePath$ For Input As 1
InputStr$ = Input$(LOF(1), 1)
OutputStr$ = Space$(Len(InputStr$))
Code& = OemToChar(InputStr$, OutputStr$)
Close 1
Open "c:/Program Files/Fuel/fueltemp.doc" For Output As 2
Print #2, OutputStr$
Close 2
Open "c:/Program Files/Fuel/fueltemp.doc" For Input As 2
Do While Not EOF(2)
Line Input #2, sLine$
If sLine = "" Then GoTo 20
'Отфильровываю ненужные строки
If InStr(1, sLine$, "----------", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "иятие", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "смена", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "Город", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "топлива", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "Табельный", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "ОПЕРАТОР", vbTextCompare) > 0 Then GoTo 20
If InStr(1, sLine$, "БУХГАЛТЕР", vbTextCompare) > 0 Then GoTo 20

'Проверка на подзаголовок (oн в первых позициях
'текста) и его нужно вставлять в каждую строку
'таблицы
If Left(sLine$, 10) = " " Then GoTo 10
str3 = Mid(sLine$, 8, 7)
'Считывание данных с конкретных позиций
10 If InStr(1, sLine$, ":", vbTextCompare) = 0 Then
str = Mid(sLine$, 1, 130)
str0 = Mid(sLine$, 78, 12)
str1 = Mid(sLine$, 106, 6)
'Убираю ненужные мне апострофы в значениях
If InStr(2, str1, "'", vbTextCompare) > 0 Then str1 = Left(str1, InStr(2, str1, "'", vbTextCompare) - 1) + Mid(str1, InStr(2, str1, "'", vbTextCompare) + 1, 6)

str2 = Mid(sLine$, 118, 12)
'Убираю ненужные мне апострофы в значениях
If InStr(2, str2, "'", vbTextCompare) > 0 Then str2 = Left(str2, InStr(2, str2, "'", vbTextCompare) - 1) + Mid(str2, InStr(2, str2, "'", vbTextCompare) + 1, 6)
'Запись в поля таблицы
Form4.Data1.Recordset.AddNew
Form4.Data1.Recordset.Fields("НомерКарточки").Value = str3
Form4.Data1.Recordset.Fields("НазваниеГСМ").Value = str0
Form4.Data1.Recordset.Fields("КоличествоЛ").Value = str1
Form4.Data1.Recordset.Fields("КоличествоР").Value = str2
Form4.Data1.Recordset.Update
End If
20 Loop
Close 2
Form4.Data1.Refresh
Form4.Show
InputError:
Exit Sub

End Sub


page10

 Private Sub mnuPrint_Click()
'---------------------------------------------------
'Пример печати бланка платежного поручения
'без применения Crystal Report, с которым никак не
'удавалось один к одному выдержать заданные
'размеры
'---------------------------------------------------
Printer.CurrentX = 1200
Printer.CurrentY = 120
Printer.FontBold = True
Printer.Print "ПЛАТЕЖНОЕ ПОРУЧЕНИЕ №"
Printer.Line (10260, 60)-(11175, 375), , B
Printer.CurrentX = 10320
Printer.CurrentY = 120
Printer.FontBold = False
Printer.Print "0401060"
Printer.CurrentX = 6660
Printer.CurrentY = 420
Printer.Print "Дата"
Printer.CurrentX = 8700
Printer.CurrentY = 420
Printer.Print "Вид платежа"
Printer.CurrentX = 1200
Printer.CurrentY = 840
Printer.Print "Сумма"
Printer.CurrentX = 1200
Printer.CurrentY = 1120
Printer.Print "прописью"
Printer.Line (2340, 840)-(2340, 1695)
Printer.CurrentX = 1200
Printer.CurrentY = 1740
Printer.Print "ИНН"
Printer.Line (6720, 1680)-(6720, 6795)
Printer.Line (7560, 1680)-(7560, 6795)
Printer.CurrentX = 6840
Printer.CurrentY = 1980
Printer.Print "Сумма"
Printer.Line (6720, 2520)-(11235, 2520)
Printer.CurrentX = 6840
Printer.CurrentY = 2880
Printer.Print "Сч.№"
Printer.Line (1200, 3360)-(7575, 3360)
Printer.CurrentX = 1200
Printer.CurrentY = 3120
Printer.Print "Плательщик"
Printer.CurrentX = 6840
Printer.CurrentY = 3600
Printer.Print "БИК"
Printer.CurrentX = 6840
Printer.CurrentY = 3900
Printer.Print "Сч.№"
Printer.CurrentX = 1200
Printer.CurrentY = 3960
Printer.Print "Банк плательщика"
Printer.Line (1200, 4200)-(11235, 4200)
Printer.Line (6000, 360)-(7935, 360)
Printer.Line (8220, 360)-(10095, 360)
Printer.Line (1200, 1680)-(11235, 1680)
Printer.CurrentX = 1200
Printer.CurrentY = 4800
Printer.Print "Банк получателя"
Printer.CurrentX = 6840
Printer.CurrentY = 4440
Printer.Print "БИК"
Printer.CurrentX = 6840
Printer.CurrentY = 4740
Printer.Print "Сч.№"
Printer.Line (1200, 5040)-(7575, 5040)
Printer.CurrentX = 1200
Printer.CurrentY = 5100
Printer.Print "ИНН"
Printer.CurrentX = 6840
Printer.CurrentY = 5340
Printer.Print "Сч.№"
Printer.Line (6720, 5880)-(11235, 5880)
Printer.CurrentX = 6780
Printer.CurrentY = 5940
Printer.Print "Вид.оп."
Printer.CurrentX = 6780
Printer.CurrentY = 6240
Printer.Print "Наз.пл."
Printer.CurrentX = 6780
Printer.CurrentY = 6540
Printer.Print "Код"
Printer.CurrentX = 8760
Printer.CurrentY = 5940
Printer.Print "Срок плат."
Printer.CurrentX = 8760
Printer.CurrentY = 6240
Printer.Print "Очер.плат."
Printer.CurrentX = 8760
Printer.CurrentY = 6540
Printer.Print "Рез. поле"
Printer.Line (6720, 6180)-(7575, 6180)
Printer.Line (6720, 6480)-(7575, 6480)
Printer.Line (8700, 6180)-(9855, 6180)
Printer.Line (8700, 6480)-(9855, 6480)
Printer.Line (1200, 6780)-(11235, 6780)
Printer.Line (8700, 5880)-(8700, 6795)
Printer.Line (9840, 5880)-(9840, 6795)

Printer.CurrentX = 1200
Printer.CurrentY = 6540
Printer.Print "Получатель"
Printer.CurrentX = 1200
Printer.CurrentY = 6840
Printer.Print "Назначение платежа, наименование товара, выполненных работ, оказанных услуг, №№ и даты товарных"
Printer.CurrentX = 1200
Printer.CurrentY = 7080
Printer.Print "документов, договоров, НДС"

Printer.FontName = "Arial"
Printer.FontSize = 10

Printer.CurrentX = 4320
Printer.CurrentY = 120
Printer.Print Text1.Text
Printer.CurrentX = 6000
Printer.CurrentY = 120
Printer.Print Text2.Text
Printer.CurrentX = 8220
Printer.CurrentY = 120
Printer.Print Combo1.Text
Printer.CurrentX = 7680
Printer.CurrentY = 1980
Printer.Print Text7.Text
Printer.CurrentX = 2460
Printer.CurrentY = 1320
Printer.Print Text4.Text
Printer.CurrentX = 1200
Printer.CurrentY = 2220
Printer.Print Text3.Text
Printer.CurrentX = 1200
Printer.CurrentY = 2700
Printer.Print DBCombo1.Text
Printer.CurrentX = 1200
Printer.CurrentY = 3600
Printer.Print Text9.Text
Printer.CurrentX = 7680
Printer.CurrentY = 2820
Printer.Print Text8.Text
Printer.CurrentX = 7680
Printer.CurrentY = 3540
Printer.Print Text10.Text
Printer.CurrentX = 7680
Printer.CurrentY = 3900
Printer.Print Text11.Text
Printer.CurrentX = 1200
Printer.CurrentY = 4400
Printer.Print Text12.Text
Printer.CurrentX = 7680
Printer.CurrentY = 4380
Printer.Print Text13.Text
Printer.CurrentX = 7680
Printer.CurrentY = 4740
Printer.Print Text14.Text
Printer.CurrentX = 7680
Printer.CurrentY = 5280
Printer.Print Text17.Text
Printer.CurrentX = 1200
Printer.CurrentY = 5580
Printer.Print Text15.Text
Printer.CurrentX = 1200
Printer.CurrentY = 6060
Printer.Print DBCombo2.Text
Printer.CurrentX = 9960
Printer.CurrentY = 5940
Printer.Print Text18.Text
Printer.CurrentX = 9960
Printer.CurrentY = 6240
Printer.Print Text19.Text

If Check2.Value = 1 Then GoTo 20
Printer.CurrentX = 1200
Printer.CurrentY = 7500
Printer.Print Text20.Text
Printer.CurrentX = 1200
Printer.CurrentY = 8060
Printer.Print Text23.Text
Printer.FontName = "MS Sans Serif"
Printer.FontSize = 8
Printer.Line (1200, 8460)-(11235, 8460)
Printer.CurrentX = 6270
Printer.CurrentY = 8520
Printer.Print "Подписи"
Printer.CurrentX = 9255
Printer.CurrentY = 8520
Printer.Print "Отметки банка"
Printer.CurrentX = 2880
Printer.CurrentY = 9460
Printer.Print "М.П."
Printer.Line (5115, 9460)-(8475, 9460)
If Check1.Value = 0 Then
Printer.Line (5115, 10260)-(8475, 10260)
GoTo 10
End If

Printer.CurrentX = 1200
Printer.CurrentY = 8520
Printer.Print "Вторая подпись не предусмотрена"
GoTo 10

20 Printer.CurrentX = 1200
Printer.CurrentY = 7260
Printer.Print Text20.Text
Printer.CurrentX = 1200
Printer.CurrentY = 7500
Printer.Print Text23.Text
Printer.FontName = "MS Sans Serif"
Printer.FontSize = 8
Printer.FontBold = True
Printer.CurrentX = 2555
Printer.CurrentY = 7900
Printer.Print "раздел"
Printer.CurrentX = 5215
Printer.CurrentY = 7900
Printer.Print "параграф"
Printer.CurrentX = 7875
Printer.CurrentY = 7900
Printer.Print "код операции"
Printer.CurrentX = 2555
Printer.CurrentY = 8200
Printer.Print "налог:"
Printer.CurrentX = 2555
Printer.CurrentY = 8500
Printer.Print "пеня:"
Printer.CurrentX = 2555
Printer.CurrentY = 8800
Printer.Print "штраф:"
Printer.FontBold = False
Printer.FontName = "Arial"
Printer.FontSize = 10

    Printer.CurrentX = 3675
Printer.CurrentY = 7900
Printer.Print Text5.Text
Printer.CurrentX = 6335
Printer.CurrentY = 7900
Printer.Print Text6.Text
Printer.CurrentX = 3675
Printer.CurrentY = 8200
Printer.Print Text16.Text
Printer.CurrentX = 3675
Printer.CurrentY = 8500
Printer.Print Text21.Text
Printer.CurrentX = 3675
Printer.CurrentY = 8800
Printer.Print Text22.Text

Printer.FontName = "MS Sans Serif"
Printer.FontSize = 8
Printer.Line (1200, 9070)-(11235, 9070)
Printer.CurrentX = 6270
Printer.CurrentY = 9130
Printer.Print "Подписи"
Printer.CurrentX = 9255
Printer.CurrentY = 9130
Printer.Print "Отметки банка"
Printer.CurrentX = 2880
Printer.CurrentY = 10070
Printer.Print "М.П."
Printer.Line (5115, 10070)-(8475, 10070)
If Check1.Value = 0 Then
Printer.Line (5115, 10870)-(8475,
10870)
GoTo 10
End If
Printer.CurrentX = 1200
Printer.CurrentY = 9130
Printer.Print "Вторая подпись не предусмотрена"

10  Printer.EndDoc
End Sub


page11

 Private Sub Command1_Click()
'---------------------------------------------------
'Еще один пример печати отчета без Crystal Report.
'Интересен тем, что во втором столбце печатаемой
'таблицы могут быть длинные строки, в которых надо
'переносить слова не разрезая, но нумерация в первом
'столбце должна появляться один раз. Поскольку
'отчеты могут быть различны по объему, организована
'постраничная печать с ориентацией на количество
'строк, выдаваемых не лучшим принтером.
'---------------------------------------------------
DBGrid1.Visible = False
Dim c, r, n, s, p, sum, rab, raz, l As Integer
Dim str, str1, str2, str3, razstr1, razstr2, razstr3, str0 As String
Dim ch, ost As Currency
Printer.FontName = "Times New Roman cyr"
Printer.FontSize = 12
Printer.FontBold = False
Printer.CurrentX = Printer.Width * 0.02 * 12
Printer.CurrentY = Printer.Height * 0.02 * 1
Printer.Print "Сметная стоимость выполнения ремонтно-отделочных работ"
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 3)
Printer.DrawWidth = 1
Printer.CurrentX = Printer.Width * 0.02 * 6.2
Printer.CurrentY = Printer.Height * 0.02 * 3.2
Printer.Print "№"
Printer.CurrentX = Printer.Width * 0.02 * 8.2
Printer.CurrentY = Printer.Height * 0.02 * 3.2
Printer.Print "Вид работ"
Printer.CurrentX = Printer.Width * 0.02 * 30.2
Printer.CurrentY = Printer.Height * 0.02 * 3.2
Printer.Print "Цена"
Printer.CurrentX = Printer.Width * 0.02 * 35.2
Printer.CurrentY = Printer.Height * 0.02 * 3.2
Printer.Print "Количество"
Printer.CurrentX = Printer.Width * 0.02 * 41.2
Printer.CurrentY = Printer.Height * 0.02 * 3.2
Printer.Print "Сумма"
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 4)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 4)
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 4)
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * 4)
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * 4)
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * 4)
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 4)
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * 3)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * 4)

c = 0
r = 0
n = 0
s = 4
p = 1
sum = 0

30  Printer.FontBold = False
If c = 7 Then GoTo 35
DBGrid1.Col = c
On Error GoTo ColsError
DBGrid1.Row = r
If c > 0 Then
str = DBGrid1.Text
Else
c = c + 1
GoTo 30
End If
If str = "" Then GoTo 70
If c = 2 Then
n = 8
rab = Len(str)
If rab > 43 Then GoTo 51
GoTo 50
End If
If c = 3 Then
n = 30
'Перевод в денежную форму
ch = (Int(str * 100)) /
100
ost = str - ch
If ost >= 0.005 Then ch
= ch + 0.01
str = ch
If InStr(1, str, ",") =
0 Then
str = str & "-" & "00"
End If
If InStr(1, str, ",") >
0 Then
str = Left(str, (InStr(1, str, ",")) - 1) & "-" & Right(str, Len(str)
- InStr(1, str, ","))
End If
If Len(Right(str, Len(str)
- InStr(1, str, "-"))) = 1 Then
str = Left(str, InStr(1, str, "-")) & Right(str, Len(str) - InStr(1,
str, "-")) & "0"
End If
If InStr(1, str, "-") =
2 Then str = "    " & str
If InStr(1, str, "-") =
3 Then str = "   " & str
If InStr(1, str, "-") =
4 Then str = "  " & str
If InStr(1, str, "-") =
5 Then str = " " & str

        GoTo 52
End If
If c = 4 Then
n = 35
GoTo 52
End If
If c = 5 Then
n = 38
GoTo 52
End If
If c = 6 Then
n = 41
sum = sum + str
'Перевод в денежную форму
ch = (Int(str * 100)) /
100
ost = str - ch
If ost >= 0.005 Then ch
= ch + 0.01
str = ch
If InStr(1, str, ",") =
0 Then
str = str & "=" & "00"
End If
If InStr(1, str, ",") >
0 Then
str = Left(str, (InStr(1, str, ",")) - 1) & "=" & Right(str, Len(str)
- InStr(1, str, ","))
End If
If Len(Right(str, Len(str)
- InStr(1, str, "="))) = 1 Then
str = Left(str, InStr(1, str, "=")) & Right(str, Len(str) - InStr(1,
str, "=")) & "0"
End If
If InStr(1, str, "=") =
2 Then str = "    " & str
If InStr(1, str, "=") =
3 Then str = "   " & str
If InStr(1, str, "=") =
4 Then str = "  " & str
If InStr(1, str, "=") =
5 Then str = " " & str

GoTo 52
End If
If c = 1 Then
If s > 41 Then
s = s - 1
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1

Printer.NewPage
s = 2
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 1)
Printer.DrawWidth = 1
Printer.CurrentX = Printer.Width * 0.02 * 6.2
Printer.CurrentY = Printer.Height * 0.02 * 1.2
Printer.Print "№"
Printer.CurrentX = Printer.Width * 0.02 * 8.2
Printer.CurrentY = Printer.Height * 0.02 * 1.2
Printer.Print "Вид работ"
Printer.CurrentX = Printer.Width * 0.02 * 30.2
Printer.CurrentY = Printer.Height * 0.02 * 1.2
Printer.Print "Цена"
Printer.CurrentX = Printer.Width * 0.02 * 35.2
Printer.CurrentY = Printer.Height * 0.02 * 1.2
Printer.Print "Количество"
Printer.CurrentX = Printer.Width * 0.02 * 41.2
Printer.CurrentY = Printer.Height * 0.02 * 1.2
Printer.Print "Сумма"
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 2)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 2)
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * 2)
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * 2)
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * 2)
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * 2)
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * 2)
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * 1)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * 2)
End If
If str1 = str Then
c = 2
GoTo 30
End If
End If
Printer.CurrentX = Printer.Width * 0.02 * 8.2
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
str3 = UCase(str)
Printer.FontBold = True
Printer.Print str3
Printer.FontBold = False
str1 = str
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * (s + 1))
c = c + 1
s = s + 1
p = 1
GoTo 30
50 Printer.CurrentX = Printer.Width * 0.02 * (n + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)

Printer.Print str
Printer.CurrentX = Printer.Width * 0.02 * (6 + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
Printer.Print p
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * (s + 1))
c = c + 1
GoTo 30
'Операция разрезания строк в одном из столбцов таблицы
51 str0 = Left(str, 43)
raz = InStr(40, str0, " ")
If raz = 0 Then
raz = InStr(37, str0, " ")
End If
If raz = 0 Then
raz = InStr(34, str0, " ")
End If
If raz = 0 Then
raz = InStr(31, str0, " ")
End If
razstr1 = Left(str, (raz - 1))

Printer.CurrentX = Printer.Width * 0.02 * (n + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
Printer.Print razstr1
Printer.CurrentX = Printer.Width * 0.02 * (6 + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
Printer.Print p
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * (s + 1))
s = s + 1

razstr2 = Mid(str, raz)

rab = Len(razstr2)
If rab > 43 Then
str0 = Left(razstr2, 43)
raz = InStr(40, str0, " ")
If raz = 0 Then
raz = InStr(37, str0, " ")
End If
If raz = 0 Then
raz = InStr(34, str0, " ")
End If
If raz = 0 Then
raz = InStr(31, str0, " ")
End If
razstr3 = Mid(razstr2, raz)
str0 = Left(razstr2, (raz - 1))
razstr2 = str0
l = 1
Else
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
End If
Printer.CurrentX = Printer.Width * 0.02 * (n + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)

Printer.Print razstr2
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * (s + 1))

If l = 1 Then
l = 0
s = s + 1
Printer.CurrentX = Printer.Width * 0.02 * (n + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
Printer.Print razstr3
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * (s + 1))

    End If
c = c + 1
GoTo 30

52  Printer.CurrentX = Printer.Width * 0.02 * (n + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s +
0.2)
Printer.Print str
c = c + 1
GoTo 30

35  r = r + 1
c = 0
s = s + 1
p = p + 1
GoTo 30

70 Printer.EndDoc
ColsError:

Printer.CurrentX = Printer.Width * 0.02 * (8 + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
Printer.Print "Итого:"
Printer.CurrentX = Printer.Width * 0.02 * (41 + 0.2)
Printer.CurrentY = Printer.Height * 0.02 * (s + 0.2)
sum = Int(sum)
Printer.Print sum;
Printer.Print "р."
Printer.DrawWidth = 2
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 6, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 6, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 46, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 46, Printer.Height * 0.02 * (s + 1))
Printer.DrawWidth = 1
Printer.Line (Printer.Width * 0.02 * 8, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 8, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 30, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 30, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 41, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 41, Printer.Height * 0.02 * (s + 1))
Printer.Line (Printer.Width * 0.02 * 35, Printer.Height * 0.02 * s)-(Printer.Width * 0.02 * 35, Printer.Height * 0.02 * (s + 1))

s = s + 2
If s > 43 Then
s = 1
Printer.NewPage
End If
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "Примечание:"
s = s + 1
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "1. Сметная стоимость выполняемых ремонтно-отделочных работ не включает"
s = s + 1
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "стоимость материалов."
s = s + 2
If s > 42 Then
s = 1
Printer.NewPage
End If
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "Заказчик"
Printer.CurrentX = Printer.Width * 0.02 * 30
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "Исполнитель"
s = s + 1
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "(_____)__________19 г."
Printer.CurrentX = Printer.Width * 0.02 * 30
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "(_____)__________19 г."
s = s + 1
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "_______________"
Printer.CurrentX = Printer.Width * 0.02 * 30
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print "_______________"
s = s + 1
Printer.CurrentX = Printer.Width * 0.02 * 6
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print " (подпись)"
Printer.CurrentX = Printer.Width * 0.02 * 30
Printer.CurrentY = Printer.Height * 0.02 * s
Printer.Print " (подпись)"

Printer.EndDoc
DBGrid1.Visible = True
Exit Sub

End Sub


page12

 Private Sub DBCombo1_Click(Area As Integer)
'---------------------------------------------------
'Вывод в DBGrid некоторых значений из таблицы по
'запросу SQL. Основанием для запроса является
'запись в DBCombo
'---------------------------------------------------
On Error GoTo ProError
Dim SQLstr As String
'проверка - должна ли быть видима таблица на этом
'этапе
If dbcAreaList = 2 Then
If t = 0 Then
t = 1
DBGrid1.Visible = False
Exit Sub
End If

10 If t = 1 Then
'запрос
SQLstr = "SELECT PP as Запись, Number as Номер_платежки, Data as Дата , Recipient as Плательщик, Summa as Сумма FROM " & "Plategs1 WHERE " & "Payer= '" & DBCombo1.Text & "'"
Data1.RecordSource = SQLstr
Data1.Refresh
If DBGrid1.Visible = False Then
DBGrid1.Visible = True
DBGrid1.SetFocus
t = 0
Exit Sub
End If
End If
End If
GoTo 10
ProError:
Exit Sub
End Sub


page13

 '-----------------------------------------------------------------
'Делит Text на элементы, разделенные строкой символов
'Delimiters (которая может быть любой длины).
'Если параметр No=0, функцией возвращается количество
'элементов (Integer), при других значениях - получившаяся
'строка, отделенная первым встретившимся элементом
'Delimiters (String).
'------------------------------------------------------------------
Function Delimit(No As Integer, DataString As String, Delimiter As String) As Variant
Dim V As Variant, N As Integer, I As Integer, I0 As Integer
If Len(DataString) = 0 Then
If No = 0 Then
V = 0
Else
V = ""
End If
Delimit = V
Exit Function
End If
I0 = 1
If No = 0 Then
N = 1
Do
I = InStr(I0, DataString, Delimiter)
If I = 0 Then Exit Do
N = N + 1
I0 = I + Len(Delimiter)
Loop
Delimit = N
Exit Function
End If
I0 = 1
N = 1
Do
I = InStr(I0, DataString, Delimiter)
If I = 0 Then Exit Do
If N = No Then Exit Do
N = N + 1
I0 = I + Len(Delimiter)
Loop
If N < No Then
Delimit = ""
Exit Function
End If
I = InStr(I0, DataString, Delimiter)
If I = 0 Then
V = Mid$(DataString, I0)
Else
V = Mid$(DataString, I0, I - I0)
End If
Delimit = V
End Function

Все функции замены текста, удаления пробелов и пр., я думаю,
можно синтезировать с помощью моих Delimit и Separate.
Соответственно например процедура "делания" всех букв
каждого слова выглядит так:

Dim I as Integer,Z as String,Text as String
Text="test, test, test, test"
Z=""
For I=1 to Delimit(0,Text," ")
Z=Z & UCase$(Mid$(Delimit(I,Text," "),1,1)+LCase$(Mid$(Delimit(I,Text,"
")& " "
Next I
If Delimit(0,Text," ")>0 Then Z=Left$(Z,Len(Z)-1) 'Чтобы убрать лишний
пробел
Text=Z

Если надо сделать первые буквы предложений заглавными, то
достаточно вместо Delimit использовать Separate, а Delimiters=".!?".

Убрать лишние пробелы? Пожалуйста:
Dim I as Integer,Z as String,Text as String
Text="te st, t est , test, test"
Z=""
For I=1 to Delimit(0,Text," ")
If Delimit(I,Text," ")<>"" Then Z=Z & Delimit(I,Text," ") & " "
Next I
If Delimit(0,Text," ")>0 Then Z=Left$(Z,Len(Z)-1)
Text=Z

Если надо вырезать какие-то символы из строки --- Delimiters=символы
и сложить все элементы.


page14

 '-----------------------------------------------------------------
'Функция Separate делает примерно тоже, что и Delimit,
'но есть несколько отличий:
'1. При любом No возвращается String
'2. Разделитель всегда состоит из одного символа, но их
'может быть несколько, например если Delimiters=",;-/", то
'в качестве разделителей будет принят любой из символов
'",", ";", "-", "/" (в Delimit разделителем была бы строка ",;-/")
'------------------------------------------------------------------

Function Separate$(CellNumber As Integer, DataString As String, SepString As String)
Dim I As Integer, J As Integer, F As Integer
Dim L As String, N As Integer
Dim C As Integer, C1 As Integer, C2 As Integer, V As String
Dim NumCell As Integer
L = DataString
N = Len(SepString)
ReDim Sep(1 To N) As String * 1
For I = 1 To N
Sep(I) = Mid$(SepString, I, 1)
Next I
NumCell = 0
For I = 1 To Len(L)
F = 0
For J = 1 To N
If Mid$(L, I, 1) = Sep(J) Then F = 1
Next J
If F = 1 Then NumCell = NumCell + 1
Next I
If Len(L) > 0 Then NumCell = NumCell + 1
If CellNumber = 0 Then
Separate$ = LTrim$(RTrim$(Str$(NumCell)))
Exit Function
End If
If NumCell = 0 Then Exit Function
If CellNumber < 0 Or CellNumber > NumCell Then Exit Function

C = 1
C1 = 0
For I = 1 To Len(L)
F = 0
For J = 1 To N
If Mid$(L, I, 1) = Sep(J) Then F = 1
Next J
If F = 1 Then
C = C + 1
If C = CellNumber Then C1 = I: Exit For
End If
Next I
C2 = 0
For I = C1 + 1 To Len(L)
F = 0
For J = 1 To N
If Mid$(L, I, 1) = Sep(J) Then F = 1
Next J
If F = 1 Then C2 = I: Exit For
Next I
If C2 = 0 Then C2 = Len(L) + 1
V = ""
If (C2 - C1) - 1 > 0 Then V = Mid$(L, C1 + 1, (C2 - C1) - 1)
Separate$ = V
End Function


page15

 '-----------------------------------------------------
'Функция fmtCurrency преобразовывает число
'к денежному формату: разделитель - точка,
'два знака после точки.
'-----------------------------------------------------
'Откройте новый проект. Добавьте на Form1
'элементы Text1, Text2, кнопку Command1
'и модуль. Код в модуле:
Function fmtCurrency(sum As Currency) As String
Dim I As String, F As String
I = Format$(Fix(sum), "0")
F = Format$(Fix((sum - Fix(sum)) * 100), "00")
fmtCurrency = I & "." & F
End Function

'Для события Сlick кнопки Command1 введите код:
Private Sub Command1_Click()
Text2.Text = fmtCurrency(Text1.Text)
End Sub


page16

 '-------------------------------------------------------
'Функция IntTxt печатает в поле прописью сумму,
'набранную цифрами в другом поле без названия
'денежных единиц.
'Используется совместно с функцией LetterSum.
'-------------------------------------------------------
Function IntTxt(Number As Long) As String
Dim Z As String, tmp As String
Dim I As Integer, P As Integer, N As Integer, V As Integer
If Number = 0 Then
IntTxt = "ноль"
Exit Function
End If
Z = Trim$(Str$(Number))
N = Fix((Len(Z) - 1) / 3)
ReDim D(0 To N) As Integer
For I = Len(Z) To 1 Step -1
P = Fix((Len(Z) - I) / 3)
tmp = Mid$(Z, I, 1) + tmp
D(P) = Val(tmp)
If Len(tmp) = 3 Then tmp = ""
Next I
Z = ""
For I = N To 0 Step -1
V = D(I)
P = Int(V / 100)
Select Case P
Case 0
Case 1
Z = Z + "сто "
Case 2
Z = Z + "двести "
Case 3
Z = Z + "триста "
Case 4
Z = Z + "четыреста "
Case 5
Z = Z + "пятьсот "
Case 6
Z = Z + "шестьсот "
Case 7
Z = Z + "семьсот "
Case 8
Z = Z + "восемьсот "
Case 9
Z = Z + "девятьсот "
End Select
P = V - 100 * Int(V / 100)
Select Case Int(P / 10)
Case 0
Case 1
Select Case P - 10 * Int(P / 10)
Case 0
Z = Z + "десять "
Case 1
Z = Z + "оди"
Case 2
Z = Z + "две"
Case 3
Z = Z + "три"
Case 4
Z = Z + "четыр"
Case 5
Z = Z + "пят"
Case 6
Z = Z + "шест"
Case 7
Z = Z + "сем"
Case 8
Z = Z + "восем"
Case 9
Z = Z + "девят"
End Select
If (P - 10 * Int(P / 10)) <> 0 Then Z = Z + "надцать "
Case 2
Z = Z + "двадцать "
Case 3
Z = Z + "тридцать "
Case 4
Z = Z + "сорок "
Case 5
Z = Z + "пятьдесят "
Case 6
Z = Z + "шестьдесят "
Case 7
Z = Z + "семьдесят "
Case 8
Z = Z + "восемьдесят "
Case 9
Z = Z + "девяносто "
End Select
If (Int(P / 10)) <> 1 Then
Select Case P - 10 * Int(P / 10)
Case 0
Case 1
Select Case I
Case 1
Z = Z + "одна "
Case Else
Z = Z + "один "
End Select
Case 2
Select Case I
Case 1
Z = Z + "две "
Case Else
Z = Z + "два "
End Select
Case 3
Z = Z + "три "
Case 4
Z = Z + "четыре "
Case 5
Z = Z + "пять "
Case 6
Z = Z + "шесть "
Case 7
Z = Z + "семь "
Case 8
Z = Z + "восемь "
Case 9
Z = Z + "девять "
End Select
End If
Select Case I
Case 0
tmp = ""
Case 1
tmp = DTxt(V, "тысяча", "тысячи", "тысяч")
Case 2
tmp = DTxt(V, "миллион", "миллиона", "миллионов")
Case 3
tmp = DTxt(V, "миллиард", "миллиарда", "миллиардов")
Case Else
tmp = "#"
End Select
If I > 0 Then Z = Z + tmp + " "
Next I
IntTxt = Z
End Function


page17

Функция LetterSum является дополнением к функции IntTxt.
Печатает названия денежных единиц.

Function LetterSum(sum As Currency) As String
Dim R As Long, K As Integer, tmp As Integer
Dim Z As String, ZR As String, ZK As String
Const RKdiv = "  "
R = Int(sum)
K = (sum - R) * 100
ZR = IntTxt(R) + " " + DTxt(R, "рубль", "рубля", "рублей")
ZK = Format$(K, "00") + " " + DTxt(K, "копейка", "копейки", "копеек")
Z = ZR & RKdiv & ZK
Z = UCase$(Mid$(Z, 1, 1)) + Mid$(Z, 2)
LetterSum = Z
End Function
------------------------------------------------------------------------------------
Function DTxt(Number As Variant, W0 As String, W1 As String, W2 As String) As String
Dim tmp As String, Z As Integer
tmp = Trim$(Str$(Number))
Z = Val(Right$(tmp, 2))
If Z < 20 Then
    Select Case Z
      Case 0, 5 To 19
        tmp = W2
      Case 1
        tmp = W0
      Case 2, 3, 4
        tmp = W1
    End Select
  Else
    Select Case (Z - 10 * Int(Z / 10))
      Case 0, 5 To 9
        tmp = W2
      Case 1
        tmp = W0
      Case 2, 3, 4
        tmp = W1
    End Select
End If
DTxt = tmp
End Function


page18

 Function HyphText(D As Control, Text As String, HyphWidth As Single, HyphZone As Variant) As String
'-----------------------------------------------------------------------
'D - Элемент типа PictureBox, в котором размещается текст
'Text - Текст, который надо перенести
'HyphWidth - Ширина, в которую надо вместить текст
'HyphZone - Величина зоны, насколько можно изменять
' HyphWidth, чтобы текст переносился по словам.
' Если равна 0, то текст будет переносится
' "насильно". Если HyphZone задан целым числом
' (Integer или Long), то ширина задана в символах,
' если тип данных Single или Double - то в
' текущих единицах измерения.
'------------------------------------------------------------------------
Dim HyphMode As Integer
If D.TextWidth(Text) <= HyphWidth Then HyphText = Text: Exit Function
Select Case VarType(HyphZone)
Case 2, 3
HyphMode = 1
Case 4, 5, 6
HyphMode = 2
Case Else
HyphMode = 0
HyphZone = 0
End Select
Dim I As Integer, Z As String, txt As String
Dim P As Integer, W As Single
ReDim s(1 To 1) As String
txt = Text
If D.TextWidth(txt) <= HyphWidth Then
HyphText = txt
Exit Function
End If
P = 0: W = 0
Z = ""
Do
If Mid(txt, Len(Z) + 1 * Abs(Len(Z) = 0), 1) = " " Then P = Len(Z): W = D.TextWidth(Z)
If D.TextWidth(Z) <= HyphWidth Then
If Len(Z) < Len(txt) Then
Z = Z & Mid$(txt, Len(Z) + 1, 1)
Else
s(UBound(s)) = Z
txt = ""
End If
Else
Z = Left$(Z, Len(Z) - 1)
Select Case HyphMode
Case 0
s(UBound(s)) = RTrim$(Z)
ReDim Preserve s(1 To UBound(s) + 1)
txt = LTrim$(Mid$(txt, Len(Z) + 1))
Z = ""
Case 1
If P > 0 Then If (Len(Z) - P) <= HyphZone Then Z = Left$(Z, P)
s(UBound(s)) = RTrim$(Z)
ReDim Preserve s(1 To UBound(s) + 1)
txt = LTrim$(Mid$(txt, Len(Z) + 1))
Z = ""
Case 2
If P > 0 Then If (D.TextWidth(Z) - W) <= HyphZone Then Z = Left$(Z, P)
s(UBound(s)) = RTrim$(Z)
ReDim Preserve s(1 To UBound(s) + 1)
txt = LTrim$(Mid$(txt, Len(Z) + 1))
Z = ""
End Select
End If
If txt = "" Then Exit Do
Loop
Z = "": For I = LBound(s) To UBound(s) - 1: Z = Z + s(I) + Chr$(10): Next I: Z = Z + s(UBound(s))
HyphText = Z
End Function


VB с азов. Печать отчетов

Формирование и вывод отчетов на принтер

Одним из наиболее важных и трудоемких процессов в написании программ на VB является создание твердых копий документов. Существуют следующие способы создания отчетов:
1. Cоздание отчетов с помощью Crystal Reports.
2. Создание отчетов с использованием генератора отчетов приложения Access 97.
3. Создание отчетов с использованием непосредственной пересылки команд принтеру.
4. Создание отчетов с использованием объекта Printer языка Visual Basic.

1.Crystal Reports.
Наиболее популярный способ, позволяющий довольно быстро создавать бланк отчета и в процессе выполнения программы выводить его на печать с заполнением данными из базы данных. Недостатки - бланк отчета формируется заранее. Если у Вас например, в процессе работы программы возникает необходимость выводить на печать сотни таблиц с изменяющимся количеством столбцов в зависимости от выбранных пользователем выборок из баз данных - это способ будет очень неудобен. Ко всему прочему на старых принтерах возникают проблемы с получением отчетов, точно воспроизводящих графическую часть - разделительные линии по отношению к тексту не всегда на своих местах. Способы использования Crystal Reports многократно описаны в литературе, поэтому мы не будем здесь подробно на них останавливаться.

2.Генератор отчетов из Access 97.
Очень неплохой генератор отчетов, но при его использовании из VB5 для вывода отчета на печать необходимо иметь на машине Access, что далеко не всегда удобно и приемлемо. Вызов готового отчета из VB осуществляется довольно просто:
Private Sub cmdReport_Click()
Dim Report As New Access.Application
Report.OpenCurrentDatabase ("C:\...путь к базе данных...")
'Запуск в Access формы, запускающей отчет
Report.DoCmd.OpenForm "ReportDialog", acNormal
Report.Visible=True

3.Непосредственная пересылка данных на принтер.
Применяется для вывода на принтер растровых графических изображений через порты LPT1, LPT2 с помощью кодов PSL (Printer Control Language - язык управления принтером, который можно использовать на HP Lazer Jet 2 и более поздних моделях). Изображения разбиваются на маленькие составные части и передаются на принтер как набор точек.
Кроме того существует способ непосредственной записи в порты LPT1,LPT2, контролируемыe операционной системой с помощью оператора Print #, аналогично записи в обычный текстовый файл. При использовании этих методов есть риск привязать программу к параметрам конкретного принтера.

4.Использование объекта Printer языка Visual Basic.
А вот на этом способе мы остановимся подробнее, поскольку он хотя и несколько трудоемок, но позволяет программным способом "на лету" сформировать практически любой текстовый или табличный отчет.
Для начала перечислим свойства и методы объекта Printer.

Методы объекта Printer
Метод | Описание
Circle | Рисование дуг, эллипсов, окружностей
EndDoc | Завершение печати документа
KillDoc | Удаление документа из очереди на печать
Line | Рисование линий с учетом установок ScaleMode
NewPage | Прогон текущей страницы и начало новой (установка координат в левый верхний угол)
PaintPicture | Вывод изображения из графического файла
Print | Печать текстовой строки с текущими установками
PSet | Печать точки текущим цветом
Scale | Задание пользовательской системы координат
ScaleX | Преобразование X-координаты в единицы, определяемые ScaleMode
ScaleY | Преобразование Y-координаты в единицы, определяемые ScaleMode
TextHeight | Установка высоты шрифта в координатной системе, определяемой Scale
TextWidth | Установка ширины шрифта в координатной системе, определяемой Scale
Свойства объекта Printer
Свойство | Описание
ColorMode | Устанавливает цветную или монохромную печать
Copies | Устанавливает количество копий, которые должны быть напечатаны
Count | Количество принтеров в коллекции Printers
CurrentX | Устанавливает текущую координату X c использованием текущих установок ScaleMode
CurrentY | Устанавливает текущую координату Y c использованием текущих установок ScaleMode
DeviceName | Возвращает имя устройства
DrawMode | Определяет вид выводимых линий и объектов
DrawStyle | Стиль выводимых линий при печати графики
DrawWidth | Толщина линий
DriverName | Возвращает имя драйвера устройства для объекта Printer
Duplex | Определяет, должна ли печататься информация на обеих сторонах листа
FillColor | Цвет, используемый для заполнения фигур
FillStyle | Стиль заливки при заполнении фигур
Font | Объект шрифта
FontBold | Полужирный шрифт (True/False)
FontCount | Количество доступных шрифтов
FontItalic | Курсив (True/False)
FontName | Наименование используемого шрифта
Fonts | Список имен доступных шрифтов (от 0 до FontCount-1)
FontSize | Размер текущего шрифта в точках
FontStrikethru | Перечеркнутый шрифт (True/False)
FontTransparent | Прозрачный шрифт (True/False)
FontUnderline | Подчеркнутый шрифт (True/False)
ForeColor | Цвет переднего плана
hDC | Дескриптор контекста устройства
Height | Высота вывода устройства (в твипсах)
Orientation | Ориентация страницы в принтере
Page | Номер текущей страницы
PaperBin | Источник бумаги
PaperSize | Размер бумаги
Port | Возвращает имя используемого порта
PrintQuality | Разрешение печати
ScaleHeight | Вертикальный размер объекта в единицах, определяемых ScaleMode
ScaleLeft | Левый угол объекта в единицах, определяемых ScaleMode
ScaleMode | Определяет единицы системы координат
ScaleTop | Верхняя граница объекта в единицах, определяемых ScaleMode
ScaleWidth | Горизонтальный размер объекта в единицах, определяемых ScaleMode
TrackDefault | Определяет, будет ли всегда использоваться принтер, который установлен в системе по умолчанию
TwipsPerPixelX | Количество твипсов на пиксель по горизонтали
TwipsPerPixelY | Количество твипсов на пиксель по вертикали
Width | Ширина вывода устройства (в твипсах)
Zoom | Масштабирование при выводе

Печать текста с использованием объекта Printer осуществляется следующим образом: задаются координаты печати с помощью свойств CurrentX и CurrentY, а затем используется метод Print.
Пример печати в абсолютных координатах:
Printer.CurrentX = 1440
Printer.CurrentY = 1440
Printer.Print "Проблемы печати отчетов"
'Физическое выполнение печати и выдача листа бумаги
Printer.EndDoc
Но обычно гораздо удобнее принудительно разделить весь лист на зоны печати и при позиционировании использовать ScaleHeight и ScaleWidth, т.е. относительные координаты.

Dim X, Y, c As Integer
Dim title As String
title="Проблемы печати отчетов"
'Установка зон печати - лист делится на 100 зон по горизонтали
'и 50 зон по вертикали
x = Printer.ScaleWidth * 0.01
Y = Printer.ScaleHeight * 0.02
'Установка текущего шрифта
Printer.FontName = "Arial cyr"
'Установка размера шрифта
Printer.FontSize = 8
'Вычисление координаты X для печати заголовка по центру листа
Printer.CurrentX = (Printer.ScaleWidth - Printer.TextWidth(title)) / 2
Printer.CurrentY = Y * c
Printer.Print title
'Увеличение значения счетчика строк на единицу
c = c + 1
'Увеличение толщины линии
Printer.DrawWidth = 2
'Печать горизонтальной линии длиной с 8 до 94 позиции
Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
Printer.DrawWidth = 1
Printer.EndDoc
Дополнительно можно посмотреть примеры печати отчетов в разделе "Кирпичики".


VB с азов. Печать отчетов

Печать отчетов. Окончание.
Мне хотелось продемонстрировать вам возможности объекта Printer и я выбрал для этого свой исходный код, позволяющий "на лету" формировать разнообразные отчеты из DBGrid, заполняемой меняющимися SQL-запросами, в зависимости от выбранных пользователем условий. Собственно говоря, на форме два элемента DBGrid: основной Grid и Grid2, в который также SQL-запросом выводится итоговая сумма по одному из полей отчета.
Этот фрагмент кода позволяет сформировать многостраничный табличный отчет, рассчитав неизвестные заранее количество и ширину столбцов для формата бумаги A4, выводит на печать заголовок Grid, названия и значения полей, наименование и значение итоговой суммы из Grid2. Ограничение - в этом варианте записи в полях таблицы могут выводиться только в одну строку (т.е. есть они не должны быть длинными), количество столбцов программно ограничено 15-ю, но фактически это значение находится в зависимости от длины текста в полях таблицы, что желательно учитывать при формировании запросов.
Думаю, этот фрагмент придется кстати тем, кто работает с базами данных, так как универсальные контролы типа RoboPrint далеко не всем российским программистам по карману. Для того, чтобы посмотреть, как он работает - разместите на форме два элемента DBGrid и 2 элемента Data, кнопку "Печать". Назовите их, соответственно, Grid, Grid2, DataReport, последний - как угодно. Свяжите Grid c DataReport, Grid2 - со вторым элементом Data.
Все, остается синхронно для обоих элементов Data менять SQL-запросы к вашей базе данных (во втором использовать "SELECT SUM(ИмяПоля) As [Итого за месяц, год и т.д. ... :] FROM ... WHERE ..." ) и нажимать на кнопку...
Сама процедура приведена здесь.

Аналогичный пример отчета для элемента MSFlexGrid. Здесь используется многостраничная печать на бумаге формата A4 в "ландшафт". Группы строк предваряются заголовками, печатающимися через всю таблицу длинной строкой. Текст заголовков должен быть заранее сформирован в MSFlexGrid в левом крайнем столбце для строк, начинающих группу строк. Завершает отчет итоговая строка. В остальном используются те же самые механизмы расчета количества и ширины столбцов с коррекцией их под свойства MSFlexGrid. В отличие от предыдущего фрагмента для формирования отчета используется только элемент MSFlexGrid, названный Grid и его свойства. Сам Grid может заполняться посредством SQL-запросов, изначально количество столбцов в нем не известно.
Процедура приведена здесь.

на предыдущую страницу


Кирпичики. Сумма прописью

Сумма прописью

Public Function Num3(trojka$, i%)
    Dim sl$(1 To 3, 0 To 3)
    sl$(1, 1) = "миллион "
    sl$(2, 1) = "тысяча "
    sl$(3, 1) = "рубль "
    '--------------------------------------
    sl$(1, 2) = "миллиона "
    sl$(2, 2) = "тысячи "
    sl$(3, 2) = "рубля "
    '--------------------------------------
    sl$(1, 3) = "миллионов "
    sl$(2, 3) = "тысяч "
    sl$(3, 3) = "рублей "
    sl$(3, 0) = "рублей "
    '--------------------------------------
    ed$ = Right$(trojka$, 1)
    des$ = Mid$(trojka$, 2, 1)
    sot$ = Left$(trojka$, 1)
    '--------------------------------------
    If ed$ = "0" Then r3$ = ""
    If ed$ = "1" Then If i% = 2 Then r3$ = "одна " Else r3$ = "один "
    If ed$ = "2" Then If i% = 2 Then r3$ = "две " Else r3$ = "два "
    If ed$ = "3" Then r3$ = "три "
    If ed$ = "4" Then r3$ = "четыре "
    If ed$ = "5" Then r3$ = "пять "
    If ed$ = "6" Then r3$ = "шесть "
    If ed$ = "7" Then r3$ = "семь "
    If ed$ = "8" Then r3$ = "восемь "
    If ed$ = "9" Then r3$ = "девять  "
    '--------------------------------------
    If des$ = "0" Then r2$ = ""
    s$ = des$ & ed$
    If s$ = "10" Then r3$ = "десять "
    If s$ = "11" Then r3$ = "одиннадцать "
    If s$ = "12" Then r3$ = "двенадцать "
    If s$ = "13" Then r3$ = "тринадцать "
    If s$ = "14" Then r3$ = "четырнадцать "
    If s$ = "15" Then r3$ = "пятнадцать "
    If s$ = "16" Then r3$ = "шестнадцать "
    If s$ = "17" Then r3$ = "семнадцать "
    If s$ = "18" Then r3$ = "восемнадцать "
    If s$ = "19" Then r3$ = "девятнадцать "
    '--------------------------------------
    If des$ = "2" Then r2$ = "двадцать "
    If des$ = "3" Then r2$ = "тридцать "
    If des$ = "4" Then r2$ = "сорок "
    If des$ = "5" Then r2$ = "пятьдесят "
    If des$ = "6" Then r2$ = "шестьдесят "
    If des$ = "7" Then r2$ = "семьдесят "
    If des$ = "8" Then r2$ = "восемьдесят "
    If des$ = "9" Then r2$ = "девяносто "
    '--------------------------------------
    If sot$ = "0" Then r1$ = ""
    If sot$ = "1" Then r1$ = "сто "
    If sot$ = "2" Then r1$ = "двести "
    If sot$ = "3" Then r1$ = "триста "
    If sot$ = "4" Then r1$ = "четыреста "
    If sot$ = "5" Then r1$ = "пятьсот "
    If sot$ = "6" Then r1$ = "шестьсот "
    If sot$ = "7" Then r1$ = "семьсот "
    If sot$ = "8" Then r1$ = "восемьсот "
    If sot$ = "9" Then r1$ = "девятьсот "
    '--------------------------------------
    If trojka$ <> "000" Then j% = (-1) * CInt(ed$ = "1" And des$ <> "1") _
    + (-2) * CInt((ed$ = "2" Or ed$ = "3" Or ed$ = "4") And des$ <> "1")
    If j% = 0 And trojka$ <> "000" Then j% = 3
    trojka$ = r1$ & r2$ & r3$ & sl$(i%, j%) 'формирование тройки цифр и
    'слова,например-"123 тысячи"
End Function
'*********************************************************************

Private Sub cmdinput_Click()
    w$ = txtinput.Text 'Входное число - текст типа "123.45",защита
    '"от дурака" не сделана
    'выделение рублей в записи числа и удаление левых пробелов
    rubli$ = LTrim$(Left$(Str(Val(w$) * 100), _
    Len(Str(Val(w$) * 100)) - 2))
    cop$ = RTrim$(Right$(Str(Val(w$) * 100), 2)) 'выделение дробной части
    'числа и удаление правых пробелов

   Do While Len(rubli$) < 9
      rubli$ = "0" & rubli$
   Loop
      res$ = ""
   For i% = 1 To 3
      trojka$ = Mid$(rubli$, 3 * i% - 2, 3)
      Call Num3(trojka$, i%) ' Вызов функции формирования готовой тройки,
      'типа "123 тысячи"
      res$ = res$ & trojka$  ' Накопление таких троек
   Next i%
      res$ = UCase$(Left$(res$, 1)) & Right$(res$, Len(res$) - 1) 'Запись
      'первой буквы res$ в верхнем регистре

      c$ = " копеек" ' Блок добавления копеек
      If (Right$(cop$, 1) = "1" And Left$(cop$, 1) <> "1") Then c$ = _
      " копейка"
      If ((Right$(cop$, 1) = "2" Or Right$(cop$, 1) = "3" Or _
      Right$(cop$, 1) = "4") And Left$(cop$, 1) <> "1") Then _
      c$ = " копейки"
      If Left(res$, 1) <> "Р" Then res$ = res$ & cop$ & c$ Else _
      res$ = cop$ & c$

      txtout.Text = res$ ' Выход текста
End Sub


Кирпичики. Шрифты, доступные объекту Printer

Определение шрифтов, доступных объекту Printer
'*******************************************************
'Определение шрифтов, доступных объекту Printer
'*******************************************************
'на форме текстбокс Text1 (свойство MultiLine=True) и кнопка Command1

Private Sub Command1_Click()
    Dim Str As String
    Dim x As Integer
    For x = 0 To Printer.FontCount - 1
    Str = Printer.Fonts(x)
    Text1.Text = Text1.Text & x & " " & Str & ", "
    Next x
End Sub


Кирпичики. Операции с файлами

Операции с файлами

'******************************
'    Чтение бинарного файла
'******************************

'Назначаем дескриптор файла
nFileHandle=FreeFile
'Открываем бинарный файл
Open "C:\TEST.BIN" For Binary As #nFileHandle
'Подготовить строку из 20 пробелов
МуString=String(20," ")
'Прочитать 20 байт из позиции 50 файла
Get #nFileHandle, 50, MyString
'Закрыть файл
Close nFileHandle

'Для прочтения всего файла можно
'вместо строки с Get использовать комбинацию на основе цикла Do While...Loop,
'которая считывает в str1 по одному символу и накапливает
'их в str2:

Do While Not EOF (nFileHandle)
    Get #nFileHandle, str1
    str2=str2 & str1
Loop

'*******************************
'    Запись в текстовый файл
'*******************************
    Dim FileName As String
    Dim Str, Str2 As String
    Str = Text1.Text
    'Открытие файла и запись информации из Str в файл
    FileName = "C:\proba.txt"
    Open FileName For Output As 1
    Print #1, Str
    Close 1
'*******************************
'    Чтение текстового файла
'*******************************
    Open FileName For Input As 1
    Str2 = Input(LOF(1), 1)
    Text2.Text = Str
    Close 1


Кирпичики. Процедура печати из DBGrid

Процедура печати из DBGrid

Private Sub cmdPrint_Click()
    '***********************************************************
    '*           Процедура печати данных из DBgrid             *
    '***********************************************************
    Dim c As Integer 'Номер строки
    Dim Dob ' Добавочная поправка к ширине столбца для привязки
    'к ширине страницы
    Dim FlagStrWrap(15) As Integer ' Флаг переноса строки
    Dim i As Integer ' Номер записи в таблице
    Dim NumDob As Integer
    Dim OStrWidth
    Dim sss(15)
    Dim Str As String
    Dim StrWidth(15) ' Ширина столбца
    Dim StrWrap As String
    Dim StrWrapOst As String
    Dim StWidth(15)
    Dim title As String ' Заголовок таблицы
    Dim w  As Integer ' Позиция пробела
    Dim x As Integer 'Координата позиции печати X
    Dim xRom As Integer ' Хранит приращение координаты X при
    'переносе строки
    Dim xx As Integer  'Приращение координаты X
    Dim Y As Integer 'Координата позиции печати Y
    Dim z As Integer ' Номер столбца (в 1-м цикле)
    Dim zz As Integer ' Номер столбца (во 2-м вложенном цикле)
    Dim zzz As Integer ' Номер столбца (в 3-м вложенном цикле)

    MousePointer = 11
    'Задание пользовательской системы координат
    x = Printer.ScaleWidth * 0.01
    Y = Printer.ScaleHeight * 0.02
    'Установка свойств объекта Printer
    Printer.FontName = "Arial Cyr"
    Printer.FontSize = 10
    Printer.FontBold = False
    '***************************************
    'Предварительный расчет ширины столбцов
    '***************************************
    'Считывание названий полей из DBGrid
    For z = 0 To Grid.Columns.Count - 1 Step 1
    StrWidth(z) = Grid.Columns(z).DataField
    Next z
    'Измерение максимальной ширины текстовых данных в
    'столбцах DBGrid
    DataReport.Recordset.MoveFirst
    For i = 0 To DataReport.Recordset.RecordCount - 1
    For z = 0 To Grid.Columns.Count - 1 Step 1
    Str = DataReport.Recordset.Fields(Grid.Columns(z).DataField).Value
    If Len(Str) > Len(StrWidth(z)) Then
        'Проверка на превышение допустимой длины строки

        If Printer.TextWidth(Str) > 25 * x Then

            StrWrap = Left(Str, InStr(w + 1, Str, " ", vbTextCompare) - 1)
VarWidth:
            If Printer.TextWidth(StrWrap) < 25 * x Then
                w = InStr(w + 1, Str, " ") ' следующая позиция пробела
                StrWrap = Left(Str, InStr(w + 1, Str, " ", vbTextCompare) - 1)

                If Not InStr(w + 1, Str, " ", vbTextCompare) - 1 = 0 Then GoTo VarWidth
            End If
            StrWidth(z) = StrWrap
            GoTo 610
        End If
        StrWidth(z) = Grid.Columns(z).Text
610 End If
    Next z
    DataReport.Recordset.MoveNext
    Next i
    DataReport.Recordset.MoveFirst
    Str = 0
    c = 1
    xx = 12

    For z = 0 To Grid.Columns.Count - 1 Step 1
    StWidth(z) = Printer.TextWidth(StrWidth(z))
    OStrWidth = OStrWidth + StWidth(z)
    Next z
    'Учет незадействованной ширины листа и равномерное
    'распределение ее по столцам
    Dob = (Printer.ScaleWidth - OStrWidth - 1847) / _
(Grid.Columns.Count - 1)
    If Dob < 1 Then GoTo 10
    NumDob = (Printer.ScaleWidth - OStrWidth - 1847) / Dob
    For z = 0 To NumDob Step 1
    StWidth(z) = StWidth(z) + Dob
    Next z
    '*********************
    'Формирование таблицы
    '*********************
    'Печать заголовка таблицы Grid
10  xx = 8
    title = Grid.Caption
    c = c + 1
    Printer.FontSize = 8

    Printer.CurrentX = (Printer.ScaleWidth - Printer.TextWidth(title)) / 2
    Printer.CurrentY = Y * c
    Printer.Print title
    c = c + 1
    'Печать вертикальной линии шапки таблицы
    Printer.DrawWidth = 2
    Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
    Printer.DrawWidth = 1
    'Печать названий столбцов
    For z = 0 To Grid.Columns.Count - 1 Step 1
    sss(z) = Grid.Columns(z).DataField
    Printer.CurrentX = x * (xx + 0.4)
    Printer.CurrentY = Y * (c + 0.2)
    Printer.Print sss(z)
    xx = xx + (StWidth(z) / (Printer.ScaleWidth / 100))
    Next z
    xx = 8
    'Печать вертикальных разделительных линий
    For z = 0 To Grid.Columns.Count - 1 Step 1
    If z = 0 Then
        Printer.DrawWidth = 2
    Else
        Printer.DrawWidth = 1
    End If
    Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
    xx = xx + (StWidth(z) / (Printer.ScaleWidth / 100))
    Next z
    Printer.DrawWidth = 2
    Printer.Line (x * 94, Y * c)-(x * 94, Y * (c + 1))
    Printer.DrawWidth = 1
    c = c + 1
    xx = 8
    '**************
    'Печать данных
    '**************
    DataReport.Recordset.MoveFirst
    For i = 0 To DataReport.Recordset.RecordCount - 1
    For z = 0 To Grid.Columns.Count - 1 Step 1
'******************************************************
    'Проверка, не превысило ли количество строк назначенное
    'для страницы значение
    If c > 38 Then
        'Завершение текущей страницы
        Printer.CurrentX = x * 84
        Printer.CurrentY = Y * (39.1)
        Printer.Print "Стр. "; Printer.page
        Printer.DrawWidth = 2
        Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
        Printer.DrawWidth = 1
        '**********************
        'Начало новой страницы
        '**********************
        Printer.NewPage
        c = 1
        'Печать верхней линии шапки таблицы
        Printer.DrawWidth = 2
        Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
        Printer.DrawWidth = 1
        'Печать названия столбцов
        For zz = 0 To Grid.Columns.Count - 1 Step 1
        sss(zz) = Grid.Columns(zz).DataField
        Printer.CurrentX = x * (xx + 0.4)
        Printer.CurrentY = Y * (c + 0.2)
        Printer.Print sss(zz)
        xx = xx + (StWidth(zz) / (Printer.ScaleWidth / 100))
        Next zz
        xx = 8
        'Печать вертикальных разделительных линий
        For zz = 0 To Grid.Columns.Count - 1 Step 1
        If zz = 0 Then
            Printer.DrawWidth = 2
        Else
            Printer.DrawWidth = 1
        End If
        Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
        xx = xx + (StWidth(zz) / (Printer.ScaleWidth / 100))
        Next zz
        'Печать крайней правой вертикальной линии
        Printer.DrawWidth = 2
        Printer.Line (x * 94, Y * c)-(x * 94, Y * (c + 1))
        Printer.DrawWidth = 1
        c = c + 1
        xx = 8
    End If
    'Окончание цикла, предназначенного для перехода на новую
    'страницу
'************************************************************

    'печать данных
    Printer.CurrentX = x * (xx + 0.4)
    Printer.CurrentY = Y * (c + 0.2)
    Str = DataReport.Recordset.Fields(Grid.Columns(z) _
.DataField).Value
    'проверка, не превышает ли длина данных в строке
    'величину в 25 позиций печати для последующей
    'организации переноса оставшейся части строки
    If Printer.TextWidth(Str) > 25 * x Then
Transportation:
        StrWrap = Left(Str, InStr(w + 1, Str, " ") - 1)
        If Printer.TextWidth(StrWrap) < 25 * x Then
            w = InStr(w + 1, Str, " ")
            GoTo Transportation
        End If
        Str = StrWrap
        'Установка флага переноса строки
        FlagStrWrap(z) = 1
        xRom = xx
        StrWrapOst = Mid(DataReport.Recordset.Fields(Grid.Columns(z) _
        .DataField).Value, Len(Str) + 1)

    Else
        FlagStrWrap(z) = 0
    End If
    Printer.Print Str

    xx = xx + (StWidth(z) / (Printer.ScaleWidth / 100))
    Next z

    'Печать вертикальных разделительных линий
    xx = 8
    For z = 0 To Grid.Columns.Count - 1 Step 1
    If z = 0 Then
        Printer.DrawWidth = 2
    Else
        Printer.DrawWidth = 1
    End If
    Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
    xx = xx + (StWidth(z) / (Printer.ScaleWidth / 100))
    Next z
    Printer.DrawWidth = 2
    Printer.Line (x * 94, Y * c)-(x * 94, Y * (c + 1))
    Printer.DrawWidth = 1
    Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
    c = c + 1

    DataReport.Recordset.MoveNext

'*****************************************
'Печать остатка строки
'*****************************************

    For zz = 0 To Grid.Columns.Count - 1 Step 1
    If FlagStrWrap(zz) = 1 Then
        xx = xRom
        Printer.CurrentX = x * (xx + 0.4)
        Printer.CurrentY = Y * (c + 0.2)
        Printer.Print StrWrapOst

        xx = 8
        For zzz = 0 To Grid.Columns.Count - 1 Step 1
        If zzz = 0 Then
            Printer.DrawWidth = 2
        Else
            Printer.DrawWidth = 1
        End If
        Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
        Printer.DrawWidth = 2
        Printer.Line (x * 94, Y * c)-(x * 94, Y * (c + 1))
        Printer.DrawWidth = 1

        xx = xx + (StWidth(zzz) / (Printer.ScaleWidth / 100))
        Next zzz
        c = c + 1

    End If
    Next zz
    xx = 8

    Next i
    DataReport.Recordset.MoveFirst
    Printer.DrawWidth = 1
    Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
    'Печать суммирующей строки из DBGrid Grid2
    For z = 0 To Grid.Columns.Count - 1 Step 1
    If z = Grid.Columns.Count - 3 Then
        Printer.CurrentX = x * (xx + 0.4)
        Printer.CurrentY = Y * (c + 0.2)
        'Печать названия итоговой величины из DBGrid Grid2
        Printer.Print Grid2.Columns(0).DataField
    End If
    If z = Grid.Columns.Count - 1 Then
        Printer.CurrentX = x * (xx + 0.4)
        Printer.CurrentY = Y * (c + 0.2)
        'Печать итоговой суммы из DBGrid Grid2
        Printer.Print Grid2.Columns(0).Text
    End If
    xx = xx + (StWidth(z) / (Printer.ScaleWidth / 100))
    Next z
    Printer.DrawWidth = 2
    Printer.Line (x * 8, Y * c)-(x * 8, Y * (c + 1))
    Printer.Line (x * 94, Y * c)-(x * 94, Y * (c + 1))
    c = c + 1
    Printer.Line (x * 8, Y * c)-(x * 94, Y * c)
    Printer.DrawWidth = 1
    Printer.CurrentX = x * 84
    Printer.CurrentY = Y * 39.1
    Printer.Print "Стр. "; Printer.page
    'Завершение построения отчета и физическая печать
    MousePointer = 0
    Printer.EndDoc
PrintError:
    MousePointer = 0

End Sub


Кирпичики. Границы печати.

Определение границ печати

'При написании процедуры печати столкнулся как-то я с проблемой:
'у любого принтера есть так называемые границы печати,т.е области
'по краям , которые "лежат за границей досягаемости".
'Причем в зависимости от марки принтера и конкретной стороны листа значения
'колеблются в диапазоне от 4 до 13 мм.
' Чтобы ваш отчет на разных принтерах выглядел одинаково,
'(в смысле размещения относительно краев листа),
'есть простой способ определить минимальные (собственные) поля принтера.
Private Const HORZSIZE = 4
Private Const VERTSIZE = 6
Private Const HORZRES = 8
Private Const VERTRES = 10
Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90
Private Const PHYSICALHEIGHT = 111
Private Const PHYSICALOFFSETX = 112
Private Const PHYSICALOFFSETY = 113
Private Const PHYSICALWIDTH = 110

Private Const DD As Single = 25.4
Private Const C_MARGIN = 1440 / DD
'точные значения  минимальных полей в мм из LoadInfo
Private Min_Margleft As Single
Private Min_MargUp As Single
Private Min_MargRigt As Single
Private Min_MargDown As Single
'получены из вашего диалога "page setup"(т.е. желаемые поля).
Private MM_Left As Long
Private MM_Rigt As Long
Private MM_Up As Long
Private MM_Down As Long

Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, _
    ByVal nIndex As Long) As Long
'**************************************************
Public Sub LoadInfo(nDC As Long)
Dim nDev As Long
Dim xOFSET, yOFSET, xWp, yHp, xDPI, _
    yDPI, xRES, yRES

nDev = nDC

xDPI = GetDeviceCaps(nDev, LOGPIXELSX) '600"X,Y Pixels/Logical Inch:"
yDPI = GetDeviceCaps(nDev, LOGPIXELSY) '600(разрешение)

xOFSET = GetDeviceCaps(nDev, PHYSICALOFFSETX) '130"Physical X/Y Margin"
yOFSET = GetDeviceCaps(nDev, PHYSICALOFFSETY) '150(отступы)

xWp = GetDeviceCaps(nDev, PHYSICALWIDTH) '4962
yHp = GetDeviceCaps(nDev, PHYSICALHEIGHT) '7014"Physical Height/Width"

xRES = GetDeviceCaps(nDev, HORZRES) '4676"X,Y Dimensions in pixels:"
yRES = GetDeviceCaps(nDev, VERTRES) '6744

'результат в миллиметрах
Min_Margleft = xOFSET / (xDPI / DD)
Min_MargUp = yOFSET / (yDPI / DD)
Min_MargRigt = (xWp - xOFSET - xRES) / (xDPI / DD)
Min_MargDown = (yHp - yOFSET - yRES) / (yDPI / DD)
'чтобы гарантировать "правильные" поля сами округлите ...
End Sub

'******************************************************
'как использовать-тo ?!

 LoadInfo Printer.hdc
 MarginLeft = 30 'в миллиметрах
 MarginRight = 10
 MarginTop = 20
 MarginBottom = 20

'*******************************************************
Public Property Get MarginLeft() As Long
MarginLeft = MM_Left
End Property

'*******************************************************
Public Property Let MarginLeft(ByVal nMargin As Long)
'Dim newval As Single, mmval As Long
If nMargin < Min_Margleft Then
MM_Left = Min_Margleft
Else
MM_Left = nMargin
End If
m_MarginLeft = (MM_Left - Min_Margleft) * C_MARGIN
End Property
'для остальных полей аналогично!
' m_MarginLeft недостающее расстояние которое необходимо учесть
' в процедуре печати коих полно всяких разных ,потому здесь не привожу.


Кирпичики. Границы печати.

Отправка почты из VB-приложения

'***************************************************************
' Name: SMTP: Simple Mail Testing Program
' Description:Allows sending of e-mail (SMTP) directly from a VB
'     app using Winsock, WITH OUT having to buy an expensive add on component
' By: Brian Anderson
'
'
' Inputs:Requires: Server Address (Name or IP), Senders & Recipei
'     ent's Names, Sender & Recipient E-Mail address, Body of message
'
' Returns:Nothing really, does give status on sending operation
'
'Assumes:Very straight forward. Makes sending mail from a VB prog
'     ram EASY!
'
'Side Effects:NONE!
'
'Code provided by Planet Source Code(tm) (http://www.PlanetSource
'     Code.com) 'as is', without warranties as to performance, fitness,
'     merchantability,and any other warranty (whether expressed or impl
'     ied).
'***************************************************************

Dim Response As String, Reply As Integer, DateNow As String
Dim first As String, Second As String, Third As String
Dim Fourth As String, Fifth As String, Sixth As String
Dim Seventh As String, Eighth As String
Dim Start As Single, Tmr As Single

Sub SendEmail(MailServerName As String, FromName As String, _
 FromEmailAddress As String, ToName As String, _
 ToEmailAddress As String, EmailSubject As String, _
 EmailBodyOfMessage As String)

    Winsock1.LocalPort = 0 ' Must set local port to 0 (Zero) or you can _
 only send 1 e-mail per program start

    If Winsock1.State = sckClosed Then ' Check to see if socet is closed
        DateNow = Format(Date, "Ddd") & ", " & Format(Date, "dd Mmm YYYY") _
 & " " & Format(Time, "hh:mm:ss") & "" & " -0600"
        first = "mail from:" + Chr(32) + FromEmailAddress + vbCrLf
' Get who's sending E-Mail address
        Second = "rcpt to:" + Chr(32) + ToEmailAddress + vbCrLf
' Get who mail is going to
        Third = "Date:" + Chr(32) + DateNow + vbCrLf
' Date when being sent
        Fourth = "From:" + Chr(32) + FromName + vbCrLf
' Who's Sending
        Fifth = "To:" + Chr(32) + ToNametxt + vbCrLf
' Who it going to
        Sixth = "Subject:" + Chr(32) + EmailSubject + vbCrLf
' Subject of E-Mail
        Seventh = EmailBodyOfMessage + vbCrLf
' E-mail message body
        Ninth = "X-Mailer: EBT Reporter v 2.x" + vbCrLf
' What program sent the e-mail, customize this
        Eighth = Fourth + Third + Ninth + Fifth + Sixth
' Combine For proper SMTP sending
        Winsock1.Protocol = sckTCPProtocol
' Set protocol For sending
        Winsock1.RemoteHost = MailServerName
' Set the server address
        Winsock1.RemotePort = 25
' Set the SMTP Port
        Winsock1.Connect
' Start connection

        WaitFor ("220")

        StatusTxt.Caption = "Connecting...."
        StatusTxt.Refresh

        Winsock1.SendData ("HELO yourdomain.com" + vbCrLf)
        WaitFor ("250")
        StatusTxt.Caption = "Connected"
        StatusTxt.Refresh
        Winsock1.SendData (first)
        StatusTxt.Caption = "Sending Message"
        StatusTxt.Refresh
        WaitFor ("250")
        Winsock1.SendData (Second)
        WaitFor ("250")
        Winsock1.SendData ("data" + vbCrLf)

        WaitFor ("354")
        Winsock1.SendData (Eighth + vbCrLf)
        Winsock1.SendData (Seventh + vbCrLf)
        Winsock1.SendData ("." + vbCrLf)
        WaitFor ("250")
        Winsock1.SendData ("quit" + vbCrLf)

        StatusTxt.Caption = "Disconnecting"
        StatusTxt.Refresh
        WaitFor ("221")
        Winsock1.Close
    Else
        MsgBox (Str(Winsock1.State))
    End If

End Sub

Sub WaitFor(ResponseCode As String)

    Start = Timer ' Time Event so won't Get stuck in Loop

    While Len(Response) = 0
        Tmr = Start - Timer

        DoEvents ' Let System keep checking For incoming response
' **IMPORTANT**

            If Tmr > 50 Then ' Time in seconds to wait
                MsgBox "SMTP service error, _
 timed out While waiting For response", 64, MsgTitle
                Exit Sub
            End If

        Wend

        While Left(Response, 3) <> ResponseCode

            DoEvents

                If Tmr > 50 Then
                    MsgBox "SMTP service error, impromper response code. _
 Code should have been: " + ResponseCode + " Code recieved: " + Response, _
 64, MsgTitle
                    Exit Sub
                End If

            Wend

            Response = "" ' Sent response code to blank **IMPORTANT**
        End Sub

Private Sub Command1_Click()

    SendEmail txtEmailServer.Text, txtFromName.Text, _
 txtFromEmailAddress.Text, txtToEmailAddress.Text, _
 txtToEmailAddress.Text, txtEmailSubject.Text, _
 txtEmailBodyOfMessage.Text
    'MsgBox ("Mail Sent")
    StatusTxt.Caption = "Mail Sent"
    StatusTxt.Refresh
    Beep

    Close
End Sub

Private Sub Command2_Click()

    End

End Sub

Private Sub Form_Load()

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)

    Winsock1.GetData Response ' Check For incoming response *IMPORTANT*
End Sub


Кирпичики. Отправка почты с аттачментом.

Отправка почты с аттачментом из VB-приложения

'***************************************************************
'Windows API/Global Declarations for :SendMail
'***************************************************************

Source Code:
Note:This code is formatted to be pasted directly into VB.
Pasting it into other editors may or may not work.

'***************************************************************
' Name: SendMail
' Description:This routine Sends mail with attachment to anybody
'     you specify
' By: Whatever
'
'
' Inputs:From : Sender (your profile)
To: Recipient

Subject:

    Text: Text Body
    UI: 0=open Mail User Interfase
    Atta: Attachment (separated by ;)
'
' Returns:None
'
'Assumes:None
'
'Side Effects:Be aware of passing all the parameters with data
'(atta is aptional)
'
'Code provided by Planet Source Code(tm) (http://www.PlanetSource
'     Code.com) 'as is', without warranties as to performance, fitness,
'     merchantability,and any other warranty (whether expressed or
'     implied).
'***************************************************************

Sub MSnAB(FromName As String, ToName As String, Subject As String, _
 Text As String, UI As Integer, Atta As String)

    Dim Count As Integer
    Static Address(0 To 30) As String
    On Error Goto MAILERROR
    MAPIAUX.MSESS.UserName = FromName
    MAPIAUX.MSESS.SignOn
    MAPIAUX.MMSG.SessionID = MAPIAUX.MSESS.SessionID
    MAPIAUX.MMSG.Compose
    Call ParseAddress(ToName, Count, Address())

    For I = 0 To Count - 1
        MAPIAUX.MMSG.RecipIndex = I
        MAPIAUX.MMSG.RecipType = mapToList
        MAPIAUX.MMSG.RecipDisplayName = Address(I)
        MAPIAUX.MMSG.ResolveName
    Next I

    MAPIAUX.MMSG.MsgSubject = Subject
    MAPIAUX.MMSG.MsgNoteText = Text & Chr$(13)

    If Trim$(Atta)<> "" And Dir(Trim$(Atta)) <>"" Then
        MAPIAUX.MMSG.AttachmentIndex =MAPIAUX.MMSG.AttachmentCount
        MAPIAUX.MMSG.AttachmentType = 0
        MAPIAUX.MMSG.AttachmentPathName = Trim$(Atta)
        MAPIAUX.MMSG.AttachmentPosition = Len(Text)
    End If

    If UI <> 0 Then
        MAPIAUX.MMSG.Send
    Else
        MAPIAUX.MMSG.Send True
    End If

    MAPIAUX.MSESS.SignOff
    Exit Sub
    MAILERROR:
    c = Err
    B = Error$
    MsgBox " Mail Function Error " & Error$
    MAPIAUX.MSESS.SignOff
End Sub

Sub ParseAddress (ANames As String, Count As Integer, Addrs() As String)

    Dim CPos As Integer
    Dim VPos As Integer
    Dim SPos As Integer
    I = 0
    SPos = 1
    CPos = 0

    Do
        CPos = InStr(ANames, ";")
        If CPos = 0 Then VPos = Len(ANames) + 1 Else VPos = CPos
        Addrs(I) = Mid$(ANames, SPos, VPos - SPos)
        I = I + 1
        ANames = Right$(ANames, Len(ANames) - CPos)
    Loop While CPos > 0

    Count = I
End Sub


Кирпичики. Процедура печати из MSFlexGrid

Процедура печати из MSFlexGrid

Private Sub cmdPrint_Click()
    Dim c As Integer
    Dim n  As Integer
    Dim p As Integer
    Dim page As Integer
    Dim i As Integer
    Dim x As Integer
    Dim Y As Integer
    Dim xx  As Integer
    Dim z As Integer
    Dim OStrWidth As Integer
    Dim Dob As Integer
    Dim StWidth(15) As Integer
    Dim NumDob As Integer
    Dim str As String
    Dim sss(15) As String
    Dim StrWidth(30) As String
    Dim title As String
    p = 1
    page = 1
    'Установка свойств объекта Printer
    Printer.Orientation = 2
    MousePointer = 11
    x = Printer.ScaleWidth * 0.01
    Y = Printer.ScaleHeight * 0.02
    Printer.PaperSize = 9
    Printer.FontName = "Times New Roman cyr"
    Printer.FontSize = 10
    Printer.DrawWidth = 2

    For z = 1 To Grid.Cols - 1 Step 1
    Grid.Row = 0
    Grid.Col = z
    StrWidth(z) = Grid.Text
    Next z

    'Печать заголовка
    xx = 4
    title = Label1.Caption
    Printer.CurrentX = x * 25
    Printer.CurrentY = Y * c
    Printer.Print title
    c = c + 1

    'Печать названий столбцов
    Printer.FontSize = 8
    Printer.DrawWidth = 2
    Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
    Printer.DrawWidth = 1

    For z = 1 To Grid.Cols Step 1
    If z = 1 Or z = Grid.Cols Then
        Printer.DrawWidth = 2
    Else
        Printer.DrawWidth = 1
    End If
    Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
    Printer.CurrentX = x * (xx + 0.2)
    Printer.CurrentY = Y * (c + 0.1)
    Printer.Print StrWidth(z)
    If z = 2 Then xx = xx + (84 / (Grid.Cols - 1)) -  _
(84 / (4 * (Grid.Cols - 1)))
    If z = 3 Then
        xx = xx + (84 / (Grid.Cols - 1)) + (84 / (4 * (Grid.Cols - 1)))
    Else
        If Not z = 2 Then xx = xx + (84 / (Grid.Cols - 1))
    End If
    Next z
    c = c + 1
    Printer.DrawWidth = 2
    Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
    Printer.DrawWidth = 1

    'Печать строк из MSFlexGrid
    i = 1
25  Grid.Row = i
    If c = 35 Then p = 1
    xx = 4
    Grid.Col = 0
            'Переход на новую страницу
            If c > 38 Then
            If p = 1 Then
                Printer.DrawWidth = 2
                Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
                Printer.CurrentX = x * 84
                Printer.CurrentY = Y * (39.1)
                Printer.Print "Стр. "; page

                Printer.NewPage
                p = p + 1
                page = page + 1
                c = 0
                Printer.FontName = "Times New Roman cyr"
                Printer.FontSize = 8
                Printer.DrawWidth = 2
                Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
                Printer.DrawWidth = 1

                For z = 1 To Grid.Cols Step 1
                If z = 1 Or z = Grid.Cols Then
                    Printer.DrawWidth = 2
                Else
                    Printer.DrawWidth = 1
                End If
                Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
                Printer.CurrentX = x * (xx + 0.2)
                Printer.CurrentY = Y * (c + 0.1)
                Printer.Print StrWidth(z)
                If z = 2 Then xx = xx + (84 / (Grid.Cols - 1)) -  _
(84 / (4 * (Grid.Cols - 1)))
                If z = 3 Then
                    xx = xx + (84 / (Grid.Cols - 1)) +  _
(84 / (4 * (Grid.Cols - 1)))
                Else
                    If Not z = 2 Then xx = xx + (84 / (Grid.Cols - 1))
                End If
                Next z
                c = c + 1
                Printer.DrawWidth = 2
                Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
                Printer.DrawWidth = 1
                xx = 4
            End If
        End If

    'Печать длинной строкой через всю таблицу названия группы
    If Not Grid.Text = "" Then
        If c > 36 Then
            ' Переход на новую страницу
            If p = 1 Then
                Printer.DrawWidth = 2
                Printer.Line (x * 4, Y * c)-(x * 88, Y * c)

                Printer.CurrentX = x * 84
                Printer.CurrentY = Y * (39.1)
                Printer.Print "Стр. "; page

                Printer.NewPage
                p = p + 1
                page = page + 1
                c = 0
                Printer.FontName = "Times New Roman cyr"
                Printer.FontSize = 8
                Printer.DrawWidth = 2
                Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
                Printer.DrawWidth = 1

                For z = 1 To Grid.Cols Step 1
                If z = 1 Or z = Grid.Cols Then
                    Printer.DrawWidth = 2
                Else
                    Printer.DrawWidth = 1
                End If
                Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
                Printer.CurrentX = x * (xx + 0.2)
                Printer.CurrentY = Y * (c + 0.1)
                Printer.Print StrWidth(z)
                If z = 2 Then xx = xx + (84 / (Grid.Cols - 1)) -  _
(84 / (3 * (Grid.Cols - 1)))
                If z = 3 Then
                    xx = xx + (84 / (Grid.Cols - 1)) +  _
(84 / (3 * (Grid.Cols - 1)))
                Else
                    If Not z = 2 Then xx = xx + (84 / (Grid.Cols - 1))
                End If
                Next z
                c = c + 1
                Printer.DrawWidth = 2
                Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
                Printer.DrawWidth = 1
                xx = 4

            End If
        End If
        Printer.DrawWidth = 2
        Printer.Line (x * 4, Y * c)-(x * 4, Y * (c + 1))
        Printer.Line (x * 88, Y * c)-(x * 88, Y * (c + 1))
        Printer.CurrentX = x * (xx + 0.2)
        Printer.CurrentY = Y * (c + 0.1)
        Printer.Print Grid.Text
        c = c + 1
        Printer.DrawWidth = 1
        Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
    End If

    For z = 1 To Grid.Cols - 1 Step 1
    Grid.Col = z
    If z = 1 Or z = Grid.Cols Then
        Printer.DrawWidth = 2
    Else
        Printer.DrawWidth = 1
    End If
    If z = 2 And Grid.Text = "Итого с начала года:" Then GoTo 10
    If z = 2 And InStr(1, Grid.Text, "В т.ч. за ") Then GoTo 10
    Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
    Printer.CurrentX = x * (xx + 0.2)
    Printer.CurrentY = Y * (c + 0.1)
    Printer.Print Grid.Text
10  If z = 2 Then xx = xx + (84 / (Grid.Cols - 1)) -  _
(84 / (4 * (Grid.Cols - 1)))
    If z = 3 Then
        xx = xx + (84 / (Grid.Cols - 1)) + (84 / (4 * (Grid.Cols - 1)))
    Else
        If Not z = 2 Then xx = xx + (84 / (Grid.Cols - 1))
    End If
    Next z
    Printer.DrawWidth = 2
    Printer.Line (x * xx, Y * c)-(x * xx, Y * (c + 1))
    Printer.DrawWidth = 1
    c = c + 1
    Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
    i = i + 1
    If i < Grid.Rows Then GoTo 25
    Printer.DrawWidth = 2
    Printer.Line (x * 4, Y * c)-(x * 88, Y * c)
    Printer.CurrentX = x * 84
    Printer.CurrentY = Y * (39.1)
    Printer.Print "Стр. "; page

    MousePointer = 0
    Printer.EndDoc
End Sub


Кирпичики. Работа с треем

Работа с треем

'+-------------------------------------+
'|        Работа с Tray'ем             |
'|       универсальный модуль          |
'| Большой сенкс добрым ФИДОшникам за  |
'|    предоставление этого модуля      |
'+-------------------------------------+

'всякие константы
Public Const NIF_MESSAGE = &H1
Public Const NIF_ICON = &H2
Public Const NIF_TIP = &H4
Public Const NIM_ADD = &H0
Public Const NIM_MODIFY = &H1
Public Const NIM_DELETE = &H2
Public Const WM_RBUTTONDOWN = &H204

'структурка
Type NOTIFYICONDATA
    cbSize           As Long
    hWnd             As Long
    uID              As Long
    uFlags           As Long
    uCallbackMessage As Long
    hIcon            As Long
    szTip            As String * 64
End Type
Private NID As NOTIFYICONDATA

Declare Function Shell_NotifyIcon Lib "shell32.dll" Alias _
 "Shell_NotifyIconA" (ByVal dwMessage As Long, lpData As NOTIFYICONDATA) _
 As Long

'Добавление иконки в Tray
Public Sub AddTrayIcon(IDhWnd As Long, Icon As Long, Text As String)
    NID.szTip = Left$(Text, 63) & Chr$(0)
    NID.uFlags = NIF_TIP + NIF_MESSAGE + NIF_ICON
    NID.uID = IDhWnd
    NID.cbSize = Len(NID)
    NID.hWnd = IDhWnd
    NID.uCallbackMessage = WM_RBUTTONDOWN
    NID.hIcon = Icon
    Res& = Shell_NotifyIcon(NIM_ADD, NID)
End Sub

'Удаление иконки из Tray
Public Sub DeleteTrayIcon()
    NID.cbSize = Len(NID)
    Res& = Shell_NotifyIcon(NIM_DELETE, NID)
End Sub

'Изменение иконки и ToolTip в Tray
Public Sub ItemModify(Icon As Long, Text As String)
    NID.uFlags = NIF_TIP + NIF_ICON
    NID.cbSize = Len(NID)
    NID.szTip = Left$(Text, 63) & Chr$(0)
    NID.hIcon = Icon
    Res& = Shell_NotifyIcon(NIM_MODIFY, NID)
End Sub


Кирпичики. Класс

Класс с универсальным набором функций

  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior = 0   'vbNone
  MTSTransactionMode = 0   'NotAnMTSObject
End
'-----------------------------
'Данный Cls файл был создан
'общими усилиями некоторых
'фидошников и мной в частности
'отредактирован Alesha Dzybalo
'alesha@ubuoik.kamaz.kazan.su
'-----------------------------
Option Explicit
Public RusLang As Boolean
Dim i As Integer
Dim fh As Integer

Private Declare Function mciExecute Lib "winmm.dll" (ByVal lpstrCommand As String) As Long
Private Declare Function WritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, ByVal hWndChildWindow As Long, ByVal lpClassName As String, ByVal lpsWindowName As String) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal msg As Long, ByVal wp As Long, lp As Any) As Long

Const WM_USER As Long = &H400
Const TB_SETSTYLE = WM_USER + 56
Const TB_GETSTYLE = WM_USER + 57
Const TBSTYLE_FLAT = &H800

'Изменяем обычный тулбар в Flat
Public Sub Flatbar(hwnd As Long)
    Dim lTBarStyle As Long, lTBarHwnd As Long
    lTBarHwnd = FindWindowEx(hwnd, 0&, "ToolbarWindow32", vbNullString)
    lTBarStyle = SendMessage(lTBarHwnd, TB_GETSTYLE, 0&, ByVal 0&)
    lTBarStyle = lTBarStyle Or TBSTYLE_FLAT
    SendMessage lTBarHwnd, TB_SETSTYLE, 0, ByVal lTBarStyle
End Sub

'Читаем данные из ini
Function ReadINIKey(Section As String, KeyName As String, FileName As String) As String
    Dim RetVal As String
    RetVal = String(255, Chr(0))
    ReadINIKey = Left(RetVal, GetPrivateProfileString(Section, KeyName, "", RetVal, Len(RetVal), FileName))
End Function

'Записываем данные в ini
Function WriteInIKey(Section As String, KeyName As String, KeyValue As String, FileName As String)
    WritePrivateProfileString Section, KeyName, KeyValue, FileName
End Function

'Проигрываем через MCI wav
Public Function Sound(FilePath As String)
    mciExecute "Play  " & FilePath
End Function

'Запустить експлорер
Public Function ShellProgramm(WebAdress As String)
    ShellProgramm = ShellExecute(0, "open", WebAdress, "", "", 1)
End Function

'XOR
Function CryptAndDecrypt(ByVal sString As String, key As Integer, Crypt As Integer) As Variant
    On Error Resume Next
    Dim i As Integer, sFinal As String
    If Crypt = 1 Then
     For i = 1 To Len(sString)
      sFinal = sFinal + Chr$(Asc(Mid$(sString, i, 1)) + key)
     Next i
    ElseIf Crypt = 2 Then
     For i = 1 To Len(sString)
      sFinal = sFinal + Chr$(Asc(Mid$(sString, i, 1)) - key)
     Next i
    End If
    CryptAndDecrypt = sFinal
    If Err.Number <> 0 Then
    Exit Function
    End If
End Function

'Отслеживаем нажатие клавиш (определенное слово)
Public Function SecretKey(KeyCode As Integer, SecretWord As String) As Boolean
    Dim i As Integer
    Dim LenText As Integer
    Static KeyPressFlg As String
    LenText = Len(SecretWord)
    For i = 1 To LenText
     If Chr(KeyCode) = Mid(SecretWord, i, 1) Then KeyPressFlg = KeyPressFlg + Chr(KeyCode)
    Next
    If Len(KeyPressFlg) > LenText Then KeyPressFlg = ""
    If CStr(KeyPressFlg) = CStr(SecretWord) Then SecretKey = True
End Function


Урок 1

Урок 1. Введение в базы данных

1. Что такое база данных

2. Структура базы данных

3. Отношения между таблицами

4. Нормализация баз данных

5. Ключи и индексы

Что такое база данных

Можно с большой степенью достоверности утверждать, что большинство приложений, которые предназначены для выполнения хотя бы какой-нибудь полезной работы, тем или иным образом используют структурированную информацию или, другими словами, упорядоченные данные. Такими данными могут быть, например, списки заказов на тот или иной товар, списки предъявленных и оплаченных счетов или список телефонных номеров ваших знакомых. Обычное расписание движения автобусов в вашем городе - это тоже пример упорядоченных данных.

При компьютерной обработке информации упорядоченные каким либо образом данные принято хранить в базах данных - особых файлах, использование которых вместе со специальными программными средствами позволяет пользователю как просматривать необходимую информацию, так и, по мере необходимости, манипулировать ею, например, добавлять, изменять, копировать, удалять, сортировать и т.д.

Таким образом, дать простое определение базы данных можно следующим образом. База данных - это набор информации, организованной тем, или иным способом. Пожалуй, одним из самых банальных примеров баз данных может быть записная книжка с телефонами ваших знакомых. Наверное, у вас есть сейчас или когда-либо была эта полезная вещь. Этот список фамилий владельцев телефонов и их телефонных номеров, представленный в вашей записной книжке в алфавитном порядке, представляет собой, вообще говоря, проиндексированную базу данных. Использование индекса - в данном случае фамилии (или имени) позволяет вам достаточно быстро отыскать требуемый номер телефона.

Структура базы данных

Телефонный справочник представляет собой так называемую “плоскую” базу данных, в которой вся информация располагается в единственной таблице. Каждая запись в этой таблице содержит идентификатор конкретного человека - имя и фамилию и его номер телефона. Таким образом таблица состоит из записей, информация в которых разделена на несколько частей - полей. В данном случае полями являются “ФИО” и “Номер телефона”, как показано на рис.1.1.

Рис.1.1. Таблица, запись и поле.

В отличие от плоских, реляционные базы данных состоят из нескольких таблиц, связь между которыми устанавливается с помощью совпадающих значений одноименных полей.

Здесь следует отметить, что использование реляционной модели баз данных не является единственно возможным способом представления информации. В настоящее время существует несколько различных моделей представления данных, которые, однако, пока не получили такого широкого распространения среди разработчиков и пользователей, как реляционная модель. То есть при разработке систем управления базами данных реляционная модель практически является стандартом.

В качестве примера реляционной базы данных можно привести поставляемую вместе с Visual Basic базу данных BIBLIO.MDB, содержащую библиографическую информацию о книгах по программированию, их авторах и издательствах, эти книги опубликовавших.

Так как Visual Basic использует ту же систему управления базами данных (MS Jet Engine), что и MS Access, то несмотря на наличие в Visual Basic средств работы со многими форматами БД, все таки в приложениях предпочтительно использовать файлы баз данных в формате MS Access. Эти файлы имеют расширение MDB и здесь в основном будут описаны приемы работы с файлами именно такого формата.

Перейдем теперь к исследованию базы данных с библиографией. Для этого откройте файл BIBLIO.MDB при помощи MS Access или VisData.

Содержимое файла базы данных BIBLIO.MDB показано на рис.1.2. В базу данных входят таблицы (Tables), запросы (Queries), формы (Forms), отчеты (Reports), макросы (Macros) и модули (Modules). Макросы, формы и модули нам не интересны, так как это вотчина разработчиков, применяющих Visual Basic for Applications или, сокращенно, VBA.

Рис.1.2. Содержимое файла BIBLIO.MDB

Из рисунка видно, что база данных состоит из таблиц: PUBLISHERS, AUTHORS и TITLES. Каждая из таблиц содержит информацию об объектах одного типа. Из названий таблиц становиться понятно, что данные в каждой таблице принадлежат одной и той же группе объектов. Каждая строка в этих таблицах однозначно определяет один объект из соответствующей группы. Вообще, база данных может состоять из одной или нескольких таблиц. Запись, в свою очередь, состоит из нескольких полей, каждое из которых содержит элемент данных об объекте.

Типы данных, которые можно поместить в таблицу, зависят от формата файла базы данных. В таблице 1.1 приведены некоторые типы данных, которые поддерживаются системой управления базами данных Visual Basic для файлов MS Access.

Таблица 1.1

Название | Описание
Text | Строки алфавитно-цифровых символов. Например, адрес, номер телефона, почтовый индекс и т.п. Текстовое поле может содержать от 0 до 255 символов.
Memo | Длинные строки. Например, комментарии. Максимальный размер ограничен 1.2 Гбайт.
Yes/No | Yes/No, True/False, On/Off, 0 или 1.
Byte | Целые числа в диапазоне от 0 до 255.
Integer | Целые числа в диапазоне от -32768 до +32767.
Long | Целые числа в диапазоне от -2147483648 до 2147483647.
Single | Вещественные числа в диапазоне от -3.4? 1038 до 3.4? 1038.
Double | Вещественные числа в диапазоне от -1.8? 10308 до 1.8? 10308.
Date/Time | Дата и время.
Currency | Используется для обозначения денежных сумм. Запоминаются 11 знаков слева от десятичной точки и 4 знака справа от десятичной точки.
Counter | Длинные целые с автоматическим приращением.
OLE | OLE-объекты, созданные в других программах с использованием протокола OLE. Размер ограничен 1.2 Гбайт.
Binary | Любой двоичный объект размером до 1.2 Гбайт. Этот тип обычно используется для хранения рисунков и двоичных файлов.

Таблица PUBLISHERS (Издатели) содержит информацию об издательствах (имя компании, ее адрес, телефон, факс и др.). На рис. 1.3 и 1.4 показаны структура таблицы PUBLISHERS и ее содержимое в табличном виде.

Рис.1.3. Структура таблицы PUBLISHERS

Рис.1.4. Содержимое таблицы PUBLISHERS

Таблица AUTHORS (Авторы) содержит информацию о авторах - ФИО и год рождения. Структура этой таблицы и ее содержимое показаны на рис.1.5 и 1.6 соответственно.

Рис.1.5. Структура таблицы AUTHORS

Рис.1.6. Содержимое таблицы AUTHORS

Таблица TITLES (Заголовки) содержит данные о самих книгах - название книги, год издания, код ISBN, издатель, краткое описание и др. Структура таблицы TITLES и ее содержимое показаны на рис.1.7 и 1.8 соответственно.

Рис.1.7. Структура таблицы TITLES

Рис.1.8. Содержимое таблицы TITLES

Из рис.1.2 видно, что в базе данных BIBLIO.MDB присутствует еще и таблица TITLE AUTHOR. На первый взгляд непонятно зачем она нужна. Ведь в базе данных есть таблица TITLES с заголовками книг и таблица AUTHORS с данными об авторах. Однако все же эта таблица нужна и для чего она так необходима станет понятно, когда в дальнейшем будем рассматривать отношения между таблицами.

Отношения между таблицами

Отношения между таблицами устанавливают связь между данными находящимися в разных таблицах базы данных.

Отношения между таблицами определяются отношением между группами объектов соответствующего типа. Например, один автор может написать несколько книг и издать их в разных издательствах. Или издательство может опубликовать несколько книг разных авторов. Таким образом, между авторами и названиями книг существует отношение один-ко-многим, а между издательствами и авторами существует отношение много-ко-многим.

Отношения между таблицами базы данных BIBLIO.MDB показаны на рис.1.9.

Рис.1.9. Отношения между таблицами базы данных BIBLIO.MDB.

Отношение один-к-одному

Если между двумя таблицами существует отношение один-к-одному, то это означает, что каждая запись в одной таблице соответствует только одной записи в другой таблице.

Примером такого отношения может служить отношение между таблицами. Таблица AUTHORS (Авторы) рассмотрена выше (рис. 1.5 и 1.6) и содержит краткую информацию о авторах (ФИО и год рождения). Таблица PERSON (Личность) содержит персональную информацию о авторах (домашний адрес, телефон, образование и др.) Структура таблицы PERSON показана на рис.1.10. Следует отметить, что в базе данных BIBLIO.MDB никакой таблицы PERSON нет и мы упоминаем о ней только как о иллюстрации отношения между таблицами - один-к-одному.

Рис.1.10. Структура таблицы PERSON

Между таблицами AUTHORS и PERSON существует отношение один-к-одному, так как одна запись, идентифицирующая автора, однозначно соответствует только одной записи в таблице PERSON, содержащей персональные данные об авторе.

Связь между таблицами определяется с помощью совпадающих полей: Au_ID в таблице AUTHORS и в таблице PERSON.

Отношение один-ко-многим

Хорошим примером отношения между таблицами один-ко-многим является отношение между авторами и названиями книг (таблицы AUTHORS и TITLES), так как каждый автор может иметь отношение к созданию нескольких книг. Связь между таблицами AUTHORS и TITLES осуществляется с помощью совпадающих полей Au_ID в обеих таблицах.

Аналогичное отношение существует между издательствами и названиями изданных книг, организацией и работающими в ней сотрудниками, автомобилем и деталями, из которых он состоит и т.п. Понятно, что подобный тип отношения между таблицами наиболее часто встречается при проектировании структуры баз данных.

Отношение много-к-одному

Отношение много-к-одному полностью аналогично рассмотренному выше отношению один-ко-многим.

Отношение много-ко-многим

При отношении между двумя таблицами много-ко-многим каждая запись в одной таблице связана с несколькими записями в другой таблице и наоборот. Иллюстрацией такого отношения может служить отношение между таблицами PUBLISHERS и AUTHORS. С одной стороны, каждое издательство может публиковать книги разных авторов и с другой стороны - каждый автор может публиковаться в разных издательствах.

Для удобства работы с таблицами, имеющими отношение много-ко-многим, обычно в базу данных добавляют еще одну таблицу, которая находится в отношении один-ко-многим и много-к-одному к соответствующим таблицам. В случае базы данных BIBLIO.MDB такой таблицей является TITLE AUTHOR.

Нормализация баз данных

Рассмотрим процесс нормализации базы данных на примере базы данных BIBLIO.MDB. Вообще говоря, все данные о книгах, авторах и издательствах можно разместить в одной таблице, названной, например, BIBLIOS. Структура этой таблицы показана на рис. 1.11. В принципе, можно работать и с такой таблицей. С другой стороны понятно, что такая структура данных является неэффективной. В таблице BIBLIOS будет достаточно много повторяющихся данных, например сведения об издательстве или авторе будут повторяться для каждой опубликованной книги. Такая организация данных приведет к следующим проблемам, с которыми столкнется конечный пользователь вашей программы:

Наличие повторяющихся данных приведет к неоправданному увеличению размера файла базы данных. Кроме нерационального использования дискового пространства, это также вызовет заметное замедление работы приложения.

Ввод пользователем большого количества повторяющейся информации неизбежно приведет к возникновению ошибок.

Изменение, например, номера телефона издательства потребует значительных усилий по корректировке каждой записи, содержащей данные об издателе.

Рис.1.11. Структура таблицы BIBLIOS.

Если, при проектировании приложения для работы с базами данных, вы организуете свои данные таким нерациональным образом, то в дальнейшем вам, скорее всего, больше не поручат решение аналогичных задач.

Чтобы избежать всех этих проблем, надо стремиться максимально уменьшить количество повторяющейся информации. Процесс уменьшения избыточности информации в базе данных посредством разделения ее на несколько связанных друг с другом таблиц и называется нормализацией данных.

Вообще говоря, существует строгая теория нормализации данных, в рамках которой разработаны алгоритмы уменьшения избыточности информации, определены несколько уровней нормализации и установлены критерии, определяющие соответствие данных определенному уровню нормализации. Знакомство с теорией нормализации данных выходит за рамки этих уроков и тем читателям, которым интересно побольше узнать об этом, можно посоветовать обратиться к специальной литературе.

Для того, чтобы построить достаточно эффективную структуру данных, достаточно придерживаться нескольких простых правил:

1. Определите таблицы таким образом, чтобы записи в каждой таблице описывали объекты одного и того же типа. В нашем случае библиографические данные можно разместить в трех таблицах:

PUBLISHERS - содержит информацию об издательствах;

AUTHORS - содержит информацию об авторах книг;

TITLES - содержит информацию об изданных книгах.

2. Если в вашей таблице появляются поля, содержащие аналогичные данные, разделите таблицу.

3. Не запоминайте в таблице данных, которые могут быть вычислены при помощи данных из других таблиц.

4. Используйте вспомогательные таблицы. Например, если в вашей таблице есть поле Страна, то может быть стоит ввести вспомогательную таблицу Country, которая будет содержать соответствующие записи (Россия, Украина, США и т.п.). Этот прием также поможет уменьшить количество ошибок при вводе данных, допускаемых пользователями.

Ключи и индексы

Как было отмечено выше при описании отношений между таблицами, в реляционных базах данных таблицы связываются друг с другом посредством совпадающих значений ключевых полей. Ключевым полем может быть практически любое поле в таблице. Ключ может быть первичным (primary) или внешним (foreign).

Первичный ключ однозначно определяет запись в таблице. В примере с базой данных BIBLIO.MDB таблицы PUBLISHERS, AUTHORS и TITLES имеют первичные ключи PubID, Au_ID и ISBN соответственно. Таблица TITLES также имеет два внешних ключа PubID и Au_ID для связи с таблицами PUBLISHERS и AUTHORS. Таким образом, первичный ключ однозначно определяет запись в таблице, в то время как внешний ключ используется для связи с первичным ключом другой таблицы.

Ключевой поле может иметь определенный смысл, как например ключ ISBN в таблице TITLES. Однако, очень часто ключевое поле не несет никакой смысловой нагрузки и является просто идентификатором объекта в таблице. Во многих случаях удобно использовать в качестве ключа поле счетчика (Counter field). При этом вся ответственность по поддержанию уникальности ключевого поля снимается с пользователя и перекладывается на процессор баз данных. Поле счетчика представляет собой четырехбайтовое целое число (Long) и автоматически увеличивается на единицу при добавлении пользователем новой записи в таблицу.

Данные запоминаются в таблице в том порядке, в котором они вводятся пользователем. Это, так называемый, физический порядок следования записей. Однако, часто требуется представить данные в другом, отличном от физического, порядке. Например может потребоваться просмотреть данные об авторах книг, упорядоченные по алфавиту. Кроме того, часто необходимо найти в большом объеме информации запись, удовлетворяющую определенному критерию. Простой перебор записей при поиске в большой таблице может потребовать достаточно много времени и поэтому будет неэффективным.

Одними из основных требований, предъявляемым к системам управления базами данных, являются возможность представления данных в определенном, отличном от физического, порядке и возможность быстрого поиска определенной записи. Эффективным средством решения этих задач является использование индексов.

Индекс представляет собой таблицу, которая содержит ключевые значения для каждой записи в таблице данных и записанные в порядке, требуемом для пользователя. Ключевые значения определяются на основе одного или нескольких полей таблицы. Кроме того, индекс содержит уникальные ссылки на соответствующие записи в таблице. На рис.1.12 показан фрагмент таблицы CUSTOMERS, содержащей информацию о покупателях, и индекс IDX_NAME, построенный на основе поля Name таблицы CUSTOMERS. Индекс IDX_NAME содержит значения ключевого поля Name, упорядоченные в алфавитном порядке, и ссылки на соответствующие записи в таблице CUSTOMERS.

Рис.1.12. Связь между таблицей и индексом.

Каждая таблица может иметь несколько различных индексов, каждый из которых определяет свой собственный порядок следования записей. Например, таблица AUTHORS может иметь индексы для представления данных об авторах, упорядоченные по дате рождения или по алфавиту. Таким образом, каждый индекс используется для представления одних и тех же данных, но упорядоченных различным образом.

Вообще говоря, таблицы в базе данных могут и не иметь индексов. В этом случае для большой таблицы время поиска определенной записи может быть весьма значительным и использование индекса становиться необходимым. С другой стороны, не следует увлекаться созданием слишком большого количества индексов, так как это может заметно увеличить время необходимое для обновления базы данных и значительно увеличить размер файла базы данных.

При разработке приложений, работающих с базами данных, наиболее широко используются простые индексы. Простые индексы используют значения одного поля таблицы. Примером простого индекса в базе данных BIBLIO.MDB может служить код ISBN, идентификатор автора Au_ID или идентификатор издательства PubID.

Хотя в большинстве случаев для представления данных в определенном порядке достаточно использовать простой индекс, часто возникают ситуации, где не обойтись без использования составных индексов. Составной индекс строится на основе значений двух или более полей таблицы. Хорошей иллюстрацией использования составных индексов может служить база данных родственников при генеалогических исследованиях какой-либо фамилии. Понятно, что использование в качестве простого индекса фамилии человека в данном случае недопустимо. Даже использование составного индекса, основанного на полях имени, фамилии и отчества может быть неэффективным, так как и в этом случае все равно возможно существование достаточно большого числа однофамильцев. Выходом из положения может быть использование составного индекса, основанного, например, на следующих полях таблицы: имя, фамилия, отчество, город и номер телефона.


Урок 2

Приложение VisData

1. Создание базы данных.

2. Редактирование структуры таблицы.

3. Работа с индексами.

4. Другие возможности приложения VisData.

Чтобы создать приложение для обработки информации из базы данных необходимо для начала иметь собственно базу данных. Базу данных в формате MS Access можно создать несколькими способами, например:

С помощью системы управления базами данных MS Access;

С использованием объектов доступа к данным Visual Basic;

С использованием приложения VisData, входящего в комплект поставки Visual Basic.

О том, как создать базу данных с помощью системы управления базами данных MS Access, можно узнать из любого соответствующего руководства. Так как эти уроки посвящены работе с Visual Basic, то все наше внимание будет уделено последним двум способам. Создание базы данных с помощью объектов доступа к данным, или другими словами, программным способом, будет описано ниже, в соответствующих уроках (про DAO).

В поставку Visual Basic входит приложение для управления базами данных VisData. Это приложение позволяет создавать и модифицировать базы данных как в формате MS Access, так и в других популярных форматах. Запустить его можно из меню Add-Ins интегрированной среды разработки Visual Basic. Кроме того, исходные тексты этого очень полезного приложения поставляются в комплекте с Visual Basic в качестве одного из примеров. Для тех, кто хочет детально разобраться во всех тонкостях работы с базами данных с помощью Visual Basic, исходные тексты приложения VisData являются просто находкой. Пожалуй, не существует таких приемов работы, которые не были бы продемонстрированы в этом приложении.

С помощью VisData создадим базу данных FRIENDS.MDB, в которой можно хранить персональные данные о ваших знакомых. Эта база данных может в дальнейшем служить основой для создания электронной записной книжки. Предположив заранее, что многие из ваших друзей и/или подруг могут работать или учиться в одном и том же месте, во избежание повторов разобьем данные на две группы - собственно персональную информацию о человеке (имя, фамилия, домашний телефон, домашний адрес, день рождения и т.д.) и информацию о месте работы/учебы (название, адрес, рабочий телефон, электронная почта). Таким образом FRIENDS.MDB будет включать в себя таблицу PERSON, содержащую следующие поля:

Name - имя,

LastName - фамилия,

Adress - домашний адрес,

HomePhone - домашний телефон,

Id_work - идентификатор места работы/учебы,

HomeWWW - адрес домашней страницы в сети Интернет,

Email - адрес электронной почты,

Note - примечание

и таблицу COMPANY, содержащую поля:

Id_work - уникальный идентификатор места работы/учебы,

Office - название места работы/учебы,

Adress - адрес места работы/учебы,

WorkPhone1 - рабочий телефон 1,

WorkPhone2 - рабочий телефон 2,

Email - адрес электронной почты,

Note - примечание.

Создание базы данных

Запустите приложение VisData и выполните команду меню File->New->Microsoft Access->Version 7.0 MDB (см. рис.2.1). В появившемся диалоговом окне введите имя файла FRIENDS.MDB и, если необходимо, измените путь к файлу. После этого, сохраните файл базы данных нажатием командной кнопки Save (рис.2.2). Дальше, в окне DataBase Window (Окно базы данных) вы можете посмотреть свойства созданной базы данных (рис.2.3).

Рис.2.1. Создание файла

Рис.2.2. Диалоговое окно создания файла

Рис.2.3. Свойства базы данных FRIENDS.MDB

После создания файла можно приступить к проектированию структуры базы данных. В окне DataBase Window нажмите правую кнопку мыши и в появившемся всплывающем меню выполните команду New Table (Новая таблица), чтобы добавить в базу таблицу PERSON. На рис. 2.4 показано окно структуры таблицы (Table Structure). Введите в поле имени таблицы (Table Name) слово Person и можете приступать к добавлению полей в таблицу. Для этого нажмите командную кнопку Add Field (Добавить поле). В окне добавления поля введите имя первого поля - Name и установите другие необходимые атрибуты. Результат показан на рис.2.5. Затем нажмите кнопку OK для подтверждения проделанных действий и подобным образом добавьте в таблицу PERSON оставшиеся поля.

Рис.2.4. Окно структуры таблицы

Рис.2.5. Добавление поля в таблицу

Когда все поля и атрибуты установлены не забудьте нажать кнопку Build the Table (Построить таблицу) в нижней части окна Table Structure. До этого момента вся вводимая вами информация сохранялась в памяти компьютера и если вы по ошибке вместо кнопки Build the Table нажмете кнопку Close (Закрыть) все данные будут потеряны.

После этого совершенно аналогично создайте таблицу WORKPLACE и добавьте в нее необходимые поля.

Редактирование структуры таблицы

После того, как в базу данных добавлены таблицы и для них определены поля и установлены необходимые свойства, вы возвращаетесь в окно DataBase Window. Теперь вы можете редактировать структуру таблиц. Для этого выделите необходимую таблицу в списке и нажмите правую кнопку мыши или просто кликните на ней правой кнопкой мыши. Выполнив команду Design (Проектировать) в появившемся меню, вы откроете окно Table Structure, в котором можно редактировать таблицу. Кроме того, в этом меню (см.рис.2.6) доступны команды Rename (Переименовать таблицу) и Delete (Удалить таблицу).

Если необходимо удалить поле из таблицы, в окне Table Structure выделите в списке полей (Fields List) требуемое поле и нажмите кнопку Remove Field (Удалить поле).

Следует отметить, что приложение VisData позволяет редактировать структуру таблицы даже в том случае, когда в эту таблицу уже введены какие-либо данные.

Рис.2.6. Меню для работы с таблицей

Работа с индексами

Если в таблицу требуется добавить индекс, нажмите кнопку Add Index (Добавить индекс) в окне Table Structure. Появится диалоговое окно Add Index, в котором следует ввести имя индекса в окне редактирования Name и выбрать поля на основе которых будет построен индекс из списка доступных полей таблицы (Available Filds). Диалоговое окно добавления индекса показано на рис.2.7. На рис.2.8 показан результат добавления индекса по двум полям таблицы - Name и LastName.

Рис.2.7. Диалоговое окно добавления индекса в таблицу PERSON

Рис.2.8. Результат добавления индекса построенного по двум полям Name и LastName.

Аналогичным образом можно добавить в таблицу и другие индексы. Чтобы удалить индекс, выделите его в списке индексов (Index List) и нажмите кнопку Remove Index (Удалить индекс). После подтверждения намерений индекс будет удален.

Другие возможности приложения VisData

Помимо создания баз данных MS Access, разработки и модифицирования их структуры, приложение VisData позволяет решить еще целый ряд задач:

Создание или открытие файлов баз данных во многих популярных форматах, включая dBase, FoxPro, Paradox, Excel, ODBC и текстовых файлов

Ввод информации в базу данных и ее редактирование

Копирование таблиц из одной базы данных в другую

Восстановление поврежденных файлов баз данных формата MS Access

Сжатие баз данных формата MS Access и их конвертация в разные версии Microsoft Jet Engine

Выполнение глобальных замен данных в таблицах, в том числе и с использованием различных критериев

Присоединение внешних файлов баз данных к базе данных MS Access

Разработка и выполнение SQL-запросов различной степени сложности


Урок 3

Введение в объект управления данными - Data Control

1. Что такое объект управления данными.

2. Как установить свойства объекта Data.

3. Что такое набор записей.

4. Текущая запись.

5. Связанные с данными элементы управления.

6. Простое приложение для просмотра полей базы данных.

Само название этого объекта говорит о том, что предназначен он для работы с данными. С помощью Data Control можно быстро создавать простые приложения для работы с базами данных практически без написания программного кода. А используя этот элемент управления вместе с объектами доступа к данным (Data Access Objects или DAO) можно создавать достаточно мощные по своим функциональным возможностям программные комплексы, предназначенные для обработки информации из баз данных.

Что такое объект управления данными

Объект управления данными - это компонент Visual Basic, который позволяет получить доступ и манипулировать данными, хранящимися в файлах баз данных различных форматов. Как уже отмечалось выше, используя объект Data, вы можете достаточно быстро создавать работоспособные Windows-приложения практически без написания кода программы. Перечислим основные действия, которые объект управления данными позволяет выполнять без вмешательства программиста (точнее, почти без вмешательства):

Подключение (connect) к базе данных.

Открытие таблицы базы данных или определение при помощи соответствующего SQL-запроса критерия выбора записей из полей базы данных.

Передача данных из полей базы данных к связанным элементам управления, расположенным на форме (например, поле редактирования), где данные можно просматривать или изменять.

Добавление новых записей, удаление записей и обновление содержимого базы данных (здесь, правда, без написания нескольких строк кода не обойтись).

Закрытие базы данных.

Применение объекта Data дает возможность разработчику использовать в своих приложениях базы данных в наиболее распространенных в настоящее время форматах, таких как Microsoft Access, dBASE, FoxPro, Paradox, Btrieve, а также файлы электронных таблиц Microsoft Excel и Lotus 1-2-3. Кроме того, можно использовать в качестве источника данных обычные текстовые файлы (с расширением txt) или базы данных, доступные через интерфейс Open Database Connectivity (ODBC).

Объект управления данными обозначен на панели инструментов интегрированной среды разработки Visual Basic 5 пиктограммой, показанной на рис.3.1.

Рис.3.1. Пиктограмма объекта Data на панели инструментов Visual Basic 5.

Внешний вид объекта Data на вашей экранной форме показан на рис.3.2. При его выделении, в углах и на серединах сторон появляются маркеры, с помощью которых можно изменять линейные размеры этого элемента управления по вашему вкусу. Объект Data содержит заголовок и четыре кнопки, предназначенные для перемещения по записям базы данных, а точнее, по созданному им набору записей (что такое набор записей вы узнаете несколько позднее). В качестве заголовка лучше использовать текст, каким-либо образом характеризующий данные, с которыми вы предполагаете работать. Заголовок определяется свойством Caption объекта Data.

Рис.3.2. Вид объекта Data на экранной форме.

Чтобы использовать объект управления данными в приложении, необходимо выполнить, по крайней мере, четыре действия:

  1. Выбрать объект Data на панели инструментов, перенести его на экранную форму и установить требуемые размеры.
  2. Определить имя файла базы данных, с которым вы собираетесь работать, установкой свойства DatabaseName объекта Data.
  3. Определить источник данных установкой свойства RecordSource объекта Data.
  4. Определить тип набора записей установкой свойства RecordsetType объекта Data (этот пункт не обязателен, так как по умолчанию будет создан набор записей динамического типа).

Если приложение должно работать с несколькими источниками данных, то можно одновременно разместить на экранной форме и использовать целый ряд объектов Data.

Как установить свойства объекта Data

Установить имя базы данных во время разработки (design time) приложения можно в окне свойств (Properties) объекта управления данными. При выборе свойства DatabaseName в правом столбце окна свойств появится кнопка с многоточием. При нажатии на нее откроется диалоговое окно просмотра файлов DatabaseName, при помощи которого необходимо выбрать требуемую для работы базу данных. Кроме того, если вы хорошо знаете полный путь к файлу, то можете просто ввести имя файла в окне свойств, используя клавиатуру компьютера. Здесь следует особо подчеркнуть, что имя должно включать в себя полный путь к файлу базы данных.

Рис.3.3. Диалоговое окно просмотра файлов баз данных.

Рис.3.4. Вы можете просто ввести имя файла.

После определения имени файла базы данных, в том же окне свойств надо определить источник данных.

Свойство RecordSource объекта Data определяет, из какой таблицы базы данных требуется использовать данные. Имя таблицы можно ввести с использованием клавиатуры компьютера, или выбрать из списка таблиц, которая появится после нажатия кнопки со стрелкой в правом столбце окна свойств.

Если требуется использовать данные из нескольких таблиц, для определения требуемого источника данных можно использовать язык структурированных запросов (SQL). Описанию языка запросов будут посвящены несколько уроков на данном сайте.

Рис.3.5. Установка свойства RecordSource.

Что такое набор записей

Теперь пришло время определить понятие набора записей (Recordset). Объект управления данными не работает напрямую с базой данных, а создает в памяти компьютера именованную область, которая используется как буфер между файлом базы данных и объектом Data. В этой области памяти и содержится информация из базы данных. Данные для объекта Recordset определяются свойствами RecordSource и RecordsetType объекта Data.

Таким образом, набор записей представляет собой некоторое подмножество записей из одной или нескольких таблиц базы данных (или из нескольких баз данных). При этом, он может содержать как полную копию таблицы, так и ее часть. Если источник данных определен с помощью языка SQL, то набор записей будет содержать строки из базы данных, удовлетворяющие соответствующему SQL-запросу.

Объект Recordset (как и любой другой объект в Visual Basic) имеет вполне конкретные свойства и методы, которые подробно будут рассмотрены позднее в уроках, посвященных объектам доступа к данным (DAO).

В пятой версии Visual Basic поддерживаются пять типов наборов записей. Но наиболее широко используются только три типа: набор записей динамического типа или динамический набор записей, набор записей типа таблица и статический набор записей. Их краткое описание приведено в таблице 3.1.

Таблица 3.1.

Тип набора записей | Описание
Table | Набор записей, представляющий собой всю таблицу базы данных. Используя такой набор записей, можно добавлять, изменять или удалять записи, причем доступ возможен только к одной таблице.
Dynaset | Динамический набор записей представляет собой таблицу базы данных или результат запроса, содержащего поля одной или нескольких таблиц. Можно добавлять, изменять или удалять записи и изменения будут отражаться в соответствующей базе данных.
Snapshot | Статическая копия таблицы, которую можно использовать, например, для поиска данных или для составления отчетов.

По умолчанию объект управления данными создает набор записей динамического типа.

Использование набора записей типа таблица по своему действию аналогично прямому открытию таблицы. При этом в память компьютера загружается только содержимое текущей записи. Из этого и вытекают преимущества и недостатки табличного набора записей. Используя его, вы можете работать только с одной таблицей, причем таблица открывается вся целиком, что может потребовать значительных ресурсов памяти.. Основным преимуществом такого набора записей является возможность использования индексов для упорядочения и поиска данных в таблице.

Из всех трех доступных типов наборов записей наиболее гибким в использовании является динамический набор записей (не зря же именно он создается объектом Data по умолчанию). Фактически, набор записей динамического типа представляет собой ряд ссылок на информацию в базе данных, поскольку использует ключевые поля, однозначно определяющие каждую строку в базе данных. Использование ключей увеличивает скорость загрузки данных и уменьшает нагрузку на сеть при работе приложений многопользовательской среде. Содержимое Memo-полей и полей, содержащих OLE-объекты не загружается до тех пор, пока не возникнет необходимость их использования, например, вывода их на экран. Все это позволяет использовать динамические наборы записей при работе с большими таблицами и таблицами, содержащими OLE-объекты и Memo-поля. Самым существенным недостатком набора записей динамического типа можно считать невозможность использования индексов. При каждом изменении способа упорядочения записей динамический набор должен пересоздаваться заново.

При использовании статического набора записей, в отличие от табличного и динамического наборов, вся информация из базы данных (определенная свойством RecordSource объекта Data) заносится в память компьютера. Статический набор может содержать данные из одной или нескольких таблиц базы данных, при этом обновление данных невозможно. Поэтому, он может использоваться в случаях, не требующих обновления данных в исходной таблице, например, для поиска определенной записи или для составления отчетов. Так как содержимое записей статического набора находится в памяти, то все операции с ним выполняются гораздо быстрее, чем при использовании динамического или табличного наборов записей. При этом платой за высокую скорость является потребность в значительных ресурсах памяти.

Объекты - наборы записей, создаваемые программно, могут быть связаны со свойством Recordset объекта управления данными при помощи оператора Set:

Set Data1.Recordset=NewRecordset.

Таким же образом набор записей, созданный одним объектом управления данными может быть связан с другим объектом управления данными во время выполнения (run time) приложения:

Set Data1.Recordset=NewRecordset

Set Data2.Recordset=Data1.Recordset.

Текущая запись

В каждый момент времени текущей является только одна запись из набора и содержимое полей именно этой записи отображается в связанных элементах управления на вашей экранной форме. Нажатие кнопок со стрелками на объекте Data эквивалентно соответствующему перемещению указателя текущей записи по данным из объекта Recordset.

Когда вы программируете какие-либо операции с данными из набора записей, вы всегда должны быть уверены, что текущая запись является допустимой (valid). Это условие является необходимым, так как, например, удаленная запись является недопустимой (invalid) и любая попытка доступа к ней записи вызовет ошибку. Такие свойства объекта Data, как BOFAction и EOFAction, предоставляют некоторый контроль над состоянием текущей записи, но вы также можете создать программный код для проверки свойств BOF (начало файла) и EOF (конец файла) объекта Recordset. Установка этих двух свойств и определяет состояние текущей записи. В таблице 3.2 представлены возможные состояния свойств BOF/EOF объекта Recordset.

Таблица 3.2.

Свойство BOF/EOF объекта Recordset | Описание
BOF и EOF равны False | Указатель текущей записи является допустимым (valid) до тех пор, пока вы не удалите последнюю запись в наборе записей.
BOF = True | Текущая запись установлена перед первой записью. Указатель текущей записи является недопустимым (invalid).
EOF = True | Текущая запись установлена за последней записью. Указатель текущей записи является недопустимым (invalid).
BOF и EOF равны True | В наборе записей нет строк. Указатель текущей записи является недопустимым (invalid).

Связанные с данными элементы управления

При разработке приложений такие действия как открытие базы данных, определение источника данных и др. возлагаются на объект управления данными. Однако, для создания полноценного приложения совершенно необходимым является наличие у разработчика средств визуализации самих данных. Для решения этой задачи в Visual Basic предусмотрены, так называемые, связанные с данными элементы управления (Data-bound Controls). Другими словами, в связанных элементах управления, расположенных на экранной форме, отображается содержимое полей текущей записи из базы данных (точнее, из набора записей).

В качестве связанных объектов управления могут выступать некоторые стандартные объекты Visual Basic, например такое как Text box (текстовое окно).

Кроме свойств, присущих обычным элементам управления Visual Basic, эти элементы управления имеют несколько общих свойств, специально предназначенных для связи с базами данных:

DataField - Определяет имя поля в наборе записей, созданном объектом управления данными.

DataSource - Определяет имя объекта управления данными, с которым связывается элемент управления.

DataChanged - Показывает, было ли изменено содержимое связанного элемента управления.

Таким образом, чтобы добавить связанный элемент управления в приложение, необходимо выполнить следующие действия:

Перетащите необходимый объект управления из панели инструментов на вашу экранную форму и установите требуемые размеры.

Установите свойство DataSource для связи с объектом управления данными.

Установите свойство DataField для связи с требуемым полем набора записей.

Кроме стандартных связанных элементов управления, в Visual Basic предусмотрено несколько специальных связанных элементов управления, которые, кроме как для работы с данными обычно не используются:

Data-bound list box,

Data-bound combo box,

Data-bound grid.

Тот факт, что они используются вместе с данными, отражен в самих их названиях. Их детальное описание и примеры использования в приложениях будут рассмотрены в отдельнх уроках.

Источником данных для любого связанного элемента управления всегда является объект управления данными. Когда пользователь при помощи кнопок со стрелками на объекте Data перемещает указатель текущей записи с одной записи на другую, обновление данных в связанных элементах управления происходит автоматически. Любое изменение данных в связанных элементах управления, сделанное пользователем, немедленно отображается в наборе записей и затем в базе данных (если, конечно, не предусмотреть специальных мер).

В таблице 3.3 представлены типы данных, которые могут отображаться в связанных объектах управления.

Таблица 3.3

Название объекта | Типы данных
Picture box | Long Binary
Image | Binary
Label | Text, Numeric, Date
Text box | Text, Numeric, Date
Check box | True/False, Yes/No
List box | Text, Numeric, Date
Combo box | Text, Numeric, Date
Data bound list box | Text, Numeric, Date
Data bound combo box | Text, Numeric, Date
Data bound grid | Text, Numeric, Date
Masked edit | Text, Numeric, Date

Простое приложение для просмотра полей базы данных

Для демонстрации использования объекта управления данными и связанных элементов управления создадим очень простое приложение, которое, тем не менее, позволит нам просматривать библиографическую информацию из базы данных BIBLIO.MDB. Для этого вы должны последовательно выполнить следующие действия:

1. Откройте новый проект и измените свойство Caption формы Form1 на “Книги по программированию”.

2. Перенесите из панели инструментов на форму объект управления данными Data1.

3. В окне свойств объекта Data1 измените свойство Caption на “Книги”.

4. В окне свойств объекта Data1 установите свойство DatabaseName, как имя файла базы данных с указанием полного пути C:\VB5\BIBLIO.MDB.

5. В окне свойств объекта Data1 определите свойство RecordSource, как следующий SQL-запрос:

SELECT * FROM TITLES, AUTHORS, PUBLISHERS, [TITLE AUTHOR] WHERE TITLES.ISBN = [TITLE AUTHOR].ISBN AND [TITLE AUTHOR].AU_ID = AUTHORS.AU_ID AND TITLES.PUBID= PUBLISHERS.PUBID

Этот запрос выбирает все поля из всех четырех таблиц базы данных BIBLIO.MDB, связанных между собой с помощью совпадающих значений ключевых полей.

6. Разместите на форме восемь меток и, соответственно, восемь текстовых окон как показано на рис.3.6 и установите их свойства следующим образом:

Label1.Caption=Название

Text1.DataSource=Data1

Text1.DataField=Title

Label2.Caption=Автор

Text2.DataSource=Data1

Text2.DataField=Author

Label3.Caption=Год издания

Text3.DataSource=Data1

Text3.DataField=Year Published

Label4.Caption=Тема книги

Text4.DataSource=Data1

Text4.DataField=Subject

Label5.Caption=Орисание

Text5.DataSource=Data1

Text5.DataField=Description

Label6.Caption=Международный код ISBN

Text6.DataSource=Data1

Text6.DataField=TITLES.ISBN

Label7.Caption=Издательство

Text7.DataSource=Data1

Text7.DataField=Name

Label8.Caption=Примечание

Text8.DataSource=Data1

Text8.DataField=Notes

Рис.3.6. Вид экранной формы.

7. Предусмотрим средство для завершения работы приложения. Для этого добавьте на форму командную кнопку, определите ее свойство Caption как “Выход” и установите следующий код в процедуре обработки события Click:

Private Sub Command1_Click ()

Unload Me

End Sub

8. Запустите проект на выполнение выбором команды Start из меню Run, или нажмите клавишу F5. Результат работы программы показан на рис.3.7. С помощью кнопок со стрелками на объекте управления данными можно перемещать текущий указатель по набору записей и содержимое соответствующих полей будет автоматически отображаться на форме в связанных элементах управления.

9. Закройте приложение нажатием кнопки “Выход”.

Рис.3.7. Результат работы приложения.


Урок 4

Свойства, методы и события объекта управления данными

1. Свойства.

2.Методы объекта Data.

3. События.

Объект Data, как и любой другой элемент управления в Visual Basic, имеет несколько стандартных, общих для многих элементов управления, свойств, методов и событий. Наряду с ними, он имеет целый ряд свойств, методов и событий, предназначенных для работы с данными. Именно они и будут последовательно рассмотрены ниже.

Свойства

BOFAction

Свойство BOFAction определяет действие объекта Data, если свойство BOF (beginning of file - начало файла) у созданного им набора записей установлено в True.

Синтаксис: data1.BOFaction=value.

Значение value может быть выбрано из двух альтернатив: MoveFirst или BOF.

При создании объекта Data свойство BOFAction устанавливается по умолчанию как MoveFirst. При этом текущей становится первая запись в наборе записей.

Установка свойства BOFAction в BOF приводит к тому, что при перемещении указателя текущей записи за пределы набора сверху, текущая запись становится недопустимой (invalid) и любая попытка ее редактирования приведет к ошибке. В этом случае происходит событие Validate на первой записи в наборе и, затем, событие Reposition, уже на недопустимой записи. При этом кнопка перемещения указателя к предыдущей записи на объекте управления данными становится неактивной.

Установить свойство BOFAction объекта управления данными data1 в режиме выполнения приложения можно следующим образом:

data1.BOFaction= vbBOFActionMoveFirst,

или

data1.BOFaction= vbBOFActionBOF.

Возможные установки свойства BOFAction приведены в следующей таблице.

Установка | Константа | Значение
MoveFirst | vbBOFActionMoveFirst | 0
BOF | vbBOFActionBOF | 1

Connect

Свойство Connect определяет тип базы данных, открываемой объектом Data. По умолчанию открывается база данных в формате Microsoft Access.

Синтаксис: data1.сonnect=string.

Возможные варианты установки свойства Connect приведены в следующей таблице.

Тип базы данных | Определение
MS Access | ";"
dBASE x | "dBASE x;"
Paradox x.x | "Paradox x.x;"
FoxPro x.x | "FoxPro x.x;"
Excel x.0 | "Excel x.0;"
Lotus WKx | "Lotus WKx;"
Text | "Text;"
Btrieve | "Btrieve;"
ODBC | "ODBC; DATABASE = defaultdatabase; UID=user; PWD=password; DSN=datasourcename; LOGINTIMEOUT=seconds"

Установка свойства Connect в режиме выполнения приложения производится следующим образом:

data1.connect= " dBASE IV;"

или

data1.connect= " FoxPro 3.0;".

Database

Использование свойства Database в режиме выполнения приложения позволяет получить ссылку на объект Database, который создан объектом Data.

Синтаксис: Set databaseobject = data1.Database.

Объект Data создает объект Database на основе своих свойств DatabaseName, Exclusive, ReadOnly, и Connect. Так как объект Database имеет свои собственные свойства и методы то, получив на него ссылку, вы можете использовать их в приложении. Например, вы можете использовать методы Close или Execute. Подробное описание работы с объектом Database приведено в уроках, посвященных объектам доступа к данным.

DatabaseName

Использование свойства DatabaseName позволяет получить или установить имя и месторасположение источника данных для объекта Data.

Синтаксис: data1.DatabaseName=string.

Строковое выражение string определяет месторасположение и имя файла базы данных в форматах MS Access и Btrieve. Если используется база данных, размещенная на файл-сервере, то строка должна включать полный путь к требуемому файлу, например:

\\SERVER\DATA\DATABASE.MDB.

Для баз данных в формате dBASE, Paradox и др. строка string должна содержать только путь к файлу.

Для баз данных ODBC это свойство не определяется, так как все необходимые параметры для связи с базой данных устанавливаются свойством Connect.

Если вы во время работы приложения изменяете свойство DatabaseName, то после сделанных изменений необходимо выполнить метод Refresh объекта Data.

Продемонстрируем использование свойства DatabaseName в режиме выполнения приложения. Для этого:

1. Откройте проект, созданный для просмотра содержимого файла BIBLIO.MDB.

2. Удалите содержимое свойства DatabaseName объекта управления данными Data1.

3. Добавьте следующий код в процедуру обработки сообщения о загрузке формы:

Private Sub Form_Load()

data1.databasename= "c:\vb5\biblio.mdb"

data1.refresh

End Sub

4. Запустите проект.

При разработке приложений для работы с базами данных лучше не привязывать пользователя к конкретной структуре каталогов установкой свойства DatabaseName на этапе разработки программы. Это может быть не всегда удобно для пользователя. Хорошим примером уважительного отношения к пользователю служит хранение путей к файлам баз данных в специальных файлах конфигурации вашего приложения с последующим считыванием этой информации в процессе загрузки программы. Код, осуществляющий эту операцию, можно включить, например, в процедуры обработки событий Load или Activate соответствующей экранной формы.

Другим способом определения пути к файлу базы данных является использование глобального объекта Visual Basic - объекта App. Хотя этот объект не имеет ни методов, ни событий, у него есть несколько очень полезных свойств. Одним из них является свойство Path, которое определяет путь к выполняемому файлу. Если вы используете интегрированную среду разработки Visual Basic, например, для отладки приложения, то App.Path определяет путь к файлу проекта, который вы запустили.

Пусть файлы баз данных для вашей программы находятся в подкаталоге DBASE каталога, в котором находится ваше приложение (проект). Тогда вы можете определить необходимый путь к файлу с помощью следующего фрагмента кода. Причем каталог, из которого запускается приложение (проект) может называться как угодно.

Dim DBName As String

DBName = "BIBLIO.MDB"

Data1.Databasename = App.Path+"\DBASE\"+DBName

Data1.Refresh

EOFAction

Свойство EOFAction определяет действие объекта Data, если свойство EOF (end of file - конец файла) у созданного им набора записей установлено в True.

Синтаксис: data1.EOFAction=value.

Значение value может быть выбрано из трех альтернатив: MoveLast, EOF, или AddNew.

При создании объекта Data свойство EOFAction устанавливается по умолчанию как MoveLast. При этом текущей остается последняя запись в наборе записей.

Установка свойства EOFAction в EOF приводит к тому, что при перемещении указателя текущей записи за пределы набора снизу, текущая запись становится недопустимой (invalid) и любая попытка ее редактирования приведет к ошибке. В этом случае происходит событие Validate на последней записи в наборе и, затем, событие Reposition, уже на недопустимой записи. При этом кнопка перемещения указателя на следующую запись на объекте управления данными становится неактивной.

Если установить свойство EOFAction в AddNew, то при перемещении указателя текущей записи за последнюю запись набора, происходит событие Validate на последней записи, затем, в набор записей добавляется новая пустая запись и происходит событие Reposition. Когда пользователь таким способом добавляет новую запись, эта она сначала создается в памяти компьютера в буфере копирования. Если после этого пользователь ввел необходимые данные и переместил указатель текущей записи на любую другую запись, то новая строка, уже содержащая данные, будет помещена в набор записей и потом в базу данных. В противном случае, если пользователь не ввел данные и переместил указатель, то пустая новая строка не добавляется в набор записей. Таким образом, установка свойства EOFAction в AddNew дает возможность пользователю добавлять в базу данных новые записи с помощью объекта Data и связанных с ним элементов управления.

Установить свойство EOFAction объекта управления данными data1 в режиме выполнения приложения можно следующим образом:

data1.EOFaction=vbEOFActionMoveLast,

data1.EOFaction=vbEOFActionEOF,

data1.EOFaction=vbEOFActionAddNew.

Возможные установки свойства EOFAction приведены в следующей таблице.

Установка | Величина | Значение
vbEOFActionMoveLast | 0 | MoveLast
vbEOFActionEOF | 1 | EOF
vbEOFActionAddNew | 2 | AddNew

Приведем пример приложения, в котором есть возможность добавлять в базу данных новые записи. Для этого:

1. Создайте вспомогательную базу данных BIBLIO2.MDB, содержащую всего одну таблицу TITLES (чтобы не изменить оригинальную базу данных BIBLIO.MDB). Это можно сделать с помощью MS Access.

2. Откройте новый проект и добавьте на форму объект управления данными Data1.

3. В окне свойств объекта Data1 установите свойство DatabaseName, как имя файла базы данных с указанием полного пути C:\VB5\BIBLIO2.MDB.

4. В окне свойств объекта Data1 установите свойство RecordSource как TITLES.

5. Разместите на форме пять меток и, соответственно, пять текстовых окон как показано на рис.4.8 и установите их свойства следующим образом:

Label1.Caption=Название

Text1.DataSource=Data1

Text1.DataField=Title

Label2.Caption=Год выпуска

Text2.DataSource=Data1

Text2.DataField=Author

Label3.Caption=Доп. информация

Text3.DataSource=Data1

Text3.DataField=Year Published

Label4.Caption=Описание

Text4.DataSource=Data1

Text4.DataField=Subject

Label5.Caption=Ид. издателя

Text5.DataSource=Data1

Text5.DataField=Description

Рис.4.1. Вид экранной формы.

6. Запустите приложение.

7. Переместите указатель на последнюю запись и, затем, добавьте новую пустую запись нажатием на кнопку перехода к следующей записи.

8. Введите какие-нибудь данные в связанных текстовых окнах и переместите указатель на любую другую запись, например, на предыдущую. Если, введенные вами данные соответствуют типам полей в таблице, то новая запись будет добавлена в набор записей и в базу данных. Результат работы программы показан на рис.4.2.

9. Теперь еще раз добавьте новую запись и, не редактируя данные в текстовых окнах, переместите указатель на другую запись. Убедитесь, что пустая запись не добавляется в базу данных.

Рис.4.2. Новая запись в базе данных.

Exclusive

Свойство Exclusive определяет режим работы открываемой базы данных: однопользовательский или многопользовательский.

Синтаксис: data1.Exclusive=value.

Значение value имеет тип boolean и может принимать значения True или False. Если свойство Exclusive установлено в True, то база данных открывается в однопользовательском режиме. Если оно установлено в False, то с базой данных могут одновременно работать несколько пользователей. По умолчанию свойство Exclusive устанавливается в False для поддержки многопользовательского режима работы.

Свойство Exclusive используется объектом Data только во время открытия базы данных, поэтому, если переустановить его во время выполнения приложения, то необходимо применить также и Refresh метод:

Data1.Exclusive=true

Data1.Refresh.

Операции с базой данных, открытой в однопользовательском режиме, выполняются несколько быстрее, чем с базой данных, доступной для многих пользователей.

Для баз данных ODBC это свойство игнорируется.

Откройте проект, созданный для демонстрации свойства EOFAction, установите свойство Exclusive объекта управления данными в True и запустите его. Таким образом, вы открыли базу данных BIBLIO2.MDB в однопользовательском режиме. Затем запустите систему управления базами данных MS Acces и попытайтесь открыть файл BIBLIO2.MDB. Результатом ваших действий будет выданное на экран сообщение, уведомляющее вас, что базу данных открыть невозможно.

Options

Свойство Options позволяет установить или получить один или несколько параметров набора записей созданного объектом Data. Это свойство обычно используется при разработке многопользовательских приложений.

Синтаксис: data1.Options=value.

Возможные значения параметра value приведены в следующей таблице:

Постоянная | Значение
dbDenyWrite | 1
dbDenyRead | 2
dbReadOnly | 4
dbAppendOnly | 8
dbInconsistent | 16
dbConsistent | 32
dbSQLPassThrough | 64
dbForwardOnly | 256
dbSeeChanges | 512

Ниже приведено краткое описание возможных установок свойства Options:

dbDenyWrite - в многопользовательской среде другие пользователи не могут вносить изменения в записи;

dbDenyRead - в многопользовательской среде другие пользователи не имеют возможности читать записи. Эта установка применяется только для наборов записей типа таблица;

dbReadOnly - набор записей открывается только для чтения. В этом случае вы не можете изменять записи;

dbAppendOnly - в этом случае вы имеете возможность добавлять новые записи в набор записей, но не можете редактировать существующие записи;

dbInconsistent - обновление может применяться для всех полей в наборе записей, даже если они нарушают условие объединения;

dbConsistent - обновление применяется только для тех полей в наборе записей, которые не нарушают условие объединения. Это значение свойства Options устанавливается по умолчанию;

dbSQLPassThrough - эта константа означает, что SQL-запрос, определенный свойством RecordSource объекта управления данными, будет выполняться на сервере MS SQL Server или Oracle (если вы, конечно, используете такие ODBC-базы данных);

dbForwardOnly - эта установка определяет набор записей таким образом, что по нему возможно перемещение только вперед (только метод MoveNext может использоваться для перемещения по набору записей);

dbSeeChanges - установка этого значения вызывает появление ошибки, когда какой либо пользователь изменил данные в то время, когда вы их редактируете.

При необходимости установить сразу несколько параметров набора записей, используйте операцию сложения. После изменения свойства Options во время выполнения приложения необходимо применить метод Refresh. Следующий пример иллюстрирует, как в многопользовательской среде открыть набор записей только для чтения исключительно для собственного пользования:

Data1.DatabaseName="C:\VB5\BIBLIO.MDB"

Data1.Options=dbDenyWrite + dbReadOnly

Data1.Refresh.

Для того, чтобы сравнить параметры набора записей с каким либо определенным значением вы можете использовать оператор And. С использованием следующего фрагмента кода вы, например, можете проверить, открыт ли набор записей только для чтения:

If Data1.Options And dbReadOnly Then...

ReadOnly

Свойство ReadOnly определяет метод доступа к базе данных, открытой с помощью объекта Data: только для чтения или для чтения-записи.

Синтаксис: data1.ReadOnly=Boolean.

Если свойство ReadOnly установлено в True, то открытая база данных доступна только для чтения и вы, следовательно, не имеете возможности редактировать данные. Если же свойство установлено в False, то база данных открыта для чтения-записи и вы можете делать с данными все, что угодно. По умолчанию свойство ReadOnly устанавливается в False.

После изменения свойства ReadOnly во время выполнения приложения необходимо применить метод Refresh. Следующий пример показывает, как открыть базу данных только для чтения:

Data1.DatabaseName="С:\VB5\BIBLIO.MDB"

Data1.ReadOnly=True

Data1.Refresh

Если нет необходимости редактировать данные, то предпочтительно открывать базу данных в режиме только для чтения, так как операции с данными в этом случае выполняются несколько быстрее.

Recordset

Свойство Recordset определяет набор записей, создаваемый объектом Data.

Синтаксис: data1.Recordset=string.

Параметр string представляет собой текстовую строку, которая определяет набор записей.

После загрузки приложения автоматически создается объект Data. Если у него корректно установлены свойства Connect, DatabaseName, Options, RecordSource, Exclusive, ReadOnly и RecordsetType, то ядро системы управления базами данных Jet database engine создаст новый объект Recordset. То же самое произойдет, если во время выполнения приложения будет применен метод Refresh объекта Data. Доступ к этому объекту - набору записей можно получить через свойство Recordset объекта Data.

Объект Recordset имеет несколько свойств и методов, о которых речь пойдет в разделе, посвященном использованию объектов доступа к данным. Тем не менее, в следующем примере показано, как использовать метод Delete объекта Recordset для удаления записей.

1. Откройте проект, который использовался для демонстрации возможности добавления записи в базу данных с использованием объекта управления данными.

2. Добавьте на форму командную кнопку и определите ее свойство Caption, как “Удалить”.

3. Добавьте в процедуру обработки события Click командной кнопки следующий фрагмент кода:

Private Sub Command1_Click()

Data1.Recordset.Delete

Data1.Refresh

End Sub

4. Запустите приложение. Теперь, используя в приложении объект управления данными, вы можете не только добавлять в базу данных новые записи, но и удалить любую запись.

RecordsetType

Свойство RecordsetType определяет тип набора записей, созданного объектом Data.

Синтаксис: data1.RecordsetType=value.

Параметр value определяет тип набора записей. Возможные значения параметра value приведены в следующей таблице:

Постоянная | Значение | Описание
vbRSTypeTable | 0 | Создается набор записей типа таблицы.
vbRSTypeDynaset | 1 | Создается набор записей динамического типа. Этот тип создается по умолчанию.
vbRSTypeSnapshot | 2 | Создается статический набор записей.

После изменения свойства RecordsetType во время выполнения приложения необходимо применить метод Refresh.

В следующем примере для объекта управления данными Data1 закрывается существующий набор записей и, затем, создается новый набор записей статического типа:

Data1.Recordset Close

Data1.RecordsetType=vbRSTypeSnapshot

Data1.DatabaseName= “C:\VB5\BIBLIO.MDB”

Data1.RecordSource= “select * from Titles”

Data1.Refresh.

RecordSource

Свойство RecordSource определяет источник данных для набора записей, создаваемого объектом Data.

Синтаксис: data1 RecordSource=string.

Параметр string представляет собой строку, определяющую источник данных. Возможные значения этого параметра представлены в следующей таблице:

Установка | Описание
Имя таблицы | Имя одной из таблиц, содержащихся в базе данных.
SQL-запрос | Текстовая строка, представляющая собой SQL-запрос к базе данных. Причем синтаксис SQL выражения должен соответствовать типу базы данных.
Объект QueryDef | Имя одного из объектов QueryDef из набора QueryDefs объекта Database. Этот источник данных поддерживается только для баз данных MS Access.

Чтобы проиллюстрировать использование свойства RecordSource объекта Data, построим приложение для просмотра полей базы данных BIBLIO2.MDB, в котором предусмотрим возможность отображать данные на экране компьютера упорядоченным образом. Для этого требуется выполнить следующие действия:

1. Откройте новый проект.

2. Добавьте на форму объект управления данными Data1 и определите его свойство DatabaseName, как C:\VB5\ BIBLIO2.MDB. Не устанавливайте свойство RecordSource.

3. Добавьте на форму объект DBGrid и определите его свойство RecordSource, как Data1.

4. Добавьте на форму метку “Упорядочить по” и объект ComboBox.

5. Добавьте в раздел General следующий код:

Dim x As Integer

Dim A(4) As String

Dim B(4) As String

6. Добавьте в процедуру загрузки формы следующий код:

Private Sub Form_Load()

A(0) = "Названиям"

A(1) = "Году издания"

A(2) = "ISBN"

A(3) = "Ид.Издателя"

For i = 0 To 3

Combo1.AddItem A(i)

Next i

Combo1.ListIndex = 0

B(0) = "Title"

B(1) = "[Year Published]"

B(2) = "ISBN"

B(3) = "PubID"

End Sub

7. Добавьте в процедуру обработки события Click элемента управления ComboBox следующий код:

Private Sub Combo1_Click()

Dim str1 As String

str1 = "select * From titles order by " & B(Combo1.ListIndex)

If Combo1.ListIndex = Empty Then

str1 = "select * From titles"

End If

Data1.RecordSource = str1

Data1.Refresh

End Sub

8. Запустите проект на выполнение.

После загрузки формы, данные в таблице DBGrid будут отображены в неупорядоченном виде, вернее, данные будут размещены в физическом порядке, то есть в том, в котором они были введены в базу данных, хотя в элементе управления ComboBox будет показано “По названиям”. Затем выберите из списка другой доступный способ упорядочения, например, “По году издания”. После обработки события Click будет создан новый набор записей, источником данных для которого будет служить результат выполнения следующего SQL-запроса

select * From titles order by [Year Published].

На рис.4.3 представлена библиографическая информации из базы данных BIBLIO2.MDB, упорядоченная по году издания книги.

Рис.4.3. Библиографические данные, упорядоченные по году издания книги.

Методы объекта Data

Refresh

После изменения свойства объекта Data во время выполнения приложения, ядро системы управления базами данных Jet database engine сразу не меняет ранее уже установленные свойства. Вообще говоря, просто изменить в программе одно установленное свойство на другое невозможно. Необходимо заново инициализировать объект управления данными, который затем автоматически создаст другие объекты доступа к данным (например, DataBase или Recordset) с уже новыми свойствами. Для этого необходимо применить метод Refresh. Таким образом, метод Refresh используется для того, чтобы во время выполнения приложения переинициализировать объект управления данными, но уже с новыми свойствами.

Синтаксис: Data1.Refresh.

После применения метода Refresh указатель текущей записи устанавливается на первой записи в наборе, созданном объектом Data.

UpdateControls

Действие метода UpdateControls полностью аналогично переустановке указателя текущей записи на себя. При этом, связанные элементы управления очищаются и, затем, их содержимое восстанавливается согласно текущей записи. То есть, никаких изменений полей базы данных не происходит, даже если содержимое связанных элементов управления было отредактировано.

Синтаксис: Data1.UpdateControls.

Хорошей иллюстрацией использования метода UpdateControls является предоставление пользователю возможности подтвердить сделанные изменения содержимого текущей записи. Для этого необходимо вмешаться в процедуру обработки события Validate объекта Data.

1. Откройте проект, который использовался для демонстрации удаления записи и добавьте следующий фрагмент кода в процедуру обработки события Validate:

Private Sub Data1_Validate(Action As Integer, Save As Integer)

If Save = False Then Exit Sub

End If

If MsgBox("Сохранить сделанные изменения",vbYesNo,"","",0)=vbNo Then

Data1.UpdateControls

Exit Sub

End If

End Sub

2. Запустите приложение.

3. Измените содержимое какого-нибудь связанного текстового окна, например, года издания, и, используя кнопки перемещения по набору записей, измените положение текущей записи. В результате, на экран будет выдано диалоговое окно, в котором можно подтвердить или отменить сделанные изменения.

4. Закройте приложение.

UpdateRecord

Метод UpdateRecord сохраняет в базе данных содержимое текущей записи, отображаемое в связанных элементах управления.

Синтаксис: Data1.UpdateRecord.

Использование этого метода позволяет сохранить содержимое связанных элементов управления в базе данных без перемещения указателя текущей записи. При этом не генерируется событие Validate. Следует отметить, что перед использованием этого метода необходимо убедиться, что соблюдены правила целостности базы данных.

Следующий пример показывает, как использовать в приложениях метод UpdateRecord:

1. Откройте проект, использованный для демонстрации метода UpdateControls.

2. Разместите на форме командную кнопку, измените ее свойство Caption на “Обновить данные” и добавьте следующий код в процедуру обработки события Click:

Private Sub Command2_Click()

Data1.UpdateRecord

End Sub

3. Запустите проект. Отредактируйте содержимое связанных элементов управления и нажмите кнопку “Обновить данные”. Теперь, сделанные вами изменения сохранены в базе данных.

События

Error

Событие Error происходит, когда при использовании объекта Data возникает какая-либо ошибка доступа к данным, причем в это время программный код, написанный программистом не выполняется. Примером такой ситуации может быть случай, когда база данных, определенная свойством DatabaseName объекта Data, не найдена. Здесь не выполняется написанного программистом кода и, поэтому, обработка такого рода ошибки невозможна с использованием стандартных средств Visual Basic. К счастью, в этом случае происходит событие Error, в процедуру обработки которого программист имеет полное право вмешаться.

Синтаксис: Private Sub Data1_Error (DataErr As Integer, Response As Integer)

Параметр DataErr содержит код ошибки. Различные варианты действия после возникновения ошибки определяются параметром Response, и приведены в следующей таблице:

Постоянная | Значение | Действие
vbDataErrContinue | 0 | После возникновения ошибки приложение продолжает выполняться.
vbDataErrDisplay | 1 | После возникновения ошибки появляется диалоговое окно с соответствующим сообщением. Этот вариант действия установлен по умолчанию.

1. Откройте любой проект и измените свойство DatabaseName объекта управления данными Data1 на C:\BIBLIO.MDB.

2. Запустите проект и убедитесь, что на экране появляется сообщение об ошибке.

3. Добавьте в процедуру обработки события Error следующий фрагмент:

Private Sub Data1_Error(DataErr As Integer, Response As Integer)

Response = vbDataErrContinue

End Sub

4. Запустите приложение. В этом случае сообщения об ошибке не возникает.

Reposition

Событие Reposition происходит, когда запись в наборе записей становится текущей.

Синтаксис: Private Sub Data1_Reposition ().

После загрузки объекта Data и открытия соответствующего набора записей, первая запись в наборе становится текущей и происходит событие Reposition. Это событие также происходит, когда пользователь нажимает кнопки на объекте Data для перемещения по набору записей, а также при использовании методов Move или Find. Вообще говоря, применение любых методов, при которых происходит изменение положения текущей записи, вызывает событие Reposition.

В отличие от события Validate, которое происходит перед изменением текущей записи, событие Reposition всегда происходит после того, как следующая запись стала текущей.

Validate

Событие Validate происходит перед изменением положения текущей записи, а так же перед выполнением методов Update (за исключением случая, когда данные сохраняются с помощью метода UpdateRecord), Delete, Unload, или Close.

Синтаксис: Private Sub Data1_Validate (Action As Integer, Save As Integer)

Аргумент Save определяет состояние связанных элементов управления. Если аргумент Save установлен в True, то содержимое связанных элементов управления было изменено. Если же аргумент Save установлен в False, то никаких изменений в связанных элементах управления не произошло.

Аргумент Action определяет действие, которое вызвало событие Validate. Возможные значения этого параметра и их краткие описания приведены в следующей таблице.

Постоянная | Значение | Описание
vbDataActionCancel | 0 | Отменяет операцию при выходе из процедуры обработки события.
vbDataActionMoveFirst | 1 | Нажата кнопка перехода к первой записи на объекте Data или применен метод MoveFirst объекта Recordset.
vbDataActionMovePrevious | 2 | Нажата кнопка перехода к предыдущей записи на объекте Data или применен метод MovePrevious объекта Recordset.
vbDataActionMoveNext | 3 | Нажата кнопка перехода к следующей записи на объекте Data или применен метод MoveNext объекта Recordset.
vbDataActionMoveLast | 4 | Нажата кнопка перехода к последней записи на объекте Data или применен метод MoveLast объекта Recordset.
vbDataActionAddNew | 5 | Применен метод AddNew объекта Recordset или указатель текущей записи перемещен за конец файла при установленном свойстве EOFAction объекта Data в AddNew.
vbDataActionUpdate | 6 | Применен метод Update объекта Recordset (но не UpdateRecord объекта Data).
vbDataActionDelete | 7 | Применен метод Delete объекта Recordset.
vbDataAction | 8 | Применен один из методов Find объекта Recordset.
vbDataActionBookmark | 9 | Изменено свойство Bookmark объекта Recordset.
vbDataActionClose | 10 | Применен метод Close объекта Recordset.
vbDataActionUnload | 11 | Выгружена экранная форма, содержащая объект управления данными.

В процедуре обработки события Validate можно заменить действие, вызвавшее это событие, на другое действие при помощи установки свойства Action соответствующим образом. При этом совершенно свободно можно менять любой из методов Move на метод AddNew и обратно. Отмена любого действия производится установкой аргумента Action в 0 (Action = vbDataActionCancel).

Примером обработки события Validate можно найти в уже рассмотренном ранее проекте, посвященном применению метода UpdateControls. Только теперь, вместо метода UpdateControls для отмены результатов редактирования, вы можете использовать установку аргумента Action в vbDataActionCancel:

Private Sub Data1_Validate(Action As Integer, Save As Integer) If Save=False Then Exit Sub End If

If MsgBox("Сохранить сделанные изменения",vbYesNo,"","",0)=vbNo Then

Action=vbDataActionCancel

Exit Sub

End If

End Sub


Урок 5

Элемент управления Data и объекты доступа к данным

1. Перемещение по набору записей с использованием Move-методов.

2. Перемещение по набору записей с помощью закладок.

3. Применение Find-методов для поиска записи.

4. Использование метода Seek для поиска записи.

5. Как узнать, можно ли изменять базу данных.

6. Добавление новой записи.

7. Редактирование текущей записи.

8. Удаление записей.

Использование объекта управления данными позволяет быстро создавать простые приложения для работы с базами данных практически без написания кода, только при помощи соответствующих установок его свойств и применения его методов. Для отображения информации на экранной форме используются связанные элементы управления. Однако, для повышения функциональности разрабатываемого приложения вам, скорее всего, придется все-таки писать код. К счастью, Visual Basic 5 обеспечивает очень широкие возможности для программирования операций с данными. В следующих разделах этой главы описана лишь небольшая часть из чрезвычайно широкого сервиса, предоставляемого разработчику системой Visual Basic 5. Более полно и подробно использование в приложениях объектов доступа к данным изложено в последующих уроках.

Перемещение по набору записей с использованием Move-методов

Ранее было показано, как перемещаться от записи к записи в базе данных с использованием кнопок со стрелками на объекте управления данными. В этом разделе обсуждаются способы перемещения по базе данных с использование методов объекта Recordset, созданного объектом Data, то есть с помощью программного кода.

Следующая таблица показывает, вызов какого из методов объекта Recordset эквивалентен нажатию кнопки со стрелкой на объекте управления данными.

Куда переместить указатель текущей записи | Кнопка на объекте Data | Использование метода Move
К первой записи |  | Data1.Recordset.MoveFirst
К предыдущей записи |  | Data1.Recordset.MovePrevious
К следующей записи |  | Data1.Recordset.MoveNext
К последней записи |  | Data1.Recordset.MoveLast

При разработке приложений часто возникает необходимость перемещения к первой или последней записи в наборе записей. Например, если вам требуется запрограммировать процедуру поиска записи, содержащей какое-либо значение, вполне естественным условием будет начинать поиск с первой строки.

Для перемещения указателя текущей записи на первую или последнюю записи используются методы MoveFirst и MoveLast:

Data1.Recordset.MoveFirst

Data1.Recordset.MoveLast.

Если текущий указатель позиционирован на первой или последней записи в наборе записей, то дальнейшее перемещение назад и вперед, соответственно, устанавливает свойства объекта Recordset BOF и EOF в True. Если BOFAction и EOFAction свойства объекта управления данными установлены в единицу, то в этом случае указатель текущей записи становится недопустимым и связанные органы управления очищаются. При использовании метода Move для перемещения указателя за пределы файла Visual Basic генерирует ошибку.

Пусть требуется последовательно проделать какие-либо операции с записями от текущей до последней включительно. В этом случае вы можете использовать следующий фрагмент кода для безопасного перемещения от любой записи в базе данных к последней:

Do While Data1.Recordset.EOF = False

Data1.Recordset.MoveNext

код для работы с текущей записью

Loop

Data1.Recordset.MoveLast

В конце этого цикла указатель текущей записи становится недопустимым и требуется использовать метод MoveLast, чтобы его переустановить на последнюю запись в наборе.

Методы MoveNext и MovePrevious перемещают указатель на следующую и предыдущую записи в наборе записей. Обычно эти методы используются для последовательного просмотра содержимого полей базы данных.

Следующий фрагмент кода загружает два списка и заполняет их названиями книг и годами их публикации из таблицы TITLES базы данных BIBLIO.MDB.

Data1.DatabaseName="С:\VB5\BIBLIO.MDB"

Data1.RecordSource="Titles"

Data1.Refresh

Do Until Data1.Recordset.EOF

Entry = Data1.Recordset("Title")

List1.AddItem Entry

Entry = Data1.Recordset("Year Published")

List2.AddItem Entry

Data1.Recordset.MoveNext

Loop

Вы также можете применить Move метод с целочисленным аргументом для перемещения указателя текущей записи на определенное количество строк вперед или назад (в зависимости от знака аргумента) от текущего положения указателя или от положения, сохраненного с использованием закладки. Следующий пример показывает, как переместить указатель текущей записи на пять строк вперед, начиная от положения, определенного свойством MyBookmark:

Data1.Recordset.Move 5, MyBookmark.

Для перемещения по набору записей также можно использовать такие свойства объекта Recordset, как AbsolutePosition и PercentPosition.

Перемещение по набору записей с помощью закладок

Использование закладки (bookmark) позволяет сначала сохранить указатель на текущую запись и затем, из любого места базы данных непосредственно позиционировать его на этой записи. Свойство Bookmark объекта Recordset содержит указатель на запись. Установкой свойства Bookmark, равному значению сохраненного ранее указателя, вы можете прямо позиционировать указатель на требуемую запись.

Следующий фрагмент кода устанавливает указатель текущей записи в положение, предварительно сохраненное с использованием закладки MyBookmark:

Dim MyBookmark As Variant

MyBookmark = Data1.Recordset.Bookmark

Data1.Recordset.MoveLast

Data1.Recordset.Bookmark=MyBookmark.

Применение Find-методов для поиска записи

Ранее было показано, как осуществлять перемещение по базе данных для просмотра записей. Однако большинству пользователей необходимо иметь возможность не только просматривать запись за записью в базе данных, но и осуществлять поиск записи, отвечающий какому-либо условию. Чтобы найти определенную запись, можно воспользоваться методами Find для объектов - наборов записей типа dynaset и snapshot и методом Seek - для наборов записей типа table.

Методы Find используются для поиска в наборах записей типа dynaset и snapshot. В Visual Basic поддерживается четыре метода Find:

Метод FindFirst осуществляет поиск первой записи, удовлетворяющей условию.

Метод FindLast осуществляет поиск последней записи, удовлетворяющей условию.

Метод FindNext осуществляет поиск следующей записи, удовлетворяющей условию.

Метод FindPrevious осуществляет поиск предыдущей записи, удовлетворяющей условию.

При использовании любого из Find методов необходимо определить условие поиска. Обычно условием поиска является равенство имени поля какому-нибудь значению. Например, следующий фрагмент кода показывает, как найти в таблице Titles первую запись, в которой поле AU_ID равно пяти:

Data1.Recordset.FindFirst "AU_ID=5".

Построим простое приложение для просмотра таблицы Titles базы данных BIBLIO2.MDB с возможностью простого поиска записи. Для этого:

1. Создайте новый проект и разместите на форме объект Data, объект DBGrid и две командные кнопки, как показано на рис.5.1.

Рис.5.1. Главное окно приложения.

2. Установите следующие свойства для объектов Data и DBGrid:

Data1.DatabaseName=" C:\VB5\BIBLIO2.MDB"

Data1.RecordSource=Titles

DBGrid1.DataSource=Data1.

3. Как обычно, предусмотрим возможность завершения работы приложения, определив обработку нажатия кнопки “Выход” следующим образом:

Private Sub Command2_Click()

Unload Me

End Sub.

4. Для загрузки и вывода на экран окна поиска записи добавьте в процедуру обработки нажатия кнопки “Поиск” следующую строку:

Private Sub Command1_Click()

Form2.Show

End Sub

5. Добавьте в проект новую форму и разместите на ней текстовое окно, метку и две командные кнопки, как показано на рис.5.2.

Рис.5.2. Окно поиска записи.

6. Добавьте в процедуру обработки нажатия кнопки “Поиск” следующий код:

Private Sub Command1_Click()

Dim strCompare As String

strCompare = "[Year Published] = " + Text1.Text

Form1!Data1.Recordset.MoveFirst

Form1!Data1.Recordset.FindFirst strCompare

Unload Me

End Sub.

Строковая переменная strCompare определяет условие поиска. Перед использованием метода FindFirst указатель текущей записи устанавливается на первую запись в наборе. Таким образом поиск осуществляется с первой записи в наборе.

В данном случае поиск предусмотрен только по году издания книги. При необходимости вы можете сами по своему усмотрению определить требуемый критерий поиска.

7. Для отмены поиска необходимо выгрузить окно в процедуре обработки нажатия кнопки “Отмена”:

Private Sub Command2_Click()

Unload Me

End Sub.

8. Запустите приложение.

Использование метода Seek для поиска записи

Метод Seek используется для поиска только в наборе записей типа Table. При использовании метода Seek процессор баз данных использует текущий индекс таблицы, который определен свойством Index объекта Recordset. После выполнения этого метода текущей записью в наборе становится найденная запись.

Метод Seek имеет следующий синтаксис:

Recordset.Seek comp, key1, key2.

Первый из аргументов - comp является строкой, которая определяет тип сравнения. В следующей таблице представлены типы сравнения, которые используются с методом Seek.

Строка | Описание
“=” | Равно определенной величине.
“>=” | Больше или равно определенной величине.
“>” | Больше определенной величины.
“<=” | Меньше или равно определенной величине.
“<” | Меньше определенной величины.

Аргументы keyn представляют собой последовательность значений, с которыми Visual Basic производит сравнение содержимого записей таблицы.

В следующем примере метод Seek используется для поиска записи, содержащей величину 5 в поле AU_ID, которое является уникальным индексным полем.

Dim MyTable As Recordset

Set MyTable = Data1.Recordset

MyTable.Index = "AU_ID"

MyTable.Seek "=", 5

Стоит еще раз подчеркнуть, что перед использованием метода Seek необходимо установить текущий индекс с использованием свойства Index. В противном случае обращение к этому методу приведет к ошибке.

Как узнать, можно ли изменять базу данных

Если вы намереваетесь редактировать содержимое базы данных, то должны быть уверены, что это можно делать. При использовании наборов записей типа snapshot, вы не можете вносить изменения в базу данных по определению. Если вы не уверены, что содержимое базы данных можно редактировать, можете проверить выполнение следующих условий:

Свойства Updatable у базы данных, набора записей и поля должны быть установлены в True. Если какое-нибудь из этих свойств установлено в False, то соответствующие данные доступны только для чтения.

Вы должны использовать объект управления данными, у которого свойство ReadOnly установлено в False.

С помощью следующего фрагмента кода вы можете проверить, можно ли редактировать базу данных:

If Data1.ReadOnly = True or Data1.Database.Updatable = False Or _

Data1.Recordset.Updatable = False Then

MsgBox "Эти данные не могут быть изменены"

End If

Для того, чтобы определить, допускает ли возможность редактирования определенное поле базы данных, надо проверить бит dbUpdatableField у свойства Attributes. Если этот бит содержит 0, то содержимое поля изменить невозможно. В противном случае редактирование поля допускается.

В следующем примере показано, как проверить возможность редактирования поля "Author":

If Data1.Recordset.Fields("Author").Attributes And _

dbUpdatableField = 0 Then

MsgBox "Это поле не может быть изменено"

End If

Добавление новой записи

Теперь, когда вы убедились, что база данных и набор записей допускают редактирование, можно добавлять новые записи.

Для того, чтобы добавить новые записи в набор записей надо проделать следующие действия:

1. Добавить новую пустую запись с помощью метода AddNew объекта Recordset. При этом указатель текущей записи сохраняется и затем перемещается на новую запись.

2. Определить новые значения в полях добавленной записи.

3. Сохранить сделанные изменения использованием метода Update. При этом указатель текущей записи принимает значение, которое у него было перед использованием метода AddNew.

Следующий фрагмент кода показывает, как с помощью программного кода добавить новую запись в таблицу Titles.

Data1.DatabaseName = "BIBLIO.MDB"

Data1.RecordSource = "Titles"

Data1.Refresh

Data1.Recordset.AddNew 'Добавить новую запись

Data1.Recordset("Title") = "Visual Basic" 'Установить значения полей

Data1.Recordset("Year Published") = "1996"

Data1.Recordset("AU_ID") = 37

Data1.Recordset("ISBN") = "111-111-11-11"

Data1.Recordset("PubID") = 43

Data1.Recordset.Update ' Сохранить изменения

Редактирование текущей записи

Для того чтобы изменить данные в вашей базе данных, вы должны сначала сделать запись, подлежащую изменению, текущей записью. Это можно просто сделать при помощи одного из методов Move или при помощи кнопок со стрелками на объекте управления данными. Затем можно провести необходимые изменения содержимого связанных объектах управления. Чтобы сохранить сделанные изменения, достаточно просто переместить указатель текущей записи на любую другую запись или использовать метод Update.

Таким образом, для редактирования текущей записи надо сделать следующее:

1. Позиционируйте указатель текущей записи на запись, которую вы хотите редактировать.

2. Определите новые значения в полях текущей записи.

3. Используйте метод Update или любой из методов Move

Следующий пример показывает, как с помощью программного кода изменить содержимое поля PubID в первой записи таблицы Titles. Обратите внимание, что вы можете изменить данные либо в самом наборе записей, либо в связанном объекте управления :

Data1.DatabaseName = "C:\VB5\BIBLIO2.MDB"

Data1.RecordSource = "Titles"

Data1.Refresh

Data1.Recordset("PubID") = "48"

TxtPub.Text = "Visual Basic"

Data1.Recordset.Update

Удаление записей

Чтобы удалить запись, установите указатель на требуемую запись и примените метод Delete объекта Recordset. Удаление одной записи нам уже встречалось при изучении свойства Recordset объекта управления данными. Чтобы удалить группу записей, надо повторить подобные действия необходимое количество раз. В следующем примере из таблицы Titles базы данных BIBLIO.MDB удаляются все записи, у которых поле “Year Published” содержит величину меньшую, чем 1989.

Data1.DatabaseName="С:\VB5\BIBLIO.MDB"

Data1.RecordSource="Select * from Titles where [Year Published]<1989"

Data1.Refresh

Do While Data1.Recordset.EOF = False

Data1.Recordset.Delete

Data1.Recordset.MoveNext

Loop

В этом примере при каждом проходе тела цикла используется метод MoveNext для перемещения указателя на следующую запись. Вам необходимо использовать этот метод, так как после удаления текущая запись становится недопустимой (invalid) и любая попытка доступа к ней вызовет ошибку.

Использование пары методов Delete и MoveNext не является единственным способом удаления сразу нескольких записей из базы данных. Для аналогичных целей можно использовать соответствующий SQL-запрос. Например, вместо приведенного выше кода можно использовать аналогичный по своему действию SQL-запрос:

DELETE FROM Titles WHERE [Year Published] < #1/1/1989#.

Причем, такой подход оказывается более эффективным, чем применение в цикле пары методов Delete и MoveNext.


Урок 6

Связанные с данными элементы управления

Применение связанных с данными элементов управления - Data-bound Controls уже кратко обсуждалось в уроке, посвященном работе с Data Control. Связанные элементы управления служат для отображения на экранной форме содержимого записей из таблиц базы данных и всегда используются только вместе с объектом Data.

После запуска приложений пользователи получают доступ к информации, содержащейся в базах данных именно через связанные элементы управления и проводят операции редактирования, обновления или выполняют другие манипуляции с данными. Любые изменения данных, сделанные пользователем в связанных элементах управления могут быть автоматически отражены в базе данных.

Практически все операции по взаимодействию связанных элементов управления с объектами управления данными реализованы внутри их самих и, поэтому, скрыты от программиста во время разработки приложения. Вследствие этого, применение связанных с данными элементов управления позволяет значительно упростить создание и понизить затраты времени на разработку приложений, ориентированных на работу с базами данных.

В Visual Basic 5 в качестве связанных с данными элементов управления может использоваться целый ряд обычных элементов управления:

Text box - текстовое окно,

Label - метка,

Check box - контрольный индикатор,

Image - изображение,

Picture box, - графическое окно

List box - список,

Combo box - комбинированное окно,

OLE container control - OLE-контейнер.

Примеры использования текстовых окон для отображения содержимого записей базы данных были уже рассмотрены в предыдущей главе.

Кроме обычных элементов управления для работы с базами данных, пятая версия Visual Basic поддерживает несколько ActiveX объектов:

Data-bound list box - связанный с данными список,

Data-bound combo box - связанное с данными комбинированное окно,

MSFlexGrid - сетка,

Data-Bound Grid (DBGrid) - связянная с данными сетка,

MaskedEdit - окно редактирования с маской,

RichTextBox - улучшенное окно редактирования.

Все эти объекты, за исключением MaskedEdit и RichTextBox позволяют отображать на экране целые группы записей в виде списков или таблиц. А применение DBGrid позволяет настолько повысить функциональность разрабатываемых приложений, что об этом будет специальный разговор.

Более того, если по каким-либо причинам вас не удовлетворяют возможности поставляемых вместе с Visual Basic связанных элементов управления, вы можете использовать связанные элементы управления, созданные другими разработчиками, или можете сами создать собственные ActiveX объекты для связи с данными по вашему вкусу.

Общие свойства связанных элементов управления

Большинство связанных с данными элементов управления имеют три свойства, само название которых определяет их принадлежность к группе объектов, имеющих самое непосредственное отношение к данным - DataField, DataSource и DataChanged. Во всех названиях свойств присутствует слово Data. В следующей таблице приведено их краткое описание.

Свойство | Описание
DataField | Этим свойством определяется имя поля в наборе записей, созданном объектом управления данными
DataSource | Этим свойством определяется имя объекта управления данными, с которым связывается данный элемент управления
DataChanged | Это свойство показывает, было ли пользователем изменено содержимое связанного элемента управления

Чтобы использовать возможности связанных элементов управления вам необходимо: во-первых - определить источник данных указанием имени объекта управления данными в свойстве DataSource и, во-вторых - связать его с требуемым полем базы данных соответствующей установкой свойства DataField. Если база данных доступна во время разработки приложения, то список допустимых полей отображается в окне Properties (Свойства) связанного элемента управления (рис.6.1).

Рис.6.1. Установка свойства DataField текстового окна


Урок 7

Метка (Label), текстовое окно (Text box), окно редактирования с маской (MaskedEdit), контрольный индикатор (Check box), Изображение (Image) и графическое окно (Picture box)

1. Label и Text Box.

2. Check box.

3. MaskedEdit.

4. Image и Picture box.

Label и Text Box

Объекты управления Label и Text Box используются для вывода текстовой информации на экранную форму. При работе с базами данных эти элементы управления могут применяться и для отображения на экране текстовой информации из полей базы данных. На панели инструментов Visual Basic метка и текстовое окно изображены пиктограммами [image] и [image] соответственно. Чтобы установить связь с объектом управления данными необходимо определить их свойства DataSource и DataField соответствующим образом.

Метка (Label) может быть как однострочной, так и многострочной, но в отличие от текстового окна в ней не предусмотрены возможности редактирования и прокрутки данных. Если выводимый текст не помещается в отведенное для метки место на форме, то текст обрезается. Управлять этим поведением можно при помощи свойств AutoSize и WordWrap. Свойство AutoSize определяет поведение размеров метки при изменении длины выводимого текста и по умолчанию устанавливается в False, при этом размер метки не увеличивается с ростом длины строки. При установленном в True свойством AutoSize с помощью установок свойства WordWrap определяется поведение вертикального размера метки. Если WordWrap установлен в False, то при увеличении длины строки переносов слов не будет и горизонтальный размер метки будет увеличиваться до тех пор, пока текст не достигнет правого края экранной формы. Если WordWrap установлен в True, то по мере достижения правой границы метки будет осуществляться перенос слов. Такое поведение меток продемонстрировано на рис.7.1.

Рис.7.1. Управление поведением размеров метки при выводе текста с помощью свойств AutoSize и WordWrap

Следует помнить, что для сохранения заданного размера метки по горизонтали необходимо установить свойство WordWrap в True до установки свойства AutoSize. В противном случае, этот размер сразу уменьшится до значения, определяемого длиной строки в свойстве Caption.

Для отображения текстовой информации из базы данных все-таки гораздо больше подходит текстовое окно. Связь с объектом управления данными осуществляется установкой свойств DataSource и DataField.

Как и метка, элемент управления - текстовое окно (TextBox) может быть однострочным или многострочным. Это определяется установкой свойства MultiLine, которое по умолчанию устанавливается в False. Если вы хотите использовать многострочное текстовое окно с возможностью прокрутки данных, то после определения его размеров и месторасположения на экранной форме установите свойство MultiLine в True и, если требуется, свойство ScrollBars (рис 7.2).

Рис.7.2. Однострочное и многострочное текстовые окна

Если вам требуется выводить текст не подлежащий редактированию пользователем, установите, например, свойство Locked в True.

Таким образом, метки все-таки удобнее используются для информирования пользователя о том, какого рода данные отображаются в текстовом окне, которое, как правило, располагается рядом. В этом случае метка не связывается с базой данных. Примеры такого использования меток и текстовых окон обсуждались в предыдущей главе.

Check box

Связанный с данными контрольный индикатор (Check box) можно использовать для наглядного представления на экранной форме информации из полей базы данных логического типа (Да или Нет, 1 или 0, True или False). Примерами таких данных могут служить: оплачен или не оплачен счет - при разработке системы управления счетами, есть товар на складе или нет - при автоматизации складского хозяйства, постоянный покупатель или нет - в системе работы с покупателями (рис7.3) и т.п. На панели инструментов Visual Basic контрольный индикатор обозначен пиктограммой [image]. Связь контрольного индикатора с объектом управления данными осуществляется установкой свойств DataSource и DataField.

7_3.gif (3861 bytes)

Рис.7.3. Пример использования контрольного индикатора

MaskedEdit

Известно, что пользователи часто совершают ошибки при вводе информации в базы данных. Впрочем, не ошибается лишь тот, кто ничего не делает. Установить некоторый контроль над действием пользователей позволяет использование для ввода специальной информации окна редактирования с маской (MaskedEdit) Этот элемент управления позволяет так же помещать в поле ввода символы разметки. Примером такой информации может выступать номер телефона или почтовый индекс. Типичный номер телефона имеет следующий формат, где скобки и дефисы являются символами разметки, а символ # определяет любую цифру:

(###)###-##-##

Почтовый индекс состоит из шести цифр и не должен содержать букв или других символов.

Окно редактирования с маской имеет свойство Mask (Маска), которое позволяет определить формат отображаемой строки в поле ввода. По умолчанию свойство Mask не определено и поведение объекта Mask полностью аналогично обычному текстовому окну. Символы, которые используются в качестве маски приведены в таблице.

Таблица. Символы, используемые в качестве маски

Символ | Описание
# | Любая цифра
. | Десятичная точка (например, 3.1415)
, | Разделитель тысяч (например, 1,000,000)
: | Разделитель времени (например, 21:30)
/ | Разделитель даты (например, 05/08/97)
& | Любой символ
A | Любая цифра или буква
? | Любая буква

С полем базы данных объект MaskedEdit связывается установкой свойств DataSource и DataField.

Построим простое приложение в котором будут реализованы возможности рассмотренных в этом разделе связанных элементов управления. Создадим, например, при помощи приложений VisData или MS Access базу данных CUSTOMER.MDB с одной таблицей CUSTOMERS, в которой будем хранить информацию о покупателях. Пусть эта таблица будет содержать следующие поля:

Таблица. Структура таблицы CUSTOMERS базы данных CUSTOMER.MDB

Поле | Тип | Ширина | Описание
Name | Text | 20 | Имя
LastName | Text | 20 | Фамилия
Concern | Text | 50 | Предприятие
Const | Boolean | 1 | Постоянный покупатель или нет
Address | Text | 50 | Адрес предприятия
Phone | Text | 20 | Телефон
Note | Text | 50 | Примечание

Так как наше приложение будет простой программой просмотра содержимого базы данных, то самостоятельно добавьте в таблицу CUSTOMERS несколько произвольных записей.

Создайте новый проект и расположите на форме объект управления данными, семь меток, пять текстовых окон, окно редактирования с маской и один контрольный индикатор, как показано на рис.7.4.

7_4.gif (5602 bytes)

Рис.7.4. Расположение элементов управления на экранной форме

Теперь в окне свойств объекта Data1 определите свойство Caption, как “Покупатели”, установите свойство DatabaseName как имя файла базы данных с указанием полного пути C:\WORK\ CUSTOMER.MDB и укажите имя таблицы Customers в качестве источника данных RecordSource.

Затем установите свойства меток, текстовых окон, окна редактирования с маской и контрольного индикатора следующим образом:

Label1.Caption=Имя

Label2.Caption=Фамилия

Label3.Caption=Предпреятие

Label4.Caption=Адрес

Label5.Caption=Телефон

Label6.Caption=Примечание

Text1.DataSource=Data1

Text1.DataField=Name

Text2.DataSource=Data1

Text2.DataField=LastName

Text3.DataSource=Data1

Text3.DataField=Concern

Text4.DataSource=Data1

Text4.DataField=Address

Text5.DataSource=Data1

Text5.DataField=Note

MaskedEdit1.DataSource=Data1

MaskedEdit1.DataField=Phone

MaskedEdit1.Mask=(###)###-##-##

Check1.Caption=Постоянный покупатель

Check1.DataSource=Data1

Check1.DataField=Const

Метку Label7 будем использовать аналогично текстовому окну, в котором отображается содержимое поля Note (Примечание). Для этого установите следующие свойства:

Label6.DataSource=Data1

Label6.DataField=Note

Теперь сохраните и запустите проект. Результат работы приложения показан на рис.7.5.

Рис.7.5. Результат работы приложения

Image и Picture box

Для отображения на форме графических объектов в формате Windows (битовые карты - файлы с расширением BMP, иконы - ICO и метафайлы - WMF), а также рисунков в форматах GIF и JPG можно использовать элементы управления Image (Изображение) и Picture Box (Графическое окно). Связываются эти объекты управления с базой данных установкой все тех же свойств DataSource и DataField и на панели инструментов Visual Basic обозначены пиктограммами [image] и [image] соответственно.

Хотя эти элементы управления и служат одной цели, тем не менее, они отличаются эти друг от друга способом отображения графики. Свойство Stretch (Растяжение) объекта Image по умолчанию устанавливается в False и линейные размеры объекта “Изображение” будут изменяться в соответствии с действительными размерами рисунка. Если Stretch равно True, то рисунок при выводе его на форму будет сжиматься или растягиваться согласно взаимному соотношению размеров рисунка и линейным размерам объекта “Изображение”. В этом случае, если длина и ширина объекта и самого рисунка не пропорциональны друг другу то, то что вы увидите на экране может заметно отличаться от оригинала. Картинка может заметно исказиться (рис.7.6). Таким образом, при использовании объекта Image для вывода графики в любом случае, независимо от установки свойства Stretch, вся картинка разместится на экранной форме.

Рис.7.6. Влияние установки свойства Stretch на вывод изображения

Использование для представления графики объекта “Графическое окно” позволяет выводить рисунки без искажений. При установке свойства AutoSize объекта Picture в False, а именно так оно определяется по умолчанию, размеры объекта не будут приводиться в соответствие с размерами картинки. Если в этом случае картинка не поместится целиком в графическое окно, то она будет обрезана (рис.7.7). С другой стороны, если AutoSize равен True, то при выводе картинки на экран размеры графического окна изменятся согласно размерам выводимого изображения.

Рис.7.7. Влияние установки свойства AutoSize на вывод изображения

Использование элементов управления Image и Picture Box не позволяют проводить редактирование изображений. Если в вашем приложении необходимо вносить изменения в графические изображения, то лучше воспользоваться контейнером OLE.


fsn.net: Урок 8

Окно списка (List box), комбинированное окно (Combo box), Контейнер OLE (OLE container), связанное окно списка (Data-bound list box) и связанное комбинированое окно (Data-bound combo box).

1. List box и Combo box.

2. OLE container.

3. Data-bound list box и Data-bound combo box.

List box и Combo box

Окна списка и комбинированные окна удобно использовать когда необходимо предоставить возможность пользователю выбирать элемент из списка. Кроме того, с помощью комбинированного окна пользователь может добавить элемент в список. На панели инструментов Visual Basic окно списка и комбинированное окно изображены пиктограммами [image] и [image]. Как и все обычные элементы управления Visual Basic окна списка и комбинированные окна связываются с базой данных установкой свойств DataSource и DataField.

Заполнение списков проводится при помощи метода AddItem или установкой свойства List.

Построим простое приложение для демонстрации использования списка и комбинированного списка. Положим, что у нас есть база данных PRODUCTS.MDB для учета товаров, единственная таблица которой PRODUCTS содержит поля, как показано в таблице .

Таблица. Структура таблицы PRODUCTS

Поле | Тип | Ширина поля | Описание
Id | Text | 10 | Код товара
Product | Text | 20 | Наименование товара
Price | Text | 10 | Цена за единицу товара
Depository | Text | 3 | Индикатор наличия товара на складе
Note | Text | 50 | Примечание

Текстовое поле Depository определяет наличие товара на складе и может содержать только одну из двух строк “Да” или “Нет”.

Создайте новый проект и расположите на форме объект управления данными, пять меток, четыре текстовых окна, командную кнопку, список и комбинированный список как показано на рис.8.1.

Рис.8.1. Расположение элементов управления на форме

В окне свойств объекта Data1 определите свойство Caption, как “Товары”, установите свойство DatabaseName как имя файла базы данных с указанием полного пути, например, C:\WORK\PRODUCTS.MDB и укажите в качестве источника данных имя таблицы PRODUCTS определением свойства RecordSource. Затем установите свойства меток, текстовых окон и командной кнопки, списка и комбинированного списка следующим образом:

Label1.Caption=Код товара

Label2.Caption=Наименование

Label3.Caption=Цена

Label4.Caption=Присутствие на складе

Label5.Caption=Примечание

Command1.Caption=Добавить

Text1.DataSource=Data1

Text1.DataField=Id

Text2.DataSource=Data1

Text2.DataField=Product

Text3.DataSource=Data1

Text3.DataField=Price

Text4.DataSource=Data1

Text4.DataField=Note

List1.DataSource=Data1

List1.DataField=Depository

Combo1.DataSource=Data1

Combo1.DataField=Depository

В процедуру обработки события нажатия кнопки добавьте код, который добавит пустую запись в набор записей и сделает ее текущей:

Private Sub Command1_Click()

Data1.Recordset.AddNew

End Sub

И наконец, в окне свойств списка и комбинированного списка определите свойство List добавив строки “Да” и “Нет”.

Запустите проект и добавьте записи в базу данных. Ввести значение в поле Depository можно выбрав “Да” или “Нет” в простом или комбинированном списке. При перемещении указателя текущей записи по набору записей в комбинированном списке будет отображаться “Да” или “Нет” в зависимости от содержимого поля Depository, в то время как в обычном списке значение поля будет выделяться подсветкой (рис.8.2).

Рис.8.2. Результат работы приложения

Контейнер OLE (OLE container)

Применение объекта OLE container дает возможность не только размещать на экранной форме приложения различные OLE-объекты, хранящиеся в базе данных, но и редактировать их. В качестве OLE-объектов могу выступать например: рисунки, документы Microsoft Word, таблицы Microsoft Excel, звуковые фрагменты и видеоклипы и многое другое. Посмотреть, какие OLE-объекты вы имеете возможность разместить в своей базе данных можно, например, выполнив команду меню Вставка->Объект текстового процессора Microsoft Word. В появившемся окне будет находиться список всех объектов OLE, зарегистрированных в вашей системе (рис.8.3).

В своем приложении вы можете использовать либо внедренные (embedded) либо связанные (linked) объекты. Внедренные OLE-объекты содержат информацию как о самом объекте, так и о приложении его создавшем - OLE-сервере. Редактирование внедренного объекта можно проводить только с помощью приложения-клиента. В нашем случае это разрабатываемая СУБД. В отличие от внедренных, связанные OLE-объекты в базе данных сохраняют связь с объектом - оригиналом и любое изменение оригинала приведет к аналогичному изменению объекта, хранящегося в БД.

В базе данных формата MS Access внедренные объекты или ссылки на связанные объекты всегда хранятся в поле типа OLE Object. Понятно, что если вы создадите базу данных с документами Microsoft Word, то просматривать эту БД можно будет только на компьютере, на котором так же установлен этот текстовый процессор.

Рис.8.3. Список OLE-объектов, зарегистрированных в системе

Контейнер OLE связывается с базой данных установкой свойств DataSource и DataField и его пиктограмма на панели инструментов - [image].

Продемонстрируем использование элемента управления OLE-контейнер в приложениях. Для этого построим простую СУБД по управлению базой данных, содержащую OLE-объекты.

Положим, что у на есть база данных OLEOBJ.MDB, таблица которой OBJ содержит поля, как показано в таблице.

Таблица. Структура таблицы OBJ

Поле | Тип | Ширина поля | Описание
Id | AutoNumber | 4 | Идентификатор объекта
Desc | Text | 20 | Описание объекта
Obj | OLE-object | Свыше 1 Гб | OLE-объект

Создайте новый проект и расположите на форме объект управления данными, две метки, два текстовых окна, OLE-контейнер и командную кнопку, как показано на рис.8.4.

Рис.8.4. Расположение элементов управления на экранной форме

Установите свойства объекта управления данными следующим образом. Свойство Caption определите как “OLE объекты”, свойство DatabaseName установите как имя файла базы данных C:\WORK\DB.MDB и укажите в качестве источника данных (свойство RecordSource) имя таблицы OBJ. Затем установите свойства меток, текстовых окон, контейнера OLE и командной кнопки:

Label1.Caption=Идентификатор

Label2.Caption=Описание

Text1.DataSource=Data1

Text1.DataField=Id

Text2.DataSource=Data1

Text2.DataField=Desc

OLE1.DataSource=Data1

OLE1.DataField=Obj

Command1.Caption=Добавить

Запустите проект. Если ваша базе данных не пуста и в ней есть какие-либо записи с OLE-объектами (в данном случае с помощью MS Access созданы две записи с математическим уравнением и рисунком), вы увидите, что в OLE-контейнере отобразится содержимое поля Obj первой записи (рис.8.5).

Рис.8.5. Результат работы приложения

Внедренные объекты можно редактировать. Нажатие правой кнопки мыши на OLE-контейнере приведет к появлению всплывающего меню с двумя пунктами: “Изменить” и “Открыть”. Выбор первого пункта позволит вам редактировать содержимое OLE-контейнера прямо в вашей экранной форме, в то время как выбор второго - приведет к запуску приложения-сервера и загрузке в него содержимого OLE-контейнера (рис.8.6). В данном случае это будет редактор формул, входящий в поставку текстового процессора MS Word. После внесения необходимых изменений закройте приложение-сервер и убедитесь, что результаты редактирования отображены в базе данных.

Рис.8.6. Запуск приложения-сервера

Таким образом мы создали приложение, которое позволяет не только просматривать, но и редактировать OLE-объекты, хранящиеся в базе данных. Это могут быть совсем разные объекты: математические уравнения, рисунки, текстовые документы, таблицы, диаграммы и др.

В нашем приложении осталась незадействована командная кнопка “Добавить”. Очевидно, что она должна быть предназначена для добавления новых записей в базу данных. Добавим в процедуру обработки события нажатия этой кнопки код, который позволит нам добавлять пустую запись в конец набора записей:

Private Sub Command1_Click()

Data1.Recordset.AddNew

OLE1.InsertObjDlg

End Sub.

Метод InsertObjDlg OLE-контейнера отображает на экране диалоговое окно выбора типа объекта (рис.8.7). Отметив кнопку-переключатель Create New (Создать новый), вы можете вставить новый объект в базу данных, выбрав требуемый тип из списка зарегистрированных в системе объектов OLE. Выбор кнопки-переключателя Create from File (Создать из файла) позволит вам вставить в базу данных уже существующий на вашем жестком диске OLE-объект (рис.8.8). При этом вы можете установить связь между файлом-оригиналом и добавленным в базу данных объектом, отметив контрольный индикатор Link (Установить связь).

Рис.8.7. Диалоговое окно вставки объекта

Рис.8.8. Вставка объекта из файла

Теперь с помощью нашего приложения вы можете не только просматривать и редактировать OLE-объекты в базе данных, но и добавлять новые, предварительно выбрав его тип. Однако в базах данных хранятся, как правило, объекты одного типа. Для создания объекта требуемого типа можно использовать метод CreateEmbed объекта OLE-контейнер. Метод имеет следующий синтаксис:

object.CreateEmbed sourcedoc, class.

Объектное выражение object определяет имя объекта OLE-контейнера. Первый аргумент - sourcedoc является обязательным и определяет имя файла документа, используемого как шаблон для внедряемого объекта. Если вы не определяете этот файл, то необходимо задать этот аргумент в виде пустой строки “”. Второй аргумент - class не обязателен и определяет класс внедряемого объекта. Этот аргумент игнорируется, если определен параметр sourcedoc.

Когда вы создаете новый внедренный OLE-объект при помощи метода CreateEmbed, приложение-сервер, определяемое именем класса - class, должно быть корректно зарегестрировано в вашей системе. Посмотреть классы объектов можно выбрав в окне свойств OLE-контейнера свойство Class и нажав справа кнопку с тремя точками. Появится окно со списком всех зарегистрированных классов (рис.8.9).

Рис.8.9. Окно выбора класса

Свойство Class объекта OLE-контейнер определяет имя класса внедряемого объекта следующим образом:

application.objecttype.version

objecttype.version

где application определяет приложение, objecttype определяет тип объекта в библиотеке объектов приложения и version определяет номер версии. Например, электронная таблица MS Ecxel пятой версии поддерживает несколько объектов - рабочие листы (worksheets) и диаграммы (charts). Соответствующие имена классов - Excel.Sheet.5 и Excel.Chart.5.

Вернемся к нашему приложению. Несколько изменим код обработки события нажатия командной кнопки, для вставки математического выражения:

Private Sub Command1_Click()

Data1.Recordset.AddNew

OLE1.CreateEmbed "", "Equation.2"

End Sub

Теперь при нажатии кнопки “Добавить” в базу данных будут добавляться записи, содержащие математические уравнения, которые вы можете редактировать в окне OLE-контейнера на вашей экранной форме.

Связанное окно списка (Data-bound list box) и связанное комбинированое окно (Data-bound combo box)

Связанные с данными окно списка и комбинированное окно в отличие от своих обычных аналогов List и Combo box специально разработаны для использования в СУБД и их основное предназначение - упростить ввод или отображение на экранной форме информации из базы данных в виде списков.

По умолчанию пиктограммы этих элементов управления отсутствуют на панели инструментов Visual Basic. Чтобы их добавить, надо выполнить команду меню Project -> Components, выбрать из списка элементов управления (Controls) Microsoft data bound list controls и затем нажать кнопку OK (рис.8.10). К аналогичному результату приведет щелчок правой кнопкой мыши на панели инструментов. Последствием этих действий будет появление следующих пиктограмм: (связанный список) и [image] (связанное комбинированное окно) на панели инструментов. Теперь вы можете добавлять эти объекты управления в проекты своих приложений просто отбуксировав их с панели инструментов на экранную форму.

Рис.8.10. Список элементов управления Visual Basic

Объекты управления Data-bound list box и Data-bound combo box имеют пять свойств, установка которых определяет их поведение как связанных с данными элементов управления. Краткое описание этих свойств приведено в таблице.

Таблица. Свойства связанного списка и связанного комбинированного окна

Свойство | Описание
RowSource | Имя объекта управления данными, которое используется для заполнения списка.
ListField | Имя поля в наборе записей, определенном свойством RowSource, которое используется для заполнения списка.
DataSource | Имя объекта управления данными, с которым связывается список.
DataField | Имя поля в наборе записей, определенном свойством DataSource. Это поле используется для определения, какой элемента списка выбран. После выбора элемента списка и перемещения указателя текущей записи на другую запись это поле будет обновлено.
BoundColumn | Имя поля в наборе записей, определенном свойством RowSource, значение которого будет скопировано.

Поля ,определяемые свойствами DataField и BoundColumn, должны быть одного типа.

Приведем пример простого приложения, в котором показано на практике как использовать связанные списки и связанные комбинированные окна для связи с данными.

Будем использовать базу данных FRIENDS.MDB, которая была создана ранее при демонстрации некоторых возможностей приложения VisData в первой главе. Эта база данных состоит из двух таблиц: PERSON, в которой находится персональная информация о ваших знакомых, и WORKPLACE, содержащая сведения об их местах работы или учебы. Связь между таблицами осуществляется на основании содержимого совпадающих полей Id_work.

В приложении реализуем функцию просмотра значений некоторых полей обоих таблиц в связанных текстовых окнах. Кроме того, предоставим пользователю возможность обновления данных о месте работы с помощью выбора подходящего элемента из связанного списка или связанного комбинированного окна. Понятно, что в этом случае выбор из списка возможных альтернатив может быть сделан только исходя из данных таблицы WORKPLACE.

Создайте новый проект и расположите на экранной форме два объекта управления данными, связанный список, связанное комбинированное окно, четыре метки и три текстовых окна, как показано на рис.8.11.

Рис.8.11. Расположение элементов управления на форме

В окне свойств первого объекта управления данными установите свойство DataBaseName как имя базы данных FRIENDS.MDB с указанием полного пути к файлу. Затем установите свойство RecordSource как PERSON. Использование кнопок со стрелками этого объекта управления даст возможность пользователю перемещаться от записи к записи в таблице PERSON. У второго объекта управления данными установите свойство DataBaseName аналогичным образом и определите источник записей как следующий SQL-запрос:

SELECT Id_work, Office FROM WORKPLACE ORDER BY Id_work.

В результате выполнения этого запроса будет создан набор записей, содержащий значения полей Id_work и Office, и упорядоченный по полю Id_work. В принципе, второй объект управления данными можно сделать невидимым, установив его свойство Visible в False, так как для работы нам потребуется только созданный им набор записей.

Определите свойства меток, текстовых окон, списка и комбинированного окна следующим образом:

Label1.Caption=Имя

Label2.Caption=Фамилия

Label3.Caption=Идентификатор места работы

Label4.Caption=Обновление места работы

Text1.DataSource=Data1

Text1.DataField=Name

Text2.DataSource=Data1

Text2.DataField=LastName

Text3.DataSource=Data1

Text3.DataField= Id_work

DBList1.RowSource=Data2

DBList1.ListField=Office

DBList1.DataSource=Data1

DBList1.DataField=Id_work

DBList1.BoundColumn=ID_work

DBCombo1.RowSource=Data2

DBCombo1.ListField=Office

DBCombo1.DataSource=Data1

DBCombo1.DataField= Id_work

DBCombo1.BoundColumn=ID_work

Сохраните и запустите проект (рис.8.12). В связанных текстовых окнах на экранной форме будет отображаться содержимое полей Name, LastName и Id_work из таблицы PERSON. Список и комбинированное окно заполнятся названиями мест работы из таблицы WORKPLACE, причем место работы человека, определенного содержимым текстовых окон, будет выделено в списке подсветкой. Перемещением по набору записей с помощью первого объекта управления данными установите указатель на запись, где вы хотите обновить место работы. Из списка или комбинированного окна выберите требуемое значение и нажмите какую-либо кнопку на первом объекте управления данными, чтобы переместить указатель текущей записи и произвести обновление данных. Теперь верните указатель на ту же запись и убедитесь что информация о месте работы изменена в соответствие со сделанным выбором.

Рис.8.12. Результат работы приложения


fsn.net: Урок 9

Сетка (MSFlexGrid) и улучшенное текстовое окно (RichTextBox)

1. MSFlexGrid.

2. RichTextBox.

MSFlexGrid

ActiveX объект MSFlexGrid может применяться для отображения на экранной форме данных, представленных в табличном виде. Если сетка MSFlexGrid связана с базой данных, то данные используются в режиме только для чтения. Это значит, что вы можете проводить операции над содержимым ячеек таблицы, например, сложение, умножение, сравнение, слияние строк, вывод результатов в ячейки, но не можете обновить данные в базе данных. Для установки связи сетки MSFlexGrid с данными используется свойство DataSource, которое определяется как имя соответствующего объекта управления данными.

Чтобы добавить объект MSFlexGrid на панель инструментов, выполните команду меню Project -> Components и выберите из списка элементов управления (Controls) Microsoft FlexGrid Control и затем нажмите кнопку OK. После этого на панели инструментов появится новая пиктограмма - [image].

Свойства Row и Col объекта MSFlexGrid определяют положение текущей ячейки. Содержимое текущей ячейки доступно через свойство Text, то есть данные в таблице хранятся в текстовом виде. Этот факт не доставляет много хлопот, так как в Visual Basic есть функции преобразования строки в число и обратно. Если в ячейку требуется занести число, то необходимо использовать функцию преобразования числа в строку CStr, например:

MSFlexGrid1.Text=CStr(sum).

С другой стороны, при извлечении числа из текстового содержимого ячейки можно использовать функцию Val, например:

sum=sum+Val(MSFlexGrid1.Text)

Свойства Rows и Cols позволяют определить количество строк и столбцов в сетке. Наличие заголовков у строк и столбцов можно установить с помощью свойств FixedRows и FixedCols. Содержимое заголовков определяется свойством FormatString. Например, следующей пример показывает, как установить заголовки столбцов сетки:

s$ = "<Код товара |<Дата зак. |<Код покуп. |>Кол-во |<Наименов.товара |<Цена "

MSFlexGrid1.FormatString = s$

Заголовки строк сетки определяются аналогичным образом, только в начало строки необходимо поместить символ “точка с запятой”, который укажет сетке, что это именно заголовки строк:

s$ = ";Имя|Адрес|Телефон"
MSFlexGrid1.FormatString = s$

Получить или установить содержимое произвольной ячейки позволяет свойство TextArray. Это свойство имеет синтаксис:

MSFlexGrid1.TextArray( cellindex ) [= string ]

где string - текстовое содержимое ячейки, определенной аргументом cellindex. Аргумент cellindex представляет собой индекс ячейки в сетке и рассчитывается следующим образом. К номеру колонки произвольной ячейки прибавляется произведение ее номера строки на общее количество колонок в сетке, определенное свойством Cols. Для расчета аргумента cellindex удобно использовать следующую функцию:

Function GetCellIndex(row As Integer, col As Integer) As Long
    GetCellIndex = row*MSFlexGrid1.Cols + col
End Function

При операциях со строками и колонками объекта MSFlexGrid следует помнить, что отсчет строк и колонок ведется не с единицы, а с нуля. Таким образом, первая строка сетки, в которой обычно находятся заголовки, на самом деле является нулевой строкой.

Для демонстрации использования объекта MSFlexGrid создадим приложение, предназначенное для просмотра полей базы данных, содержащей информацию о покупателях, сделанных ими заказах и товарах.

База данных CASTOMER.MDB включает в себя три таблицы:

CASTOMER - содержит информацию о покупателях,

ORDERSALES - содержит информацию о сделанных ими заказах,

PRODUCTS - содержит информацией о товарах.

Структуры таблиц базы данных показаны в следующих таблицах.

Таблица. Структура таблицы CASTOMER

Поле | Описание
Id_cust | Код покупателя
Name | Имя
LastName | Фамилия
Concern | Предприятие
Address | Адрес
Phone | Телефон
Note | Примечание

Таблица. Структура таблицы ORDERSALES

Поле | Описание
Id_prod | Код товара
Date_order | Дата заказа
id_cust | Код покупателя
num_order | Количество заказанного товара
Date_sale | Дата продажи
num_sale | Количество проданного товара
Note | Примечание

Таблица. Структура таблицы PRODUCTS

Поле | Описание
Id_prod | Код товара
Product | Наименование товара
Price | Цена за единицу товара
Repository | Наличие товара на складе
Note | Примечание

Связь между таблицами осуществляется на основании значений совпадающих полей id_cust и Id_prod.

Создайте новый проект и расположите на форме два объекта управления данными, пять меток, четыре текстовых окна, командную кнопку и объект управления MSFlexGrid. Размещение объектов показано на рис.9.1.

Рис.9.1. Расположение элементов управления на экранной форме

В окне свойств первого объекта управления данными установите свойство DataBaseName как имя базы данных CASTOMER.MDB. Затем определите свойство RecordSource этого объекта как CASTOMER. У второго объекта Data установите свойство DataBaseName аналогичным образом, а его свойство RecordSource определим позднее в коде программы и оно будет устанавливаться должным образом во время выполнения приложения. Этот объект можно сделать невидимым, установив его свойство Visible в False.

Определите свойства меток, текстовых окон и объекта MSFlexGrid следующим образом:

Label1.Caption=Имя
Label2.Caption=Фамилия
Label3.Caption=Предприятие
Label4.Caption=Код покупателя
Label5.Caption=Заказы
Text1.DataSource=Data1
Text1.DataField=Name
Text2.DataSource=Data1
Text2.DataField=LastName
Text3.DataSource=Data1
Text3.DataField=Concern
Text4.DataSource=Data1
Text4.DataField=Id_Cust
DataSource=Data2
FixedCols=0
FixedRows=1
Cols=2
Rows=2

Теперь установите свойство Caption командной кнопки как “Просмотр заказов” и добавьте в процедуру обработки нажатия кнопки следующий код:

Private Sub Command1_Click()
' Определим источник данных для объекта Data2
Dim str As String
str = "SELECT ORDERSALES.Id_prod, PRODUCT.product, PRODUCT.price,
ORDERSALES.num_order, ORDERSALES.Date_order, ORDERSALES.Id_cust, ORDERSALES.Date_sale,
ORDERSALES.num_sale FROM ORDERSALES, PRODUCT WHERE ordersales.Id_cust=" + Text4.Text
+ "AND ordersales.id_prod = product.id_prod"

Data2.RecordSource = str
Data2.Refresh

' Установим заголовки столбцов у объекта MSFlexGrid
s$ = "<Код товара |<Наименов.товара |<Цена
|<Кол-во |<Дата заказа |<Код покуп. |<Дата
продажи |<Кол-во прод. "

MSFlexGrid1.FormatString = s$
MSFlexGrid1.Rows = MSFlexGrid1.Rows + 1
MSFlexGrid1.row = MSFlexGrid1.Rows - 1
MSFlexGrid1.col = 0
MSFlexGrid1.Text = "Всего:"
MSFlexGrid1.col = 1
' Расчет суммы заказа
Dim i As Integer
Dim sum As Double
sum = 0
For i = MSFlexGrid1.FixedRows To MSFlexGrid1.Rows - 1
    sum = sum + Val(MSFlexGrid1.TextArray(GetCellIndex(i, 2))) *
    Val(MSFlexGrid1.TextArray(GetCellIndex(i, 3)))
Next

MSFlexGrid1.Text = CStr(sum) + " руб."
MSFlexGrid1.row = 1
MSFlexGrid1.col = 1
End Sub

Function GetCellIndex(row As Integer, col As Integer) As Long
    GetCellIndex = row * MSFlexGrid1.Cols + col
End Function

Сохраните и запустите проект. Результат работы приложения показан на рис.9.2.

Рис.9.2. Результат работы приложения

Улучшенное текстовое окно (RichTextBox)

Возможностей обычного текстового окна в большинстве случаев достаточно для отображения текстовой информации из базы данных. Однако, у него есть один недостаток - текстовое окно не допускает форматирования текста. Если необходимо хранить в базе данных текст с элементами форматирования, вы можете использовать улучшенное текстовое окно (RichTextBox). Этот объект управления позволяет работать со шрифтами, изменять атрибуты у выделенного фрагменты текста (полужирный, курсив, подчеркнутый или перечеркнутый), осуществлять различные виды выравнивание текста (по левому краю окна, по правому краю окна или по центру), устанавливать отступы и выступы у абзацев и др.

Все эти функции реализованы посредством поддержки этим объектом управления формата RTF (Rich Text Format - расширенный текстовый формат). Объект RichTextBox транслирует текст в формате RTF и выполняет форматирование и отображение текста на экранной форме.

С базой данных RichTextBox связывается установкой свойств DataSource и DataField. Тип поля в базе данных формата MS Access, с которым связывается объект RichTextBox, должен быть Binary или Memo. Если используется другой формат, то поле должно быть большой емкости (например, типа Text у базы данных Microsoft SQL Server).

По умолчанию RichTextBox недоступен на панели инструментов Visual Basic. Добавить его в проект можно выполнив команду меню Project->Components и отметив контрольный индикатор Microsoft Rich TexBox Control в окне объектов управления (Controls). После этого но панели инструментов появится его пиктограмма [image].

Создадим с помощью приложения VisData базу данных RICH.MDB, единственная таблица TEXTS которой содержит два поля Id (типа Integer) и Text1 (типа Memo).

В демонстрационном приложении предусмотрим возможность форматирование текста изменением следующих нескольких свойств объекта RichTextBox: SelBold, SelItalic, SelUnderline и SelStrikeThru. Возможные установки этих свойств приведены в таблице.

Таблица. Некоторые свойства улучшенного текстового окна.

Свойство | Описание
SelBold | Если установлено True, то выделенный текст отображается полужирным стилем.
SelItalic | Если установлено True, то выделенный текст отображается курсивом.
SelUnderline | Если установлено True, то выделенный текст подчеркивается.
SelStrikeThru | Если установлено True, то выделенный текст перечеркивается.

Создайте новый проект и расположите на экранной форме объект управления данными, текстовое окно, улучшенное текстовое окно и пять командных кнопок, как показано на рис.9.3. Обычное текстовое окно позволит увидеть, что представляет собой текст в формате RTF.

Рис.9.3. Расположение элементов управления на экранной форме

Определите свойства объекта управления данными DataBaseName и RecordSource как “C:\WORK\RICH.MDB” и “Texts”, соответственно. Свойства остальных объектов, расположенных на форме установите следующим образом:

Command1.Caption=Полужирный
Command2.Caption=Курсив
Command3.Caption=Подчеркнутый
Command4.Caption=Перечеркнутый
Command5.Caption=Добавить
Text1.DataSource=Data1
Text1.DataField=Text1
RichTextBox1.DataSource=Data1
RichTextBox1.DataField=Text1

В процедуры обработки событий нажатия командных кнопок добавьте следующий код:

Private Sub Command1_Click()
If RichTextBox1.SelBold = Fulse Then
    RichTextBox1.SelBold = True
Else
    RichTextBox1.SelBold = Fulse
End If
End Sub

Private Sub Command2_Click()
If RichTextBox1.SelItalic = Fulse Then
    RichTextBox1.SelItalic = True
Else
    RichTextBox1.SelItalic = Fulse
End If
End Sub

Private Sub Command3_Click()
If RichTextBox1.SelUnderline = Fulse Then
    RichTextBox1.SelUnderline = True
Else
    RichTextBox1.SelUnderline = Fulse
End If
End Sub

Private Sub Command4_Click()
If RichTextBox1.SelStrikeThru = Fulse Then
    RichTextBox1.SelStrikeThru = True
Else
    RichTextBox1.SelStrikeThru = Fulse
End If
End Sub

Private Sub Command5_Click()
    Data1.Recordset.AddNew
End Sub

Сохраните и запустите проект. Добавьте произвольную текстовую строку в базу данных и проведите операции форматирования выделяемых участков текста с помощью командных кнопок.

Результат работы этого приложения показан на рис.9.4. Обратите внимание на то, как в обычном текстовом окне отображается содержимое объекта RichTexBox.

Рис.9.4. Результат работы приложения


fsn.net: Урок 10

Применение Data Form Wizard для быстрого создания экранных форм

Для быстрого создания приложений, предназначенных для работы с базами данных, может быть использован Мастер форм данных (Data Form Wizard). Эта программа - Мастер входит в комплект поставки Visual Basic и доступна из меню Add-Ins (Дополнения). Однако, по умолчанию, запуск этого приложения недоступен и соответствующую команду следует сначала добавить в меню при помощи Менеджера дополнений (Add-In Manager). Выполните команду меню Add-Ins->Add-In Manager, отметьте контрольный индикатор VB Data Form Wizard в диалоговом окне Менеджера дополнений и подтвердите сделанный выбор нажатием кнопки OK. Теперь запуск этой программы доступен из меню Visual Basic.

С помощью Мастера форм данных вы в интерактивном режиме легко создадите экранную форму приложения, откроете требуемую базу данных и определите источники данных, расположите на форме объект управления данными и связанные с ним элементы управления Visual Basic. Причем, тип связанных элементов управления будет соответствовать данным, содержащимся в соответствующем поле базы данных. Если потребуется, вы легко добавите на форму командные кнопки для выполнения таких типичных действий пользователя, как добавление или удаление записей, обновление данных и завершение работы приложения.

После запуска в первом диалоговом окне (если не принимать во внимание окна-заставки - Introduction) Мастер потребует указать тип базы данных, которую вы предполагаете использовать в приложении (рис.10.1). Выбор типа БД производится из списка поддерживаемых Мастером форматов. Это могут быть базы данных в формате Access, dBase, FoxPro или Paradox различных версий. Поддерживаются также удаленные (Remote) базы данных ODBC. Выделите в списке требуемый тип, например, Access и нажмите кнопку Next (Дальше).

Рис.10.1. Окно выбора типа БД

В следующем окне необходимо указать имя файла базы данных и определить источник данных. Имя файла можно ввести при помощи клавиатуры непосредственно в текстовом окне DataBase Name (Имя базы данных) или, нажав кнопку Browse (Просмотреть), выбрать файл в диалоговом окне Open DataBase (Открыть базу данных). В качестве источников данных могут использоваться таблицы или запросы, доступные из БД. Выбор источников данных проводится с помощью одноименных контрольных индикаторов (рис.10.2). Сделав выбор, нажмите кнопку Next.

Рис.10.2. Окно выбора базы данных

Следующее окно Мастера - окно формы (Form). Здесь вы можете определить вид экранной формы приложения нажатием одной из трех доступных кнопок-переключателей (рис.10.3):

Single record - на форме будут размещены данные только из одной записи,

Grid (Datasheet) - данные на форме будут размещены в виде таблицы,

Master/Detail - при таком выборе возможен просмотр данных из двух связанных таблиц .

В зависимости от сделанного выбора, будущий внешний вид вашей экранной формы будет показан в левом верхнем углу диалоговой панели Form.

После выбора типа формы нажмите кнопку Next.

Рис.10.3. Окно формы

В следующем окне Record Source (Источник записей) (рис.10.4) требуется выбрать источник записей из комбинированного списка Record Source, в который включены все источники записей, доступные для выбранной базы данных. В списке Available Fields (Доступные поля) выберите те поля, содержимое которых вы собираетесь отображать на экранной форме. Выбор производится двойным щелчком мыши на требуемом поле или нажатием кнопки с изображением правой стрелки. После этого название выбранного поля переместится из списка доступных полей в список Selected Fields (Выбранные поля). Если вы хотите просматривать содержимое всех доступных полей, нажмите кнопку с двойной правой стрелкой. Кнопки с левыми стрелками позволяют удалить одно или все поля из списка выбранных полей.

Кнопки с вертикальными стрелками позволяют расположить выбранные поля в требуемом вам порядке.

В этом диалоговом окне вы также можете задать поле, которое будет использоваться для сортировки данных. Список доступных полей содержится в комбинированном списке Column to sort by (Колонка сортировки).

Определив источник записей, нажмите кнопку Next.

Рис.10.4. Диалоговое окно определения источника записей

Разместить на экранной форме дополнительные элементы управления Visual Basic такие, как командные кнопки и объект управления данными вы можете в следующем диалоге - Control Selection (Выбор элементов управления) (рис.10.5). Из списка Available Controls (Доступные элементы управления) выберите необходимые элементы и нажмите кнопку Next.

Рис.10.5. Добавление элементов управления на экранную форму

В последней диалоговой панели Мастера вы можете изменить установленное по умолчанию имя вашей экранной формы.

Нажмите кнопку Finish (Завершить) и получите готовую экранную форму вашего приложения (рис.10.6). В дальнейшем можете вносить, по мере необходимости, любые изменения в расстановку элементов управления на этой экранной форме.

Рис.10.6. Экранная форма, созданная Мастером.


Lesson VB DB programming

Уроки программирования баз данных в Visual Basic
Урок 1. Введение в базы данных.

Урок 2. Приложение VisData.

Урок 3. Введение в объект управления данными - Data Control.

Урок 4. Свойства, методы и события объекта управления данными.

Урок 5. Элемент управления Data и объекты доступа к данным.

Урок 6. Связанные с данными элементы управления.

Урок 7. Метка (Label), текстовое окно (Text box), окно редактирования с маской (MaskedEdit), контрольный индикатор (Check box), Изображение (Image) и графическое окно (Picture box).

Урок 8. Окно списка (List box), комбинированное окно (Combo box), Контейнер OLE (OLE container), связанное окно списка (Data-bound list box) и связанное комбинированое окно (Data-bound combo box).

Урок 9. Сетка (MSFlexGrid) и улучшенное текстовое окно (RichTextBox)


Введение.

  • Почему Windows и почему Visual Basic?
  • Что необходимо для работы с Visual Basic?
  • Установка Visual Basic
  • Запуск программы установки
  • Запуск Visual Basic
  • Работа с Visual Basic
  • Простое приложение
  • Использование Application Wizard
  • Работа с готовым проектом Visual Basic
  • Последовательность действий при разработке
  • Дополнительная информация

Данная глава представляет собой обзор Microsoft Visual Basic версии 5 (Далее мы будем называть ее Visual Basic или просто VB, чтобы отличить текущую версию от более ранних, будем использовать сокращения VB4 или VB3). В настоящее время доступны 4 версии данного языка, значительно отличающиеся по возможностям.

Почему Windows и почему Visual Basic?

Графические интерфейсы пользователя, или GUIs, революционизировали микрокомпьютерную индустрию. Они продемонстрировали, что выражение "Лучше один раз увидеть, чем сто раз услышать не потеряло своего смысла для большинства пользователей компьютеров. Вместо загадочной командной строки. С:>, которую как долго наблюдали пользователи DOS, теперь они смотрят на "рабочий стол" (desktop), заполненный значками программ, управляя ими при помощи мыши или посредством меню.
Вероятно, при длительном использовании Windows то, как выглядят приложения, менее важно, чем-то, как они созданы. В Windows, как правило, программы имеют стандартный интерфейс пользователя. Это означает, что пользователи потратят больше времени на работу с приложением и меньше на изучение меню, диалоговых панелей и комбинации клавиш (Конечно, приложения Windows 95 и Windows NT 4.0 выглядят немного не так, как приложения Windows 3.1: стандарты сохраняются внутри версий Windows, а не между ними).
В то время как программисты испытывают смешанные чувства к GUIs, он нравится начинающим пользователям, поэтому от любых программ Windows ожидается соответствие модели GUI. Поэтому, если необходимо разработать программу для любой из версий Windows, программисту понадобится эффективное средство для разработки приложений с графическим интерфейсом пользователя.
За все это долгое время существовало лишь несколько таких средств разработки. До появления Visual Basic в 1991 году разработка приложений Windows была намного тяжелее процесса создания приложений для DOS. Программисты должны были позаботиться буквально обо всем, например, о работе с мышью, обработке событий меню, и даже отслеживать, щелкнул пользователь один либо два раза в конкретном месте экрана. Разработка приложений Windows требовала экспертных знаний по программированию на языке С и сотен строк кода для выполнения простейшие задач. Проблемы были даже у профессионалов. (Комплект Microsoft Windows Software Development Kit, необходимый в то время в дополнение к компилятору С, весил тогда около пяти килограммов).
Вот почему, когда появился Visual Basic 1. 0, Билл Гейтс, глава и владелец компании Microsoft, назвал ею "внушительным". Стив Гибсон из Infoworld описал данный язык как "новое великолепное чудо", которое сильно изменит взгляд людей на использование Microsoft Windows". Стюарт Олшоп, давая интервью газете "New York Times", назвал Visual Basic "идеальной средой разработки программ 90-х годов". Чарльз Петцольд, автор одной из классических книг по программированию на С для Windows, писал в "New York Times": "Для тех из нас, кто всю свою жизнь посвятил обучению сложностям программирования Windows, Visual Basic представляет реальную угрозу потерять средства к существованию". Последняя версия Visual Basic продолжает данную традицию: сложные приложения Windows 95 и Windows NT разрабатываются теперь за гораздо меньший период времени. Ошибки в программах (bugs) возникают не так часто, и их локализация происходит быстрее и легче. С Visual Basic программирование в Windows не становится более эффективным, но оно становится более простым (в большинстве случаев).

Примечание: Последняя версия Visual Basic предназначена для Windows 95 и Windows NT; ее нельзя использовать при разработке приложений для Windows 3.1.

В частности. Visual Basic позволяет добавлять к окнам поля ввода (text boxes), меню, командные кнопки (command buttons), переключатели (option buttons), флажки (check boxes), списки (list boxes), линейки прокрутки (scroll bars), a также диалоговые окна для выбора файла или каталога. Программист может использовать сетку (grid) для обработки табличных данных, организовать взаимодействие с другими приложениями Windows и доступ к базам данных. (Кстати, в Visual Basic такие компоненты обычно называют элементами управления.)
На экране одновременно может находиться несколько окон. Они должны иметь полный доступ к буферу обмена (clipboard) и к информации большинства приложений Windows, работающих в то же время. Можно использовать Visual Basic для взаимодействия с другими приложениями, работающими под управлением Windows, используя наиболее современную версию технологии OLE фирмы Microsoft.
Однако все более ранние версии Visual Basic могли в той или иной степени выполнять вышеописанные задачи. Что же особенного добавлено к последней версии Visual Basic? В двух словах ответ состоит в том, что Visual Basic теперь поддерживает элементы ActiveX.

Примечание: Если читатели еще не знакомы с этим последним писком моды, то вот его краткое описание -ActiveX представляет собой технологию Microsoft для активизации работы с Интернет и корпоративными интернет-сетями, причем данная технология может использоваться в обычных приложениях Windows для увеличения продуктивности работы пользователей. Идея состоит в том, что появляется возможность загрузить из Интернет небольшой компонент. Эта программа увеличивает функциональность вашего броузера или операционной системы. (Кстати, обычным термином для такого небольшого компонента является наименование "элемент управления ActiveX". Существуют специальные версии элементов управления, о которых мы только что говорили.)

Приведем пример того, как пользователь может загрузить компоненты ActiveX для добавления полезных функций к странице Web. Предположим, что на ней размещена движущаяся картинка и он хочет загрузить ее к себе на компьютер и просмотреть. У Microsoft есть компонент ActiveX Movie, загрузить его можно из ActiveX Gallery no адресу www.microsoft.com/activex/gallery, а затем использовать. Более того, добавив его один раз к своей системе, пользователю не придется загружать его еще раз. Каждый раз, когда страница Web будет содержать объект "active movie", броузер Internet Explorer (а вскоре и Netscape Navigator) сможет использовать один и тот же элемент Active Movie уже из операционной системы пользователя для просмотра видеоматериала.

Примечание: Даже фирма Netscape объявила о поддержке ActiveX в следующих версиях Navigator. Дополнительный модуль (plug-in) к Netscape Navigator 3 для Windows 95, предназначенный для поддержки ActiveX, уже доступен от лаборатории NCompass (www.ncompasslabs.com).

Большинство наиболее интересных элементов управления в Visual Basic являются элементами ActiveX, поэтому их можно использовать без изменений в броузере с поддержкой ActiveX. Два примера - это сетка (grid control) и элемент управления, использующий одну из стандартных диалоговых панелей для обработки файлов. Даже элементы ActiveX, наиболее мощные из всех, занимают менее чем 200 килобайт каждый. Более специализированные (но тем не менее очень мощные) элементы ActiveX, укладываются в 20 килобайт. На одну дискету 3,5 легко можно уместить 50 наиболее мощных элементов ActiveX.

Примечание: Некоторые из наиболее общих элементов управления, такие как поля ввода и командные кнопки Visual Basic, фактически не являются стандартными элементами ActiveX; их обычно называют внутренними элементами управления (intrinsic controls). Это происходит из-за того, что они автоматически становятся частью программы на Visual Basic и не могут находиться вне ее, в отличие от элементов ActiveX.

Смысл всего вышесказанного состоит в том, что если выбрать одну из многих новых возможностей Visual Basic 5 с целью объяснить, в чем ее сильное отличие от ранних версий, то можно сказать так: "Можно строить свои собственные элементы управления ActiveX полностью на базе Visual Basic". Впервые программисты на Visual Basic могут расширять данный язык, используя только его возможности. Теперь они могут создавать свои элементы ActiveX, тем самым используя полностью всю мощь и простоту Visual Basic, которые всегда являлись отличительными чертами данного языка.
На этом этапе может возникнуть вопрос о производительности: работают ли приложения Visual Basic 5 медленнее по сравнению с более ранними версиями языка? Ответом будет сенсационное "Нет!". Фактически профессиональные версии Visual Basic 5 впервые могут генерировать исполняемый код (т.е. создавать исполняемые ЕХЕ-модули). Предыдущие версии Visual Basic бьши интерпретаторами. Если не вдаваться в отличия между компилятором и интерпретатором VB, то результатом является исполнение программ Visual Basic 5 со скоростью в 10-20 раз быстрее, чем это было в предыдущих версиях.

Что необходимо для работы с Visual Basic?

Все версии Visual Basic занимают много места на жестком диске. Даже самая небольшая (и наименее мощная) бесплатная версия Control Creation edition требует около 10 Мбайт свободного места на диске, процессор 486 или Pentium, а также не менее 16 Мбайт оперативной памяти. Полная инсталляция самой мощной версии Visual Basic Enterprise edition требует более 100 Мбайт дискового пространства.

Примечание: Коммерческие (не бесплатные) версии Visual Basic поступают с полной документацией, поставляемой обычно на бумаге, но кроме этого на компакт-диске присутствует электронная версия документации, называемая "Books Online". Ее можно установить на жесткий диск для увеличения скорости работы. Это потребует около 20 Мбайт дискового пространства.

Совет: Не устанавливая определенные компоненты, можно уменьшить размер необходимого для работы дискового пространства. Но нельзя уменьшить требования ни к скорости процессора, ни к количеству оперативной памяти. Если количество оперативной памяти не соответствует требованиям или используются недостаточно быстрые микропроцессоры, то запустить Visual Basic все равно можно, но это будет очень неприятный эксперимент.

Установка Visual Basic

Visual Basic обычно поставляется на CD. Бесплатную версию Control Creation edition можно загрузить из Интернет (это около 8 Мбайт). Если Вы приобрели Visual Basic 5, рекомендуем заполнить и выслать регистрационную карточку. Несомненно, что по электронной почте придет определенное количество ненужных писем, но зато так проще получить техническую поддержку и извещения об обновленных версиях от Microsoft.

Запуск программы установки

Компакт-диск или дискеты с Visual Basic содержат программу установки Setup. Версия Control Creation edition представляет собой самораспаковывающийся ЕХЕ-файл. Все версии требуют наличия на компьютере Microsoft Windows 95 или Windows NT 3.51 (или их более новые версии).
Запустить программу Setup можно столько раз, сколько необходимо, что означает отсутствие ограничений на выбор различных вариантов установки (неплохая идея для начинающих пользователей - применять установки по умолчанию). При первой установке программа Setup запрашивает имя пользователя и наименование компании. Программа сохраняет данную информацию и выводит ее каждый раз при запуске среды разработки Visual Basic.
Мы полагаем, что Вы за свою жизнь инсталлировали достаточно программ Windows. Если используется одна из коммерческих версий Visual Basic, нужно перейти к программе Setup.ехе на компакт-диске или на первой дискете и просто запустить ее на выполнение. Если устанавливается версия Control creation Edition, нужно дважды щелкнуть по названию файла VB5Ccein.exe из Windows Explorer. Здесь нодо следовать инструкциям на экране.
После завершения процедуры инсталляции Visual Basic будет установлен со своей собственной группой программ. Для перемещения Visual Basic из одной группы программ в другую либо на рабочий стол можно использовать Windows Explorer или технологию drag-and-drop. Для этого требуется изучить документацию по Windows или прочитать книгу типа "Windows 95 Made Easy" Тома Шелдона.
Если имеются какие-либо дополнения или исправления к документации, их можно обнаружить в файле типа Readme. txt. Если данный файл есть на дистрибутивных дискетах или CD, программа установки автоматически копирует его на винчестер и размещает значок на рабочем столе Windows в группе программ Visual Basic. Теперь, если дважды щелкнуть по этому значку, Microsoft Windows загружает данный файл в редакторе WordPad. Это помогает легко изучить его содержимое.

Примечание: Для Visual Basic не требуется явной установки принтера, так как он использует информацию о текущем принтере в Windows.

Запуск Visual Basic

Самый простой способ запустить Visual Basic в Windows 95 или Windows NT 4.0 состоит в использовании пункта Программы главного меню Пуск. (Программа установки автоматически добавляет Visual Basic в главное меню Пуск.) На рис. 1-1 приведен примерный вид того, что будет видно на экране. Кроме того, запустить Visual Basic можно, перейдя в его каталог при помощи значка "Мой компьютер на рабочем столе Windows". Затем необходимо дважды щелкнуть по значку Visual Basic. Запустить Visual Basic можно, открыв диалоговую панель "Выполнить" из главного меню и введя путь к Visual Basic. (В Windows 95 или в Windows NT 4.0 существует множество различных вариантов запуска программ.)
Когда Visual Basic начинает загружаться, пользователь видит экран с информацией об авторских правах, где указывается, кем лицензирована данная копия программы. После этого на экране появится картина, примерно соответствующая рис. 1 -2. (Точный вид экрана зависит от версии Visual Basic.) Диалоговая панель New Project позволяет начать работу по созданию приложения Visual Basic любого типа. В данной главе рассматривается только один тип приложения - Standard ЕХЕ - сокращение от наименования "обычная автономная программа".

Работа с Visual Basic

В данном разделе будет описано создание двух приложений. Первое мы создадим буквально вручную, оно представляет собой обычное окно с кнопками Maximize, Minimize и Exit. Для второго приложения будет использован Application Wizard (мастер приложений), поставляемый с Visual Basic, применяемый при построении интерфейсов к программам. Такой тип приложений носит название MDI (multiple document interface). Как и в обычной среде разработки, поставляемой с Visual Basic, данные приложения позволяют добавлять окна внутри родительского окна.

Рисунок 1-1.Типичный список программ, выводимый по кнопке Пуск в Windows 95.

Кроме того, в приложениях будет присутствовать расширяемое меню, содержащее ряд стандартных пунктов: File, Help и так далее. Application Wizard генерирует большое количество полезного кода автоматически. Поэтому требуется добавить только небольшое количество дополнительного кода, чтобы полностью "оживить" все автоматически созданные возможности. (Конечно, написание такого кода потребует дополнительных знаний о программировании в Visual Basic. Application Wizard представляет собой мощное, но всего лишь вспомогательное средство программирования; он не может полностью заменить программиста.)

Простое приложение

Как отмечалось ранее, при запуске Visual Basic его первый экран будет выглядеть так, как на рис. 1-2. В диалоговой панели New Project, показанной на рис. 1 -2, следует дважды щелкнуть на значку Standard EXE (она первая на рисунке).
Программа тут же перейдет в режим, показанный на рис. 1 -3. Отметим, что пустое окно носит имя Projecti-Form1 (Form). Внутри него находится другое окно с названием Form1, в котором видна сетка из точек. Это и есть форма, которую можно модифицировать. Сетка используется для выравнивания элементов управления на экране, таких, как командные кнопки и списки.
При запуске проекта (или его компиляции с целью независимого запуска от среды разработки Visual Basic), формы, наподобие Form1, становятся окнами, которые и видит пользователь. Вверху пустой формы размещается строка заголовка (title bar) с заголовком формы (caption). Заголовком в Visual Basic называется текст в строке заголовка конкретного окна. В настоящий момент форма озаглавлена по умолчанию как Form1; такой заголовок Visual Basic присваивает форме в начале работы над новым проектом. Слева от окна Form1 на рис. 1-3 размещается окно панели инструментов (toolbox), в котором видны элементы управления, доступные к размещению на форме. (Как было отмечено до этого, элемент управления представляет собой термин, используемый в Visual Basic для объектов, размещаемых на форме при ее создании.) Справа от окна формы размещаются еще три окна. Верхнее носит название Project Explorer. Непосредственно под ним находится окно Properties, где устанавливаются различные свойства формы и элементов управления на ней. Справа внизу размещается окно Form Layout, рассказать о функциях которого можно за несколько секунд.

Рисунок 1-2. Первый экран при запуске Visual Basic.

Теперь сконцентрируем внимание на центральном окне с названием Project1 -Form 1 (Form) и дополнительном окне внутри его с наименованием Form1. В большинстве приложений Visual Basic размер и местоположение формы в момент окончания ее разработки (обычно это называют временем создания) определяют размеры того, что пользователь видит на экране в момент работы программы. Это не означает того, что в Visual Basic нельзя изменить размер или местоположение форм при запуске проекта; фактически одним из основных достоинств Visual Basic является возможность производить динамические изменения в ответ на события, генерируемые как реакция на действия пользователя.
Изменим размер окна Project1 с тем, чтобы оставить побольше места для работы с формой внутри его. Для этого необходимо просто щелкнуть по кнопке Maximize. Результат показан на рис. 1 -4.
Сейчас стало больше места для работы с окном Form1, попробуем изменить его размер. Опишем один из способов изменения размеров формы, который является общим для всех приложений Windows. Сперва необходимо щелкнуть мышью внутри формы, чтобы она стала активной. (Всегда можно определить, является ли окно активным, поскольку его заголовок выделяется цветом.) Затем сдвинем курсор мыши к любой части рамки окна формы. При этом он должен изменить свой вид на двунаправленную стрелку. С этого момента размеры формы можно изменять, двигая курсор по экрану.
Для того чтобы начать разработку первого тестового приложения, необходимо сделать следующие шаги:

      1. Изменить размер формы по умолчанию, пользуясь технологией, описанной выше.
      2. Запустить проект, нажав F 5 либо выбрав пункт Start из меню Run (ALT+R,S).
Отметим, что на экране появляется обычное окно Windows, размеры которого совпадают с размерами окна в режиме разработки. Кроме того, данное окно имеет все стандартные черты окон Windows, такие, как границы с изменением размера, оконное меню управления (в левом верхнем углу), а также кнопки Maximize, Minimize и Exit в правом верхнем углу. Данный факт иллюстрирует одну из основных особенностей Visual Basic: созданная программистом форма становится окном, по умолчанию получающим все стандартные характеристики окна текущей версии Windows, причем для этого не нужно писать ни одной строчки кода.
Рисунок 1-3. Первоначальное состояние среды разработки Visual Basic.

Вернемся в среду разработки, нажав ALT+F4 либо дважды щелкнув по оконному меню управления в форме Form1, или щелкнув по кнопке Exit в форме Form1. Отметим, что текущее приложение реагирует на все эти стандартные способы завершения работы с программой Windows. Это демонстрирует такой важный момент, что в большинстве случаев приложения Visual Basic ведут себя так, как ожидает пользователь Windows, не требуя при этом специального вмешательства программиста (или пользователя).
Далее попробуем изменить месторасположение формы, видимое пользователю изначально. Для этого передвинем окно Form Layout в правый нижний угол экрана.
Попробуем просто перетащить форму внутри окна на новое место. Затем снова запустим приложение. Можно увидеть, что окно формы теперь размещается там, куда его перетащили внутри окна Form Layout.

Примечание: Размер, форма и месторасположение окна являются примерами свойств (properties) данной формы. Как можно наблюдать, они установлены путем непосредственных манипуляций с ней. Кроме того, их значения можно установить при помощи окна Properties.

Использование Application Wizard

Следующее, на чем Мы хотели бы остановиться, — использование мощного VB Application Wizard ("Мастер приложений"), входящего во все редакции Visual Basic, за исключением Control Creation edition. Чтобы запустить его, необходимо выбрать в меню File|New Project и далее, как показано на рис. 1-2, выбрать VB Application Wizard. Если это будет проделаете, то после небольшой задержки на экране будет видна картинка, похожая на рис. 1-5.
Для продолжения работы с Application Wizard необходимо щелкнуть по кнопке Next. Следующий экран показан на рис. 1-6. Здесь программист может выбрать, как будет "выглядеть" новое приложение. Отметим, что возможные варианты на экране отображены как словами, так и в картинках.

Рисунок 1-4. Максимально увеличеное окно Projtct1.

Чтобы посмотреть, как это все работает, нужно щелкнуть по кнопке Explorer Style. Результат показан на рис. 1-7. Рисунок демонстрирует, что в данном случае выбранный стиль представляет собой древовидную структуру, напоминающую Windows Explorer (но не Internet Explorer).

Рисунок 1-5. Начальный вид окна Application Wizard.

Сейчас, тем не менее, будет использоваться значение по умолчанию при выборе типа интерфейса - multiple document interface (MDI). Убедившись, что выбор сделан, как показано на рис. 1 -6, можно щелкнуть по кнопке Next. На экране появится окно для выбора типа меню, как показано на рис. 1 -8.

Рисунок 1-6. Окно Interfaces Type приложения Application Wizard.

Снова примем значения по умолчанию и щелкнем по Next. После этого мы попадаем в окно, где выбираются параметры файла ресурсов. Сейчас данный вопрос не будет обсуждаться (для любознательных сообщим, что этот файл позволяет легко изменять наименования заголовков и пунктов меню при изменении международных установок, например, названия страны). Для перехода к рис. 1-9 снова нужно щелкнуть по кнопке Next.

Рисунок 1-7. Экран с выбранным типом интерфейса Explorer.

Информация на новом экране станет для читателя более понятной, когда он изучит взаимодействие Visual Basic с Интернет. Сейчас же просто щелкнем по кнопке Next. В двух следующих экранах можно добавить некоторые дополнительные формы к приложению, например, окно заставки или панель "О программе..." (About). Впрочем, в данный момент не будем разбираться с этим. Опять щелкнем по кнопке Next. Появится окно, где можно подключить текущее приложение к базе данных. Просто выберем кнопку Next.
Вот мы и добрались до последнего экрана, как показано на рис. 1-10. Щелкнем по кнопке Finished и "мастер" сгенерирует код для нового приложения. (Кроме этого, он спросит о необходимости вывода итоговой формы. В ней будет описано кое-что из того, что предстоит еще сделать программисту. Пример итогового отчета можно увидеть на рис. 1-11.)

Работа с готовым проектом Visual Basic

Следующим шагом в освоении Visual Basic станет запуск одного из примеров программ, поставляемых вмете с ним. (Все версии Visual Basic, за исключением Control Creation edition, поставляются с сотнями примеров программ.

Рисунок 1-8. Экран с меню построителя приложения.

Когда читатели поближе познакомятся с Visual Basic, они поймут, что данные примеры представляют собой хороший источник информации и полезных идей.) Тот пример, о котором пойдет речь сейчас, демонстрирует богатство выбора элементов управления, поставляемых вместе с Visual Basic.
Ниже приведены основные шаги, необходимые для загрузки существующего проекта. (Напомним, что проект представляет собой набор файлов, компонуемых Visual Basic при создании приложения, т.е. это комбинация визуальных компонентов и исходного кода.)

    1. Выбрать из меню пункт File, нажав АLТ+F, либо щелкнув мышью по пункту File в линейке меню.
    2. Выбрать пункт Open Project (Открыть проект).
После этого откроется диалоговая панель, как показано слева. (Если пользователь уже работал с Visual Basic, то сперва на экране появится диалоговая панель с предложением сохранить существующий проект.)
Необходимый проект находится в подкаталоге внутри каталога с примерами программ (там хранятся все примеры, поставляемые с Visual Basic). Поэтому сперва нужно перейти в каталог с примерами программ, а затем в каталог с наименованием PGuide. Далее необходимо перейти в каталог Controls и там дважды щелкнуть по файлу с наименованием Controls. vbp.
Рисунок 1-9. Окно "Связь с Интернет" в программе Application Wizard.

После небольшой паузы проект с примером об элементах управления будет загружен в среду Visual Basic. Для его запуска необходимо нажать F5. (Если появляется диалоговая панель с вопросом о необходимости сохранить предыдущий проект, нужно ответить "No".) Теперь экран будет выглядеть примерно так, как показано на рис. 1-12. После того как читатель закончит развлекаться с различными примерами, демонстрирующими мощность Visual Basic при работе с элементами управления, он должен щелкнуть по кнопке Exit либо нажать ALT+F4 для возврата в среду разработки.

Последовательность действий при разработке приложений Visual Basic (краткий обзор)

Автор хотел бы завершить данную главу кратким обзором на тему о том, как разрабатываются приложения Visual Basic. Некоторые шаги могут сперва показаться читателям непонятными, но очень скоро все прояснится.

Рисунок 1-10. Последний экран в Application Wizard.

Первым шагом при построении приложения Visual Basic является планирование того, что пользователь видит на экране, другими словами, разработка интерфейса. Какие меню понадобятся? Окно какого размера требуется для приложения? Сколько всего будет окон? Должен ли пользователь иметь возможность изменять размеры окон? Где разместить кнопки для управления приложением? Будут ли в приложении размещены поля для ввода текста? Какой тип элемента управления подойдет для решения данной задачи? Подходят ли элементы управления из данной версии Visual Basic или их необходимо приобретать или разрабатывать самим?
Количество элементов управления, находящихся в распоряжении программиста, зависит от редакции Visual Basic, а также от состояния его кошелька или количества времени, потраченного на их разработку. В стандартную редакцию входит более 20 элементов управления, в версии Professional и Enterprise - более 50. Более того, поскольку Visual Basic подталкивает независимых разработчиков к созданию большого количества элементов управления для особых задач, почти всегда можно найти готовый элемент для решения любой проблемы. (Microsoft оценивает количество только коммерческих элементов управления более чем в 2000 единиц, не считая буквально бесчисленного множества бесплатных и условно-бесплатных.) В заключение нельзя не отметить следующее: пользоваться новыми возможностями VB5 можно, применяя либо готовые элементы управления, либо разрабатывая их с самого начала (последнее, при всем уважении к читателю, не совсем подходящий способ).
Хотя мы еще многое не попробовали, но автор хочет повторить еще раз, что в конечном счете Visual Basic отличается от остальных средств программирования простотой разработки интерфейса пользователя и его связи с кодом программы. В действительности можно просто нарисовать этот интерфейс; такой подход очень напоминает графический редактор. После того как разработка интерфейса завершена, кнопки управления, поля и другие элементы, размещенные на пустой форме, автоматически будут распознавать действия пользователя, такие, например, как движение мыши или щелчок ее кнопки. Visual Basic включает в себя мощные средства разработки меню, что делает создание обычных и всплывающих меню простым делом.
Только после того как подготовлен интерфейс программы, начинается процесс, похожий на традиционное программирование. Это второй шаг при построении приложений Visual Basic: создание программного кода для активизации визуального интерфейса, подготовленного на первом этапе. Смысл состоит в том, что объекты Visual Basic распознают события типа щелчков мыши; то, как они их обрабатывают, зависит от программного кода, написанного программистом.

Рисунок 1-11. Экран с итоговым отчетом для Application Wizard.
Рисунок 1-12. Демонстрационная версия PControl.

Пользователю почти всегда придется писать собственный код для обработки событий. В этом и состоит фундаментальное отличие программирования на Visual Basic от обычного программирования.
Обычно программы выполняются от начала до конца. В более старых языках программирования выполнение приложений начиналось с первой строчки программы и протекало через различные ее части согласно алгоритму. Приложение на Visual Basic работает совершенно по-другому. Ядром такой программы является набор независимых частей кода, активизирующихся в ответ на события, происходящие вокруг. Это является принципиальным моментом. Теперь вместо создания программы, отражающей мысли программиста, получается приложение, управляемое пользователем.
Большинство всего кода программы, отвечающего за обработку таких событий, как щелчки мыши, размещается в процедурах обработки событий (event procedures). Они представляют собой не что иное, как программный код, сообщающий Visual Basic, как реагировать на текущее событие. По существу, весь исполняемый код в программах на Visual Basic представляет собой либо процедуру обработки событий, либо используется данной процедурой.
К сожалению, третий и четвертый шаги для построения приложения Visual Basic заключаются в поисках ошибок в программе и их исправлении (на жаргоне программистов это называется отладкой (debugging)).
Ниже приведен перечень шагов, необходимых для создания приложения Visual Basic:
    1. Подготовить интерфейс окна, с которым будет работать пользователь.
    2. Определить, какие события будут обрабатывать элементы управления внутри окна.
    3. Написать процедуру обработки для этих событий (и вспомогательные процедуры, если это необходимо).
Далее рассмотрим, что происходит при запуске приложения:
    1. Visual Basic отслеживает все окна и элементы управления для каждого окна на предмет определения всех событий, относящихся к ним (движения мыши, щелчки, комбинации клавиш и т.п.).
    2. Когда Visual Basic определяет событие и не находит для него встроенной процедуры обработки, он пытается найти процедуру, написанную программистом для обработки данного события.
    3. Если такая процедура существует, Visual Basic выполняет ее и переходит опять к пункту 1.
    4. Если процедуры не существует, Visual Basic ожидает следующее событие и возвращается к пункту 1.
Данные шаги повторяются циклически до тех пор, пока приложение не завершит работу. Обычно событие должно произойти перед тем, как Visual Basic что-нибудь выполнит. Такая событийно-ориентированная программа является более реагирующей, чем действующей, что делает ее более дружественной по отношению к пользователю.
И последнее: поскольку язык программирования Visual Basic основан на современной структурированной версии BASIC, с его помощью легко создавать большие программы, используя современные модульные и объектно-ориентированные технологии (все это не похоже на старый, всеми осуждаемый BASIC). Кроме того, Visual Basic обеспечивает превосходную обработку ошибок для всех наиболее значительных событий. Компилятор языка работает быстро и даже позволяет компилировать программы в фоновом режиме, необходим только код для запуска приложения. Это означает, что любые изменения, необходимые при коррекции ошибок программирования и набора текста программ, которые являются общими для всех начинающих программистов, несложно быстро внести в программу. Добавим, что VB имеет расширенную электронную систему помощи для быстрого поиска информации при разработке приложений.

Дополнительная информация

Обычно пользователь выбирает кнопку, щелкая по ней мышью. Однако иногда может
понадобиться большая гибкость. Один из методов активизации кнопки является общим для всех
приложений Windows: переместить фокус на кнопку, нажимая TAB, а затем нажать SPACEBAR. Оба
этих метода генерируют событие Click. Другими словами, Visual Basic вызывает процедуру
обработки событий Click в любом из этих случаев. Событие Click можно сгенерировать, используя код. (Пользователю известно, что кнопка, получающая фокус, становится трехмерной. Это достигается рисованием тонкого пунктирного прямоугольника вокруг текста на кнопке, и тонкого прямоугольника вокруг самой кнопки.)
Иногда требуется организовать взаимодействие с пользователем по клавише ESC. Обычно
данная клавиша отменяет операцию либо просто выводит пользователя из запутанной ситуации.
При этом одна из командных кнопок на форме активизируется клавишей ESC. В руководстве по
Visual Basic она носит название cancel button.
Обычно привязка командной кнопки к клавише ESC осуществляется через окно Properties, однако
можно использовать и код. Если просмотреть список свойств для командной кнопки, обнаружится
свойство Cancel. Если его значение равняется True, то при нажатии ESC будет обработано событие
Click для cancel button, даже если на ней не установлен фокус. Установка значения True в свойстве
Cancel для одной кнопки означает автоматическую установку значения False для остальных кнопок
формы.
Другая возможность - часто затруднительная для новичков - установка командной кнопки по
умолчанию. При этом событие Click для такой кнопки генерируется, когда кто-нибудь нажимает
клавишу ENTER. Здесь могут возникнуть проблемы, так как пользователи склонны нажимать
данную клавишу в самые неожиданные моменты. (Это происходит, поскольку они считают, что,
если фокус установлен на каком-либо объекте, необходимо нажимать не SPACEBAR, а именно
ENTER.) В любом случае, если необходимо использовать данную возможность, следует установить
свойству Default значение True. Кроме того, на форме может быть несколько кнопок по умолчанию.

Совет: Можно скомбинировать кнопки default и cancel в default cancel button. Обычно это применяют, когда необходимо принять какое-то окончательное решение.


Переменные.

  • Типы переменных.
  • Подробнее о переменных.
  • Объявление переменных: оператор Dim для различных типов данных.
  • Необходимиость объявления переменных.
  • Изменение значений по умолчанию для типов.
  • Область видимости переменных.

В Visual Basic переменные хранят информацию (значения). При их использовании Visual Basic резервирует область в памяти компьютера для хранения данной информации. Имена переменных могут достигать 255 символов в длину, начинаются они с буквы, за которой могут следовать другие буквы, цифры или символы подчеркивания. Регистр символов и наименований переменной значения не имеет. Следующая таблица перечисляет некоторые возможные имена переменных и объясняет, подходят ли они по стандарту.

Base1_Ball | Подходит
1Base_Ball | Не подходит, так как начинается не с буквы.
Base.1 | Не подходит, так как используется точка.
Base&1 | Не подходит, так как используется амперсанд.
ThisIsLongButOK | Не подходит, так как длина всего 15 символов.

Все символы в имени переменной являются значимыми, но их регистр не имеет значения. BASE обозначает ту же переменную, что и base. С другой стороны, Base отличается от Base 1, и оба они - от Base_ I. Однако Visual Basic всегда изменяет первую букву переменной на заглавную при ее определении.
Использование осмысленных имен помогает документировать текст программы и облегчает неизбежный процесс ее отладки. Выразительное имя переменной является прекрасным способом для объяснения смысла применения многих инструкций в коде программы.
В качестве имен новых переменных нельзя использовать зарезервированные слова; например. Print не подходит для этого. Однако такие слова могут присутствовать как часть имени переменной, например, Printlt. Visual Basic будет выдавать сообщение об ошибке, если программист попытается использовать зарезервированное слово в качестве названия своей переменной, причем обычно сразу после нажатия клавиши ENTER.
Одно из наиболее общих соглашений об именах переменных состоит в использовании заглавных букв в начале каждого из слов, составляющих данное имя (например, Printlt, а не Printit). Это соглашение носит название "имена переменных со смешанным регистром". Оно используется и в данной книге, поскольку большинство людей находят такие имена переменных более читабельными. Некоторые еще используют и символ подчеркивания (например, Print_It), но здесь данный стиль не используется, поскольку отнимает много места и иногда вызывает проблемы при отладке.

Типы переменных

Visual Basic работает с 14 стандартными типами переменных. Кроме того, программист может определить собственный тип данных. В данном разделе описаны те из них, которые в основном применяются при работе с данными.

String
Строковые переменные хранят символы. Один из способов обозначить данный тип переменной состоит в добавлении символа "$" к концу ее имени, например: AStringVariable$. Теоретически такая переменная может содержать до 2 миллиардов символов. В любом случае на конкретном компьютере это число будет гораздо меньше из-за ограниченных объемов оперативной памяти, ресурсов Windows или числа символов, используемых в форме.
Одно из наиболее основных применений строковых переменных состоит в выборе информации из полей ввода. Например, если есть поле ввода с именем Textl, то тогда оператор ContentOfTextlS = Textl.Text присваивает строку из поля ввода переменной в левой части данного оператора.

Integer
Целочисленные переменные могут хранить только относительно небольшие целые числа (в диапазоне от -32768 до +32767). Арифметические операции над такими числами выполняются очень быстро. Для обозначения такого типа используется символ "%":
AnIntegerVariable% = 3

Long Integer
Данный тип впервые появился в языке QuickBASIC. В таких переменных хранятся целые значения от -2 147483648 до +2 147483647. Обозначается символом "&". Арифметические операции над такими числами выполняются тоже очень быстро, и при работе с процессором 386DX или 486DX существует лишь небольшая разница в скорости вычислений между Long Integer и Integer.

ALongIntegerVariableS = 123456789 Single Precision

Идентификатором для данных чисел является символ "!". Данный тип переменной позволяет хранить дробные числа с точностью до семи цифр. Это означает, что если получается результат 12345678.97, то часть 8.97 не является точной. Результат может быть равен, к примеру, 12345670.01. Длина чисел может достигать 38 знаков. Вычисления с данными переменными тоже будут приблизительными. Более того, арифметические действия выполняются медленнее, чем с целочисленными переменными.

Double Precision
Переменные такого типа позволяют хранить числа с точностью до 16 цифр и длиной до 300 символов. Идентификатором является "#". Вычисления с ними тоже являются приблизительными, а скорость их относительно небольшая. В основном переменные типа Double Precision используются для научных расчетов.

Currency
Данный тип отсутствовал в версиях GW-BASIC и QuickBASIC. Он создан для того, чтобы избежать ошибок при преобразовании десятичных чисел в двоичную форму и наоборот. (Невозможно 1/10 представить как сумму 1/2,1/4, 1/8, 1/16 и т.п.) Данный тип может иметь до 4 цифр после запятой, и до 14 - перед ней. Внутри данного диапазона вычисления будут точными. Идентификатор представляет собой символ "@". Поскольку все арифметические операции, кроме сложения и вычитания, выполняются так же медленно, как и в случае переменных с двойной точностью, данный тип является предпочтительным для проведения финансовых расчетов. (Для тех, кто интересуется: данный тип использует целые числа из 19 разрядов, которые затем делятся на 10000. Это позволяет организовать 15 знаков до запятой и 4 после нее.)

Date
Этот тип данных позволяет хранить значения времени и даты в промежутке от полуночи 1 января 100 года до полуночи 31 декабря 9999 года. Такие значения в тексте программ обозначаются символами "#", например:
Millenium = #January 1, 2000#
Если вводится только значение даты, Visual Basic полагает, что время равняется 00:00.

Byte
Байтовый тип является новым в Visual Basic 5 и предназначен для хранения целых чисел от 0 до 255. Его использование позволяет сильно экономить оперативную память и уменьшать размер массивов (см. главу 10) по сравнению с предыдущими версиями Visual Basic. Кроме того, он используется при работе с двоичными файлами.

Boolean
Булев тип данных может хранить только два значения: True либо False. Его использование вместо целочисленных переменных является хорошим стилем программирования.

Variant
Данный тип добавлен в Visual Basic 5 из версии 2.0. Переменная типа variant может содержать данные любого типа. Если Visual Basic не знает тип принимаемых данных, необходимо использовать variant.
Тип информации не имеет значения, поскольку variant может содержать любой тип данных (численный, дата/ время, строковый). Visual Basic автоматически производит необходимые преобразования данных, поэтому не стоит беспокоиться об этом. С другой стороны, можно использовать встроенные функции для проверки типа данных, хранящихся в переменной типа variant. Они позволяют легко проверить, правильно ли пользователь вводит информацию.
Использование такого типа данных, как variant, замедляет работу программы, так как требуется время и ресурсы для операций преобразования типов. Кроме того, многие программисты понимают, что использование автоматических преобразований типов данных приводит к неаккуратному виду программ. Единственная причина в использовании variant состоит в возможных ошибках при преобразовании типов непосредственно программистом.

Подробнее о переменных

В отличие от большинства версии BASIC, в одной программе Visual Basic нельзя использовать имена переменных, отличающиеся только типом (идентификатором), например, А% и А!. При попытке использовать двойное имя возникает ошибка "двойное определение" (duplicate difmition) при запуске программы.
При первом использовании переменной Visual Basic временно присваивает ей пустое значение и тип variant. Это значение исчезает в тот момент, когда переменной присваивают реальное. Каждый тип данных имеет свой образец "пустого" значения. Для строковых переменных это строка нулевой длины (" "). Для численных переменных это ноль. Полагаться можно только на значения по умолчанию, если они документированы (например, в комментариях). Иначе придется столкнуться с множеством трудно уловимых ошибок. Поэтому рекомендуется инициализировать значения переменных в первых строках процедур обработки событий.

Пример: обмен значениями
Одной из распространенных задач является обмен значениями между двумя переменными. Удивительно, но разработчики Visual Basic убрали из языка оператор Swap, известный в QuickBASIC. Поэтому код придется писать самим. Предположим, что есть две переменные х и у, и попробуем поменять местами их значения:

х = у
у = х

Такой вариант работать не будет, но важно разобраться из-за чего. Первый оператор присваивает переменной х значение переменной у, но тем самымуничтожает старое значение х.. В результате второй оператор присваивает переменной у ее же первоначальное значение. Решение состоит в использовании временной переменной:

temp = х 'Копирует старое значение х в temp
х = у 'Теперь х содержит старое значение у
у = temp 'Теперь у содержит оригинальное значение х

Объявление переменных: оператор Dim для различных типов данных

Большинство людей предпочитают не пользоваться идентификаторами для обозначения типа переменной (тем более, что для таких типов, как дата/время, вообще не существует идентификаторов). Вместо этого они используют оператор Dim. Такая операция носит название объявления. Объявление типов переменных в процедурах обработки событий перед их использованием - и, естественно, с комментариями - является хорошим стилем в программировании. Это также помогает улучшить читабельность текстов программ.

Private Sub cmdCalculate Click
'Данная процедура вычисляет проценты по закладной
Dim Years As Integer
Dim Rate As Currency
Dim Amount As Currency
Dim I As Integer
Dim TextBox As String
Dim Interest As Currency End Sub

Объявления переменных можно комбинировать в одной строке, например:
Dim Year As Integer, Rate As Currency, Name As String

Предупреждение: Общей ошибкой является использование следующего выражения:

Dim X, Y, Z As Integer

в предположении, что все три переменные будут целого типа. Фактически в данном случае переменные Х и Y будут объявлены как variant, и только Z - как integer. Чтобы избежать этого, необходимо каждый раз использовать идентификатор типа.

Если переменная объявлена при помощи оператора Dim, в случае использования переменной с тем же именем и другим идентификатором типа произойдет ошибка "двойное определение" при запуске программы. Например, если следующее выражение Dim Count As Integer объявляет переменную Count, то нельзя использовать переменные CountS, Count!, Count# и Count@. Можно использовать только имя Count%, но это всего лишь другая форма для имени переменной Count.
И, наконец, чтобы присвоить переменной тип variant, используют оператор Dim без As:

Dim Foo 'объявляет Foo переменной типа variant

Можно написать и так:

Dim Foo As Variant ' проще для прочтения

Примечание: Некоторые программисты используют соглашение о наименованиях, по которому перед именем переменной указывается префикс с ее типом строчными буквами, например, sngInterestRate или intCount.

Необходимость объявления переменных

Одной из наиболее общих ошибок в программах является использование необъявленных переменных. Проблема состоит в том, что Visual Basic позволяет создавать переменные "на лету", как только они первый раз встречаются в программе. Если перепутать имя переменной. Visual Basic создаст новую с похожим именем и присвоит ей значение по умолчанию, что тут же приведет к ошибкам. Эту ошибку трудно обнаружить, поскольку имена переменных очень похожи.
Простейший способ избежать подобных ошибок состоит в принудительном объявлении всех переменных. Тогда при ошибке в имени переменной будет выдано сообщение. Разработчики Visual Basic предусмотрели такую возможность работы, но по умолчанию она не используется.
Оператор, необходимый для этого, называется Option Explicit. Это первый из операторов, не используемый в процедурах обработки событий. Хороший способ запомнить для себя его принцип работы состоит в использовании Option Explicit для изменения значений по умолчанию.

Примечание: Любая информация, которая должна быть доступна всем процедурам обработки событий, относящихся к форме, размещается в разделе (General) данной формы.

Для того чтобы поместить Option Explicit в раздел (General), необходимо:

  • Открыть окно Code.
  • Выбрать объект (General) из списка объектов, представленных в окне Object.
  • Выбрать (Declaration) из списка Ргос.
  • Ввести Option Explicit.
Примечание: Часто использовать объявления на уровне формы в разделе (General) понадобится, когда читатель будет экспериментировать с примерами программ из справочной системы.

(Для копирования примера программы из справочной системы можно использовать кнопку Copy в окне Code для примера. Затем можно использовать пункт Paste из меню Edit для вставки примера в окно Code.). После того как Visual Basic встречает команду Option Explicit, он более не позволяет использовать необъявленные переменные. Если попробовать все же применить такую переменную, будет выдано сообщение об ошибке, как показано ниже.

Совет: Чтобы использовать обязательное объявление типа переменной, можно воспользоваться страницей Editor диалоговой панели Tools|0ptions. Автор (и, по его сведениям, все серьезные программисты) всегда устанавливает данный флажок. После этого оператор Option Explicit автоматически вставляется в те места кода, где это необходимо.

Изменение значений по умолчанию для типов

Следующее выражение автоматически присваивает переменным I, J и К тип variant:

Dim I, J, К

Предположим, в новой программе почти все переменные должны быть целочисленными. В таком случае удобно организовать их объявление так, чтобы переменная, для которой не указан тип, больше не объявлялась как variant. Для этого используется оператор DefType. В следующей таблице приведено несколько примеров его использования:

Оператор DefType | Описание
DefInt A-Z | Все переменные получают тип integer.
DefInt I-J | Все переменные, начинающиеся с букв I и J, получают тип integer.
DefStr S-Z | Все переменные, начинающиеся с букв от S до Z, получают тип String

В частности, можно установить соглашение, что все переменные, начинающиеся с I, будут целочисленными, с помощью Deflnt I. После этого оператор Dim I... будет объявлять переменные типа integer. Основные формы различных операторов DefType, необходимых в данной главе:

Deflnt диапазон букв (для integer)
DefLng диапазон букв (для long integer)
DefSng диапазон букв (для single precision)
DefDbl диапазон букв (для double precision)
DefCur диапазон букв (для currency)
DefStr диапазон букв (для string)
DefVar диапазон букв (для variant)
DefBooi диапазон букв (для boolean)
DefByte диапазон букв (для byte)
DefDate диапазон букв (для date)

Буквы не обязательно должны быть заглавными: DefStr s-Z и DefStr S-Z работают одинаково. Соглашения о типах можно всегда изменить, используя идентификатор или оператор Dim для конкретной переменной. Оператор DefType размещают в том же разделе (General), что и Option Explicit.

Область видимости переменных

Программисты применяют термин область видимости, когда хотят сказать о возможности использования переменной из одной части программы в другой ее части. В старых языках программирования все переменные были доступны во всех частях программы, поэтому сохранение целостности наименований представляло большую проблему. Например, если в приложении использовались две переменные Total, то их значения уничтожали друг друга.
Решение данной проблемы в современных языках высокого уровня, таких как Visual Basic, состоит в изолировании переменных внутри процедур. До тех пор пока это не задано специальным образом, значение переменной Total в одной процедуре никак не влияет на значение переменной с таким же именем в другой процедуре. В терминах программирования данный подход означает, что данные переменные являются локальными по отношению к процедурам, если не указано иначе. В частности, процедура обработки события обычно не имеет никакой связи с другой процедурой того же типа. Обычно не принято работать с переменными по умолчанию. Если необходимо быть уверенным, что данная переменная является локальной, нужно объявить ее внутри процедуры события при помощи оператора Dim.


Совместное использование значений процедурами.

Иногда требуется разделять значения переменных между процедурами обработки событий. Например, если приложение выполняет вычисления, всегда используя одинаковую процентную ставку, ее значение должно быть доступно всем процедурам формы. Такие переменные называют переменными уровня формы или уровня модуля (form-level, module-level). На рис. 5-4 показаны области видимости для переменных в проекте Visual Basic с одной формой. Как и оператор Option Explicit, объявление переменных уровня формы происходит в разделе Declarations. Например, если открыто окно Code, можно выбрать (Declarations) для объекта (General) и ввести:

Dim InterestRate As Currency

    • Значение переменной InterestRate будет доступно всем процедурам, связанным с данной формой.
    • Все изменения этой переменной, внесенные в любую из процедур, будут сохраняться.
Очевидно, что последний пункт призывает к осторожному изменению значений переменных уровня формы. Любая информация, передаваемая между процедурами обработки событий, является благодатной почвой для программных ошибок. Более того, зачастую такие ошибки тяжело исправлять.
В принципе, для локальных переменных и переменных уровня формы можно использовать одинаковые имена, хотя это не совсем хорошая идея. Любой оператор Dim на уровне процедуры имеет преимущество перед аналогичным на уровне формы, поэтому такие переменные будут локальными. Но при этом теряется возможность использования информации, содержащейся в переменных уровня формы. То есть дублирование имен приводит к недоступности для процедуры значения переменной уровня формы. Visual Basic, в свою очередь, не сообщает об этом программисту. Вот еще одна причина, по которой рекомендуется явно объявлять локальные переменные при помощи оператора Dim.

Совет: Некоторые программисты любят использовать префикс f (например, flnterest) для переменных уровня формы, а букву g - при обозначении глобальных переменных (например, ginterest). Это позволяет распознавать область видимости для конкретной переменной буквально с одного взгляда.

Зарезервированное слово Private. Visual Basic 5 использует это новое слово Private для обозначения переменных уровня формы. Private работает аналогично оператору Dim, и его можно использовать вместо этого оператора в разделе (General) для формы. Смысл состоит в том, чтобы отделить переменные уровня формы от так называемых глобальных (или public) переменных.

Сохраняем значения

Когда Visual Basic вызывает процедуру обработки события, старые значения локальных переменных уничтожаются. Они принимают значения по умолчанию. (Как часто отмечалось, лучше их проинициализировать заново.) Такие переменные называются динамическими. Однако этого недостаточно в некоторых ситуациях. Например, необходимо отследить, сколько раз была нажата командная кнопка. Если счетчик будет все время сбрасываться, сосчитать будет невозможно. В принципе, можно использовать для таких целей переменные уровня формы, но лучше применять их только для обмена информацией. Большинство программистов используют такой подход, только если необходимо считать и другим процедурам.
Выходом является использование статических переменных. Такие переменные не инициализируются повторно при вызове процедуры. Кроме того, что они идеально подходят для счетчиков, их используют, чтобы сделать элементы управления видимыми либо невидимыми (вообще говоря, для любых операций с булевыми свойствами), а также в отладочных целях. Для того чтобы объявить внутри процедуры статическую переменную, необходимо оператор Dim заменить на зарезервированное слово Static:

Static Counter As Integer, IsVisibie As Boolean

Ниже приведен пример процедуры обработки события с командной кнопкой, которая подсчитывает число щелчков и выводит суммарное значение:

Private Sub Commandl_Click ( )
'Данная процедура использует статическую переменную для подсчета щелчков
Static Counter As Integer 'Счетчик начинается с нуля
Counter = Counter + 1
Print Counter End Sub

При первом щелчке счетчику присваивается значение по умолчанию, то есть 0. Затем Visual Basic добавляет к нему 1 и затем выводит результат.
Иногда требуется, чтобы все переменные в процедуре были статическими. Для этого необходимо добавить зарезервированное слово Static перед строкой Private Sub, с которой начинается любая процедура:

Static Private Sub Commandl Click ()

Примечание: Переменные уровня формы и уровня модуля всегда сохраняют свои значения. Проблема состоит в том, что их может изменить любая процедура, а это может приводить к ошибкам. И когда необходимо сохранять значения переменных в перерывах между использованием процедуры, лучше применять статические переменные, чем переменные уровня формы или модуля.

Рисунок 5-4. Области видимости переменных.


Строки.

  • Формат ASCII/ANSI.
  • Переход на новую строку.
  • Другие полезные строковые константы.
  • Строки фиксированной длины.

Поскольку информация в полях ввода Visual Basic всегда хранится в текстовом формате, работа со строками представляется здесь более важной, чем в обычном BASIC. Для того чтобы сложить две строки вместе (т. н. конкатенация), можно использовать операторы & или +. Например:

Titles = "Queen "
Name$ = "Elizabeth "
Numeral$ = "I"
Title$ & Name$ &. Numeral$ = "Queen Elizabeth I"
Title$ 6 Name$ & Numeral$ & Numeral$ = "Queen Elizabeth II"

Оператор & объединяет строки в том порядке, как они представлены. Поэтому, в отличие от суммирования чисел, для слияния строк важен их порядок следования. С помощью & можно складывать сколько угодно строковых значении. Ниже приведен пример с использованием уже объявленных выше переменных:

CurrentQueen$ = Title$ & Name$ & Numeral$ & Numeral$

Совет: Основным отличием оператора & от + является возможность использования & для объединения строковых данных с другими их типами. Например, выражение С=А% & В$ объединяет целочисленную и строковую переменные, изменяя тип их значений на variant.

Формат ASCII/ANSI

У компьютера нет отдельного участка памяти для хранения текста, а еще одного - для чисел. Все, что поступает в память компьютера, преобразуется в числовой формат (реально - в двоичное представление). В программе помечается только, что данный участок оперативной памяти содержит закодированный текст. Обычно формат для преобразования текстовой информации в цифровую называется ASCII (American Standard Code for Information Interchange). Данный формат присваивает каждому символу соответствующее число в диапазоне от 0 до 255, хотя Windows не может вывести на экран все 255 символов и использует более ограниченный их набор ANSI (American National Standards Institute). Управляющие символы, а также специальные типа табуляции или перевода строки -имеют номера до 32. Значением функции Chr(n) является символ, соответствующий числу п из формата ASCII. Оператор

Print Chr(n)

либо выводит на экран символ, соответствующий коду ASCII, текущим шрифтом, либо имеет место особый эффект в зависимости от типа управляющего символа. Например, оператор

Print Chr(227)

выводит греческую букву "пи" на экране, если перед этим значение FontType было установлено как MS LineDraw при помощи окна Propoties или через Code.
Следующий пример использует код символа кавычек ("), 34, для вывода на экран фразы, заключенной в кавычки с обеих сторон:

Print Chr (34) ;
Print "Quoth the raven, nevermore." ;
Print Chr (34)

Совет: Функция Chr возвращает строку, хранимую в типе variant. Аналогичная более старая функция ChrS возвращает непосредственно строковое значение.

На экране будет выведено

"Quoth the raven, nevermore."

Примечание: Предыдущий результат можно получить с помощью оператора:

Print "'"'Quoth the raven, nevermore.""" ;

так как Visual Basic, в отличие от многих версий BASIC, воспринимает два символа кавычек как один и выводит его на экран в выражениях с оператором Print или при работе со строковыми значениями.

В Visual Basic существует функция Asc, параметром которой является строковое значение, а результат равняется коду ASCII для первого символа. Если строка пустая, функция выдаст ошибку во время выполнения (run time error).
Порядок ASCII/ANSI используется по умолчанию Visual Basic при сравнении различных строк с помощью операторов сравнения, таких как > и <. Наиболее важным местом для применения кодировки ASCII/ANSI является процедура обработки событий KeyPress.

Примечание: Внутри Visual Basic используется Unicode - система, поддерживающая все возможные языки. В ней содержится порядка 65 тысяч различных символов. Более подробно об этом можно узнать из приложения к книге "Руководство программиста". За исключением нескольких особых ситуаций, включая двоичные файлы, переключение между ASCII и Unicode является вполне прозрачным для программиста.

Переход на новую строку

В ранних версиях Visual Basic одним из основных способов использования функции Chr является формирование управляющих символов для перехода к новой строке в программах. Переход к новой строке используется при работе с многостроковыми полями ввода или при добавлении информации в информационной панели. Как и в старых печатных машинках, для перехода на новую строку необходимо проделать две операции: перевод каретки (carriage return) для возврата к первому символу строки, а затем перевод строки для перехода на следующую строку. При использовании функции Chr переход на новую строку выглядит так:

vbCrLf = Chr (13) + Chr (10)

Но теперь имеется возможность использовать встроенную константу vbCrLf.
Например, необходимо разорвать строку в информационной панели или в многостроковом поле ввода. Быстрее всего это реализовать с использованием vbCrLf:

TextString$ = "Visual Basic For Windows" + vbCrLf
TextString$ = TextString$ + "Osborne McGraw-Hill" + vbCrLf
TextString$ = TextString$ + "Berkeley, CA"
Textl.Text = TextString$

Совет: Если пренебречь установкой свойства MultiLine в значение True и попробовать применить переход на новую строку, то на экране можно будет увидеть две интересные вертикальные линейки прокрутки.

Кстати, может возникнуть вопрос: почему строковые значения сначала "накапливаются", а только затем выводятся на экран. Ответ прост: это один из основных способов ускорения работы программ на Visual Basic.

Совет: Гораздо быстрее сначала скомпоновать всю строку, а затем использовать свойство Text, чем изменять его постоянно.

Аналогичным образом можно ускорить формирование информационной панели, если в ней необходимо вывести несколько строк:

Message$ = "This will be on line 1."
Message = Message$ + vbCrLf + "This will be on line 2."
MsgBox Message$

Другие полезные строковые константы

Кроме константы vbcrLf, существует еще ряд других, не менее полезных встроенных констант (см. таблицу далее). Все эти константы можно использовать в программах на Visual Basic вместо соответствующих значений функций Chr$ или Chr. Однако для Microsoft Windows применение найдут только коды для carriage return, line feed, tab и backspace.

Строки фиксированной длины

Строки фиксированной длины представляют собой специальный тип строки, в дальнейшем часто используемый. Подобные переменные создаются при помощи оператора Dim. Вот пример:

Dim ShortString As String * 10
Dim strShort As String * 10


Объект Printer.

  • Полезные свойства и методы объекта Printer.
  • Набор Printers.
  • Печать информации в RichTextBox.

Visual Basic использует принтер, установленный по умолчанию в операционной системе. Он помогает легко установить его разрешение, свойства шрифтов и т.п.
Сперва рассмотрим команду PrintForm, выводящую на принтер графический образ окна формы. Если в приложении используется несколько форм, то необходимо указать требуемое имя в качестве параметра:

ИмяФормы.PrintForm

Поскольку данная команда выполняет побитовый перенос формы вместе с заголовком и рамкой на принтер, она не обладает достаточной гибкостью. Более того, большинство принтеров обладают большей разрешающей способностью, чем экран компьютера.
Большинство команд принтера в Visual Basic зависят от ориентации страницы. Это означает, что Visual Basic пересчитывает все выводимые символы (фактически точки) перед передачей информации на принтер. Это позволяет полностью управлять внешним видом печатающейся страницы.
Обычный способ для передачи информации на принтер состоит в использовании метода Print, связанного с объектом Printer. Например, поскольку метод Print учитывает ориентацию страницы, можно использовать свойства CurrentX и CurrentY для точного позиционирования текста и даже точек на странице.
Синтаксис команды для вывода текста на печать очень прост:

Printer.Print текст

В данном контексте можно использовать символы ";" и "," таким же образом, как в формах. Точка с запятой подавляет автоматический переход на следующую строку, а запятая осуществляет переход к новой печатной зоне (те же 14 колонок). Функции Tab и Spc работают аналогичным образом.
Управлять свойствами шрифта тоже можно обычным образом. Например:

Printer.Font.Name = "Script" 'Используется индексный шрифт размером 18 пунктов
Printer.Font.Size = 18

Как и в формах, изменения шрифта принтера не влияют на предыдущий текст. Все изменения отражаются только на вновь выводимом тексте.

Полезные свойства и методы для объекта Printer

Если изучить контекстную справку, то можно убедиться в наличии у объекта Printer 40 свойств и 12 методов. Большинство из них еще незнакомы читателю, например, DrawMode. Однако их знание является очень важным в плане работы не только с принтером, но и с формами. Ниже следует краткое описание некоторых наиболее часто используемых свойств и методов объекта Printer. (См. электронную документацию по тем свойствам и методам, которые не описаны здесь, а также документацию по символьным константам, обозначающим различные установки принтеров.)

ColorMode. Позволяет определить тип принтера - цветной он или монохромный. Возможны два значения данного свойства, они приведены в таблице ниже.

Символическая константа | Значение
vbPRSMMonochrome | 1
vbPRSMColor | 2

Copies. Устанавливает количество печатающихся копии.

Height, Width. Возвращают горизонтальный и вертикальный размеры бумаги в принтере, установленном в Windows. Они измеряются в твипсах, если только не установлена другая единица измерения. Данные значения нельзя изменить в период работы программы; они предназначены только для чтения. Они используются, например, для проверки того, переключился ли пользователь на другой размер бумаги, если выводимая информация не умещается на стандартном листе 8,5х11 дюймов. (Для такой страницы Visual Basic выдает значение ширины 12 288 твипсов и высоты - 15 744 твипсов.)

EndDoc. Этот метод сообщает Windows об окончании работы с документом. Его синтаксис следующий:

Printer.EndDoc

Данная строка освобождает память от последних страниц текста и посылает их в Диспетчер Печати Windows на печать.

NewPage. Данный метод завершает текущую страницу и посылает принтеру команду перехода на следующую страницу. Синтаксис:

Printer.NewPage

Page. Это свойство сохраняет количество напечатанных страниц текущего документа. Счетчик начинается с 1 и заканчивается после выполнения операции EndDoc. Он увеличивается на 1 всякий раз при использовании метода NewPage, а также в случаях, когда выводимая информация не вмещается на одну страницу. Обычно его
используют при выводе верхнего колонтитула.

PrintQuality. Используется для установки качества печати, если это поддерживает драйвер принтера. Синтаксис:

Printer. PrintQuality = значение где можно использовать четьгое встпоенные константы, пепечисленные в таблице ниже

Константа | Значение | Описание
vbPRPQDraft | 1 | Черновая печать
vbPRPQLow | 2 | Низкое разрешение
vbPRPQMedium | 3 | Среднее разрешение
vbPRPQHigh | 4 | Высокое разрешение

Примечание: Дополнительно можно устанавливать разрешение в точках на дюйм, если принтер (и его драйвер) поддерживают это.

Набор Printers

По определению объект Printer представляет собой текущий принтер Windows. Набор Printers, с другой стороны, позволяет получить доступ ко всем установленным драйверам принтеров в системе (например, это может быть и драйвер факса). Число установленных принтеров можно получить так:

Printers.Count

Доступ к элементу набора осуществляется следующим образом:

Printers {индекс},

где индекс означает число от 0 до Printers.Count - 1.

Примечание: Фактически принтер по умолчанию можно изменить, используя Visual Basic. Это делается при помощи команды Set.

Например, следующий фрагмент кода использует оператор For-Next для проверки каждого принтера системы по признаку цветной/монохромный. (После изучения еще нескольких команд Вы сможете легко добавить сюда несколько строк, чтобы предложить пользователю перейти на цветной принтер.)

For I = 1 То Printers.Count - 1
If Printers(I).ColorMode = vbPRCMColor Then
MsgBox "At least one printer has a color mode"
End If Next I

Примечание: Изменять параметры можно только для текущего принтера системы.

Печать информации в RichTextBox

Метод SelPrint позволяет печатать текущее отформатированное содержимое данного элемента управления на текущий принтер. Если при этом пользователь выделил текст внутри RichTextBox, то метод SelPrint посылает на принтер только выделенный фрагмент. Если текст не выделен, то на принтер посылается все содержимое RichTextBox. Чтобы перевести принтер в состояние готовности (Ready), необходимо послать сперва на него пустую строку. Далее необходимо использовать контекст устройства для текущего принтера. (Контекст устройства представляет собой целое число, которое Windows использует для идентификации объекта.)
Таким образом, пример программы будет выглядеть так:

Printer.Print. " "
RichTextBoxl.SelPrint(Printer.hDC)

(Свойство hDC объекта Printer определяет идентификатор контекста устройства. Он используется Windows для специальных целей и лучше не вникать в то, как он работает.)


Функция DoEvents.

Обычно программист ожидает, что Windows (и Visual Basic) постоянно отслеживает все события и реагирует на них. С другой стороны, много времени уходит на различного рода вычисления или сортировку данных. Однако при этом не всегда желательно запрещать приложению Visual Basic реагировать на события. Если создается процедура, в которой много времени будет занято различного рода вычислениями, необходим механизм отслеживания состояния операционной системы и реагирования на различного рода события.
Функция, выполняющая такого рода задачи, носит название DoEvents. В каком бы месте программы ни стоял данный оператор, он сигнализирует Visual Basic о том, что управление передано операционной системе для обработки всех событий. (Windows сохраняет последовательность наступления событий в очереди, а также нажатий клавиш в очереди SendKeys.) DoEvents нельзя использовать в процедуре обработки событий, которая вызывается несколько раз. Например, процедура обработки событий Click может быть вызвана еще раз щелчком мыши. Если забыть про это, можно легко организовать в программе бесконечный цикл.
Цикл, работающий только в случае отсутствия каких-либо событий, носит наименование цикла ожидания. Такие циклы создаются внутри специализированной процедуры Sub, носящей имя Main, и подключающейся к любому программному модулю. В каждом проекте существует только одна процедура Main. Ее формат следующий:

Sub Main()
Do While DoEvents()
'Код, работающий в процессе цикла ожидания
Loop
End Sub

Далее необходимо сделать так, чтобы модуль с Main загружался первым. Для этого нужно выбрать в меню пункт Project|Project Properties и затем страницу General. На этой странице выбрать и открыть список StartUp Object и выбрать Sub Main вместо формы. После того как процедура Main указана загрузочной. Visual Basic не будет автоматически загружать какую-нибудь форму. Для этого теперь необходимо писать программный код, используя, сервированные слова Load и Show.
Ниже приведен простой пример цикла ожидания. Необходимо создать новый проект и добавить к нему программный модуль. Далее в нем объявляется глобальная переменная Counter типа long integer:

Public Counter As Long

Теперь добавим несколько строк кода к процедуре Main:

Sub Main()
Fonnl. Show
Do While DoEventsf)
Counter = Counter + 1
loop
End Sub

В завершение установим Sub Main, как загрузочный модуль, и добавим процедуру Form_Click:

Sub Form_Click()
Print Counter
End Sub

После запуска такой программы счетчик будет увеличиваться каждый раз после щелчка внутри формы. Причина состоит в том, что в течение периода ожидания (когда пользователь не щелкает мышью) Visual Basic переходит к процедуре Main и добавляет 1 к счетчику. Поскольку Counter является глобальной переменной, ее значение сохраняется между вызовами процедуры.
Функция DoEvents на самом деле возвращает число форм, загруженных приложением в текущий момент. Цикл ожидания останавливается, когда выгружены все формы. (Или Visual Basic встречает оператор End.)
Другое общее использование функции DoEvents внутри функции состоит в запуске больших по времени вычислений. Установка цикла ожидания позволяет Visual Basic реагировать на события в момент вычислений. Небольшое дополнительное время при этом с лихвой окупается.


Обработка ошибок.

    Подробнее о объектеErr.

Независимо от того, насколько тщательно разработана и отлажена программа, никогда нельзя полностью исключить возможность возникновения ошибок. Поэтому при написании устойчивых программ необходимо обрабатывать возникновение неисправимых ошибок. Команда, которая применяется для их перехвата в конкретной процедуре, выглядит следующим образом:

On Error GoTo...

где три точки означают метку (номер строки), определяющую начало фрагмента обработки ошибки. Данный фрагмент должен размещаться в рамках этой же процедуры. Невозможно при помощи команды On Error GoTo перейти к другой процедуре. С другой стороны, фрагменты для перехвата ошибок часто используются в других процедурах или функциях.
Метка для оператора перехвата ошибки представляет собой любой идентификатор с последним символом в виде двоеточия, удовлетворяющий соглашению об именованиях переменных. Метка обозначает начало фрагмента программы, как показано ниже:

ErrorTrap:
'программный код следует далее

Поскольку необходимо исключить возможность "вылета" из программы в процедуре обработки ошибок, неплохой идеей является использование оператора Exit в строке, непосредственно перед меткой фрагмента по перехвату ошибок.
Команда On Error GoTo может находиться в любом месте процедуры обработки события, функции или процедуры Sub. Обычно там же размещается и программный код для обработки ошибок. Единственным исключением является тот случаи, когда процедура вызывается в рамках другой процедуры. Тогда Visual Basic сначала проверяет наличие фрагмента по обработке ошибок сначала в текущей процедуре, а затем в "вышестоящей".
После того, как программист начинает пользоваться обработкой ошибок при помощи On Error GoTo, в его приложениях исчезают ошибки во время выполнения. (Но это не защищает от ошибок в операционной системе; в Windows 95 их немного меньше, чем в Windows 3-х.) В любом случае команда On Error GoTo должна передавать управление на участок кода, который идентифицирует проблему и, возможно, решает ее.
Если ошибку можно исправить, то оператор Resume используется для возврата к месту в программе, где она случилась. Однако невозможно исправить ошибку, не зная о вызвавшей ее причине. Это можно сделать, используя функцию Err и объект Err. Он представляет собой целое значение, которое можно присваивать переменной. Например, если написать

ErrorNumber = Err.Number

то значением переменной ErrorNumber будет номер ошибки.
Visual Basic может идентифицировать более 80 ошибок во время выполнения. (Информацию об этом необходимо искать в электронной документации в разделе "trappable errors"). Ниже приведено два примера.

Код ошибки | Объяснение
57 | Ошибка ввода вывода (например, попытка печати при выключеном принтере).
68 | Устройство недоступно (устройство не существует или недоступно в данный момент).

Способ использования данной информации очень прост. Предположим, процедура обработки события использует принтер. Где-нибудь внутри процедуры перед оператором печати необходимо разместить строку

On Error GoTo PrinterCheck

Теперь перед оператором End Sub необходимо добавить следующий код:

Exit sub
PrinterCheck:
ErrorNumber = Err. Number
Beep
Select Case ErrorNumber
Case 25
MsgBox "Your printer may be off-line."
Case 27
MsgBox "Is there a printer available?" Case Else
M$ = "Please tell the operator (= program author?) that"
M$ = M$ & vbCrLf '= Chr$(10) + Chr$(13) New Line
M$ = M$ & "error number " & ErrorNumber & " occurred."
MsgBox M$
End
End Select
М$ = "If the error has been corrected click on OK."
M$ = M$ & vbCrLf
M$ = M$ & "Otherwise click on Cancel."
Continue = MsgBox(M$, vbOKCancel)
If Continue = vbOK Then Resume Else End

Идея данного обработчика ошибок очень проста, а оператор Select Case подходит в данном случае идеально. В каждом отдельном случае программа проверяет тип ошибки и предлагает возможные рекомендации по ее исправлению. Если достигнут оператор Case Else, то на экране появляется номер ошибки. В любом случае после цикла по обработке ошибок пользователю предлагается возможность выбора при помощи информационной панели с двумя кнопками. Возможно, понадобится написать общую процедуру для анализа ошибок. Тогда фрагмент по обработке ошибок в процедуре будет передавать управление выше. После этого такую процедуру можно использовать для перехвата и обработки ошибок во многих различных проектах.
Перехват ошибок не является панацеей. Например, трудно что-либо сделать, если возникает сбой жесткого диска или в принтере отсутствует бумага.
Вариант команды Resume позволяет пропустить оператор, в котором возникла ошибка. Если использовать

Resume Next

Visual Basic начинает обрабатывать оператор, находящийся непосредственно после текущего. Можно даже использовать

On Error Resume Next

для автоматического пропуска любого программного кода, вызывающего ошибку. (Это приведено не случайно, хотя в данной главе не применяется.)
Кроме того, можно возобновить работу программы с любой строки кода, на которую указывает метка. Для этого используется следующий оператор:

Resume Label

Обычно в Visual Basic метки применяются только для обозначения фрагментов по обработке ошибок. Несмотря на это, для совместимости с предыдущими версиями BASIC введен оператор безусловного перехода GoTo, но нужда в нем возникает очень редко.
Команды Resume и Resume Next ведут себя по-разному при возврате назад в том случае, когда ошибка произошла в другой процедуре. Вспомним, что такое имеет место, когда одна процедура вызывается из другой и в ней отсутствует фрагмент по обработке ошибок: В обоих случаях Visual Basic не переходит к исходной процедуре. В случае оператора Resume происходит повторный вызов процедуры. В случае Resume Next Visual Basic начинает выполнять программу с первого (после вызова процедуры) оператора. То есть не происходит возврата к исходной процедуре.
Предположим, что цепочка взаимосвязанных процедур удлинилась: Процедура1 вызывает Процедуру2, а та, в свою очередь, ФункциюЗ. Допустим, что ошибка происходит в момент выполнения ФункцииЗ, но обработчик ошибок есть только у Процедуры1. Если в данном фрагменте присутствует оператор Resume, то Visual Basic переходит к оператору, вызывающему Процедуру2. Поскольку такой подход на самом деле очень громоздкий, то лучше всегда полагаться только на обработчики ошибок, находящиеся в текущей процедуре. Если одна процедура вызывает другую, то у нее необходимо отключить фрагмент по обработке ошибок.
Существует еще одна функция для обработки ошибок, Erl (Error Line). Если программист отчаялся найти строку, в которой возникает ошибка, a Visual Basic не останавливается на данной строке, нужно проделать следующее:

  • Добавить номера строк перед каждым оператором в процедуре.
  • Добавить оператор Debug.Print Eri в фрагмент по обработке ошибок.
При разработке программ часто необходимо протестировать функционирование обработчика ошибок. Visual Basic включает в себя оператор

Error (номер кода ошибки)

который заставляет Visual Basic срабатывать так, как будто произошла ошибка с указанным номером. Это облегчает процесс отладки перехвата ошибок.
Если программист уверен в том, что программа более не нуждается в обработчике ошибок, он может отключить его при помощи оператора

On Error GoTo 0

(хотя значение 0 в общем-то не очень и необходимо). Похожим образом можно изменить ссылку на другой обработчик ошибок, используя другой оператор On Error GoTo. Необходимо убедиться, что между обработчиками ошибок присутствует оператор Exit. Visual Basic использует последний оператор On Error GoTo для того, чтобы решить, куда двигаться далее.

Подробнее о объекте Err

Для того чтобы обеспечить более централизованную обработку ошибок, когда это необходимо, Visual Basic использует специальный объект Err, свойства которого можно анализировать после возникновения ошибки. Ранее мы видели, как работает свойство Number (Err.Number), выдающее номер ошибки.

Примечание: Microsoft рекомендует использовать свойства и методы объекта Err вместо различного рода функций обработки ошибок, за исключением только программ, написанных для более ранних версий языка.

После того как выполняется оператор Resume или On Error, все свойства объекта Err сбрасываются в 0 или сводятся к строке "". Свойства данного объекта инициализируются и после выхода из функции или процедуры. Кроме того, для этого можно использовать оператор

Err.Clear

Примечание: Правильным является создание централизованного обработчика ошибок. Однако данной процедуре необходимо передавать текущее значение различных свойств объекта Err. Если вызвать объект Err из новой процедуры, все его значения будут инициализированы.

Если необходимо генерировать ошибку для тестовых целей, можно использовать метод Raise объекта Err. Его синтаксис следующий:

Err.Raise (Номер)

Совет: Можно использовать метод Raise для определения собственных типов ошибок.

Если необходимо вывести описание текущей ошибки, используется оператор

Err.Description


Заключительные штрихи интерфейса

    Элементы управления в версиях Professional и Enterprise
    Управляющий элемент Microsoft Comm 5.0
    Пример использования элемента управления коммуникациями
    Управляющий элемент редактирования с маской ввода Masked Edit Control 5.0
    Элемент управления средствами мультимедиа
    Элемент управления фрагментами Microsoft Picture Clip Control 5.0
    Диалоговый элемент управления с закладками Microsoft Tabbed Dialog Control 5.0
    Пример: использование элемента управления ярлыками
    Управляющие элементы Microsoft Windows Common Controls 5.0

Примечание: Фирма Microsoft недавно анонсировала планы по переводу обычной справочной системы Windows в формат HTML (формат, используемый в Web-документах).

Элементы управления в версиях Professional и Enterprise

В этом разделе рассказывается о нескольких наиболее часто используемых управляющих элементах, поставляемых в версиях Professional и Enterprise системы Visual Basic. Все эти элементы доступны через диалоговую панель Project|Components.

Управляющий элемент Microsoft Comm 5.0

При помощи элементов управления коммуникациями можно легко создать собственный коммуникационный пакет, работающий через модем с телефонной линией (не с сетью Internet). Также можно самостоятельно изменить его настройки при помощи многочисленных свойств. На настоящий момент максимальной доступной скоростью обмена является 28 800 бод. Кроме скорости обмена можно изменять такие настройки, как число бит данных, наличие бита четности и др. Элемент управления коммуникациями служит блестящим примером, демонстрирующим достоинства событийно-управляемого языка программирования. К примеру, можно создать элемент управления, вызываемый только при приходе сигнала активности от коммуникационного порта.

Примечание: Для построения коммуникационных средств работы с сетью Internet можно воспользоваться элементом Microsoft Web Browser Control 5.0.

Приведенная ниже таблица содержит ключевые свойства элементов управления коммуникациями.

Свойство | Описание
CommEvent | Ключевое слово элементов управления коммуникациями. Различные значения этого свойства соответствуют как событиям, так и ошибкам коммуникационного порта. Например, если значение этого свойства равно константе comEOF (=7), то это говорит о наличии символа конца файла (ASCII 26) в буфере приема.
CommPort | При записи устанавливает адрес коммуникационного порта, при чтении - возвращает адрес текущего порта. (1 - для порта Сош 1,2— для порта Corn 2 и т.д.)
Settings | Устанавливает или возвращает следующие параметры: скорость обмена, наличие бита четности, число бит данных, число стоп-битов. Может устанавливаться как при создании элемента, так и во время работы приложения.
PortOpen | Открывает и закрывает коммуникационный порт.
Input | Возвращает символы, находящиеся в буфере приема.
Output | Посылает строку символов в буфер отправки.

Элемент управления коммуникациями реагирует только на событие OnComm. Это событие соответствует изменению значения свойства CommEvent. При возникновении события можно, проанализировав значение свойства CommEvent, вызвать соответствующий обработчик события.
Так же, как и остальные управляющие элементы, элемент управления коммуникациями дает возможность легкой настройки свойств через диалоговую панель. Для этого достаточно в окне Properties перейти к пользовательским настройкам (Custom). В появившейся диалоговой панели можно установить необходимые свойства элемента управления.

Пример использования элемента управления коммуникациями.

В качестве простейшего примера использования элемента управления коммуникациями приведем программу принимающую информацию через один из коммуникационных портов (в данном примере используется порт СОМ I):

Private Sub cmdGetData Click ()
Dim Foo As Integer, TheData As String MsCornml. CommPort = 1 ' COM1
'28800 baud, no parity, 8 data, and 1 stop bit.
'the Communications control can't go faster than 28800 baud
MsCornml.Settings = "28800,N,8,1"
MsCornml.InputLen = 0 ' Clear the comm buffer
MsCornml.PortOpen = True
'Send the usual wake up signal to the modem.
MsCornml.Output = "AT" + Chr$(13)
Do
Foo = DoEvents ()
MsCornml.InputLen = 0 'clear buffer
TheData = TheData + ComiTil .Input "TheData will contain the data
Loop Until Commi.CommEvent = comEvEOF
MsCornml. PortOpen = False 'close the port
End Sub

Управляющий элемент редактирования с маской ввода Masked Edit Control 5.0

Применение элемента редактирования с маской ввода позволит управлять информацией, вводимой в панель текста, без написания дополнительного кода. Использование элемента редактирования с маской ввода напоминает использование обычной текстовой панели с той лишь разницей, что можно ограничивать вводимые символы без написания дополнительного кода в поле Key Events. Также можно использовать определенные символы для указания пользователю о типе и внешнем виде вводимых данных (например, телефонного номера или номера социальной страховки). Управляющий элемент редактирования с маской ввода относится к классу элементов с определенными данными (data - aware controls).
При работе с элементами редактирования с маской ввода наиболее важным является свойство Mask. Можно устанавливать это свойство как при создании элемента, так и во время выполнения. Это свойство предназначено для задания маски, по которой пользователь вводит данные. Например, если надо получить номер телефона в формате, принятом в США, то необходимо установить свойство Mask следующим образом:

MaskEdBoxl.Mask = " (###) -###-####"

В результате элемент редактирования с маской ввода будет выглядеть так, как представлено на рисунке слева.
(Естественно, при создании этого элемента не надо использовать поле ограничений в окне Properties.)
Элемент редактирования с маской ввода имеет собственную диалоговую панель со списком свойств.
В общем случае строка, задаваемая свойством Mask, работает аналогично строке формата в выражении Format. Так, в предыдущем примере символ "#" обозначает цифру, а символ " - " используется как разделитель.
Наиболее часто употребляемые символы маски приведены в следующей таблице.

Символ маски | Описание
# | Требуется цифра.
. | Десятичная точка. Внешний вид данного символа при выводе зависит от текущих настроек системы Windows.
, | Разделитель множества. Как и в предыдущем случае, внешний вид выводимого символа зависит от настроек операционной системы.
: | Разделитель для поля времени.
/ | Разделитель для поля даты.

Символ маски | Описание
? | Требуется буква. Например, а - z или А - Z.
& (или С) | Требуется символ ASCII, код которого находится в промежутке 32-126 или 128 -255.
> | Переводит все вводимые символы в верхний регистр.
< | Переводит все вводимые символы в нижний регистр.
А | Требуется буква или цифра.
а | Позволяет ввести букву или цифру.
9 | Позволяет ввести цифру.

К примеру, маска "а9" позволяет, но не требует от пользователя введения двух символов. Первым символом может быть любая буква или цифра, вторым -только цифра.
Все остальные символы, которые не указаны в таблице, непосредственно выводятся на экран. (Если надо просто вывести один из специальных символов, не учитывая его значение, просто поставьте перед ним символ обратной косой черты \. Например, маска "\##" выводит на экран символ #, после которого следует поле для ввода цифры).
Для создания ноля ввода времени и даты можно использовать одну из предопределенных масок.

Маска | Описание
Пустая строка (" " в коде) | (Используется по умолчанию.) Означает отсутствие маски ввода. В этом случае панель редактирования с маской ввода ведет себя как обычная текстовая панель.
## - ??? - ## | Маска для ввода даты (соответствует стандарту, принятому в США).
## - ## - ## | То же самое. За исключением того, что название месяца заменяется его номером.
##:## ?? | Маска для ввода времени (расширенный формат).
##:## | Маска для ввода времени (сокращенный формат).

Одним из наиболее редко используемых событий при работе с панелями редактирования с маской ввода является событие Validation Error. Оно имеет место при попытке пользователя ввести неверные данные. (Это событие может использоваться только для установления факта ввода символа, не соответствующего маске.)

Элемент управления средствами мультимедиа

Устройства мультимедиа, такие, как проигрыватель компакт-дисков, становятся неотъемлемой частью современного персонального компьютера. Идея совместного объединения текста, звука и изображения действительно является передовой для нашего времени.
Элементы управления средствами мультимедиа дают пользователю возможность воспользоваться командами интерфейса media control interface (MCI). Команды MCI позволяют управлять аудио- и видеоустройствами и не зависят от используемого оборудования. При помощи элемента управления средствами мультимедиа можно посылать такие команды, как старт, перемотка вперед, перемотка назад, пауза и т.д. Всего этот- элемент управления содержит около 60 свойств, поэтому вместо детального их описания здесь приводятся только основные принципы работы, исходя из которых можно самостоятельно настроить элемент управления, используя страницу Custom Properties.
Это просто набор кнопок с названиями: Prev, Next, Play, Pause, Bask, Step, Stop, Record и Eject.
Всякий раз, когда пользователь нажимает одну из кнопок, необходимо послать соответствующую команду MCI. Но прежде чем сделать это, необходимо решить, нужно ли:

  • чтобы пользователь управлял устройством мультимедиа посредством нажатия на кнопки:
  • самостоятельно управлять устройством без участия пользователя.
В первом случае следует установить значение True для свойств Visible и Enable (обычно это сделано по умолчанию в процедуре события Form_Load). Во втором случае необходимо использовать значение False для подавления вывода элемента на экран и для блокировки его использования.
В качестве простейшего примера использования элемента управления средствами мультимедиа приведем программу проигрывания .wav-файлов. Последовательность шагов при этом будет такая:
  • Создать текстовую панель для названия проигрываемого файла.
  • Создать командную кнопку на пустом поле рабочей формы.
  • Создать элемент управления средствами мультимедиа с установленными в False свойствами Visible и Enabled.
  • Для работы элемента в фоновом режиме надо воспользоваться следующей процедурой Form_Load:
Private Sub Form Load ()
'Set the properties needed by the MCI control to work
MMControll.Notify = False
MMControll.Wait = True
MMControli.Shareable = False
MMControll.DeviceType = "WaveAudio" \
End Sub
  • Теперь добавим следующие строки в процедуру Command! Click:
Command! Click ()
MMControll.FileName = Textl.Text
MMControll.Command = "Open" 'play the file named in the textbox
End Sub

Вот и все!

Элемент управления фрагментами Microsoft Picture Clip Control 5.0

Элемент управления фрагментами Microsoft Picture Clip Control представляет собой еще один способ размещения растровых изображений (bitmaps) в элементе управления. Так же, как и в случае с "оживленными" кнопками, подобная технология позволяет сохранять ресурсы системы Windows, а также повышать скорость доступа к изображению. К примеру, такой элемент может использоваться для создания панели инструментов, что позволит пользователю настраивать инструментарий по собственному усмотрению. В этом случае вместо считывания каждого инструмента как отдельного растрового изображения, можно сделать это одним действием, воспользовавшись элементом управления фрагментами.

Совет: Одним из путей объединения нескольких растровых изображении в одно является одновременное использование двух копии программы Windows Paint. Надо просто вырезать одно изображение и вставить его в другое. Подобную операцию можно повторить необходимое число раз.

Из-за сложности в получении одного большого растрового изображения из нескольких файлов, необходимого при использовании элемента управления фрагментами, обычно используют элемент List Image.
В любом случае полезно посмотреть программу, использующую элемент управления фрагментами (например, программу

C:\VB\Samples\comptool\Picclip\Redtop.vbp).

Диалоговый элемент управления с закладками Microsoft Tabbed Dialog Control 5.0

Этот элемент управления дает возможность создавать диалоговые панели с закладками, наподобие диалоговой панели Tools/0ptions в системе Visual Basic. Этот элемент позволяет создавать группу диалоговых панелей, каждая из которых снабжена собственной закладкой.
Как можно было заметить, первым шагом при создании такого элемента является установка его размера, необходимого для вывода информации в каждой закладке.

Пример: использование элемента управления ярлыками

При создании этого управляющего элемента нужно:

  • определить необходимое количество закладок;
  • определить для каждой закладки необходимый элемент управления.
Число закладок задается значением свойства Tabs (по умолчанию это значение равно 3). Если необходимо создать несколько рядов с закладками, надо воспользоваться свойством Tabs Per Row. При этом Visual Basic автоматически поместит необходимое количество закладок в рабочую форму. При помощи свойства Caption можно изменить надпись на закладке. (Если надо добавить горячую клавишу, то перед соответствующим символом необходимо поставить знак амперсанда &.) Свойство Picture позволяет поместить изображение на закладку. Свойства Tab Height и Tab Width задают геометрические размеры закладки.
Следующим шагом является назначение каждой закладке соответствующего элемента управления. Для этого надо выбрать нужную закладку, щелкнув по ней указателем мыши. В качестве элемента управления, соответствующего закладке, можно использовать любой обычный элемент, используя технологию работы с контейнерными элементами. (Не следует забывать о недопустимости использования метода двойного щелчка.)

Примечание: После запуска проекта диалоговый элемент управления с закладками работает обычным образом. Пользователь может перемещаться по закладкам при помощи комбинаций клавиш CTRL+TAB, CTRL+SHIFT+TAB или посредством горячих клавиш, описанных в свойстве Caption.

Как и в случае с обычными диалоговыми панелями, можно использовать в элементе управления закладками кнопки ОК и Cancel.

Совет: При помощи свойства Border Style можно сделать из диалогового управляющего элемента с закладками обычную диалоговую панель. Можно также расположить кнопки ОК и Cancel за пределами элемента управления.

Управляющие элементы Microsoft Windows Common Controls 5.0

Можно одним действием добавить к панели инструментов восемь управляющих элементов. Для этого необходимо выбрать из диалоговой панели Project|Components соответствующую строку. В этом разделе мы вкратце опишем эти управляющие элементы.

Управляющий элемент Image List

Использование элемента Image List -это еще один способ размещения группы изображений в одном месте. Эти изображения могут использоваться в любой части приложения (например, в качестве изображений для панели инструментов). Ключевым моментом при работе с элементом Image List является понимание принципов работы объектов List Image (а также коллекции элементов List Image), которые определяют изображения, используемые в управляющем элементе. (Можно рассматривать элемент Image List как контейнер для коллекции List Image.) Проще всего работать с элементом Image List при помощи его свойства (Custom), появляющегося из диалоговой панели Property Pages.
Закладка Image на этой панели позволяет добавить изображения при создании элемента.
(В процессе работы можно добавлять изображения в коллекцию List Image при помощи метода Add).
Управляющий элемент Image List может хранить одновременно как растровые изображения, так и иконки. Так же, как и для других коллекций, можно ссылаться на хранящиеся изображения при помощи индекса или ключа. Например, если назначить изображение ImageListI. Listlmages(l), то команда

Set Picturel. Picture = ImageListI. Listlmages (1) .Picture
' set Picture.

заполнит панель рисунка первым изображением из коллекции. (Нумерация в коллекции List Images начинается с 1.)
Следует отметить, что не существует какого-либо ограничения на размер отдельного изображения, но общее число изображении, которое может быть считано, ограничено размерами доступной памяти.

Совет: Управляющий элемент List Image не обязательно использовать в качестве пассивного контейнера. Использование метода Overlay позволит создать одно изображение из двух других.

Управляющий элемент List View

Управляющий элемент List View позволяет выводить элементы на дисплей различными путями - по существу, это те же пути, которые можно было наблюдать, работая с рабочим столом системы Windows 95. Например, можно использовать List View для выравнивания элементов по столбцам (с заголовком каждого столбца или без него). Элементы при этом состоят из иконки и текста. Различные методы представления элемента описываются свойством View, возможные значения которого приведены в таблице.

Константа | Значение | Описание
IvwIconO | 0 | (Используется по умолчанию.) Каждый объект List Item состоит из полноформатной иконки и метки.
IvwSmallIcon 0 | 1 | Каждый объект List Item представляется иконкой малого размера и меткой, расположенной справа от иконки. Все элементы выравниваются по горизонтали.
IvwList | 2 | Как и в предыдущем случае, каждый объект .состоит из иконки малого размера и текстовой Сметки, расположенной справа от иконки. Но при .этом все объекты представляются в виде списка.
IvwReport | 3 | Каждый объект элемента List Item представляется иконкой малого размера и меткой. Иконки и метки располагаются в колонках. Также для вывода пояснительного текста используются дополнительные колонки.

Изображаемые элементы управляющего элемента List View называются List Item объектами. Каждый объект List Item имеет собственные свойства, описывающие его поведение. Наиболее важные из них представлены в таблице далее.
Также можно выбрать заголовок для выводимого столбца в управляющем элементе List View. Для этого необходимо установить значение True/False для свойства Hide Column Headers. Соответствующий заголовок хранится в объекте Column Header, а все заголовки колонок собраны в коллекцию Column Headers.

Свойство | Описание
Icons | Возвращает или позволяет установить индекс или ключ для иконки, ассоциированной с объектом List Item.
Selected | Сообщает о том, что объект List Item был выбран пользователем (или позволяет его выбрать самостоятельно).
Smalllcons | Возвращает или позволяет установить индекс или ключ для иконки малого размера, ассоциированный с объектом.
Subltems | Возвращает или позволяет установить поясняющую строку для подэлемента, ассоциированного со специальным объектом List Item.
Text | Задает текст, который будет виден пользователем.

Совет: Для сохранения системных ресурсов можно привязать элемент Image List к управляющему элементу List View. Для этого надо установить ссылки свойств Icons и Smalllcons элемента List View на управляющий элемент Image List. Также можно указать соответствующий элемент Image List во время проектирования, воспользовавшись свойством (Custom) диалоговой панели.

Управляющий элемент Progress Bаr

Использование управляющего элемента Progress Bar (.линейка прогресса) аналогично использованию вертикальной линейки в элементе управления размерами. Он представляет собой линейку, заполненную прямоугольными блоками. Далее описываются ключевые свойства этого элемента.
Свойство Align предназначено для автоматического выравнивания положения элемента на экране. Возможные значения этого свойства сведены в следующую таблицу.

Значение | Описание
0 | (Используется по умолчанию.) Размер и положение элемента могут быть произвольными.
1 | Линейка расположена наверху формы и имеет ширину, равную ширине формы.
2 | То же самое, но линейка расположена снизу от формы.
3 | Линейка выровнена по левому краю формы, а ее высота равна высоте формы.
4 | То же самое, но линейка выровнена по правому краю формы.

Точно так же, как и в случае с полосой прокрутки, можно, изменяя свойства Min и Мах, установить границы изображаемой линейки прогресса. Свойство Value определяет текущую позицию прогресса.

Совет: Для того чтобы изобразить большее количество блоков внутри линейки, можно уменьшить высоту или увеличить ширину управляющего элемента.

Рассмотрим пример: пусть необходимо изобразить количество времени, прошедшего с начала какого-нибудь процесса. Для этого создадим элемент управления таймером, метку и линейку прогресса.
Теперь установим для свойства Min значение 0, а для свойства Мах - значение 60. Свойство Interval для элемента управления таймером следует установить в 1000 (что соответствует 1 секунде). Тогда следующий фрагмент кода будет изображать при помощи линейки прогресса прошедшее время (в секундах).
Private Sub Timerl_Timer ()
Static Progress As Integer
Progress = (Progress + 1) Mod 60
Labell.Caption = Str$(Progress) & " seconds elapsed."
ProgressBarl.Value = Progress
End Sub

Управляющий элемент Slider Control

Управляющий элемент Slider (бегунок) работает аналогично линейке прокрутки. Он представляет собой небольшую линейку с делениями, по которой может перемещаться бегунок.
Пользователь может перемещать бегунок обычным перетягиванием или же щелкая мышкой с соответствующей стороны линейки. Для этой пели можно также применять клавиатуру. Так же, как и для линейки прокрутки, ключевыми свойствами являются Мах, Min и Value, что, соответственно, определяет максимальную, минимальную и текущую позицию бегунка. Ключевое событие Scroll возникает всякий раз, когда пользователь перемещает бегунок.

Управляющий элемент Status Bar (строка состояния)

Управляющий элемент Status Bar представляет собой строку (обычно расположенную в нижней части формы), в которой выводится текущее состояние приложения. (Например, текстовый редактор Microsoft Word использует строку состояния для указания номера текущей страницы, а также для информации о наличии режима вставки или замены, и т.д.). Свойство Align указывает метод выравнивания для строки состояния. Значения этого свойства такие же как и для управляющего элемента Progress Bar. Каждая строка состояния может быть разделена максимум на 16 областей (панелей). При создании строки состояния можно добавлять области, используя страницу Panels.
При помощи диалоговой панели можно добавить текст или картинку к любой области строки состояния. В процессе работы при использовании метода Add обычно сначала объявляют переменную, описывающую тип панели, а затем используют метод Add, синтаксис которого представлен ниже:

Set PanelObject = NamedfStatusBar.Panels.Add{[index], [key], [text], [style], [picture])

где все параметры являются необязательными. (В силу того, что этот метод не поддерживает именованные аргументы, необходимо использовать кавычки для указания невключаемых элементов.) Параметр index используется для нумерования области. Если этот параметр отсутствует, то по умолчанию данный элемент будет добавлен в конец списка существующих областей. (Так как можно использовать не более 16 областей, то значение параметра index должно находиться в промежутке 1-16). Параметр Key (так же, как и в любой коллекции) представляет собой еще один способ установить ссылку на элемент. Параметр text задает текст для области. Параметр style устанавливает метод вывода информации в строке состояния. Возможные значения этого параметра собраны в следующей таблице.

Константа | Описание
SbrText | (Используется по умолчанию.) Позволяет выводить произвольный текст или картинку. Выводимый текст определяется значением свойства text. Вывод картинки осуществляется при помощи метода Load Picture.
SbrCaps | Если клавиша CAPS LOOK была нажата, то слово CAPS выводится отчетливыми символами, в противном случае - слово выводится приглушенным серым цветом.
SbrNum | То же самое, но для клавиши NumLock.
SbrIns | То же самое, но для клавиши Insert.
SbrScrl | То же самое, но для клавиши ScrollLock.
SbrTime | Служит для вывода текущего времени в текущем используемом системой формате.
SbrDate | Служит для вывода текущей даты в текущем используемом системой формате.

И наконец, необязательный параметр picture задает соответствующее растровое изображение. Например, следующий фрагмент кода:

Private Sub Form Load()
Dim MyPanel As Panel
StatusBar1.Panels(1).Text. = "Default panel exits already"
Set MyPanel = StatusBarl.Panels.Add(,,, sbrTime)
Set MyPanel = StatusBarl.Panels.Add(,,"Panel demo")
Set MyPanel = StatusBarl.Panels.Add(,,,, LoadPicture("C:\VB\ICONS\TRAFFIC\TRFFC01.ICO"))
End Sub

создает строку состояния.
Заметим, что приходиться использовать переменные объекта для создания панели с соответствующими свойствами. Однако можно заменить это двумя строками кода, первым шагом создав панель.
Например:

MyPanel = StatusBarl.Panels.Add
MyPanel == StatusBarl.Panels(2).Text = "Panel demo"

Еще одним свойством, связанным со строкой состояния, является свойство AutoSize. Оно может принимать следующие значения.

Константы | Описание
SbrNoAutoSize | (Используется по умолчанию.) Запрещает автоматическую установку размера.
SbrSpring | При изменении размера родительской строки состояния автоматически высчитывается новый размер области. (Ширина области не может быть меньше, чем ширина, определяемая свойством MinWidth окна состояния).
SbrContents | При наличии свободного места размер панели соответствует ширине, необходимой для помещения надписи.

Примечание: В некоторых приложениях бывает необходимо размещать только один элемент в строке состояния. (Например, многие программы используют строку состояния для вывода пояснений при выборе какого-либо пункта меню, но при закрытии меню выводят в эту строку информацию общего назначения.) В этом случае необходимо установить для свойства Style значение Simple (константа SbrSimple используется во время выполнения приложения). После этого надо назначить необходимый для вывода текст свойству SimpleText.

Управляющий элемент Tab Strip

Элемент Tab Strip является еще одним способом создания компонентов, внешний вид которых похож на элемент управления с закладками. Однако в силу того, что он не является контейнером, его использование лишено гибкости, присущей элементу управления с закладками. По этой причине данный элемент здесь не рассматривается.

Управляющий элемент Toolbar

Элемент Toolbar дает возможность легко встраивать панели инструментов в приложения. Возможности данного средства аналогичны по своему содержанию возможностям элемента Tool Tips (данный элемент будет описан в одном из следующих разделов). Во-первых, свойство Align задает месторасположение панели инструментов. По умолчанию значение свойства Align равно 1 (Top), что помещает панель в верхнюю часть формы.
Далее, если надо поместить изображение на панель инструментов, то сначала необходимо сохранить его в элементе Image List (это действие доступно как во время проектирования, так и во время выполнения приложения). Следует учесть, что для назначения изображений во время проектирования необходимо, чтобы элемент Image List и панель инструментов были ассоциированы с одной и той же рабочей формой.
После сохранения изображений в элементе Image List необходимо в качестве значения свойства Image List использовать имя соответствующего элемента. Например, добавим панель инструментов и управляющий элемент Image List на пустую рабочую форму, оставив установленные по умолчанию имена. Теперь, если посмотреть на первую страницу из диалоговой панели Property Pages панели инструментов, то можно увидеть, что выпадающий список Image List показывает элемент Image List 1.
После того как установлено соответствие для элемента Image List, можно назначить изображение из элемента Image List любым кнопкам, используя страницу (Buttons) диалоговой панели. Это можно сделать, установив нужный номер изображения из Image List в текстовой панели Image. Например, в примере справа второй колонке назначается первое изображение из элемента Image List.

Пример: работа с панелью инструментов в процессе выполнения

Управляющий элемент Toolbar содержит коллекцию из кнопок. Во время выполнения следует использовать методы Add и Remove для добавления и удаления кнопок из панели. Естественно, каждой кнопке соответствует свое событие Button Click, позволяющее производить необходимые действия при нажатии пользователем на кнопку.
Обычно для использования метода Add необходимо объявить переменную типа Button. Синтаксис метола Add может быть записан следующим образом:

Set ButtonObject = NameOfToolBar.Button.Add ([index], [key], [caption], [style], [image])

где все параметры являются необязательными. (В силу того, что этот метод не поддерживает именованные аргументы, необходимо использовать кавычки для указания включаемых элементов.)
Параметр index указывает место расположения кнопки в коллекции. Если этот параметр отсутствует, кнопка автоматически помещается в конец списка. Параметр Key (так же, как и в любой коллекции) представляет собой еще один способ создать ссылку на кнопку. Параметр Caption служит для задания выводимого текста для объекта Button. Свойство style задает способ вывода кнопки. Возможные значения этого свойства представлены в следующей таблице.

Константа | Описание
Tbr Default | (Используется по умолчанию.) Указывает на то, что кнопка является обычной нажимаемой кнопкой (push button).
Tbr Check | Кнопка является кнопкой проверки (check button).
Tbr Button Group | Кнопка принадлежит к группе. Это означает, что данная кнопка не может быть нажата до тех пор, пока нажата другая кнопка группы. В одно и то же время только одна кнопка из группы может находиться в нажатом состоянии.
Tbr Separator | Кнопка представляет собой обычный разделитель, имеющий стандартную ширину в 8 пикселов.
Tbr Placeholter | Кнопка представляет собой разделитель с переменной шириной

И, наконец, параметр image задает ключ или индекс для ассоциации с изображением, предварительно сохраненным в элементе Image List.
Так же, как и в случае с объектом Panel, можно сначала добавить кнопки, а затем описать их свойства. Например, если надо на этапе проектирования добавить только одну кнопку с надписью "First button", следует воспользоваться следующим фрагментом кода:

Tooibarl.Buttons.Add
Toolbarl.Buttons(2).Caption = "Second button"
Tooibarl.Buttons.Add
Toolbarl.Buttons(3).Caption = "Third button"

Примечание: Свойство AllowCustomize позволяет пользователю управлять такими настройками панели инструментов, как выравнивание колонок, скрытие и вывод панели и другими. Соответствующее меню вызывается при двойном щелчке мыши по панели инструментов. Можно запретить подобные действия путем установки значения свойства AllowCustomize в False. Для получения более подробной информации следует обратиться к справочной системе.

Ярлычки ToolTips

ToolTips представляют собой небольшие ярлычки, использующиеся для краткого описания инструмента. Такой ярлычок появляется примерно через 1 секунду после того, как указатель мыши помещается на инструмент, и пропадает после сдвига указателя мыши. Для добавления такой возможности в панель инструментов необходимо:

  • Установить свойство ShowTips в True.
  • Назначить свойству ToolTipText тот текст, который будет выводиться в ярлычке.
Управляющий элемент TreeView (просмотр дерева)

Управляющий элемент TreeView, судя по его названию, позволяет создавать дерево, похожее на используемое в Windows Explorer. Отдельные объекты, составляющие дерево, называются узлами (nodes), и, соответственно, управляющий элемент TreeView содержит коллекцию Nodes. Верхний узел указывается при помощи свойства Root. За дополнительной информацией об управляющем элементе TreeView мы отправляем читателя к справочной системе.


Справочная система

    Создание тематических разделов справочной системы
    Обратная сторона тематических разделов
    Создание контекстно-зависимой справочной системы
    Создание и компиляция файла справки
    Доступ к справочной системе Windows
    Справочная система What's This
Любое профессиональное приложение для операционной системы Windows должно содержать справочную систему стандартного вида, позволяющую пользователю получить дополнительную информацию по любому вопросу. Если внешний вид и принципы работы справочной системы будут отличаться от принятых в среде Windows, то это означает, что пользователь будет вынужден затратить дополнительное время на обучение. Обе версии Visual Basic (Professional и Enterprise) поставляются с компилятором файловой системы Windows, который позволяет легко встраивать справочную систему в приложения.

Примечание: Полное рассмотрение всех средств справочной системы Windows заняло бы слишком мною места, поэтому в данной главе рассказывается в основном только о взаимодействии справочной системы Windows с приложением. Справочное руководство Visual Basic Books Online для обоих версий Professional и Enterprise включает полную информацию, необходимую для построения справочной системы.

Грубо говоря, метод использования компилятора справочной системы достаточно прост: создается текстовый файл, содержащий соответствующие коды формата, которые компилятор преобразует во внутренние ссылки, выпадающие окна и т.д. Текстовый файл должен создаваться в текстовом редакторе, поддерживающем формат RTF (rich text format) фирмы Microsoft. Большинство современных текстовых редакторов поддерживают этот формат.

Совет: Некоторые инструменты независимых поставщиков существенно облегчают процесс создания справочной системы. Один из них, который постоянно используется автором в его работе, это "RoboHelp" фирмы BlueSky Software. Если нужно создать сложную справочную систему, то пакет RoboHelp окажет в этом неоценимую помощь. Для создания более простых справочных систем можно воспользоваться продуктами, распространяемыми по принципу shareware. Список подобных систем приведен в WINDSK forum системы CompuServe. Инструменты WHAT (Windows Help Authoring Templates) и WHPE (Windows Help Project Editor), которые можно найти на компакт-диске Microsoft Software Developers, также отличается простотой в использовании.

Система помощи Windows также включает в себя меню с дополнительными средствами. (При работе с Windows 95 может понадобиться создать многоуровневую систему меню.) Подобное меню содержит такие пункты, как Search, который выводит список ключевых слов и позволяет пользователю перемещаться по списку, вводя первые буквы интересующего его слова. Далее, при помощи ключевого слова можно сразу перейти к нужному разделу. Также различные части приложения (как и в Visual Basic) могут содержать контекстно-зависимую подсказку. Этот способ получения подсказки доступен при нажатии клавиши F 1 (или при помощи кнопки WhatsThisHelp в среде Windows 95) и выдает информацию о том инструменте среды, который задействован в настоящий момент.

Создание тематических разделов справочной системы

Базовой операцией при создании справочной системы является определение набора тематических разделов помощи. В идеале это должен быть короткий список (не более одного - двух экранов) тем помощи, объединенных общим принципом классификации. Обычно тематические разделы организуются в виде иерархической системы. Это позволяет пользователю легко перемещаться от общих тем к более специфическим. Тем не менее каждый раздел должен содержать достаточное количество ссылок для перехода к другим темам без необходимости подниматься на самый верхний уровень иерархии.
Хотя определение достаточного набора тематических разделов и бывает желательным, выполнить это требование удается не всегда. В этих случаях может возникнуть необходимость добавить всплывающее или дополнительное окно к справочной системе.

  • Всплывающее окно может содержать описания используемых терминов. Время от времени такое окно может использоваться для вывода дополнительной информации. Те места справочной системы, для которых доступно всплывающее окно, изображаются как текст, подчеркнутый прерывистой линией.
  • Дополнительное окно можно использовать для получения дополнительной ииформагщн, не покидая текущий раздел помощи, однако, в отличие от всплывающего окна, оно содержит собственное название (и. если это необходимо, полосы прокрутки).

Обратная сторона тематических разделов

Совет: Если найти файл Iconwrks.rtfB директории VBVHC, то он сможет стать отличным примером файла RTF. Этот файл содержит простейшую справочную систему для приложения Iconwrks. (Исполняемый файл этого приложения называется Iconwrks.ехе и находится в том же директории.) Далее, сравнивая содержимое RTF-файла с экраном помощи Iconwrks.ехе, можно увидеть, как управляющие символы формата RTF превращаются в дополнительные возможности справочной системы. Возможно, это даже поможет достаточно хорошо изучить значение управляющих символов. Однако более предпочтительным является использование программных инструментов независимых поставщиков.

Практически нет людей (за редким исключением), которые для создания справочной системы не использовали бы простые пакеты, основанные на макроопределениях, для расстановки управляющих символов RTF. Используя подобный пакет можно смело забыть о том, что обозначет тот или иной идентификатор формата RTF, щелкнув вместо этого несколько раз мышкой. В качестве примера можно привести внешний вид панели инструментов пакета RoboHelp, дающей доступ к некоторым из его возможностей.
В любом случае, как об этом уже говорилось ранее, необходим текстовый редактор, поддерживающий формат RTF (rich text format) для сохранения файла, который будет впоследствии перетранслирован компилятором в справочную систему. (Это связано с тем, что большинство доступных инструментов типа RoboHelp доступны в качестве дополнительных модулей к редактору Microsoft Word, который, без сомнения, полностью поддерживает спецификацию RTF-формата.)
Каждый тематический раздел справочной системы должен быть отделен от другого символом "разрыв страницы" (hard page break). (Этот символ надо вводить самостоятельно, в отличие от других символов разрыва, которые тестовый редактор проставляет автоматически. Символ "разрыв страницы" можно ввести путем нажатия комбинации клавиш CTRL+ENTER в семействе редакторов фирмы Microsoft.) Каждый тематический раздел должен содержать идентификатор контекстной строки, название, а также список доступных ключевых слов. Контекстная строка используется для перехода к разделу, следовательно, каждый раздел должен иметь уникальную строку. Ключевые слова используются для организации возможностей поиска в справочной системе. Естественно, несколько тематических разделов могу т иметь одинаковые ключевые слова.
Привязка по ключевым словам, использующая идентификаторы, является основой для построения справочной системы. Например, контекстная строка выделяется при помощи идентификатора привязки "#". Контекстная строка может использовать символы от А до Z, числа, точки и символы подчеркивания. Максимальная длина строки ограничена 255 символами.
Название тематического раздела выделяется при помощи идентификатора привязки "$" (знак доллара). Название может содержать до 127 символов из стандартного набора системы Windows. И, наконец, ключевые слова выделяются при помощи идентификатора привязки "К" (в верхнем регистре).
(Ключевые слова не являются обязательными, однако можно назначить тематическому разделу более чем одно ключевое слово, перечислив их в списке, разделенном знаками ";". Ключевые слова могут содержать произвольные символы из стандартного набора символов системы Windows. Максимальная длина ключевого слова не должна превышать 255 символов.)
Далее заметим, что можно организовать ссылку на файл с растровым изображением.bmp, указав его имя в фигурных скобках. При этом растровое изображение будет помещено в соответствующее место экрана помощи. Можно также поместить растровое изображение на страницу со списком тематических разделов. Если для создания справочной системы используется текстовый редактор Word for Windows, то того же результата можно достичь, воспользовавшись функцией Insert Picture. (Для вставки изображения большого размера следует воспользоваться методом вставки "By Reference".) Графическая часть справочной системы также может использоваться в качестве точек перехода к другим темам. Если не используется редактор Word for Windows, то следует создать ссылку на растровое изображение в файле тематических разделов.
Обратим внимание, что некоторые фрагменты текста (например, "Status Area") подчеркнуты двойной линией. Такие фрагменты используются в качестве точек перехода к другим разделам. Вслед за каждым таким фрагментом следует текст, указывающий место перехода (этот текст не выводится на экран в справочной системе). Общий синтаксис подобного механизма ссылок может быть записан как:

JumpString CONTEXT_SroWG

где контекстная строка, указывающая место ссылки, должна быть отформатирована как скрытый текст без пробелов. Точно так же можно разместить скрытую контекстную строку после фрагмента текста, подчеркнутого одинарной линией, для указания местоположения всплывающей справки.

Примечание: Способ указания фрагмента текста как скрытого зависит от используемого текстового редактора.

Создание контекстно-зависимой справочной системы.

Свойство, ссылающееся на файл справочной системы, является базовым свойством любого объекта типа Арр (application). Для того чтобы связать это свойство с соответствующим слайдом, необходимо выполнить следующее:

  • Выбрать пункт Tools\0ptions. а затем щелкнуть мышкой по странице Project.
  • Ввести имя скомпилированного файла справочной системы в текстовой панели HelpFile. (Если файл находится в отличном от базового для приложения директории, то следует указать полный путь.)
Далее, предположим, что уже создан файл справочной системы, который будет откомпилирован и добавлен к приложению. Прежде чем использовать возможности контекстно-зависимой справочной системы, необходимо выполнить ряд действий.
  • Ключевым моментом для программиста при создании контекстно-зависимой справочной системы является установка соответствующих значений HelpContextJD для рабочей формы или элементов управления. Все управляющие элементы, за исключением теневых линий, изображений и элементов управления метками, имеют в своем составе свойство HelpContextID.
  • Ключевым моментом для создателя справочной системы является проверка того. чтобы значения HelpContextID соответствовали различным элементам, для которых необходима контекстно-зависимая подсказка.
Например, если программист установит значение 5 для свойства HelpContextID меню File, то создатель справочной системы должен об этом знать. После этого он (или она) должен сообщить компилятору, как следует соотносить значение свойства HelpContextID с соответствующим тематическим разделом (более подробно эта операция описана в следующем разделе). В нашем случае тематический раздел FILE MENU должен иметь контекстный номер 5.

Совет: Если требуется назначить элементу управления изображением контекстно-зависимую подсказку (это невозможно сделать обычным способом по причине отсутствия свойства HelpContextID у такого элемента), следует заменить подобный элемент панелью с изображением.

Создание и компиляция файла справки

Файл проекта справочной системы содержит всю информацию, необходимую для корректной работы компилятора. Кроме этого, он содержит таблицу (карчу) соответствий для установления связей между контекстной строкой и контекстным номером ID, которая будет использоваться в проекте (еще раз напомним, что должно существовать строгое соответствие между информацией, содержащейся в файле проекта справочной системы, и кодом приложения).
Файл проекта справочной системы представляет собой обычный текст в формате ASCII. В качестве расширения для всех подобных файлов проекта используется.hpj. Файл проекта справочной системы содержит список всех файлов с тематическими разделами, а также может содержать растровое изображение или таблицу соответствия между контекстными строками и контекстными номерами ID. Можно также назначить две контекстные строки одному и тому же тематическому разделу, модифицировав файл проекта.
Для установления соответствий между контекстным номером ID и определенным тематическим разделом необходимо указать имя раздела после ключевого слова [MAP], после чего через пробел (т.е. после нажатия клавиши SPACE или TAB) следует указать контекстный номер справки. Например, секция [МАР]-файла проекта справочной системы может содержать следующую информацию:

[MAP]
FILE_MENU 5 ;5 is the context number
;Comments follow semicolons
EDIT_MENU 10 ;10 is context number
VIEW_MENU 15 ;15 is context number

Теперь, если вызвать инструмент создания справочной системы со значением HelpContextID, установленным в 5, получим экран помощи для тематического раздела FILE_MENU; установив HelpContextID в 10 и вызвав инструмент создания справочной системы, получим экран EDIT MENU и т.д.
После создания файла проекта справочной системы остается только вызвать компилятор системы помощи. (Заметим, что директорий, в котором находится компилятор, должен содержаться в системной переменной PATH, в противном случае необходимо будет указать полный путь.) Например, в системе Windws 95 можно использовать для этого диалоговую панель Run, доступную из меню Start, в которой нужно написать нечто подобное:

C:\VB\HC\HC Named fHelpPi-ojectFile

если компилятор находится в назначенном по умолчанию директории (для версии Professional). Или же можно написать:

НС NameOfHelpProjectFile

в IUM случае, когда директорий, содержащий компилятор, содержится в переменной PATH.
Компилятор справочной системы имеет большое количество возможностей, не рассматриваемых здесь. Например, можно компилировать только определенные части файла или управлять размерами и видом его окон. Кроме этого, можно использовать всю мощь макроопределений, написанных фирмой Microsoft, для расширения возможностей компилятора справочной системы. Эта и другая информация содержатся в Visual Basic Books Online. Также можно почерпнуть большое количество информации, внимательно изучив содержание файла Iconwrks.rtf.

Доступ к справочной системе Windows

Для доступа к справочной системе можно использовать метод ShowHelp обычной диалоговой панели. Далее приведена последовательность действий, которые необходимо для этого выполнить:

  • Добавитъ ооычныч аналоговый управляющий элемент в то место формы, где надо использовать средство справочной системы Windows.
  • Установить соответствующие значения для свойств HelpFile u HelpCommand управляющего элемента CommonDialog.

    • Например, если надо использовать метод ShowHelp для получения контекстно-зависимой подсказки:
  • Назначить свойству HelpFile имя скомпилированного файла справки.
  • Установить для свойства HelpCommand -значение cdlHelpContext.
  • Установить соответствующее значение контекстного номера ID для свойства HelpContext.
  • И. наконец, можно вызвать справочную систему при номоиш метода ShowHeIn
Если необходимо вызвать справочную систему самой системы Windows (например, для получения доступа к экрану Windows "Help on Help"), необходимо вызвать API - функцию WinHelp. Для этого необходимо добавить в приложение соответствующий фрагмент кода. Для 32-битной версии справочной системы это может быть такой фрагмент:

Declare Function WinHeip Lib "user32" Alias "WinHelpA" (ByVal hHnd As Long, ByVal IpHclpFu
As String, ByVal wCommand As Long, ByVal dwData As Long) As Long

Параметр hWnd ("window's handle) является параметром свойства hWnd рабочей формы. Параметр IpHelpF указывает путь к файлу справки (этот файл находится в директории, отличном от директория .ехе-файлов). Мог понадобиться следующие константы, используемые параметром wCommand:

Global Const HELP CONTEXT = &H1 'Display topic by id number
Global Const HELP QUIT -- &H2 'Terminate help
Global Const HELP INDEX = &H3 'Display index
Global Const HELP HELPONHELP = &H4 'Display help on using help
Global Const HELP SETINDEX = &H5 'Set an alternate Index
Global Const HELP KEY = &H101 'Display topic for keyword in Data
Global Const HELP MULTIKEY = &H201 'Look up keyword in alternate table

Например, для того, чтобы вызвать стандартную справочную систему "Help on Help", необходимо испольвать следующую процедуру:

Public Sub DisplayHelp On Help(X As Form)
Dim Foo As Long ' dummy variable
Foo = WinHeip(X.hWnd,"C:\WINDOWS\HELP\WINDOWS.HLP",
HELP HELPONHELP,CLng(O))
End Sub

После этого любая строка вида

DisplayHelp_On_Help_Me

будет вызывать стандартную справочную систему Windows.

Примечание: После присвоения параметру hCommand значения HELP_CONTEXT следует установить для параметра dwData соответствующий номер ID контекстной строки.

Если для вызова справочной системы используется WinHeip API, то для того чтобы закрыть дополнительные окна справки, вам нужно снова вызвать функцию WinHeip API (т.к. справочная система Windows является отдельным приложением). Это можно сделать следующим образом (обычно для этого используется событие Form Unload). Переменные Foo и Bar должны иметь правильный тип, хотя реально они не несут никакой полезной нагрузки.

Sub Form_Unload(Cancel As Integer)
Dim Foo As Integer, Bar As String 'dummy variables
Foo = WinHeip(Me.hWnd, Bar, Help_Quit, CLng(O))
End Sub

Справочная система What's This

Операционная система Windows 95 имеет новый тип контекстно-зависимой подсказки, который вызывается щелчком мышки над изображением небольшого вопросительного знака, расположенного на заголовке экранной формы. При этом указатель мыши изменяет свой внешний вид на вопросительный знак, после чего пользователь может щелкнуть мышкой по любому объекту. В результате этих действий на экран выводится контекстно-зависимая информация о выбранном объекте. Точно так же, как и в других вариантах контекстно-зависимой подсказки, для корректной работы подсказки "What's This" надо установить соответствие между приложением и секцией MAP в файле проекта справочной системы.

Примечание: Для использования этого варианта контекстно-зависимой подсказки необходимо установить значение True для свойства WhatsThisHelp. Кроме этого, надо установить для свойства WhatsThisHelpID значение соответствующего контекстного номера ID.

Для встраивания контекстно-зависимой подсказки What's This в приложение доступны три способа. Но в любом случае свойство WhatsThisHelp должно быть установлено в True.

  • Создать кнопку What's This на заголовке вашей формы, установив свойство WhatsThisButton в True. Теперь, если пользователь нажмет эту кнопку, указатель мышки изменит свой внешний вид на стрелку с вопросительным знаком, а после выбора объекта система Windows автоматически вызовет тематический раздел, чей контекстный номер ID соответствует свойству WhatsThisHelpID выбранного элемента.
  • Вызвать метод WhatsThisMode. Синтаксис метода следующий:
FormObject.ShowWhatThis
  • Вызвать метод ShowWhatsThis управляющего элемента. Точно так же пользователю будет выведен экран помощи, чей контекстный номер ID соответствует значению свойства WhatsThisHelpID управляющего элемента.
И, наконец, можно вызывать систему помощи напрямую из приложения при помощи метода WhatsThisMode. Эффект вызова при этом такой же, как и в случае, когда пользователь щелкает по кнопке What's This на полосе заголовка. (Подобный метод вызова может использоваться для запуска приложения в режиме обучения, когда пользователь будет видеть описание каждого выбираемого им пункта меню.)


Создание дистрибутива приложения:мастер установки.

  • Введение.
  • Создание исполняемого файла.
  • Мастер установки.
  • Использование мастера.
  • Создание дистрибутива для проекта калькулятора.
  • Создание дистрибутива элемента управления.

Введение.

Все что пишется в Visual Basic должно выполняться вне среды разработчика Visual Basic, требут файла Msvbvm.dll. Этот файл содержит все подпрограммы поддержки и существенные элементы управления, которые необходимы Visual Basic, чтобы выполнять все что угодно. Это довольно большой файл (1,3 М). Но, к счастью, не имеет значения, сколько приложений Visual Basic выполняет пользователь в одно и тоже время, все равно в памяти необходима только одна копия этого файла.
Для каждого пользовательского элемента управления в приложении и для того, что является частью вашего элемента управления, необходим файл пользовательского элемента управления. Эти файлы относительно малы- часто менне 30К каждый. В результате то, что оказывается необходимым для инсталляции исполняемых файлов не находится на машине, куда производится инсталляция, то даже простейший исполняемый файл Visual Basic, даже подвергнутый сжатию при помощи мастера установки потребует 2 или более дисков.

Примечание: Разработчику действительно нужно убедиться, что пользователи его проекта или элемента управления не имеют файла Msvbvm50.dll и других файлов поддержки, т.к. многие пользователи Windows 95 и Windows NT уже будут иметь на своих машинах эти файлы. Например, большинство пользоваптелей Internet Explorer уже будут их иметь, и можно предположить, что вскоре Microsoft будет просто инсталлировать этот файл вместе с инсталляцией файлов операционной системы. Это сильно уменьшит дисковое пространство, необходимое джля создания дистрибутивов проектов Visual Basic.

И, наконец, когда выполняет исполняемый файл, который содержит пользовательский элемент управления, .ocx-файл должен находиться в системном каталоге или в одном каталоге с .exe-файлом. Мастер установки автоматически размещает файлы в соотвествующем месте. (Если соотвествующий .ocx файлне может быть найден, то виртуальная машина Visual Basic выведет сообщение об ошибке и завершит выполнение.)

Создание исполняемого файла

Тот, кто перед покупкой своей копии Visual Basic читал какую либо рекламу о нем, возможно, слышал, что Visual Basic 5-первая версия Visual Basic, которая позволяет создавать действительно исполняемые файлы. Кто-то возможно, удивится, что же это значит. Прежде всего, предыдущие версии Visual Basic всегда транслировали код Visual Basic в некий р-код. Это промежуточный язык, который отличается от машинного кода микропроцессора Intel. А затем полученный р-код построчно интерпретировался в машинный код Intel. Но это не эффективно. Например, для циклов явно легче выполнить перобразование в машинный код один раз и не преобразовывать код каждый раз при выполнении тела цикла.

Рисунок 23-1.Диалоговое окно Make Project. .

Только версии Professional и Enterprise имеют возможность создавать действительно исполняемые файлы. Этого можно достичь выбором пунктов File|Make Exe или File|Make осх и последующим нажатием кнопки Options в диалоговой панели Make Project, показанной на рис. 23-1.
Рисунок 23-2.Cтраница Make.

То, что будет видно, показано на рис. 23-2. На этой странице диалоговой панели можно добавить информацию о версии элемента управления и о защите прав. Здесь также устанавливается значок для проекта, который пользователь увидит представления исполняемого файла, и информацию командной строки, если такая предусматривается.
Если есть версия Professional или Enterprise, щелкнем на закладке Compile. Покажется экран, приведенный на рис. 23-3. Пройдемся поочередно по всем опциям в этой важной диалоговой панели.

Compile to P-Code. Позволяет скомпилировать проект в р-код. Хотя он выполняется медленнее, но необходимое дисковое пространство может быть значительно меньшим по сравнению с кодом, скомпилированным в машинный код микропроцессора.

Compile to Native Code. Позволяет скомпилировать проект, используя родной код с оптимизациями того, что выбирается посредством оставшихся кнопок. Они описаны в следующей таблице.

Кнопка | Описание
Optimize for Fast Code | Максимально увеличивает скорость выполнения.
Optimize for small Size | Минимизирует размер. Вероятно, этим значительного преимущества перед простым использованием р-кода достигнуто не будет.
No Optimize | Компилирует без оптимизации.
Favor Pentium Pro | Оптимизирует код таким образом, что он лучше всего работает на процессоре Pentium Pro. Код, сгенерированный с этой опцией, будет работать и с более ранними процессорами, но на процессорах 486 и Pentium (даже если они используют MMX), будет работать медленнее, чем тот код, но сгенерированный без этой опции.
Create symbolic Debug Info | Используется, когда надо запустить свой VB-код на отдельном отладчике, такой как один из отладчиков фирмы NuMega или тех, что поставляются с компонентами Microsoft DevStudio.

Примечание: Программа, которая выполняет большое количество циклов, такая, как, например, стандартные тесты, покажет поразительное ускорение - часто даже десятикратное по сравнению с программами на Visual Basic 4. На практике, однако, ускорение, вероятно, будет значительно меньше (часто менее 50 процентов, иногда даже около 20 процентов).

Advanced Optimizations. Нажатие этой кнопки выведет на экран диалоговую панель Advanced Optimizations, показанную справа. Не рекомендуется изменять какие-либо опции в этой панели, установленные по умолчанию - они только для больших знатоков VB.

Мастер установки

Мастер установки - это действительно отличный инструмент, который позволяет создавать дистрибутивы приложений и элементов управления практически без усилий. Это действительно автономная программа, которая называется Application Setup Wizard. Можно запустить ее, к примеру, посредством подменю Visual Basic 5 в меню Start. (Предположим, что используется стандартная установка, тогда программа мастера установки будет называться VB\setupkit\kitfil32\Setupwiz.exe).

Примечание: Перед тем как использовать мастер установки, следует по крайней мере хотя бы раз откомпилировать код, а также сохранить проектный файл.

Когда запускается мастер установки, мы попадаем на начальный экран мастера установки, который выглядит, как рис. 23-4.
Разберемся с пунктами в основной панели. Прежде всего, здесь есть кнопка помощи Help, которая выведет файл справки для мастера. Кнопка выхода Exit в верхнем правом углу панели закрывает мастер установки. Кнопка движения вперед Next перемещает к следующему шагу мастера, а кнопка Back возвращает к предыдущему. Щелкнем на кнопке Next, чтобы перейти к первому реальному экрану мастера.
Далее приведены описания новых пунктов этого экрана.

Project File Text Box. Файлом проекта является имя файла с расширением .vbp. Можно щелкнуть на кнопке Browse, для того чтобы открыть стандартную диалоговую панель, которая позволяет осуществить поиск файла .vbp или .ctl (для элемента управления).

Rebuild the Protect. Файлы, дистрибутивы которых создаются, являются автономно выполняемыми. Если необходимо создать, отдельный продукт заново, надо пометить эту опцию. Мастер будет автоматически создавать новый .ехе-файл, или .осх —если .ехе не найден.

Options. Возможные опции описаны в следующей таблице.

Опция | Описание
Creat a setup Programm | Сообщает о том, что требуется создать обычную программу установки на гибких дисках. Если также установить опции Generate Dependency File, то матер установки создаст отчет, содержащий информацию о файлах, которые использует приложение.
Creat Internet Download Setup | Эта опция используется только для элементов управления ActiveX и создает все необходимые файлы таким образом, что кто-либо по сети Internet сможет использовать этот код.
Generate Dependency File Only | Установка этой опции только создает отчет, содержащий информацию о файлах, которые использует приложение.

Использование мастера

Для наглядности предположим, что надо создать дистрибутивы двух различных приложении:

  • калькулятора, поставляемого с Visual Basic;
  • элемента управления числового поля ввода NumericTextBox.
Создание дистрибутива для проекта калькулятора
Рисунок 23-3. Страница Compile.

Начнем с простого проекта калькулятора, который можно найти в подкаталоге \samples\PGuide\calc каталога VB. Сначала придется скомпилировать этот проект, так как он не поставляется в исполняемой форме. Итак, загрузим VB и затем скомпилируем проект Calc.vbp. После этого введем имя и путь к файлу Calc.vbp и щелкнем на кнопке Next (которая сейчас доступна). После некоторого промежутка времени, необходимого для обработки файлов, перейдем к экрану шага 3 мастера установки.
Рисунок 23-4. Начальный экран мастера установки.
В этом экране надо сообщить мастеру, где сохранять дистрибутив. Например, выберем гибкие диски. Для этого щелкнем на опции Floppy Disk. Щелкнем по кнопке Next и попадем на экран, где сообщим мастеру о типе используемых дискет.
Щелкнем по кнопке Next. Получающийся экран, используется только для серверов ActiveX - это более сложная тема, которую мы не рассматривали в этой книге. Щелкнем по кнопке Next, чтобы пропустить этот экран.
После небольшой задержки появится экран File Summary. Если выбрать любой из этих файлов и щелкнуть по кнопке File Details, мастер выведет на экран диалоговую панель. Она содержит подробную информацию о выбранном файле. Если щелкнуть на Summary Details в экране File Summary, увидим диалоговую панель с информацией.
Щелкнем по кнопке Next, при нахождении в экране FileSummary и перейдем к экрану Finished.
Если сейчас щелкнуть по кнопке Finished, мастер начнет выводить информационные панели с сообщениями о выполняемых им действиях (сжатие файлов, вычисление файлов, которые необходимо скопировать, и так далее). Когда это будет выполнено, на экране появится информационная панель, похожая на ту, что приведена справа. Она попросит вставить первый (из двух в данном случае) дисков.
А сейчас пользователи могут инсталлировать приложение, просто помещая первый диск дистрибутива в дисковод и запуская из Windows программу Setup.exe, содержащуюся на нем.
Программа инсталляции, созданная мастером установки, соответствует всем стандартам Windows. Пользователи могут изменить каталог установки; они могут наблюдать за ходом выполнения процесса инсталляции по диаграмме; и им также будет сообщаться, когда вставлять очередной диск. (К тому же, посредством функции AddRemove в Windows95/Windows NT возможна деинсталляция приложения.)

Создание дистрибутива элемента управления

Создание дистрибутива элемента управления в мастере установки для использования на отдельной системе по существу аналогично созданию дистрибутива приложения. Так что в этом разделе сосредоточимся на использовании мастера для создания файла установки Internet Download Setup таким образом, чтобы элемент управления мог быть использован в Internet Explorer.
Запустим мастер. Необходимо заметить, что мы хотим создать дистрибутив элемента управления таким образом, чтобы он мог быть загружен из Internet или в интрасеть. После принятия решения сделать Interenet-установку щелкнем по кнопке Next и перейдем к следующему экрану.
Щелкнем по кнопке Next. Появится следующий экран. Microsoft предоставляет опцию, которая позволяет пользователю загружать только определенные стандартные файлы, как, например, огромный файл виртуальной машины Visual Basic, необходимый для элемента управления, прямо с сайта Microsoft при первом использовании элемента управления в Internet Explorer. Это действительно очень полезная опция, так как если пользователи уже имеют какие-либо из необходимых файлов на своей машине, то Internet Explorer достаточно разумен для того, чтобы не загружать их заново.
В основном это все. Оставшиеся экраны мастера подобны тем, что мы уже обсуждали.


Visual_Basic

Содержание:

    Введение
    Переменные
    Совместное использование значений процедурами
    Строки
    Объект Printer
    Функция DoEvents
    Обработка ошибок
    Заключительные штрихи интерфейса
    Справочная система
    Создание дистибутива приложений: мастер установки


Untitled Document

Учимся программировать на языке C++
Часть 1. Изучение основ
    • Урок 1. Создание вашей первой программы
    • Урок 2. Более внимательный взгляд на C++
    • Урок 3. Вывод сообщений на экран
    • Урок 4. Программы хранят информацию в переменных
    • Урок 5. Выполнение простых операций
    • Урок 6. Чтение ввода с клавиатуры
    • Урок 7. Программа принимает решение
    • Урок 8. Повторение одного или нескольких операторов
Часть 2. Создание программ с пом. функций

    • Урок 9. Знакомство с функциями
    • Урок 10. Изменение значений параметров
    • Урок 11. Преимущества использования библиотеки этапа выполнения
    • Урок 12. Локальные переменные и область видимости
    • Урок 13. Перегрузка функций
    • Урок 14. Использование ссылок в C++
    • Урок 15. Значения параметров по умолчанию
Часть 3. Хранение информации с помощью массивов и структур

    • Урок 16. Хранение значений в массивах
    • Урок 17. Символьные строки
    • Урок 18. Хранение связанной информации в структурах
    • Урок 19. Объединения
    • Урок 20. Указатели
    Часть 4. Использование классов C++

      • Урок 21. Знакомство с классами C++
      • Урок 22. Частные и общие данные
      • Урок 23. Конструктор и деструкто
      • Урок 24. Перегрузка операторов
      • Урок 25. Статические функции и элементы данных
Часть 5. Наследование и шаблоны

    • Урок 26. Наследование
    • Урок 27. Множественное наследование
    • Урок 28. Частные элементы и друзья
    • Урок 29. Использование шаблонов функций
    • Урок 30. Использование шаблонов классов
Часть 6. Расширенные возможности C++

    • Урок 31. Использование свободной памяти в C++
    • Урок 32. Управление свободной памятью
    • Урок 33. Дополнительные возможности cin и cout
    • Урок 34. Файловые операции В/В в C++
    • Урок 35. Встроенные функции и ассемблерные коды
    • Урок 36. Использование аргументов командной строки
    • Урок 37. Использование констант и макрокоманд
    • Урок 38. Полиморфизм
    • Урок 39. Использование исключительных ситуаций C++ для обработки ошибок


Untitled Document

Урок 1

Создание вашей первой программы

Все вы использовали компьютерные программы, такие как текстовый процессор, электронные таблицы и даже Microsoft Windows 95. Компьютерные программы, или программное обеспечение, представляют собой файлы, содержащие инструкции, которые указывают компьютеру, что следует делать. Если вы работаете в среде MS-DOS или Windows, то, например, файлы с расширениями ЕХЕ и СОМ содержат команды, которые компьютер может выполнять. Другими словами, файлы содержат специальные инструкции, выполняемые компьютером, обычно одну за другой, для решения определенной задачи. При создании программы вы указываете инструкции, которые компьютер должен выполнить. Из этого урока вы узнаете, как указать такие инструкции с помощью операторов C++. К концу данного урока вы освоите следующие основные концепции:

• При создании программы используйте текстовый редактор, чтобы ввести операторы C++ в исходный файл программы.

• Для преобразования операторов программы C++ в выполнимую программу, в единицы и нули, которые понимает компьютер, используйте специальную программу — компилятор C++.

• Для изменения или исправления программы используйте текстовый редактор.

• При нарушении одного (или более) правил программирования на C++ компилятор выдаст на экран сообщения о синтаксических ошибках. Вам следует отредактировать программу, чтобы исправить ошибки, а затем запустить компилятор снова.

Программирование представляет собой процесс определения последовательности инструкций, которые должен выполнить компьютер для решения определенной задачи. Для указания этих инструкций вы используете язык программирования, например C++. С помощью текстового редактора вы вносите программные операторы в исходный файл. Далее вы используете специальную программу — компилятор — для преобразования операторов из формата, который вы можете читать и понимать, в единицы и нули, которые понимает компьютер.

Лучший способ понять процесс создания и компиляции программы — построить простую программу на C++. Давайте этим займемся!

СОЗДАНИЕ ПРОСТОЙ ПРОГРАММЫ

Как и следовало ожидать, ваша первая программа на C++ называется FIRST.СРР. При создании программ на C++ используйте расширение СРР, чтобы другие могли понять, что этот файл содержит программу на C++. Когда вы позже запустите эту программу, она выведет на экран дисплея сообщение Учимся программировать на языке C++/Следующий пример вывода показывает подсказку командной строки (в данном примере С:\>), вводимую вами командную строку (имя программы FIRST, за которым следует ENTER) и вывод программы на экран:

С:\> FIRST <ENTER>

Учимся программировать на языке C++!

Как известно, при программировании вы можете работать в среде, основанной на командной строке, например MS-DOS или UNIX, или в среде типа Windows. Для упрощения вывода в данной книге подразумевается, что вы работаете из командной строки. В этом случае для выполнения программы FIRST.EXE вы должны ввести имя программы FIRST в ответ на системную подсказку и затем нажать ENTER.

Для начала вам следует использовать текстовый редактор, например EDIT (поставляется с MS-DOS), для создания файла, который содержит операторы программы и называется исходным файлом. Не используйте текстовый процессор, такой как Word или WordPerfect, для создания исходного файла программы. Как известно, текстовые процессоры позволяют вам создавать форматированные документы, которые могут содержать полужирный текст, выровненные поля, а также другие особенности. Чтобы отформатировать документы таким образом, текстовый процессор вставляет специальные (скрытые) символы внутрь документа. Такие символы могут включать или выключать курсив или выбирать определенную ширину полей. Несмотря на то что такие специальные символы имеют смысл для текстового процессора, C++ их не поймет и эти символы приведут к ошибкам.

С помощью текстового редактора введите следующие операторы программы C++ (точно так, как они изображены, используя верхний и нижний регистры), как показано ниже:

#include <iostream.h>

void main(void)

{
cout << "Учимся программировать на языке C++!";
}

Не беспокойтесь, если операторы C++ не имеют для вас смысла. Вы узнаете назначение каждого из них из урока 2. А пока обратите особое внимание на ваш ввод. Удостоверьтесь, например, что вы ввели верное количество кавычек, точек с запятой и скобок. Еще раз более внимательно проверьте операторы своей программы. Если они верны, сохраните операторы в файле FIRST. СРР

Что означает имя?

При создании программы на C++ вы вносите операторы программы в исходный файл. Применяйте расширение СРР, чтобы другие программисты могли понять, что данный файл содержит программу на C++. Далее используйте имя файла, которое указывает назначение программы. Например, если вы создаете финансовую программу, можно использовать имя BUDGET.CPP. Аналогичным образом программу, которая вычисляет оклады в фирме, вы можете назвать SALARY. СРР. Чтобы избежать путаницы, никогда не используйте для названия программы имя существующей команды MS-DOS, например COPY или DEL.

КОМПИЛЯЦИЯ ВАШЕЙ ПРОГРАММЫ

Компьютер работает с комбинациями единиц и нулей (называемых машинным языком), которые представляют наличие или отсутствие электрических сигналов. Если сигнал равен единице (наличие), компьютер может выполнить одну операцию, а если сигнал равен нулю (отсутствие), компьютер может выполнить другую операцию. Однако к счастью, нет необходимости писать программы в нулях и единицах (как это делали программисты в 1940 и 50 гг.). Вместо этого специальная программа — компилятор C++ — преобразует операторы программы (ваш исходный код) в машинный язык.

Другими словами, компилятор просматривает исходный файл, содержащий операторы программы на C++. Если ваши операторы не нарушают ни одно правило языка C++, компилятор преобразует их в машинный язык (единицы и нули), который компьютер может выполнить. Компилятор хранит машинный язык в выполняемом файле, имеющем, как правило, расширение ЕХЕ. Если файл ЕХЕ существует, вы можете запустить программу, вводя ее имя в ответ на командную подсказку.

В зависимости от используемого вами компилятора, команды, которые вы применяете для его вызова, будут различны. Например, при использовании Borland C++ вам следует компилировать программу FIRST.CPP с помощью команды ВСС:

C:\> BCC FIRST.CPP <ENTER>

Если вы применяете не Borland C++, обратитесь к документации, поставляемой с вашим компилятором, чтобы определить правильную команду для его запуска. По окончании работы компилятор создаст выполнимую программу и сохранит ее в файле на диске. В среде MS-DOS файл выполни-

мой программы будет иметь расширение ЕХЕ, например FIRST.EXE. Если при компиляции программы компилятор выдает сообщения об ошибках, отредактируйте свой исходный файл и сравните каждый символ исходного файла с символами, которые приведены в этой книге. Исправьте все ошибки, сохраните сделанные изменения, а затем откомпилируйте программу второй раз. После того как вы успешно откомпилировали свою программу, запустите ее, вводя имя программы в ответ на командную подсказку как это было показано выше.

Представление о компиляторе

При создании программы вы используете язык программирования (такой как C++), чтобы указать инструкции, выполняемые компьютером. Применяя текстовый редактор, вы вносите операторы программы в исходный файл. Далее используется специальная программа — компилятор, которая преобразует ваш исходный файл в машинный язык (единицы и нули, понимаемые компьютером). Если компиляция прошла успешно, результатом будет файл выполнимой программы. Однако если вы допустили одну или несколько ошибок или нарушили какое-либо правило C++, компилятор выдаст на экран сообщения об ошибках, и для их исправления вам следует заново отредактировать исходный файл.

Если вы работаете на большой машине или мини-ЭВМ, у вас должен быть компилятор, доступный для вас и других пользователей вашей системы. Если же вы используете ПК, вам следует приобрести и установить компилятор, такой как Borland C++ или Microsoft Visual C++.

СОЗДАНИЕ ВТОРОЙ ПРОГРАММЫ

Хочется надеяться, что вы смогли успешно откомпилировать и выполнить программу FIRST. CPP. Если это так, используйте ваш текстовый редактор, чтобы создать второй программный файл с именем EASY. CPP, который содержит следующие операторы программы:

#include <iostream.h>

void main(void)

{
cout << "Программировать на C++ просто!";
}

Как и ранее, сохраните свои операторы программы на C++ в исходном файле и вызовите компилятор, указав имя файла программы в командной строке компилятора. В случае Borland C++ используйте следующую команду для компиляции программы:

С:\> ВСС EASY.CPP <ENTER>

Если компиляция программы прошла успешно, компилятор создаст выполнимую программу с именем EASY.EXE. Когда вы запустите эту программу, на вашем экране появится следующее сообщение:

С.\> EASY <ENTER>

Программировать на C++ просто!

Далее используя редактор, отредактируйте исходный файл EASY. CPP и измените выводимое на экран сообщение таким образом, чтобы включить слово очень, как показано ниже:

cout << "Программировать на C++ очень просто!";

Сохраните ваше изменение в исходном файле и откомпилируйте программу. После успешной компиляции запустите программу, как показано ниже:

С:\> EASY <ENTER>

Программировать на C++ очень просто!

Каждый раз, когда вы изменяете исходный файл, вам следует откомпилировать программу заново, чтобы изменения вступили в силу. Например, используйте текстовый редактор, чтобы снова изменить исходный файл. В этот раз добавьте новую строку в свою программу:

#include <iostream.h>

void main(void)

{
 cout << "Программировать на C++ очень просто!";
 cout << endl << "Можно расслабиться!";
}

Сохраните ваши изменения в исходном файле. Затем запустите программу, как показано ниже:

С:\> EASY <ENTER>

Программировать на C++ очень просто!

Как видите, программа не выводит новую строку на экран. Чтобы изменения в исходном файле вступили в силу, вы должны откомпилировать программу. В данном случае необходимо откомпилировать программу так, как было описано выше, а затем запустить ее. Так как компилятор использовал изменения вашего исходного кода, то, как показано ниже, на экран будет выведена новая строка:

C:\> EASY <ENTER>

Программировать на C++ очень просто!

Можно расслабиться!

ИЗУЧЕНИЕ СИНТАКСИЧЕСКИХ ОШИБОК

Каждый язык — английский, французский, немецкий и даже C++ — имеет набор правил, называемых синтаксисом, которым вы должны следовать, когда используете данный язык. В английском языке, например, предложения обычно заканчиваются точкой, восклицательным или вопросительным знаком. Вы также используете заглавные буквы в начале предложения. В синтаксисе C++ используется точка с запятой, круглые скобки, фигурные скобки и многие другие символы. Когда вы забываете или неправильно употребляете эти символы, компилятор C++ выводит на экран сообщение об ошибке, которое описывает ошибку и соответствующий ей номер строки в исходном файле.

Компилятор C++ не может создать выполнимую программу, пока не будут исправлены все синтаксические ошибки. Чтобы понять процесс обнаружения и исправления синтаксических ошибок, создайте следующую программу с именем SYNTAX. CPP:

#include <iostream.h>

void main(void)

{
 cout << Заключайте сообщение в кавычки;
}

Если посмотреть внимательно, можно заметить, что сообщения, выведенные двумя предыдущими программами, в вашем исходном файле взяты в кавычки. Синтаксис (правила) C++ требует кавычек. При компиляции программы компилятор выведет сообщения о синтаксических ошибках. В случае Borland C++ компилятор выведет следующие сообщения:

С:\> ВСС SYNTAX.CPP <ENTER>

Borland C++ Version 4.00 Copyright (с) 1993 Borland

International syntax.cpp:

Error syntax.cpp 5: Undefined symbol 'Заключайте' in function main()

Error syntax.cpp 5: Statement missing; in function main() *** 2 errors in Compile ***

В этом случае компилятор вывел две синтаксические ошибки. Обе ошибки относятся к 5 строке исходного файла. Отредактируйте файл и возьмите сообщение в кавычки, как показано ниже:

cout << "Заключайте сообщение в кавычки";

Теперь можете успешно откомпилировать программу и получить выполнимый файл. Когда вы впервые начинаете использовать какой-либо язык программирования, можете рассчитывать на несколько синтаксических ошибок каждый раз при компиляции программы. После того как вы самостоятельно создадите несколько программ, вы будете быстро определять и исправлять подобные ошибки.

Изучение синтаксических ошибок

При создании программы на C++ вам следует придерживаться определенных правил, называемых правилами синтаксиса. Например, надо брать текстовые сообщения в кавычки и ставить точку с запятой после большинства операторов вашей программы (позже вы узнаете, для каких операторов требуется точка с запятой, а для каких нет). Если в программе нарушаются правила синтаксиса, компилятор C++ выводит сообщение об ошибке на экран. Вам следует исправить все синтаксические ошибки до того, как компилятор сможет создать выполнимую программу.

РАБОТА В СРЕДЕ ТИПА WINDOWS

Для упрощения в каждом из предыдущих примеров предполагается, что вы работаете в среде, основанной на командной строке, например MS-DOS или UNIX. Однако сегодня большинство программистов на C++ программируют в среде типа Windows, такой как Visual C++, или интегрированной среде разработки фирмы Borland. При программировании в среде типа Windows операторы программы не отличаются от тех, которые показаны здесь. Другими словами, операторы C++ в программе FIRST.CPP, написанной в Windows, идентичны тем, которые вы будете использовать в среде, основанной на командной строке. Что изменяется в Windows, так это процесс компиляции и запуска программы.

Рисунок 1, например, иллюстрирует среду программирования Windows. Внутри такой среды программирования вы можете создавать исходные файлы, используя встроенный редактор, и затем компилировать программу с помощью выбора пункта меню или щелчка мыши по кнопке инструментальной линейки. Если программа содержит синтаксические ошибки, среда программирования сводит сообщения об ошибках в специальное окно. После того как вы успешно откомпилируете вашу программу, можно использовать пункт меню (или кнопку инструментальной линейки) для запуска программы. Программная среда может открыть отдельное окно, в котором будет отображаться вывод программы.

Рис.1. Среда программирования Windows.

Среда программирования называется так, потому что обеспечивает все инструментальные средства, необходимые для создания, компиляции и запуска программ.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, как создавать и компилировать программы на C++! В уроке 2 вы получите более подробный обзор операторов, которые использовались в программах, созданных в данном уроке. Вы изучите использование фигурных скобок {}, ключевых слов, таких как void, а также как научить программы направлять вывод на экран.

До изучения урока 2 убедитесь, что вы освоили следующие основные концепции:
    1. Программы представляют собой файлы, содержащие последовательность инструкций, которые компьютер будет выполнять.
    2. Вы создаете программы на C++, используя текстовый редактор.
    3. Вы сохраняете свои программы на C++ в исходных файлах, для которых используется расширение СРР.
    4. Компилятор преобразует операторы программы на C++ в единицы и нули — машинный язык, который понимает компьютер.
    5. Подобно всем языкам, в C++ существует набор правил, называемых синтаксисом.
    6. Если вы нарушаете правила синтаксиса, компилятор выводит сообщение, описывающее ошибку.
    7. Вы должны исправить все синтаксические ошибки, прежде чем компилятор создаст выполняемую программу.
    8. После внесения изменений в исходный файл следует заново откомпилировать программу, чтобы изменения вступили в силу.

Следующий урок.


Untitled Document

Урок 2

Более внимательный взгляд на C++

В уроке 1 вы создали несколько программ на C++. В то время ваша цель заключалась в том, чтобы понять процесс создания и компиляции программ на C++, а не в том, чтобы понять операторы C++. В данном уроке вы впервые более внимательно рассмотрите операторы, из которых состоит программа на C++. Вы увидите, что большинство программ на C++ придерживаются одного и того же формата: начинаются с одного или нескольких операторов #include, содержат строку void main(void), а затем набор операторов, сгруппированных между левой и правой фигурными скобками. Из этого урока вы поймете, что эти несколько запугивающие операторы реально очень просто освоить. К концу данного урока вы изучите следующие основные концепции:

  • Оператор #include обеспечивает преимущества использования заголовочных файлов, которые содержат операторы C++ или программные определения.
  • Основная часть программы на C++ начинается с оператора void main(void).
  • Программы состоят из одной или нескольких функций, которые, в свою очередь, состоят из операторов, предназначенных для решения определенной задачи.
  • При выводе на экран ваши программы будут широко использовать выходной поток cout.

Когда вы создаете программы на C++, вы реально работаете в терминах операторов, но не инструкций. Позже вы изучите оператор присваивания, который присваивает значения переменным, оператор if, который позволяет программе принимать решения и т. д. А пока мы просто будем ссылаться на содержимое вашей программы, как на операторы программы.

ВЗГЛЯД НА ОПЕРАТОРЫ ПРОГРАММЫ

В уроке 1 вы создали на C++ программу FIRST.CPP, которая содержала следующие операторы:

#include <iostream.h>

void main(void)

{
 cout << "Учимся программировать на языке С++!";
}

В данном случае программа содержит три оператора. Фигурные скобки (называемые группирующими символами) группируют связанные операторы:

#include <iostream.h>

void main (void)

{
 cout << "Учимся программировать << "на языке С++!";
}

В следующем разделе каждый из операторов программы описывается более подробно.

ПРЕДСТАВЛЕНИЕ ОБ ОПЕРАТОРЕ #include

Каждая программа, представленная в уроке 1, начинается со следующего оператора #include:

#include <iostream.h>

При создании программ на C++ вы получаете преимущества от использования операторов и определений, которые обеспечивает вам компилятор. При компиляции программы оператор #include заставляет компилятор включить содержимое заданного файла в начало вашей программы. В данном случае компилятор включит содержимое файла iostream.h.

Файлы с расширением h, которые вы включаете в начало (или заголовок) вашей программы, называются заголовочными файлами. Если вы посмотрите на каталог, содержащий файлы вашего компилятора, то найдете подкаталог с именем INCLUDE, в котором находятся разные заголовочные файлы. Каждый заголовочный файл содержит определения, предоставляемые компилятором для различных операций. Например, существует заголовочный файл, который содержит определения для математических операций, другой заголовочный файл описывает файловые операции и т. д.

Заголовочные файлы представляют собой файлы в формате ASCII, следовательно, вы можете вывести их содержимое на экран или принтер. В данный момент не беспокойтесь о содержимом заголовочных файлов. Просто поймите, что оператор #include позволяет вам использовать эти файлы. Все программы на C++, созданные вами в процессе изучения этой книги, содержат операторы #include, которые вы должны применять в ваших программах.

Заголовочные файлы C++

Каждая создаваемая вами программа на C++ начинается с одного или нескольких операторов #include. Эти операторы указывают компилятору включить содержимое заданного файла (заголовочного файла) в вашу программу, как если бы программа содержала операторы, которые находятся во включаемом файле. Заголовочные файлы содержат определения, используемые компилятором для операций различных типов. Существуют заголовочные файлы, которые определяют операции В/В (ввода/вывода) C++, системные функции (например, функции, возвращающие текущие дату и время) и многое другое.

Заголовочные файлы, подобно программам на C++, представляют собой файлы в формате ASCII, содержимое которых вы можете просмотреть или напечатать. Чтобы лучше понять содержимое заголовочных файлов, найдите время для того, чтобы напечатать заголовочный файл IOSTREAM.H, содержимое которого вы будете использовать в каждой создаваемой вами программе на C++. Обычно заголовочный файл IOSTREAM.H расположен в подкаталоге с именем INCLUDE, который находится в каталоге, содержащем файлы компилятора C++. Используйте текстовый редактор, чтобы просмотреть и напечатать содержимое заголовочных файлов.

Замечание: Никогда не изменяйте содержимое заголовочных файлов. Это может привести к ошибкам компиляции в каждой создаваемой вами программе.

ЧТО ТАКОЕ void main(void)

При создании программы на C++ ваш исходный файл будет содержать множество операторов. Как вы поймете в процессе изучения, порядок, в котором операторы появляются в программе, не обязательно должен совпадать с порядком, в котором операторы будут выполняться при запуске программы. Каждая программа на C++ имеет один вход, с которого начинается выполнение программы, — главную программу. В программах на C++ оператор void main(void) указывает стартовую точку вашей программы.

По мере того как ваши программы становятся больше и сложнее, вы будете делить их на несколько небольших легко управляемых частей. При этом оператор void main(void) указывает начальные (или главные) операторы программы — часть программы, которая выполняется первой.

Представление о главной программе

Исходные файлы C++ могут содержать очень много операторов. При запуске программы оператор void main(void) определяет главную программу, содержащую первый выполняемый оператор. Ваши программы на C++ должны всегда включать один и только один оператор с именем main.

При рассмотрении больших программ на C++ ищите main, чтобы определить операторы, с которых начинается выполнение программы.

Использование void

Как только ваша программа становится более сложной, вы должны разделить ее на небольшие более легко управляемые части, называемые функциями. Функция представляет собой простой набор операторов внутри программы, выполняющих определенную задачу. Например, при создании программы платежных документов, вы могли бы создать функцию с именем salary, вычисляющую оклад служащих. Аналогичным образом, если вы пишете математическую программу, вы могли бы создать функции с именами square_root или cube, которые возвращают результат определенных математических операций. Если ваша программа использует функцию, функция выполняет свою задачу и затем возвращает свой результат программе.

Каждая функция в вашей программе имеет уникальное имя. А каждая программа имеет по крайней мере одну функцию. Каждая программа из урока 1 имела только одну функцию с именем main. Урок 9 предоставляет более подробный обзор функций. В данный момент просто имейте в виду, что функция состоит из нескольких связанных по смыслу операторов, выполняющих определенную задачу.

При исследовании различных программ на C++ вы будете постоянно сталкиваться со словом void. Программы используют слово void для указания того, что функция не возвращает значения или не имеет значений, передаваемых в нее. Например, если вы используете среду MS-DOS или UNIX, программа может завершить свое выполнение с возвратом операционной системе значения статуса, которое может быть проверено командным файлом. Командные файлы MS-DOS проверяют выходной статус программы, используя команду IF ERRORLEVEL. Например, предположим, что программа с именем PAYROLL. EXE завершается с одним из следующих выходных значений статуса в зависимости от результата обработки:

Значение статуса | Смысл
0 | Успех
1 | Файл не найден
2 | В принтере нет бумаги

Внутри командного файла MS-DOS вы можете проверить результат работы программы, используя команду IF ERRORLEVEL:

PAYROLL

IF ERRORLEVEL 0 IF NOT ERRORLEVEL 1 GOTO SUCCESSFUL
IF ERRORLEVEL 1 IF NOT ERRORLEVEL 2 GOTO NO_FILE
IF ERRORLEVEL 2 IF NOT ERRORLEVEL 3 GOTO NO_PAPER
REM Далее идут другие команды

Большинство простых программ на C++, которые будут созданы вами в процессе изучения этой книги, не возвращают выходное значение статуса операционной системе. Поэтому вы должны размещать слово void перед main, как показано ниже:

void main (void) //—-------> Программа не возвращает значение

В следующих уроках вы узнаете, что ваши программы могут использовать информацию (например, имя файла), которую пользователь указывает в командной строке при запуске программы. Если программа не использует информацию командной строки, вы должны разместить слово void внутри круглых скобок после main, как показано ниже:

void main (void) //----------------------> Программа не использует аргументы командной строки

По мере усложнения ваши программы могут возвращать значения в операционную систему или использовать параметры командной строки. Однако в настоящий момент просто используйте void в операторе с main, как показано в этой программе.

ПРЕДСТАВЛЕНИЕ О ГРУППИРУЮЩИХ ОПЕРАТОРАХ { }

По мере усложнения в ваших программах будет один набор операторов, которые компьютер должен выполнить определенное число раз, и другой набор операторов, которые компьютер должен выполнить, если выполняется определенное условие. В первом случае компьютер может выполнить один и тот же набор операторов 100 раз, чтобы добавить для 100 студентов тестовые очки. Во втором случае компьютер может вывести на экран одно сообщение, если все студенты прошли тест, и другое сообщение, если один или несколько студентов потерпели неудачу. Внутри своих программ на C++ вы будете использовать правую и левую фигурные скобки {}, чтобы сгруппировать связанные операторы. В простых программах, представленных в нескольких первых уроках книги, эти символы группируют операторы, которые соответствуют операторам вашей главной программы.

ИСПОЛЬЗОВАНИЕ cout ДЛЯ ОТОБРАЖЕНИЯ ВЫВОДА НА ЭКРАН

Все программы на C++, созданные вами в уроке 1, выводили сообщения на экран. Чтобы вывести сообщение, программы использовали cout и двойной знак "меньше" (<<), как показано ниже:

cout << "Привет, C++!";

Слово cout представляет собой выходной поток, который C++ назначает на стандартное устройство вывода операционной системы. По умолчанию операционная система назначает стандартное устройство вывода на экран дисплея. Чтобы вывести сообщение на экран, вы просто используете двойной символ "меньше" (называемый оператором вставки) с выходным потоком cout. Из урока 3 вы узнаете, что можно использовать оператор вставки для передачи символов, чисел и других знаков на экран.

Представление о выходном потоке cout

Вы уже знаете, что программы на C++ используют выходной поток cout для вывода сообщений на экран. При использовании cout для вывода сообщений представляйте cout в виде потока символов, которые операционная система отображает на экране. Другими словами, порядок, в котором ваша программа посылает символы в cout, определяет порядок символов, которые будут появляться на экране. Например, для следующих операторов программы:

cout << "Это сообщение появляется первым,";
cout << " а за ним следует настоящее сообщение.";

Операционная система выводит поток символов следующим образом:

Это сообщение появляется первым, а за ним следует настоящее сообщение.

Оператор вставки (<<) называется так, потому что позволяет вашей программе вставлять символы в выходной поток.

Вы уже знаете, что выходной поток cout по умолчанию соответствует вашему экрану. Другими словами, когда ваши программы посылают вывод в cout, вывод появляется на экране. Однако, используя операторы переназначения вывода операционной системы, вы можете послать вывод программы на принтер или в файл. Например, следующая команда предписывает MS-DOS направить вывод программы FIRST.EXE на принтер, а не на экран:

С:\> FIRST > PRN <ENTER>

Как вы узнаете из Урока 3, с помощью cout в C++ можно выводить символы, целые числа, например 1001, и числа с плавающей точкой, например 3.12345. Из Урока 8 вы узнаете, что в C++ существует также входной поток с именем cin, который ваши программы могут использовать для чтения информации, вводимой с клавиатуры.

ЧТО ВЫ ДОЛЖНЫ ЗНАТЬ

В этом уроке обсуждались некоторые общие вопросы, с которыми вы столкнетесь в программах на C++. Из Урока 3 вы узнаете, как использовать cout для вывода символов, целых чисел и значений с плавающей точкой. Вы также узнаете, как форматировать вывод. До изучения урока 3 убедитесь, что вы освоили следующие основные концепции:

    1. Большинство программ на C++ начинаются с оператора #include, который предписывает компилятору включить содержимое заданного заголовочного файла в программу.
    2. Заголовочные файлы содержат определения, предоставляемые компилятором, которые ваши программы могут использовать.
    3. Исходный файл может состоять из множества операторов; оператор void main(void) указывает начало главной программы, которая содержит первый выполняемый оператор программы.
    4. По мере того как ваша программа становится более сложной, вы будете группировать связанные операторы в небольшие легко управляемые части, называемые функциями. Группируйте операторы программы с помощью правой и левой фигурных скобок {}.
    5. Большинство программ на C++ используют выходной поток cout для вывода информации на экран; однако, используя операторы переназначения В/В операционной системы, вы можете перенаправить вывод cout в файл, устройство (например, принтер) или даже сделать его входом другой программы.


Untitled Document

Урок З

Вывод сообщений на экран

Все программы на C++, созданные вами в уроках 1 и 2, использовали выходной поток cout для вывода сообщений на экран. В этом уроке вы будете использовать cout для вывода символов, целых чисел, например 1001, и чисел с плавающей точкой, например 0.12345. К концу данного урока вы освоите следующие основные концепции:

  • Для вывода символов и чисел на экран вы можете использовать выходной поток cout.
  • В C++ можно использовать с cout специальные символы для вывода табуляции или новой строки и даже для воспроизведения звука на вашем компьютере.
  • В C++ можно легко отображать числа в десятичном, восьмеричном (по основанию 8) или шестнадцатеричном (по основанию 16) формате.
  • Используя в командной строке операционной системы операторы переназначения, вы можете перенаправить выходные сообщения своей программы, посылаемые в cout, с экрана в файл или на принтер.
  • Используя выходной поток cerr, ваши программы могут посылать сообщения на стандартное устройство ошибок, избавляя пользователей от необходимости переназначения сообщений.
  • Вы можете форматировать вывод вашей программы, используя модификатор setw внутри выходного потока.

Почти все создаваемые вами программы на C++ используют cout для вывода сообщений на экран. Из этого урока вы узнаете, как лучше использовать сом/.

ИСПОЛЬЗОВАНИЕ cout ДЛЯ ВЫВОДА ЧИСЕЛ

До сих пор созданные вами программы использовали сом/ для вывода символьных строк (букв и чисел, взятых в кавычки). Теперь вы узнаете, что сом/ можно также использовать для вывода чисел. Следующая программа 1001.СРР выводит число 1001 на ваш экран:

#include <iostream.h>

void main(void)

{
 cout << 1001;
 }

Откомпилируйте и запустите эту программу. На вашем экране будет отображено число 1001, как показано ниже:

С:\> 1001 <ENTER>

1001

Далее отредактируйте программу и измените оператор cout, чтобы вывести число 2002, как показано ниже:

cout << 2002;

Кроме отображения целых чисел (чисел без десятичной точки), сом/также позволяет вашим программам отображать числа с плавающей точкой, например 1.2345. Следующая программа FLOATING.CPP использует сом/для вывода числа 0.12345 на экран:

#include <iostream.h>

void main(void)

{
cout << 0.12345;
}

Как и ранее, откомпилируйте и запустите эту программу. На вашем экране появится следующий вывод:

С:\> FLOATING <ENTER>

0.12345

ВЫВОД НЕСКОЛЬКИХ ЗНАЧЕНИЙ ОДНОВРЕМЕННО

Как вы уже знаете, двойной знак "меньше" является операцией вставки (эта операция вставляет символы в выходной поток для отображения). С помощью cout вы можете использовать несколько операций вставки в пределах одного оператора. Например, следующая программа 1001ТОО.СРР использует эту операцию четыре раза для отображения числа 1001 на вашем экране:

#include <iostream.h>

void main(void)

(
cout << 1 << 0 << 0 << 1;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующее:

С:\> 1001TOO <ENTER>

1001

Каждый раз, когда в C++ встречается операция вставки, число или символы просто добавляются к тем, что находятся в настоящее время в выходном потоке. Следующая программа SHOW1001.CPP с помощью cout выводит символьную строку и число:

#include <iostream.h>

void main(void)

{
cout << "Мое любимое число равно " << 1001;
}

Обратите внимание, что пробел, следующий за словом равно (внутри кавычек), служит для отделения числа 1001 от этого слова. Без пробела число сливается со следующим словом (равно 1001). Подобным образом следующая программа 1001MID.CPP отображает число 1001 в середине символьной строки:

#include <iostream.h>

void main(void)

{
cout << "Число " << 1001 << " мне очень нравится";
}

Как и ранее, обратите внимание на расстановку пробелов до и после числа 1001.

Наконец, следующая программа MIXMATCH.CPP комбинирует строки, символы, целые числа и числа с плавающей точкой внутри одного и того же выходного потока:

#include <iostream.h>

void main(void)

{
 cout << "B " << 20 << " лет мой оклад был " << 493.34 << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> MIXMATCH <ENTER>

В 20 лет мой оклад был 493.34

ИСПОЛЬЗОВАНИЕ СПЕЦИАЛЬНЫХ СИМВОЛОВ ВЫВОДА

Все программы, созданные вами до сих пор, отображали свой вывод в виде одной строки. Однако большинство программ, которые вы создадите в дальнейшем, будут отображать несколько строк вывода. Например, предположим, что вы пишете программу, которая будет выводить адреса на экран. Вероятно, вы захотите, чтобы адреса появлялись в виде нескольких строк.

Если необходимо переместить курсор в начало следующей строки, можно поместить символ новой строки (\n) в выходной поток. В C++ вам предоставляется два разных способа генерации новой строки. Во-первых, вы можете поместить символы \n внутри символьной строки. Например, следующая программа TWOLINES.CPP отображает свой вывод в виде двух строк, используя символ новой строки:

#include <iostream.h>

void main(void)
{
cout << "Это строка один\nЭто строка два";
}

Когда вы откомпилируете и запустите эту программу, символ новой строки обеспечит вывод двух строк, как показано ниже:

С:\> TWOLINES <ENTER>

Это строка один

Это строка два

Если вы не выводите символьную строку, можете поместить символ новой строки внутри одинарных кавычек. Например, следующая программа NEWLINES.CPP выводит числа 1, 0, 0 и 1, каждое на своей собственной строке:

#include <iostream.h>

void main(void)
{
cout << 1 << '\n' << 0 << '\n' << 0 << '\n' << 1;
}

В дополнение к использованию символа новой строки для продвижения курсора в начало следующей строки ваши программы могут использовать символ endl (конец строки). Следующая программа ENDL.CPP иллюстрирует использование endl для продвижения курсора в начало новой строки:

#include <iostream.h>

void main(void)

{
cout << "А теперь..." << endl
<< "Учимся программировать на языке C++";
}

Как и ранее, когда вы откомпилируете и запустите эту программу, на экране будет отображен вывод программы в виде двух строк:

C:\> ENDL <ENTER>

А теперь

Учимся программировать на языке C++

Наконец, следующая программа ADDRESS.CPP выводит адрес издательства "Jamsa Press" в несколько строк:

#include <iostream.h>

void main(void)

{
cout << "Jamsa Press" << endl;
cout << "2975 South Rainbow, Suite I" << endl;
cout << "Las Vegas, NV 89102" << endl;
}

Другие специальные символы

В дополнение к символу новой строки, позволяющему вашим программам продвигать курсор в начало новой строки, вы можете использовать специальные символы, перечисленные в табл. 3.1.

Таблица 3.1. Специальные символы для использования с cout.
Символ | Назначение
\а | Сигнальный (или звонок) символ
\Ь | Символ возврата
V | Символ перевода страницы
\n | Символ новой строки
\г | Возврат каретки (не перевод строки)
\t | Символ горизонтальной табуляции
\v | Символ вертикальной табуляции
\\ | Символ обратный слеш
\? | Знак вопроса
\' | Одинарные кавычки
\" | Двойные кавычки
\0 | Нулевой символ
\000 | Восьмеричное значение, например \007
\xhhhh | Шестнадцатеричное значение, например \xFFFF

Замечание: При использовании специальных символов, перечисленных в табл. 3.1, вам следует располагать их внутри одинарных кавычек, если вы используете данные символы сами по себе, например '\n', или внутри двойных кавычек, если вы используете их внутри строки, например "Привem\nMup!".

Следующая программа SPECIAL.CPP использует специальные символы сигнала (\а) и табуляции (\t) для выдачи звука на встроенный динамик компьютера и затем выводит слова Звонок Звонок Звонок, разделенные табуляцией:

#include <iostream.h>

void main(void)

{
cout << "3вонок\a\tЗвонок\a\tЗвонок\a";
}

ВЫВОД ВОСЬМЕРИЧНЫХ И ШЕСТНАДЦАТЕРИЧНЫХ ЗНАЧЕНИЙ

Программы, представленные в этом уроке до сих пор, выводили числа в десятичном виде. В зависимости от назначения ваших программ вам, возможно, потребуется выводить числа в восьмеричном или шестнадцатеричном виде. Для этого можно разместить модификаторы dec, oct и hex внутри выходного потока. Следующая программа ОСТНЕХ.СРР использует эти модификаторы для вывода значений в десятичном, восьмеричном и шестнадцатеричном виде:

#include <iostream.h>

void main(void)

{
cout << "Восьмеричный: " << oct << 10 << ' ' << 20 << endl;
cout << "Шестнадцатеричный: " << hex << 10 << ' ' << 20 << endl;
cout << "Десятичный: " << dec << 10 << ' ' << 20 << endl;
}

Когда вы откомпилируете и запустите эту программу, на экране появится следующий результат:

С:\> OCTEX <ENTER>

Восьмеричный: 12 24

Шестнадцатеричный: а 14

Десятичный: 10 20

Примечание: Когда вы используете один из модификаторов для выбора восьмеричного, шестнадцатеричного или десятичного вывода, ваш выбор будет оставаться в силе до тех пор, пока программа не закончится или пока вы не используете другой модификатор.

ВЫВОД НА СТАНДАРТНОЕ УСТРОЙСТВО ОШИБОК

Как вы уже знаете, используя cout, вы можете перенаправить вывод программы на устройство или файл с помощью операторов переназначения вывода операционной системы. Однако, если ваши программы сталкиваются с ошибкой, вы, вероятно, не захотите, чтобы сообщение об ошибке было перенаправлено с экрана. Перенаправление сообщений об ошибках в файл может скрыть от пользователя факт появления ошибки.

Если вашей программе нужно вывести сообщение об ошибке, вы должны использовать выходной поток cerr. C++ связывает cerr со стандартным устройством ошибок операционной системы. Следующая программа CERR.CPP использует выходной поток cerr для вывода на экран сообщения "Это сообщение появляется всегда ":

#include <iostream.h>

void main(void)

{
cerr << "Это сообщение появляется всегда";
}

Откомпилируйте и запустите эту программу. Далее попытайтесь перенаправить вывод программы в файл, используя оператор переназначения вывода:

С:\> CERR > FILENAME.EXT <ENTER>

Так как операционная система не позволит вашим программам перенаправить вывод, записываемый на стандартное устройство ошибок, сообщение появится на вашем экране.

УПРАВЛЕНИЕ ШИРИНОЙ ВЫВОДА

Несколько предыдущих программ выводили числа на экран. Чтобы гарантировать правильное отображение этих чисел (с правильной расстановкой пробелов), программы включали пробелы до и после чисел. При выводе на cout или cerr ваши программы могут указать ширину вывода каждого числа, используя модификатор setw (установка ширины). С помощью setw программы указывают минимальное количество символов, занимаемое числом. Например, следующая программа SETW.CPP использует модификатор setw для выбора ширины 3, 4, 5 и 6 для числа 1001. Чтобы использовать модификатор setw, ваша программа должна включать заголовочный файл iomanip.h:

#include <iostream.h>

#include <iomanip.h>

void main (void)

{
cout << "Мое любимое число равно" << setw(3) << 1001 << endl;
cout << "Мое любимое число равно" << setw(4) << 1001 << endl;
cout << "Мое любимое число равно" << setw(5) << 1001 << endl;
cout << "Мое любимое число равно" << setw(6) << 1001 << endl;
}

Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод:

С:\> SETW <ENTER>

Мое любимое число равно1001

Мое любимое число равно1001

Мое любимое число равно 1001

Мое любимое число равно 1001

Если вы указываете ширину с помощью setw, вы указываете минимальное количество символьных позиций, занимаемых числом. В предыдущей программе модификатор setw(3) указывал минимум три символа. Однако, так как число 1001 потребовало больше трех символов, cout использовал реально требуемое количество, которое в данном случае равнялось четырем. Следует отметить, что при использовании setw для выбора ширины, указанная ширина действительна для вывода только одного числа. Если вам необходимо указать ширину для нескольких чисел, вы должны использовать setw несколько раз.

Замечание: Предыдущая программа использует заголовочный файл IOMANIP.H. Вам, возможно, понадобится сейчас напечатать и исследовать содержимое этого файла. Как и в случае с заголовочным файлом IOSTREAM.H вы найдете данный файл внутри подкаталога INCLUDE, который находится в каталоге с файлами вашего компилятора.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали несколько способов использования cout для отображения вывода на экран. Все программы, которые вы создадите в процессе изучения оставшейся части книги, будут использовать cout для отображения вывода. Из урока 4 вы узнаете, как использовать переменные внутри своих программ для хранения значений, которые могут изменяться в процессе выполнения программы. Однако до изучения урока 4 убедитесь, что вы освоили следующие основные концепции:

    1. Выходной поток cout позволяет вам выводить символы и числа.
    2. Используя специальные символы внутри выходного потока, ваша программа может указать новую строку, табуляцию и другие специальные возможности.
    3. Для продвижения курсора в начало следующей строки программы могут создать новую строку, используя символ \n или модификатор endl.
    4. Модификаторы dec, oct и hex позволяют программам выводить значения в десятичном, восьмеричном и шестнадцатеричном виде.
    5. Используя выходной поток cerr, программы могут записать сообщения в стандартное устройство ошибок операционной системы.
    6. С помощью модификатора setw ваши программы могут управлять шириной вывода чисел.


Untitled Document

Урок 4

Программы хранят информацию в переменных

Все программы, представленные в уроках 1—3, были очень простыми. Однако по мере того, как ваши программы начинают выполнять более многоплановые задачи, они должны хранить информацию во время выполнения. Например, программе, печатающей файл, нужно знать имя файла и, возможно, число копий, которые вы хотите напечатать. В процессе выполнения программы хранят такую информацию в памяти компьютера. Чтобы использовать определенные ячейки памяти, программы применяют переменные. Проще говоря, переменная представляет собой имя ячейки памяти, которая может хранить конкретное значение. В этом уроке описано, как создавать и использовать переменные в программах на C++. К концу данного урока вы освоите следующие основные концепции:

  • Вы должны объявлять переменные, которые будете использовать в программе, сообщая компилятору имя и тип переменной.
  • Тип переменной определяет тип значения (например, целое число или число с плавающей точкой), которое может хранить переменная, а также операции, которые ваши программы могут выполнять над переменной.
  • Чтобы присвоить значения переменной, используйте оператор присваивания C++ (знак равно).
  • Для вывода значения переменной на экран программы используют выходной поток cout.
  • При объявлении переменных используйте имена, выражающие смысл переменных, чтобы ваши программы было легче читать и понимать.
  • Используйте комментарии, описывающие работу программы. В этом случае, если другим программистам нужно изменить вашу программу, комментарии подробно опишут работу программы.

Когда вы присваиваете значение переменной, представьте себе переменную в виде ящика, в который можно поместить значение. Если вам позже потребуется использовать значение переменной, компьютер просто посмотрит значение, содержащееся в ящике.

ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ В ПРОГРАММАХ

Ваши программы используют переменные для хранения информации. В зависимости от типа хранимого значения, например, целое число, буква алфавита или число с плавающей точкой, тип вашей переменной будет разным.

Тип переменной указывает тип значения, хранимого в переменной, а также набор операций (таких как сложение, умножение и другие), которые программа может выполнять над значением переменной. Большинство программ на C++ будут использовать типы переменных, перечисленные в табл. 4.1.

Таблица 4.1. Типы переменных C++.

Тип | Хранимые значения
char | Значения в диапазоне от -128 до 127. Обычно используется для хранения букв алфавита
int | Значения в диапазоне от -32768 до 32767
unsigned | Значения в диапазоне от 0 до 65535
long | Значения в диапазоне от -2147483648 до 2147483647
float | Значения в диапазоне от -3.4 x 10-38 до 3.4 x 1038
double | Значения в диапазоне от 1.7х 10-308 до 1.7х 10308

Прежде чем вы сможете использовать переменную, ваша программа должна ее объявить. Другими словами, вам следует представить переменную компилятору C++. Чтобы объявить переменную в программе, вам следует указать тип переменной и ее имя, по которому программа будет обращаться к данной переменной. Указывайте тип и имя переменной после открывающей фигурной скобки главной программы, как показано ниже:

тип_переменной имя_переменной;

Как правило, тип переменной будет одним из типов, перечисленных в табл. 4.1. Выбираемое вами имя переменной должно нести смысловую нагрузку, которая описывает (для всех, кто читает вашу программу) использование переменной. Например, ваша программа могла бы использовать переменные, такие как employee_name, employee_age и т. д. Обратите внимание на точку с запятой, которая следует за именем переменной. В C++ объявление переменной считается оператором. Поэтому вы должны поставить после объявления точку с запятой.

Фрагмент следующей программы объявляет три переменные, используя типы int, float и long:

#include <iostream.h>

void main(void)

{
int test_score;
float salary;
long distance_to_mars;
}

Важно обратить внимание, что данная программа ничего не выполняет, а только объявляет переменные. Как видите, объявление каждой переменной заканчивается точкой с запятой. Если вы объявляете несколько переменных одного и того же типа, можно разделять их имена запятой. Следующий оператор, например, объявляет три переменных с плавающей точкой:

float salary, income_tax, retirement_fund;

Изучение переменных

Переменная представляет собой имя ячейки в памяти компьютера. Во время выполнения ваши программы хранят информацию в переменных. При создании программ вы должны объявлять переменные, сообщая компилятору C++ имя и тип переменной. Например, следующий оператор объявляет переменную с именем age типа int:

int age;

Смысловые имена переменных

Каждая создаваемая вами переменная должна иметь уникальное имя. Чтобы сделать свои программы более легкими для чтения и понимания, следует использовать смысловые имена переменных. Например, следующий оператор объявляет три переменных с именами х, y и z:

int х, у, z;

Предположим, что эти переменные хранят возраст, тестовые очки и оценку студента, тогда следующие имена переменных более понятны по смыслу для других программистов, читающих ваш исходный код:

int student_age, test_score, grade;

При выборе имен переменных можно использовать комбинацию букв, цифр и подчеркивания (_). Первый символ в имени переменной должен быть буквой или подчеркиванием. Нельзя начинать имя переменной с цифры. Кроме того, в C++ буквы нижнего и верхнего регистров считаются разными. Сначала для имен своих переменных используйте только буквы нижнего регистра. Как только вы освоитесь в C++, можете комбинировать буквы верхнего и нижнего регистров для получения смысловых имен, как показано ниже:

float MonthlySalary, IncomeTax;

Слова, которые нельзя использовать для имен переменных

При создании имен переменных необходимо знать, что в C++ слова, перечисленные в табл. 4.2, резервируются в качестве ключевых слов, имеющих специальное значение для компилятора. Вы не имеете права использовать ключевые слова C++ в качестве имен переменных.

Таблица 4.2. Ключевые слова C++.

asm | auto | break | case | catch | char | class | const
default | delete | do | double | else | enum | extern | float
friend | goto | if | inline | int | long | new | operator
protected | public | register | return | short | signed | sizeof | static
switch | template | this | throw | try | typedef | union | unsigned
void | volatile | while | continue | for | private | struct | virtual

Почему ваши программы используют переменные

По мере усложнения ваши программы могут выполнять самые разнообразные операции. Например, программа платежей обрабатывает информацию о каждом служащем. В такой программе вы могли бы использовать переменные с именами employee_name, employee_id, employee_salary и т. д. После запуска программа занесет информацию о первом служащем в эти переменные. После расчета оклада первого служащего программа повторит весь процесс для следующего служащего. Чтобы рассчитать оклад второго служащего, программа занесет информацию о втором служащем (его или ее имя, номер и оклад) в перечисленные выше переменные, а затем выполнит свою обработку. Другими словами, в процессе выполнения программа присваивает переменной разные значения, которые, в свою очередь, изменяют или варьируют значение переменной.

ПРИСВАИВАНИЕ ЗНАЧЕНИЯ ПЕРЕМЕННОЙ

Как вы уже знаете, переменные хранят значения во время выполнения программы. После объявления переменной вы используете оператор присваивания C++ (знак равно), чтобы присвоить значение переменной. Следующие операторы присваивают значения нескольким разным переменным. Обратите внимание на использование точки с запятой в конце каждого оператора:

age = 32;
salary = 25000.75;
distance_to_the_moon = 238857;

Замечание: Значения, присваиваемые переменным, не должны содержать запятые (например, 25,000.75 и 238,857) *. Если вы включаете запятые, компилятор C++ будет генерировать и выводить сообщения о синтаксических ошибках.

Фрагмент следующей программы сначала объявляет переменные, а затем использует оператор присваивания, чтобы присвоить переменным значения:

*Американская запись чисел предполагает отделение каждых трех разрядов целой части числа запятой. - Прим. ред.

#include <iostream.h>

void main(void)

{
int age;
 float salary;
long distance_to_the_moon;

   age
    = 32;
salary = 25000.75;
distance_to_the_moon = 238857;
}

Присваивание значения при объявлении

При объявлении переменной часто удобно присваивать ей начальное значение. Чтобы упростить такую процедуру, C++ позволяет присваивать значение во время объявления переменной, как показано ниже:

int age = 32;
float salary = 25000.75;
long distance_to_the_moon = 238857;

Многие программы, представленные в этой книге, присваивают значения переменным при объявлении.

Присваивание значений переменным

При выполнении программы переменные хранят информацию. Для записи значения в переменную программы должны использовать оператор присваивания C++ (знак равно). Следующая строка использует оператор присваивания для записи значения 4 в переменную lesson:

lesson = 4;

Для упрощения этого процесса в C++ можно также присвоить значение переменной при ее объявлении:

int lesson = 4;

ИСПОЛЬЗОВАНИЕ ЗНАЧЕНИЯ ПЕРЕМЕННОЙ

После присвоения значения переменной ваши программы могут использовать это значение, просто обращаясь к ее имени. Следующая программа SHOWVARS.CPP присваивает значения трем переменным и затем выводит значение каждой переменной, используя cout:

#include <iostream.h>

void main (void)

{
int age = 32,
 float salary = 25000.75;
 long distance_to_the_moon = 238857;
 cout << "Служащему " << age << " года (лет)" << endl;
 cout << "Оклад служащего составляет $" << salary << endl;
 cout << "От земли до луны " << distance_to_the_moon << " миль" << endl;
}

Замечание: Последний оператор cout не помещается на одной строке. В этом случае программа просто переносит слова на следующую строку. Вы можете сделать такой перенос, поскольку C++ использует точку с запятой для указания конца оператора. Если вам необходимо перенести строку, постарайтесь не делать этого в середине символьной строки (внутри двойных кавычек), используйте дополнительный отступ для перенесенной части строки, как показано выше.

Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод:

С:\> SHOWVARS <ENTER>

Служащему 32 года (лет)

Оклад служащего составляет $25000.75

От земли до луны 238857 миль

Как видите, для использования значения переменной вы просто обращаетесь к имени переменной в вашей программе.

ПРЕВЫШЕНИЕ ДИАПАЗОНА ЗНАЧЕНИЙ ПЕРЕМЕННОЙ

Как вы уже знаете, тип переменной определяет набор значений, которые переменная может хранить. Например, переменная типа int может хранить значения в диапазоне от -32768 до 32767. Если вы присваиваете переменной значение, которое находится вне этого диапазона, возникает ошибка переполнения. Например, следующая программа OVERFLOW.CPP иллюстрирует, как превышение диапазона значений переменной приводит к ошибке. Как видите, программа присваивает значения, которые находятся вне диапазона для переменной каждого типа:

#include <iostream.h>

void main(void)

{
int positive = 40000;
long big_positive = 4000000000;
char little_positive = 210;
cout << "сейчас positive содержит " << positive
    << endl;
cout << "сейчас big_positive содержит " << big_positive
    << endl;
cout << "сейчас little_poaitive содержит " <<
    little_positive << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

сейчас positive содержит -25536

сейчас big_positive содержит -294967296

сейчас little_positive содержит Т

Как видите, программа присваивает переменным типа int, long и char значения, которые находятся вне диапазона хранения каждого типа, поэтому возникает ошибка переполнения. При работе с переменными вам необходимо помнить диапазон значений, которые может хранить переменная каждого типа. Ошибки переполнения плохо уловимы, и поэтому их трудно определить и исправить. Обратите внимание на значение, которое программа выводит для переменной little_positive. Поскольку это переменная типа char, выходной поток cout пытается вывести ее значение в символьном виде. В этом случае выведенное значение соответствует второй половине таблицы ASCII со значением 210.

ПРЕДСТАВЛЕНИЕ О ТОЧНОСТИ

В предыдущем разделе вы узнали, что ошибки переполнения возникают при присваивании переменной значения, которое находится вне диапазона значений для переменной данного типа. Подобно этому вам также необходимо знать, что компьютеры не могут обеспечить неограниченную точность при хранении чисел. Например, при работе с числами с плавающей точкой (числа с десятичной точкой) компьютер не всегда может представить число с требуемой точностью. Поэтому возможны ошибки округления, которые тяжело обнаружить.

Следующая программа PRECISE. CPP присваивает значение чуть меньше 0.5 переменным типа float и double. К сожалению, поскольку компьютер обладает ограниченной способностью в представлении чисел, переменные реально содержат не присваиваемые им значения, а число 0.5:

#include <iostream.h>

void main(void)

{
 float f_not_half = 0.49999990;
 double d_not_half = 0.49999990;
 cout << "Значение типа float 0.49999990 равно " << f_not_half << endl;
 cout << "Значение типа double 0.49999990 равно " << d_not_half << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

Значение типа float 0.49999990 равно 0.5

Значение типа double 0.49999990 равно 0.5

Как видите, значения, присваиваемые программой переменным, и значения, которые переменные содержат в действительности, не идентичны. Такие ошибки округления происходят потому, что компьютер должен представлять числа, используя фиксированное количество единиц и нулей. В большинстве случаев компьютер может точно представлять числа. Однако иногда, как показано в этой программе, компьютер представляет числа приближенно, а не точно. При программировании вам необходимо всегда помнить о точности. В зависимости от значений, с которыми работают ваши программы, могут возникать трудно обнаруживаемые ошибки округления.

ИСПОЛЬЗОВАНИЕ КОММЕНТАРИЕВ ДЛЯ УЛУЧШЕНИЯ ЧТЕНИЯ ВАШИХ ПРОГРАММ

По мере усложнения ваших программ количество содержащихся в них операторов может сделать программы слишком трудными для понимания. Поскольку другим программистам может потребоваться понять и, возможно, изменить ваши программы, вам следует делать программы более удобочитаемыми. Способы улучшения удобочитаемости программ включают:

  • Использование понятных по смыслу имен переменных, описывающих их применение.
  • Использование подходящих отступов и выравнивания (см. урок 7).
  • Использование пустых строк для разделения несвязанных операторов.
  • Использование комментариев, которые объясняют работу программы.

При создании программ вы можете поместить в исходном файле замечания, которые объясняют работу программы. Такие замечания (называемые комментариями) не только помогают другим программистам понять вашу программу, но могут напомнить, почему программа содержит определенные операторы, если вы ее посмотрите через несколько месяцев. Для размещения комментария в своих программах на C++ просто поставьте два знака прямого слеша (//), как показано ниже:

// Это комментарий

Когда компилятор C++ встречает двойной слеш, он игнорирует весь оставшийся на этой строке текст. По крайней мере размещайте в начале каждой программы комментарии, которые указывают, кто написал программу, когда и почему:

// Программа: BUDGET.CPP
// Программист: Kris Jamsa
// дата создания: 1-10-96
//
// Цель: Ежемесячная информация о бюджете.

При выполнении вашей программой различных процессов вам следует поместить до или после определенных операторов комментарии, которые объясняют их назначение. Например, рассмотрим следующий оператор присваивания:

distance_to_the_moon = 238857; // Расстояние в милях

Комментарий справа от оператора присваивания обеспечивает дополнительную информацию всем, кто читает программу. У начинающих программистов часто возникают проблемы, связанные с тем, что и когда комментировать. Как правило, вы включаете немного комментариев в ваши программы. Поэтому убедитесь, что они полезны. Следующие комментарии не дают дополнительной информации программистам, читающим код:

аде =32; // Присвоить 32 переменной age
salary = 25000.75; // Присвоить 25000.75 переменной salary

Цель использования комментариев заключается в том, чтобы объяснить работу программы.

Добавление комментариев в ваши программы

При создании программ включайте комментарии, которые объясняют работу программы. Если другим программистам понадобится изменить вашу программу, они смогут воспользоваться комментариями, чтобы понять поведение программы. Обычно в программах на C++ комментарий начинается с двойного слеша:

// Это комментарий C++

Когда компилятор C++ встречает двойной слеш, он игнорирует весь текст (оставшийся в текущей строке), который следует за слешем. Хорошие программы должны быть легки для чтения и понимания. Комментарии улучшают удобочитаемость вашей программы.

Замечание: В дополнение к использованию комментариев для улучшения удобочитаемости своей программы вам следует использовать пустые строки для разделения несвязанных операторов. Когда компилятор C++ встречает пустую строку, он просто пропускает ее.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что программы во время выполнения хранят информацию в переменных. Говоря кратко, переменная представляет собой имя, которое ваша программа назначает ячейке памяти, в которой программа хранит информацию. Прежде чем ваша программа сможет использовать переменную, вы должны объявить ее тип и имя. Из урока 5 вы узнаете, как выполнять простые операции, например, сложение и вычитание переменных. Однако до изучения урока 5 убедитесь, что вы освоили следующие основные концепции:

    1. Для использования переменной в вашей программе вы должны объявить ее тип и имя.
    2. Имена переменных должны быть уникальны и понятны по смыслу другим программистам, читающим ваш исходный текст. Имя переменной должно соответствовать ее назначению.
    3. Имена переменных должны начинаться с буквы или символа подчеркивания.
    4. В C++ буквы верхнего и нижнего регистров считаются разными.
    5. Тип переменной определяет тип значения, которое переменная может содержать. Общими типами переменных являются char, int, float и long.
    6. Комментарии повышают удобочитаемость вашей программы, поясняя ее работу. В программах на C++ комментарии начинаются с двойного слеша (//).


Untitled Document

Урок 5

Выполнение простых операций

Из урока 4 вы узнали, как объявлять и использовать переменные в своих программах. По мере усложнения программ вы будете выполнять арифметические операции, такие как сложение, вычитание, умножение и деление, над значениями, содержащимися в переменных. Данный урок описывает, как использовать арифметические операторы C++ для выполнения этих операций. К тому времени, когда вы закончите изучение данного урока, вы освоите следующие основные концепции:

  • Для выполнения математических операций используйте в своих программах арифметические операторы C++.
  • Чтобы гарантировать последовательность операций, C++ назначает приоритет каждому оператору.
  • Используя круглые скобки в арифметических выражениях, вы можете управлять порядком, в котором C++ выполняет операции.
  • Многие программы на C++ прибавляют или вычитают единицу, используя операции увеличения (++) или уменьшения (--).

После того как вы научитесь распознавать разные арифметические операторы C++, вы поймете, что выполнять математические операции очень легко!

ОСНОВНЫЕ МАТЕМАТИЧЕСКИЕ ОПЕРАЦИИ

Независимо от назначения большинство ваших программ на C++ будут складывать, вычитать, умножать или делить. Вы узнаете, что ваши программы могут выполнять арифметические операции с константами (например, 3*5) или с переменными (например, payment — total). Таблица 5.1 перечисляет основные математические операции C++:

Таблица 5.1. Основные математические операции С++

Операция | Назначение | Пример
+ | Сложение | total = cost + tax;
- | Вычитание | change = payment - total;
*. | Умножение | tax = cost * tax_rate;
/ | Деление | average = total / count;

Следующая программа SHOWMATH.CPP использует cout для вывода реультата нескольких простых арифметических операций:

#include <iostream.h>

void main(void)

{
 cout << "5 + 7 = " << 5 + 7 << endl;
 cout << "12 - 7 = " << 12 - 7 << endl;
 cout << "1.2345 * 2 = " << 1.23.45 * 2 << endl;
 cout << "15 / 3 = " << 15 / 3 << endl;
}

Посмотрите внимательно на операторы программы. Обратите внимание, что каждое выражение сначала появляется в кавычках, которые обеспечивают вывод символов (например, 5 + 7 =) на экран. Затем программа выводит результат операции и символ новой строки. Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> SHOWMATH <ENTER>

5 + 7 = 12

12 - 7 = 5

1.2345 * 2 = 2.469

15 / 3 = 5

В данном случае программа выполняла арифметические операции, используя только постоянные значения. Следующая программа MATHVARS.CPP выполняет арифметические операции, используя переменные:

#include <iostream.h>

void main(void)

{
float cost =15.50; // Стоимость покупки
float sales_tax = 0.06; // Налог на продажу 6%
float amount_paid = 20.00; // Деньги покупателя
float tax, change, total; // Налог на продажу, сдача покупателю
    и общий счет
tax = cost * sales_tax;
total = cost + tax;
change = amount_paid - total;
cout << "Стоимость покупки: $" << cost <<
    "\tHaлor: $" << tax << "\tОбщий счет: $" << total <<
    endl;
cout << "Сдача покупателю: $" << change <<
    endl;
}

В данном случае программа использует только переменные с плавающей точкой. Как видите, программа присваивает значения переменным при объявлении. Далее программа выполняет арифметические операции над переменными для определения налога на продажу, общего счета и сдачи покупателю. Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> MATHVARS <ENTER>

Стоимость покупки: $15.5 Налог: $0.93 Общий счет: $16.43

Сдача покупателю: $3.57

УВЕЛИЧЕНИЕ ЗНАЧЕНИЯ ПЕРЕМЕННОЙ НА 1

Обычной операцией, которую вы будете выполнять при программировании, является прибавление 1 к значению целой переменной. Например, предположим, что ваша программа использует переменную с именем count, чтобы сохранить данные о количестве напечатанных файлов. Каждый раз, когда программа печатает файл, 1 будет добавляться к текущему значению count. Используя оператор присваивания C++, ваша программа может увеличивать значение count, как показано ниже:

count = count + 1;

В данном случае программа сначала выбирает значение count, а затем добавляет к нему единицу. Далее программа записывает результат сложения обратно в переменную count. Следующая программа INTCOUNT.CPP использует оператор присваивания для увеличения переменной count (которая первоначально содержит значение 1000) на единицу (присваивая переменной результат 1001):

#include <iostream.h>

void main(void)

{
int count = 1000;
cout << "начальное значение count равно" << count << endl;
count = count + 1;
cout << "конечное значение count равно" << count << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> INCCOUNT <ENTER>

начальное значение count равно 1000

конечное значение count равно 1001

Так как увеличение значения переменной представляет собой обычную операцию в программах, в C++ есть операция увеличения — двойной знак плюс (++). Операция увеличения обеспечивает быстрый способ прибавления единицы к значению переменной. Следующие операторы, например, увеличивают значение переменной count на 1:

count = count + 1; count++;

Следующая программа INC_OP.CPP использует операцию увеличения для наращивания значения переменной count на 1:

#include <iostream.h>

void main(void)

{
int count = 1000;
cout << "начальное значение count равно " << count << endl;
count++;
cout << "конечное значение count равно " << count << endl;
}

Эта программа работает так же, как INCCOUNT.CPP, которая использовала оператор присваивания для увеличения значения переменной. Когда C++ встречает операцию увеличения, он сначала выбирает значение переменной, добавляет к этому значению единицу, а затем записывает результат обратно в переменную.

Представление о префиксной (до) и постфиксной (после) операциях увеличения

При использовании операций увеличения ваши программы могут размещать оператор увеличения до или после переменной, как показано ниже:

++variable; variable++;

Так как первый оператор появляется до переменной, он называется префиксным оператором увеличения. Аналогично этому, второй оператор появляется после переменной и называется постфиксным оператором увеличения. Вам необходимо знать, что C++ трактует эти два оператора по-разному. Например, рассмотрим следующий оператор присваивания:

current_count = count++;

Этот оператор присваивания указывает C++ присвоить текущее значение count переменной current_count. В дополнение к этому постфиксный оператор увеличения заставляет C++ увеличить текущее значение count. Использование постфиксного оператора в этом случае делает показанный выше оператор эквивалентным следующим двум операторам:

current_count = count;

count = count + 1;

Теперь рассмотрим следующий оператор присваивания, который использует префиксный оператор увеличения:

current_count = ++count;

В этом случае оператор присваивания указывает C++ сначала увеличить значение count, а затем присвоить результат переменной current_count. Использование префиксного оператора увеличения делает показанный выше оператор эквивалентным следующим двум операторам:

count = count + 1;

current_count = count;

Важно освоить префиксную и постфиксную операции увеличения, так, как они будут встречаться вам в большинстве программ на C++. Следующая программа PRE_POST.CPP иллюстрирует использование префиксной и постфиксной операций увеличения:

#include <iostream.h>

void main(void)

{
 int small_count = 0;
 int big_count = 1000;
 cout << "small_count равно " << small_count << endl;
 cout << "small_count++ производит " << small_count++ << endl;
 cout << "конечное значение small_count равно " << sniall_count << endl;
cout << "big_count равно " << big_count << endl;
 cout << "++big_count производит " << ++big_count << endl;
 cout << "конечное значение big_count равно " << big_count << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> PRE_POST <ENTER>

small_count равно 0

small_count++ производит 0

конечное значение small_count равно 1

big_count равно 1000

++big_count производит 1001

конечное значение big_count равно 1001

С переменной small_count программа использует постфиксную операцию увеличения. В результате программа выводит текущее значение переменной (0), а затем увеличивает его на 1. С переменной big_count программа использует префиксную операцию увеличения. В результате программа сначала увеличивает значение переменной (1000 + 1), а затем выводит результат (1001). Найдите время, чтобы отредактировать эту программу, и сначала измените постфиксную операцию на префиксную, а затем префиксную на постфиксную. Откомпилируйте и запустите программу, обращая внимание на то, как изменение операции изменяет вывод.

С++ обеспечивает также операции уменьшения

Как вы уже знаете, двойной знак плюс (++) представляет собой оператор увеличения C++. Подобным образом двойной знак минус (--) соответствует оператору уменьшения C++, который уменьшает значение переменной на 1. Как и в случае с операцией увеличения, C++ поддерживает префиксный и постфиксный операторы уменьшения. Следующая программа DECCOUNT.CPP иллюстрирует использование оператора уменьшения C++:

#include <iostream.h>

void main(void)

{
int small_count = 0;
int big_count = 1000;
cout << "small_count равно " << small_count
    << endl;
cout << "small_count-- производит " << small_count--
    << endl;
cout << "конечное значение small_count равно " “ small_count
    << endl;
cout << "big_count равно " << big_count <<
    endl;
cout << "--big_count производит " << --big_count
    << endl;
cout << "конечное значение big_count равно " <<
    big_count << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> DECCOUNT <ENTER”

small_count равно 0

small_count-- производит 0

конечное значение small_count равно -1

big_count равно 1000

—big_count производит 999

конечное значение big_count равно 999

Как видите, префиксный и постфиксный операторы уменьшения C++ работают так же, как и соответствующие операторы увеличения, с той лишь разницей, что они уменьшают значение переменной на 1.

ДРУГИЕ ОПЕРАТОРЫ С++

В этом уроке описаны обычные арифметические операции C++, а также операции увеличения и уменьшения. В программах на C++ вы можете встретить одну или несколько операций, перечисленных в табл. 5.2:

Таблица 5.2. Операции C++, которые вы можете встретить в программах.

Операция | Функция
% | Взятие по модулю или остаток; возвращает остаток целочисленного деления
~ | Дополнение; инвертирует биты значений
& | Побитовое И
| | Побитовое включающее ИЛИ
^ | Побитовое исключающее ИЛИ
<< | Сдвиг влево; сдвигает биты значения влево на указанное количество разрядов
>> | Сдвиг вправо; сдвигает биты значения вправо на указанное количество разрядов

СТАРШИНСТВО ОПЕРАЦИЙ

При выполнении арифметических операций в C++ необходимо знать, что C++ выполняет операции в определенном порядке, основанном на старшинстве операций. Например, операция умножения выполняется до сложения. Чтобы лучше понять старшинство операций, рассмотрите следующие выражения:

result =5+2*3;

В зависимости от порядка, в котором C++ выполняет умножение и сложение, результат будет разным:

result =5+2*3;
=7*3;
= 21;
result =5+2*3;
 =5+6;
 = 11;

Чтобы избежать путаницы, C++ присваивает каждой операции приоритет, который определяет порядок выполнения операций. Так как C++ выполняет операции в определенном порядке, то и ваши программы будут проводить арифметические вычисления соответствующим образом.

Таблица 5.3 перечисляет старшинство операций C++. Операции, находящиеся в верхней части, имеют более высокий приоритет. Операции внутри каждой части имеют одинаковый приоритет. Если вы рассмотрите таблицу, то увидите, что в C++ умножение имеет более высокий приоритет, чем сложение. Вы не знакомы со многими операциями, представленными в таблице. В настоящее время не думайте об этих операциях. К концу изучения этой книги вы сможете использовать (и понять) каждую из них!

Таблица 5.3. Старшинство операций в C++.

Операция | Имя | Пример
:: Разрешение области видимости classname::classmember_name
:: | Глобальное разрешение | ::variable_name
. | Выбор элемента | object.member_name
-> | Выбор элемента | pointer->membername
[] | Индексация | pointer[element]
() | Вызов функции | expression(parameters)
() | Построение значения | type(parameters)
sizeof | Размер объекта | sizeof expression
sizeof | Размер типа | sizeof(type)
++ | Приращение после | variable++
++ | Приращение до | ++variable
-- | Уменьшение после | variable--
-- | Уменьшение до | -- variable
& | Адрес объекта | &variable
* | Разыменование | *pointer
new | Создание (размещение) | new type
delete | Уничтожение (освобождение) delete pointer
delete[] | Уничтожение массива | delete pointer
~ | Дополнение | ~expression
! | Логическое НЕ | ! expression
+ | Унарный плюс | +1
- | Унарный минус | -1
() | Приведение | (type) expression
.* | Выбор элемента | object.*pointer
-> | Выбор элемента | object->*pointer
* | Умножение | expression * expression
/ | Деление | expression / expression
% | Взятие по модулю | expression % expression
+ | Сложение (плюс) | expression + expression
- | Вычитание (минус) | expression expression

Управление порядком, в котором C++ выполняет операции

Как вы уже знаете, C++ назначает операциям различный приоритет, который и управляет порядком выполнения операций. К сожалению, иногда порядок, в котором C++ выполняет арифметические операции, не соответствует порядку, в котором вам необходимо их выполнить. Например, предположим, что вашей программе необходимо сложить две стоимости и затем умножить результат на налоговую ставку:

cost = price_a + price_b * 1.06;

К сожалению, в этом случае C++ сначала выполнит умножение (price_b * 1.06), а затем прибавит значение price_a.

Если ваши программы должны выполнять арифметические операции в определенном порядке, вы можете заключить выражение в круглые скобки. Когда C++ оценивает выражение, он сначала всегда выполняет операции, сгруппированные в круглых скобках. Например, рассмотрим следующее выражение:

result =(2+3)* (3+4);

C++ вычисляет данное выражение в следующем порядке:

result = (2 + 3) * (3
    + 4);
= (5) * (3 + 4);
= 5 * (7);
=5*7;
= 35;

Подобным образом группируя выражения внутри круглых скобок, вы можете управлять порядком, в котором C++ выполняет арифметические операции. Аналогично предыдущему примеру, ваша программа может сложить две стоимости внутри круглых скобок, как показано ниже:

cost = (price_a + price_b) * 1.06;

СЛЕДИТЕ ЗА ОШИБКАМИ ПЕРЕПОЛНЕНИЯ ПРИ АРИФМЕТИЧЕСКИХ ОПЕРАЦИЯХ

Из урока 4 вы узнали, что, если вы присваиваете переменной значение, которое не входит в диапазон значений для данного типа переменной, возникает ошибка переполнения. При выполнении арифметических операций необходимо помнить о возможности возникновения ошибок переполнения. Например, следующая программа MATHOVER.CPP умножает 200 на 300 и присваивает результат переменной типа int. Однако, поскольку результат умножения (60000) превышает наибольшее возможное значение для типа int (32767), возникает ошибка переполнения:

#include <iostream.h>

void main(void)

{
int result;
result = 200 * 300;
cout << "200 * 300 = " << result << endl;
}

Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод:

С:\> MATHOVER <ENTER>

200 * 300 = -5536

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

В данном уроке вы изучали обычные арифметические операции и операции приращения C++. Чтобы программы корректно выполняли арифметические вычисления, C++ назначает каждой операции приоритет, который управляет порядком выполнения операций. Из урока 6 вы узнаете, как использовать входной поток с именем cin для выполнения операций ввода с клавиатуры. До изучения урока 6 убедитесь, что вы освоили следующее:

  1. C++ использует операторы +,- , * и / для сложения, вычитания, умножения и деления.
  2. C++ обеспечивает префиксные (до) и постфиксные (после) операции увеличения, которые прибавляют единицу к значению переменной.
  3. C++ обеспечивает префиксную (до) и постфиксную (после) операции уменьшения, которые вычитают единицу из значения переменной.
  4. Префиксные (до) операции указывают C++ сначала увеличить (или уменьшить) значение переменной, а затем использовать это значение.
  5. Постфиксные (после) операции указывают C++ сначала использовать значение переменной, а затем увеличить (или уменьшить) его.
  6. Чтобы гарантировать, что выражения выполняются корректно, C++ назначает каждой операции приоритет, управляющий порядком выполнения операций.
  7. Если вам нужно управлять порядком выполнения арифметических операций, используйте круглые скобки. C++ всегда вычисляет сначала выражение в скобках.


Untitled Document

Урок 6

Чтение ввода с клавиатуры

В этой книге ваши программы использовали выходной поток cout для отображения вывода на экран. Из данного урока вы узнаете, что C++ обеспечивает входной поток с именем cin, из которого программы могут читать информацию, введенную пользователем с клавиатуры. При использовании cin для чтения ввода с клавиатуры вы указываете одну или несколько переменных, которым cin будет присваивать входные значения. К тому времени, когда вы закончите этот урок, вы освоите следующие основные концепции:

  • Вы можете использовать входной поток cin для присваивания определенным переменным символов и чисел, введенных с клавиатуры.
  • При применении cin для чтения и присваивания переменной значения, введенного с клавиатуры, можно использовать содержимое переменной так, как если бы ваша программа использовала оператор присваивания для сохранения значения в переменной.
  • Когда ваша программа использует cin для выполнения ввода с клавиатуры, остерегайтесь ошибок переполнения и ошибок, возникающих при вводе пользователем значения неверного типа.

Как вы уже знаете, если ваши программы используют выходной поток cout, они помещают данные в поток с помощью оператора вставки (<<). Подобным образом, если ваши программы применяют cin для чтения ввода с клавиатуры, они будут использовать оператор извлечения (>>).

ПЕРВОЕ ЗНАКОМСТВО С cin

Точно так же как выходной поток cout позволяет вашим программам записать вывод на экран, входной поток cin позволяет программам читать ввод с клавиатуры. Когда программы используют cin для чтения ввода с клавиатуры, они должны указать переменную, в которую cin поместит данные. Следующая программа FIRSTCIN.CPP использует cin для чтения числа, введенного с клавиатуры. Программа присваивает введенное число переменной с именем number, а затем выводит значение переменной, используя выходной поток cout:

#include <iostream.h>

void main(void)

{
int number; II Число, читаемое с клавиатуры
cout << "Введите ваше любимое число и нажмите Enter: ";
cin >> number;
cout << "Ваше любимое число равно " << number << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится сообщение, предлагающее вам ввести ваше любимое число. Если вы введете число и нажмете ENTER, программа присвоит ввод переменной number. Затем, используя cout, программа выведет сообщение, отображающее ваше любимое число.

Следующая программа TWONBRS.CPP запрашивает у вас два числа. Программа присваивает числа переменным first и second. Затем программа выводит числа, используя cout:

#include <iostream.h>

void main(void)

{
int first, second; // Числа, введенные с клавиатуры
cout << "Введите два числа и нажмите Enter: ";
cin >> first >> second;
cout << "Были введены числа " << first << " и " << second << endl;
}

Обратите внимание на использование с cin двух операторов извлечения:

cin >> first >> second;

В этом случае cin присвоит первое введенное значение переменной first, a второе переменной second. Если для вашей программы требуется третье значение, вы можете использовать третий оператор извлечения, как показано ниже:

cin >> first >> second >> third;

Если вы применяете cin для чтения чисел с клавиатуры, cin использует левый пустой символ (пробел, табуляцию, возврат каретки), чтобы определить, где начинается одно значение, а где второе. Экспериментируйте с программой TWONBRS, разделяя числа табуляцией, пробелом и возвратом каретки.

Чтение ввода с клавиатуры с помощью cin

Для чтения ввода с клавиатуры программы могут использовать входной поток cin. При использовании cin вы должны указать переменную, в которую cin помещает данные. Затем используйте оператор извлечения (>>) для направления данных, как показано ниже:

cin >> some_variable;

Оператор извлечения называется так, потому что он извлекает (удаляет) данные из входного потока, присваивая значение указанной переменной.

Следите за ошибками переполнения

Если ваши программы выполняют ввод с использованием cin, остерегайтесь возможных ошибок, возникающих при вводе пользователем неверного числа. Например, запустите программу FIRSTCIN, которую вы только что создали. Когда программа запросит вас ввести ваше любимое число, введите число 1000000 и нажмите ENTER. При этом программа не сможет отобразить число 1000000 в качестве введенного значения. Вместо этого возникнет ошибка переполнения, так как 1000000 превышает наибольшее значение, которое может хранить тип int.

Если вы внимательно рассмотрите программу FIRSTCIN. CPP, то обратите внимание, что cin присваивает введенное число переменной типа int. Как вы узнали из урока 4, переменные типа int могут хранить значения только в диапазоне от -32768 до 32767. Поскольку переменная типа int не может вместить значение 1000000, возникает ошибка. Запустите программу еще несколько раз, вводя отрицательные и положительные числа. Обратите внимание на ошибки, которые возникают, если вы выходите за допустимые пределы значений для той переменной, в которую cin помещает ввод.

Следите за ошибками несовпадения типов

Как уже обсуждалось, программа FIRSTCIN.CPP предполагает, что пользователь вводит значение в диапазоне от -32768 до 32767. Если вместо ввода числа вне этого диапазона, пользователь вводит буквы или другие символы, то возникает другая ошибка — ошибка несовпадения типов. Другими словами, программа ожидала значение одного типа (int), а пользователь ввел значение другого типа (char). Для примера запустите программу второй раз. Когда программа запросит число, введите буквы АВС. Как и раньше, возникнет ошибка, поскольку программа ожидает целое число, а не буквы.

Выполните подобные эксперименты с программой TWONBRS, вводя бессмысленные значения или числа с плавающей точкой. Вы обнаружите, что программа наталкивается на те же ошибки. В последующих уроках вы научитесь выполнять операции ввода таким образом, чтобы уменьшить возможность подобных ошибок. А сейчас просто помните, что такие ошибки могут возникать.

Чтение символьных данных

Обе предыдущие программы использовали cin для чтения целых чисел в переменные типа int. Следующая программа CIN_CHAR.CPP использует входной поток cin для чтения символов с клавиатуры. Как видите, программа читает символ в переменную типа char.

#include <iostream.h>

void main(void)

{
char letter;
 cout << "Введите любой символ и нажмите Enter: ";
 cin >> letter;
 cout << "Был введен символ " << letter << endl;
}

Откомпилируйте и поэкспериментируйте с данной программой, вводя более одного символа и наблюдая за реакцией программы. Вы обнаружите, что программа каждый раз работает только с одним символом.

Чтение слов с клавиатуры

Во второй части данной книги вы научитесь сохранять слова или даже строки текста в одной переменной. Там же вы узнаете, как использовать входной поток cin для чтения слов и целых строк. А сейчас можете создать свою собственную простую программу, которая читает значения типа float или long. Например, следующая программа CIN_LONG.CPP использует cin для чтения значения типа long:

#include <iostream.h>

void main(void)

{
long value;
cout << "Введите большое число и нажмите Enter: ";
cin >> value;
cout << "Было введено число " << value <<
    endl;
}

Как и раньше, поэкспериментируйте с этой программой, вводя очень большие числа (и отрицательные тоже).

Перенаправление В/В и входной поток cin

Как вы уже знаете из урока 3, если ваши программы используют выходной поток cout, пользователь может перенаправить вывод программы с экрана дисплея в файл или на принтер. Как уже обсуждалось, выходной поток cout соответствует стандартному выводу операционной системы. Подобным образом входной поток cin соответствует стандартному вводу операционной системы. В результате, если ваша программа использует cin для выполнения операций ввода, пользователь может перенаправить ввод программы с клавиатуры на файл. В последующих уроках вы научитесь писать программы, которые читают и обрабатывают перенаправленный ввод.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

В этом уроке вы научились использовать входной поток cin для выполнения ввода с клавиатуры. Как вы уже знаете, если ваши программы используют cin для чтения ввода с клавиатуры, вам следует указать переменные, которым cin присваивает вводимые значения. В уроке 7 вы научитесь использовать оператор C++ if, чтобы позволить программам принимать собственные решения. Однако перед тем, как приступить к уроку 7, убедитесь, что вы освоили следующие основные концепции:

    1. C++ предоставляет входной поток cin, который ваши программы могут использовать для чтения ввода с клавиатуры.
    2. Если программы для чтения ввода используют cin, они должны указать одну или несколько переменных, в которые cin будет помещать данные.
    3. Чтобы направить ввод в переменную, вам следует использовать cin с оператором извлечения (>>).
    4. При применении cin для чтения нескольких значений, cin использует пустые символы (пробел, табуляция или возврат каретки), чтобы определить, где заканчивается одно значение и начинается другое.
    5. Если пользователь вводит неверные данные, могут возникать ошибки переполнения или несоответствия типов, а значения, присвоенные входным потоком cin переменным вашей программы, будут неверны.


Untitled Document

Урок 7

Программа принимает решение

Как вы уже знаете, программа представляет собой последовательность инструкций, выполняемых компьютером для реализации определенных задач. Все созданные вами до сих пор простые программы на C++ выполняли операторы по порядку, начиная с первого до конца программы. По мере усложнения программ вам потребуется, чтобы выполнялся один набор операторов, если определенное условие соблюдается, и другой набор, если условие не соблюдается. Другими словами, вам потребуется, чтобы ваши программы приняли решение и соответственно отреагировали. В этом уроке описывается оператор C++ if, который будет использоваться вашей программой для принятия подобных решений. К концу данного урока вы освоите следующие основные концепции:

  • Программы на C++ используют операции сравнения, чтобы определить, равны ли два значения или одно значение больше или меньше другого.
  • Для принятия решений используется оператор C++ if.
  • Операторы C++ могут быть простыми (одна операция) или составными (несколько операций, сгруппированных внутри правой и левой фигурных скобок {}).
  • Ваши программы используют оператор C++ if-else для выполнения одного набора операторов, если определенное условие соблюдается, и другого набора операторов, если условие не соблюдается.
  • Комбинируя несколько операторов if-else, программы могут проверять несколько условий.
  • Используя логические операторы C++ И и ИЛИ, ваши программы могут проверить несколько условий, например: Есть ли у пользователя собака И долматин ли это ?

Программы, которые принимают решения, выполняют условную обработку. Другими словами, на основании результата одного или нескольких Условий программа будет выполнять определенные операторы. Экспериментируйте с программами данного урока; ваш набор инструментальных средств C++ уже достаточно обширен для создания полезных программ.

СРАВНЕНИЕ ДВУХ ЗНАЧЕНИЙ

Когда ваши программы принимают решение, они обычно выполняют некоторый вид проверки. Например, одна программа может проверять, равны ли тестовые очки студента 100, а вторая программа — составляет ли стоимость покупки больше $50.00. Для выполнения подобных проверок ваши программы будут использовать операции сравнения C++, перечисленные в табл. 7.1. Операции сравнения позволяют проверять, каким образом одно значение соотносится с другим. Другими словами, используя операции сравнения, программы могут проверить, больше одно значение, меньше или равно другому.

Таблица 7.1. Операции сравнения C++.

Операция | Проверка | Пример
== | Если два значения равны | (score == 100)
!= | Если два значения не равны | (old != new)
> | Если первое значение больше второго | (cost > 50.00)
< | Если первое значение меньше второго | (salary < 20000.00)
>= | Если первое значение больше или равно второму | (stock_price >= 30.0)
<= | Если первое значение меньше или равно второму | (age <= 21)

Если ваша программа использует операции сравнения для сравнения двух значений, результат сравнения может быть истиной или ложью. Другими словами, два значения или равны (истина), или нет (ложь). Каждый из операторов if, представленных в этой книге, использует операции сравнения, перечисленные в табл. 7.1.

ЗНАКОМСТВО С ОПЕРАТОРОМ if

Оператор C++ if позволяет вашим программам осуществлять проверку и затем на основании этой проверки выполнять операторы. Формат оператора if следующий:

if (условие_выполняется) оператор;

Обычно оператор if выполняет проверку, используя операцию сравнения C++. Если результат проверки является истиной, if выполняет оператор, который следует за ним. Следующая программа FIRST_IF.CPP использует оператор if для сравнения значения переменной test_score со значением 90. Если набранные тестовые очки больше или равны 90, программа выведет сообщение пользователю, что он получил А. В противном случае, если значение меньше 90, программа просто завершается:

#include <iostream.h>

void main(void)

{
int test_score = 95;
if (test_score >= 90) cout << "Поздравляем, вы получили А!" << endl;
}

Как видите, для выполнения проверки программа использует операцию сравнения C++ "больше или равно" (>=). Если результат сравнения значений является истиной, программа выполняет оператор, который следует за if, в данном случае вывод сообщения с использованием cout. Если результат сравнения не является истинным, программа не выводит сообщение. Экспериментируйте с этой программой, изменяя проверочные очки (в том числе и меньше 90), и обратите внимание на работу оператора if.

Представление о простых и составных операторах

При использовании оператора if для условной обработки в некоторых случаях, если условие истинно, программе потребуется выполнить один оператор, а в других случаях несколько операторов. Когда программа выполняет только один оператор, следующий за if, такой оператор называется простым оператором:

if (test_score >= 90) cout << "Поздравляем, вы получили A!" << endl; //-------> Простой оператор

Если программе необходимо выполнить несколько инструкций, когда результат сравнения — истина, операторы должны быть сгруппированы внутри левой и правой фигурных скобок {}. Операторы, которые находятся внутри фигурных скобок, образуют составной оператор, как показано ниже:

if (test_score >= 90)

{
cout << "Поздравляем, вы получили A!" << endl;
cout << "Ваши тестовые очки были " << test_Score << endl;
} //----------------->Составной оператор

Вам необязательно запоминать термины "простой" и "составной" операторы, но вы должны знать, что следует группировать связанные операторы внутри левой и правой фигурных скобок. Следующая программа COMPOUND.CPP представляет собой измененный вариант предыдущей и выводит два сообщения, если тестовые очки больше или равны 90:

#include <iostream.h>

void main(void)

{
 int test_score = 95;

if (test_score >= 90)

{
 cout << "Поздравляем, вы получили А!" << endl;
 cout << "Ваши тестовые очки были " << test_score << endl;
}

}

Использование простых и составных операторов

При выполнении условной обработки в некоторых случаях программе необходимо выполнить только один оператор (простой оператор), если условие истинно. Однако в других случаях программа должна будет выполнить несколько операторов (составной оператор). Если в зависимости от результата определенного условия вашей программе нужно выполнить два или несколько связанных операторов, вы должны сгруппировать операторы внутри левой и правой фигурных скобок, как показано ниже:

if (age >= 21)

{
cout << "Все на выборы!" << endl;
cout << "Это то, что вам надо!" << endl;
}

ОПЕРАТОР else

Две предыдущие программы использовали оператор if, чтобы определить, больше тестовые очки или равны 90. Если условие было истинным, программы выводили сообщение на экран. В противном случае, т. е. если тестовые очки были меньше 90, программы не выводили сообщение, они просто завершались. В большинстве случаев вашим программам потребуется указать один набор операторов, выполняющийся, если условие истинно, и второй набор, выполняющийся, если условие ложно. Для указания операторов, которые должны выполняться, когда условие ложно, ваши программы должны использовать оператор else. Ниже приведен формат оператора else:

if (условие_истинно)
 оператор;
else
 оператор;

Следующая программа IF_ELSE.CPP использует оператор if, чтобы проверить, больше тестовые очки или равны 90. Если условие истинно, программа выводит поздравление. Если условие ложно, программа сообщает, что студент должен работать усерднее:

#include <iostream.h>

void main(void)

{
int test_score =95;
if (test_score >= 90)
 cout << "Поздравляю, вы получили А!" << endl;
else
 cout << "В следующий раз вы должны" << " работать усерднее!" << endl;
}

ПРИМЕНЕНИЕ СОСТАВНЫХ ОПЕРАТОРОВ ДЛЯ else

Как вы уже знаете, составной оператор представляет собой группу связанных операторов, заключенных между левой и правой фигурными скобками. Когда программа использует else для указания операторов, выполняющихся, если условие ложно, то для указания нескольких операторов можно использовать составной оператор. Следующая программа CMP_ELSE.CPP использует составные операторы как для if, так и для else:

#include <iostream.h>

void main(void)

{
int test_score = 65;
if (test_score >= 90)
{
cout << " Поздравляю,
    вы получили A!" << endl;
cout << "Ваши
    тестовые очки были " << test_score << endl;
}
else
{
cout << "Вы должны
    работать усерднее!" << endl;
cout << "Вы потеряли
    " << 100 - test_score << " очков " << endl;
}
}

Как и ранее, найдите время поэкспериментировать с этой программой, изменяя значения переменной test_score так, чтобы оно было меньше или больше 90. Следующая программа GETSCORE.CPP использует входной поток cin для ввода тестовых очков пользователем. Затем программа сравнивает тестовые очки со значением 90, выводя соответствующее сообщение:

#include <iostream.h>

void main(void)

{
 int test_score;
 cout << "Введите тестовые очки и нажмите Enter: ";
 cin >> test_score;
 if (test_score >= 90)
{
 cout << "Поздравляем, вы получили А!" << endl;
 cout << "Ваши тестовые очки были " << test_score << endl;
 }
 else
{
 cout << "Вы должны работать усерднее!" << endl;
cout << "Вы потеряли " << 100 - test_score << " очков " << endl;
 }
}

Откомпилируйте и запустите эту программу. Вы увидите, что при комбинации операций ввода с условной обработкой ваши программы станут более совершенными.

Представление об if-else

По мере усложнения ваши программы будут проверять разные условия и выполнять один набор операторов, если условие истинно, и другой набор, если условие ложно. Для выполнения такой условной обработки программы используют операторы if-else, как показано ниже:

if (условие_истинно)
оператор;
else
 оператор;

Когда программе требуется выполнить несколько операторов, если условие ложно или истинно, вы должны сгруппировать связанные операторы внутри левой и правой фигурных скобок {}:

if (условие_истинно)

{
 первый_оператор_для_истины;
 второй_оператор_для_истины;
 }
else
{
 первый_оператор_для_лжи;
 второй_оператор_для_лжи;
 }

ИСПОЛЬЗОВАНИЕ ОТСТУПОВ ДЛЯ УЛУЧШЕНИЯ УДОБОЧИТАЕМОСТИ ВАШЕЙ ПРОГРАММЫ

Рассматривая программы, представленные в этой главе, вы увидите, что в них применяются отступы перед операторами, которые следуют за if, else или левой скобкой. Сдвигая подобным образом свои операторы на одну или две позиции, вы упрощаете процесс чтения ваших программ, выделяя связанные группы операторов, как показано ниже:

if (test_score >= 90)

{
 cout << "Поздравляем, вы получили А!" << endl;
 cout << "Ваши тестовые очки были " << test_score << endl;
 }
else
{
 cout << "Вы должны работать усерднее!" << endl;
 cout << "Вы потеряли " << 100 - test_score << " очков " << endl;
}

При создании программ используйте подобные отступы, чтобы программы были более удобочитаемыми. Отступы нужны не C++, а программистам, которые будут читать и пытаться понять ваш код.

ПРОВЕРКА ДВУХ ИЛИ БОЛЕЕ УСЛОВИЙ

Как вы уже знаете, оператор if позволяет программам проверять определенные условия. По мере усложнения ваших программ возникает необходимость в проверке сразу нескольких условий. Например, программа могла бы проверить, будут ли тестовые очки больше или равны 90, и получит ли студент оценку А. Подобно этому, вы могли бы проверить, есть ли у пользователя собака и долматин ли это. Для выполнения таких проверок можно использовать логическую операцию C++ И (&&). Кроме того, если вы хотите проверить, есть ли у пользователя собака или кошка, вам следует использовать логическую операцию ИЛИ (||). Если программы для проверки нескольких условий используют логические операции И или ИЛИ, поместите каждое условие внутри круглых скобок, как показано ниже:

if ((user_owns_a_dog) && (dog == dalmatian)) //------------->Полное условие

Как видите, программа группирует каждое условие внутри своих собственных круглых скобок, которые затем заключаются во внешние круглые скобки.

if ((user_owns_a_dog) && (dog == dalmatian))

Когда ваша программа использует логическую операцию И (&&), то результат полного условия является истинным, только если все проверяемые условия истинны. Если какое-либо условие ложно, то полное условие становится ложным. Например, если у пользователя нет собаки, то предыдущее условие является ложным. Подобно этому, если собака пользователя не долматин, условие является ложным. Чтобы условие было истинным, у пользователя должна быть собака и она должна быть породы долматин. Следующий оператор использует логическую операцию ИЛИ (||), чтобы определить, есть ли у пользователя собака или кошка:

if ((user_owns_a_dog) || (user_owns_a_cat))

При использовании логической операции ИЛИ полное условие будет истинным, если хотя бы одно условие является истинным. Например, если у пользователя есть собака, условие истинно. Если у пользователя есть кошка, условие истинно. Подобно этому, если у пользователя есть и собака, и кошка, условие истинно. Условие будет ложным только в том случае, если у пользователя нет ни собаки, ни кошки.

В C++ истина представляется как ненулевое значение, а ложь как 0

Достоинством C++ является то, что истина представляется как любое ненулевое значение, а ложь как 0. Предположим, ваша программа использует переменную с именем user_owns_a_dog, чтобы определить, есть ли у пользователя собака или нет. Если у пользователя нет собаки, вы можете присвоить этой переменной значение 0 (ложь), как показано ниже:

user_owns_a_dog = 0;

Если у пользователя есть собака, вы можете присвоить этой переменной любое ненулевое значение, например 1:

user_owns_a_dog = 1;

Затем ваши программы могут проверить эту переменную, используя оператор if, как показано ниже:

if (user_owns_a_dog)

Если переменная содержит ненулевое значение, условие оценивается как истина; в противном случае, если переменная содержит 0, условие ложно. Исходя из того, как C++ представляет истину и ложь, предыдущий оператор идентичен следующему:

if (user_owns_a_dog == 1)

Следующая программа DOG_CAT.CPP использует переменные user_owns_a_dog и user_owns_a_cat внутри оператора if, чтобы определить, какие животные есть у пользователя.

#include <iostream.h>

void main(void)

{
 int user_owns_a_dog = 1;
 int user_owns_a_cat = 0;
 if (user_owns_a_dog)
cout << "Собаки великолепны" << endl;
 if (user_owns_a_cat)
cout << "Кошки великолепны" << endl;
 if ((user_owns_a_dog) && (user_owns_a_cat))
cout << "Собаки и кошки могут ужиться" << endl;
 if {(user_owns_a_dog) II (user_owns_a_cat))
cout << "Домашние животные великолепны!" << endl;
}

Экспериментируйте с этой программой, присваивая обоим переменным значение 1 или 0, а затем одной 1, а другой 0 и наоборот. Как видите, проверить два условия очень легко, используя логические операции И и ИЛИ.

Использование операции НЕ в C++

Вы уже знаете, что, когда программа проверяет определенное условие, в ряде случаев она должна выполнить некоторые операторы, если это условие истинно. С другой стороны, вам может потребоваться, чтобы программа выполнила операторы, если условие является не истинным. Операция C++ НЕ — восклицательный знак (!) — позволяет вашим программам проверить, является ли условие не истинным. Например, следующий оператор проверяет, нет ли у пользователя собаки:

if (! user_owns_a_dog)
cout << "вы должны купить собаку" << endl;

Операция НЕ превращает ложь в истину, а истину в ложь. Например, предположим, что у пользователя нет собаки. Следовательно, переменная user_owns_a_dog должна содержать значение 0. Если C++ оценивает условие с Помощью операции НЕ, он использует текущее значение переменной (0) и применяет операцию НЕ. Операция НЕ превращает значение 0 в 1 (истину). Таким образом, полное условие превращается в истину и выполняется соответствующий оператор.

Следующая программа USE_NOT.CPP иллюстрирует использование операции НЕ:

#include <iostream.h>

void main(void)

{
int user_owns_a_dog = 0;
int user_owns_a_cat = 1;
if (! user_owns_a_dog)
cout << "Вы должны купить собаку"
    << endl;
if (! user_owns_a_cat)
cout << "Вы должны купить кошку"
    << endl;
}

Как и раньше, экспериментируйте со значениями, присваиваемыми переменным user_owns_a_dog и user_owns_a_cat, и обратите внимание на поведение программы. По мере усложнения ваших программ вы будете постоянно использовать операцию НЕ. Например, ваша программа может продолжать повторять обработку, пока не встретит конец файла.

Использование логических операций C++

Если вы указываете условия в программах, то иногда эти условия будут состоять из нескольких частей. Например, ваша программа может проверять, имеет ли служащий почасовую оплату и работал ли он более 40 часов на этой неделе. Если для истинности условия каждая из двух его частей должна быть истиной, вам следует использовать операцию C++ И (&&). При ,использовании операции И группируйте каждое условие внутри круглых скобок, а затем оба условия заключите в еще одну пару круглых скобок, как показано ниже:

if ((employee_pay
      == hourly) && (employee_hours > 40))
оператор;

Когда необходимо, чтобы условие было истинным, если только одна из его частей истинна, вам следует использовать операцию C++ ИЛИ (|[). Например, следующее условие проверяет, есть ли у пользователя машина или мотоцикл:

if ((vehicle == car) II (vehicle == motorcycle))
оператор;

Как и ранее, программа группирует каждое условие внутри скобок. В ряде случаев вам потребуется, чтобы ваши программы выполняли оператор, если условие является не истинным. В таких случаях следует использовать операцию C++ НЕ (!). Операция НЕ превращает истину в ложь, а ложь в истину.

Операции C++ И, ИЛИ и НЕ представляют собой логические операции.

ОБРАБОТКА НЕСКОЛЬКИХ УСЛОВИЙ

Программы, представленные в этом уроке, использовали if и else, чтобы указать один набор операторов, который программе следует выполнить, если условие истинно, и другой набор операторов, выполняемых, если условие ложно. Однако в некоторых случаях программам потребуется проверить несколько разных условий. Предположим, например, что вашей программе необходимо определить тестовые очки студента. Для этого программа должна проверить, больше тестовые очки или равны 90, 80, 70, 60 и т. д. Следующая программа SHOWGRAD.CPP использует для этого серию операторов if-else:

#include <iostream.h>

void main(void)

{
 int test_score;
 cout << "Введите тестовые очки и нажмите Enter: ";
 cin >> test_score;
 if (test_score >= 90)
cout << "Вы получили А!" << endl;
 else if (test_score >= 80)
cout << "Вы получили В!" << endl;
else if (test_score >= 70)
cout << "Вы получили С" << endl;
else if (test_score >= 60)
 cout << "Ваша оценка была D" << endl;
 else
 cout << "Вы провалили тест" << endl;
}

При выполнении первого оператора if программа проверяет, больше тестовые очки или равны 90. Если это так, программа выводит сообщение пользователю, что он получил А. В противном случае, если тестовые очки не больше или равны 90, программа выполняет следующие else if, чтобы проверить, больше ли тестовые очки или равны 80. Программа повторяет данный процесс до тех пор, пока не определит правильную оценку. Как и ранее, экспериментируйте с этой программой, вводя разные тестовые очки.

ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА switch

Как вы уже знаете, комбинируя серии операторов if-else, программы могут проверять несколько условий. В предыдущей программе использовались операторы if-else, чтобы определить, находятся ли тестовые очки в данном диапазоне значений. В тех случаях, когда вашим программам необходимо осуществить проверку определенных значений, они могут использовать оператор C++ switch.

Если вы используете оператор switch, вы должны указать условие и затем один или несколько вариантов (case), которые программа попытается сопоставить с условием. Например, следующая программа SWITCH.CPP использует оператор switch для вывода сообщения, основываясь на текущей оценке студента:

#include <iostream.h>

void main(void)

{
 char grade = 'В';
 switch (grade)

      {
case
    'A': cout << "Поздравляем, вы получили А" << endl; break;
case
    'В': cout << "Хорошо, у вас В" << endl; break;
case
    'С': cout << "У вас всего лишь С" << endl; break;
case
    'D': cout << "Плохо, у вас D" << endl; break;
default:
    cout << "Ужасно! Учите лучше!" << endl; break;
}
}

Оператор switch состоит из двух частей. Первая часть оператора switch представляет собой условие, которое появляется после ключевого слова switch. Вторая часть представляет собой возможные варианты соответствия. Когда программа встречает оператор switch, она сначала исследует условие, а затем пытается найти среди возможных вариантов тот, который соответствует условию. Если программа находит соответствие, выполняются указанные операторы. Например, в предыдущей программе выбор варианта 'В' соответствует условию. Таким образом, программа выводит сообщение, что пользователь получил В. Найдите время для эксперимента с этой программой, изменяя оценку и наблюдая поведение программы. Если же ни один из указанных вариантов не соответствует условию, то выполняется вариант default.

Обратите внимание на использование оператора break в каждом варианте предыдущей программы. Оказывается, если C++ встречает вариант, соответствующий условию оператора switch, то он подразумевает, что все последующие варианты тоже соответствуют условию. Оператор break указывает C++ завершить текущий оператор switch и продолжить выполнение программы с первого оператора, следующего за оператором switch. Если вы удалите операторы break из предыдущей программы, то программа выведет не только требуемое сообщение, но и сообщение для всех последующих вариантов (потому что если один вариант является истинным, то и все последующие варианты в C++ рассматриваются как истинные).

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, как использовать оператор C++ if для выполнения условной обработки, которая позволяет вашим программам принимать собственные решения. Как вы знаете, программы могут использовать оператор if, чтобы выполнить один набор операторов, когда условие истинно, и else для указания операторов, выполняющихся программой, если условие ложно. Из урока 8 вы узнаете, как использовать итеративные операторы C++ для повторения операторов указанное число раз или до тех пор, пока выполняется определенное условие. Например, вы можете повторять один и тот же оператор 100 раз, чтобы прибавить 100 студентам тестовые очки. Однако до изучения урока 8 убедитесь, что вы освоили следующие основные концепции:

    1. Операции сравнения C++ позволяют вашим программам проверять, равны ли два значения или нет, или одно значение больше или меньше другого.
    2. Оператор C++ if позволяет программе проверять условия и выполнять один или несколько операторов, если условие истинно.
    3. Оператор C++ else позволяет указать один или несколько операторов, которые выполняются, если условие, проверяемое с помощью оператора if, является ложным.
    4. C++ представляет истину, используя любое ненулевое значение, а ложь как 0.
    5. Логические операции C++ И (&&) и ИЛИ (||) позволяют вашим программам проверять несколько условий.
    6. Логическая операция НЕ (!) позволяет программам проверять условие на неистинность.
    7. Если в операторе if или else нужно выполнить несколько операторов, то такие операторы следует расположить внутри левой и правой фигурных скобок {}.
    8. Используйте отступы при записи операторов программы, чтобы помочь программистам, читающим ваш код, легко находить связанные операторы.
    9. Если вашей программе необходимо проверить, соответствует ли условие указанным значениям, следует использовать оператор switch.
    10. Когда программа встречает в операторе switch вариант (case), соответствующий условию, то все последующие варианты рассматриваются как удовлетворяющие условию. Используя оператор break, вы можете указать C++ прервать оператор switch и продолжить выполнение программы с первого оператора, который следует за switch.


Untitled Document

Урок 8

Повторение одного или нескольких операторов

Из урока 7 вы узнали, как использовать в ваших программах оператор C++ if для принятия решений. С подобным принятием решений тесно связана способность повторять одну или несколько инструкций определенное число раз или до достижения некоторого условия. В этом уроке вы будете использовать итеративные конструкции C++ для повторения одного или нескольких операторов. К концу данного урока вы освоите следующие основные концепции:

  • Для повторения операторов определенное число раз ваши программы используют оператор C++ for.
  • С помощью оператора C++ while программы повторяют операторы до тех пор, пока указанное условие истинно.
  • Оператор C++ do while позволяет программам выполнять операторы по крайней мере один раз, а затем, возможно, повторять операторы, основываясь на определенном условии.

Возможность повторять операторы очень важна в программировании. Экспериментируйте с программами, представленными в этом уроке. К концу урока вы сможете значительно усовершенствовать свои возможности программирования на C++.

ПОВТОРЕНИЕ ОПЕРАТОРОВ УКАЗАННОЕ ЧИСЛО РАЗ

Одной из наиболее широко используемых операций в ваших программах является повторение одного или нескольких операторов определенное число раз. Например, одна программа могла бы повторять один и тот же оператор, чтобы напечатать пять копий файла, а другая могла бы повторять некоторый Набор операторов 30 раз, чтобы определить, поднялась или упала цена ваших 30 акций. Оператор C++ for предоставляет чрезвычайно простую возможность вашим программам повторять один или несколько операторов указанное число раз.

Если ваша программа использует оператор for (часто называемый циклом for), она должна указать переменную, которая называется управляющей переменной, хранящей количество выполнений цикла. Например, следующий цикл for использует переменную count для хранения количества выполнений цикла. В данном случае цикл будет выполнен десять раз.

for (count = 1; count <= 10; count++)
оператор;

Цикл for состоит из четырех частей. Первые три части управляют количеством выполнений цикла. Сначала оператор count = 1, присваивает переменной управления начальное значение. Цикл for выполняет эту инициализацию один раз при запуске цикла. Далее цикл проверяет условие count <= 10. Если условие истинно, цикл for выполняет следующий оператор. Если условие ложно, цикл завершается и программа продолжает свое выполнение с первого оператора, следующего за циклом. Если условие истинно и цикл for выполняет свой оператор, то после этого цикл увеличивает переменную count, используя оператор count++. Далее программа проверяет условие count <= 10. Если это условие все еще истинно, то опять повторяется выполнение оператора внутри цикла, увеличение и проверка переменной count

for (count = 1; count <= 10; count++)

Инициализация Проверка Увеличение

Следующая программа FIRSTFOR.CPP использует цикл for для вывода на экран дисплея значений от 1 до 100:

#include <iostream.h>

void main(void)

{
 int count;
 for (count = 1; count <=100; count++) cout << count <<' ';
}

Как видите, оператор for инициализирует переменную count значением 1. Затем цикл проверяет, меньше ли значение переменной count или равно 100. Если это так, цикл for выполняет соответствующий оператор и затем увеличивает count, повторяя проверку. Экспериментируйте с этой программой, изменяя значение 100 на 10, 20 и даже 5000.

Следующая программа ASKCOUNT.CPP выводит сообщение, запрашивающее пользователя ввести число, при котором цикл должен завершиться. Затем программа выводит числа от одного до указанного значения:

#include <iostream.h>

void main(void)

{
 int count;
 int ending_value;
 cout << "Введите конечное значение и нажмите Enter: ";
 cin >> ending_value;
 for (count = 0; count <= ending_value; count++)
cout << count << ' ';
}

Экспериментируйте с этой программой, вводя разные числа, например 10, 1 и даже 0. Если вы вводите значение 0 или 1, цикл for никогда не выполняется, потому что условие count <= ending_value сразу же ложно. Помните, если вы введете значение вне диапазона значений, которые может хранить переменная типа int, возникнет ошибка переполнения. Например, запустите программу и введите значение 50000. Поскольку это значение превышает наибольшее возможное для переменной типа int, то переполнение приводит к отрицательному значению, которое предотвращает выполнение цикла.

Циклы for C++ поддерживают составные операторы

Из урока 7 вы узнали, что если программы выполняют несколько операторов внутри if или else, то такие операторы следует сгруппировать внутри левой и правой фигурных скобок. Это же относится и к нескольким операторам в цикле for. Следующая программа ADD1_100.CPP зацикливает числа от 1 до 100, выводя и добавляя каждое число в общий итог:

#include <iostream.h>

void main(void)

{
 int count;
 int total = 0;
 for (count = 1; count <= 100; count++)
{
 cout << "Прибавляю " << count << " к " << total;
 total = total + count;
 cout << " получаю " << total << endl;
 }
}

Группируя операторы внутри фигурных скобок, цикл for тем самым может выполнить несколько операторов за один проход (называемый umepa-цией цикла).

Изменение и увеличение цикла for

Все представленные до настоящего момента циклы for увеличивали управляющую переменную цикла на 1 на каждой итерации цикла. Однако цикл for не обязывает ваши программы увеличивать эту переменную на единицу. Следующая программа BY_FIVES.CPP выводит каждое пятое число в диапазоне от 0 до 100:

#include <iostream.h>

void main(void)

{
 int count;
 for (count = 0; count <= 100; count += 5)
cout << count << ' '';
 }

Если вы откомпилируете эту программу, на вашем экране будут отображаться числа 0, 5,10 и т. д. до 100. Обратите внимание, что оператор цикла for использует для увеличения переменную count.

count += 5;

Если вы хотите добавить некоторое значение к текущему значению переменной, а затем присвоить результат той же переменной, C++ позволяет вам сделать это двумя способами. Первый: предположим, вашей программе необходимо добавить значение 5 к переменной count, это можно сделать, как показано ниже:

count = count + 5;

Второй: C++ позволяет вам использовать краткую запись, представленную ниже, для добавления значения 5 к переменной count.

count += 5;

Поскольку это легче записать, то данная краткая форма является общепринятой внутри циклов.

При использовании цикла for вы не обязаны продвигать счетчик в сторону увеличения. Следующая программа CNT_DOWN.CPP использует цикл for для вывода чисел в порядке уменьшения от 100 до 1:

#include <iostream.h>

void main(void)

{
 int count ;
 for (count = 100; count >= 1; count--)
cout << count << ' ';
}

Как видите, цикл for инициализирует переменную count значением 100. На каждой итерации цикл уменьшает значение этой переменной на 1. Цикл завершается, когда переменная count содержит значение 0.

Остерегайтесь бесконечных циклов

Как вы уже знаете, цикл for предоставляет вашим программам способ повторять связанные операторы определенное количество раз. Используя переменную управления, цикл for по существу считает количество выполненных итераций. Когда цикл достигает своего конечного условия, ваша программа прекращает повторение операторов и продолжает свое выполнение с первого оператора, следующего за циклом for.

К сожалению, из-за ошибок в программах в некоторых случаях цикл никогда не достигает своего завершающего условия и, таким образом, зацикливается навсегда (или до тех пор, пока вы не прервете программу). Такие не завершающиеся циклы называются бесконечными циклами. Другими словами, это циклы, не имеющие способа для завершения. Например, следующий оператор for создает бесконечный цикл:

for (count = 0; count < 100; wrong_variable++)
// операторы

Как видите, цикл for использует переменную count в качестве своей управляющей переменной. Однако в секции цикла увеличения программа увеличивает не ту переменную. В результате цикл никогда не увеличивает переменную count, и она никогда не будет иметь значение больше или равно 100. Таким образом, этот цикл превращается в никогда не завершающийся бесконечный цикл.

Важно обратить внимание, что циклы for не ограничиваются использованием переменных типа int в качестве их управляющих переменных. Например, следующая программа LOOPVAR.CPP использует переменную типа char для вывода букв алфавита внутри одного цикла и переменную типа float для вывода чисел с плавающей точкой внутри другого цикла:

#include <iostream.h>

void main(void)

{
 char letter;
 float value;
 for (letter = 'A'; letter <= 'Я'; letter++)
cout << letter;
 cout << endl;
 for (value = 0.0; value <= 1.0; value += 0.1)
cout << value << ' ';
 cout << endl;
}

Если вы откомпилируете и запустите эту программу, на экране появится следующий вывод:

АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ

0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9

Повторение цикла определенное число раз

Одна из наиболее общих операций, которую будут выполнять ваши программы, состоит в повторении одного или нескольких операторов определенное количество раз. Оператор C++ for позволяет вашим программам сделать именно это. Такой оператор for использует управляющую переменную, хранящую количество выполнений цикла. Общий формат оператора for выглядит так:

for (инициализация; проверка; увеличение)
оператор;

При запуске этот цикл for присваивает начальное значение управляющей переменной цикла. Далее программа проверяет условие цикла. Если условие истинно, она выполняет операторы внутри цикла, затем увеличивает управляющую переменную цикла и повторяет проверку условия. Если условие истинно, процесс повторяется. Если же условие ложно, цикл for завершается и программа продолжает свое выполнение с первого оператора, следующего за циклом for.

ВЗГЛЯД НА ЦИКЛ while

Как вы только что узнали, цикл C++ for позволяет вашим программам повторять один или несколько операторов определенное количество раз. Однако в некоторых случаях программе необходимо повторять операторы, пока удовлетворяется (истинно) некоторое условие. Например, в следующих уроках вы узнаете, как читать содержимое файла. Такие программы могли бы повторять цикл, пока не встретится конец файла. В ситуациях, когда программам необходимо выполнять цикл, пока удовлетворяется некоторое условие (но не обязательно определенное количество раз), ваши программы могут использовать оператор C++ while. Общий формат оператора while выглядит так:

while (условие_верно)
оператор;

Если ваша программа встречает оператор while, она проверяет заданное условие. Если условие истинно, программа выполняет операторы цикла while. После выполнения последнего оператора в цикле, цикл while опять проверяет условие. Если условие все еще истинно, повторяются операторы цикла и повторяется данный процесс. Когда условие, наконец, становится ложным, цикл завершается и программа продолжает свое выполнение с первого оператора, следующего за циклом. Следующая программа GET_YN.CPP просит вас ввести Д для да или Н для нет. Затем программа использует цикл while для чтения символов с клавиатуры, пока пользователь не введет Д или Н. Если пользователь вводит значение, отличное от Д или Н, программа сигналит встроенным динамиком, записывая символ сигнала '\а' в выходной поток cout:

#include <iostream.h>

void main(void)

{
 int done = 0; // Устанавливается в состояние „истина", если введены Д или Н char letter;
 while (! done)

{
 cout << "\nВведите Д или Н" << " и нажмите Enter для продолжения: ";
 cin >> letter;
 if ((letter == 'Д') II (letter == 'д'))
done = 1;
 else if ((letter == 'Н') II (letter == 'н'))
done = 1;
else cout << '\а'; // Играть сигнал динамика для неверного символа
}
cout << "Вы ввели букву " << letter << endl;
}

Как видите, цикл while тоже поддерживает несколько операторов, сгруппированных внутри левой и правой фигурных скобок. В данном случае программа использует переменную done для управления циклом. Пока программа не завершится (т. е. пока пользователь не введет Д или Н), цикл продолжает выполняться. Когда пользователь вводит Д или Я, программа устанавливает переменную done в значение истина и цикл завершается. Как только ваши программы начнут работать с файлами, вы регулярно будете использовать цикл while.

Повторение цикла до выполнения заданного условия

По мере усложнения ваших программ им, возможно, понадобится выполнять группы связанных операторов, пока не реализуется заданное условие. Например, программа может вычислять суммы платежей для служащих компании. В этом случае цикл будет выполняться до тех пор, пока не обработаны данные для последнего служащего. Для повторения операторов до выполнения заданного условия программы как правило, будут использовать оператор while:

while (условие)
оператор;

Сталкиваясь с оператором while, программа будет оценивать условие цикла. Если условие истинно, ваша программа выполняет операторы цикла while. После выполнения последнего оператора цикла программа снова проводит проверку условия. Если условие истинно, программа повторит этот процесс, выполнит операторы, а затем повторит проверку условия. Если условие оценивается как ложь, программа продолжит свое выполнение с первого оператора, который следует за оператором while.

ВЫПОЛНЕНИЕ ОПЕРАТОРОВ ПО КРАЙНЕЙ МЕРЕ ОДИН РАЗ

Как вы уже знаете, цикл C++ while позволяет вашим программам повторять набор операторов, пока данное условие удовлетворяется. Когда программа встречает оператор while, она сначала оценивает заданное условие. Если условие истинно, программа входит в цикл. Если условие ложно, операторы цикла while никогда не выполняются. В зависимости от назначения ваших программ, возможны ситуации, когда некоторый набор операторов должен выполняться по крайней мере один раз, а затем выполнение, основываясь на некотором условии, может повторяться. В подобном случае ваши программы могут использовать цикл do while:

do

{
 операторы;
}

while (условие_истинно);

Если программа встречает цикл do while, она входит в цикл и запускает выполнение операторов, содержащихся в цикле. Затем программа оценивает заданное условие. Если условие истинно, программа возвращается к началу цикла:

do <----------------------------------------

{ |
 операторы; |
} |

while (условие_истинно); ----------

Если условие ложно, программа не повторяет инструкции цикла, продолжая вместо этого выполнение с первого оператора, следующего за циклом. Обычно цикл do while используется для отображения пунктов меню и затем обработки выбора пользователя. Вам требуется, чтобы программа отобразила меню по крайней мере один раз. Если пользователь выбирает какой-либо пункт меню, кроме Quit, программа выполнит пункт, а затем отобразит меню снова (повторяя оператор цикла). Если пользователь выбирает Quit, цикл завершится и программа продолжит свое выполнение с первого оператора после цикла.

Повторение операторов, если условие истинно

В зависимости от назначения программы, возможно, потребуется выполнить набор операторов, по крайней мере, один раз, и повторить операторы, если заданное условие истинно. В таких случаях ваши программы используют оператор C++ do while:

do {
оператор;
}
    while (условие);

Когда программа встречает оператор do while, она сразу же выполняет операторы, содержащиеся в цикле. Затем программа исследует условие цикла. Если условие истинно, программа повторяет операторы цикла и процесс продолжается. Если условие цикла становится ложным, программа продолжает свое выполнение с первого оператора, следующего за оператором do while.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Итеративная обработка представляет собой способность программы повторять один или несколько операторов. В этом уроке были описаны итеративные (или циклические) операторы C++. Как вы уже знаете, оператор for позволяет вашим программам повторять один или несколько операторов заданное число раз. Используя оператор while, программы повторяют операторы до тех пор, пока указанное условие истинно. Наконец, с помощью оператора do while программы выполняют операторы, по крайней мере один раз, повторяя их, если заданное условие истинно. Из урока 9 вы узнаете, как разделить программы на небольшие легко управляемые части, называемые функциями. Однако до изучения урока 9 убедитесь, что освоили следующее:

    1. Оператор C++ for позволяет вашим программам повторять один или более операторов заданное число раз.
    2. Оператор for состоит из четырех частей: инициализации, проверяемого условия, операторов, которые повторяются, и приращения.
    3. Оператор for не обязывает вас увеличивать управляющую переменную цикла именно на 1 или использовать именно приращение.
    4. Цикл C++ while позволяет вашим программам повторять операторы, пока указанное условие истинно.
    5. Программы часто используют цикл while для чтения содержимого файла, пока не встретится конец файла.
    6. Оператор C++ do while позволяет вашим программам выполнять один или несколько операторов, по крайней мере один раз, и воз можно, повторять их на основании заданного условия.
    7. Программы часто используют операторы do while для работы с меню
    8. Если проверяемые условия в циклах, while или do while становятся ложью, программа продолжает свое выполнение с первого оператора, следующего за циклом.


Урок 9 Знакомство с функциями

Урок 9

Знакомство с функциями

По мере увеличения размера и сложности ваших программ вам следует разделить их на небольшие легко управляемые части, называемые функциями. Каждая функция в вашей программе должна выполнять определенную задачу. Например, если вы пишете программу платежей, можете создать одну функцию, определяющую количество часов, отработанных служащим, вторую функцию, определяющую сверхурочную оплату, третью функцию, выводящую на печать и т. д. Если программе необходимо выполнить определенную задачу, то она вызывает соответствующую функцию, обеспечивая эту функцию информацией, которая ей понадобится в процессе обработки. Из этого урока вы узнаете, как создавать и использовать функции в ваших программах на C++. К концу данного урока вы освоите следующие основные концепции:

• Функции группируют связанные операторы для выполнения определенной задачи.

• Ваша программа вызывает функцию, обращаясь к ее имени, за которым следуют круглые скобки, например bеер ().

• После завершения обработки большинство функций возвращают значение определенного типа, например int или float, которое программа может проверить или присвоить переменной.

• Ваши программы передают параметры (информацию) функциям, например имя, возраст или оклад служащего, заключая параметры в круглые скобки, которые следуют за именем функции.

• C++ использует прототипы функций для определения типа возвращаемого функцией значения, а также количества и типов параметров, передаваемых функции.

По мере увеличения ваших программ использование функций становится их неотъемлемой необходимостью. Однако, как вы увидите, создавать и использовать функции в C++ очень легко.

СОЗДАНИЕ И ИСПОЛЬЗОВАНИЕ ВАШИХ ПЕРВЫХ ФУНКЦИЙ

При создании программ вам следует спроектировать каждую функцию для выполнения определенной задачи. Если вы обнаружите, что функция выполняет несколько задач, вам следует разделить ее на две или более функций. Каждая создаваемая в программах функция должна иметь уникальное имя.

Как в случае с именами переменных, имена функций, которые вы выбираете те, должны соответствовать операции, выполняемой функцией. Например глядя на имена функций, перечисленные в табл. 9, вы получите хорошее представление о назначении каждой функции.

Таблица 9. Примеры смысловых имен функций.

Имя функции | Назначение функции
Print_test_scores | Печатать тестовые очки класса
Accounts_payable | Обработать счета компании
Get_user_name | Запрос имени пользователя
Print_document | Напечатать указанный документ
Calculate_income_tax | Определить подоходный налог пользователя

Функция C++ по структуре подобна программе main, которую вы использовали во всех предыдущих программах. Другими словами, имя функции предваряется ее типом, а за ним следует список параметров, описание которых появляется в скобках. Вы группируете операторы функций внутри левой и правой фигурных скобок, как показано ниже:

тип_возврата имя_функции (список_параметров)

{

объявления_переменных;
операторы;
 }

Рассмотрите, например, как структура этой функции соответствует следующей программе main:

void main (void) //-----------> тип имя (список_параметров)

{
int count;
    //--------------------------->объявления_переменных;
for (count = 0; count < 10; count++) cout << count
    << ' '; //-----> Оператор
}

Следующие операторы определяют функцию с именем show_message, которая выводит сообщение на экран, используя cout.

void show_message (void)

{
cout << "Привет, учусь программировать на C++" << endl;
}

Как вы, возможно, помните из урока 2, слово void, предшествующее имени функции, указывает функции не возвращать значение. Подобно этому, слово void, содержащееся внутри круглых скобок, указывает (компилятору C++ и программистам, читающим ваш код), что функция не использует параметры (информацию, которую программа передает функции). Следующая программа SHOW_ MSG.CPP использует функцию show_message для вывода сообщения на экран:

#include <iostream.h>

void show_message (void)

{
cout << "Привет, учусь программировать на C++" << endl;
}

void main (void)

{
cout << "Перед вызовом функции" << endl;
show_message ();
cout << "Вернулись из функции" << endl;
}

Вы уже знаете, что выполнение программы всегда начинается внутри main. Внутри main следующий оператор (вызов функции) вызывает функцию show_message:

show_message ();

Круглые скобки после имени функции сообщают компилятору C++, что ваша программа использует функцию. Позже вы узнаете, что внутри этих скобок программа может передавать в функции информацию (параметры). Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С: \>SHOW_MSG <ENTER>

Перед вызовом функции

Привет, учусь программировать на C++

Вернулись из функции

Если программа встречает вызов функции, она начинает выполнять операторы, находящиеся внутри функции. После того как программа выполнит все операторы, которые содержит функция (другими словами, функция завершится); выполнение программы продолжается с оператора, следующего непосредственно за вызовом функции:

#include <iostream.h>

void show_message (void)

{
cout << "Привет, учусь программировать на C++" << endl;
}

void main (void)

{
cout << "Перед вызовом функции" << endl;
show_message ();
cout << "Вернулись из функции" << endl;
}

В данном случае программа выполняет первый оператор в main, которой выводит сообщение для пользователя, что программа собирается вызвать функцию. Далее программа встречает вызов функции и запускает выполнение операторов в show_messsage. После того как программа выполнит единственный оператор функции, она возвращается обратно в main и продолжает свое выполнение с оператора, непосредственно следующего за вызовом функции. В этом случае программа выводит сообщение, извещая пользователя о том, что она возвратилась из функции, и после этого заканчивает свою работу. Следующая программа TWO__MSGS.CPP использует две функции -show_title и show_lesson для вывода информации об этой книге:

#include <iostream.h>

void show_title (void)

{
cout << "Книга: Учимся программировать на C++" << endl;
}

void
    show_lesson (void)
{
cout << "Урок: Знакомство с функциями"	<<	endl;
}

void
    main (void)
{
show_title ();
show_lesson ();
}

Когда программа начинает выполнение, она сначала вызывает функцию show_title, которая выводит сообщение с помощью cout. После завершения show_title программа вызывает функцию show_lesson, также выводящую сообщение. После завершения show_lesson программа завершается, поскольку в main больше нет операторов.

Функции, представленные в этом уроке, выполняли очень простые задачи. В каждом случае ваша программа могла бы легко выполнить ту же самую обработку без использования функции просто включением тех же операторов в main. Однако назначение функций заключалось в том, чтобы показать вам, как программа определяет и затем вызывает функцию. По мере усложнения ваших программ вы будете использовать функции, чтобы упростить большие задачи, разбивая программу на небольшие легко управляемые части. При создании функций вы обнаружите, что, поскольку они содержат меньше строк кода, чем одна большая программа, их легче понять и изменить. В дополнение к этому в большинстве случаев функцию, созданную для одной программы, вы можете использовать без изменений в другой программе. Создавая библиотеку функций, вы тем самым снижаете количество времени, израсходованного на кодирование и тестирование подобных функций в будущем.

Вызов функции

Функция Представляет собой набор связанных операторов, которые Выполняют определенную задачу. Создавая функции внутри программы, вы можете делить большие задачи на небольшие легко управляемые части.
Ваши программы выполняют операторы функций посредством вызова функции. Для вызова функции программы просто обращаются к имени функции, за которым следуют круглые скобки, как показано ниже:

function_name();

Если программа передает информацию (параметры) в функцию, она размещает эту информацию внутри круглых скобок, разделяя ее запятыми:

payroll(employee_name, employee_id, salary);

После того как последний оператор функции завершен, выполнение программы продолжается с первого оператора следующего за вызовом функции.

ПРОГРАММА МОЖЕТ ПЕРЕДАВАТЬ ИНФОРМАЦИЮ В ФУНКЦИИ

Для увеличения потенциальных возможностей ваших функций C++ позволяет программам передавать информацию (параметры) в функции. Если функция использует параметры, вы должны сообщить C++ тип каждого параметра, например int, float, char и т.д. Следующая функция show_number использует параметр типа int.

void show_number (int value)

{
cout << "Значение параметра равно " << value << endl;
}

Если ваша программа вызывает функцию show_number, она должна передать ей значение, как показано ниже:

show_number (1001); //----------------> Значение, передаваемое в функцию

C++ будет подставлять переданное число вместо каждого имени параметра value внутри функции:

show_number (1001)

void show_number (int value)

{
cout << "Значение параметра равно " << value << endl;
}

void show_number (1001)

{
cout << "Значение параметра равно " << 1001 << endl;
}

Как видите, поскольку C++ замещает значение параметра, функция show_number выводит число 1001, переданное ей главной программой.

Следующая программа USEPARAM.CPP использует функцию show_number несколько раз, каждый раз передавая разные числа:

#include <iostream.h>

void show_number (int value)

{
cout << "Значение параметра равно " << value << endl;
}

void main (void)

{
show_number (1);
show_number (1001);
show_number (-532);
}

Если вы откомпилируете и запустите эту программу, на вашем экране будет отображено следующее:

С: \>USEPARAM <ENTER>

Значение параметра равно 1

Значение параметра равно 1001

Значение параметра равно -532

Как видите, каждый раз, когда программа вызывает функцию, C++ присваивает передаваемое число переменной value. Найдите время для эксперимента с этой программой, изменяя значения, которые main передает в функцию, и обращая внимание на результат.

Каждый параметр функции имеет определенный тип. В случае функции show_number параметр value должен быть типа int. Если вы попытаетесь передать в функцию значение другого типа, например с плавающей точкой, компилятор будет сообщать об ошибке. В большинстве случаев ваши программы будут передавать несколько значений в функцию. Для каждого передаваемого параметра функция должна указать имя и тип. Например, следующая программа BIGSMALL.CPP использует функцию show_big_and_little для вывода самого большого и самого маленького из трех полученных целочисленных значений:

#include <iostream.h>

void show_big_and_little (int a, int b, int c)

{
int small = a;
int big = a;

if (b > big)
big = b;

if (b < small)
small = b;

if (c > big)
big = c;

if (c < small)
small = c;

cout << "Самое большое значение равно " << big << endl;
cout << "Самое маленькое значение равно " << small << endl;
}

void main (void)

{
show_big_and_little (1, 2, 3);
show_big_and_little (500, 0, -500);
show_big_and_little (1001, 1001, 1001);
}

Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С: \> BIGSMALL <ENTER>

Самое большое значение равно 3

Самое маленькое значение равно 1

Самое большое значение равно 500

Самое маленькое значение равно -500

Самое большое значение равно 1001

Самое маленькое значение равно 1001

Наконец, следующая программа SHOW_EMP.CPP использует функцию show_employee для вывода возраста (тип int) и оклада (тип float) служащего:

#Include <iostream.h>

void show_employee (int age, float salary)

{
cout << "Возраст служащего " << age << " года (лет)" << endl;
cout << "Служащий получает $" << salary << endl;
}

void main (void)

{
show_employee (32, 25000.00);
}

Как видите, функция show_employee определяет параметры типа int и float

Передача параметров в функцию

Если ваша функция использует параметры, она должна указать уникальное имя и тип для каждого параметра. Когда программа вызывает функцию, C++ присваивает значения параметров именам параметров функции слева направо. Каждый параметр функции имеет определенный тип, например int, float или char. Значения, которые ваша программа передает в функцию, используя параметры, должны соответствовать типу параметров.

ФУНКЦИИ МОГУТ ВОЗВРАЩАТЬ РЕЗУЛЬТАТ ВЫЗВАВШЕЙ ФУНКЦИИ

Функция должна выполнять определенную задачу для вашей программы. В большинстве случаев функции будут выполнять некоторые типы вычислений. Затем функция возвратит свой результат вызвавшей функции. Когда функция возвращает значение, вы должны сообщить C++ тип значения, например int, float, char т. д. Чтобы информировать C++ о типе возвращаемого функцией значения, просто поставьте перед именем функции соответствующий тип. Например, следующая функция add_values складывает два своих целочисленных параметра и возвращает результат типа int вызвавшей программе:

int add_values (int а, int b)

{
int result;
result=а+b;
return
    (result);
}

В данном случае слово int, появляющееся перед именем функции, указывает тип возвращаемого значения функции. Функции используют оператор return для возврата значения вызвавшей функции. Когда ваша программа встречает оператор return, она возвращает заданное значение и завершает выполнение функции, возвращая управление вызвавшей программе. В программе вы можете использовать возвращаемое значение, как показано ниже:

result=add_values (1, 2);

В данном случае программа присваивает возвращаемое функцией значение переменной result. Ваша программа может также сразу же напечатать возвращаемое функцией значение с помощью cout, как показано ниже:

cout << "Сумма значений равна " << add_values (500, 501) << endl;

Предыдущая реализация функции add_values использовала три оператора, чтобы было легче понять смысл функции. Однако вы можете сократить функцию до единственного оператора return, как показано ниже:

int add_values (int a, int b)

{
return (a+ b);
}

Следующая программа ADDVALUE.CPP использует функцию add_values для сложения нескольких значений:

#include <iostream.h>

int add_values (int a, int b)

{
return (a+ b);
}

void main (void)

{
cout << " 100 + 200 = " << add_values(100, 200)
    <<	endl;
cout << " 500 + 501 = " << add_values(500, 501)
    <<	endl;
cout << "-1 + 1 = " << add_values(-1, 1)
    << endl;
}

Выберите время для эксперимента с этой программой, изменяя значения, которые программа передает в функцию. Вы могли бы попытаться передать в функцию большие значения, например 20000 и 30000. Как и можно предложить, выполнение функции, возвращающей значение типа int, приведет к ошибке переполнения.

Не все функции возвращают значение типа int. Следующая функция average_value возвращает среднее двух целочисленных значений, которое может быть дробным, например 3.5:

float average_value(int a, int b)

{
return( (a + b) / 2.0);
}

В этом случае слово float, которое предшествует имени функции, указывает тип возвращаемого функцией значения.

ФУНКЦИИ, КОТОРЫЕ НЕ ВОЗВРАЩАЮТ ЗНАЧЕНИЕ

Если функция не возвращает значение, вам необходимо предварить имя функции типом void. В противном случае вы должны предварять имя функции типом возвращаемого функцией значения, например, int, float, char и т. д. Чтобы возвратить значение вызвавшей функции, функция использует оператор return. Когда ваша программа встречает оператор return, выполнение функции завершается и указанное значение возвращается вызвавшей функции. Возможны ситуации, когда вы встретите оператор return в функции, которая не возвращает значение:

return;

В этом случае функция имеет тип void (не возвращает значение) и оператор return просто завершает выполнение функции.

Замечание: Если операторы появляются в функции после оператора return они не будут выполняться. Как уже обсуждалось выше, если ваша программа встречает оператор return в функции, то возвращается соответствующее значение, функция заканчивается и выполнение программы продолжается с первого оператора, следующего за вызовом функции.

ИСПОЛЬЗОВАНИЕ ВОЗВРАЩАЕМОГО ФУНКЦИЕЙ ЗНАЧЕНИЯ

Когда функция возвращает значение, вызвавшая программа может присвоить возвращенное значение переменной, используя оператор присваивания, как показано ниже:

payroll_amount = payroll (employee, hours, salary);

В дополнение к этому вызвавшая программа просто может обращаться к функции. Например, следующий оператор выводит возвращаемое функцией значение, используя cout.

cout << "Служащий получил" << payroll(employee, hours, salary) < < endl;

Вызвавшая функция может также использовать возвращаемое значение в условии, как показано ниже:

if (payroll(employee, hours, salary) < 500.00)
cout << "Этот служащий нуждается в повышении" << endl;

Как видите, программа может использовать возвращаемое функцией значение различными способами.

ПРЕДСТАВЛЕНИЕ О ПРОТОТИПАХ ФУНКЦИЙ

Прежде чем ваша программа сможет вызвать функцию, C++ должен знать тип воозвращаемого значения, а также количество и тип параметров, используемых функцией. В каждой из программ, представленных в этом уроке, определение функции, вызываемой программой, всегда предшествует вызову функции в исходном файле. Однако в большинстве случаев функции появляются в вашем исходном файле и, как правило, одна функция вызывает другую. Чтобы гарантировать, что C++ знает особенности каждой функции, используемой в программе, вы можете поместить прототипы функций в начало исходного файла. В общем случае прототип функции обеспечивает информацию о типе возвращаемого функцией значения и ее параметрах. Следующий оператор иллюстрирует прототипы функций для нескольких функций, используемых в данном уроке:

void show_message(void);

void show_number(int);

void show_employee(int, float);

int add_values(int, int);

float average_value(int, int);

Как видите, прототип функции указывает тип возвращаемого значения, а также количество и тип каждого параметра. Обратите внимание на точку с запятой в конце каждого прототипа.

float average_value (int, int);

Если ваша программа вызывает функцию, для которой компилятор C++ не нашел определения или прототипа, компилятор сообщает о синтаксической ошибке. При исследовании заголовочных файлов C++ или других программ вы будете постоянно сталкиваться с прототипами функций. Следующая программа PROTO.CPP иллюстрирует использование прототипа Функции:

#include <iostream.h>

float average_value(int, int); // Прототип функции

void main(void)

{
cout << "Среднее значение 2000 и 2 равно " << average_value(2000, 2) << endl;
}

float average_value (int a, int b)

{
return((a + b) / 2.0);
}

В этом случае программа вызывает функцию average_value до того, как функция определена. Таким образом, программа использует прототип функции, который предваряет определение main. Если вы удалите прототип функции и откомпилируете эту программу, компилятор C++ будет сообщать о синтаксических ошибках.

Использование прототипов функций

Прототип функции сообщает компилятору C++ тип возвращаемого значения, а также количество и тип параметров функции. Когда вы компилируете вашу программу, компилятор C++ использует прототип каждой функции, чтобы убедиться, что вы не перепутали тип возвращаемого функцией значения (например, не присваиваете возвращаемое значение типа float переменной типа int) и что вы не передаете в качестве параметра значение неверного типа. Раньше многие компиляторы C++ не выполняли подобную проверку. В результате программисты часто тратили часы, пытаясь найти ошибки, возникающие из-за того, что вместо ожидаемого значения типа float в функцию передавалось значение типа int. Если вы встречаете синтаксическую ошибку, которая возникает из-за противоречия с прототипом функции, будьте благодарны. В прошлом компилятор не определял подобную ошибку, и ваша программа просто не работала.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, как использовать функции в программах на C++. Данный урок охватил целый ряд основных понятий, таких как параметры, типы возвращаемых значений и прототипы функций. Сейчас вы можете потратить несколько больше времени на эксперименты с простыми программами. Из урока 10 вы узнаете, как изменять значения параметров внутри функций. Однако до изучения урока 10 убедитесь, что вы освоили следующие основные концепции:

  1. По мере усложнения ваших программ вам следует делить их на небольшие легко управляемые части, называемые функциями. Каждая функция должна иметь уникальное имя. Присваивайте вашим функциям имена, выражающие смысл задач, которые выполняют функции.
  2. Функции могут возвращать значение вызвавшей функции. При этом вы должны указать тип возвращаемого функцией значения (int, char и т. д.) до имени функции, в противном случае вы должны предварять имя функции словом void.
  3. Программы передают информацию в функции с помощью параметров. Если функция получает параметры, вы должны указать уникальное имя и тип каждого параметра. Если функция не получает параметры, вы должны поместить ключевое слово void внутри круглых скобок, следующих за именем функции.
  4. C++ должен знать тип возвращаемого значения и количество и тип параметров, которые получает функция. Если определение функции следует за использованием функции, вы должны поместить прототип функции в начале исходного файла.


Урок 10 Изменение значений параметров

Урок 10

Изменение значений параметров

Из урока 9 вы узнали, как разделить ваши программы на небольшие легко управляемые части, называемые функциями. Как вы уже знаете, программы могут передавать информацию (параметры) функциям. Представленные в уроке 9 программы использовали или выводили значения параметров, но не меняли их. Из этого урока вы узнаете, как изменить значение параметра в функции. Вы обнаружите, что для изменения параметров в функции фактически требуется больше шагов, чем можно предположить. Однако этот урок обучит вас всем шагам, которые необходимо знать. К концу данного урока вы освоите следующие основные концепции:

• Если функция не использует указатели или ссылки, она не может изменить значение параметра.

• Для изменения значения параметра функция должна знать адрес параметра в памяти.

• Оператор адреса C++ (&) позволяет вашей программе определить адрес переменной в памяти.

• Когда ваша программа узнает адрес памяти, она сможет использовать операцию разыменования C++ (*) для определения значения, хранимого по данному адресу.

• Если программе нужно изменить значение параметров функции, программа передает в функцию адрес параметра.

Изменение значения параметра функции представляет собой обычную операцию. Экспериментируйте с программами, представленными в этом уроке, чтобы убедиться, что вы полностью освоили этот процесс.

ПОЧЕМУ ФУНКЦИИ ОБЫЧНО НЕ МОГУТ ИЗМЕНИТЬ ЗНАЧЕНИЯ ПАРАМЕТРОВ

Следующая программа NOCHANGE.CPP передает два параметра с именами big и small в функцию display_values. Функция display_values, в свою очередь, присваивает обоим параметрам число 1001 и затем выводит значение каждого параметра. Когда функция завершается, программа возобновляется и выводит значения этих же параметров:

#include <iostream.h>

void display_values(int a, int b)

{
     a = 1001;
b = 1001;
cout << "Значения в функции display_values
  равны " << а << " и " << b << endl;
}

void main(void)

{
int big = 2002, small = 0;
 cout << "Значения до функции " << big << " и" << small << endl;
 display_values(big, small);
 cout << "Значения после функции " << big << " и " << small << endl;
}

Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод:

С:\> NOCHANGE <ENTER>

Значения до функции 2002 и 0

Значения в функции display_values равны 1001 и 1001

Значения после функции 2002 и 0

Как видите, значения параметров в функции display_values были изменены (1001). Однако после завершения функции значения переменных big и small в main остались прежними. Чтобы понять, почему изменение параметров не повлияло на переменные big и small в main, вам необходимо понять, как C++ передает параметры в функции.

Когда ваши программы передают параметр в функцию, то по умолчанию С++ делает копию значения параметра и помещает эту копию во временный участок памяти, называемый стеком. Затем функция использует копию значения для выполнения своих операций. Когда функция завершается, C++ сбрасывает содержимое стека и все изменения, сделанные функцией в копиях эначений параметра.

Как вы знаете, переменная представляет собой имя, присваиваемое вашей программой ячейке памяти, которая хранит значение определенного типа. Предположим, например, что переменные big и small находятся в ячейках памяти 10 и 12. Если вы передадите переменные в функцию display_values, C++ поместит копии значений этих переменных в стек. На рис. 10.1 показано, что далее функция display_values будет использовать копии значений переменных.

Рис. 10.1. C++ размещает копии значений параметров во временном участке памяти, называемом стеком.

Как видите, функция display_values может обращаться к содержимому стека, в котором находятся копии значений 2002 и 0. Так как функция display_values ничего не знает о ячейках памяти big и small (адреса 10 и 12), функция не может изменить реальные значения переменных.

Почему функции C++ обычно не могут изменить значения параметров

Когда вы передаете параметры в функцию, C++ размещает копии значений параметров во временном участке памяти, называемом стеком. Любые изменения, выполняемые функцией над параметрами, проявляются только внутри стека. Когда функция завершается, C++ сбрасывает содержимое стека вместе с любыми изменениями, которые функция произвела в параметрах. Поскольку функция не знает адрес памяти параметра, она не может изменить его значение.

ИЗМЕНЕНИЕ ЗНАЧЕНИЯ ПАРАМЕТРА

Для изменения значения параметра функция должна знать адрес памяти параметра. Чтобы сообщить функции адрес параметра, ваши программы должны использовать оператор адреса C++ (&). Следующий вызов функции иллюстрирует, как программа будет использовать оператор адреса для передачи адресов переменных big и small в функцию change_values:

change_values (&big, &small); -->Передача параметров по адресу

Внутри функции вы должны сообщить C++ , что программа будет передавать параметры с помощью адреса. Для этого вы объявляете переменные-указатели, предваряя имя каждой переменной звездочкой, как показано ниже:

void сhange_values (int. *big, int. *small) ---> Указатель на тип int

Переменная-указатель представляет собой переменную, которая содержит адрес памяти. Внутри функции вы должны сообщить C++ , что функция работает с адресом параметра. Для этого вы предваряете имя параметра звездочкой, как показано ниже:

*big = 1001;
*small = 1001;

Следующая программа CHGPARAM.CPP использует оператор адреса для передачи адресов параметров big и small в функцию change_values. Функция, в свою очередь, использует указатели участков памяти параметров. Следовательно, изменения параметров, сделанные функцией, остаются и после завершения функции:

#include <iostream.h>

void change_values (int *a, int *b)

 {
*a = 1001;
*b = 1001;
cout << "Значения в функции display_values"
    << " равны " << *а << " и " << *b << endl;
}

void main(void)

{
int big = 2002, small = 0;
cout << "Значения перед функцией " << big << " и " << small << endl;
change_values(&big, &small);
 cout << "Значения после функции " << big << " и " << small << endl;
}

Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод:

C:\> CHGPARAM <ENTER>

Значения перед функцией 2002 и 0

Значения в функции display_values равны 1001 и 1001

Значения после функции 1001 и 1001

Как видите, значения, которые функция change_values присваивает параметрам, остаются и после завершения функции. Чтобы понять, почему изменения, которые функция выполнила над переменными, остались после ее завершения, необходимо вспомнить, что функция имеет доступ к ячейке памяти каждой переменной. Если вы передаете параметры по адресу, C++ помещает адрес каждой переменной в стек, как показано на рис. 10.2.

Рис. 10.2. Передача параметров по адресу.

Используя указатели (адреса памяти) внутри функции, change_values может обратиться к памяти по адресу каждого параметра, изменяя значения параметров, что и требуется.

Изменение значений параметров в функциях

Для изменения значения параметра в функции, функция должна знать адрес параметра в памяти. Следовательно, ваша программа должна передать адрес параметра с помощью оператора адреса C++ :

some_function(&some_variable);

Внутри функции вы должны сообщить C++ , что функция будет работать с адресом памяти (указателем). Для этого при объявлении вы предваряете имя параметра звездочкой:

void some_function(int *some_variable);

Далее внутри функции вы должны употреблять звездочку перед именем переменной:

*some_variable = 1001;
cout << *some_variable;

Во избежание ошибок C++ не позволит вашей программе передать адрес переменной в функцию, которая не ожидает указатль в качестве параметра. Кроме того, C++ обычно генерирует предупреждение компилятора, когда ваша программа пытается передать значение в функцию, которая ожидает указатель в качестве параметра.

Второй пример

Если ваша программа передает указатели на параметры, параметры могут быгь любого типа, например int, float или char. Функция, которая использует указатели, объявляет переменные соответствующего типа, предваряя имя каждой переменной звездочкой, подтверждающей, что такая переменная является указателем. Следующая программа SWAPVALS.CPP передает адреса двух параметров типа float в функцию swap_values. Функция в свою очередь использует указатели на каждый параметр, чтобы обменять значения параметров:

#include <iostream.h>

void swap_values(float *a, float *b)

{
float temp;
temp = *a;
 *a = *b;
 *b = temp;
}

void main(void)

 {
float big = 10000.0;
float small = 0.00001;
swap_values(&big, &small);
cout << "Big содержит " << big << endl;
cout << "Small содержит " << small <<
    endl;
}

Как видите, программа передает параметры в функцию swap_values по адресу. Внутри функции программа использует указатели на ячейки памяти параметров. Давайте более внимательно посмотрим на действия внутри функции swap_values. Как видите, функция объявляет а и b как указатели на значения типа float:

void swap_values(float *a, float *b)

Однако функция объявляет переменную temp просто как float, а не как указатель на float. float temp;

Рассмотрим следующий оператор:

temp = *а;

Этот оператор побуждает C++ присвоить переменной temp значение указываемое переменной а (т. е. значение переменной big, равное 10000.0). Поскольку temp имеет тип float, присваивание корректно. Переменная-указатель представляет собой переменную, которая хранит адрес. Следующий оператор объявляет temp как указатель на ячейку памяти, содержащую значение типа float.

float *temp;

В данном случае temp может хранить адрес значения с плавающей точкой но не само значение.

Если вы удалите оператор разыменования (*), стоящий перед переменной а внутри присваивания, то оператор будет пытаться присвоить значение, хранимое в а (которое является адресом), переменной temp. Поскольку temp может содержать значение с плавающей точкой, но не адрес значения с плавающей точкой, возникнет ошибка.

Не беспокойтесь, если вы не можете свободно обращаться с указателями, вы будете изучать их более подробно в части 3. На настоящий момент, однако, просто поймите, что, если хотите изменить в ваших функциях значения параметров, вы должны использовать указатели.

Использование ассемблерных листингов для лучшего понимания работы компилятора

Лучшим способом понять, как компилятор C++ трактует указатели, является исследование ассемблерного вывода компилятора. Большинство компиляторов C++ обеспечивают ключ командной строки, который вы можете использовать, чтобы указать компилятору выводить ассемблерный листинг. Читая ассемблерный листинг, вы можете лучше понять, как компилятор использует стек, когда передает параметры в функцию.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из данного урока вы узнали, как изменить значение параметра внутри функции. Для этого ваши функции должны использовать указатели. Сначала вы можете найти указатели слишком сложными. Из урока 14 вы узнаете, как использовать ссылки C++ , которые упрощают процесс изменения параметров внутри функции. Однако, поскольку многие программисты С используют указатели для изменения параметров, вам необходимо знать и такой вариант программирования.

Из урока 11 вы выясните, как функции библиотеки этапа выполнения, обеспечиваемые компилятором C++ , могут ускорить программирование, позволяя быстро разрабатывать сложные программы. Однако до изучения урока 11 убедитесь, что вы освоили следующие основные Концепции:
    1. Пока вы не используете указатели или ссылки C++ , функция не может изменить значение параметра.
    2. Когда ваша программа передает параметр в функцию, C++ помещает копию значения параметра во временный участок памяти, называемый стеком. Любые изменения, которые функция осуществляет над параметром, влияют только на эту копию, расположенную в стеке.
    3. Для изменения значения параметра функция должна знать адрес соответствующей переменной.
    4. Используя оператор адреса C++ (&), ваши программы могут передать адрес переменной в функцию.
    5. Когда функция получает адрес переменной, она должна объявить переменную параметра как указатель (предваряя имя переменной звездочкой).
    6. Если функции требуется использовать значение, на которое ссылаются (указывают) по указателю, функция должна предварять имя переменной-указателя звездочкой, т. е. оператором разыменования C++.


Урок 11

Преимущества использования библиотеки этапа выполнения

Из урока 9 вы узнали, как разделить ваши программы на небольшие легко управляемые части, называемые функциями и выполняющие определенную задачу. Одно из преимуществ использования функций заключается в том, что вы можете часто применяемую функцию, созданную для одной программы, использовать в другой программе. Как вы узнаете из этого урока, большинство компиляторов C++ обеспечивают широкий набор функций, использующихся в программах и называющихся библиотекой этапа выполнения. Применение этих функций сокращает объем программирования, который вы должны выполнить самостоятельно. Вместо этого ваша программа просто вызывает функции библиотеки этапа выполнения. В зависимости от компилятора библиотека этапа выполнения может состоять из тысяч функций. В данном уроке описывается использование таких функций в ваших программах. К тому времени, когда вы закончите этот урок, вы освоите следующие основные концепции:

• Библиотека этапа выполнения представляет собой набор функций, обеспечиваемых вашим компилятором, которые вы можете легко использовать в программах.

• Для использования функций библиотеки этапа выполнения вы должны включить соответствующие заголовочные файлы, содержащие прототипы функций.

• Некоторые компиляторы обращаются к библиотеке этапа выполнения как к интерфейсу прикладных программ или API.

Большинство библиотек этапа выполнения содержат сотни прикладных функций, которые помогут вам сохранить огромное количество времени и быстро разработать сложные программы. Вы узнаете, что очень легко использовать функции библиотеки этапа выполнения!

ИСПОЛЬЗОВАНИЕ ФУНКЦИЙ БИБЛИОТЕКИ ЭТАПА ВЫПОЛНЕНИЯ

Из урока 9 вы узнали, что до того, как ваши программы смогут вызвать функцию, компилятор C++ должен узнать определение или прототип функции. Поскольку функции библиотеки этапа выполнения не определены в вашей программе, вы должны указать прототип для каждой библиотечной функции, которую намерены использовать. Для упрощения использования библиотечных функций компилятор C++ предоставляет заголовочные файлы, содержащие корректные прототипы. Таким образом, вашим программам необходимо просто включить требуемый заголовочный файл с помощью оператора #include, а затем вызвать необходимую функцию. Например, следующая программа SHOWTIME.CPP будет использовать функции библиотеки этапа выполнения time и ctime для вывода текущей системной даты и времени. Прототипы этих двух функций библиотеки этапа выполнения содержатся в заголовочном файле time.h:

#include <iostream.h>
#include <time.h> // Для функций библиотеки этапа выполнения

void main(void)

{
time_t system_time;
system_time = time(NULL);
cout << "Текущее системное время " << ctime(&system_time)
    << endl;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появятся текущие системные дата и время:

С:\> SHOWTIME <ENTER>

Текущее системное время Mon Jan 01 16:13:51 1996

Как видите, программа использует функции time и ctime. В случае функции ctime программа передает адрес переменной system_time, используя оператор адреса, описанный в уроке 10. Для использования этих функций вам просто следует включить заголовочный файл time.h в начало вашего исходного файла.

Подобным образом следующая программа SQRT.CPP использует функцию sqrt для возврата квадратного корня нескольких значений. Прототип функции sqrt находится в заголовочном файле math.h:

#include <iostream.h>
#include <math.h> // Содержит прототип sqrt

void main(void)

{
 cout << "квадратный корень 100.0 равен " << sqrt(100.0) << endl;
cout << "Квадратный корень 10.0 равен " << sqrt (10.0) << endl;
cout << "квадратный корень 5.0 равен " << sqrt(5.0) << endl;
}

Наконец, программа SYSCALL.CPP использует функцию system, прототип которой определяется в заголовочном файле stdlib.h. Функция system обеспечивает легкий способ выполнения вашей программой команды операционной системы, такой как "DIR", или другой программы:

#include <stdlib.h>

void main(void)

{
system("DIR");
}

В этом случае программа использует функцию system для вызова команды MS-DOS DIR. Выберите время для эксперимента с этой программой, запуская другие команды или даже одну из программ, созданных вами ранее при изучении этой книги.

ИЗУЧЕНИЕ ФУНКЦИЙ БИБЛИОТЕКИ ЭТАПА ВЫПОЛНЕНИЯ

Ваш компилятор C++ обеспечивает сотни функций библиотеки этапа выполнения. Документация, поставляемая с вашим компилятором, должна содержать полное описание всех функций библиотеки этапа выполнения. Если вы просмотрите эту документацию, то найдете, что функции обычно используют простые прототипы. Например, для функции sqrt вы могли бы найти следующий прототип:

double sqrt(double);

В данном случае прототип функции сообщает вам, что функция возвращает значение типа double и ожидает параметр тоже типа double. Аналогично этому можно найти следующий прототип для функции time:

time_t time (time_t *);

И опять прототип сообщает вам, что функция возвращает значение типа time_t (этот тип определен в заголовочном файле time.h). Функция ожидает, что ее параметр должен быть указателем на переменную типа time_t. По мере чтения документации о функциях библиотеки этапа выполнения вы очень много узнаете о самих функциях и о C++ , при этом обращайте внимание на прототипы функций.

Другой путь изучения библиотечных функций вашего компилятора состоит в просмотре заголовочных файлов, содержащихся в подкаталоге INCLUDE. Выделите время, например, для того, чтобы сейчас распечатать заголовочные файлы math.h, time.h и stdlib.h, которые вы использовали в программах этого урока.

Использование функций API

В дополнение к стандартной библиотеке этапа выполнения многие компиляторы обеспечивают функции API или интерфейс прикладных программ. Например, если вы программируете в среде Windows, то вам потребуются функции графического API, телефонного API (TAPI), API для мультимедиа и т. д. Прежде чем создавать свои собственные функции, убедитесь, что вы не нашли таких функций в API, предоставляемом вашим компилятором.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Библиотека этапа выполнения C++ предоставляет мощный набор функций, которые вы можете использовать в ваших программах. Не жалейте времени, чтобы изучить документацию по библиотеке этапа выполнения, поставляемой с вашим компилятором. Выясните смысл функций, имеющихся в библиотеке этапа выполнения. Преимущество этих функций состоит в том, что вы избавляетесь от значительного объема программирования. В уроке 12 вы научитесь обращаться с локальными переменными и областью видимости (участок вашей программы, где известно имя переменной). Прежде чем перейти к уроку 12, убедитесь, что вы освоили следующие основные концепции:

    1. Библиотека этапа выполнения представляет собой набор функций, обеспечиваемых компилятором для ваших программ.
    2. Для использования функции из библиотеки этапа выполнения вы должны указать ее прототип.
    3. Большинство компиляторов C++ предоставляют заголовочные файлы, которые содержат корректные прототипы для каждой библиотечной функции.
    4. В дополнение к библиотеке этапа выполнения многие компиляторы C++ предоставляют функции API (интерфейс прикладных программ) для выполнения определенных задач, например программирования графики или мультимедиа.


Untitled Document

Урок 12

Локальные переменные и область видимости

Как вы уже знаете, функции позволяют разделить программу на небольшие легко управляемые части. Все функции, используемые вами до настоящего момента, были совершенно просты. Как только вашим функциям потребуется выполнить более сложную работу, они должны будут использовать переменные для реализации своих задач. Переменные, объявляемые внутри функции, называются локальными переменными. Их значения и даже сам факт, что локальные переменные существуют, известны только данной функции, Другими словами, если вы объявляете локальную переменную с именем salary в функции payroll, то другие функции не имеют доступа к значению переменной salary. Фактически, другие функции не имеют никакого представления о том, что переменная salary существует. Этот урок рассматривает область видимости переменной, или участок внутри вашей программы, в котором переменная известна. К концу этого урока вы освоите следующие основные концепции:

• Вы объявляете локальные переменные внутри функции точно так же, как и внутри main: указывая тип и имя переменной.

• Имена переменных, используемых внутри функций, должны быть уникальными только по отношению к данной функции.

• Область видимости переменной определяет участок программы, где переменная известна и доступна.

• Глобальные переменные в отличие от локальных переменных известны на протяжении всей программы и доступны внутри всех функций.

• Оператор глобальной области видимости C++ (::) позволяет вам управлять областью видимости переменной.

Объявление локальных переменных внутри функции достаточно просто. Фактически вы уже это делали каждый раз, когда объявляли переменные внутри main.

ОБЪЯВЛЕНИЕ ЛОКАЛЬНЫХ ПЕРЕМЕННЫХ

Локальная переменная представляет собой переменную, определенную внутри функции. Такая переменная называется локальной, потому что ее известность ограничена данной функцией. Вы объявляете локальные переменные в начале функции после открывающей фигурной скобки:

void some_function(void)

{
int count;
float result;
}

Следующая программа USEBEEPS.CPP использует функцию sound_speaker, которая заставляет играть встроенный компьютерный динамик столько раз, сколько указано параметром beeps. Внутри функции sound_speaker локальная переменная counter хранит количество звуков, издаваемых динамиком:

#include <iostream.h>

void sound_beeps(int beeps)

{
for (int counter = 1; counter <= beeps; counter++) cout
    << '\a';
}

void main(void)

{
sound_beeps(2);
sound_beeps(3);
}

Как видите, функция sound_beeps объявляет переменную counter сразу же после открывающей фигурной скобки. Поскольку counter определяется внутри функции sound_beeps, эта переменная является локальной по отношению к sound_beeps, и это означает, что только sound_beeps знает об этой переменной и может к ней обращаться.

О конфликте имен

При объявлении локальных переменных внутри функции очень вероятно, что имя локальной переменной, объявляемой вами в одной функции, будет таким же, как и имя переменной, используемой в другой функции. Как уже упоминалось, локальные переменные известны только в текущей функции. Таким образом, если две функции используют одно и то же имя для своих локальных переменных, это не приводит к конфликту. C++ трактует имя каждой переменной как локальное по отношению к соответствующей функции. Следующая программа LCLNAME.CPP использует функцию add_values для сложения двух целочисленных значений. Эта функция присваивает свой результат локальной переменной value. Однако в main один из параметров, передаваемых в функцию, также носит имя value. Тем не менее, поскольку C++ трактует обе переменные как локальные для соответствующих функций, их имена не конфликтуют:

#include <iostream.h>

int add_values(int
    a,	int b)
{
int value;
value =a + b;
return(value);
}

void main (void)

{
int value = 1001;
int other value = 2002;
cout << value << " + " << other_value
    << " = " << add_values(value, other_value) << endl;
}

О локальных переменных

Локальные переменные представляют собой переменные, объявляемые внутри функции. Имя и значение локальной переменной известны только функции, внутри которой переменная объявлена. Вы должны объявлять локальные переменные в начале вашей функции сразу же после первой открывающей фигурной скобки. Имена, назначаемые локальным переменным, должны быть уникальными только для функции, внутри которой эти переменные определены. При объявлении локальной переменной внутри функции ее можно инициализировать с помощью оператора присваивания.

ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ

Как вы уже знаете, локальная переменная объявляется и известна только в определенной функции. В дополнение к локальным переменным C++ позволяет вашим программам объявлять глобальные переменные, которые известны на протяжении всей программы (глобально для всех функций). Чтобы объявить глобальную переменную, следует просто поместить объявление переменной в начало вашей программы вне какой-либо функции:

int some_global_variable; ---> Объявление глобальной переменной

void main(void)

{
// Здесь должны быть операторы программы
}

Следующая программа GLOBAL. CPP использует глобальную переменную именем number. Каждая функция в программе может использовать (или изменять) значение глобальной переменной. В данном случае каждая функция выводит текущее значение этой переменной, а затем увеличивает это значение на единицу:

#include <iostream.h>

int number = 1001;

void first_change(void)

(
cout << "значение number в first_cbange " <<
    number << endl;
number++;
}

void second_change(void)

{
cout << "значение number в second_change " <<
    number << endl;
number++;
}

void main(void)

 {
cout << "значение number в main " << number
    << endl;
number++;
first_change () ;
second_change();
}

Как правило, следует избегать использования в ваших программах глобальных переменных. Поскольку любая функция может изменить значение глобальной переменной, сложно отследить все функции, которые потенциально могли бы изменить данную переменную. Вместо этого вашим программам следует объявлять переменную внутри main и затем передавать ее (как параметр) в функции, которым она нужна. (Помните, что в данной ситуации в стек помещается временная копия этой переменной; оригинал не изменяется.)

Если имена глобальных и локальных переменных конфликтуют

По мере возможности следует избегать использования в программах глобальных переменных. Однако если ваша программа должна использовать глобальную переменную, то может случиться, что имя глобальной переменной конфликтует с именем локальной переменной. При возникновении такого конфликта C++ предоставляет приоритет локальной переменной. Другими словами, программа предполагает, что в случае конфликта каждая ссылка на имя соответствует локальной переменной.

Однако могут быть ситуации, когда вам необходимо обратиться к глобальной переменной, чье имя конфликтует с именем локальной переменной. В таких случаях ваши программы могут использовать глобальный оператор разрешения С++ (::), если вы хотите использовать глобальную переменную. Например, предположим, что у вас есть глобальная и локальная переменные с именем number. Если ваша функция хочет использовать локальную переменную number, она просто обращается к этой переменной, как показано ниже:

number = 1001; // обращение к локальной переменной

С другой стороны, если ваша функция хочет обратиться к глобальной переменной, программа использует глобальный оператор разрешения, как показано ниже:

::number = 2002; // Обращение к глобальной переменной

Следующая программа GLOBLOCA.CPP использует глобальную переменную number. В дополнение к этому функция show_numbers использует локальную переменную с именем number. Эта функция использует оператор глобального разрешения для обращения к глобальной переменной:

#include <iostream.h> int number = 1001; // Глобальная переменная

void show_numbers(int number)

{
cout << "Локальная переменная number" << " содержит " << number << endl;
cout << "Глобальная переменная number" << " содержит " << ::number << endl;
}

void
    main(void)
{
int some_value = 2002;
show_numbers(some_value) ;
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> GLOBLOCA <ENTER>

Локальная переменная number содержит 2002

Глобальная переменная number содержит 1001

Как видите, ваши программы могут выбрать глобальную или локальную переменную с помощью глобального оператора разрешения. Однако, как вы, вероятно, заметили, использование глобальных и локальных переменных может вызвать путаницу, которая в свою очередь может привести к ошибкам. Поэтому по мере возможности избегайте использования глобальных переменных.

О глобальных переменных

Глобальная переменная представляет собой переменную, чье имя и значение известны на протяжении всей программы. Для создания глобальной переменной вы объявляете переменную в начале вашего исходного файла вне какой-либо функции. Все функции, которые следуют за таким объявлением, могут использовать эту глобальную переменную. Однако, поскольку злоупотребление глобальными переменными может привести к ошибкам, следует избегать их использования в ваших программах везде, где это возможно.

Представление об области видимости переменных

При чтении книг и журнальных статей по C++ вы можете встретить термин область видимости, что определяет участок в программе, где имя переменной имеет смысл (и, следовательно, используется). Для локальной переменной область видимости ограничена функцией, внутри которой эта переменная объявлена. С другой стороны, глобальные переменные известны на протяжении всей программы. В результате глобальные переменные имеют большую область видимости.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, как объявлять локальные переменные внутри функции. Как только ваши функции выполняют более или менее существенную работу, им требуются локальные переменные. В этом уроке представлены также глобальные переменные, чьи имена и значения известны на протяжении всей программы. Поскольку они могут приводить к трудно обнаруживаемым ошибкам, лучше избегать использования глобальных переменных, если только это возможно. Из урока 13 вы узнаете, как C++ позволяет объявлять две или несколько функций с одним и тем же именем, но с разными параметрами и типами возвращаемых значений. Перегружая подобным образом имена функций, вы упрощаете использование функций. Прежде чем перейти к уроку 13, убедитесь, что вы изучили следующие основные концепции:

    1. Локальные переменные представляют собой переменные, объявленные внутри функции.
    2. Локальные переменные известны только той функции, внутри которой они объявлены.
    3. Несколько функций могут использовать одно и то же имя для локальной переменной без каких-либо конфликтов.
    4. Глобальная переменная представляет собой переменную, чье имя и значение известны на протяжении всей программы.
    5. Чтобы объявить глобальную переменную, объявите переменную в начале вашего исходного файла вне какой-либо функции.
    6. Поскольку глобальные переменные могут быть легко изменены любой функцией, они привносят возможность появления трудно обнаруживаемьгх ошибок, поэтому избегайте использования глобальных переменных в своих программах.


Untitled Document

Урок 13

Перегрузка функций

При определении функций в своих программах вы должны указать тип возвращаемого функцией значения, а также количество параметров и тип каждого из них. В прошлом (если вы программировали на языке С), когда у вас была функция с именем add_values, которая работала с двумя целыми значениями, а вы хотели бы использовать подобную функцию для сложения трех целых значений, вам следовало создать функцию с другим именем. Например, вы могли бы использовать add_two_values и add_three_values. Аналогично если вы хотели использовать подобную функцию для сложения значений типа float, то вам была бы необходима еще одна функция с еще одним именем. Чтобы избежать дублирования функции, C++ позволяет вам определять несколько функций с одним и тем же именем. В процессе компиляции C++ принимает во внимание количество аргументов, используемых каждой функцией, и затем вызывает именно требуемую функцию. Предоставление компилятору выбора среди нескольких функций называется перегрузкой. В этом уроке вы научитесь использовать перегруженные функции. К концу данного урока вы освоите следующие основные концепции:

• Перегрузка функций позволяет вам использовать одно и то же имя для нескольких функций с разными типами параметров.

• Для перегрузки функций просто определите две функции с одним и тем же именем и типом возвращаемого значения, которые отличаются количеством параметров или их типом.

Перегрузка функций является особенностью языка C++, которой нет в языке С. Как вы увидите, перегрузка функций достаточно удобна и может улучшить удобочитаемость ваших программ.

ПЕРВОЕ ЗНАКОМСТВО С ПЕРЕГРУЗКОЙ ФУНКЦИЙ

Перегрузка функций позволяет вашим программам определять несколько функций с одним и тем же именем и типом возвращаемого значения. Например, следующая программа перегружает функцию с именем add_values. Первое определение функции складывает два значения типа int. Второе определение функции складывает три значения. В процессе компиляции C++ корректно определяет функцию, которую необходимо использовать:

#include <iostream.h>

int add_values(int a,int b)

{
   return(a + b);
)

int add_values (int a, int b, int c)

(
   return(a + b + c);
)

void main(void)

{
cout << "200 + 801 = " << add_values(200, 801)
    << endl;
cout << "100 + 201 + 700 = " << add_values(100,
    201, 700) << endl;
}

Как видите, программа определяет две функции с именами add_values Первая функция складывает два значения типа int, в то время как вторая складывает три значения. Вы не обязаны что-либо предпринимать специально для того, чтобы предупредить компилятор о перегрузке, просто используйте ее. Компилятор разгадает, какую функцию следует использовать, основываясь на предлагаемых программой параметрах.

Подобным образом следующая программа MSG_OVR.CPP перегружает функцию show_message. Первая функция с именем show_message выводит стандартное сообщение, параметры ей не передаются. Вторая выводит передаваемое ей сообщение, а третья выводит два сообщения:

#include <iostream.h>

void show_message(void)

{
cout << "Стандартное сообщение: " << "Учимся программировать на C++" << endl;
}

void show_message(char *message)

{
cout << message << endl;
}

void show_message(char *first, char *second)

{
   cout << first << endl;
   cout << second << endl;
}

void main(void)

{
show_message();
show_message("Учимся программировать на языке C++!");
show_message("B C++ нет предрассудков!","Перегрузка - это
    круто!") ;
}

КОГДА НЕОБХОДИМА ПЕРЕГРУЗКА

Одним из наиболее общих случаев использования перегрузки является применение функции для получения определенного результата, исходя из различных параметров. Например, предположим, что в вашей программе есть функция с именем day_of_week, которая возвращает текущий день недели (0 для воскресенья, 1 для понедельника, ..., 6 для субботы). Ваша программа могла бы перегрузить эту функцию таким образом, чтобы она верно возвращала день недели, если ей передан юлианский день в качестве параметра, или если ей переданы день, месяц и год:

int day_of_week(int julian_day)

{
// Операторы
}

int day_of_week(int month, int day, int year)

{
// Операторы
}

По мере изучения объектно-ориентированного программирования в C++, представленного в следующих уроках, вы будете использовать перегрузку функций для расширения возможностей своих программ.

Перегрузка функций улучшает удобочитаемость программ

Перегрузка функций C++ позволяет вашим программам определять несколько функций с одним и тем же именем. Перегруженные функции должны возвращать значения одинакового типа*, но могут отличаться количеством и типом параметров. До появления перегрузки функций в C++ программисты языка С должны были создавать несколько функций с почти одинаковыми именами. К сожалению программисты, желающие использовать такие функции, должны были помнить, какая комбинация параметров соответствует какой функции. С другой стороны, перегрузка функций упрощает задачу программистов, требуя, чтобы они помнили только одно имя функции.

* Перегруженные функции не обязаны возвращать значения одинакового типа по той причине, что компилятор однозначно идентифицирует функцию по ее имени и набору ее аргументов. Для компилятора функции с одинаковыми именами, но различными типами аргументов — разные функции, поэтому тип возвращаемого значения — прерогатива каждой функции. — Прим.перев.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Перегрузка функций позволяет вам указать несколько определений для одной и той же функции. В процессе компиляции C++ определит, какую функцию следует использовать, основываясь на количестве и типе передаваемых параметров. Из данного урока вы узнали, что перегружать функции достаточно просто. Из урока 14 вы узнаете, как ссылки C++ упрощают процесс изменения параметров внутри функций. Однако, прежде чем перейти к уроку 14, убедитесь, что вы изучили следующие основные концепции:
    1. Перегрузка функций предоставляет несколько "взглядов" на одну и ту же функцию внутри вашей программы.
    2. Для перегрузки функций просто определите несколько функций с одним и тем же именем и типом возвращаемого значения, которые отличаются только количеством и типом параметров.
    3. В процессе компиляции C++ определит, какую функцию следует вызвать, основываясь на количестве и типе передаваемых параметров.
    4. Перегрузка функций упрощает программирование, позволяя программистам работать только с одним именем функции.


Untitled Document

Урок 14

Использование ссылок в C++

Из урока 10 вы узнали, как изменять параметры внутри функции с помощью указателей. Для использования указателей вы должны предварять имена переменных-указателей звездочкой. Использование указателей досталось в "наследство" от языка С. Чтобы упростить процесс изменения параметров, С++ вводит такое понятие как ссылка. Как вы узнаете из этого урока, ссылка представляет собой псевдоним (или второе имя), который ваши программы могут использовать для обращения к переменной. К концу данного урока вы освоите следующие основные концепции:

• Для объявления и инициализации ссылки внутри программы объявите переменную, размещая амперсанд (&) сразу же после типа переменной, и затем используйте оператор присваивания для назначения псевдонима, например int& alias_name = variable',.

• Ваши программы могут передавать ссылки в функцию в качестве параметров, а функция, в свою очередь, может изменять соответствующее значение параметра, не используя указателей.

• Внутри функции вам следует объявить параметр как ссылку, размещая амперсанд (&) после типа параметра, затем можно изменять значение параметра внутри функции без помощи указателей.

Как вы узнаете, использование указателей очень упрощает изменение значений параметров внутри функции.

ССЫЛКА ЯВЛЯЕТСЯ ПСЕВДОНИМОМ

Ссылка C++ позволяет создать псевдоним (или второе имя) для переменных в вашей программе. Для объявления ссылки внутри программы укажите знак амперсанда (&) непосредственно после типа параметра. Объявляя ссылку, вы должны сразу же присвоить ей переменную, для которой эта ссылка будет псевдонимом, как показано ниже:

int& alias_name = variable; //---> Объявление ссылки

После объявления ссылки ваша программа может использовать или переменную, или ссылку:

alias_name = 1001;
variable = 1001;

Следующая программа SHOW_REF.CPP создает ссылку с именем alias_name и присваивает псевдониму переменную number. Далее программа использует как ссылку, так и переменную:

#include <iostream.h>

void main(void)

{
    int number = 501;
   int& alias_name = number; // Создать ссылку
   cout << "Переменная number содержит " << number << endl;
   cout << "Псевдоним для number содержит " << alias_name << endl;
   alias_name = alias_name + 500;
   cout << "Переменная number содержит " << number << endl;
   cout << "Псевдоним для number содержит " << alias_name << endl;
}

Как видите, программа прибавляет 500 к ссылке alias_name. В итоге программа прибавляет 500 также и к соответствующей переменной number, для которой ссылка служит псевдонимом или вторым именем. Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> SHOW_REF <ENTER>

Переменная number содержит 501

Псевдоним для number содержит 501

Переменная number содержит 1001

Псевдоним для number содержит 1001

В общем случае использование ссылки таким образом, как только что было показано, создает трудности для понимания. Однако вы увидите, что использование ссылок значительно упрощает процесс изменения параметров внутри функции.

Объявление ссылки

Ссылка C++ представляет собой псевдоним (второе имя), которое ваши программы могут использовать для обращения к переменной. Для объявления ссылки поставьте амперсанд (&) сразу же после типа переменной, а затем укажите имя ссылки, за которым следует знак равенства и имя переменной, для которой ссылка является псевдонимом:

float& salary_alias = salary;

ИСПОЛЬЗОВАНИЕ ССЫЛОК В КАЧЕСТВЕ ПАРАМЕТРОВ

Основное назначение ссылки заключается в упрощении процесса изменения параметров внутри функции. Следующая программа REFERENC.CPP присваивает ссылку с именем number_alias переменной number. Программа передает ссылку на переменную в функцию change_value, которая присваивает переменной значение 1001:

#include <iostream.h>

void change_value(int &alias)

{
alias = 1001;
}

void main(void)

 {
int number;
int& number_alias = number;
change_value(number_alias);
out << "Переменная number содержит " << number
    << endl;
}

Как вы видите, программа передает ссылку в функцию change_value. Если вы рассмотрите объявление функции, вы обнаружите, что change_value объявляет параметр alias как ссылку на значение типа int.

void change_value(int& alias)

Внутри функции change_value можете изменять значение параметра без помощи указателя. В результате звездочка (*) не используется и операция внутри функции становится легче для понимания.

Использование комментариев для объяснения ссылок внутри ваших программ

Большинство программистов C++ знакомы с языком программирования С, и они привыкли использовать указатели внутри функции, если необходимо изменить значение параметра. В результате, если такие программисты не видят указатели внутри функций, которые используют ссылки, они могут предположить, что значения параметров не изменяются. Для предотвращения подобных промахов не забывайте размещать несколько комментариев до и внутри функций, которые изменяют параметры с помощью ссылок. В таком случае программисты С лучше поймут работу ваших функций.

Рассмотрим второй пример

В уроке 10 вы использовали следующую функцию для перестановки двух значений с плавающей точкой:

void swap_values(float *a, float *b)

{
float temp;
temp = *a;
*a = *b;
*b = temp;
}

Как видите, функция комбинирует переменные-указатели с переменными-неуказателями. Следующая программа SWAP_REF.CPP использует ссылки на значения с плавающей точкой для упрощения функции:

#include <iostream.h>

void
    swap_values(float& a, float& b)
{    float
    temp;
temp = a;
a = b;
b = temp;
}

void main(void)

 {
       float
    big = 10000.0;
float small = 0.00001;
float& big_alias = big;
float& small_alias = small;
swap_values(big_alias, small_alias);
cout << "Big содержит " << big << endl;
cout << "Small содержит " << small <<	endl;
}

Как видите, функцию swap_values сейчас легче понять, однако ваша программа имеет теперь два дополнительных имени (ссылки big_alias и small_alias), за которыми вы должны следить.

ПРАВИЛА РАБОТЫ СО ССЫЛКАМИ

Ссылка не является переменной. Один раз присвоив значение ссылке, вы уже не можете ее изменить. Кроме того в отличие от указателей вы не можете выполнить следующие операции над ссылками:

• Вы не можете получить адрес ссылки, используя оператор адреса C++.

• Вы не можете присвоить ссылке указатель.

• Вы не можете сравнить значения ссылок, используя операторы сравнения C++.

• Вы не можете выполнить арифметические операции над ссылкой, например добавить смещение.

•Вы не можете изменить ссылку.

По мере использования объектно-ориентированного программирования на C++ вы вернетесь к ссылкам.

Использование ссылок для изменения параметров функции

Из урока 10 вы узнали, что ваши программы с помощью указателей могут изменять значение параметров внутри функции. Для изменения параметра вы должны передать его адрес в функцию. Чтобы получить адрес параметра, используйте оператор адреса C++ (&). В свою очередь функция использует переменные-указатели (которые хранят адрес памяти). Для объявления переменной-указателя внутри функции предваряйте имя параметра звездочкой (*). Чтобы изменить или использовать значение параметра внутри функции, предваряйте каждое обращение к имени этого параметра оператором разыменования C++ (*). К сожалению, многие операции внутри функции комбинируют переменные-указатели и переменные-неуказатели.
Ссылки C++ упрощают процесс изменения параметров функции, избавляя от операторов, которые смешивают переменные-указатели и переменные-неуказатели.

ЧТ0 ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, как использовать ссылки C++ для создания псевдонима или второго имени переменной. Использование ссылок может упростить функции, изменяющие значения параметров. Из урока 15 вы узнаете, что C++ позволяет вам задавать значения по умолчанию для параметров функции. При вызове функции программа может опускать значения одного или нескольких параметров и функция будет использовать значения по умолчанию. До изучения урока 15 убедитесь, что вы освоили следующие основные концепции:
    1. Ссылка C++ является псевдонимом (или вторым именем) переменной.
    2. Для объявления ссылки поместите знак амперсанда (&) непосредственно после типа переменной, а затем укажите имя ссылки, за которым следует знак равенства и имя переменной, для которой ссылка является псевдонимом.
    3. Если вы однажды присвоили ссылке значение, вы не можете его изменить.
    4. Вам следует помещать несколько комментариев до и внутри функций, которые используют ссылки для изменения значений параметра, чтобы другие программисты, читающие ваш код, сразу обратили, на это внимание.
    5. Чрезмерное использование ссылок может привести к слишком трудному для понимания программному коду.


Untitled Document

Урок 15

Значения параметров по умолчанию

Как вы уже знаете, C++ позволяет вам с помощью параметров передавать информацию в функции. Из урока 13 вы выяснили, что C++ также обеспечивает перегрузку функций, предусматривая определения, содержащие разное количество параметров или даже параметры разных типов. Кроме этого, в C++ при вызове функций можно опускать параметры. В таких случаях для опущенных параметров будут использоваться значения по умолчанию. Этот урок описывает как устанавливать значения по умолчанию для параметров функций. К концу данного урока вы освоите следующие основные концепции:

• C++ позволяет программам указывать для параметров значения по умолчанию.

• Значения по умолчанию для параметров указываются в заголовке функции при ее определении.

• Если вызов функции опускает значения одного или нескольких параметров, C++ будет использовать значения по умолчанию.

• Если вызов функции опускает значение определенного параметра, то должны быть опущены и значения всех последующих параметров.

Обеспечение значений по умолчанию для параметров упрощает возможность повторного использования функций (их использования несколькими программами).

ОПРЕДЕЛЕНИЕ ЗНАЧЕНИЙ ПО УМОЛЧАНИЮ

Обеспечить значения по умолчанию для параметров функции очень легко. Вы просто присваиваете значение параметру с помощью оператора присваивания С++ прямо при объявлении функции, как показано ниже:

void some_function(int size=12, float cost=19.95) //---->Значения по умолчанию

{
// Операторы функции
}

Следующая программа DEFAULTS. CPP присваивает значения по умолчанию параметрам a, b и c внутри функции show_parameters. Затем программа четыре раза вызывает эту функцию, сначала не указывая параметров вообще, затем указывая значение только для а, потом значения для а и b и, наконец, указывая значения для всех трех параметров:

#include <iostream.h>

void show__parameters (int a=1, int b=2, int c=3)

{
cout << "a" << a << " b " << b << " с " << с << endl;
}

void main(void)

{
show_parameters();
show_parameters(1001);
show_parameters(1001, 2002);
show_parameters(1001, 2002, 3003);
}

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> DEFAULTS <ENTER>

а 1 b 2 с 3

а 1001 b 2 с 3

а 1001 b 2002 с 3

а 1001 b 2002 с 3003

Как видите, если необходимо, функция использует значения параметров по умолчанию.

Правила для пропуска значений параметров

Если программа опускает определенный параметр для функции, обеспечивающей значения по умолчанию, то следует опустить и все последующие параметры. Другими словами, вы не можете опускать средний параметр. В случае предыдущей программы, если требовалось опустить значение параметра b в show_parameters, программа также должна была опустить значение параметра с. Вы не можете указать значение для а и с, опуская значение Ь.

Задание значений по умолчанию

Когда вы определяете функцию, C++ позволяет вам указать значения по умолчанию для одного или нескольких параметров. Если программа в дальнейших вызовах этой функции опускает один или несколько параметров, то функция будет использовать для них значения по умолчанию. Чтобы присвоить параметру значение по умолчанию, просто используйте оператор присваивания внутри определения функции.
Например, следующая функция payroll указывает значения по умолчанию для параметров hours и rate:

float payroll(int employ_id, float hours = 40, float rate = 5.50)

{

// операторы
}

Когда программа опускает один параметр, она должна опускать все последующие параметры.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из данного урока вы узнали, что C++ позволяет вам указывать значения по умолчанию для параметров функции. Если программа опускает один или несколько параметров, функция использует значения по умолчанию. В следующих уроках, когда ваши программы начнут использовать объектно-ориентированное программирование на C++, вы будете применять параметры по умолчанию для инициализации различных переменных класса. Как вы уже знаете, переменная позволяет вам хранить значение определенного типа (int, float и. т. д). В уроке 16 вы освоите, как хранить несколько значений одного и того же типа внутри массива. Например, ваша программа может хранить тестовые очки для 100 студентов или стоимость 50 акций. С помощью массивов хранить и использовать такие значения очень легко. До изучения урока 16 убедитесь, что вы освоили следующие основные концепции:

    1. Чтобы присвоить значения по умолчанию параметрам функции, используйте оператор присваивания C++ непосредственно в определении функции.
    2. Если программа опускает значения параметров при вызове функции, функция использует значения по умолчанию.
    3. Если программа опускает значение одного параметра, она должна опустить значения для всех последующих параметров; программа не может опускать средний параметр.
    4. Указывая параметры по умолчанию, вы тем самым облегчаете использование ваших функций, в том числе и для других программ.


Untitled Document

Урок 16

Хранение значений в массивах

Как вы уже знаете, ваши программы во время выполнения хранят информацию в переменных. До сих пор каждая переменная в программе хранила только одно значение в каждый момент времени. Однако в большинстве случаев программам необходимо хранить множество значений, например 50 тестовых очков, 100 названий книг или 1000 имен файлов. Если вашим программам необходимо хранить несколько значений, они должны использовать специальную структуру данных, называемую массивом. Для объявления массива необходимо указать имя, тип массива и количество значений, которые массив будет хранить. Этот урок описывает, как программы объявляют массивы, а затем сохраняют и обращаются к информации, содержащейся в массиве. К концу данного урока вы освоите следующие основные концепции:

  • Массив представляет собой структуру данных, которая позволяет одной переменной хранить несколько значений.
  • При объявлении массива вы должны указать тип значений, хранящихся в массиве, а также количество значений (называемых элементами массива).
  • Все элементы внутри массива должны быть одного и того же типа, например, int, float или char.
  • Для сохранения значения внутри массива вам следует указать номер элемента массива, в котором вы хотите сохранить свое значение.
  • Чтобы обратиться к значению, хранящемуся внутри массива, ваши программы указывают имя массива и номер элемента.
  • При объявлении массива программы могут использовать оператор присваивания для инициализации элементов массива.
  • Программы могут передавать переменные-массивы в функции точно так же, как они передают любой другой параметр.

Программы на C++ широко используют массивы. Когда в уроке 17 вы начнете работать с символьными строками (например, название книги, имя файла и т. д.), вы будете оперировать массивами символов.

ОБЪЯВЛЕНИЕ ПЕРЕМЕННОЙ МАССИВА

Массив представляет собой переменную, способную хранить одно или несколько значений. Подобно переменным, используемым вашими программами до сих пор, массив должен иметь тип (например, inl, char или float) и уникальное имя. В дополнение к этому вам следует указать количество значений, которые массив будет хранить. Все сохраняемые в массиве значения должны быть одного и того же типа. Другими словами, ваша программа не может поместить значения типа float, char и long в один и тот же массив. Следующее объявление создает массив с именем test_scores, который может вмещать 100 целых значений для тестовых очков:

———————————————— Тип массива

int test_scores[100]; //------> Размер массива

Когда компилятор C++ встречает объявление этой переменной, он распределит достаточно памяти для хранения 100 значений типа int. Значения, хранящиеся в массиве, называются элементами массива.

Массивы хранят несколько значений одного и того же типа

По мере усложнения вашим программам потребуется работать с несколькими значениями одного и того же типа. Например, программы могут хранить возраст 100 служащих или стоимость 25 акций. Вместо того чтобы заставлять программу работать со 100 или с 25 переменными с уникальными именами, C++ позволяет вам определить одну переменную — массив —, которая может хранить несколько связанных значений.

Для объявления массива вы должны указать тип и уникальное имя массива, а также количество элементов, которые будет содержать массив. Например, следующие операторы объявляют три разных массива:

float part_cost[50];
int employee_age[100];
float stock_prices[25];

Обращение к элементам массива

Как вы уже знаете, массив позволяет вашим программам хранить несколько значений в одной и той же переменной. Для обращения к определенным значениям, хранящимся в массиве, используйте значение индекса, которое указывает на требуемый элемент. Например, для обращения к первому элементу массива test_scores вы должны использовать значение индекса 0. Для обращения ко второму элементу используйте индекс 1. Подобно этому, для обращения к третьему элементу используйте индекс 2. Как показано на рис. 16.1, первый элемент массива всегда имеет индекс 0, а значение индекса последнего элемента на единицу меньше размера массива:

Рис. 16.1. Как C++ индексирует элементы массива.

Важно помнить, что C++ всегда использует 0 для индекса первого элемента массива, а индекс последнего элемента на единицу меньше размера массива. Следующая программа ARRAY. CPP создает массив с именем values, который вмещает пять целочисленных значений. Далее программа присваивает элементам значения 100, 200, 300, 400 и 500:

#include <iostream.h>

void main(void)

{
int values[5]; // Объявление массива
values[0] = 100;
values[1] = 200;
values[2] = 300;
values[3] = 400;
values [4] = 500;
cout << "Массив содержит следующие значения"
    << endl;
cout << values [0] << ' ' << values [1]
    << ' ' << values [2] << ' ' << values [3] <<
    ' ' << values [4] << endl;
}

Как видите, программа присваивает первое значение элементу 0 (values[0]). Она также присваивает последнее значение элементу 4 (размер Массива (5) минус 1).

Использование индекса для обращения к элементам массива

Массив позволяет вашим программам хранить несколько значений внутри одной и той же переменной. Для обращения к определенному значению внутри массива программы используют индекс. Говоря кратко, значение индекса указывает требуемый элемент массива. Все массивы C++ начинаются с элемента с индексом 0. Например, следующий оператор присваивает значение 100 первому элементу массива с именем scores:

scores[0] = 100;

Когда ваша программа объявляет массив, она указывает количество элементов, которые массив может хранить. Например, следующий оператор объявляет массив, способный хранить 100 значений типа int.

int scores[100];

В данном случае массив представляет собой элементы от scores[0] до scores[99].

Использование индексной переменной

Если ваши программы используют массив, обычной операцией является использование индексной переменной для обращения к элементам массива. Например, предположим, что переменная / содержит значение 3, следующий оператор присваивает значение 400 элементу values[3J:

values[i] = 400;

Следующая программа SHOWARRA.CPP использует индексную переменную i внутри цикла for для вывода элементов массива. Цикл for инициализирует i нулем, так что программа может обращаться к элементу values[O]. Цикл for завершается, когда i больше 4 (последний элемент массива):

#include <iostream.h>

void main (void)

{
int values[5]; // Объявление массива int i;
values[0] = 100;
values[1] = 200;
values[2] = 300;
values[3] = 400;
values[4] = 500;
cout << "Массив содержит следующие значения"
    << endl;
for (i = 0; i < 5; i++) cout << values [i]
    << ' ';
}

Каждый раз, когда цикл for увеличивает переменную i, программа может обратиться к следующему элементу массива. Экспериментируйте с этой программой, изменяя цикл for следующим образом:

for (i = 4; i >= 0; i--) cout << values [i] << ' ';

В данном случае программа будет выводить элементы массива от большего к меньшему.

ИНИЦИАЛИЗАЦИЯ МАССИВА ПРИ ОБЪЯВЛЕНИИ

Как вы уже знаете, C++ позволяет вашим программам инициализировать переменные при объявлении. То же верно и для массивов. При объявлении массива вы можете указать первоначальные значения, поместив их между левой и правой фигурными скобками, следующими за знаком равенства. Например, следующий оператор инициализирует массив values:

int values[5] = { 100, 200, 300, 400, 500 };

Подобным образом следующее объявление инициализирует массив с плавающей точкой:

float salaries[3] = { 25000.00. 35000.00, 50000.00 };

Если вы не указываете первоначальное значение для какого-либо элемента массива, большинство компиляторов C++ будут инициализировать такой элемент нулем. Например, следующее объявление инициализирует первые три из пяти элементов массива:

int values[5] = { 100, 200, 300 };

Программа не инициализирует элементы values[3] и values[4]. В зависимости от вашего компилятора, эти элементы могут содержать значение 0. Если вы не указываете размер массива, который вы инициализируете при объявлении, C++ распределит достаточно памяти, чтобы вместить все определяемые элементы. Например, следующее объявление создает массив, способяый хранить четыре целочисленных значения:

int numbers[] = { 1, 2, 3, 4 };

ПЕРЕДАЧА МАССИВОВ В ФУНКЦИИ

Ващи программы будут передавать массивы в функции точно так же, как и любые другие переменные. Функция может инициализировать массив, прибавить к массиву значения или вывести элементы массива на экран. Когда вы передаете массив в функцию, вы должны указать тип массива. Нет необходимости указывать размер массива. Вместо этого вы передаете параметр например number_of_elements, который содержит количество элементов в массиве:

void some_function(int array[], int number_of_elements);

Следующая программа ARRAYFUN.CPP передает массивы в функцию show_array, которая использует цикл for для вывода значений массивов:

#include <iostream.h>

void show_array (int array [] , int number_of_elements)

{
int i;
for (i
    = 0; i < number_of_elements; i++) cout << array[i] << ' ';
cout
    << endl;
}

void main(void)

{
 int little_numbers[5] ={1,2,3,4,5};
 int big_numbers[3] = { 1000, 2000, 3000 };
 show_array(little_numbers, 5);
 show_array(big_numbers, 3);
}

Как видите, программа просто передает массив в функцию по имени, а также указывает параметр, который сообщает функции количество элементов, содержащихся в массиве:

show_array(little_numbers, 5);

Следующая программа GETARRAY.CPP использует функцию get_values, ч тобы присвоить три значения массиву numbers:

#include <iostream.h>

void get_values(int array[], int number_of_elements)

{
int i;
for (i = 0; i < number_of_elements; i++)

   {
cout “ "Введите значение " <<
    i << ": ";
cin ” array [i];
}
}

void main(void)

{
int numbers[3];
get_values(numbers, 3);
cout << "Значения массива" <<
    endl;
for (int i = 0; i < 3; i++)
cout << numbers [i] << endl;
}

Как видите, программа передает массив в функцию по имени. Функция в свою очередь присваивает массиву элементы. Из урока 10 вы узнали, что, пока ваша программа не передаст параметры в функцию с помощью адреса, функция не может изменить эти параметры. Однако, как можно заметить в данном случае, функция get_values изменяет параметр-массив numbers. Как вы узнаете из урока 20, C++ действительно передает массивы в функцию, используя указатели. Таким образом, функция может изменить элементы массива, если ей это нужно.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что ваши программы могут хранить несколько значений одного и того же типа внутри массива. Программы на C++ широко используют массивы. Из урока 17 вы узнаете, что программы могут использовать массивы для хранения символьных строк. До изучения урока 17 убедитесь, что вы освоили следующие основные концепции:

  1. Массив представляет собой переменную, которая может хранить одно или несколько значений одного и того же типа.
  2. Для объявления массива вам следует указать тип, имя массива, а также количество значений, хранящихся в массиве.
  3. Значения внутри массива называются элементами массива.
  4. Первый элемент массива хранится как элемент 0 (array[OJ), индекс последнего элемента массива на единицу меньше размера массива.
  5. Программы часто используют индексные переменные для обращения к элементам массива.
  6. Если функция воспринимает массив как параметр, она должна указать тип и имя, но не размер массива.
  7. Если программа передает массив в функцию, она, как правило, передает и параметр, который сообщает функции количество элементов содержащихся в массиве.
  8. Так как C++ передает массив в функцию с помощью адреса массива функция может изменять значения, содержащиеся в массиве.


Untitled Document

Урок 17

Символьные строки

Символьные строки хранят такую информацию, как имена файлов, названия книг, имена служащих и другие символьные сочетания. Большинство программ на C++ широко используют символьные строки. Далее вы узнаете, что в C++ символьные строки хранятся в массиве типа char, который заканчивается символом NULL (или ASCII 0). В данном уроке символьные строки рассматриваются более подробно. Вы узнаете, как хранить и обрабатывать символьные строки, а также как использовать функции библиотеки этапа выполнения, которые манипулируют символьными строками. К концу этого урока вы освоите следующие основные концепции:

  • Чтобы объявить символьную строку, вы должны объявить массив типа char,
  • Чтобы присвоить символы символьной строке, ваши программы просто присваивают символы элементам массива символьных строк.
  • Программы C++ используют символ NULL (ASCII 0), чтобы отметить последний символ строки.
  • C++ позволяет вашим программам инициализировать символьные строки при их объявлении.
  • Программы могут передавать символьные строки в функцию, как и любой массив.
  • Большинство библиотек этапа выполнения C++ обеспечивают набор функций, которые управляют символьными строками.

Программы на C++ хранят символьные строки как массив типа char. Большинство программ широко используют символьные строки. Экспериментируйте с каждой программой, представленной в этом уроке, чтобы освоиться с символьными строками. Вы обнаружите, что работа с символьными строками подобна работе с массивами, описанной в уроке 16.

ОБЪЯВЛЕНИЕ СИМВОЛЬНЫХ СТРОК В ПРОГРАММАХ

Программисты на C++ широко используют символьные строки для хранения имен пользователей, имен файлов и другой символьной информации.

Для объявления символьной строки внутри программы просто объявите массив типа char с количеством элементов, достаточным для хранения требуемых символов. Например, следующее объявление создает переменную символьной строки с именем filename, способную хранить 64 символа (не забывайте, что символ NULL является одним из этих 64 символов):

char filename[64];

Как видно из рис. 17.1, это объявление создает массив с элементами, индексируемыми от filename[0] до filename[63].

Рис. 17.1. C++ трактует символьную строку как массив типа char.

Главное различие между символьными строками и другими типами массивов заключается в том, как C++ указывает последний элемент массива, Как вы уже знаете, программы на C++ представляют конец символьной строки с помощью символа NULL, который в C++ изображается как специальный символ '\0'. Когда вы присваиваете символы символьной строке, вы должны поместить символ NULL ('\0') после последнего символа в строке. Например, следующая программа ALPHABET. CPP присваивает буквы от А до Я переменной alphabet, используя цикл for. Затем программа добавляет символ NULL в эту переменную и выводит ее с помощью cout.

#include <iostream.h>

void main(void)

{
 char alphabet [34]; // 33 буквы плюс NULL char letter;
 int index;
 for (letter = 'A', index = 0; letter <= 'Я';
 letter++, index++) alphabet[index] = letter;
 alphabet[index] = NULL;
 cout << "Буквы " << alphabet;
}

Как видите, программа присваивает строке символ NULL, чтобы указать последний символ строки:

alphabet[index] = NULL;

Когда выходной поток cout выводит символьную строку, он по одному выводит символы строки, пока не встретит символ NULL. Короче говоря, cимвол NULL указывает программе последний символ в строке.

Обратите внимание на цикл for, который появляется в предыдущей программе. Как видите, цикл инициализирует и увеличивает две переменные (letter и index). Когда цикл for инициализирует или увеличивает несколько переменных, разделяйте операции запятой (запятая тоже является оператором C++):

for (letter = 'A', index = 0; letter <= 'Я'; letter++, index++)

C++ автоматически добавляет NULL к строковым константам

Все созданные вами программы использовали символьные строковые константы, заключенные внутри двойных кавычек, как показано ниже:

"Это строковая константа"

При создании символьной строковой константы компилятор C++ автоматически добавляет символ NULL, как показано на рис. 17.2.

Рис. 17.2. Компилятор C++ автоматически добавляет символ NULL к строковым константам.

Когда ваши программы выводят символьные строковые константы с помощью выходного потока cout, cout использует символ NULL (который компилятор добавляет к строке) для определения последнего символа вывода.

Использование символа NULL

Символьная строка представляет собой массив символов, за которыми следует символ NULL ('\0'). При объявлении символьной строки вы объявляете массив типа char. Когда программа позднее присваивает символы строке, она отвечает за добавление символа NULL, который представляет конец строки.

Если вы используете строковые константы, заключенные в двойные кавычки, компилятор C++ автоматически добавляет символ NULL. Большинство функций C++ используют символ NULL для определения последнего символа строки.

Следующая программа LOOPNULL.CPP слегка изменяет предыдущую программу, используя цикл for для вывода содержимого строки:

#include <iostream.h>

void main(void)

{
char
    alphabet[34]; //33 символа плюс NULL char letter;
int
    index;
for (letter = 'A', index = 0; letter <= 'Я'; letter++,index++)
    alphabet[index] = letter;
alphabet[index]
    = NULL;
for
    (index = 0; alphabet[index] 1= NULL; index++) cout << alphabet[index];
cout
    << endl;
}

Как видите, цикл for по одному исследует символы строки. Если символ не NULL (не последний символ строки), цикл выводит символ, увеличивает индекс, и процесс продолжается.

Как 'А' отличается от "А"

При рассмотрении программ на C++ вы можете встретить символы, заключенные в одинарные кавычки (например, 'А') и символы, заключенные в

Рис. 17.3. Как компилятор C++ хранит символьную константу 'А' и строковую константу "А".

двойные кавычки ("А"). Символ внутри одинарных кавычек представляет собой символьную константу. Компилятор C++ выделяет только один байт памяти для хранения символьной константы. Однако символ в двойных кавычках представляет собой строковую константу — указанный символ и символ NULL (добавляемый компилятором). Таким образом, компилятор будет выделять два байта для символьной строки. Рисунок 17.3 иллюстрирует, как компилятор C++ хранит символьную константу 'А' и строковую константу "А".

ИНИЦИАЛИЗАЦИЯ СИМВОЛЬНОЙ СТРОКИ

Как вы уже знаете из урока 16, C++ позволяет вам инициализировать массивы при объявлении. Символьные строки C++ не являются исключением. Для инициализации символьной строки при объявлении укажите требуемую строку внутри двойных кавычек, как показано ниже:

char title[64] = "Учимся программировать на языке C++";

Если количество символов, присваиваемое строке, меньше размера массива, большинство компиляторов C++ будут присваивать символы NULL остающимся элементам строкового массива. Как и в случае с массивами других типов, если вы не указываете размер массива, который инициализируете при объявлении, компилятор C++ распределит достаточно памяти для размещения указанных букв и символа NULL:

char title[] = "Учимся программировать на языке C++";

Следующая программа INIT_STR.CPP инициализирует символьную строку при объявлении:

#include <iostream.h>

void main(void)

{
 char title[64] = "Учимся программировать на языке C++";
 char lesson[64] = "Символьные строки";
 cout << "Книга: " << title << endl;
 cout << "Урок: " << lesson << endl;
}

Некоторые программы, представленные в оставшейся части книги, будут инициализировать символьные строки подобным способом. Найдите время для эксперимента с этой программой, изменяя символы, присваиваемые каждой строке.

ПЕРЕДАЧА СТРОК В ФУНКЦИИ

Передача символьной строки в функцию подобна передаче любого массива в качестве параметра. Внутри функции вам нужно просто указать тип массива (char) и левую и правую скобки массива. Вам не надо указывать размер строки. Например, следующая программа SHOW_STR.CPP использует функцию show_string для вывода символьной строки на экран:

#include <iostream.h>

void show_string(char string[])

{
 cout << string << endl;
}

void main(void)

{
 show_string("Привет, C++!");
 show_string("Учусь программировать на C++");
}

Как видите, функция show_string трактует параметр символьной строки как массив:

void show_string(char string[])

Так как символ NULL указывает конец строки, функция не требует параметр, который задает количество элементов в массиве. Вместо этого функция может определить последний элемент, просто найдя в массиве символ NULL.

Как вы уже знаете, функции C++ часто используют символ NULL для определения конца строки. Следующая программа STR_LEN.CPP создает функцию с именем string_length, которая ищет символ NULL в строке для определения количества символов, содержащихся в строке. Далее функция использует оператор return для возврата длины строки вызвавшей функции. Программа передает несколько различных символьных строк в функцию, отображая длину каждой из них на экране:

#include <iostream.h>

int string_length(char string[])

{
int i;
 for (i = 0; string[] != '\0'; i++); // Ничего не делать, но перейти к
// следующему символу return(i); Длина строки
}

void main(void)

{
 char title[] = "Учимся программировать на языке C++";
 char lesson[] = "Символьные строки";
 cout << "Строка " << title << " содержит " << string_length(title) << " символов" << endl;
 cout << "Строка " << lesson << " содержит " << string_length(lesson) << " символов" << endl;
}

Как видите, функция запускается с первого символа строки (элемент 0) и затем исследует каждый элемент до тех пор, пока не встретит NULL. Рассматривая программы на C++, вы встретите целый ряд функций, которые подобным образом просматривают символьные строки в поисках символа NULL.

ПРЕИМУЩЕСТВА ТОГО, ЧТО NULL ПРЕДСТАВЛЯЕТ СОБОЙ ASCII 0

Как вы уже знаете, символ NULL представляет собой символ ASCII 0. В уроке 7 вы изучали, что C++ использует значение 0, чтобы представлять ложь. Таким образом, поскольку символ NULL равен 0, ваши программы могут упростить многие операции цикла. Например, многие функции просматривают символьные строки символ за символом в поиске NULL. Следующий цикл for иллюстрирует, как программа может искать NULL в строке:

for (index = 0; string[index] != NULL; index++)

Поскольку символ NULL равен 0, многие программы упрощают циклы, которые ищут NULL, как показано ниже:

for (index = 0; string[index]; index++);

В данном случае пока символ, содержащийся в string[index] не NULL (0 или ложь), цикл продолжается.

ИСПОЛЬЗОВАНИЕ СТРОКОВЫХ ФУНКЦИЙ БИБЛИОТЕКИ ЭТАПА ВЫПОЛНЕНИЯ

Из урока 11 вы узнали, что большинство компиляторов C++ обеспечивает обширный набор функций, называемых библиотекой этапа выполнения. Рассматривая библиотеку этапа выполнения, вы обнаружите, что она содержат много разных функций, манипулирующих строками. Например, функция strupr преобразует символьную строку в строку верхнего регистра. Подобно этому, функция strlen возвращает количество символов в строке. Большинство библиотек этапа выполнения обеспечивают даже функции, которые позволяют вам просматривать строки в поисках определенного символа. Например, следующая программа STRUPR.CPP иллюстрирует использование функций strupr и strlwr библиотеки этапа выполнения:

#include <iostream.h>

#include <string.h>
    // Содержит прототипы
//
    функций strupr и strlwr

void main(void)
{
 char title[] = "Учимся программировать на языке C++";
 char lesson[] = "Символьные строки";
 cout << "Верхний регистр: " << strupr(title) << endl;
 cout << "Нижний регистр: " << strlwr(lesson) << endl;
}

Использование библиотечных функций, манипулирующих строками может сохранить вам время, требуемое для программирования. Выберите время напечатать копию заголовочного файла STRING.H для определения функций манипулирования строками, которые поддерживаются библиотекой вашего компилятора.

Вы должны играть по правилам

Как вы уже знаете, большинство функций, которые манипулируют строками, полагаются на символ NULL как на конец строки. Если ваши программы присваивают строки символам, то следует убедиться, что они добавляют символ NULL в качестве последнего символа строки. Если ваши программы не используют NULL соответствующим образом, то функции, которые полагаются на символ NULL, будут сбиваться.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Большинство программ на C++ широко использует символьные строки. Из этого урока вы узнали, как работать со строками. Из урока 18 вы узнаете, как сохранять связанную информацию различных типов в переменных, представляющих структуры C++. Используя структуру, вы можете хранить всю информацию о служащем, например его фамилию, возраст, оклад и номер телефона, в одной переменной. Однако, прежде чем приступить к уроку 18, убедитесь, что освоили следующие основные концепции:

  1. Символьная строка представляет собой массив символов, завершающийся 0 (символом NULL).
  2. Вы создаете символьную строку, объявляя массив типа char.
  3. Ваша программа ответственна за размещение символа NULL за последним символом строки.
  4. Если программа использует строковые константы, заключенные в
  5. двойные кавычки, компилятор C++ автоматически добавляет символ NULL.
  6. C++ позволяет вам инициализировать строки при объявлении, указывая требуемые символы внутри двойных кавычек.
  7. Большинство компиляторов C++ в своих библиотеках этапа выполнения обеспечивают широкий набор функций для манипулирования строками.


Untitled Document

Урок 18

Хранение связанной информации в структурах

Из урока 16 вы узнали, что C++ позволяет хранить в массиве связанную информацию одного и того же типа. Вы уже выяснили, что группировка связанных значений в массив очень удобна. В большинстве случаев программам необходимо группировать связанную информацию разного типа. Например предположим, что ваша программа работает с информацией о служащих. Она должна отслеживать данные о фамилии, возрасте, окладе, адресе, номере служащего и т. д. Для хранения этой информации программе потребуются переменные типа char, int, float, а также символьные строки.

Если вашей программе требуется хранить связанную информацию разных типов, она может использовать структуру. Вы узнаете, что структура представляет собой переменную, группирующую связанные части информации, называемые элементами, типы которых могут различаться. Группируя данные в одну переменную подобным образом, вы упрощаете ваши программы, снижая количество переменных, которыми необходимо управлять, передавать в функции и т. д. В данном уроке рассматривается создание и использование структур. К концу этого урока вы освоите следующие основные концепции:
  • Структуры позволяют вашим программам группировать в одной переменной связанные данные, типы которых могут различаться.
  • Структура состоит из одной или нескольких частей данных, называемых элементами.
  • Для определения структуры внутри программы следует указать имя структуры и ее элементы.
  • Каждый элемент структуры имеет тип, например char, int и float, и имя каждого элемента должно быть уникальным.
  • После того как ваша программа определит структуру, она может объявить переменные типа этой структуры.
  • Для изменения элементов структуры внутри функции ваши программы должны передать структуру в функцию с помощью адреса.

Ваше умение понимать структуры и работать с ними облегчит использование объектно-ориентированных классов C++ в части 4. Выберите время для эксперимента с программами, представленными в этом уроке.

ОБЪЯВЛЕНИЕ СТРУКТУРЫ

Структура определяет шаблон, с помощью которого ваша программа может позднее объявить одну или несколько переменных. Другими словами, ваша программа сначала определяет структуру, а затем объявляет переменные типа этой структуры. Для определения структуры ваши программы используют ключевое слово struct, за которым обычно следует имя и левая фигурная скобка. Следом за открывающей фигурной скобкой вы указываете тип и имя одного или нескольких элементов. За последним элементом вы размещаете правую закрывающую фигурную скобку. В этот момент вы можете (необязательно) объявить переменные данной структуры:

struct name

{
int member_name_l; |—————— Объявления элементов структуры
 float member_name_2; } variable; |——————————————— Объявление переменной
}

Например, следующее определение создает структуру, содержащую информацию о служащем:

struct employee

{
char name [64] ;
long employee_id;
float salary;
char phone[10];
int office_number;
};

В данном случае определение не объявляет какие-либо переменные типа этой структуры. После того как вы определите структуру, ваша программа может объявить переменные типа этой структуры, используя имя структуры (иногда называемое структурным тэгом), как показано ниже:

—————————————————————————— Тэг

employee boss, worker, new_employee; Объявление переменных

В данном случае оператор создает три переменные структуры employee. В некоторых случаях вы можете увидеть объявление, в котором тэг структуры предваряется ключевым словом struct, как показано ниже:

struct employee boss, worker, new_employee;

Ключевое слово struct является обязательным при программировании на С, так что некоторые программисты могут включать его по привычке. Однако в C++ использовать ключевое слово struct необязательно.

Использование элементов структуры

Структура позволяет вашим программам группировать информацию, называемую элементами, в одной переменной. Чтобы присвоить значение элементу или обратиться к значению элемента, используйте оператор C++ точку (.). Например, следующие операторы присваивают значения различным элементам переменной с именем worker тута employee;

worker.employee_id = 12345;

worker.salary = 25000.00;

worker.оffice_number = 102;

Для обращения к элементу структуры укажите имя переменной, за которым следует точка и имя элемента. Следующая программа EMPLOYEE.CPP иллюстрирует использование структуры типа employee:

#include <iostream.h>

#include <string.h>

void main(void)

{
struct employee

    {
char name [64];
long employee_id;
float
    salary;
char
    phone[10];
int office_number;
} worker;

// Копировать имя в строку strcpy(worker.name, "Джон Дой");

   worker.employee_id
    = 12345;
worker.salary = 25000.00;
worker.office_number = 102;

// Копировать номер телефона в строку strcpy(worker.phone, "555-1212") ;

cout << "Служащий: " << worker.name << endl;
cout << "Телефон: " << worker.phone << endl;
cout << "Номер служащего: " << worker.employee_id << endl;
cout << "Оклад: " << worker.salary << endl;
cout << "Офис: " << worker.office_number << endl;
}

Как видите, присваивание целому элементу и элементу с плавающей точкой очень просто. Программа использует оператор присваивания, чтобы присвоить значение соответствующему элементу. Однако обратите внимание на использование функции strcpy для копирования символьной строки в элементы name и phone. Если вы не инициализируете элементы при объявлении переменной типа данной структуры, вы должны копировать символьные строки в символьно-строковые элементы.

Объявление переменных структуры

Структуры C++ позволяют вашим программам группировать в одну переменную связанную информацию различных типов. Структура определяет шаблон для объявлений будущих переменных вашей программы. Каждая структура имеет уникальное имя (иногда называемое тэгом). Используя имя структуры, вы можете объявить переменные типа данной структуры. Биты информации, хранящиеся в структуре, называются элементами. Чтобы использовать или присвоить значение элементу, используйте оператор C++ точку, как показано ниже:

variable.member = some_value;
some_variable = variable.other_member;

СТРУКТУРЫ И ФУНКЦИИ

Если функция не изменяет структуру, вы можете передать структуру в функцию по имени. Например, следующая программа SHOW_EMP.CPP использует функцию show_employee для вывода элементов структуры типа employee:

#include <iostream.h>

#include <string.h>

struct employee

{
char name[64];
long employee_id;
float salary;
char phone[10];
int office_number;
};

void show_employee(employee worker)

{
cout << "Служащий: " << worker.name << endl;
cout << "Телефон: " << worker.phone << endl;
cout << "Номер служащего: " << worker. employee_id << endl;
cout << "Оклад: " << worker.salary << endl;
cout << "Офис: " << worker.office_number << endl;
}

void main(void)

{
employee worker;

// Копировать имя в строку strcpy(worker.name, "Джон Дой");

   worker.employee_id
    = 12345;
worker.salary = 25000.00;
worker.office_number = 102;

// Копировать номер телефона в строку strcpy(worker.phone, "555-1212");

show_employee(worker);
}

Как видите, программа передает переменную типа данной структуры worker в функцию show__employee по имени. Далее функция show_employee выводит элементы структуры. Однако обратите внимание, что программа теперь определяет структуру employee вне main и до функции show_employee. Поскольку функция объявляет переменную worker типа employee, определение структуры employee должно располагаться до функции.

Функции, изменяющие элементы структуры

Как вы знаете, если функция изменяет параметр, вам следует передавать этот параметр в функцию с помощью адреса. Если функция изменяет элемент структуры, вы должны передавать эту структуру в функцию с помощью адреса, Для передачи переменной типа структуры с помощью адреса вы просто предваряете имя переменной оператором адреса C++ (&), как показано ниже:

some_function(&worker);

Внутри функции, которая изменяет один или несколько элементов, вам следует работать с указателем. Если вы используете указатель на структуру, легче всего обращаться к элементам структуры, используя следующий синтаксис:

pointer_variable->member = some_value;

Например, следующая программа CHG_MBR.CPP передает структуру типа employee в функцию с именем get_employee_id, которая запрашивает у пользователя идентификационный номер служащего и затем присваивает этот номер элементу структуры employee_id. Чтобы изменить элемент, функция работает с указателем на структуру:

#include <iostream.h>

#include <string.h>

struct employee

{
char name[64];
long employee_id;
float salary;
char phone[10];
int office_number;
};

void get_employee_id(employee *worker)

{
cout << "Введите номер служащего: ";
cin >> worker->employee_id;
}

void main(void)

{
employee worker;

// Копировать имя в строку strcpy(worker.name, "Джон Дой");

get_employee_id(&worker) ;
cout << "Служащий: " << worker, name << endl;
cout << "Номер служащего: " << worker.employee_id << endl;
}

Как видите, внутри main программа передает переменную worker типа структуры в функцию get_employee_id с помощью адреса. Внутри функции gel_employee_id значение, введенное пользователем, присваивается элементу employee_id с помощью следующего оператора:

cin >> worker->employee_id;

Работа с указателями на структуры

Если функция изменяет элемент структуры, вызвавшая программа должна передать структуру в функцию с помощью адреса. Функция, в свою очередь, использует указатель на структуру. Для обращения к элементу структуры функции следует использовать следующий формат:

value = variable->member;
variable->other_member
      = some_value;

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Структуры позволяют вашим программам группировать связанные части информации различных типов в одной и той же переменной. Комбинируя подобным образом данные в одной переменной, ваши программы могут лучше представлять объекты, состоящие из двух или более частей, например, информацию о служащих, книгах и т. д. Из урока 19 вы узнаете, как использовать объединения C++, которые, подобно структурам, используют элементы, но в отличие от структур по-другому хранятся в памяти. Независимо от количества элементов объединение может хранить только одно значение в каждый момент времени. Прежде чем вы приступите к уроку 19, убедитесь, что освоили следующие основные концепции:

  1. Структуры позволяют вашим программам группировать связанную информацию различных типов в одной переменной.
  2. Части информации, из которых состоит структура, называются элементами.
  3. Структура определяет шаблон, который ваши программы могут использовать для объявления переменных.
  4. После определения структуры вы можете использовать ее имя (тэг) для объявления переменных типа данной структуры.
  5. Чтобы присвоить значение или обратиться к значению элемента структуры, ваши программы используют оператор точку, например variable.member.
  6. Если функция изменяет значение элемента структуры, переменная структуры должна быть передана в функцию с помощью адреса.
  7. Если функция использует указатель на структуру, функция должна использовать формат variable->member для обращения к элементу структуры.


Untitled Document

Урок 19

Объединения

Из урока 18 вы узнали, как группировать связанную информацию в одной переменной с помощью структур C++. По мере усложнения вашим программам могут потребоваться разные способы просмотра части информации. Кроме того, программе может потребоваться работать с двумя или несколькими значениями, используя при этом только одно значение в каждый момент времени. В таких случаях для хранения данных ваши программы могут использовать объединения. В данном уроке вы изучите, как создавать и использовать объединения для хранения информации. Как вы узнаете, объединения очень похожи на структуры, описанные в уроке 18. Прежде чем вы закончите этот урок, вы освоите следующие основные концепции:

  • Объединения C++ очень похожи на структуры, за исключением того, как C++ хранит их в памяти; кроме того, объединение может хранить значение только для одного элемента в каждый момент времени.
  • Объединение представляет собой структуру данных, подобную структуре C++, и состоит из частей, называемых элементами.
  • Объединение определяет шаблон, с помощью которого программы далее объявляют переменные.
  • Для обращения к определенному элементу объединения ваши программы используют оператор C++ точку.
  • Чтобы изменить значения элемента объединения внутри функции, ваша программа должна передать переменную объединения в функцию с помощью адреса.
  • Анонимное объединение представляет собой объединение, у которого нет имени (тэга).

Как вы узнаете, объединения очень похожи на структуры C++, однако способ, с помощью которого C++ хранит объединения, отличается от способа, с помощью которого C++ хранит структуры.

КАК C++ ХРАНИТ ОБЪЕДИНЕНИЯ

Внутри ваших программ объединения C++ очень похожи на структуры. Например, следующая структура определяет объединение с именем distance, содержащее два элемента:

union distance

{
 int miles;
 long meters;
};

Как и в случае со структурой, описание объединения не распределяет память. Вместо этого описание предоставляет шаблон для будущего объявления переменных. Чтобы объявить переменную объединения, вы можете использовать любой из следующих форматов:

union distance

{
union distance

   {
int
    miles; int miles;
long
    meters; long meters;
}
    japan, germany, franee;
};
distance japan,
    germany, franee;

Как видите, данное объединение содержит два элемента: miles и meters. Эти объявления создают переменные, которые позволяют вам хранить расстояния до указанных стран. Как и для структуры, ваша программа может присвоить значение любому элементу. Однако в отличие от структуры значение может быть присвоено только одному элементу в каждый момент времени. Когда вы объявляете объединение, компилятор C++ распределяет память для хранения самого большого элемента объединения. В случае объединения distance компилятор распределяет достаточно памяти для хранения значения типа long, как показано на рис. 19.

Рис. 19. C++ распределяет память, достаточную для хранения только самого большого элемента объединения.

Предположим, что ваша программа присваивает значение элементу miles, как показано ниже:

japan.miles = 12123;

Если далее ваша программа присваивает значение элементу meters, значение, присвоенное элементу miles, теряется.

Следующая программа USEUNION.CPP иллюстрирует использование объединения distance. Сначала программа присваивает значение элементу

miles и выводит это значение. Затем программа присваивает значение элементу meters. При этом значение элемента miles теряется:

#include <iostream.h>

void main(void)

{
union distance

{
int miles;
long meters;
} walk;

walk.miles = 5;
cout << "Пройденное расстояние в милях " << walk.miles << endl;
walk.meters = 10000;
cout << "Пройденное расстояние в метрах " << walk.meters << endl;
}

Как видите, программа обращается к элементам объединения с помощью точки, аналогичная запись использовалась при обращении к элементам структуры в уроке 18.

Объединение хранит значение только одного элемента в каждый момент времени

Объединение представляет собой структуру данных, которая, подобно структуре C++, позволяет вашим программам хранить связанные части информации внутри одной переменной. Однако в отличие от структуры объединение хранит значение только одного элемента в каждый момент времени. Другими словами, когда вы присваиваете значение элементу объединения, вы перезаписываете любое предыдущее присваивание.

Объединение определяет шаблон, с помощью которого ваши программы могут позднее объявлять переменные. Когда компилятор C++ встречает определение объединения, он распределяет количество памяти, достаточное для хранения только самого большого элемента объединения.

ПРЕДСТАВЛЕНИЕ ОБ АНОНИМНЫХ ОБЪЕДИНЕНИЯХ C++

Анонимное объединение представляет собой объединение, у которого нет имени. C++ предоставляет анонимные объединения, чтобы упростить использование элементов объединений, предназначенных для экономии памяти или создания псевдонимов для определенного значения. Например, предположим, что вашей программе требуются две переменные miles и meters. Кроме того, предположим, что программа использует только одну из них каждый данный момент времени. В этом случае программа могла бы использовать элементы объединения, подобного уже обсуждавшемуся объединению distance, а именно name.miles и name.meters. Следующий оператор создает анонимное (безымянное) объединение:

union

{
 int miles;
 long meters;
};

Как видите, объявление не использует имя объединения и не объявляет переменную объединения. Программа, в свою очередь, может обращаться к элементам с именами miles и meters без помощи точки. Следующая программа ANONYM.CPP создает анонимное объединение, которое содержит элементы miles и meters. Обратите внимание, что программа трактует элементы как обычные переменные. Однако различие между элементами и обычными переменными заключается в том, что, когда вы присваиваете значение любому из этих элементов, значение другого элемента теряется:

#include <iostream.h>

void main(void)

{
 union

{
 int miles;
 long meters;
 };

miles = 10000;
 cout << "Значение в милях " << miles << endl;
 meters = 150000;
 cout << "Значение в метрах " << meters << endl;
}

Как видите, с помощью анонимного объединения, программа может сэкономить память, не используя имя объединения и точку для обращения к значениям элементов.

Анонимные объединения позволяют вашим программам экономить пространство

Анонимное объединение представляет собой безымянное объединение. Анонимные объединения обеспечивают вашим программам способ экономии памяти, и при этом можно не использовать имя объединения и точку. Следующие олераторы определяют анонимное объединение, способное хранить две символьные строки:

union

{
char short_name[13];
char long_name[255];
};

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, как создать объединение внутри вашей программы. Вы уже поняли, что формат объединения подобен формату структуры. Однако способ, с помощью которого C++ хранит объединения, очень отличается от способа хранения структуры. Из урока 10 вы впервые узнали, что для того, чтобы функция изменила параметр, вашей программе следует передать этот параметр в функцию с помощью указателя (или адреса памяти). Начиная с десятого урока, ваши программы использовали указатели для массивов и символьных строк. В уроке 20 вы рассмотрите операции с указателями C++ с другой стороны. До изучения урока 20 убедитесь, что вы освоили следующее:

  1. Когда вы объявляете объединение, компилятор C++ распределяет память, достаточную для хранения только самого большого элемента объединения.
  2. Описание объединения не распределяет память, вместо этого оно обеспечивает шаблон, с помощью которого программы могут позднее объявлять переменные.
  3. Программы обращаются к элементам объединения, используя точку. Когда ваша программа присваивает значение элементу объединения, то значение, присвоенное, возможно, ранее другому элементу, теряется.
  4. Анонимное объединение представляет собой объединение, у которого нет имени. Когда программа объявляет анонимное объединение, она может использовать элементы такого объединения подобно любым другим переменным без точки.


Урок 20

Указатели

Как вы уже знаете, программы на C++ хранят переменные в памяти. Указатель представляет собой адрес памяти, который указывает (или ссылается) на определенный участок. Из урока 10 вы узнали, что для изменения параметра внутри функции ваша программа должна передать адрес параметра (указатель) в функцию. Далее функция в свою очередь использует переменную-указатель для обращения к участку памяти. Некоторые программы, созданные вами в нескольких предыдущих уроках, использовали указатели на параметры. Аналогично этому, когда ваши программы работают с символьными строками и массивами, они обычно используют указатели, чтобы оперировать элементами массива. Так как применение указателей является общепринятым, очень важно, чтобы вы хорошо понимали их использование. Таким образом, этот урок рассматривает еще один аспект применения указателей. К концу данного урока вы освоите следующие основные концепции:

  • Для простоты (для уменьшения кода) многие программы трактуют символьную строку как указатель и манипулируют содержимым строки, используя операции с указателями.
  • Когда вы увеличиваете переменную-указатель (переменную, которая хранит адрес), C++ автоматически увеличивает адрес на требуемую величину (на 1 байт для char, на 2 байта для int, на 4 байта для float и т.д.).
  • Ваши программы могут использовать указатели для работы с массивами целочисленных значений или значений с плавающей точкой.

Операции с указателями широко используются в C++. Выберите время для эксперимента с программами, представленными в этом уроке.

ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЯ НА СИМВОЛЬНУЮ СТРОКУ

Как вы уже знаете, указатель содержит адрес памяти. Когда ваша программа передает массив (например, символьную строку) в функцию, C++ передает адрес первого элемента массива. В результате совершенно обычно для функции использовать указатель на символьную строку. Чтобы объявить указатель на символьную строку, функция просто предваряет имя переменной звездочкой, как показано ниже:

void some_function(char *string);

Звездочка, которая предваряет имя переменной, указывает C++, что переменная будет хранить адрес памяти — указатель. Следующая программа PTR_STR.CPP использует указатель на символьную строку внутри функции show_string для вывода содержимого строки по одному символу за один раз:

#include <iostream.h>

void show_string(char *string)

{
while (*string != '\0')

{
cout << *string;
string++;
}
}

void main(void)

{
show_string( "Учимся программировать на языке C++!");
}

Обратите внимание на цикл while внутри функции show_slring. Условие while (*string != '\0') проверяет, не является ли текущий символ, указываемый с помощью указателя string, символом NULL, который определяет последний символ строки. Если символ не NULL, цикл выводит текущий символ с помощью cout. Затем оператор string++; увеличивает указатель siring таким образом, что он указывает на следующий символ строки. Когда указатель string указывает на символ NULL, функция уже вывела строку и цикл завершается.

Рис. 20. Сканирование строки с помощью указателя.

Предположим, например, что строка, переданная в функцию, находится в памяти компьютера по адресу 1000. Каждый раз, когда функция увеличивает указатель string, он указывает на следующий символ (адрес 1001,1002, 1003 и т. д.), как показано на рис. 20.

Второй пример

Вы только что узнали, что, используя указатель, ваша функция может сканировать строку символов, пока не будет обнаружен символ NULL. Следующая программа PTR_LEN.CPP использует указатель на строку в функции string_length для определения количества символов в строке:

#include <iostream.h>

int string_length(char *string)

{
int length = 0;
while (*string != '\0')

{
length++;
string++;
}
return(length);
}

void main(void)

{
char title[] = "Учимся программировать на языке C++";
cout << title << " содержит " << string_length(title) << " символов";
}

Как видите, функция string_length сканирует символы строки до тех пор, пока не встретит символ NULL.

Увеличение указателя на символьную строку

Когда программа передает массив в функцию, C++ передает адрес памяти первого элемента этого массива. Используя переменную-указатель, функция может перемещаться по содержимому массива, просто увеличивая значение указателя. Например, предположим, что программа передает в функцию символьную строку "Привет". Внутри функции переменная-указатель сначала указывает на участок памяти, который содержит букву 'П'. Когда функция увеличивает указатель, то он далее указывает на участок памяти, который содержит букву 'р'. По мере увеличения функцией значения указателя, он поочередно указывает на каждую букву в строке и наконец указывает на символ NULL.

Уменьшение количества операторов

Чтобы определить конец символьной строки, каждая из предыдущих программ использовала следующий цикл while:

while (*string != '\0')

Как уже обсуждалось, символ NULL ('\0') представляет собой значение ASCII 0. Так как C++ использует значение 0, чтобы представить ложь, ваши программы могут записать предыдущий цикл следующим образом:

while (*string)

В данном случае пока символ, определяемый указателем строки, не равен 0 (NULL), условие оценивается как истинное и цикл будет продолжаться. Из урока 5 вы узнали, что постфиксная операция увеличения C++ позволяет вам использовать значение переменной, а затем увеличивает это значение. Многие программы на C++ используют постфиксные операции увеличения и уменьшения, чтобы сканировать массивы с помощью указателей. Например, использование постфиксной операции увеличения делает следующие циклы while идентичными:

while (*string)

{
cout << *string++;
}

while (*string)
{
cout << *string;
 string++;
}

Оператор cout << *string++, заставляет C++ вывести символ, указываемый указателем string, а затем увеличить текущее значение string, чтобы он указывал на следующий символ. С помощью этих методов следующая программа SMARTPTR.CPP иллюстрирует новую реализацию функций show_string и string_length:

#include <iostream.h>

void show_string(char *string)

{
while (*string) cout << *string++;
}

int string_length(char •string)

(
int length = 0;
while (*string++) length++;
return(length) ;
}

void main(void)

{
char title[] = "Учимся программировать на языке C++";
show_string(title) ;
сout <<
    " содержит " <<
    string_length(title) << " символов";
}

Если вы встретите функции C++, которые манипулируют строками с помощью указателей, то они с большой долей вероятности будут использовать подобную краткую запись.

Сканирование символьной строки

Одно из наиболее широко употребляемых использовании указателей в программах на C++ заключается в сканировании символьных строк. Для уменьшения количества кода многие программы используют следующие операторы для сканирования строки:

while (*string)

{
// операторы
string++;
// продвинуть к следующему символу
}

Следующая функция string_uppercase использует указатели для преобразования символов строки в символы верхнего регистра:

char *string_uppercase(char* string)

{
char *starting_address = string; // адрес string[0];
while (*string)

   {
if ((*string >= 'а') && (*string
      <= 'я')) *string = *string - 'a' + 'A';
string++;
}
return(starting_address);
}

Эта функция сохраняет и возвращает начальный адрес строки, который позволяет вашим программам использовать функцию следующим образом:

cout << Btring_uppercase("Привет, мир!") << endl;

* Поскольку при переводе книги обрабатываемые символы заменены с английских на русские, то этот алгоритм работает не для всех кодировок кирилицы в DOS и Windows. — Прим. перев.

ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ С ДРУГИМИ ТИПАМИ МАССИВОВ

Несмотря на то что указатели широко используются с символьными строками, вы можете использовать указатели с массивами других типов. Например, следующая программа PTRFLOAT.CPP использует указатель на массив типа float для вывода значений с плавающей точкой:

#include <iostream.h>

void show_float(float *array, int number_of_elements)

{
 int i;
 for (i = 0; i < number_of_elements; i++) cout << *array++ << endl;
}

void main(void)

{
 float values[5] = {1.1, 2.2, 3.3, 4.4, 5.5);
 show_float(values, 5);
}

Как видите, внутри функции show_float цикл for использует значение, указываемое с помощью указателя array, а затем увеличивает этот указатель до следующего значения. В данном случае программа должна передать параметр, который задает количество элементов массива, поскольку в отличие от символьных строк массивы типа float (или int, long и т. д.) не используют символ NULL для определения последнего элемента.

О МАТЕМАТИКЕ УКАЗАТЕЛЯ

Как вы уже знаете, ваши программы могут использовать указатели на массивы любых типов. В предыдущей программе функция show_float увеличивала указатель для продвижения по массиву типа float. Указатель указывает на участок памяти, содержащий значение определенного типа, например char, int или float. Когда функция сканирует массив с помощью указателя, функция увеличивает указатель для продвижения от одного значения к следующему. Чтобы указатель указывал на следующий элемент массива, C++ должен знать размер каждого элемента (в байтах), чтобы определить, на сколько необходимо увеличить значение указателя. Например, для продвижения указа-

теля к следующему символу в массиве, C++ должен увеличить значение указателя на 1. Однако, чтобы указать следующее значение в массиве типа int C++ должен увеличить указатель на два байта (значение типа int занимает два байта памяти). Для значений типа. float C++ увеличивает указатель на 4 байта. Зная тип значения, на которое указывает указатель, C++ знает, на сколько необходимо увеличить значение этого указателя. В ваших программах вы просто используете оператор увеличения, например pointer++. Однако за кулисами C++ увеличивает реальное значение (адрес памяти), содержащееся в указателе, на корректную величину.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Программы на C++ широко используют указатели, особенно для манипулирования строками. В данном уроке рассмотрен еще один широко используемый аспект при работе с указателями. В уроке 21 вы начнете использовать объектно-ориентированные возможности C++! Для начала вы создадите классы, подобные структурам. Ваши программы будут использовать класс для определения объекта, например file. Внутри класса вы укажете функции для манипулирования этим объектом, например print Jile или delete_file. До перехода к уроку 21 убедитесь, что вы изучили следующее:

    1. Указатели содержат адрес памяти. Когда вы передаете в функцию массив, C++ передает адрес первого элемента массива.
    2. Увеличивая значение указателя, вы можете адресовать с его помощью следующий элемент массива.
    3. Функции, которые манипулируют строками с помощью указателей, обычно сканируют строку до того момента, пока не найден символ NULL.
    4. При использовании указателей с массивами других типов ваши функции должны знать количество элементов массива или специальный маркер конца массива.
    5. При использовании указателей с массивами других типов C++ автоматически (за кулисами) увеличивает указатель (адрес памяти) на требуемую величину таким образом, чтобы данный указатель указывал на следующий элемент массива.


Untitled Document

Урок 21

Знакомство с классами C++

Класс представляет собой главное инструментальное средство C++ для объектно-ориентированного программирования. Как вы узнаете из данного урока, класс очень похож на структуру, в которой сгруппированы элементы, соответствующие данным о некотором объекте, и оперирующие этими данными функции (называемые методами). Вы узнаете, что объект представляет собой некоторую сущность, например телефон. Класс C++ позволяет вашим программам определять все атрибуты объекта. В случае, когда объектом является телефон, класс может содержать такие элементы данных, как номер и тип телефона (кнопочный или дисковый) и функции, работающие с телефоном, например dial, answer, и hang_up. Группируя данные об объекте и кодируя их в одной переменной, вы упрощаете процесс программирования и увеличиваете возможность повторного использования своего кода. Этот урок знакомит вас с классами C++ . К концу урока вы освоите следующие основные концепции:

  • Для определения класса программа должна указать имя класса, элементы данных класса и функции класса (методы).
  • Определение класса обеспечивает шаблон, с помощью которого ваши программы могут создать объекты типа этого класса, подобно тому, как программы создают переменные типа int, char и т. д.
  • Программа присваивает значения элементам данных класса, используя оператор точку.
  • Программа вызывает функцию-элемент класса, используя оператор точку.

ПРЕДСТАВЛЕНИЕ ОБ ОБЪЕКТАХ И ОБЪЕКТНО-ОРИЕНТИРОВАННОМ ПРОГРАММИРОВАНИИ

В известном смысле объект представляет собой сущность. Программа обычно использует переменные для хранения информации о различных реально существующих сущностях, например служащих, книгах и даже файлах. При объектно-ориентированном программировании вы фокусируетесь на предметах, образующих систему, и операциях, которые вы должны выполнять над этими предметами. Например, для объекта-файла вы могли бы иметь операции, которые печатают, отображают или изменяют файл. В C++ вы используете класс для определения своих объектов. Ваша цель состоит в том чтобы включить в класс столько информации об объекте, сколько требуется. Исходя из этого, можно подобрать класс, созданный вами для одной программы, и использовать его в нескольких разных программах.

Класс позволяет вашим программам группировать данные и функции которые выполняют операции над этими данными. Большинство книг и статей об объектно-ориентированном программировании называют функции класса методами. Подобно структуре, класс C++ должен иметь уникальное имя, за которым следует открывающая фигурная скобка, один или несколько элементов и закрывающая фигурная скобка:

class class_name

{
 int data_member; // Элемент данных
 void show_member(int); // Функция-элемент
};

После определения класса вы можете объявлять переменные типа этого класса (называемые объектами), как показано ниже:

class_name object_one, object_two, object_three;

Следующее определение создает класс employee, который содержит определения данных и метода:

class employee

{
public:
 char name[64] ;
 long employee_id;
 float salary;
 void show_employee(void)

{
 cout << "Имя: " << name << endl;
 cout << "Номер служащего: " << employee_id << endl;
 cout << "Оклад: " << salary << endl;
 };
};

В данном случае класс содержит три переменные и одну функцию-элемент. Обратите внимание на использование метки public внутри определения класса. Как вы узнаете из урока 22, элементы класса могут быть частными (private) или общими {public), от чего зависит, как ваши программы обращаются к элементам класса. В данном случае все элементы являются общими, это означает, что программа может обращаться к любому элементу, используя оператор точку. После определения класса внутри вашей программы вы можете объявить объекты (переменные) типа этого класса, как показано ниже:

————————————————————— Имя класса

employee worker, boss, secretary; //--------->Переменные класса (объекты)

Следующая программа EMPCLASS.CPP создает два объекта employee. Используя оператор точку, программа присваивает значения элементам данных Затем программа использует элемент show_employee для вывода информации о служащем:

#include <iostream.h>

#include <string.h>

class employee

{
public:
char name [64];
 long employee_id;
 float salary;
 void show_employee(void)

{
 cout << "Имя: " << name << endl;
 cout << "Номер служащего: " << employee_id << endl;
 cout << "Оклад: " << salary << endl;
 };
};

void main(void)

{
employee
    worker, boss;
strcpy(worker.name,
    "John Doe");
worker.employee_id
    = 12345;
worker.salary
    = 25000;
strcpy(boss.name,
    "Happy Jamsa");
boss.employee_id
    = 101;
boss.salary
    = 101101.00;
worker.show_employee();
boss.show_employee();
}

Как видите, программа объявляет два объекта типа employee — worker и boss, а. затем использует оператор точку для присваивания значений элементам и вызова функции show_employee.

Представление об объектах

Большинство программ на C++ отражают реальные существующие объекты. В известном смысле объекты представляют собой сущности, например автомобиль, собаку, часы и т. д. Обычно объект имеет несколько атрибутов и операций, которые программа должна выполнять над этими атрибутами. Например, в случае часов свойства могут включать текущее время и время будильника. Операции такого объекта могли бы содержать установку времени, установку будильника или выключение будильника. При объектно-ориентированном программировании ваши программы фокусируются на объектах и операциях над этими объектами.

ОПРЕДЕЛЕНИЕ МЕТОДОВ КЛАССА ВНЕ КЛАССА

В предыдущем классе employee функция была определена внутри самого класса (встроенная (inline) функция). При увеличении функций определение встроенных функций внутри класса может внести беспорядок в описание класса. В качестве альтернативы вы можете поместить прототип функции внутри класса, а затем определить функцию вне класса. Ваше определение класса с прототипом становится следующим:

class employee

{
public:
char name[64];
long employee_id;
float salary;
void show_employee(void); |————————> Прототип функции
 };

Так как разные классы могут использовать функции с одинаковыми именами, вы должны предварять имена определяемых вне класса функций именем класса и оператором глобального разрешения (::). В данном случае определение функции становится следующим:

void employee:: show_employee (void) //-------------->Имя класса

{
сout << "Имя: " << name << endl; Имя элемента cout << "Номер служащего: " << employee_id << endl;
cout << "Оклад: " << salary << endl;
};

Как видите, приведенный код предваряется определением функции с именем класса (employee) и оператором глобального разрешения (::). Следующая программа CLASSFUN.CPP помещает определение функции show_employee вне класса, используя оператор глобального разрешения для указания имени класса:

#include <iostream.h>

#include <string.h>

class employee

{
public:
char name [64];
long employee_id;
float salary;
void show_employee(void);
};

void employee::show_employee(void)
{
cout << "Имя: " << name << endl;
cout << "Номер служащего: " << employee_id << endl;
cout << "Оклад: " << salary << endl;
};

void main(void)

{
employee worker, boss;
strcpy(worker.name, "John Doe");
worker.employee_id = 12345;
worker.salary = 25000;
strcpy(boss.name, "Happy Jamsa");
boss.employee_id = 101;
boss.salary = 101101.00;
worker.show_employee();
boss.show_employee();
}

Методы класса

Классы C++ позволяют вашим программам группировать данные объекта и функции объекта (методы), которые оперируют с этими данными, в одной переменной. У вас есть две возможности определения методов объекта. Первая состоит в том, что вы можете включить весь код функции внутрь определения класса. Несмотря на то что включение кода метода в определение класса может представляться удобным, однако, когда классы становятся сложнее и включают несколько методов, операторы функций могут вносить беспорядок в определение классов. Таким образом, многие программы определяют операторы функции вне класса. В определение класса программа должна включать прототип функции, который указывает имя функции, тип возвращаемого значения и типы параметров.

Для определения функции вне определения класса ваша программа должна предварять определение функции именем класса и оператором глобального разрешения, как показано ниже:

return_type class_name::function_name(parameters)
{// Операторы }

ВТОРОЙ ПРИМЕР

Следующая программа PEDIGREE.CPP создает класс dog, который содержит несколько полей данных и функцию show_breed. Программа определяет функцию класса вне определения самого класса. Затем программа создает два объекта типа dog и выводит информацию о каждой собаке:

#include <iostream.h>

#include <string.h>

class dogs

{
public:
 char breed[64];
 int average_weight;
 int average_height;
 void show_dog(void) ;
};

void dogs::show_breed(void)

{
 cout << "Порода: " << breed << endl;
 cout << "Средний вес: " << average_weight << endl;
 cout << "Средняя высота: " << average_height << endl;
}

void main(void)

{
dogs
    happy, matt;
strcpy(happy.breed,
    "Долматин") ;
happy.average_weight
    = 58;
happy.average_height
    = 24;
strcpy(matt.breed,
    "Колли");
matt.average_weight
    =22;
matt.average_height
    = 15;
happy.show_breed()
    ;
matt.show_breed();
}

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Программы на C++ широко используют классы. Говоря кратко, классы позволяют вашим программам группировать для определенного объекта его данные и методы (функции), которые оперируют этими данными, в одной переменной. Как видите, классы очень похожи на структуры, которые вы рассматривали в уроке 18. Классы C++ являются основой объектно-ориентированного программирования. Следующие уроки расширяют возможности предоставляемые классами. Упоминавшаяся в этом уроке метка public, появляющаяся в определении класса, делает элементы класса доступными для всей программы. Из урока 22 вы больше узнаете о частных и общих элементах класса. До изучения урока 22 убедитесь, что освоили следующие основные концепции:

    1. В известном смысле объект представляет собой сущность, с которой ваши программы выполняют разные операции.
    2. Программы на C++ представляют объекты посредством классов.
    3. Класс, подобно структуре, содержит элементы. Элементы класса могут хранить информацию (данные) или быть функциями (методами), которые оперируют этими данными.
    4. Каждый класс имеет уникальное имя.
    5. После определения класса вы можете объявлять объекты этого класса, используя имя класса в качестве типа.
    6. Для обращения к элементам класса (как к данным, так и к функциям) ваши программы используют оператор точку.
    7. Программы могут определять функцию класса внутри или вне определения класса. Если вы определяете функцию вне определения класса, вам следует указать имя класса и использовать оператор глобального разрешения, например class:: function.


Untitled Document

Урок 22

Частные и общие данные

В уроке 21 вы создали свои первые классы в C++. При этом вы включали метку public в определение класса, чтобы обеспечить программе доступ к каждому элементу класса. Из данного урока вы узнаете, как атрибуты public и private управляют доступом к элементам класса со стороны программы. Вы узнаете, что ваши программы могут обратиться к общим (public) элементам из любой функции. С другой стороны, ваша программа может обращаться к частным (private) элементам только в функциях данного класса. Этот урок подробно исследует частные и общие элементы. К концу данного урока вы освоите следующие основные концепции:

  • Чтобы управлять тем, как ваши программы обращаются к элементам класса, C++ позволяет вам определять элементы как частные или общие.
  • Частные элементы дают возможность классу скрыть информацию, которую программе не требуется знать.
  • Класс, использующий частные элементы, обеспечивает интерфейсные функции, которые обращаются к частным элементам класса.

Как уже вкратце обсуждалось в уроке 21, вы должны поместить в определение класса столько информации об объекте, сколько считаете необходимым. При этом объекты становятся самообеспеченными, что может повысить возможность их повторного использования несколькими программами.

СОКРЫТИЕ ИНФОРМАЦИИ

Как вы уже знаете, класс содержит данные и методы (функции). Для использования класса программы просто должны знать информацию, которую хранит класс (его элементы данных) и методы, которые манипулируют данными (функции). Вашим программам не требуется знать, как работают методы. Более того, программы должны знать только, какую задачу выполняют методы. Например, предположим, что у вас есть класс file. В идеале ваши программы должны знать только то, что этот класс обеспечивает методы file.print, который печатает отформатированную копию текущего файла, или file.delete, который удаляет файл. Вашей программе не требуется знать, как эти два метода работают. Другими словами, программа должна рассматривать класс как "черный ящик". Программа знает, какие методы необходимо вызвать и какие параметры им передать, но программа ничего не знает о рельной работе, выполняющейся внутри класса (в "черном ящике").

Сокрытие информации представляет собой процесс, в результате которого программе предоставляется только минимальная информация, необходимая для использования класса. Частные и общие элементы класса помогают вам получить информацию, скрытую внутри вашей программы. В уроке 21 каждый из созданных вами классов использовал метку public для объявления всех элементов класса общими, т.е. видимыми для всей программы. Таким образом, программа могла бы непосредственно обратиться к любому элементу класса, используя оператор точку:

class employee

{
public:
 char name [64];
 long employee_id;
 float salary;
 void show_employee(void);
}

При создании класса вы могли бы иметь элементы, чьи значения используются только внутри класса, но обращаться к которым самой программе нет необходимости. Такие элементы являются частными (private), и их следует скрывать от программы. Если вы не используете метку public, то по умолчанию C++ подразумевает, что все элементы класса являются частными. Ваши программы не могут обращаться к частным элементам класса, используя оператор точку. К частным элементам класса могут обращаться только элементы самого класса. При создании класса вам следует разделить элементы на частные и общие, как показано ниже:

class some_class

{
public:
int some_variable;
void initialize_private(int, float); //———> Общие элементы
 void show_data(void);
private:
int key_value; //—————————————> Частные элементы
 float key_number;
}

Как видите, метки public и private легко позволяют определять, какие элементы являются частными, а какие общими. В данном случае программа может использовать оператор точку для обращения к общим элементам, как показано ниже:

some_class object; // Создать объект
object.some_variable = 1001;
object.initialize_private(2002, 1.2345);
object.show_data()

Если ваша программа пытается обратиться к частным элементам key_value или key_number, используя точку, компилятор сообщает о синтаксических ошибках.

Как правило, вы будете защищать элементы класса от прямого доступа к ним делая их частными. При этом программы не могут непосредственно присваивать значения таким элементам, используя оператор точку. Вместо того чтобы присвоить значение, программа должна вызвать метод класса. Предотвращая прямой доступ к элементам данных, вы, таким образом, можете гарантировать, что им всегда будут присваиваться допустимые значения. Например, предположим что объект nuclear_reactor вашей программы использует переменную с именем melt_down, которая всегда должна содержать значение в диапазоне от 1 до 5. Если элемент melt_down является общим, программа может непосредственно обратиться к элементу, изменяя его значение произвольным образом:

nuclear_reactor.melt_down = 101

Если вместо этого вы делаете переменную частной, то можете использовать метод класса, например assign_meltdown, чтобы присвоить значение этой переменной. Как показано ниже, функция assign_meltdown может проверять присваиваемое значение, чтобы убедиться, что оно является допустимым:

int nuke::assign_meltdown(int value)

{
 if ((value > 0) && (value <= 5))

   {
melt_down
    = value;
return(0);
    // Успешное присваивание
} else return(-1);
    // Недопустимое значение
}

Методы класса, которые управляют доступом к элементам данных, представляют собой интерфейсные функции. При создании классов вы будете использовать интерфейсные функции для защиты данных своих классов.

Общие и частные элементы

Классы C++ содержат данные и элементы. Чтобы указать, к каким элементам классов ваши программы могут обращаться напрямую, используя оператор точку, C++ позволяет вам определять элементы класса как общие {public) и частные (private). Таким образом, ваши программы могут непосредственно обращаться к любым общим элементам класса, используя оператор точку. С другой стороны, к частным элементам можно обратиться только через методы класса. Как правило, вы должны защищать большинство элементов данных класса, объявляя их частными. Следовательно, единственным способом, с помошью которого ваши программы могут присвоить значение элементам данных, является использование функций класса, которые способны проверить и скорректировать присваиваемые значения.

ИСПОЛЬЗОВАНИЕ ОБЩИХ И ЧАСТНЫХ ЭЛЕМЕНТОВ КЛАССА

Следующая программа INFOHIDE.CPP иллюстрирует использование общих и частных элементов класса. Программа определяет объект типа employee как показано ниже:

class employee

{
public:
 int assign_values(char *, long, float);
 void show_employee(void);
 int change_salary(float);
 long get_id(void);
private:
 char name [64] ;
 long employee_id;
 float salary;
}

Как видите, класс защищает все свои элементы данных, объявляя их частными. Для доступа к элементам данных программа должна использовать интерфейсные функции. Ниже приведена реализация программы INFOHIDE.CPP:

#include <iostream.h>

#include <string.h>

class employee

{
public:
 int assign_values(char *, long, float);
 void show_employee(void);
 int change_salary(float);
 long get_id(void);
private:
 char name [64];
 long employee_id;
 float salary;
);

int employee::assign_values(char *emp_name, long emp_id, float emp_salary)

{
 strcpy(name, emp_name);
 employee_id = emp_id;
 if (emp_salary < 50000.0)

   {
salary
    = emp_salary;
return(0);
    // Успешно
}
else
return(-1); //
    Недопустимый оклад }

void employee::show_employee(void)

{
 cout << "Служащий: " << name << endl;
 cout << "Номер служащего: " << employee_id << endl;
 cout << "Оклад: " << salary << endl;
}

int employee::change_salary(float new_salary)

{
 if (new_salary < 50000.0)

   {
salary
    = new_salary;
return(0);
    // Успешно } else return(-1);
    // Недопустимый оклад }

long employee::get_id(void)

{
return(employee_id)
    ;
}

void main(void)

{
 employee worker;

if (worker.assign_values("Happy Jamsa", 101, 10101.0) == 0)

{
 cout << "Служащему назначены следующие значения" << endl;}
 worker.show_employee();
 if (worker.change_salary(35000.00) == 0)

{
 cout << "Назначен новый оклад" << endl;
 worker.show_employee();
 }
}
else
 cout << "Указан недопустимый оклад" << endl;
}

Выберите время, чтобы исследовать операторы программы более подробно. Несмотря на то что программа достаточно длинна, ее функции на самом деле очень просты. Метод assign_values инициализирует частные данные класса. Метод использует оператор if, чтобы убедиться, что присваивается допустимый оклад. Метод show_employee в данном случае выводит частные элементы данных. Методы change_salary и get_id представляют собой интерфейсные функции, обеспечивающие программе доступ к частным данным. После успешной компиляции и запуска этой программы отредактируйте ее и попытайтесь обратиться напрямую к частным элементам данных, используя оператор точку внутри main. Так как вы не можете напрямую обратиться к частным элементам, компилятор сообщит о синтаксических ошибках.

Что такое интерфейсные функции

Для снижения количества возможных ошибок ограничивайте доступ программ к данным класса, определяя элементы данных класса как частные. Таким образом, программа не сможет обратиться к элементам данных класса, используя оператор точку. Вместо этого класс должен определять интерфейсные функции, с помощью которых программа может присваивать значения частным элементам. Интерфейсные функции в свою очередь, могут исследовать и скорректировать значения, которые программа пытается присвоить.

ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА ГЛОБАЛЬНОГО РАЗРЕШЕНИЯ ДЛЯ ЭЛЕМЕНТОВ КЛАССА

Если вы рассмотрите функции в программе INFOHIDE.CPP, вы обнаружите, что имена параметров функции часто предваряются символами етр_, как показано ниже:

int employee::assign_values(char *emp_name, long emp_id, float emp_salary)

Символы етр_ использовались, чтобы избежать конфликта между именами параметров и именами элементов класса. Если подобный конфликт имен всe же происходит, вы можете разрешить его, предваряя имена элементов класса именем класса и оператором глобального разрешения (::). Следующая функция использует оператор глобального разрешения и имя класса перед именем элементов класса. Исходя из этого, любой читающий эти операторы поймет, какие имена соответствуют классу employee:

int employee::assign_values(char *name, long employee_id, float salary)

{
strcpy(employee::name,
    name) ;
employee::employee_id
    = employee_id;
if
    (salary < 50000.0)

{
employee::salary
    = salary;

   return(0);
    // Успешно } else
return(-1);
    // Недопустимый оклад
}

При создании функций, работающих с элементами класса, вам следует использовать имя класса и оператор глобального разрешения, чтобы таким образом избежать конфликта имен.

Использование оператора глобального разрешения для указания элементов класса

При создании функций-элементов класса возможны ситуации, когда имя локальной переменной, которое вы используете внутри функции, конфликтует с именем элемента класса. По умолчанию имя локальной переменной будет переопределять имя элемента класса. Когда происходит подобный конфликт имен, функция может использовать имя класса и оператор глобального разрешения для доступа к элементам класса, как показано ниже:

class_naine: :member_name = some_value;

ЧАСТНЫЕ ЭЛЕМЕНТЫ КЛАССА НЕ ВСЕГДА ЯВЛЯЮТСЯ ДАННЫМИ

В примере, представленном в этом уроке, частные элементы были всегда элементами данных. По мере того как определение класса становится более сложным, вы, возможно, захотите создать функции, используемые другими методами класса, но для оставшейся части программы доступ к таким функциям должен быть закрыт. В подобных случаях вы просто объявляете такие методы частными элементами. Если функция класса не объявлена как общая, программа не может вызвать такую функцию, используя оператор точку.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Управляя доступом программы к элементам класса, вы снижаете возможность ошибок, которые происходят в результате злоупотребления этими элементами. Чтобы управлять доступом к элементам класса, можно использовать частные элементы. Большинство определений классов C++, которые вы встретите, будут использовать сочетание частных и общих элементов. Одна из наиболее широко используемых операций, которую ваши программы выполняют при создании объекта, представляет собой инициализацию элементов данных объекта. Из урока 23 вы узнаете, что C++ позволяет вам определять специальную функцию, называемую конструктором, которая автоматически вызывается каждый раз при создании объекта. Используя конструктор, ваша программа легко может инициализировать элементы класса. До изучения урока 23 убедитесь, что освоили следующие основные концепции:

    1. Элементы класса могут быть общими или частными. Программы могут напрямую обращаться к общим элементам, используя оператор точку. С другой стороны, к частным элементам можно обращаться, используя только методы класса.
    2. Если это не оговорено явно, C++ считает все элементы частными.
    3. Программы присваивают значения и обращаются к частным элементам, используя интерфейсные функции.
    4. При создании программы, манипулирующей элементами класса, вы можете предварять имя каждого элемента именем класса и оператором глобального разрешения (::), например employее::name, для избежания возможных конфликтов имен.


Untitled Document

Урок 23

Конструктор и деструктор

При создании объектов одной из наиболее широко используемых операций которую вы будете выполнять в ваших программах, является инициализация элементов данных объекта. Как вы узнали из урока 22, единственным способом, с помощью которого вы можете обратиться к частным элементам данных, является использование функций класса. Чтобы упростить процесс инициализации элементов данных класса, C++ использует специальную функцию, называемую конструктором, которая запускается для каждого создаваемого вами объекта. Подобным образом C++ обеспечивает функцию, называемую деструктором, которая запускается при уничтожении объекта. В данном уроке конструктор и деструктор рассматриваются более подробно. К концу этого урока вы освоите следующие основные концепции:

  • Конструктор представляет собой метод класса, который облегчает вашим программам инициализацию элементов данных класса.
  • Конструктор имеет такое же имя, как и класс.
  • Конструктор не имеет возвращаемого значения.
  • Каждый раз, когда ваша программа создает переменную класса, C++ вызывает конструктор класса, если конструктор существует.
  • Многие объекты могут распределять память для хранения информации; когда вы уничтожаете такой объект, C++ будет вызывать специальный деструктор, который может освобождать эту память, очищая ее после объекта.
  • Деструктор имеет такое же имя, как и класс, за исключением того, что вы должны предварять его имя символом тильды (~).
  • Деструктор не имеет возвращаемого значения.

Термины конструктор и деструктор не должны вас пугать. Вместо этого представьте конструктор как функцию, которая помогает вам строить (конструировать) объект. Подобно этому, деструктор представляет собой функцию, которая помогает вам уничтожать объект. Деструктор обычно используется, если при уничтожении объекта нужно освободить память, которую занимал объект.

СОЗДАНИЕ ПРОСТОГО КОНСТРУКТОРА

Конструктор представляет собой метод класса, который имеет такое же имя, как и класс. Например, если вы используете класс с именем employee, конструктор также будет иметь имя employee. Подобно этому, для класса с именем dogs конструктор будет иметь имя dogs. Если ваша программа определяет конструктор, C++ будет автоматически вызывать его каждый раз, когда вы создаете объект. Следующая программа CONSTRUC.CPP создает класс с именем employee. Программа также определяет конструктор с именем employee который присваивает начальные значения объекту. Однако конструктор не возвращает никакого значения, несмотря на то, что он не объявляется как void. Вместо этого вы просто не указываете тип возвращаемого значения:

class employee

{
public:
 employee(char *, long, float); //Конструктор
 void show_employee(void);
 int change_salary(float);
 long get_id(void);
private:
 char name [64];
 long employee_id;
 float salary;
};

В вашей программе вы просто определяете конструктор так же, как любой другой метод класса:

employee::employee(char *name, long employee_id, float salary)

{
strcpy(employee::name,
    name) ;
employee::employee_id
    = employee_id;
if
    (salary < 50000.0)
employee::salary
    = salary;
else
    // Недопустимый
    оклад
employee::salary = 0.0;
}

Как видите, конструктор не возвращает значение вызвавшей функции. Для него также не используется тип void. В данном случае конструктор использует оператор глобального разрешения и имя класса перед именем каждого элемента, как уже обсуждалось в уроке 23. Ниже приведена реализация программы CONSTRUC.CPP:

#include <iostream.h>

#include <string.h>

class employee

{
public:
 employee(char *, long, float);
 void show_employee(void);
 int change_salary(float) ;
 long get_id(void);
private:
 char name [64] ;
 long employee_id;
 float salary;
};

employee::employee(char *name, long employee_id, float salary)

{
strcpy(employee::name,
    name) ;
employee::employee_id
    = employee_id;
if
    (salary < 50000.0)
employee::salary
    = salary;
else
    // Недопустимый
    оклад
employee::salary = 0.0;
}

void employee::show_employee(void)

{
 cout << "Служащий: " << name << endl;
 cout << "Номер служащего: " << employee_id << endl;
 cout << "Оклад: " << salary << endl;
}

void main(void)

{
employee
    worker("Happy Jamsa", 101, 10101.0);
worker.show_employee();
}

Обратите внимание, что за объявлением объекта worker следуют круглые скобки и начальные значения, как и при вызове функции. Когда вы используете конструктор, передавайте ему параметры при объявлении объекта:

employee worker("Happy Jamsa", 101, 10101.0);

Если вашей программе потребуется создать несколько объектов employee, вы можете инициализировать элементы каждого из них с помощью конструктора, как показано ниже:

employee worker("Happy Jamsa", 101, 10101.0);

employee secretary("John Doe", 57, 20000.0);

employee manager("Jane Doe", 1022, 30000.0);

Представление о конструкторе

Конструктор представляет собой специальную функцию, которую C++ автоматически вызывает каждый раз при создании объекта. Обычное назначение конструктора заключается в инициализации элементов данных объекта. Конструктор имеет такое же имя, как и класс. Например, класс с именем file использует конструктор с именем file. Вы определяете конструктор внутри своей программы так же, как и любой метод класса. Единственное различие заключается в том, что конструктор не имеет возвращаемого значения. Когда вы позже объявляете объект, вы можете передавать параметры конструктору, как показано ниже:

class_name object(valuel, value2, value3)

Конструкторы и параметры по умолчанию

Как вы уже знаете из урока 15, C++ позволяет указывать значения по умолчанию для параметров функции. Если пользователь не указывает каких-либо параметров, функция будет использовать значения по умолчанию. Конструктор не является исключением; ваша программа может указать для него значения по умолчанию так же, как и для любой другой функции. Например, следующий конструктор employee использует по умолчанию значение оклада равным 10000.0, если программа не указывает оклад при создании объекта. Однако программа должна указать имя служащего и его номер:

employee::employee(char *name, long employee_id, float salary = 10000.00)

{
strcpy(employee::name,
    name);
employee::employee_id
    = employee_id;
if
    (salary < 50000.0)
employee::salary
    = salary;
else
    // Недопустимый
    оклад
employee::salary = 0.0;
}

Перегрузка конструкторов

Как вы уже знаете из урока 13, C++ позволяет вашим программам перегружать определения функций, указывая альтернативные функции для других типов параметров. C++ позволяет вам также перегружать конструкторы. Следующая программа CONSOVER.CPP перегружает конструктор employee. Первый конструктор требует, чтобы программа указывала имя служащего, номер служащего и оклад. Второй конструктор запрашивает пользователя ввести требуемый оклад, если программа не указывает его:

employee::employee(char *name, long employee_id)

{
strcpy(employee::name,
    name);
employee::employee_id
    = employee_id;
do

{
 cout << "Введите оклад для " << name << " меньше $50000: ";
 cin >> employee::salary;
 }
 while (salary >= 50000.0);
}

Внутри определения класса программа должна указать прототипы для обоих конструкторов, как показано ниже:

class employee

{
public:
 employee (char *, long, float);|___ Прототипы перегруженных
 employee(char *, long); |функций
 void show_employee(void);
 int change_salary(float);
 long get_id(void);
private:
 char name [64];
 long employee_id;
 float salary;
}

Ниже приведена реализация программы CONSOVER.CPP:

#include <iostream.h>

#include <string.h>

class employee

{
public:
 employee(char *, long, float);
 employee(char *, long);
 void show_employee(void);
 int change_salary(float) ;
 long get_id(void);
private:
 char name [64];
 long employee_id;
 float salary;
};

employee::employee(char *name, long employee_id, float salary)

{
strcpy(employee::name,
    name);
employee::employee_id
    = employee_id;
if
    (salary < 50000.0) employee::salary = salary;
else
    // Недопустимый
    оклад
employee::salary = 0.0;
}

employee::employee(char *name, long employee_id)

{
strcpy(employee::name,
    name);
employee::employee_id
    = employee_id;
do

   {
cout
    << "Введите оклад для "
    << name << " меньше $50000: ";
cin
    >> employee::salary;
}
while (salary >= 50000.0);
}

void employee::show_employee(void)

{
 cout << "Служащий: " << name << endl;
 cout << "Номер служащего: " << employee_id << endl;
 cout << "Оклад: " << salary << endl;
}

void main(void)

{
employee
    worker("Happy Jamsa", 101, 10101.0);
employee
    manager("Jane Doe", 102);
worker.show_employee();
manager.sbow_employee();
}

Если вы откомпилируете и запустите эту программу, на вашем экране появится запрос ввести оклад для Jane Doe. Когда вы введете оклад, программа отобразит информацию об обоих служащих.

ПРЕДСТАВЛЕНИЕ О ДЕСТРУКТОРЕ

Деструктор автоматически запускается каждый раз, когда программа уничтожает объект. В следующих уроках вы узнаете, как создать списки объектов, которые увеличиваются или уменьшаются по мере выполнения программы. Чтобы создать такие динамические списки, ваша программа для хранения объектов распределяет память динамически (что вы еще не научились делать). К настоящему моменту вы можете создавать и уничтожать объекты в процессе выполнения программы. В таких случаях имеет смысл применение деструкторов.

Каждая из созданных вами до сих пор программ создавала объекты в самом начале своего выполнения, просто объявляя их. При завершении программ C++ уничтожал объекты. Если вы определяете деструктор внутри своей программы, C++ будет автоматически вызывать деструктор для каждого объекта, когда программа завершается (т.е. когда объекты уничтожаются). Подобно конструктору, деструктор имеет такое же имя, как и класс объекта. Однако в случае деструктора вы предваряете его имя символом тильды (~), как показано ниже:

~class_name (void) //----------->указывает деструктор

{
// Операторы деструктора
}

В отличие от конструктора вы не можете передавать параметры деструктору. Следующая программа DESTRUCT.CPP определяет деструктор для класса employee:

void employee::-employee(void)

{
 cout << "Уничтожение объекта для " << name << endl;
}

В данном случае деструктор просто выводит на ваш экран сообщение о том, что C++ уничтожает объект. Когда программа завершается, C++ автоматически вызывает деструктор для каждого объекта. Ниже приведена реализация программы DESTRUCT.CPP:

#include <iostream.h>

#include <string.h>

class employee

{
public:
 employee(char *, long, float);
 ~employee(void);
 void show_employee(void);
 int change_salary(float);
 long get_id(void);
private:
 char name [64] ;
 long employee_id;
 float salary;
};

employee::employee(char *name, long employee_id, float salary)

{
strcpy(employee::name,
    name) ;
employee::employee_id
    = employee_id;
if
    (salary < 50000.0) employee::salary
    = salary;
else
    // Недопустимый
    оклад
employee::salary
    в 0.0;
}

void employee::-employee(void)

{
 cout << "Уничтожение объекта для " << name << endl;
}

void employee::show_employee(void)

{
 cout << "Служащий: " << name << endl;
 cout << "Номер служащего: " << employee_id << endl;
 cout << "Оклад: " << salary << endl;
}

void main(void)

{
employee
    worker("Happy Jamsa", 101, 10101.0);
worker.show_employee();
}

Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> DESTRUCT <ENTER>

Служащий: Happy Jamsa

Номер служащего: 101

Оклад: 10101

Уничтожение объекта для Happy Jamsa

Как видите, программа автоматически вызывает деструктор, без какого-либо явного вызова функции деструктора. До настоящего момента вашим программам, вероятно, не требовалось использовать деструктор. Однако, когда программы начнут распределять память внутри объектов, вы обнаружите, что деструктор обеспечивает удобный способ освобождения памяти при уничтожении объекта.

Деструкторы

Деструктор представляет собой функцию, которую C++ автоматически запускает, когда он или ваша программа уничтожает объект. Деструктор имеет такое же имя, как и класс объекта; однако вы предваряете имя деструктора символом тильды (~), например ~employee. В своей программе вы определяете деструктор точно так же, как и любой другой метод класса.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Конструкторы и деструкторы представляют собой специальные функции класса, которые ваша программа автоматически вызывает при создании или уничтожении объекта. Большинство программ используют конструктор для инициализации элементов данных класса. Простые программы, создаваемые сейчас вами, вероятно, не потребуют использования деструктора. Из урока 24 вы узнаете, как перегружать операторы. Другими словами, вы можете переопределить символ плюс таким образом, что он будет добавлять содержимое одной строки к другой. Как вы уже знаете, тип (например, char, float и int) определяет набор значений, которые может хранить переменная, и набор операций, которые ваши программы могут выполнять над этой переменной. Когда вы определяете класс, вы по существу определяете тип. C++ позволяет вам указать, как ведут себя операторы с данным типом. До изучения урока 24 убедитесь, что освоили следующие основные концепции:

    1. Конструктор представляет собой специальную функцию, которую ваша программа автоматически вызывает каждый раз при создании объекта. Конструктор имеет такое же имя, как и класс объекта.
    2. Конструктор не имеет возвращаемого значения, но вы не указываете ему тип void. Вместо этого вы просто не указываете возвращаемое значение вообще.
    3. Когда ваша программа создает объект, она может передать параметры конструктору во время объявления объекта.
    4. C++ позволяет вам перегружать конструкторы и разрешает использовать значения по умолчанию для параметров.
    5. Деструктор представляет собой специальную функцию, которую ваша программа вызывает автоматически каждый раз при уничтожении объекта. Деструктор имеет такое же имя, как и класс объекта, но его имя предваряется символом тильды (~).


Untitled Document

Урок 24

Перегрузка операторов

Как вы уже знаете, тип переменной определяет набор значений, которые она может хранить, а также набор операций, которые можно выполнять над этой переменной. Например, над значением переменной типа int ваша программа может выполнять сложение, вычитание, умножение и деление. С другой стороны, использование оператора плюс для сложения двух строк лишено всякого смысла. Когда вы определяете в своей программе класс, то по существу вы определяете новый тип. А если так, C++ позволяет вам определить операции, соответствующие этому новому типу.

Перегрузка оператора состоит в изменении смысла оператора (например, оператора плюс (+), который обычно в C++ используется для сложения) при использовании его с определенным классом. В данном уроке вы определите класс string и перегрузите операторы плюс и минус. Для объектов типа string оператор плюс будет добавлять указанные символы к текущему содержимому строки. Подобным образом оператор минус будет удалять каждое вхождение указанного символа из строки. К концу данного урока вы изучите следующие основные концепции:

  • Вы перегружаете операторы для улучшения удобочитаемости ваших программ, но перегружать операторы следует только в том случае, если это упрощает понимание вашей программы.
  • Для перегрузки операторов программы используют ключевое слово C++ operator.
  • Переопределяя оператор, вы указываете функцию, которую C++ вызывает каждый раз, когда класс использует перегруженный оператор. Эта функция, в свою очередь, выполняет соответствующую операцию.
  • Если ваша программа перегружает оператор для определенного класса, то смысл этого оператора изменяется только для указанного класса, оставшаяся часть программы будет продолжать использовать этот оператор для выполнения его стандартных операций.
  • C++ позволяет перегружать большинство операторов, за исключением четырех, перечисленных в таблице 24, которые программы не могут перегружать.

Перегрузка операторов может упростить наиболее общие операции класса и улучшить читаемость программы. Найдите время для эксперимента с программами, представленными в этом уроке, и вы обнаружите, что перегрузка операторов выполняется очень просто.

ПЕРЕГРУЗКА ОПЕРАТОРОВ ПЛЮС И МИНУС

Когда вы перегружаете оператор для какого-либо класса, то смысл данного оператора не изменяется для переменных других типов. Например, если вы перегружаете оператор плюс для класса string, то смысл этого оператора не изменяется, если необходимо сложить два числа. Когда компилятор С++ встречает в программе оператор, то на основании типа переменной он определяет ту операцию, которая должна быть выполнена.

Ниже приведено определение класса, создающее класс string. Этот класс содержит один элемент данных, который представляет собой собственно символьную строку. Кроме того, этот класс содержит несколько различных методов и пока не определяет каких-либо операторов:

class string

{
public:
 string(char *); // Конструктор
void str_append(char *);
 void chr_minus(char);
 void show_string(void);
private:
 char data[256] ;
};

Как видите, класс определяет функцию str_append, которая добавляет указанные символы к содержимому строки класса. Аналогичным образом функция chr_minus - удаляет каждое вхождение указанного символа из строки класса. Следующая программа STRCLASS.CPP использует класс string для создания двух объектов символьных строк и манипулирования ими.

#include <iostream.h>

#include <string.h>

class string

{
public:
 string(char *); // Конструктор
void str_append(char *);
 void chr_minus(char);
 void show_string(void);
private:
 char data[256] ;
};

string::string(char *str)

{
 strcpy(data, str);
}

void string::str_append(char *str)

{
 strcat(data, str);
}

void string::chr_minus(char letter)

{
 char temp[256] ;
 int i, j;
 for (i = 0, j = 0; data[i]; i++) // Эту букву необходимо удалить?
if (data[i] != letter) // Если нет, присвоить ее temp
temp[j++] = data[i];
 temp[j] = NULL; // Конец temp
// Копировать содержимое temp обратно в data
strcpy(data, temp);
}

void string::show_string(void)

{
 cout << data << endl;
}

void main(void)

{
string
    title( "Учимся программировать на языке C++");
string
    lesson("Перегрузка операторов");
title.show_string()
    ;
title.str_append("
    я учусь!");
itle.show_string();
lesson.show_string();
lesson.chr_minus('p')
    ;
lesson.show_string();
}

Как видите, программа использует функцию str_append для добавления символов к строковой переменной title. Программа также использует функцию chr_minus для удаления каждой буквы "р" из символьной строки lesson. В данном случае программа использует вызовы функции для выполнения этих операций. Однако, используя перегрузку операторов, программа может выполнять идентичные операции с помощью операторов плюс (+) и минус (-).

При перегрузке оператора используйте ключевое слово C++ operator вместе с прототипом и определением функции, чтобы сообщить компилятору C++, что класс будет использовать этот метод как оператор. Например, следующее определение класса использует ключевое слово operator, чтобы назначить операторы плюс и минус функциям str_append и chr_minus внутри класса string:

class string

{
public:
 string(char *); // Конструктор
void operator +(char *);
 void operator -(char); —————Определение операторов класса void show_string(void);
private:
 char data[256];
};

Как видите, класс перегружает операторы плюс и минус. Как уже упоминалось, когда класс перегружает оператор, он должен указать функцию, которая реализует операцию, соответствующую этому оператору. В случае оператора плюс определение такой функции становится следующим:

void string::operator +(char *str)

{
strcat(data, str);
}

Как видите, определение этой функции не содержит имени, поскольку здесь определяется перегруженный оператор класса. Для перегрузки оператора плюс программа не изменила обработку, которая осуществляется внутри функции (код этой функции идентичен коду предыдущей функции str_append). Вместо этого программа просто заменила имя функции ключевым словом operators соответствующим оператором. Следующая программа OPOVERLD.CPP иллюстрирует использование перегружаемых операторов плюс и минус:

#include <iostream.h>

#include <string.h>

class string

{
public:
 string(char *); // Конструктор
void operator +(char *);
 void operator -(char);
 void show_string(void);
private;
 char data[256] ;
};

string::string(char *str)

{
 strcpy(data, str);
}

void string::operator +(char *str)

{
 strcat(data, str);
}

void string::operator -(char letter)

{
 char temp[256] ;
 int i, j;
 for (i = 0, j = 0; data[i]; i++) if (data[il 1= letter) temp[j++] = data[i];
 temp[j] = NULL;
 strcpy(data, temp);
}

void string::show_string(void)

{
 cout << data << endl;
}

void main(void)

{
string title( "Учимся программировать на C++");
string
    lesson("Перегрузка операторов");
title.show_string();
title
    + " я учусь!";
title.show_string()
    ;
lesson.show_string();
lesson
    - 'P';
lesson.show_string();
}

Как видите, программа использует перегруженные операторы:

title + " я учусь!"; // Добавить текст " я учусь!"

lesson - 'р'; // Удалить букву 'р'

В данном случае синтаксис оператора законен, но немного непривычен. Обычно вы используете оператор плюс в выражении, которое возвращает результат, например, как в операторе some_str = title + "текст ";. Когда вы определяете оператор, C++ предоставляет вам полную свободу в отношении поведения оператора. Однако, как вы помните, ваша цель при перегрузке операторов состоит в том, чтобы упростить понимание ваших программ. Поэтому следующая программа STR_OVER.CPP немного изменяет предыдущую программу, чтобы позволить ей выполнять операции над переменными типа string, используя синтаксис, который более согласуется со стандартными операторами присваивания:

#include <iostream.h>

#include <string.h>

class string

{
public:
 string(char *); // Конструктор
char * operator +(char *) ;
 char * operator -(char);
 void show_string(void);
private:
 char data[256] ;
} ;

string::string(char *str)

{
 strcpy(data, str);
}

char * string::operator +(char *str)

{
 return(strcat(data, str));
}

char * string::operator -(char letter)

{
 char temp[256];
 int i, j;
 for (i = 0, j = 0; data[i]; i++) if (data[i] 1= letter) temp[j++] = data[i];
 temp[j] = NULL;
 return(strcpy(data, temp));
}

void string::show_string(void)

{
 cout << data << endl;
}

void main(void)

{
string
    title("Учимся программировать на C++");
string
    lesson("Перегрузка операторов");
title.show_string();
title
    = title + " я учусь";
title.show_string()
    ;
lesson.show_string();
lesson
    = lesson - '?';
lesson.show_string();
}

Изменив перегруженные операторы плюс и минус таким образом, чтобы они возвращали указатель на символьную строку, программа может теперь использовать эти операторы в привычном для оператора присваивания виде:

title = title + " учимся программировать!";

lesson = lesson - 'р';

Второй пример

При создании ваших собственных типов данных с помощью классов наиболее общей операцией будет проверка, являются ли два объекта одинаковыми. Используя перегрузку, ваши программы могут перегрузить операторы равенства (==), неравенства (!=) или другие операторы сравнения. Следующая программа COMP_STR.CPP добавляет новый оператор в класс string, который проверяет, равны ли два объекта string. Используя перегрузку операторов, ваши программы могут проверять, содержат ли строковые объекты одинаковые строки, как показано ниже:

if (some_string == another_string)

Ниже приведена реализация программы COMP_STR.CPP:

#include <iostream.h>

#include <string.h>

class string

{
public:
 string(char *); // конструктор
 char * operator +(char *);
 char * operator -(char);
 int operator ==(string);
 void show_string(void);
private:
 char data[256];
};

string::string(char *str)

{
 strcpy(data, str);
}

char * string::operator +(char *str)

{
 return(strcat(data, str));
}

char * string::operator -(char letter)

{
 char temp[256];
 int i, j;
 for (i = 0, j = 0; data[i]; i++) if (data[i] 1= letter) temp[j++] = data[i];
 temp[j] = NULL;
 return(strcpy(data, temp));
}

int string::operator ==(string str)

{
 int i;
 for (i = 0; data[i] == str.data[i]; i++)
 if ((data[i] == NULL) && (str.data[i] == NULL)) return(1); // Равно
 return (0); //He равно
}

void string::show_string(void)

{
 cout << data << endl;
}

void main(void)

{
 string title( "Учимся программировать на C++");
 string lesson("Перегрузка операторов");
 string str( "Учимся программировать на C++");
 if (title == lesson) cout << "title и lesson равны" << endl;
 if (str == lesson) cout << "str и lesson равны" << endl;
 if (title == str) cout << "title и str равны" << endl;
}

Как видите, перегружая операторы подобным образом, вы упрощаете понимание ваших программ.

ОПЕРАТОРЫ, КОТОРЫЕ Вbl HE МОЖЕТЕ ПЕРЕГРУЗИТЬ

В общем случае ваши программы могут перегрузить почти все операторы С++. В табл. 24 перечислены операторы, которые C++ не позволяет перегружать.

Таблица 24. Операторы C++, которые ваши программы не могут перегрузить.

Оператор | Назначение | Пример
. | Выбор элемента | object.member
.* | Указатель на элемент | object.*member
:: | Разрешение области видимости | classname::member
?: | Условный оператор сравнения | с = (а > b) ? а : b;

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Перегрузка операторов — это возможность назначать новый смысл операторам при использовании их с определенным классом. Используя перегрузку операторов, вы можете повысить удобочитаемость ваших программ и облегчить их понимание, выражая операции класса более понятным образом. Из урока 25 вы узнаете, как разделить данные между объектами с помощью элемента static и как использовать методы класса, когда никакие объекты класса не объявляются. До изучения урока 25 убедитесь, что вы освоили следующее:

    1. Чтобы перегрузить оператор, вы должны определить класс, которому оператор будет назначен.
    2. Когда вы перегружаете оператор, перегрузка действует только для класса, в котором он определяется. Если программа использует оператор с неклассовыми переменными (например, переменными типа int или float), используется стандартное определение оператора.
    3. Чтобы перегрузить оператор класса, используйте ключевое слово C++ operator для определения метода класса, который C++ вызывает каждый раз, когда переменная класса использует оператор.
    4. C++ не позволяет вашим программам перегружать оператор выбора элемента (.), оператор указателя на элемент (.*), оператор разрешения области видимости (::) и условный оператор сравнения (?:).


Урок 21 Знакомство с классами C++

Урок 25

Статические функции и элементы данных

До настоящего момента каждый создаваемый вами объект имел свой собственный набор элементов данных. В зависимости от назначения вашего приложения могут быть ситуации, когда объекты одного и того же класса должны совместно использовать один или несколько элементов данных. Например, предположим, что вы пишете программу платежей, которая отслеживает рабочее время для 1000 служащих. Для определения налоговой ставки программа должна знать условия, в которых работает каждый служащий. Пусть для этого используется переменная класса state_of_work. Однако, если все служащие работают в одинаковых условиях, ваша программа могла бы совместно использовать этот элемент данных для всех объектов типа employee. Таким образом, ваша программа уменьшает необходимое количество памяти, выбрасывая 999 копий одинаковой информации. Для совместного использования элемента класса вы должны объявить этот элемент как static (статический). Этот урок рассматривает шаги, которые вы должны выполнить для совместного использования элемента класса несколькими объектами. К концу этого урока вы освоите следующие основные концепции:

  • C++ позволяет иметь объекты одного и того же типа, которые совместно используют один или несколько элементов класса.
  • Если ваша программа присваивает значение совместно используемому элементу, то все объекты этого класса сразу же получают доступ к этому новому значению.
  • Для создания совместно используемого элемента данных класса вы должны предварять имя элемента класса ключевым словом static.
  • После того как программа объявила элемент класса как static, она должна объявить глобальную переменную (вне определения класса), которая соответствует этому совместно используемому элементу класса.
  • Ваши программы могут использовать ключевое слово static, чтобы сделать метод класса вызываемым в то время, как программа, возможно, еще не объявила каких-либо объектов данного класса.

СОВМЕСТНОЕ ИСПОЛЬЗОВАНИЕ ЭЛЕМЕНТА ДАННЫХ

Обычно, когда вы создаете объекты определенного класса, каждый объект получает свой собственный набор элементов данных. Однако возможны такие ситуации, при которых объектам одного и того же класса необходимо совместно использовать один или несколько элементов данных (статические элементы данных). В таких случаях объявляйте элементы данных как общие или частные, а затем предваряйте тип ключевым словом static, как показано ниже:

private:
static int shared_value;

После объявления класса вы должны объявить элемент как глобальную переменную вне класса, как показано ниже:

int class_name::shared_value;

Следующая программа SHARE_IT.CPP определяет класс book_series, совместно использующий элемент page_count, который является одинаковым для всех объектов (книг) класса (серии). Если программа изменяет значение этого элемента, изменение сразу же проявляется во всех объектах класса:

#include <iostream.h>

#include <string.h>

class book_series

{
public:
 book_series(char *, char *, float);
 void show_book(void);
 void set_pages(int) ;
private:
 static int page_count;
 char title[64];
 char author[ 64 ];
 float price;
};

int book_series::page__count;

void book_series::set_pages(int pages)

{
 page_count = pages;
}

book_series::book_series(char *title, char *author, float price)

{
strcpy(book_series::title,
    title);
strcpy(book_series::author,
    author);
book_series::price
    = price;
}

void book_series:: show_book (void)

{
 cout << "Заголовок: " << title << endl;
 cout << "Автор: " << author << endl;
 cout << "Цена: " << price << endl;
 cout << "Страницы: " << page_count << endl;
}

void main(void)

{
book_series
    programming( "Учимся программировать на C++", "Jamsa", 22.95);
book_series
    word( "Учимся работать с Word для Windows", "Wyatt", 19.95);
word.set_pages(256);
programming.show_book
    ();
word.show_book()
    ;
cout
    << endl << "Изменение page_count " << endl;
programming.set_pages(512);
programming.show_book();
word.show_book();
}

Как видите, класс объявляет page_count как static int. Сразу же за определением класса программа объявляет элемент page_count как глобальную переменную. Когда программа изменяет элемент page_count, изменение сразу же проявляется во всех объектах класса book_series.

Совместное использование элементов класса

В зависимости от вашей программы возможны ситуации, когда вам потребуется совместно использовать определенные данные несколькими экземплярами объекта. Для этого объявите такой элемент как static. Далее объявите этот элемент вне класса как глобальную переменную. Любые изменения, которые ваша программа делает с этим элементом, будут немедленно отражены в объектах данного класса.

Использование элементов с атрибутами public static, если объекты не существуют

Как вы только что узнали, при объявлении элемента класса как static этот элемент совместно используется всеми объектами данного класса. Однако возможны ситуации, когда программа еще не создала объект, но ей необходимо использовать элемент. Для использования элемента ваша программа должна объявить его как public и static. Например, следующая программа USЕ_MBR.CPP использует элемент page_count из класса book_series, даже если объекты этого класса не существуют:

#include <iostream.h>

#include <string.h>

class book_series

{
public:
 static int page_count;
private:
 char title [64];
 char author[64];
 float price;
};

int book_series::page_count;

void main(void)
{
book_series::page_count
    = 256;
cout
    << "Текущее значение page_count равно " <<
    book_series::page_count << endl;
}

В данном случае, поскольку класс определяет элемент класса page_count как public, программа может обратиться к этому элементу класса, даже если объекты класса book_series не существуют.

ИСПОЛЬЗОВАНИЕ СТАТИЧЕСКИХ ФУНКЦИЙ-ЭЛЕМЕНТОВ

Предыдущая программа иллюстрировала использование статических элементов данных. Подобным образом C++ позволяет вам определить статические функции-элементы (методы). Если вы создаете статический метод, ваша программа может вызывать такой метод, даже если объекты не были созданы. Например, если класс содержит метод, который может быть использован для данных вне класса, вы могли бы сделать этот метод статическим. Ниже приведен класс menu, который использует esc-последовательность драйвера ANSI для очистки экрана дисплея. Если в вашей системе установлен драйвер ANSI.SYS, вы можете использовать метод clear_screen для очистки экрана. Поскольку этот метод объявлен как статический, программа может использовать его, даже если объекты типа menu не существуют. Следующая программа CLR_SCR.CPP использует метод clear_screen для очистки экрана дисплея:

#include <iostream.h>

class menu

{
public:
 static void clear_screen(void);
// Здесь должны быть другие методы
private:
 int number_of_menu_options;
};

void menu::clear_screen(void)

{
 cout << '\033' << "[2J";
}

void main(void)

{
 menu::clear_screen();
}

Так как программа объявляет элемент clear_screen как статический, она может использовать эту функцию для очистки экрана, даже если объекты типа menu не существуют. Функция clear_screen использует esc-последовательность ANSI Esc[2J для очистки экрана.

Использование в ваших программах методов класса

По мере создания методов класса возможны ситуации, когда функция, созданная вами для использования классом, может быть полезна для операций вашей программы, которые не включают объекты класса. Например, в классе menu была определена функция clear_screen, которую вы, возможно, захотите использовать в программе. Если ваш класс содержит метод, который вы захотите использовать вне объекта класса, поставьте перед его прототипом ключевое слово static и объявите этот метод как public:

public:
static void clear_screen(void);

Внутри вашей программы для вызова такой функции используйте оператор глобального разрешения, как показано ниже:

menu::clear_screen();

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что, если предварять элемент данных класса ключевым словом static, все объекты данного класса могут совместно использовать этот элемент. Когда элемент данных является общим, ваши программы могут обращаться к его значению, даже если объекты этого класса не существуют. Подобно этому, если ваши программы предваряют общий метод класса ключевым словом static, они могут использовать эту функцию для операций, которые не включают объекты класса. Из урока 26 вы узнаете, как использовать наследование для построения объекта из одного или нескольких уже существующих объектов. Использование наследования для создания новых объектов может сэкономить огромные усилия, затрачиваемые на программирование. До изучения урока 26 убедитесь, что освоили следующие основные концепции:

    1. Когда вы объявляете элемент класса как static, то такой элемент может совместно использоваться всеми объектами данного класса.
    2. После того как ваша программа объявляет элемент класса как static, она должна вне определения класса объявить глобальную переменную, соответствующую совместно используемому элементу класса.
    3. Если вы объявляете элемент как public и static, ваша программа может использовать такой элемент, даже если объекты данного класса не существуют. Для обращения к этому элементу ваша программа должна использовать оператор глобального разрешения, например class_name::member_name.
    4. Если вы объявляете общую статическую функцию-элемент, ваша программа может вызывать эту функцию, даже если объекты данного класса не существуют. Для вызова данной функции программа должна использовать оператор глобального разрешения, например menu::clear_screen().


Untitled Document

Урок 26

Наследование

Цель объектно-ориентированного программирования состоит в повторном использовании созданных вами классов, что экономит ваше время и силы. Если вы уже создали некоторый класс, то возможны ситуации, что новому классу нужны многие или даже все особенности уже существующего класса, и необходимо добавить один или несколько элементов данных или функций. В таких случаях C++ позволяет вам строить новый объект, используя характеристики уже существующего объекта. Другими словами, новый объект будет наследовать элементы существующего класса (называемого базовым классом). Когда вы строите новый класс из существующего, этот новый класс часто называется производным классом. В этом уроке впервые вводится наследование классов в C++ . К концу данного урока вы изучите следующие основные концепции:
  • Ели ваши программы используют наследование, то для порождения нового класса необходим базовый класс, т.е. новый класс наследует элементы базового класса.
  • Для инициализации элементов производного класса ваша программа должна вызвать конструкторы базового и производного классов.
  • Используя оператор точку, программы могут легко обращаться к элементам базового и производного классов.
  • В дополнение к общим (public) (доступным всем) и частным (private) (доступным методам класса) элементам C++ предоставляет защищенные (protected) элементы, которые доступны базовому и производному классам.
  • Для разрешения конфликта имен между элементами базового и производного классов ваша программа может использовать оператор глобального разрешения, указывая перед ним имя базового или производного класса.

Наследование является фундаментальной концепцией объектно-ориентированного программирования. Выберите время для экспериментов с программами, представленными в этом уроке. И вы обнаружите, что реально наследование реализуется очень просто и может сохранить огромные усилия, затрачиваемые на программирование.

ПРОСТОЕ НАСЛЕДОВАНИЕ

Наследование представляет собой способность производного класса наследовать характеристики существующего базового класса. Например, предположим, что у вас есть базовый класс employee:

class employee

{
public:
 employee(char *, char *, float);
 void show_employee(void);
private:
 char name[64];
 char position[64];
 float salary;
};

Далее предположим, что вашей программе требуется класс manager, который добавляет следующие элементы данных в класс employee:

float annual_bonus;
char company_car[64];
int stock_options;

В данном случае ваша программа может выбрать два варианта: во-первых, программа может создать новый класс manager, который дублирует многие элементы класса employee, или программа может породить класс типа manager из базового класса employee. Порождая класс manager из существующего класса employee, вы снижаете объем требуемого программирования и исключаете дублирование кода внутри вашей программы.

Для определения этого класса вы должны указать ключевое слово class, имя manager, следующее за ним двоеточие и имя employee, как показано ниже:

Производный класс //-----> class manager : public employee { <-------// Базовый класс

// Здесь определяются элементы
};

Ключевое слово public, которое предваряет имя класса employee, указывает, что общие (public) элементы класса employee также являются общими и в классе manager. Например, следующие операторы порождают класс manager.

class manager : public employee

{
public:
 manager(char *, char *, char *, float, float, int);
 void show_manager(void);
private:
 float annual_bonus;
 char company_car[64];
 int stock_options;
};

Когда вы порождаете класс из базового класса, частные элементы базового класса доступны производному классу только через интерфейсные функции базового класса. Таким образом, производный класс не может напрямую обратиться к частным элементам базового класса, используя оператор точку.

Следующая программа MGR_EMP.CPP иллюстрирует использование наследования в C++ , создавая класс manager из базового класса employee:

#include <iostream.h>

#include <string.h>

class employee

{
public:
 employee(char *, char *, float);
 void show_employee(void);
private:
 char name [ 64 ];
 char position[64];
 float salary;
};

employee::employee(char *name, char *position,float salary)

{
strcpy(employee::name,
    name);
strcpy(employee::position,
    position);
employee::salary
    = salary;
}

void employee::show_employee(void)

{
 cout << "Имя: " << name << endl;
 cout << "Должность: " << position << endl;
 cout << "Оклад: $" << salary << endl;
}

class manager : public employee

{
public:
 manager(char *, char *, char *, float, float, int);
 void show_manager(void);
private:
 float annual_bonus;
 char company_car[64];
 int stock_options;
};

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) : employee(name, position, salary)

{
strcpy(manager::company_car,
    company_car) ;
manager::annual_bonus
    = bonus ;
manager::stock_options
    = stock_options;
}

void manager::show_manager(void)

{
 show_employee();
 cout << "Машина фирмы: " << company_car << endl;
 cout << "Ежегодная премия: $" << annual_bonus << endl;
 cout << "Фондовый опцион: " << stock_options << endl;
}

void main(void)

{
employee
    worker("Джон Дой", "Программист", 35000);
manager
    boss("Джейн Дой", "Вице-президент ", "Lexus", 50000.0, 5000, 1000);
worker.show_employee()
    ;
boss.show_manager();
}

Как видите, программа определяет базовый класс employee, а затем определяет производный класс manager. Обратите внимание на конструктор manager. Когда вы порождаете класс из базового класса, конструктор производного класса должен вызвать конструктор базового класса. Чтобы вызвать конструктор базового класса, поместите двоеточие сразу же после конструктора производного класса, а затем укажите имя конструктора базового класса с требуемыми параметрами:

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) :
employee(name, position, salary) //————————————— Конструктор базового класса

{
strcpy(manager::company_car,
    company_car);
manager::annual_bonus
    = bonus;
manager::stock_options
    = stock_options;
}

Также обратите внимание, что функция show_manager вызывает функцию show_employee, которая является элементом класса employee. Поскольку класс manager является производным класса employee, класс manager может обращаться к общим элементам класса employee, как если бы все эти элементы были определены внутри класса manager,

Представление о наследовании

Наследование представляет собой способность производного класса наследовать характеристики существующего базового класса. Простыми словами это означает, что, если у вас есть класс, чьи элементы данных или функции-элементы могут быть использованы новым классом, вы можете построить этот новый класс в терминах существующего (или базового) класса. Новый класс в свою очередь будет наследовать элементы (характеристики) существующего класса. Использование наследования для построения новых классов сэкономит вам значительное время и силы на программирование. Объектно-ориентированное программирование широко использует наследование, позволяя вашей программе строить сложные объекты из небольших легко управляемых объектов.

Второй пример

Предположим, например, что вы используете следующий базовый класс book внутри существующей программы:

class book

{
public:
 book (char *, char *, int);
 void show_book(void);
private:
 char title[64];
 char author[б 4];
 int pages;
};

Далее предположим, что программе требуется создать класс library_card, который будет добавлять следующие элементы данных в класс book:

char catalog[64];
int checked_out;
    // 1, если проверена, иначе О

Ваша программа может использовать наследование, чтобы породить класс library _card из класса book, как показано ниже:

class library_card : public book

{
public:
 library_card(char *, char *, int, char *, int);
 void show_card(void);
private:
 char catalog[64] ;
 int checked_out;
};

Следующая программа BOOKCARD.CPP порождает класс library_card из клacca book:

#include <iostream.h>

#include <string.h>

class book

{
public:
 book(char *, char *, int);
 void show_book(void);
private:
 char title [64];
 char author[64];
 int pages;
};

book::book(char •title, char *author, int pages)

{
strcpy(book::title,
    title);
strcpy(book::author,
    author);
book::pages
    = pages;
}

void book::show_book(void)

{
 cout << "Название: " << title << endl;
 cout << "Автор: " << author << endl;
 cout << "Страниц: " << pages << endl;
}

class library_card : public book

{
public:
 library_card(char *, char *, int, char *, int);
 void show_card(void) ;
private:
 char catalog[64];
 int checked_out;
};

library_card::library_card(char *title, char *author, int pages, char *catalog, int checked_out) : book(title, author, pages)

{
strcpy(library_card::catalog,
    catalog) ;
library_card::checked_out
    = checked_out;
}

void 1ibrary_card::show_card(void)

{
 show_book() ;
 cout << "Каталог: " << catalog << endl;
 if (checked_out) cout << "Статус: проверена" << endl;
 else cout << "Статус: свободна" << endl;
}

void main(void)

{
library_card
    card( "Учимся программировать на языке C++", "Jamsa", 272, "101СРР", 1);
card.show_card();
}

Как и ранее, обратите внимание, что конструктор library _card вызывает конструктор класса book для инициализации элементов класса book. Кроме того, обратите внимание на использование функции-элемента show_book класса book внутри функции show_card. Поскольку класс library_card наследует методы класса book, функция show_card может вызвать этот метод (show_book) без помощи оператора точки, как если бы этот метод был методом класса library _card.

ЧТО ТАКОЕ ЗАЩИЩЕННЫЕ ЭЛЕМЕНТЫ

При изучении определений базовых классов вы можете встретить элементы, объявленные как public, private и protected (общие, частные и защищенные). Как вы знаете, производный класс может обращаться к общим элементам базового класса, как будто они определены в производном классе. С другой стороны, производный класс не может обращаться к частным элементам базового класса напрямую. Вместо этого для обращения к таким элементам производный класс должен использовать интерфейсные функции. Защищенные элементы базового класса занимают промежуточное положение между частными и общими. Если элемент является защищенным, объекты производного класса могут обращаться к нему, как будто он является общим. Для оставшейся части вашей программы защищенные элементы являются как бы частными. Единственный способ, с помощью которого ваши программы могут обращаться к защищенным элементам, состоит в использовании интерфейсных функций. Следующее определение класса book использует метку protected, чтобы позволить классам, производным от класса book, обращаться к элементам title, author и pages напрямую, используя оператор точку:

class book

{
public:
 book(char *, char *, int) ;
 void show_book(void) ;
protected:
 char title [64];
 char author[64];
 int pages;
};

Если вы предполагаете, что через некоторое время вам придется породить новые классы из создаваемого сейчас класса, установите, должны ли будущие производные классы напрямую обращаться к определенным элементам создаваемого класса, и объявите такие элементы защищенными, а не частными.

Защищенные элементы обеспечивают доступ и защиту

Как вы уже знаете, программа не может обратиться напрямую к частным элементам класса. Для обращения к частным элементам программа должна использовать интерфейсные функции, которые управляют доступом к этим элементам. Как вы, вероятно, заметили, наследование упрощает программирование в том случае, если производные классы могут обращаться к элементам базового класса с помощью оператора точки. В таких случаях ваши программы могут использовать защищенные элементы класса. Производный класс может обращаться к защищенным элементам базового класса напрямую, используя оператор точку. Однако оставшаяся часть вашей программы может обращаться к защищенным элементам только с помощью интерфейсных функций этого класса. Таким образом, защищенные элементы класса находятся между общими (доступными всей программе) и частными (доступными только самому классу) элементами.

РАЗРЕШЕНИЕ КОНФЛИКТА ИМЕН

Если вы порождаете один класс из другого, возможны ситуации, когда имя элемента класса в производном классе является таким же, как имя элемента в базовом классе. Если возник такой конфликт, C++ всегда использует элементы производного класса внутри функций производного класса. Например, предположим, что классы book и library_card используют элемент price. В случае класса book элемент price соответствует продажной цене книги, например $22.95. В случае класса library'_card price может включать библиотечную скидку, например $18.50. Если в вашем исходном тексте не указано явно (с помощью оператора глобального разрешения), функции класса library_card будут использовать элементы производного класса {library_card). Если же функциям класса library_card необходимо обращаться к элементу price базового класса {book), они должны использовать имя класса book и оператор разрешения, например book::price. Предположим, что функции show_card необходимо вывести обе цены. Тогда она должна использовать следующие операторы:

cout << "Библиотечная цена: $" << price << endl;
cout << "Продажная цена: $" << book::price << endl;

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что наследование в C++ позволяет вам строить /порождать) новый класс из существующего класса. Строя таким способом один класс из другого, вы уменьшаете объем программирования, что, в свою очередь, экономит ваше время. Из урока 27 вы узнаете, что C++ позволяет вам порождать класс из двух или нескольких базовых классов. Использование нескольких базовых классов для порождения класса представляет собой множественное наследование. До изучения урока 27 убедитесь, что освоили следующие основные концепции:
    1. Наследование представляет собой способность производить новый класс из существующего базового класса.
    2. Производный класс — это новый класс, а базовый класс — существующий класс.
    3. Когда вы порождаете один класс из другого (базового класса), производный класс наследует элементы базового класса.
    4. Для порождения класса из базового начинайте определение производного класса ключевым словом class, за которым следует имя класса, двоеточие и имя базового класса, например class dalmatian: dog.
    5. Когда вы порождаете класс из базового класса, производный класс может обращаться к общим элементам базового класса, как будто эти элементы определены внутри самого производного класса. Для доступа к частным данным базового класса производный класс должен использовать интерфейсные функции базового класса.
    6. Внутри конструктора производного класса ваша программа должна вызвать конструктор базового класса, указывая двоеточие, имя конструктора базового класса и соответствующие параметры сразу же после заголовка конструктора производного класса.
    7. Чтобы обеспечить производным классам прямой доступ к определенным элементам базового класса, в то же время защищая эти элементы от оставшейся части программы, C++ обеспечивает защищенные {protected) элементы класса. Производный класс может обращаться к защищенным элементам базового класса, как будто они являются общими. Однако для оставшейся части программы защищенные элементы эквивалентны частным.
    8. Если в производном и базовом классе есть элементы с одинаковым именем, то внутри функций производного класса C++ будет использовать элементы производного класса. Если функциям производного класса необходимо обратиться к элементу базового класса, вы должны использовать оператор глобального разрешения, например base class:: member.


Untitled Document

Урок 27

Множественное наследование

Из урока 26 вы узнали, что можно построить один класс из другого, наследуя его характеристики. Оказывается, C++ позволяет порождать класс из нескольких базовых классов. Когда ваш класс наследует характеристики нескольких классов, вы используете множественное наследование. Как вы узнаете из данного урока, C++ полностью поддерживает множественное наследование. К концу этого урока вы изучите следующие основные концепции:

  • Если вы порождаете класс из нескольких базовых классов, то получаете преимущества множественного наследования.
  • При множественном наследовании производный класс получает атрибуты двух или более классов.
  • При использовании множественного наследования для порождения класса конструктор производного класса должен вызвать конструкторы всех базовых классов.
  • При порождении класса из производного класса вы создаете иерархию наследования (иерархию классов).

Множественное наследование является мощным инструментом объектно-ориентированного программирования. Экспериментируйте с программами, представленными в этом уроке, и вы обнаружите, что построение класса из уже существующего значительно экономит усилия на программирование.

ПРОСТОЙ ПРИМЕР

Предположим, к примеру, у вас есть класс computer_screen:

class computer_screen

{
public:
 computer_screen(char *, long, int, int);
 void show_screen(void);
private:
 char type[32] ;
 long colors;
 int x_resolution;
 int y_resolution;
};

Предположим, что у вас есть также класс mother_board:

class mother_board

{
public:
 mother_board(int, int, int);
 void show_mother_board(void);
private:
 int processor;
 int speed;
 int RAM;
};

Используя эти два класса, можно породить класс computer, что показано ниже:

class computer : public computer_screen, public mother_board

{
public:
 computer(char *, int, float, char *, long, int, int, int, int, int);
 void show_computer(void);
private:
 char name[64];
 int hard_disk;
 float floppy;
};

Как видите, этот класс указывает свои базовые классы сразу после двоеточия, следующего за именем класса computer.

class computer : public computer_screen, public mother_board //——————> Базовые классы

Следующая программа COMPUTER. CPP порождает класс computer, используя базовые классы computer_screen и mother_board:

#include <iostream.h>

#include <string.h>

class computer_screen

{
public:
 computer_screen(char *, long, int, int);
 void show_screen(void);
private:
 char type[32];
 long colors;
 int x_resolution;
 int y_resolution;
};

computer_screen::computer_screen(char *type, long colors, int x_res, int y_ree)

{
strcpy(computer_screen::type,
    type);
computer_screen::colors
    = colors;
computer_screen::x_resolution
    = x_res;
computer_screen::y_resolution
    = y_res;
}

void computer_screen::show_screen(void)

{
 cout << "Тип экрана: " << type << endl;
 cout << "Цветов: " << colors << endl;
 cout << "Разрешение: " << x_resolution << " на " << y_resolution << endl;
}

class mother_board

{
public:
 mother_board(int, int, int);
 void show_mother_board(void);
private:
 int processor;
 int speed;
 int RAM;
};

mother_board::mother_board(int processor, int speed, int RAM)

{
mother_board::processor
    = processor;
mother_board::speed
    = speed;
mother_board::RAM
    = ram;
}

void mother_board::show_mother_board(void)

{
 cout << "Процессор: " << processor << endl;
 cout << "Частота: " << speed << "МГц" << endl;
 cout << "ОЗУ: " << RAM << " МВайт" << endl;
}

class computer : public computer_screen, public mother_board

{
public:
 computer(char *, int, float, char *, long, int, int, int, int, int);
 void show_computerf void);
private:
 char name [64];
 int hard_disk;
 float floppy;
};

computer::computer(char *name, int hard_disk, float floppy, char *screen, long colors, int x_res, int y_res, int processor, int speed, int RAM) : computer_screen(screen, colors, x_res, y_res), mother_board(processor, speed, ram)

{
strcpy(computer::name,
    name);
computer::hard_disk
    = hard_disk;
computer::floppy
    = floppy;
}

void computer::show_computer(void)

{
cout
    << "Тип: " << name << endl;
cout
    << "Жесткий диск: " << hard_disk << "МВайт" << endl;
cout
    << "Гибкий диск: " << floppy << "МВайт" << endl;
show_mother_board();
show_screen();
}

void main(void)

{
computer
    my_pc("Compaq", 212, 1.44, "SVGA", 16000000, 640, 480, 486, 66, 8);
my_pc.show_computer();
}

Если вы проанализируете конструктор класса computer, то обнаружите, что он вызывает конструкторы классов mother_board и computer_screen, как показано ниже:

computer::computer(char *name, int hard_disk, float floppy, char *screen, long colors, int x_res, int y_res, int processor, int speed, int RAM) : computer_screen(screen, colors, x_res, y_res), mother_board(processor, speed, RAM)

ПОСТРОЕНИЕ ИЕРАРХИИ КЛАССОВ

При использовании наследования в C++ для порождения одного класса из другого возможны ситуации, когда вы порождаете свой класс из класса, который уже, в свою очередь, является производным от некоторого базового класса. Например, предположим, вам необходимо использовать класс сотputer базовый для порождения класса workstation, как показано ниже:

class work_station : public computer

{
public:
 work_station (char *operating_system, char *name, int hard_disk, float floppy, char *screen, long colors, int x_res, int y_res,int processor, int speed, int RAM);
 void show_work_station(void);
private:
 char operating_system[64];
};

Конструктор класса workstation просто вызывает конструктор класса computer, который в свою очередь вызывает конструкторы классов сотрuter_screen и mother_board:

work_station::work_station( char *operating_system, char *name, int hard_disk, float floppy, char *screen, long colors, int x_res, int y_res, int processor, int speed, int RAM) : computer (name, hard_disk, floppy, screen, colors, x_res, y_res, processor, speed, RAM)

{
strcpy(work_station::operating_system,
      operating_system);
}

В данном случае класс computer выступает в роли базового класса. Однако вы знаете, что класс computer был порожден из классов computer_screen и mother_board. В результате класс work_station наследует характеристики всех трех классов. На рис. 27 показано, что порождение классов приводит к иерархии классов.

Рис. 27. Построение иерархии классов.

Когда ваши программы будут интенсивно использовать наследование, ваша иерархия классов, а следовательно, и количество наследуемых элементов может стать довольно длинной.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Множественное наследование представляет собой возможность порождать класс из нескольких базовых классов. При использовании множественного наследования производный класс получает характеристики (элементы) существующих базовых классов. Поддержка множественного наследования в C++ предоставляет вашим программам огромные возможности объектно-ориентированного программирования. Из урока 28 вы узнаете, как обеспечить доступ к частным элементам класса со стороны других классов или функций других классов, которые вы указываете как друзей. Используя таких друзей, вы можете предоставить определенным функциям прямой доступ к элементам класса, одновременно обеспечивая их защиту от остальной части программы. Прежде чем перейти к уроку 28, убедитесь, что вы изучили следующее:
    1. Множественное наследование является способностью порожденного класса наследовать характеристики нескольких базовых классов.
    2. Для порождения класса из нескольких базовых после имени нового класса и двоеточия вы указываете имена базовых классов, разделяя их запятыми, например class cabbit: public cat, public rabbit.
    3. При определении конструктора производного класса вы должны вызвать конструкторы всех базовых классов, передавая им необходимые параметры.
    4. При порождении классов может случиться так, что используемый вами базовый класс реально порожден из других базовых классов. Если так, то ваша программа создает иерархию классов. Если вызывается конструктор вашего производного класса, то вызываются также и конструкторы наследуемых классов (последовательно).


Untitled Document

Урок 28

Частные элементы и друзья

Как вы уже знаете, ваши программы могут обращаться к частным (private) элементам класса только с помощью функций-элементов этого же класса. Используя частные элементы класса вместо общих во всех ситуациях, где это только возможно, вы уменьшаете возможность программы испортить значения элементов класса, так как программа может обращаться к таким элементам только через интерфейсные функции (которые управляют доступом к частным элементам). Однако в зависимости от использования объектов вашей программы, иногда вы можете существенно увеличить производительность позволяя одному классу напрямую обращаться к частным элементам другого. В этом случае уменьшаются издержки (требуемое время выполнения) на вызов интерфейсных функций. В подобных ситуациях C++ позволяет определить класс в качестве друга (friend} другого класса и разрешает классу-другу доступ к частным элементам этого другого класса. В этом уроке объясняется, как ваши программы могут указать, что два класса являются друзьями. К концу данного урока вы освоите следующие основные концепции:

  • Используя ключевое слово friend, класс может сообщить C++, кто является его другом, т. е. другими словами, что другие классы могут обращаться напрямую к его частным элементам.
  • Частные элементы класса защищают данные класса, следовательно, вы должны ограничить круг классов-друзей только теми классами, которым действительно необходим прямой доступ к частным элементам искомого класса.
  • C++ позволяет ограничить дружественный доступ определенным набором функций.

Частные (private) элементы позволяют вам защищать классы и уменьшить вероятность ошибок. Таким образом, вы должны ограничить использование классов-друзей настолько, насколько это возможно. Иногда программа напрямую может изменить значения элементов класса, это увеличивает вероятность появления ошибок.

ОПРЕДЕЛЕНИЕ ДРУЗЕЙ КЛАССА

C++ позволяет друзьям определенного класса обращаться к частным элементам этого класса. Чтобы указать C++, что один класс является другом (friend) другого класса, вы просто помещаете ключевое слово friend и имя соответствующего класса-друга внутрь определения этого другого класса. Например, приведенный ниже класс book объявляет класс librarian своим другом. Поэтому объекты класса librarian могут напрямую обращаться к частным элементам класса book, используя оператор точку:

class book

{
public:
 book (char *, char *, char *);
 void show_book(void);
 friend librarian;
private:
 char title [64] ;
 char author[64];
 char catalog[64];
};

Как видите, чтобы указать друга, необходим только один оператор внутри определения класса. Например, следующая программа VIEWBOOK.CPP использует librarian в качестве друга класса book. Следовательно, функции класса librarian могут напрямую обращаться к частным элементам класса book. В данном случае программа использует функцию change_catalog класса librarian для изменения номера карточки каталога определенной книги:

#include <iostream.h>

#include <string.h>

class book

{
public:
 book (char *, char *, char *);
 void show_book(void);
 friend librarian;
private:
 char title[64] ;
 char author[64];
 char catalog[64];
};

book::book(char *title, char *author, char •catalog)

{
strcpy(book::title,
    title);
strcpy(book::author,
    author) ;
strcpy(book::catalog,
    catalog);
}

void book::show_book(void)

{
 cout << "Название: " << title << endl;
 cout << "Автор: " << author << endl;
 cout << "Каталог: " << catalog << endl;
}

class librarian

{
public:
 void change_catalog(book *, char *);
 char *get_catalog(book);
};

void librarian::change_catalog(book *this_book, char *new_catalog)

{
strcpy(this_book->catalog,
    new_catalog);
}

char *librarian: :get__catalog(book this_book)

{
static
    char catalog[64];
strcpy(catalog,
    this_book.catalog);
return(catalog)
    ;
}

void main(void)

{
book
    programming( "Учимся программировать на языке C++", "Jamsa", "P101");
librarian
    library;
programming.show_book();
library.change_catalog(&programming,
    "Легкий C++ 101");
programming.show_book();
}

Как видите, программа передает объект book в функцию change_catalog класса librarian по адресу. Поскольку эта функция изменяет элемент класса book, программа должна передать параметр по адресу, а затем использовать указатель для обращения к элементу этого класса. Экспериментируйте с данной программой, попробуйте удалить оператор friend из определения класса book. Поскольку класс librarian больше не имеет доступа к частным элементам класса book, компилятор C++ сообщает о синтаксических ошибках при каждой ссылке на частные данные класса book.

О друзьях класса

Обычно единственный способ, с помощью которого ваши программы могут обращаться к частным элементам класса, заключается в использовании интерфейсных функций. В зависимости от использования объектов программы иногда может быть удобным (или более эффективным с точки зрения скорости вычислений) разрешить одному классу обращаться к частным элементам другого. Для этого вы должны информировать компилятор C++, что класс является другом (friend). Компилятор, в свою очередь, позволит классу-другу обращаться к частным элементам требуемого класса. Чтобы объявить класс другом, поместите ключевое слово friend и имя класса-друга в секцию public определения класса, как показано ниже:

class abbott

{
public:
friend costello;
// Общие элементы
private:
// Частные элементы
};

Как друзья отличаются от защищенных (protected) элементов

Из урока 26 вы узнали, что в C++ существуют защищенные (protected) элементы класса, что позволяет производным классам обращаться к защищенным элементам базового класса напрямую, используя оператор точку. Помните, что к защищенным элементам класса могут обращаться только те классы, которые являются производными от данного базового класса, другими словами, классы, которые наследуют элементы базового класса (защищенные элементы класса являются как бы частными по отношению к остальным частям программы). Классы-друзья C++ обычно не связаны между собой узами наследования. Единственный способ для таких не связанных между собой классов получить доступ к частным элементам другого класса состоит в том, чтобы этот другой класс информировал компилятор, что данный класс является другом.

ОГРАНИЧЕНИЕ КОЛИЧЕСТВА ДРУЗЕЙ

Как вы только что узнали, если вы объявляете один класс другом другого класса, вы обеспечиваете классу-другу доступ к частным элементам данных этого другого класса. Вы также знаете и то, что чем больше доступа к частным данным класса, тем больше шансов на внесение ошибок в программу. Следовательно, если доступ к частным данным другого класса необходим только нескольким функциям класса, C++ позволяет указать, что только определенные функции дружественного класса будут иметь доступ к частным элементам. Предположим, например, что класс librarian, представленный в предыдущей программе, содержит много разных функций. Однако предположим, что только функциям change_catalog и get_catalog необходим доступ к частным элементам класса book. Внутри определения класса book мы можем ограничить доступ к частным элементам только этими двумя функциями, как показано ниже:

class book

{
public:
 book(char *, char *, char *);
 void show_book(void);
 friend char *librarian::get_catalog(book);
 friend void librarian: :change_catalog( book *, char *);
private:
 char title[64];
 char author[ 64 ];
 char catalog[64];
};

Как видите, операторы friend содержат полные прототипы всех дружественных функций, которые могут напрямую обращаться к частным элементам.

О функциях-друзьях

Если ваша программа использует друзей для доступа к частным данным класса, вы можете ограничить количество функций-элементов класса-друга, который может обращаться к частным данным, используя дружественные функции. Для объявления функции-друга укажите ключевое слово friend, за которым следует полный прототип, как показано ниже:

public:
friend class_name::function_name(parameter types);

Только функции-элементы, указанные как друзья, могут напрямую обращаться к частным элементам класса, используя оператор точку.

Если ваша программа начинает ссылаться на один класс из другого, вы можете получить синтаксические ошибки, если порядок определения классов неверен. В данном случае определение класса book использует прототипы функций, определенные в классе librarian. Следовательно, определение класса librarian должно предшествовать определению класса book. Однако если вы проанализируете класс librarian, то обнаружите, что он ссылается на класс book:

class librarian

{
public:
 void change_catalog(book *, char *);
 char *get_catalog(book);
};

Поскольку вы не можете поставить определение класса book перед определением класса librarian, C++ позволяет вам объявить класс book, тем самым сообщая компилятору, что такой класс есть, а позже определить его. Ниже показано, как это сделать:

class book; // объявление класса

Следующая программа LIMITFRI.CPP использует дружественные функции для ограничения доступа класса librarian к частным данным класса book. Обратите внимание на порядок определения классов:

#include <iostream.h>

#include <string.h>

class book;

class librarian

{
public:
 void change_catalog(book *, char *);
 char *get_catalog(book);
};

class book

{
public:
 book(char *, char *, char *) ;
 void show_book (void);
 friend char *librarian::get_catalog(book);
 friend void librarian::change_catalog( book *, char *);
private:
 char title[64];
 char author[64];
 char catalog[64];
};

book::book(char *title, char *author, char *catalog)

{
strcpy(book::title,
    title);
strcpy(book::author,
    author);
strcpy(book::catalog,
    catalog);
}

void book::show_book(void)

{
 cout << "Название: " << title << endl;
 cout << "Автор: " << author << endl;
 cout << "Каталог: " << catalog << endl;
}

void librarian::change_catalog(book *this_book, char *new_catalog)

{
strcpy(this_book->catalog,
    new_catalog) ;
}

char *librarian::get_catalog(book this_book)

{
static
    char catalog[64];
strcpy(catalog,
    this_book.catalog);
return(catalog)
    ;
}

void main(void)

{
book
    programming( "Учимся программировать на C++", "Jamsa", "P101");
librarian
    library;
programming.show_book();
library.change_catalog(&programming,
    "Легкий C++ 101");
programming.show_book();
}

Как видите, программа сначала использует объявление, чтобы сообщить компилятору, что класс book будет определен позже. Поскольку объявление извещает компилятор о классе book, определение класса librarian может ссылаться на класс book, который еще не определен в программе.

Что такое идентификатор класса

Идентификатор представляет собой имя, например имя переменной или класса. Если ваши программы используют дружественные классы, то может случиться, что определение одного класса ссылается на другой класс (его имя или идентификатор), о котором компилятор C++ еще ничего не знает. В таких случаях компилятор C++ будет сообщать о синтаксических ошибках. Чтобы избавиться от ошибок типа "что следует определять сначала", C++ позволяет вам включать в начало исходного текста программы объявление класса, тем самым вводя идентификатор класса:

class class_name;

Эта строка сообщает компилятору, что ваша программа позже определит указанный класс, а пока программе разрешается ссылаться на этот класс.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

В данном уроке вы изучили, как использовать классы-друзья для обращения к частным элементам другого класса напрямую с использованием оператора точки. В уроке 29 вы изучите, как использовать в C++ шаблоны функций для упрощения определения подобных функций. Но прежде чем перейти к уроку 29 убедитесь, что вы освоили следующее:
    1. Использование в ваших программах на C++ друзей позволяет одному классу обращаться к частным элементам другого класса напрямую, используя оператор точку.
    2. Для объявления одного класса другом (friend) другого класса вы должны внутри определения этого другого класса указать ключевое слово friend, за которым следует имя первого класса.
    3. После объявления класса другом по отношению к другому классу, все функции-элементы класса-друга могут обращаться к частным элементам этого другого класса.
    4. Чтобы ограничить количество дружественных методов, которые могут обращаться к частным данным класса, C++ позволяет указать дружественные функции. Для объявления функции-друга вы должны указать ключевое слово friend, за которым следует прототип функции, которой, собственно, и необходимо обращаться к частным элементам класса.
    5. При объявлении дружественных функций вы можете получить синтаксические ошибки, если неверен порядок определений классов. Если необходимо сообщить компилятору, что идентификатор представляет имя класса, который программа определит позже, вы можете использовать оператор такого вида class class_name;.


Untitled Document

Урок 29

Использование шаблонов функций

При создании функций иногда возникают ситуации, когда две функции выполняют одинаковую обработку, но работают с разными типами данных (например, одна использует параметры типа int, а другая типа float). Вы уже знаете из урока 13, что с помощью механизма перегрузки функций можно использовать одно и то же имя для функций, выполняющих разные действия и имеющих разные типы параметров. Однако, если функции возвращают значения разных типов, вам следует использовать для них уникальные имена (см. примечание к уроку 13). Предположим, например, что у вас есть функция с именем тах, которая возвращает максимальное из двух целых значений. Если позже вам потребуется подобная функция, которая возвращает максимальное из двух значений с плавающей точкой, вам следует определить другую функцию, например fmax. Из этого урока вы узнаете, как использовать шаблоны C++ для быстрого создания функций, возвращающих значения разных типов. К концу данного урока вы освоите следующие основные концепции:

  • Шаблон определяет набор операторов, с помощью которых ваши программы позже могут создать несколько функций.
  • Программы часто используют шаблоны функций для быстрого определения нескольких функций, которые с помощью одинаковых операторов работают с параметрами разных типов или имеют разные типы возвращаемых значений.
  • Шаблоны функций имеют специфичные имена, которые соответствуют имени функции, используемому вами в программе.
  • После того как ваша программа определила шаблон функции, она в дальнейшем может создать конкретную функцию, используя этот шаблон для задания прототипа, который включает имя данного шаблона, возвращаемое функцией значение и типы параметров.
  • В процессе компиляции компилятор C++ будет создавать в вашей программе функции с использованием типов, указанных в прототипах функций, которые ссылаются на имя шаблона.

Шаблоны функций имеют уникальный синтаксис, который может быть на первый взгляд непонятен. Однако после создания одного или двух шаблонов вы обнаружите, что реально их очень легко использовать.

СОЗДАНИЕ ПРОСТОГО ШАБЛОНА ФУНКЦИИ

Шаблон функции определяет типонезависимую функцию. С помощью такого шаблона ваши программы в дальнейшем могут определить конкретные функции с требуемыми типами. Например, ниже определен шаблон для функции с именем тах, которая возвращает большее из двух значений:

template<class Т> Т mах(Т а, Т b)

{
 if (а > b) return(а);
 else return(b);
}

Буква T данном случае представляет собой общий тип шаблона. После определения шаблона внутри вашей программы вы объявляете прототипы функций для каждого требуемого вам типа. В случае шаблона тах следующие прототипы создают функции типа float и int.

float max(float, float);
int max(int, int);

Когда компилятор C++ встретит эти прототипы, то при построении функции он заменит тип шаблона T указанным вами типом. В случае с типом float функция тах после замены примет следующий вид:

template<class Т> Т max(Т а, Т b)

{
 if (a > b) return(а) ;
 else return(b);
}

float max(float a, float b)

{
 if (a > b) return(a) ;
 else return(b);
}

Следующая программа МАХ_ТЕМР.СРР использует шаблон тах для создания функции типа int и float.

#include <iostream.h>

template<class T> Т mах(Т а, Т b)

{
 if (a > b) return(a);
 else return(b);
}

float max(float, float);

int max(int, int);

void main(void)

{
 cout << "Максимум 100 и 200 равен " << max(100, 200) << endl;
 cout << "Максимум 5.4321 и 1.2345 равен " << max(5.4321, 1.2345) << endl;
}

В процессе компиляции компилятор C++ автоматически создает операторы для построения одной функции, работающей с типом int, и второй функции, работающей с типом float. Поскольку компилятор C++ управляет операторами, соответствующими функциям, которые вы создаете с помощью шаблонов, он позволяет вам использовать одинаковые имена для функций, которые возвращают значения разных типов. Вы не смогли бы это сделать, используя только перегрузку функций, как обсуждалось в уроке 13.

Использование шаблонов функций

По мере того как ваши программы становятся более сложными, возможны ситуации, когда вам потребуются подобные функции, выполняющие одни и те же операции, но с разными типами данных. Шаблон функции позволяет вашим программам определять общую, или типонезависимую, функцию. Когда программе требуется использовать функцию для определенного типа, например int или float, она указывает прототип функции, который использует имя шаблона функции и типы возвращаемого значения и параметров. В процессе компиляции C++ создаст соответствующую функцию. Создавая шаблоны, вы уменьшаете количество функций, которые должны кодировать самостоятельно, а ваши программы могут использовать одно и то же имя для функций, выполняющих определенную операцию, независимо от возвращаемого функцией значения и типов параметров.

ШАБЛОНЫ, КОТОРЫЕ ИСПОЛЬЗУЮТ НЕСКОЛЬКО ТИПОВ

Предыдущее определение шаблона для функции max использовало единственный общий тип Т. Очень часто в шаблоне функции требуется указать несколько типов. Например, следующие операторы создают шаблон для функции show_array, которая выводит элементы массива. Шаблон использует тип Т для определения типа массива и тип Т1 для указания типа параметра count:

template<class T,class T1> void show_array(T *array,T1 count)

{
 T1 index;
 for (index =0; index < count; index++) cout << array[index] << ' ';
cout << endl;
}

Как и ранее, программа должна указать прототипы функций для требуемых типов:

void show_array(int *, int);
void show_array(float *, unsigned);

Следующая программа SHOW_TEM.CPP использует шаблон для создания функций, которые выводят массивы типа int и типа float.

#include <iostream.h>

template<class T,class T1> void show_array( T *array,T1 count)

{
 T1 index;
 for (index =0; index < count; index++) cout << array[index] “ ' ';
 cout << endl;
}

void show_array(int *, int);

void show_array(float *, unsigned);

void main(void)

{
int pages[] = { 100, 200, 300, 400, 500 };
 float pricesH = { 10.05, 20.10, 30.15 };
 show_array(pages, 5);
 show_array(prices, 3);
}

Шаблоны и несколько типов

По мере того как шаблоны функций становятся более сложными, они могут обеспечить поддержку нескольких типов. Например, ваша программа может создать шаблон для функции с именем array_sort, которая сортирует элементы массива. В данном случае функция может использовать два параметра: первый, соответствующий массиву, и второй, соответствующий количеству элементов массива. Если программа предполагает, что массив никогда не будет содержать более 32767 значений она может использовать тип int для параметра размера массива. Однако более универсальный шаблон мог бы предоставить программе возможность указать свой собственный тип этого параметра, как показано ниже:

template<class Т, class T1> void array_sort(T array[], T1 elements)

{
// операторы
}

С помощью шаблона array_sort программа может создать функции которые сортируют маленькие массивы типа float (менее 128 элементов) и очень большие массивы типа int, используя следующие прототипы:

void array_sort(float, char);
void array_sort(int, long);

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Как вы уже знаете, использование шаблонов функций уменьшает объем программирования, позволяя компилятору C++ генерировать операторы для функций, которые отличаются только типами возвращаемых значений и параметров. Из урока 30 вы узнаете, как использовать шаблоны для создания типонезависимых, или общих, классов. До изучения урока 30 убедитесь, что вы освоили следующие основные концепции:
    1. Шаблоны функций позволяют вам объявлять типонезависимые, или общие, функции.
    2. Когда вашей программе требуется использовать функцию с определенными типами данных, она должна указать прототип функции, который определяет требуемые типы.
    3. Когда компилятор C++ встретит такой прототип функции, он создаст операторы, соответствующие этой функции, подставляя требуемые типы.
    4. Ваши программы должны создавать шаблоны для общих функций, которые работают с отличающимися типами. Другими словами, если вы используете с какой-либо функцией только один тип, нет необходимости применять шаблон.
    5. Если функция требует несколько типов, шаблон просто назначает каждому типу уникальный идентификатор, например Т, T1 и Т2. Позже в процессе компиляции компилятор C++ корректно назначит типы, указанные вами в прототипе функции.


Урок 30

Использование шаблонов классов

Из урока 29 вы узнали, как в C++ использовать шаблоны функций для создания общих, или типонезависимых, функций. Определяя шаблоны функций вы заставляете компилятор C++ создавать в случае необходимости функции, которые отличаются типом возвращаемого значения или типами параметров. Если возникает необходимость создавать подобные функции, отличающиеся только используемыми типами, то может возникнуть необходимость и создания общих классов. А если так, то ваши программы могут определять шаблоны классов. В этом уроке рассмотрены действия вашей программы, необходимые для объявления и дальнейшего использования шаблонов классов. К концу данного урока вы освоите следующие основные концепции:
  • Используя ключевое слово template и символы типов (например, Т, T1 и Т2) ваши программы могут создать шаблон класса — определение шаблона класса может использовать эти символы для объявления элементов данных, указания типов параметров и возвращаемого значения функций-элементов и т.д.
  • Для создания объектов класса с использованием шаблонов ваши программы просто ссылаются на имя класса, за которым внутри угловых скобок следуют типы (например, <int, float>), каждому из которых компилятор назначает символы типов и имя переменной.
  • Если у класса есть конструктор, с помощью которого вы инициализируете элементы данных, вы можете вызвать этот конструктор при создании объекта с использованием шаблона, например class_name<int,float>values(200);.
  • Если компилятор C++ встречает объявление объекта, он создает класс из шаблона, используя соответствующие типы.

Как и в случае с шаблонами функций, шаблоны классов на первый взгляд могут показаться достаточно сложными, однако если вы хоть раз создали и использовали пару шаблонов классов, то обнаружили, что дело это вполне простое.

СОЗДАНИЕ ШАБЛОНА КЛАССА

Предположим, к примеру, вы создаете класс массива, в котором есть методы для вычисления суммы и среднего значения хранимых в массиве чисел. Предположим, что вы работаете с массивом типа int, и ваш класс мог бы выглядеть так:

class array

{
public:
 array(int size);
 long sum(void);
 int average_value(void);
 void show_array(void);
 int add_value(int);
private:
 int *data;
 int size;
 int index;
};

Следующая программа I_ARRAY.CPP использует класс array ддя работы со значениями типа int.

#include <iostream.h>

#include <stdlib.h>

class array

{
public:
 array(int size);
 long sum(void);
 int average_value(void);
 void show_array(void);
 int add_value(int) ;
private:
 int *data;
 int size;
 int index;
};

array::array(int size)

{
 data = new int [size];
 if (data == NULL)

   {
cerr
    << "Недостаточно памяти - программа завершается " << endl;
exit(l);
}

   array::
    size = size;
array::index
    = 0;
}

long array::sum(void)

{
long
    sum = 0;
for
    (int i = 0; i < index; i++) sum += data[i];
return(sum);
}

int array::average_value(void)

{
 long sum = 0;
 for (int i = 0; i < index; i++) sum += data[i];
 return (sum / index);
}

void array::show_array(void)

{
 for (int i = 0; i < index; i++) cout << data[i] << ' ';
 cout << endl;
}

int array::add_value(int value)

{
 if (index == size) return(-1); // массив полон
else

   {
data[index]
    = value;
index++;
return(0);
    // успешно
}
}

void main(void)

{
 array numbers (100); // массив из 100 эл-тов
int i;
 for (i = 0; i < 50; i++) numbers.add_value(i);
 numbers.show_array();
 cout << "Сумма чисел равна " << numbers.sum () << endl;
 cout << "Среднее значение равно " << numbers.average_value() << endl;
}

Как видите, программа распределяет 100 элементов массива, а затем заносит в массив 50 значений с помощью метода add_value. В классе array переменная index отслеживает количество элементов, хранимых в данный момент в массиве. Если пользователь пытается добавить больше элементов, чем может вместить массив, функция add_value возвращает ошибку. Как видите, функция average_value использует переменную index для определения среднего значения массива. Программа запрашивает память для массива, используя оператор new, который подробно рассматривается в уроке 31.

Шаблоны классов

По мере того как количество создаваемых вами классов растет, вы обнаруживаете, что некоторый класс, созданный для одной программы (или, возможно, для этой), очень похож на требующийся вам сейчас. Во многих случаях классы могут отличаться только типами. Другими словами, один класс работает с целочисленными значениями, в то время как требующийся вам сейчас должен работать со значениями типа. float. Чтобы увеличить вероятность повторного использования существующего кода, C++ позволяет вашим программам определять шаблоны классов. Если сформулировать кратко, то шаблон класса определяет типонезависимый класс, который в дальнейшем служит для создания объектов требуемых типов. Если компилятор C++ встречает объявление объекта, основанное на шаблоне класса, то для построения класса требуемого типа он будет использовать типы, указанные при объявлении. Позволяя быстро создавать классы, отличающиеся только типом, шаблоны классов сокращают объем программирования, что, в свою очередь, экономит ваше время.

Пойдем дальше. Теперь предположим, что вашей программе необходимо работать с массивом значений с плавающей точкой, кроме того, что она работает с целочисленным массивом. Один из способов обеспечить поддержку массивов различных типов состоит в создании разных классов. С другой стороны, используя шаблоны классов, вы можете избавиться от необходимости дублировать классы. Ниже представлен шаблон класса, который создает общий класс array:

template<class T, class T1> class array

{
public:
 array(int size);
 T1 sum (void);
 T average_value(void);
 void show_array(void);
 int add_value(T);
private:
 T *data;
 int size;
 int index;
};

Этот шаблон определяет символы типов T и T1. В случае массива целочисленных значений Т будет соответствовать int, а T1 — long. Аналогичным образом для массива значений с плавающей точкой значения Т и Т1 равны float. Теперь потратьте время, чтобы убедиться, что вы поняли, как компилятор С++ будет подставлять указанные вами типы вместо символов Т и Т1.

Далее, перед каждой функцией класса вы должны указать такую же запись со словом template. Кроме того, сразу же после имени класса вы должны указать типы класса, например array <T, T1>::average_value. Следующий оператор иллюстрирует определение функции average_value для этого класса:

template<class Т, class T1> Т array<T, T1>::average_value(void)

{
T1 sum = 0;
 int i;
for (i = 0; i < index; i++) sum += data[i] ;
return (sum / index);
}

После создания шаблона вы можете создавать класс требуемого типа, указывая имя класса, а за ним в угловых скобках необходимые типы, как показано ниже:

Имя шаблона //----> array <int, long> numbers (100); <------//Типы шаблона
 array <float, float> values(200);

Программа GENARRAY.CPP использует шаблон класса array для создания двух классов, один из которых работает со значениями типа int, а второй — со значениями типа float.

#include <iostream.h>

#include <stdlib.h>

template<class T, class T1> class array

{
public:
 array(int size);
 T1 sum(void);
 T average_value(void);
 void show_array(void);
 int add_value(T);
private:
 T *data;
 int size;
 int index;
};

template<class T, class T1> array<T, t1>::array(int size)

{
 data = new T[size];
 if (data == NULL)

   {
cerr << "Недостаточно памяти - программа
    завершается" << endl;
exit(l);
}

   array::size
    = size;
array::index
    = 0;
}

template<class T, class T1> Tl array<T, Tl>::sum(void)

{
T1
    sum = 0;
for
    (int i = 0; i < index; i++) sum += data[i];
return(sum);
}

template<class T, class T1> T array<T, T1>::average_value(void)

{
 Tl sum =0;
 for (int i = 0; i < index; i++) sum += data[i];
 return (sum / index);
}

template<class T, class T1> void array<T, T1>::show_array(void)

{
 for (int i = 0; i < index; i++) cout << data[i] << ' ';
 cout << endl;
}

template<class T, class T1> int array<T, T1>::add_value(T value)

{
 if (index == size)
 return(-1); // Массив полон
else

   {
data[index]
    = value;
index++;
return(0);
    // Успешно
}
}

void main(void)

{
//
    Массив из 100 элементов
array<int, long> numbers(100)7
// Массив из 200 элементов
array<float, float> values(200);
int
    i;
for
    (i = 0; i < 50; i++) numbers.add_value(i);
numbers.show_array();
cout
    << "Сумма чисел равна " << numbers.sum () << endl;
cout
    << "Среднее значение равно " << numbers.average_value() <<
    endl;
for
    (i = 0; i < 100; i++) values.add_value(i * 100);
values.show_array();
cout
    << "Сумма чисел равна." << values.sum() << endl;
cout
    << "Среднее значение равно "
    << values.average_value() << endl;
}

Лучшим способом понять шаблоны классов будет напечатать две копии этой программы. В первой копии замените все символы T и Т1 на int и long. A во второй замените Т и Т1 на float.

Объявление объектов, основанных на шаблоне класса

Для создания объектов с использованием шаблона класса вы просто должны указать имя шаблона класса, за которым между левой и правой угловыми скобками укажите типы, которыми компилятор заменит символы Т, T1, T2 и т. д. Затем ваша программа должна указать имя объекта (переменной) со значениями параметров, которые вы хотите передать конструктору класса, как показано ниже:

template_class_name<typel, type2> object_name( parameter1, parameter2);

Когда компилятор C++ встречает такое объявление, он создает класс, основанный на указанных типах. Например, следующий оператор использует шаблон класса array для создания массива типа char, в котором хранится 100 элементов:

array<char, int> small_numbers(100) ;

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что шаблоны классов помогут вам избавиться от дублирования кода программы, если вам необходимы объекты похожих классов, которые отличаются только типом. Поскольку шаблоны классов могут быть сложными, они могут вас смутить. Когда вы определяете ваш класс, начните с определения, как будто бы вы создаете класс для конкретного типа. После того как вы полностью опишете класс, определите какие элементы необходимо изменить, чтобы работать с объектами различных типов. Теперь замените типы этих элементов такими символами, как, например, Т, Т1, Т2 и т.д.
    1. Программы, представленные в данном уроке, использовали оператор C++ new для динамического (во время выполнения программы) распределения памяти для массива. В уроке 31 вы подробно ознакомитесь с оператором new. Прежде чем перейти к уроку 31, убедитесь, что вы изучили следующее:
    2. Шаблоны классов позволяют избавиться от дублирования кода для таких классов, чьи объекты отличаются только типом их элементов.
    3. Для создания шаблона класса предварите определение класса ключевым словом template и символами типов, например Т и T1.
    4. Далее вы должны предварить определение каждой функции класса таким же оператором с ключевым словом template. Кроме того, укажите типы шаблона между левой и правой угловыми скобками, а выражение в угловых скобках поместите между именем класса и оператором разрешения области видимости, например class_name<T,T1>::function_name.
    5. Для создания класса с использованием шаблона укажите имя класса и замещающие значения для типов между левой и правой угловыми скобками, например class_name<int, long> object.


Untitled Document

Урок 31

Использование свободной памяти в С++

Как вы уже знаете, если ваша программа объявляет массив, компилятор C++ распределяет память для хранения его элементов. Однако представляется возможным, что до некоторого времени размер массива может быть не так велик, чтобы вместить все необходимые данные. Например, предположим, что вы создали массив для хранения 100 акций. Если позже вам потребуется хранить более 100 акций, вы должны изменить свою программу и перекомпилировать ее. С другой стороны, вместо распределения массива фиксированного размера ваши программы могут запрашивать необходимое количество памяти динамически, т.е. во время выполнения. Например, если программе необходимо следить за акциями, она могла бы запросить память, достаточную для хранения 100 акций. Аналогично, если программе необходимы только 25 акций, она могла бы запросить меньше памяти. Распределяя подобным образом память динамически, ваши программы непрерывно изменяют свои потребности без дополнительного программирования. Если ваши программы запрашивают память во время выполнения, они указывают требуемое количество памяти, а C++ возвращает указатель на эту память. C++ распределяет память из областей памяти, которые называются свободной памятью. В этом уроке рассматриваются действия, которые должна выполнить ваша программа для динамического распределения, а впоследствии освобождения памяти во время выполнения. К концу данного урока вы освоите следующие основные концепции:

  • Чтобы запросить память во время выполнения, ваши программы должны использовать оператор C++ new.
  • При использовании оператора new программы указывают количество требуемой памяти. Если оператор new может успешно выделить требуемый объем памяти, он возвращает указатель на начало области выделенной памяти.
  • Если оператор new не может удовлетворить запрос на память вашей программы (возможно, свободной памяти уже не осталось), он возвращает указатель NULL.
  • Чтобы позже освободить память, распределенную с помощью оператора new, ваши программы должны использовать оператор C++ delete.

Динамическое распределение памяти во время выполнения является чрезвычайно полезной возможностью. Экспериментируйте с программами, представленными в данном уроке. И вы поймете, что динамическое распределение памяти реально выполняется очень просто.

ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА new

Оператор C++ new позволяет вашим программам распределять память во время выполнения. Для использования оператора new вам необходимо указать количество байтов памяти, которое требуется программе. Предположим, например, что вашей программе необходим 50-байтный массив. Используя оператор new, вы можете заказать эту память, как показано ниже:

char *buffer = new char[50];

Говоря кратко, если оператор new успешно выделяет память, он возвращает указатель на начало области этой памяти. В данном случае, поскольку программа распределяет память для хранения массива символов, она присваивает возвращаемый указатель переменной, определенной как указатель на тип char. Если оператор new не может выделить запрашиваемый вами объем памяти, он возвратит NULL-указатель, который содержит значение 0. Каждый раз, когда ваши программы динамически распределяют память с использованием оператора new, они должны проверять возвращаемое оператором new значение, чтобы определить, не равно ли оно NULL.

Зачем необходимо динамически распределять память с использованием new

Многие программы интенсивно используют массивы для хранения множества значений определенного типа. При проектировании своих программ программисты обычно пытаются объявить массивы с размерами, достаточными для удовлетворения будущих потребностей программы. К сожалению, если случится так, что потребности программы когда-нибудь превысят подобные ожидания программиста, то кому-то придется редактировать и перекомпилировать такую программу.

Вместо редактирования и перекомпилирования программ, которые просто запрашивают память с запасом, вам следует создавать свои программы таким образом, чтобы они распределяли требуемую им память динамически во время выполнения, используя оператор new. В этом случае ваши программы могут адаптировать использование памяти в соответствии с вашими изменившимися потребностями, избавляя вас от необходимости редактировать и перекомпилировать программу.

Например, следующая программа USE_NEW.CPP использует оператор new для получения указателя на 100-байтный массив:

#include <iostream.h>

void main(void)

{
 char *pointer = new char[100];
 if (pointer != NULL) cout << "Память успешно выделена" << endl;
 else cout << "Ошибка выделения памяти" << endl;
}

Как видите, программа сразу проверяет значение, присвоенное оператором new переменной-указателю. Если указатель содержит значение NULL, значит new не смог выделить запрашиваемый объем памяти. Если же указатель содержит не NULL, следовательно, new успешно выделил память и указатель содержит адрес начала блока памяти.

Если new не может удовлетворить запрос на память, он возвратит NULL

При использовании оператора new для выделения памяти может случиться так, что ваш запрос не может быть удовлетворен, поскольку нет достаточного объема свободной памяти. Если оператор new не способен выделить требуемую память, он присваивает указателю значение NULL. Проверяя значение указателя, как показано в предыдущей программе, вы можете определить, был ли удовлетворен запрос на память. Например, следующий оператор использует new для распределения памяти под массив из 500 значений с плавающей точкой:

float *array = new float[100];

Чтобы определить, выделил ли оператор new память, ваша программа должна сравнить значение указателя с NULL, как показано ниже:

if (array != NULL) cout << "Память выделена успешно" << endl;
else cout << "new не может выделить память" << endl;

Предыдущая программа использовала оператор new для выделения 100 байт памяти. Поскольку эта программа "жестко закодирована" на объем требуемой памяти, возможно, вам потребуется ее редактировать и перекомпилировать, если возникнет необходимость, чтобы программа выделила меньше или больше памяти. Как уже кратко обсуждалось, одна из причин для динамического распределения памяти состоит в том, чтобы избавиться от необходимости редактировать и перекомпилировать программу при изменении требований к объему памяти. Следующая программа ASK_MEM.CPP запрашивает у пользователя количество байт памяти, которое необходимо выделить, и затем распределяет память, используя оператор new:

#include <iostream.h>

void main(void)

{
 int size;
 char *pointer;
 cout << "Введите размер массива, до 30000: ";
 cin >> size;
 if (size <= 30000)

{
 pointer = new char[size];
 if (pointer != NULL) cout << "Память выделена успешно" << endl;
 else cout << "Невозможно выделить память" << endl;
 }
}

Когда ваши программы используют оператор new для динамического распределения памяти, то вполне вероятно, что они сами знают, сколько памяти необходимо выделить. Например, если программа распределяет память для хранения информации о служащих, она, возможно, сохранила количество служащих в файле. Следовательно, при запуске она может прочитать количество служащих из файла, а затем выделить соответствующее количество памяти.

Следующая программа NOMEMORY.CPP выделяет каждый раз память для 10000 символов до тех пор, пока оператор new не сможет больше выделить память из свободной памяти. Другими словами, эта программа удерживает выделенную память, пока не использует всю доступную свободную память. Если программа успешно выделяет память, она извещает об этом сообщением. Если память больше не может быть выделена, программа выводит сообщение об ошибке и завершается:

#include <iostream.h>

void main(void)

{
 char * pointer;
 do

{
 pointer = new char[10000];
 if (pointer != NULL) cout << "Выделено 10000 байт" << endl;
 else cout << "Больше нет памяти" << endl;
 } while (pointer 1= NULL);
}

Замечание: Если выработаете в среде MS-DOS, то, возможно, будете удивлены тем, что свободная память исчерпается после того, как программа выделит 64 Кбайт, Большинство работающих в MS-DOS компиляторов C++ по умолчанию используют малую модель памяти, которая обеспечивает только 64 К6aйm свободной памяти. Аналогично, если вы используете среду MS-DOS, то наибольшая область памяти, к которой могут обратиться ваши программы, может быть ограничена 64Кбайт.

О свободной памяти

Каждый раз при запуске вашей программы компилятор C++ устанавливает отдельную область неиспользуемой памяти, которая называется свободной памятью. Используя оператор new, ваша программа может выделить память из этой свободной памяти во время выполнения. Используя свободную память для распределения требуемой памяти, ваши программы не стеснены фиксированными размерами массивов. Размер свободной памяти может изменяться в зависимости от вашей операционной системы и модели памяти компилятора. Если увеличивается количество динамической памяти, требуемой вашими программами, вам необходимо убедиться, что вас не сдерживают ограничения свободной памяти вашей системы.

ОСВОБОЖДЕНИЕ ПАМЯТИ, ЕСЛИ ОНА БОЛЬШЕ НЕ НУЖНА

Как вы знаете, оператор C++ new позволяет вашим программам выделять память динамически во время выполнения. Если вашей программе больше не нужна выделенная память, она должна ее освободить, используя оператор delete. Для освобождения памяти с использованием оператора delete вы просто указываете этому оператору указатель на данную область памяти, как показано ниже:

delete pointer;

Следующая программа DEL_MEM.CPP использует оператор delete для освобождения выделенной с помощью оператора new памяти:

#include <iostream.h>

#include <string.h>

void main(void)

{
 char *pointer = new char[100];
 strcpy(pointer, "Учимся программировать на языке C++");
 cout << pointer << endl;
 delete pointer;
}

По умолчанию, если ваша программа не освобождает выделенную ей память до своего завершения, операционная система автоматически освобождает эту память после завершения программы. Однако если ваша программа использует оператор delete для освобождения памяти по мере того, как она (память) становится ненужной, то эта память вновь становится доступной для других целей (возможно, для вашей программы, которая опять будет использовать оператор new, или для операционной системы).

Второй пример

Следующая программа ALLOCARR.CPP выделяет память для хранения массива из 1000 целочисленных значений. Затем она заносит в массив значения от 1 до 1000, выводя их на экран. Потом программа освобождает эту память и распределяет память для массива из 2000 значений с плавающей точкой, занося в массив значения от 1.0 до 2000.0:

#include <iostreain.h>

void main(void)

{
 int *int_array = new int[1000];
 float *float_array;
 int i;
 if (int_array 1= NULL)

{
 for (i = 0; i < 1000; i++) int_array[i] = i + 1;
 for (i = 0; i < 1000; i++) cout << int_array[i] << ' ';
 delete int_array;
 }
 float_array = new float[2000];
 if (float_array != NULL)

   {
for (i = 0; i < 2000; i++) float_array[i]
    = (i + 1) • 1.0;
for
    (i = 0; i < 2000; i++) cout
    << float_array[i] << ' ' ;
delete
    float_array;
}
}

Как правило, ваши программы должны освобождать память с помощью оператора delete по мере того, как память становится программам не нужна.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что ваши программы могут распределять память динамически во время выполнения из неиспользуемой памяти, называемой свободной памятью, используя для этих целей оператор new. Выделяя подобным образом память динамически, программы могут удовлетворять ваши изменяющиеся запросы без редактирования и перекомпиляции самой программы. Из урока 32 вы узнаете, как управлять распределением свободной памяти, и что делают операторы new, если они не могут удовлетворить запрос на распределение памяти. Прежде чем перейти к уроку 32, убедитесь, что вы изучили следующее:

    1. Способность выделять память динамически во время выполнения снимает с ваших программ зависимость от фиксированных размеров массивов.
    2. Если оператор new успешно выделяет требуемую вашей программой память, он возвращает указатель на начало области этой памяти.
    3. Если оператор new не может выделить требуемую вашей программой память, он возвращает NULL-указатель, который содержит значение 0.
    4. Каждый раз, когда ваши программы распределяют память динамически с использованием оператора new, они должны проверять значение возвращаемого оператором new указателя, чтобы определить, не равен ли он NULL, что указывает на невозможность выделения памяти.
    5. Используя указатель на массив, ваши программы могут обращаться к памяти, выделенной с помощью оператора new.
    6. Оператор new выделяет память из блока неиспользуемой памяти, называемой свободной памятью.
    7. В зависимости от вашей операционной системы и модели памяти компилятора размер свободной памяти может быть различным. В среде MS-DOS свободная память может быть ограничена 64 Кбайт.
    8. Если вашей программе больше не нужна выделенная память, она должна освободить ее (вернуть в свободную память), используя для этого оператор delete.


Untitled Document

Урок 32

Управление свободной памятью

Из урока 31 вы узнали, что при выполнении ваши программы могут использовать оператор new для динамического распределения памяти из свободной памяти. Если оператор new успешно выделяет память, ваша программа получает на нее указатель. Если оператор new не может выделить требуемую память, он присваивает вашей переменной-указателю значение NULL. В зависимости от назначения вашей программы, вы, возможно, захотите, чтобы программа выполнила определенные операции, если new не может удовлетворить запрос на память. Из этого урока вы узнаете, как заставить C++ вызвать специальную функцию, если new не может удовлетворить запрос на память. К концу данного урока вы освоите следующие основные концепции:

  • Вы можете создать свой собственный обработчик ситуации, когда памяти недостаточно — функции, которую C++ вызывает, если new не может удовлетворить запрос на память.
  • C++ позволяет вам определить собственный оператор new для выделения и, возможно, инициализации памяти.
  • C++ позволяет вам определить собственный оператор delete для освобождения памяти.

Как вы узнаете, с помощью собственных операторов new и delete вы можете лучше управлять ошибками при недостаточности памяти.

СОЗДАНИЕ ОБРАБОТЧИКА ДЛЯ ОПЕРАЦИЙ СО СВОБОДНОЙ ПАМЯТЬЮ

Как вы уже знаете из урока 31, если оператор new не может выделить требуемую память из свободной памяти, он присваивает значение NULL вашей переменной-указателю. Следующая программа USE_FREE.CPP неоднократно вызывает оператор new, выделяя каждый раз 1000 байт, пока свободная память не исчерпается:

#include <iostream.h>

void main (void)

{
 char *pointer;
 do

{
 pointer = new char[1000];
 if (pointer 1= NULL) cout << "Выделено 1000 байт" << endl;
 else cout << "Свободной памяти нет " << endl;
 } while (pointer);
}

Как видите, программа просто выполняет цикл, пока new не присвоит указателю значение NULL. Если вы хотите, чтобы new выполнил другие действия (что-нибудь отличное от тупого возвращения значения NULL), когда он не может удовлетворить запрос на память, то сначала вам следует определить функцию, которую должна вызывать ваша программа, если памяти недостаточно для удовлетворения запроса. Например, следующая функция end_pro-gram выводит на экран сообщение, а затем использует функцию библиотеки этапа выполнения exit для завершения программы:

void end_program(void)

{
cout
    << "Запрос на память не может быть удовлетворен" <<
    endl;
exit(l);
}

Чтобы заставить C++ вызывать функцию end_program, если new не может удовлетворить запрос на память, вам необходимо вызвать функцию set_new_handler, указав ей функцию end_program в качестве параметра, как показано ниже:

set_new_handler(end_program);

Следующая программа END_FREE.CPP вызывает функцию end_program, если new не может удовлетворить запрос на память:

#include <iostream.h>

#include <stdlib.h> // Прототип exit

#include <new.h> // Прототип set_new_handler

void end_program(void)

{
cout
    << "Запрос на память не может быть удовлетворен" << endl;
exit(l);
}

void main(void)

{
 char* pointer;
 set_new_handler(end_program);
 do

   {
pointer
    = new char[10000];
cout
    << "Выделено 10000 байт" <<
    endl;
}
    while (1);
}

В данном случае программа просто завершается, если new не может выделить память из свободной памяти. В зависимости от потребностей вашей программы вы могли бы использовать функцию для выделения памяти из другого источника, например из расширенной памяти компьютера, которая существует в среде MS-DOS. Кроме того, ваша программа могла бы освободить память распределенную ею для других целей, чтобы сделать доступной свободную память. Обеспечивая вашим программам возможность создавать обработчик ситуации отсутствия памяти, C++ предоставляет вам полный контроль над процессом распределения памяти.

СОЗДАНИЕ СОБСТВЕННЫХ ОПЕРАТОРОВ new И delete

Как вы знаете, C++ позволяет вашим программам перегружать операторы. Аналогично вы можете перегрузить операторы new и delete, чтобы изменить их поведение. Например, предположим, что вы выделяете 100 байт памяти для хранения супер-секретных данных о вашей компании. Когда вы в дальнейшем освобождаете эту память с помощью оператора delete, освобождается буфер, который содержал эту память, т.е. те самые 100 байт, содержащие супер-секретные данные о вашей компании. Предположим, корпоративный шпион (и программист) имеет доступ к вашему компьютеру, его программа теоретически может распределить тот же 100-байтный массив в памяти вашего компьютера и изучить ваши супер-секреты. Перегружая оператор delete, ваша программа может сначала заполнить этот буфер нулями или другими бессмысленными символами, а потом освободить эту память. Следующая программа MYDELETE.CPP перегружает оператор delete. Она сначала перезаписывает 100 байт, на которые указывает указатель, а затем освобождает память, используя для этого функцию библиотеки этапа выполнения free:

#include <iostream.h>

#include <stdlib.h>

#include <string.h>

static void operator delete(void *pointer)

{
 char *data = (char *) pointer;
 int i;
 for (i = 0; i < 100; i++) data[i] = 0;
 cout << "Секрет в безопасности!" << endl;
 free(pointer);
}

void main(void)

{
 char *pointer = new char[100];
 strcpy(pointer, "Секреты моей компании");
 delete pointer;
}

При запуске программа выделяет память для строкового массива с помощью оператора new. Затем она копирует секреты компании в эту строку. В дальнейшем программа использует перегруженный оператор delete для освобождения памяти. Внутри функции delete приведенный ниже оператор присваивает значение переменной pointer указателю на символьную строку:

char *data = (char *) pointer;

Символы (char *), которые называются оператором приведения типов, предназначены только для того, чтобы сообщить компилятору C++, что функция знает, что она присваивает указатель типа void (см. выше параметры функции) указателю типа char. Если вы опустите оператор приведения типов, программа не откомпилируется. Затем функция копирует нули в 100 байт буфера и освобождает память, используя для этого функцию библиотеки этапа выполнения free. Очень важно отметить, что эта функция (оператор delete) работает только с областью памяти размером 100 байт. Поскольку данная программа выделяет память только один раз, она работает корректно. Если вы измените программу таким образом, чтобы выделялось только десять байт памяти и не сделаете подобных изменений в этой функции, то она перезапишет 90 байт памяти, которые ваша программа, возможно, использовала для других целей, приведя к ошибке. Однако, используя функции библиотеки этапа выполнения, ваши программы могут получить больше информации о размере области памяти, на которую указывает определенный указатель.

Подобным образом следующая программа NEW_OVER.CPP перегружает оператор C++ new. В данном случае перегруженная функция помещает символьную строку "Учимся программировать на языке C++!" в начало выделяемой памяти:

#include <iostream.h>

#include <alloc.h>

#include <string.h>

static void *operator new(size_t size)

{
 char *pointer;
 pointer = (char *) malloc(size);
 if (size > strlen( "Учимся программировать на языке C++!"))
strcpy(pointer, "Учимся программировать на языке C++!");
 return(pointer);
}

void main(void)

{
 char *str = new char[100];
 cout << str << endl;
}

Как видите, функция new использует для выделения памяти функцию malloc библиотеки этапа выполнения. Если размер выделяемой памяти достаточен для хранения строки "Учимся программировать на языке C++!", данная функция использует функцию strcpy библиотеки этапа выполнения для копирования строки в область памяти.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

По мере того как ваши программы становятся более сложными, вы будете выделять память в процессе выполнения, используя оператор new. Из этого урока вы узнали, как изменить поведение оператора new, сначала определяя функцию-обработчик, которую вызывает ваша программа, если new не может удовлетворить запрос на память, а затем с помощью перегрузки самого оператора new. Из урока 33 вы узнаете новые способы использования входного потока cm и выходного потока соut для усовершенствования возможностей ввода и вывода ваших программ. Прежде чем перейти к уроку 33, убедитесь, что вы изучили следующее:

    1. Если оператор new не может удовлетворить запрос на память, то по умолчанию он присваивает значение NULL соответствующему указателю.
    2. Если вашим программам необходима другая обработка в том случае, когда new не может удовлетворить запрос на память, ваши программы могут определить свои собственные обработчики. Используя функцию set_new_handler, программа может заставить new вызвать вашу функцию, если невозможно удовлетворить запрос на память.
    3. C++ позволяет вашим программам перегружать операторы new и delete. Однако, прежде чем это сделать, вы должны иметь четкое представление о свободной памяти (куче) и функциях библиотеки этапа выполнения, которые ею манипулируют.


Untitled Document

Урок 33

Дополнительные возможности cin и cout

На всем протяжении этой книги вы использовали выходной поток cout для вывода информации на экран дисплея. Аналогично, многие из ваших программ использовали входной поток cin для чтения информации с клавиатуры. Оказывается, cin и cout представляют собой классовые объекты, определяемые и создаваемые с помощью заголовочного файла iostream.h. Как объекты cin и cout поддерживают различные операторы и операции. Из данного урока вы узнаете, как расширить возможности ввода и вывода, используя функции, встроенные в классы cin и cout. К концу этого урока вы освоите следующие основные концепции:

  • Заголовочный файл iostream.h содержит определения классов, которые вы можете проанализировать, чтобы лучше понять потоковый ввод/вывод.
  • Используя метод cout.width, ваши программы могут управлять шириной вывода.
  • Используя метод cout.fill, ваши программы могут заменить пустые выходные символы (табуляцию и пробелы) некоторым определенным символом.
  • Для управления количеством цифр, выводимых выходным потоком cout для значений с плавающей точкой, ваши программы могут использовать метод cout.setprecision.
  • Для вывода и ввода по одному символу за один раз ваши программы могут использовать потоковые методы cout.put и cin.get.
  • Используя метод cin.getline, ваши программы могут вводить целую строку за один раз.

Почти любая создаваемая вами на C++ программа будет использовать cout или cin для выполнения операций В/В (ввода/вывода). Выберите время для экспериментов с программами из этого урока.

ЧТО ВНУТРИ iostream.h

Начиная с урока 1, каждая написанная вами на C++ программа включала заголовочный файл iostream.h. Этот файл содержит определения, позволяющие вашим программам использовать cout для выполнения вывода и cin для выполнения ввода. Более точно, этот файл определяет классы istream и ostream (входной поток и выходной поток), a cin и соut являются переменными (объектами) этих классов. Выберите время, чтобы напечатать файл iostream.h. Он находится в подкаталоге INCLUDE. Определения в этом файле достаточно сложны. Однако если вы пройдете по файлу медленно, то обнаружите, что большинство определений являются просто определениями классов и констант. Внутри файла вы найдете объявления переменных cin и cout.

ИСПОЛЬЗОВАНИЕ cout

Как вы уже знаете, cout представляет собой класс, который содержит несколько разных методов. Следующие программы иллюстрируют использование некоторых методов, которые ваши программы могут применять для форматирования вывода. Из урока 3 вы узнали, что манипулятор setw позволяет вашим программам указать минимальное количество символов, которое может занять следующее выходное значение:

#include <iostream.h>

#include <iomanip.h>

void main(void)

{
 cout << "Мое любимое число" << setw(3) << 1001 << endl;
 cout << "Мое любимое число" << setw (4) << 1001 << endl;
 cout << "Мое любимое число" << setw (5) << 1001 << endl;
 cout << "Мое любимое число" << setw(6) << 1001 << endl;
}

Подобным образом метод cout.width позволяет вам указать минимальное количество символов, которое будет использовать сои/для вывода следующего значения. Следующая программа COUTWIDT.CPP использует функцию cout.width для выполнения работы, аналогичной той, которую выполняет setw, что и показано ниже:

#include <iostream.h>

#include <iomanip.h>

void main (void)

{
 int i;
 for (i = 3; i < 7; i++)

   {
cout
    << "Мое любимое число";
cout.
    width (i);
cout
    << 1001 << endl;
}
}

Если вы откомпилируете и запустите вашу программу, на экране дисплея появится следующий вывод:

С:\> COUTWIDT <ENTER>

Мое любимое число1001

Мое любимое число 1001

Мое любимое число 1001

Мое любимое число 1001

Подобно манипулятору setw, ширина, выбираемая с помощью функции cout.width, действует только для следующего выходного значения.

Использование символа-заполнителя

Если вы используете манипулятор setw или функцию cout.width для управления шириной вывода, cout будет помещать пробелы до (или после для выровненных влево) значений, как это и требуется. В зависимости от назначения вашей программы вы, возможно, захотите использовать символ, отличный от пробела. Предположим, например, что ваша программа создает такую таблицу:

Таблица информации
Профиль компании................................................ 10
Доходы и убытки компании...................................11
Члены правления компании..................................13

В данном случае вывод предваряет номера страниц точками. Функция cout.fill позволяет вам указать символ, который cout будет использовать для заполнения пустого пространства. Следующая программа COUTFILL.CPP создает таблицу, подобную приведенной выше:

#include <iostream.h>

#include <iomanip.h>

void main(void)

{
 cout << "Таблица информации" << endl;
 cout.fill (' . ');
 cout << "Профиль компании" << setw(20) << 10 << endl;
 cout << "Доходы и убытки компании" << setw(12) << 11 << endl;
 cout << "Члены правления компании" << setw(14) << 13 << endl;
}

Если вы однажды выбрали символ-заполнитель с помощью cout.fill, он будет оставаться действительным, пока вы не измените его повторным вызовом cout.fill.

Управление цифрами значений с плавающей точкой

Если вы используете cout для вывода значения с плавающей точкой, то обычно не можете сделать каких-либо предположений о том, сколько цифр будет выводить cout no умолчанию. Однако, используя манипулятор setprecision, вы можете указать количество требуемых цифр- Следующая программа SETPREC.CPP использует манипулятор setprecision для управления количеством цифр, которые появятся справа от десятичной точки:

#include <iostream.h>

#include <iomanip.h>

void main(void)

{
 float value = 1.23456;
 int i;
 for (i = 1; i < 6; i++) cout << setprecision(i) << value << endl;
}

Когда вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С:\>SETPREC <ENTER>

1.2

1.23

1.235

1.2346

1.23456

Если вы используете манипулятор setprecision для изменения точности, ваша установка действует до тех пор, пока программа повторно не использует setprecision.

ВЫВОД И ВВОД ОДНОГО СИМВОЛА ЗА ОДИН РАЗ

В зависимости от назначения вашей программы вам, возможно, потребуется выводить символы на дисплей или читать с клавиатуры по одному символу за один раз. Для вывода одного символа за один раз ваши программы могут использовать функцию cout.put. Следующая программа COUTPUT.CPP использует эту функцию для вывода на экран сообщения Учимся программировать на языке C++! по одному символу за раз:

#include <iostream.h>

void main(void)

{
 char string[] = "Учимся программировать на языке C++!";
 int i;
 for (i = 0; string[i]; i++) cout.put(string[i]) ;
}

Библиотека этапа выполнения предоставляет функцию с именем toupper, которая возвращает заглавный эквивалент строчной буквы. Следующая программа COUTUPPR.CPP использует функцию toupper для преобразования символа в верхний регистр, а затем выводит эту букву с помощью cout.put.

#include <iostream.h>

#include <ctype.h> // прототип toupper

void main(void)

{
 char string[] = "C++ language";
 int i;
 for (i = 0; string[i]; i++) cout.put(toupper(string[i]));
 cout << endl << "Результирующая строка: " << string << endl;
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод*:

С:\> COUTUPPR <ENTER>

C++ LANGUAGE

Результирующая строка: C++ language

* К сожалению, функция toupper применима только к английским буквам. Прим. перев.

ЧТЕНИЕ ВВОДА С КЛАВИАТУРЫ ПО ОДНОМУ СИМВОЛУ ЗА РАЗ

Точно так же, как cout предоставляет функцию cout.put для вывода символа, cin предоставляет функцию cin.get, которая позволяет вам читать один символ данных. Чтобы воспользоваться функцией cin.get, вы просто присваиваете переменной возвращаемый этой функцией символ, как показано ниже:

letter = cin.get();

Следующая программа CIN_GET.CPP выводит сообщение, в ответ на которое вам необходимо ввести Y или N. Затем она повторяет в цикле вызов cin.get для чтения символов, пока не получит Y или N:

#include <iostream.h>

#include <ctype.h>

void main(void)

{
 char letter;
 cout << "Хотите продолжать? (Y/N): ";
 do

   {
letter
    = cin.get();
//
    Преобразовать к верхнему регистру
letter
    = toupper(letter);
}
    while ((letter != 'Y') && (letter != 'N'));
cout
    << endl << "Вы ввели " <<
    letter << endl;
}

ЧТЕНИЕ С КЛАВИАТУРЫ ЦЕЛОЙ СТРОКИ

Как вы уже знаете, при использовании cin для выполнения ввода с клавиатуры, cin использует пустые символы, такие как пробел, табуляция или возврат каретки, для определения, где заканчивается одно значение и начинается другое. Во многих случаях вы захотите, чтобы ваши программы считывали целую строку данных в символьную строку. Для этого программы могут использовать функцию cin.getline. Для использования cin.getline вам необходимо указать символьную строку, в которую будут помещаться символы, а также размер строки, как показано ниже:

cin.getline(string, 64);

Когда cin.get читает символы с клавиатуры, она не будет читать символов больше, чем может вместить строка. Удобным способом определить размер массива является использование оператора C++ sizeof, как показано ниже:

сin.getline(string, sizeof(string));

Если позже вы измените размер массива, то вам не нужно будет искать и изменять каждый оператор с cin.get, встречающийся в вашей программе. Вместо этого оператор sizeof' будет использовать корректный размер массива. Следующая программа GETLINE.CPP использует функцию cin.getline для чтения с клавиатуры строки текста:

#include <iostream.h>

void main(void)

{
 char string[128];
 cout << "Введите строку текста и нажмите Enter" << endl;
 cin.getline(string, sizeof(string));
 cout << "Вы ввели: " << string << endl;
}

Когда вы читаете символы с клавиатуры, то, возможно, вам понадобится читать символы вплоть до и включая определенный символ. Когда такой символ будет прочитан, возможно, вы захотите завершить операцию ввода. Для выполнения подобной операции ваша программа может передать искомый символ в cin.getline. Например, следующий вызов заставляет функцию cin.getline читать строку текста, пока не встретится возврат каретки, или пока не будут прочитаны 64 символа, или пока не встретится буква Я:

cin.getline(string, 64, 'Я');

Следующая программа UNTIL_Z.CPP использует cin.getline для чтения строки текста или символов вплоть до появления буквы Я (включая и эту букву):

#include <iostream.h>

void main(void)

{
 char string[128];
 cout << "Введите строку текста и нажмите Enter" << endl;
 cin.getline(string, sizeof(string), 'Я');
 cout << "Вы ввели: " << string << endl;
}

Откомпилируйте и запустите эту программу. Экспериментируйте с различными строками текста. Некоторые из них начинайте с буквы Я, некоторые заканчивайте буквой Я, а некоторые пусть вообще не содержат букву Я.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Каждая созданная вами на C++ программа будет, вероятно, использовать cin или cout для выполнения операций ввода и вывода. Этот урок посвящен некоторым манипуляторам В/В и функциям, которые вы можете использовать с потоками cin и cout. По мере усложнения ваших программ они часто будут сохранять информацию в файлах. Из урока 34 вы узнаете, как в C++ выполнять операции файлового ввода и вывода. Прежде чем приступить к изучению урока 34, убедитесь, что вы освоили следующие основные концепции:

    1. cin и cout являются объектами (переменными) классов istream и ostream, которые определены в заголовочном файле iostream.h. А если так, они предоставляют функции, которые ваши программы могут вызывать для решения определенных задач.
    2. Функция cout.width позволяет вашим программам указать минимальное количество символов, которые будет использовать следующее выходное значение.
    3. Функция cout. fill позволяет вашим программам указать символ, который cout будет использовать для заполнения пустого пространства устанавливаемого с помощью cout.width или setw.
    4. Манипулятор setprecision позволяет вашим программам управлять количеством цифр, выводимых справа от десятичной точки для значений с плавающей точкой.
    5. Функции cin.get и cout.put позволяют вашим программам вводить или выводить один символ.
    6. Функция cin.getline позволяет вашим программам читать строку текста с клавиатуры.


Untitled Document

Урок 34

Файловые операции В/В в C++

По мере усложнения ваших программ они будут сохранять и получать информацию, используя файлы. Если вы знакомы с файловыми манипуляциями в языке С, вы сможете использовать подобные методы и в C++. Кроме того, как вы узнаете из этого урока, C++ предоставляет набор классов файловых потоков, с помощью которых можно очень легко выполнять операции ввода и вывода (В/В) с файлами. К концу данного урока вы освоите следующие основные концепции:

  • Используя выходной файловый поток, вы можете писать информацию в файл с помощью оператора вставки (<<).
  • Используя входной файловый поток, вы можете читать хранимую в файле информацию с помощью оператора извлечения (>>).
  • Для открытия и закрытия файла вы используете методы файловых классов.
  • Для чтения и записи файловых данных вы можете использовать операторы вставки и извлечения, а также некоторые методы файловых классов.

Многие программы, которые вы создадите в будущем, будут интенсивно использовать файлы. Выберите время для экспериментов с программами, представленными в данном уроке. И вы обнаружите, что в C++ выполнять файловые операции очень просто.

ВЫВОД В ФАЙЛОВЫЙ ПОТОК

Из урока 33 вы узнали, что cout представляет собой объект типа ostream (выходной поток). Используя класс ostream, ваши программы могут выполнять вывод в cout с использованием оператора вставки или различных методов класса, например cout.put. Заголовочный файл iostream.h определяет выходной поток cout. Аналогично, заголовочный файл fstream.h определяет класс выходного файлового потока с именем ofstream. Используя объекты класса ofstream, ваши программы могут выполнять вывод в файл. Для начала вы должны объявить объект типа ofstream, указав имя требуемого выходного файла как символьную строку, что показано ниже:

ofstream file_object("FILENAME.EXT");

Если вы указываете имя файла при объявлении объекта типа ofstream, C++ создаст новый файл на вашем диске, используя указанное имя, или перезапишет файл с таким же именем, если он уже существует на вашем диске Следующая программа OUT_FILE.CPP создает объект типа ofstream и затем использует оператор вставки для вывода нескольких строк текста в файл BOOKINFO.DAT:

#include <fstream.h>

void main(void)

{
 ofstream book_file("BOOKINFO.DAT");
 book_file << "Учимся программировать на языке C++, " << "Вторая редакция" << endl;
 book_file << "Jamsa Press" << endl;
book_file << "22.95" << endl;
}

В данном случае программа открывает файл BOOKINFO.DAT и затем записывает три строки в файл, используя оператор вставки. Откомпилируйте и запустите эту программу. Если вы работаете в среде MS-DOS, можете использовать команду TYPE для вывода содержимого этого файла на экран:

С:\> TYPE BOOKINFO.DAT <ENTER>

Учимся программировать на языке C++, Вторая редакция

Jamsa Press

$22.95

Как видите, в C++ достаточно просто выполнить операцию вывода в файл.

ЧТЕНИЕ ИЗ ВХОДНОГО ФАЙЛОВОГО ПОТОКА

Только что вы узнали, что, используя класс ofstream, ваши программы могут быстро выполнить операции вывода в файл. Подобным образом ваши программы могут выполнить операции ввода из файла, используя объекты типа ifstream. Опять же, вы просто создаете объект, передавая ему в качестве параметра требуемое имя файла:

ifstream input_file("filename.EXT");

Следующая программа FILE_IN.CPP открывает файл BOOKINFO.DAT, который вы создали с помощью предыдущей программы, и читает, а затем отображает первые три элемента файла:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 ifstream input_file("BOOKINFO.DAT") ;
 char one[64], two[64], three[64];
 input_file >> one;
 input_file >> two;
 input_file >> three;
 cout << one << endl;
 cout << two << endl;
cout << three << endl;
}

Если вы откомпилируете и запустите эту программу, то, вероятно, предположите, что она отобразит первые три строки файла. Однако, подобно cin, входные файловые потоки используют пустые символы, чтобы определить, где заканчивается одно значение и начинается другое. В результате при запуске предыдущей программы на дисплее появится следующий вывод:

С:\> FILE_IN <ENTER>

учимся

программировать

на

Чтение целой строки файлового ввода

Из урока 33 вы узнали, что ваши программы могут использовать cin.getline для чтения целой строки с клавиатуры. Подобным образом объекты типа ifstream могут использовать getline для чтения строки файлового ввода. Следующая программа FILELINE.CPP использует функцию getline для чтения всех трех строк файла BOOKINFO.DAT:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 ifstream input_file("BOOKINFO.DAT");
 char one[64], two[64], three [64] ;
 input_file.getline(one, sizeof(one)) ;
 input_file.get line(two, sizeof(two));
 input_file.getline(three, sizeof(three)) ;
 cout << one << endl;
 cout << two << endl;
 cout << three << endl;
}

В данном случае программа успешно читает содержимое файла, потому что она знает, что файл содержит три строки. Однако во многих случаях ваша программа не будет знать, сколько строк содержится в файле. В таких случаях ваши программы будут просто продолжать чтение содержимого файла пока не встретят конец файла.

ОПРЕДЕЛЕНИЕ КОНЦА ФАЙЛА

Обычной файловой операцией в ваших программах является чтение содержимого файла, пока не встретится конец файла. Чтобы определить конец файла, ваши программы могут использовать функцию еоf потокового объекта. Эта функция возвращает значение 0, если конец файла еще не встретился, и 1, если встретился конец файла. Используя цикл while, ваши программы могут непрерывно читать содержимое файла, пока не найдут конец файла, как показано ниже:

while (! input_file.eof())

{
 // Операторы
}

В данном случае программа будет продолжать выполнять цикл, пока функция eof возвращает ложь (0). Следующая программа TEST_EOF.CPP использует функцию eof для чтения содержимого файла BOOKINFO.DAT, пока не достигнет конца файла:

#include <iostream.h>

#include <fstream.h>

void main (void)

{
 ifstream input_file("BOOKINFO.DAT");
 char line[64];
 while (! input_file.eof())

   {
input_file.getline(line,
    sizeof(line));
cout
    << line << endl;
}
}

Аналогично, следующая программа WORD_EOF.CPP читает содержимое файла по одному слову за один раз, пока не встретится конец файла:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 ifstream input_file("BOOKINFO.DAT");
 char word[64] ;
 while (! input_file.eof())

   {
input_file
    >> word;
cout
    << word << endl;
}
}

И наконец, следующая программа CHAR_EOF.CPP читает содержимое файла по одному символу за один раз, используя функцию get, пока не встретит конец файла:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 ifstream input_file("BOOKINFO.DAT");
 char letter;
 while (! input_file.eof())

   {
letter
    = input_file.get();
cout
    << letter;
}
}

ПРОВЕРКА ОШИБОК ПРИ ВЫПОЛНЕНИИ ФАЙЛОВЫХ ОПЕРАЦИЙ

Программы, представленные до настоящего момента, предполагали, что во время файловых операций В/В не происходят ошибки. К сожалению, это сбывается не всегда. Например, если вы открываете файл для ввода, ваши программы должны проверить, что файл существует. Аналогично, если ваша программа пишет данные в файл, вам необходимо убедиться, что операция прошла успешно (к примеру, отсутствие места на диске, скорее всего, помешает записи данных). Чтобы помочь вашим программам следить за ошибками, вы можете использовать функцию fail файлового объекта. Если в процессе файловой операции ошибок не было, функция возвратит ложь (0). Однако, если встретилась ошибка, функция fail возвратит истину. Например, если программа открывает файл, ей следует использовать функцию fail, чтобы определить, произошла ли ошибка, как это показано ниже:

ifstream input_file("FILENAME.DAT");
if (input_file.fail())

{
cerr
    << "Ошибка открытия FILENAME.EXT" << endl;
exit(1);
}

Таким образом, программы должны убедиться, что операции чтения и записи прошли успешно. Следующая программа TEST_ALL.CPP использует функцию fail для проверки различных ошибочных ситуаций:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 char line[256] ;
 ifstream input_file("BOOKINFO.DAT") ;
 if (input_file.fail()) cerr << "Ошибка открытия BOOKINFO.DAT" << endl;
 else

{
 while ((! input_file.eof()) && (! input_file.fail()))

      {
input_file.getline(line,
    sizeof(line)) ;
if
    (! input_file.fail()) cout << line << endl;
}
}
}

ЗАКРЫТИЕ ФАЙЛА, ЕСЛИ ОН БОЛЬШЕ НЕ НУЖЕН

При завершении вашей программы операционная система закроет открытые ею файлы. Однако, как правило, если вашей программе файл больше не нужен, она должна его закрыть. Для закрытия файла ваша программа должна использовать функцию close, как показано ниже:

input_file.close ();

Когда вы закрываете файл, все данные, которые ваша программа писала в этот файл, сбрасываются на диск, и обновляется запись каталога для этого файла.

УПРАВЛЕНИЕ ОТКРЫТИЕМ ФАЙЛА

В примерах программ, представленных в данном уроке, файловые операции ввода и вывода выполнялись с начала файла. Однако, когда вы записываете данные в выходной файл, вероятно, вы захотите, чтобы программа добавляла информацию в конец существующего файла. Для открытия файла в режиме добавления вы должны при его открытии указать второй параметр, как показано ниже:

ifstream output_file("FILENAME.EXT", ios::app);

В данном случае параметр ios::app указывает режим открытия файла. По мере усложнения ваших программ они будут использовать сочетание значений для режима открытия файла, которые перечислены в табл. 34.

Таблица 34. Значения режимов открытия.

Режим открытия | Назначение
ios::app | Открывает файл в режиме добавления, располагая файловый указатель в конце файла.
ios::ate | Располагает файловый указатель в конце файла.
ios::in | Указывает открыть файл для ввода.
ios::nocreate | Если указанный файл не существует, не создавать файл и возвратить ошибку.
ios::noreplace | Если файл существует, операция открытия должна быть прервана и должна возвратить ошибку.
ios::out | Указывает открыть файл для вывода.
ios::trunc | Сбрасывает (перезаписывает) содержим, з существующего файла.

Следующая операция открытия файла открывает файл для вывода, используя режим ios::noreplace, чтобы предотвратить перезапись существующего файла:

ifstream output_file("FIlename.EXT", ios::out | ios::noreplace);

ВЫПОЛНЕНИЕ ОПЕРАЦИЙ ЧТЕНИЯ И ЗАПИСИ

Все программы, представленные в данном уроке, выполняли файловые операции над символьными строками. По мере усложнения ваших программ, возможно, вам понадобится читать и писать массивы и структуры. Для этого ваши программы могут использовать функции read и write. При использовании функций read и write вы должны указать буфер данных, в который данные будут читаться или из которого они будут записываться, а также длину буфера в байтах, как показано ниже:

input_file.read(buffer, sizeof(buffer)) ;
output_file.write(buffer, sizeof(buffer));

Например, следующая программа STRU_OUT.CPP использует функцию write для вывода содержимого структуры в файл EMPLOYEE.DAT:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 struct employee

   {
char name[64];
int
    age;
float
    salary;
}
    worker = { "Джон Дой", 33, 25000.0 };

ofstream emp_file("EMPLOYEE.DAT") ;
 emp_file.write((char *) &worker, sizeof(employee));
}

Функция write обычно получает указатель на символьную строку. Символы (char *) представляют собой оператор приведения типов, который информирует компилятор, что вы передаете указатель на другой тип. Подобным образом следующая программа STRU_IN.CPP использует метод read для чтения из файла информации о служащем:

#include <iostream.h>

#include <fstream.h>

void main(void)

{
 struct employee

   {
char name [6 4] ;
int
    age;
float
    salary;
}
    worker = { "Джон Дой", 33, 25000.0 };

ifstream emp_file("EMPLOYEE.DAT");
 emp_file.read((char *) &worker, sizeof(employee));
 cout << worker.name << endl;
 cout << worker.age << endl;
 cout << worker.salary << endl;
}

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

По мере усложнения ваших программ вы регулярно будете использовать файловые операции. Выберите время для экспериментов с программами, представленными в этом уроке. Из урока 35 вы узнаете, как улучшить производительность ваших программ с использованием встроенных функций. Прежде чем перейти к уроку 35, убедитесь, что вы изучили следующее:

    1. Заголовочный файл fstream.h определяет классы ifstream и ofstream, с помощью которых ваша программа может выполнять операции файлового ввода и вывода.
    2. Для открытия файла на ввод или вывод вы должны объявить объект типа ifstream или ofstream, передавая конструктору этого объекта имя требуемого файла.
    3. После того как ваша программа открыла файл для ввода или вывода, она может читать или писать данные, используя операторы извлечения (>>) и вставки (<<).
    4. Ваши программы могут выполнять ввод или вывод символов в файл или из файла, используя функции get и put.
    5. Ваши программы могут читать из файла целую строку, используя функцию getline.
    6. Большинство программ читают содержимое файла, пока не встретится конец файла. Ваши программы могут определить конец файла с помощью функции eof.
    7. Когда ваши программы выполняют файловые операции, они должны проверять состояние всех операций, чтобы убедиться, что операции выполнены успешно. Для проверки ошибок ваши программы могут использовать функцию fail.
    8. Если вашим программам необходимо вводить или выводить такие данные, как структуры или массивы, они могут использовать методы read и write.
    9. Если ваша программа завершила работу с файлом, его следует закрыть с помощью функции close.


Untitled Document

Урок 35

Встроенные функции и ассемблерные коды

Начиная с урока 8, ваши программы интенсивно использовали функции. Как вы уже знаете, единственное неудобство при использовании функций состоит в том, что они увеличивают издержки (увеличивают время выполнения), помещая параметры в стек при каждом вызове. Из данного урока вы узнаете, что для коротких функций можно использовать метод, называемый встроенным кодом, который помещает операторы функции для каждого ее вызова прямо в программу, избегая таким образом издержек на вызов функции. Используя встроенные (inline) функции, ваши программы будут выполняться немного быстрее. К концу этого урока вы освоите следующие основные концепции:

  • Для улучшения производительности за счет уменьшения издержек на вызов функции вы можете заставить компилятор C++ встроить в программу код функции, подобно тому, как это делается при замещении макрокоманд.
  • Используя встроенные (inline) функции, ваши программы остаются удобными для чтения (читающий программу видит вызов функции), но вы избегаете издержек на вызов функции, которые вызваны помещением параметров в стек и их последующим извлечением из стека, а также переходом к телу функции и последующим возвратом из нее.
  • В зависимости от требований, предъявляемых к вашей программе, иногда вам потребуется использовать язык ассемблера для решения определенной задачи.
  • Для упрощения применения программирования на языке ассемблера C++ позволяет определить функции на встроенном языке ассемблера внутри ваших программ на C++.

ВСТРОЕННЫЕ ФУНКЦИИ

Когда вы определяете в своей программе функцию, компилятор C++ переводит код функции в машинный язык, сохраняя только одну копию инструкций функции внутри вашей программы. Каждый раз, когда ваша программа вызывает функцию, компилятор C++ помещает в программу специальные инструкции, которые заносят параметры функции в стек и затем выполняют переход к инструкциям этой функции. Когда операторы функции завершаются, выполнение программы продолжается с первого оператора, который следует за вызовом функции. Помещение аргументов в стек и переход в функцию и из нее вносит издержки, из-за которых ваша программа выполняется немного медленнее, чем если бы она размещала те же операторы прямо внутри программы при каждой ссылке на функцию. Например, предположим, что следующая программа CALLBEEP.CPP вызывает функцию show_message, которая указанное число раз выдает сигнал на динамик компьютера и затем выводит сообщение на дисплей:

#include <iostream.b>

void show_message(int count, char *message)

{
 int i;
 for (i = 0; i < count; i++) cout << '\a';
 cout << message << endl;
}

void main(void)

{
 show_message(3, "Учимся программировать на языке C++");
 show_mes sage(2, "Урок 35");
}

Следующая программа NO_CALL.CPP не вызывает функцию show_message. Вместо этого она помещает внутри себя те же операторы функции при каждой ссылке на функцию:

#include <iostream.h>

void main (void)

{
 int i;
 for (i = 0; i < 3; i++) cout << '\a';
 cout << " Учимся программировать на языке C++" << endl;
 for (i = 0; i < 2; i++) cout << '\a';
 cout << "Урок 35" << endl;
}

Обе программы выполняют одно и то же. Поскольку программа NO_CALL не вызывает функцию show_message, она выполняется немного быстрее, чем программа CALLBEEP. В данном случае разницу во времени выполнения определить невозможно, но, если в обычной ситуации функция будет вызываться 1000 раз, вы, вероятно, заметите небольшое увеличение производительности. Однако программа NO_CALL более запутана, чем ее двойник CALL_BEEP, следовательно, более тяжела для восприятия.

При создании программ вы всегда должны попытаться определить, когда лучше использовать обычные функции, а когда лучше воспользоваться встроенными функциями. Для более простых программ предпочтительно использовать обычные функции. Однако, если вы создаете программу, для которой производительность имеет первостепенное значение, вам следовало бы уменьшить количество вызовов функций. Один из способов уменьшения количества вызовов функций состоит в том, чтобы поместить соответствующие операторы прямо в программу, как только что было сделано в программе NO_CALL. Однако, как вы могли убедиться, замена только одной функции внесла значительную путаницу в программу. К счастью, C++ предоставляет ключевое слово inline, которое обеспечивает лучший способ.

Использование ключевого слова inline

При объявлении функции внутри программы C++ позволяет вам предварить имя функции ключевым словом inline. Если компилятор C++ встречает ключевое слово inline, он помещает в выполнимый файл (машинный язык) операторы этой функции в месте каждого ее вызова. Таким образом, можно улучшить читаемость ваших программ на C++, используя функции, и в то же время увеличить производительность, избегая издержек на вызов функций. Следующая программа INLINE.CPP определяет функции тах и min как inline:

#include <iostream.h>

inline int max(int a, int b)

{
 if (a > b) return(a);
 else return(b) ;
}

inline int min(int a, int b)

{
 if (a < b) return(a);
 else return(b);
}

void main(void)

{
 cout << "Минимум из 1001 и 2002 равен " << min(1001, 2002) << endl;
 cout << "Максимум из 1001 и 2002 равен " << max(1001, 2002) << endl;
}

В данном случае компилятор C++ заменит каждый вызов функции на соответствующие операторы функции. Производительность программы увеличивается без ее усложнения.

О встроенных функциях

Если компилятор C++ перед определением функции встречает ключевое слово inline, он будет заменять обращения к этой функции (вызовы) на последовательность операторов, эквивалентную выполнению функции. Таким образом ваши программы улучшают производительность, избавляясь от издержек на вызов функции и в то же время выигрывая в стиле программы, благодаря использованию функций.

ВСТРОЕННЫЕ ФУНКЦИИ И КЛАССЫ

Как вы уже знаете, при определении класса вы определяете функции этого класса внутри или вне класса. Например, класс employee определяет свои функции внутри самого класса:

class employee

{
public:
 employee(char *name, char *position, float salary)

   {
strcpy(employee::name,
    name);
strcpy(employee::position,
    position);
employee::salary
    = salary;
}

void show_employee(void)

{
 cout << "Имя: " << name << endl;
 cout << "Должность: " << position << endl;
 cout << "Оклад: $" << salary << endl;
 }

private:
 char name [64];
 char position[64];
 float salary;
};

Размещая подобным образом функции внутри класса, вы тем самым объявляете их встроенными {inline). Если вы создаете встроенные функции класса этим способом, C++ дублирует функцию для каждого создаваемого объекта этого класса, помещая встроенный код при каждой ссылке на метод (функцию) класса. Преимущество такого встроенного кода состоит в увеличении производительности. Недостатком является очень быстрое увеличение объема самого определения класса. Кроме того, включение кода функции в определение класса может существенно запугать класс, делая его элементы трудными для восприятия.

Для улучшения читаемости определений ваших классов вы можете вынести функции из определения класса, как вы обычно и делаете, и разместить ключевое слово inline перед определением функции. Например, следующее определение заставляет компилятор использовать встроенные операторы для функции show_employee:

inline void employee::show_employee(void)

{
 cout << "Имя: " << name << endl;
 cout << "Должность: " << position << endl;
 cout << "Оклад: $" << salary << endl;
}

ИСПОЛЬЗОВАНИЕ ОПЕРАТОРОВ ЯЗЫКА АССЕМБЛЕРА

Как вы знаете из урока 1, программисты могут создавать программы, используя широкий спектр языков программирования. Затем компилятор преобразует операторы программы в машинный код (нули и единицы), который понимает компьютер. Каждый тип компьютеров поддерживает промежуточный язык, называемый языком ассемблера, который попадает в категорию между машинным языком и языком программирования, таким как C++.

Язык ассемблера использует другие символы для представления инструкций машинного языка. В зависимости от назначения ваших программ, возможно, вам потребуется выполнить операции низкого уровня, для которых необходимо использовать операторы языка ассемблера. В таких случаях вы можете использовать оператор C++ asm для встраивания операторов языка ассемблера в программу. Большинство создаваемых вами программ не потребуют операторов языка ассемблера. Следующая программа USE_ASM.CPP использует оператор asm, чтобы вставить операторы языка ассемблера, необходимые для озвучивания динамика компьютера в среде MS-DOS:

#include <iostream.h>

void main(void)

{
 cout << "Сейчас будет звонить!" << endl;
 asm

{
 MOV AH,2
 MOV DL,7
 INT 21H
}

cout << "Есть!" << endl;
}

Как видите, используя оператор asm, программа комбинирует C++ и операторы языка ассемблера.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Встроенные функции улучшают производительность ваших программ, уменьшая издержки на вызов функций. Из этого урока вы узнали, как и где использовать встроенные функции в своих программах. Вы также узнали, что иногда вашим программам необходимо использовать язык ассемблера для решения определенных задач. Из урока 36 вы узнаете, как ваши программы могут обратиться к аргументам командной строки, которые пользователь вводит при запуске программы. Однако, прежде чем перейти к уроку 36, убедитесь, что вы освоили следующие основные концепции:

    1. Помещение параметров в стек и переход к функции и из нее вносит издержки, из-за которых ваша программа выполняется немного медленнее.
    2. Ключевое слово inline заставляет компилятор C++ заменять вызов функции эквивалентной последовательностью операторов, которые бы выполняла эта функция. Поскольку встроенные операторы избавляют от издержек на вызов функции, программа будет выполняться быстрее.
    3. Если вы используете встроенные функции внутри класса, каждый создаваемый вами объект использует свои собственные встроенные операторы. Обычно все объекты одного и того же класса совместно используют один и тот же код функции.
    4. Ключевое слово asm позволяет вам встраивать операторы языка ассемблера в программы на C++.


Untitled Document

Урок 36

Использование аргументов командной строки

Как вы знаете, большинство команд, вводимых вами в ответ на системную подсказку, позволяют включать дополнительную информацию, такую как имя файла. Например, при использовании команды MS-DOS COPY для копирования содержимого одного файла во второй файл вы указываете в командной строке имена обоих файлов. Аналогично, если ваш компилятор основан на командной строке, вы должны включать имя вашего исходного файла при вызове компилятора. В этом уроке рассмотрены способы, с помощью которых ваши программы на C++ обращаются к аргументам командной строки. К концу данного урока вы освоите следующие основные концепции:

  • Программы на C++ трактуют аргументы командной строки как параметры функции main.
  • По традиции C++ передает два (иногда три) параметра в main, которые в большинстве программ называются argc и argv.
  • Параметр argc содержит количество аргументов командной строки, передаваемых в вашу программу.
  • Параметр argv представляет собой массив указателей на символьные строки, каждая из которых соответствует одному параметру командной строки.
  • В зависимости от компилятора ваши программы могут получить доступ к третьему параметру с именем env, который представляет собой массив указателей на символьные строки, указывающих на переменные среды.

Способность программ обращаться к аргументам командной строки повышает количество способов, которыми вы можете использовать одну и ту же программу. Например, вы можете создать свою собственную программу, используемую для копирования любого исходного файла, который вы указываете в качестве первого аргумента командной строки, в целевой файл, который вы указываете в качестве второго аргумента командной строки.

ДОСТУП К argv И argc

Если вы запускаете программу из системной подсказки, командная строка, которую вы вводите, становится вашей командной строкой:

С:\> COPY SOURCE.DOC TARGET.DOC <ENTER>

В данном случае командная строка указывает команду (COPY) и два аргумента (имя файлов SOURCE. DOC и TARGET. DOC). Чтобы разрешить вашей программе доступ к командной строке, C++ передает два параметра в функцию main:

void main(int argc, char *argv[])

Первый параметр argc содержит количество элементов в массиве argv. Например, в случае предыдущей команды COPY параметр argc должен содержать значение 3 (он включает имя команды и два аргумента). Следующая программа SHOWARGC.CPP использует параметр argc для вывода количества аргументов командной строки:

#include <iostream.h>

void main(int argc, char *argv[])

{
 cout << "Количество аргументов командной строки равно " << argc << endl;
}

Выберите время для эксперимента с этой программой, вызывая ее с разным количеством параметров, как показано ниже:

С:\> SHOWARGC А В С <ENTER>

Количество аргументов командной строки равно 4

В зависимости от типа компилятор может рассматривать аргументы, которые группируются внутри двойных кавычек, в качестве одного аргумента:

С:\> SHOWARGC "Это один аргумент" <ENTER>

Количество аргументов командной строки равно 2

Рис. 36. Массив argv указывает аргументы командной строки.

Второй параметр функции main с именем argv представляет собой массив указателей на символьные строки, которые содержат индивидуальные части командной строки. Например, рис. 36 иллюстрирует, как элементы массива argv могли бы указывать записи командной строки.

Следующая программа SHOWARGV.CPP использует оператор for для вывода элементов массива argv (командная строка программы). Программа запускается с первого элемента массива (имя программы) и затем выводит каждый элемент, пока значение переменной цикла не станет больше, чем argc:

#include <iostream.h>

void main(int argc, char *argv[])

{
 int i;
 for (i = 0; i < argc; i++) cout << "argv[" << i << "] содержит " << argv[i] << endl;
}

Откомпилируйте и запустите эту программу, используя командную строку, подобную следующей:

С:\> SHOWARGV А В С <ENTER>

argv[0] содержит SHOWARGV.EXE

argv[l] содержит А

argv[2] содержит В

argv[3] содержит С

Доступ к аргументам командной строки

Для увеличения количества задач, выполняемых программой, C++ позволяет вашей программе обращаться к аргументам командной строки, используя два параметра, которые C++ передает в main. Первый параметр argc содержит количество аргументов командной строки (включая имя программы). Второй параметр argv представляет собой массив указателей на символьные строки. Каждая символьная строка соответствует аргументу командной строки. Чтобы обратиться к аргументам командной строки, измените заголовок функции main, как показано ниже:

void main(int argc, char *argv[])

ВЫПОЛНЕНИЕ ЦИКЛА, ПОКА argv НЕ СОДЕРЖИТ NULL

Как вы уже знаете, программы C++ используют символ NULL для завершения символьной строки. Подобным способом C++ использует символ NULL, чтобы отметить последний элемент массива argv. Следующая программа ARGVNULL.CPP изменяет оператор for предыдущей программы, чтобы выполнять цикл по элементам argv, пока текущий элемент argv не будет равен NULL:

#include <iostream.h>

void main(int argc, char *argv[])

{
 int i;
 for (i = 0; argv[i] != NULL; i++) cout << "argv[" << i << "] содержит " << argv[i] << endl;
}

ТРАКТОВКА argv КАК УКАЗАТЕЛЯ

Как вы уже знаете, C++ позволяет вам обращаться к элементам массивов, используя указатели. Следующая программа ARGVPTR.CPP трактует argv как указатель на указатель символьной строки (другими словами, указатель на указатель), чтобы вывести содержимое командной строки:

#include <iostream.h>

void main(int argc, char **argv)

{
 int i = 0;
 while (*argv) cout << "argv[" << i++ << "] содержит " << *argv++ << endl;
}

Выберите время, чтобы проанализировать объявление параметра argvв main:

void main(int argc, char **argv)

Первая звездочка в объявлении сообщает компилятору C++, что argv представляет собой указатель. Вторая звездочка сообщает компилятору, что argv представляет собой указатель на указатель — в данном случае указатель на указатель типа char. Представьте argv как массив указателей. Каждый элемент массива в данном случае указывает на массив типа char.

ИСПОЛЬЗОВАНИЕ АРГУМЕНТОВ КОМАНДНОЙ СТРОКИ

Следующая программа FILESHOW.CPP использует аргументы командной строки для вывода содержимого указанного пользователем файла на экран. Например, чтобы использовать программу FILESHOW для вывода содержимого файла AUTOEXEC.BAT из корневого каталога, ваша командная строка становится следующей:

C: \> FILESHOW \AUTOEXEC.BAT <Enter>

Следующие операторы реализуют программу FILESHOW.CPP. Эта программа начинается с проверки параметра argc, чтобы убедиться, что пользователь указал файл в командной строке. Если пользователь включает имя файла параметр argc будет содержать значение 2. Далее программа открывает и выводит содержимое файла, используя методы, которые вы изучали в уроке 34. Как видите, если программа не может открыть указанный файл, она выводит сообщение об ошибке и завершается:

#include <iostream.h>

#include <fstream.h>

#include <stdlib.h>

void main(int argc, char *argv[])

{
 char line[256];
 if (argc < 2)

   {
cerr
    << "Вы должны указать имя файла" <<
    endl;
exit(1);
}

ifstream input_file(argv[1]) ;
 if (input_file.fail()) cerr << "Ошибка открытия BOOKINPO.DAT" << endl;
 else

{
 while ((! input_file.eof()) && (! input_file.fail()))

      {
input_file.getline(line,
    sizeof(line));
if
    (! input_file.fail()) cout << line << endl;
}
}
}

ДОСТУП К ПЕРЕМЕННЫМ СРЕДЫ ОПЕРАЦИОННОЙ СИСТЕМЫ

Как вы знаете, большинство операционных систем позволяют вам определять переменные среды, к которым ваши программы могут обращаться для определения разных параметров, таких как командный путь. Например, если вы используете среду MS-DOS, вы устанавливаете или выводите переменные среды с помощью команды SET. В зависимости от типа вашего компилятора вы можете обращаться к переменным среды из вашей программы, используя третий параметр main с именем env. Подобно параметру argv, параметр env представляет собой указатель на массив указателей на символьные строки. Также, подобно argv, C++ завершает этот массив символом NULL. Если ваш компилятор поддерживает параметр env, вы можете изменить заголовок функции main, как показано ниже:

void main(int argc, char *argv[], char *env[])

Следующая программа SHOWENV.CPP выполняет цикл по элементам массива env для вывода переменных среды программы:

#include <iostream.b>

void main(int argc, char *argv[], char *env[])

{
 while (*env) cout << *env++ << endl;
}

Как видите, программа просто выполняет цикл по элементам массива env, пока не встретит указатель NULL, который указывает последнюю запись массива. Если вы откомпилируете и запустите эту программу, она выведет ваши переменные среды, как показано ниже:

С:\> SHOWENV <ENTER>

ТЕМР=С:\WINDOWS\TEMP

PROMPT=$p$g

COMSPEC=C:\WINDOWS\COMMAND.СОМ

РАТН=С:\WINDOWS;С:\DOS

Доступ к переменным среды

В зависимости от типа компилятора, ваши программы могут обращаться к переменным среды операционной системы, используя третий параметр функции main с именем env. Подобно параметру argv, параметр env представляет собой массив указателей на символьные строки, каждый из которых указывает переменную среды. Чтобы обратиться к переменным среды, используя параметр env, измените заголовок функции main следующим образом:

void main(int argc, char *argv[], char *env[])

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Чтобы увеличить количество приложений, поддерживаемых вашей программой, C++ позволяет использовать аргументы командной строки. Из урока 37 вы узнаете, как с помощью макрокоманд и констант можно упростить ваше программирование или создать более удобочитаемый код. Однако, прежде чем перейти к уроку 37, убедитесь, что вы освоили следующие основные концепции:

  1. Когда вы запускаете программу из системной подсказки, информация которую вы вводите, становится командной строкой программы.
  2. Чтобы позволить вашим программам обращаться к командной строке C++ передает функции main два параметра: argc и argv.
  3. Параметр argc содержит количество аргументов командной строки.
  4. Параметр argv представляет собой массив указателей на символьные строки, каждая из которых содержит аргумент командной строки.
  5. В зависимости от вашего компилятора программа может обращаться к третьему параметру main с именем env, который представляет собой массив указателей на символьные строки, содержащие переменные среды.


Untitled Document

Урок 37

Использование констант и макрокоманд

Чтобы улучшить удобочитаемость программы, C++ поддерживает именованные константы и макрокоманды. Например, используя именованную константу, вы можете заменить цифровое значение, такое как 50, внутри вашего исходного кода смысловой константой, такой как CLASS_SIZE. Когда ругой программист читает ваш код, он не сможет предположить, что означает цифровое значение 50. Если же вместо этого он каждый раз будет видеть СLASS_SIZE, то он поймет, что это значение соответствует числу студентов в классе. Аналогично, используя макрокоманды, ваши программы могут заменить сложные выражения типа

result = (х*у-3) * (х*у-3) * (х*у-3);

вызовом функции с именем CUBE, как показано ниже:

result = CUBE(x*y-3);

В данном случае макрокоманда не только улучшает удобочитаемость вашего кода, но и упрощает ваш оператор, уменьшая вероятность ошибки. Этот урок рассматривает именованные константы и макрокоманды более подробно. К концу данного урока вы освоите следующие основные концепции:
  • Чтобы сделать программы легче для чтения, программисты часто заменяют цифровые значения более понятными по смыслу именованными константами.
  • Используя именованные константы в вашей программе вместо цифровых значений, вы можете сделать свои программы более легкими для изменения в будущем.
  • C++ позволяет программам заменять выражения смысловыми именами макрокоманд.
  • До компиляции программы компилятор C++ запускает специальную программу, называемую препроцессором, чтобы заменить каждую константу или макрокоманду соответствующим значением.
  • Макрокоманды выполняются быстрее, чем функции, но увеличивают размер выполняемой программы.
  • Большинство компиляторов C++ имеют предопределенные константы и макрокоманды, которые вы можете использовать в своих программах.

ИСПОЛЬЗОВАНИЕ ИМЕНОВАННЫХ КОНСТАНТ

Именованная константа — это просто имя, которому вы присваиваете постоянное значение (константу). Такая константа в отличие от значения переменной не может изменяться по мере выполнения программы. Вы создаете именованную константу, используя директиву препроцессора #define (специальную инструкцию для препроцессора компилятора). Например, следующий оператор определяет именованную константу CLASS_SIZE как значение 50:

#define CLASS_SIZE 50

Чтобы отличить именованную константу от переменной, большинство программистов используют для именованных констант буквы верхнего регистра. Например, следующая программа CONSTANT.CPP определяет и выводит именованную константу CLASS_SIZE:

#include <iostream.h>

#define CLASS_SIZE 50 // Число студентов в классе

void main(void)

{
 cout << "Константа CLASS_SIZE равна " << CLASS_SIZE << endl;
}

Как видите, программа определяет константу, используя директиву #define в начале исходного кода. После того как вы определяете константу, вы можете использовать ее значение на протяжении всей программы, просто обращаясь к имени значения константы.

Замечание: Предыдущее определение константы не заканчивается точкой с запятой. Если вы поставите точку с запятой в конце определения, препроцессор включит ее в ваше определение. Например, если вы в директиве #define предыдущей программы поставите точку с запятой после значения 50, препроцессор в дальнейшем каждый экземпляр константы CLASS_SIZE заменит значением 50 с точкой с запятой (50;), что, очень вероятно, приведет к синтаксической ошибке.

Что такое директивы препроцессора

Прежде чем приступить к компиляции программы, компилятор C++ запускает специальную программу, которая называется препроцессором. Препроцессор ищет в программе строки, начинающиеся с символа #, например #include или #define. Если препроцессор, например, встречает директиву #include, он включает указанный в ней файл в ваш исходный файл, как будто бы вы сами печатали содержимое включаемого файла в вашем исходном коде. Каждая программа, которую вы создали при изучении данной книги, использовала директиву #include, чтобы заставить препроцессор включить содержимое заголовочного файла iostream.h в ваш исходный файл. Если препроцессор встречает директиву #define, он создает именованную константу или макрокоманду. В дальнейшем, если препроцессор встречает имя константы или макрокоманды, он заменяет это имя значением, указанным в директиве #define.

Если вы определяете константы в своих программах, C++ не ограничивает вас в использовании только цифровых значений. Вы можете также использовать константы для хранения символьных строк и значений с плавающей точкой. Например, следующая программа BOOKINFO.CPP использует директиву #define для создания трех констант, которые содержат информацию об этой книге:

#include <iostream.h>

#define TITLE "Учимся программировать на языке C++"
#define LESSON 37
#define PRICE 22.95

void main(void)

{
 cout << "Название книги: " << TITLE << endl;
 cout << "Текущий урок: " << LESSON << endl;
 cout << "Цена: $" << PRICE << endl;
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С:\> BOOKINFO <ENTER>

Название книги: Учимся программировать на языке C++

Текущий урок: 37

Цена: $22.95

Использование #define для создания именованных констант

Для улучшения читаемости ваших программ заменяйте числовые значения в исходном коде константами со смысловыми именами. Для определения именованной константы ваши программы должны использовать директиву препроцессора #define. Размещайте свои константы в верхней части вашего исходного файла. Кроме того, чтобы отличать константы от переменных, большинство программистов для имен констант используют буквы верхнего регистра.

Например, следующая директива #define создает константу с именем SECONDS_PER_HOUR

#define SECONDS_PER_HOUR 3600

Во время компиляции программы препроцессор C++ будет заменять каждый экземпляр имени SECONDS_PER_HOUR числовым значением 3600. Обратите внимание, что определение константы не заканчивается точкой с запятой. Если вы поставите после 3600 точку с запятой, препроцессор C++ в дальнейшем заменит каждый экземпляр имени SECONDS_PER_HOUR его значением с точкой с запятой (3600;), что, очень вероятно, приведет к синтаксической ошибке.

ИСПОЛЬЗОВАНИЕ ИМЕНОВАННЫХ КОНСТАНТ ДЛЯ УПРОЩЕНИЯ ИЗМЕНЕНИЯ КОДА

Кроме того, что именованные константы делают вашу программу легче для восприятия, они еще и облегчают модификацию программ. Например, следующий фрагмент кода несколько раз ссылается на число 50 (количество студентов в классе):

#include <iostream.h>

void main(void)

{
 int test_score8[50];
 char grades[50];
 int student;
 for (student = 0; student < 50; student++) get_test_score(student);
 for (student =0; student < 50; student++) calculate_grade(student);
 for (student =0; student < 50; student++) print_grade(student) ;
}

Предположим, например, что количество студентов в классе увеличилось до 55. В этом случае вы должны отредактировать предыдущую программу, чтобы заменить каждый экземпляр значения 50 значением 55. В следующей программе применен другой подход, она использует именованную константу CLASS_SIZE:

#include <iostream.h>

#define CLASS_SIZE 50

void main(void)

{
int test_scores[CLASS_SIZE] ;
 char grades[CLASS_SIZE] ;
 int student;
 for (student = 0; student < CLASS_SIZE; student++) get_test_score(student);
 for (student = 0; student < CLASS_SIZE; student++) calculate_grade(student) ;
 for (student = 0; student < CLASS_SIZE; student++) print_grade(student);
}

В данном случае для изменения количества студентов во всей программе вам необходимо изменить только одну строку, которая содержит директиву #define, определяющую эту константу:

#define CLASS_SIZE 55

ЗАМЕНА ВЫРАЖЕНИЙ МАКРОКОМАНДАМИ

Если ваши программы выполняют реальные вычисления, то в общем случае ваш код будет содержать сложные выражения типа:

result = (х*у-3) * (х*у-3) * (х*у-3);

В данном случае программа вычисляет куб выражения (х*у-3). Чтобы улучшить читаемость вашей программы и уменьшить вероятность внесения ошибок из-за опечаток, создайте макрокоманду с именем CUBE, которую ваша программа может использовать следующим образом:

result = CUBE(x*y-3);

И опять общепринятым среди программистов считается использование больших букв для имен макрокоманд, чтобы отличить их от функций.

Для создания макрокоманды вы должны использовать директиву препроцессора #define. Например, следующий оператор создает макрокоманду CUBE:

#define CUBE(x) ((х)*(х)*(х))

Как видите, программа определяет макрокоманду CUBE для умножения параметра х на самого себя дважды. Следующая программа SHOWCUBE.CPP использует макрокоманду CUBE для вывода куба значений от 1 до 10:

#include <iostream.h>

#define CUBE(x) ((x)* (x)* (x))

void main (void)

{
 for (int i = 1; i <= 10; i++) cout << "Для " << i << " куб равен " << CUBE(i) << endl;
}

При компиляции этой программы препроцессор C++ заменит каждый экземпляр макрокоманды CUBE соответствующим определением. Другими словами, замена макрокоманды препроцессором приведет к следующему коду:

#include <iostream.h>

#define CUBE(x) ((х)*(х)*(х))
void main(void)

{
 for (int i = 1; i <= 10; i++) cout << "Для " << i << " куб равен " << ((i) * (i) * (i)) << endl;
}

Обратите внимание, что предыдущая макрокоманда поместила параметр х внутрь круглых скобок, использовав запись ((х)*(х)*(х)) вместо (х*х*х). При создании макрокоманд вы должны помещать параметры в круглые скобки, как показано выше, тогда можете быть уверены, что C++ трактует ваши выражения именно так, как вы хотите. Как вы помните из урока 5, C++ использует старшинство операций для определения порядка выполнения арифметических операций. Предположим, например, что программа использует макрокоманду CUBE с выражением 3+5-2, как показано ниже:

result = CUBE(3+5-2);

Если макрокоманда заключает свой параметр в круглые скобки, то препроцессор сгенерирует следующий оператор:

result = ((3+5-2) * (3+5-2) * (3+5-2));

Однако, если в определении макрокоманды опустить круглые скобки, препроцессор сгенерирует следующий оператор:

result = (3+5-2*3+5-2*3+5-2);

Если вы вычислите оба выражения, то обнаружите, что их результаты отличаются. Заключая аргументы макрокоманды в круглые скобки, вы избавитесь от подобных ошибок.

ЧЕМ МАКРОКОМАНДЫ ОТЛИЧАЮТСЯ ОТ ФУНКЦИЙ

Определение макрокоманды не является функцией. Если программа использует функцию, то в выполняемую программу помещается только одна копия операторов функции. Каждый раз при вызове функции ваша программа помещает параметры в стек и затем выполняет переход к коду функции. После завершения функции программа удаляет параметры из стека и переходит обратно к оператору, который следует непосредственно за вызовом функции.

В случае с макрокомандой препроцессор заменяет в вашем коде каждую ссылку на макрокоманду соответствующим определением макрокоманды. Например, если предыдущая программа использует макрокоманду CUBE в 100 различных местах, препроцессор подставит код макрокоманды 100 раз. Используя макрокоманды, вы избегаете издержек на вызов функции (издержек на помещение параметров в стек и удаление их оттуда, а также издержек на выполнение перехода к коду функции и возврат из него). Это происходит благодаря тому, что в случае с макрокомандой препроцессор встраивает в тело программы соответствующие операторы. Однако, поскольку препроцессор заменяет каждую ссылку на макрокоманду соответствующим кодом, макрокоманды увеличивают размер вашей выполняемой программы.

ИСПОЛЬЗОВАНИЕ МАКРОКОМАНД ПРЕДОСТАВЛЯЕТ БОЛЬШУЮ ГИБКОСТЬ

Вы можете использовать макрокоманды в своих программах различным образом. Однако имейте в виду, что цель использования макрокоманд состоит в упрощении кодирования и улучшении восприятия ваших программ. Следующая программа MACDELAY.CPP иллюстрирует гибкость макрокоманд. Кроме того, эта программа поможет вам лучше представить, как препроцессор заменяет имя макрокоманды соответствующими операторами:

#include <iostream.h>

#define delay(х)

{ \
 cout << "Задержка на " << х << endl; \
for (long int i=0; i < х; i++) \
; \
}

void main (void)

{
 delay(l00000L);
 delay(200000L);
 delay(300000L);
}

В данном случае, поскольку определение макрокоманды занимает несколько строк, это определение помещает один символ обратного слэша (\) в конце каждой строки, которая имеет продолжение. Когда препроцессор встретит ссылку на макрокоманду, он заменит эту ссылку операторами, которые появляются в определении макрокоманды.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Макрокоманды и именованные константы предназначены для улучшения восприятия ваших программ и упрощения программирования. Данный урок описывает создание и использование именованных констант и макрокоманд в ваших кодах. Из урока 38 вы узнаете, что такое полиморфизм, который позволяет объектам изменять форму во время выполнения программы. Однако, прежде чем приступить к уроку 38, убедитесь, что вы освоили следующие основные концепции:

    1. Макрокоманды и именованные константы облегчают чтение ваших программ, заменяя сложные выражения и числовые константы смысловыми именами.
    2. Заменяя на протяжении всей программы числовые выражения именованными константами, вы уменьшаете количество изменений, которые вам придется выполнить позже, если значение константы потребуется изменить.
    3. В процессе компиляции компилятор C++ использует специальную программу, которая называется препроцессором, для замены каждой именованной константы или макрокоманды соответствующим значением.
    4. Макрокоманды выполняются быстрее функций, но они увеличивают размер вашей выполняемой программы.
    5. Если определение макрокоманды выходит за пределы одной строки, вы должны в конце каждой строки поместить символ обратного слэша (\), чтобы информировать препроцессор о том, что определение продолжается на следующей строке.


Untitled Document

Урок 38

Полиморфизм

Когда программисты говорят о C++ и объектно-ориентированном программировании, то очень часто употребляют термин полиморфизм. В общем случае полиморфизм представляет собой способность объекта изменять форму. Если вы разделите этот термин на части, то обнаружите, что поли означает много, а морфизм относится к изменению формы. Полиморфный объект, следовательно, представляет собой объект, который может принимать разные формы. Этот урок вводит понятие полиморфизма и рассматривает, как использовать полиморфные объекты внутри ваших программ для упрощения и уменьшения кода. К концу данного урока вы освоите следующие основные концепции:

  • Полиморфизм представляет собой способность объекта изменять форму во время выполнения программы.
  • C++ упрощает создание полиморфных объектов.
  • Для создания полиморфных объектов ваши программы должны использовать виртуальные (virtual) функции.
  • Виртуальная (virtual) функция — это функция базового класса, перед именем которой стоит ключевое слово virtual.
  • Любой производный от базового класс может использовать или перегружать виртуальные функции.
  • Для создания полиморфного объекта вам следует использовать указатель на объект базового класса.

ЧТО ТАКОЕ ПОЛИМОРФИЗМ

Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Предположим, например, что вы программист, работающий в телефонной компании, и вам необходимо написать программу, которая эмулирует телефонные операции. Поскольку вы знакомы с тем, как люди используют телефон, вы быстро выберете общие операции, такие как набор номера, звонок, разъединение и индикация занятости. С помощью этих операций вы определите следующий класс:

class phone

{
public:
 void dial(char "number) { cout << "Набор номера " << number << endl; }
 void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
 void ring(void) { cout << "Звонок, звонок, звонок" << endl;)
phone(char *number) { strcpy(phone::number, number); };
private:
 char number[13];
);

Следующая программа PHONEONE.CPP использует класс phone для создания объекта-телефона:

#include <iostream.h>

#include <string.h>

class phone

{
public:
 void dial(char *number) { cout << "Набор номера " << number << endl; }
 void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
phone(char *number) { strcpy(phone::number, number); };
private:
 char number[13];
};

void main(void)

{
phone
    telephone("555-1212");
telephone.dial("212-555-1212");
}

Если вы продемонстрируете программу вашему боссу, то он или она скажет, что ваша программа не делает различий между дисковым и кнопочным телефонами, и что она не поддерживает платные телефоны, когда пользователь должен заплатить 25 центов, чтобы позвонить.

Поскольку вы знаете наследование, то примете решение породить классы touch_tone и pay_phone из класса phone, как показано ниже:

class touch_tone : phone

{
public:
 void dial(char * number) { cout << "Пик пик Набор номера " << number << endl; }
 touch_tone(char *number) : phone(number) { }
};

class pay_phone : phone

{
public:
 void dial(char * number)

{
cout << "Пожалуйста, оплатите " << amount << " центов" << endl;
 cout << "Набор номера " << number << endl;
}

pay_phone(char *number, int amount) : phone(number) { pay_phone::amount = amount; }
private:
 int amount;
};

Как видите, классы touch_tone и pay__phone определяют свой собственный метод dial. Если вы предположите, что метод dial класса, phone основан на дисковом телефоне, то вам не потребуется создавать класс для дискового телефона. Следующая программа NEWPHONE.CPP использует эти классы для создания объектов rotary, touch_tone и pay_phone:

#include <iostream.h>

#include <string.h>

class phone

{
public:
 void dial(char *number) { cout << "Набор номера " << number << endl; }
 void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
phone(char *number) { strcpy(phone::number, number); };
protected:
 char number[13];
);

class touch_tone : phone

{
public:
 void dial(char *number) { cout << "Пик пик Набор номера " << number << endl; }
 touch_tone(char *number) : phone(number) { }
};

class pay_phone : phone

{
public:
 void dial(char * number) { cout << "Пожалуйста, оплатите " << amount << " центов" << endl; cout << "Набор номера " << number << endl; }
pay_phone(char * number, int amount) : phone(number) { pay_phone::amount = amount; }
private:
 int amount ;
};

void main (void)

{
phone
    rotary("303-555-1212");
rotary.dial("602-555-1212");
touch_tone
    telephone("555-1212");
telephone.dial("212-555-1212");
pay_phone
    city_phone("555-1111", 25);
city_phone.dial("212-555-1212");
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С:\> NEWPHONE <Enter>

Набор номера 602-555-1212

Пик пик Набор номера 212-555-1212

Пожалуйста, оплатите 25 центов

Набор номера 212-555-1212

Как уже упоминалось, полиморфный объект представляет собой такой объект, который изменяет форму во время выполнения программы. Предыдущая программа, например, не использовала полиморфные объекты. Иначе говоря, в ней нет объектов, которые бы изменяли форму.

СОЗДАНИЕ ПОЛИМОРФНОГО ОБЪЕКТА-ТЕЛЕФОНА

После того как вы показали начальству вашу новую телефонную программу, ваше руководство сказало вам, что ваш объект-телефон должен уметь эмулировать дисковый, кнопочный или платный телефон на выбор. Другими словами, для одного звонка объект-телефон мог бы представлять кнопочный аппарат, для другого выступал бы как платный телефон и т.д. Иначе говоря, от одного звонка к другому ваш объект-телефон должен изменять форму.

В этих разных классах телефона существует единственная отличающаяся функция — это метод dial. Для создания полиморфного объекта вы сначала определяете функции базового класса, которые отличаются от функций производных классов тем, что они виртуальные, предваряя их прототипы ключевым словом virtual, как показано ниже:

class phone

{
public:
 virtual void dial(char •number) { cout << "Набор номера " << number << endl; }
 void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
 void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
 phone(char *number) { strcpy(phone::number, number); };
protected:
 char number[13];
};

Далее в программе вы создаете указатель на объект базового класса. Для вашей телефонной программы вы создадите указатель на базовый класс phone:

phone *poly_phone;

Для изменения формы объекта вы просто присваиваете этому указателю адрес объекта производного класса, как показано ниже:

poly_phone = (phone *) &home_phone;

Символы (phone *), которые следуют за оператором присваивания, являются оператором приведения типов, который сообщает компилятору C++, что все в порядке, необходимо присвоить адрес переменной одного типа (touch_tone) указателю на переменную другого типа (phone). Поскольку ваша программа может присваивать указателю объекта poly_phone адреса различных объектов, то этот объект может изменять форму, а следовательно, является полиморфным. Следующая программа POLYMORP.CPP использует этот метод для создания объекта-телефона. После запуска программы объект poly_phone меняет форму с дискового телефона на кнопочный, а затем на платный:

#include <iostream.h>

#include <string.h>

class phone

{
public:
 virtual void dial(char *number) { cout << "Набор номера " << number << endl; }
 void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
phone(char *number) { strcpy(phone::number, number); };
protected:
 char number[13] ;
};

class touch_tone : phone

{
public:
 void dial(char * number) { cout << "Пик пик Набор номера " << number << endl; }
touch_tone(char *number) : phone(number) { }
};

class pay_phone: phone

{
public:
 void dial(char *number) { cout << "Пожалуйста, оплатите " << amount << " центов" << endl; cout << "Набор номера " << number << endl; }
pay_phone(char *number, int amount) : phone(number) { pay_phone::amount = amount; }
private:
 int amount;
};

void main(void)

{
pay_phone
    city_phone("702-555-1212", 25);
touch_tone
    home_phone("555-1212");
phone
    rotary("201-555-1212") ;
//
    Сделать объект дисковым телефоном
phone *poly_phone = &rotary;
poly_phone->dial("818-555-1212");
//
    Заменить форму объекта на кнопочный телефон
poly_phone = (phone *) &home_phone;
poly_phone->dial("303-555-1212");
//
    Заменить форму объекта на платный телефон
poly_phone = (phone *) &city_phone;
poly_phone->dial("212-555-1212");
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С:\> POLYMORP <ENTER>

Набор номера 818-555-1212

Пик пик Набор номера 303-555-1212

Пожалуйста, оплатите 25 центов

Набор номера 212-555-1212

Поскольку объект poly_phone изменяет форму по мере выполнения программы, он является полиморфным.

Полиморфные объекты могут изменять форму во время выполнения программы

Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Для создания полиморфного объекта ваша программа должна использовать указатель на объект базового класса. Далее программа должна присвоить этому указателю адрес объекта производного класса. Каждый раз, когда программа присваивает указателю адрес объекта другого класса, объект этого указателя (который является полиморфным) изменяет форму. Программы строят полиморфные объекты, основываясь на виртуальных функциях базового класса.

ЧТО ТАКОЕ ЧИСТО ВИРТУАЛЬНЫЕ ФУНКЦИИ

Как вы уже знаете, для создания полиморфного объекта ваши программы определяют один или несколько методов базового класса как виртуальные функции. Производный класс может определить свою собственную функцию, которая выполняется вместо виртуальной функции базового класса, или использовать базовую функцию (другими словами, производный класс может и не определять свой собственный метод). В зависимости от программы иногда не имеет смысла определять виртуальную функцию в базовом классе. Например, объекты производных типов могут настолько сильно отличаться, что им не нужно будет использовать метод базового класса. В таких случаях вместо определения операторов для виртуальной функции базового класса ваши программы могут создать чисто виртуальную функцию, которая не содержит операторов.

Для создания чисто виртуальной функции ваша программа указывает прототип функции, но не указывает ее операторы. Вместо них программа присваивает функции значение ноль, как показано ниже:

class phone

{
public:
 virtual void dial (char *number) =0; // Чисто виртуальная функция
void answer(void) { cout << "Ожидание ответа" << endl; }
void hangup(void) { cout << "Звонок выполнен - повесить трубку" << endl; }
void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
phone(char *number) { strcpy(phone::number, number); };
protected:
 char number[13];
};

Каждый производный класс, определенный в вашей программе, должен определить свою функцию вместо чисто виртуальной функции базового класса. Если производный класс опустит определение функции для чисто виртуальной функции, компилятор C++ сообщит о синтаксических ошибках.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Полиморфизм представляет собой способность объекта изменять форму во время выполнения программы. В этом уроке рассмотрены шаги, которые вам необходимо выполнить для создания полиморфных объектов. Из урока 39 вы узнаете, как использовать исключительные ситуации в C++ для обеспечения надежности вашей программы. Прежде чем приступить к уроку 39 убедитесь, что вы освоили следующие основные концепции:

    1. Полиморфный объект может изменять форму во время выполнения программы.
    2. Вы создаете полиморфные объекты, используя классы, порожденные от существующего базового класса.
    3. В базовом для полиморфного объекта классе вы должны определить одну или несколько функций как виртуальные (virtual).
    4. В общем случае полиморфные объекты отличаются использованием виртуальных функций базового класса.
    5. Для создания полиморфного объекта вам необходимо создать указатель на объект базового класса.
    6. Для изменения формы полиморфного объекта вы просто направляете указатель на различные объекты, присваивая новый адрес объекта указателю на полиморфный объект.
    7. Чисто виртуальная функция — это виртуальная функция базового класса, для которой в базовом классе не определены операторы. Вместо них базовый класс присваивает такой функции значение 0.
    8. Производные классы должны обеспечить определение функции для каждой чисто виртуальной функции базового класса.


Урок 39

Использование исключительных ситуаций C++ для обработки ошибок

После того как вы создали и отладили (удалили ошибки) несколько программ, вы уже способны предвидеть ошибки, которые могут встретиться в программе. Например, если ваша программа читает информацию из файла, ей необходимо проверить, существует ли файл и может ли программа его открыть. Аналогично, если ваша программа использует оператор new для выделения памяти, ей необходимо проверить и отреагировать на возможное отсутствие памяти. По мере увеличения размера и сложности ваших программ вы обнаружите, что необходимо включить много таких проверок по всей программе. Из этого урока вы узнаете, как использовать исключительные ситуации C++ для упрощения проверки и обработки ошибок. К концу данного урока вы освоите следующие основные концепции:

  • Исключительная ситуация (exception) представляет собой неожиданное событие — ошибку — в программе.
  • В ваших программах вы определяете исключительные ситуации как классы.
  • Чтобы заставить ваши программы следить за исключительными ситуациями, необходимо использовать оператор C++ try.
  • Для обнаружения определенной исключительной ситуации ваши программы используют оператор C++ catch.
  • Для генерации исключительной ситуации при возникновении ошибки ваши программы используют оператор C++ throw.
  • Если ваша программа обнаруживает исключительную ситуацию, она вызывает специальную (характерную для данной исключительной ситуации) функцию, которая называется обработчиком исключительной ситуации.
  • Некоторые (старые) компиляторы не поддерживают исключительные ситуации C++.

C++ ПРЕДСТАВЛЯЕТ ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ КАК КЛАССЫ

Ваша цель при использовании исключительных ситуаций C++ состоит в упрощении обнаружения и обработки ошибок в программах. В идеале, если ваши программы обнаруживают неожиданную ошибку (исключительную ситуацию), им следует разумным образом ее обработать вместо того, чтобы просто прекратить выполнение.

В программах вы определяете каждую исключительную ситуацию как класс. Например, следующие ситуации определяют три исключительные ситуации для работы с файлами:

class file_open_error
    {};
class file_read_error {};
class file_write_error {};

Позже в этом уроке вы создадите исключительные ситуации, которые используют переменные и функции-элементы класса. А пока просто поверьте, что каждая исключительная ситуация соответствует классу.

КАК ЗАСТАВИТЬ C++ ПРОВЕРЯТЬ ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ

Прежде чем ваши программы могут обнаружить и отреагировать на исключительную ситуацию, вам следует использовать оператор C++ try для разрешения обнаружения исключительной ситуации. Например, следующий оператор try разрешает обнаружение исключительной ситуации для вызова функции file_соpy:

try

{
file_copy("SOURCE.ТХТ", "TARGET.ТХТ") ;
};

Сразу же за оператором try ваша программа должна разместить один или несколько операторов catch, чтобы определить, какая исключительная ситуация имела место (если она вообще была):

try

{
 file_copy("SOURCE.ТХТ", "TARGET.ТХТ") ;
};

catch (file_open_error)

{
cerr
    << "Ошибка открытия исходного или целевого файла" << endl;
exit(1);
}

catch (file_read_error)

{
cerr
    << "Ошибка чтения исходного файла" <<
    endl;
exit(1);
}

catch (file_write_error)

{
cerr
    << "Ошибка записи целевого файла" <<
    endl;
exit(1);
}

Как видите, приведенный код проверяет возникновение исключительных ситуаций работы с файлами, определенных ранее. В данном случае независимо от типа ошибки код просто выводит сообщение и завершает программу. В идеале ваш код мог бы отреагировать и не так — возможно, попытаться исключить причину ошибки и повторить операцию. Если вызов функции прошел успешно и исключительная ситуация не выявлена, C++ просто игнорирует операторы catch.

ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА throw ДЛЯ ГЕНЕРАЦИИ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ

Сам C++ не генерирует исключительные ситуации. Их генерируют ваши программы, используя оператор C++ throw. Например, внутри функции file_copy программа может проверить условие возникновения ошибки и сгенерировать исключительную ситуацию:

void file_copy(char *source, char *target)

{
 char line[256];
 ifstream input_file(source);
 ofstream output_file(target);
 if (input_file.fail())
throw(file_open_error);
 else
if (output_file.fail()) throw(file_open_error);
 else

   {
while ((! input_file.eof()) &&
    (! input_file.fail()))

      {
input_file.getline(line,
    sizeof(line)) ;
if
    (! input_file.fail()) output_file << line << endl;
else
    throw(file_read_error);
if
    (output_file.fail()) throw (file_write_error) ;
}
}
}

Как видите, программа использует оператор throw для генерации определенных исключительных ситуаций.

Как работают исключительные ситуации

Когда вы используете исключительные ситуации, ваша программа проверяет условие возникновения ошибки и, если необходимо, генерирует исключительную ситуацию, используя оператор throw. Когда C++ встречает оператор throw, он активизирует соответствующий обработчик исключительной ситуации (функцию, чьи операторы вы определили в классе исключительной ситуации). После завершения функции обработки исключительной ситуации C++ возвращает управление первому оператору, который следует за оператором try, разрешившим обнаружение исключительной ситуации. Далее, используя операторы catch, ваша программа может определить, какая именно исключительная ситуация возникла, и отреагировать соответствующим образом.

ОПРЕДЕЛЕНИЕ ОБРАБОТЧИКА ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ

Когда ваша программа генерирует исключительную ситуацию, C++ запускает обработчик исключительной ситуации (функцию), чьи операторы вы определили в классе исключительной ситуации. Например, следующий класс исключительной ситуации nuke_meltdown определяет операторы обработчика исключительной ситуации в функции nuke_meltdown:

class nuke_meltdown

{
public:
 nuke_meltdown(void){ cerr << "\а\а\аРаботаю! Работаю! Работаю!" << endl; }
};

В данном случае, когда программа сгенерирует исключительную ситуацию nuke_meltdown, C++ запустит операторы функции nuke_meltdown, прежде чем возвратит управление первому оператору, следующему за оператором try, разрешающему обнаружение исключительной ситуации. Следующая программа MELTDOWN.CPP иллюстрирует использование функции nuke_meltdown. Эта программа использует оператор try для разрешения обнаружения исключительной ситуации. Далее программа вызывает функцию add_u232 c параметром amount. Если значение этого параметра меньше 255, функция выполняется успешно. Если же значение параметра превышает 255, функция генерирует исключительную ситуацию nuke_meltdown:

#include <iostream.h>

class nuke_meltdown

{
public:
 nuke_meltdown(void){ cerr << "\а\а\аРаботаю! Работаю! Работаю!" << endl; }
};

void add_u232(int amount)

{
 if (amount < 255) cout << "Параметр add_u232 в порядке" << endl;
 else throw nuke_meltdown();
}

void main(void)

{
 try

   {
add_u232(255);
}
catch
    (nuke_meltdown)

   {
cerr
    << "Программа устойчива" <<
    endl;
}
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С:\> MELTDOWN <ENTER>

Работаю! Работаю! Работаю!

Программа устойчива

Если вы проследите исходный код, который генерирует каждое из сообщений, то сможете убедиться, что поток управления при возникновении исключительной ситуации проходит в обработчик исключительной ситуации и обратно к оператору catch. Так, первая строка вывода генерируется обработчиком исключительной ситуации, т.е. функцией nuke_meltdown. Вторая строка вывода генерируется в операторе catch, который обнаружил исключительную ситуацию.

Определение обработчика исключительной ситуации

Когда C++ обнаруживает в программе исключительную ситуацию, он запускает специальную функцию, которая называется обработчиком исключительной ситуации. Для определения обработчика исключительной ситуации вы просто создаете функцию в классе исключительной ситуации, которая имеет такое же имя, как и сама исключительная ситуация (подобно конструктору). Когда ваша программа в дальнейшем сгенерирует исключительную ситуацию, C++ автоматически вызовет соответствующий обработчик. В идеале обработчик исключительной ситуации должен выполнить операции, которые бы исправили ошибку, чтобы ваша программа могла повторить операцию, ставшую причиной ошибки. После завершения обработки исключительной ситуации выполнение вашей программы продолжается с первого оператора, следующего за оператором try, разрешившего обнаружение исключительной ситуации.

ИСПОЛЬЗОВАНИЕ ЭЛЕМЕНТОВ ДАННЫХ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ

В предыдущих примерах ваши программы, используя оператор catch, могли определить, какая именно исключительная ситуация имела место, и отреагировать соответствующим образом. В идеале, чем больше информации об исключительной ситуации могут получить ваши программы, тем лучше они смогут отреагировать на ошибку. Например, в случае с исключительной ситуацией file_open_error вашей программе необходимо знать имя файла, который вызвал ошибку. Аналогично, для файловых исключительных ситуаций file_read_error или file_write_error программа, возможно, захочет узнать расположение байта, на котором произошла ошибка. Чтобы сохранить подобную информацию об исключительной ситуации, ваши программы могут просто добавить элементы данных в класс исключительной ситуации. Если в дальнейшем ваша программа сгенерирует исключительную ситуацию, она передаст эту информацию функции обработки исключительной ситуации в качестве параметра, как показано ниже:

throw file_open_error(source);
throw file_read_error(344);

В обработчике исключительной ситуации эти параметры могут быть присвоены соответствующим переменным класса (очень похоже на конструктор). Например, следующие операторы изменяют исключительную ситуацию file_open_error, чтобы присвоить имя файла, который вызвал ошибку, соответствующей переменной класса:

class file_open_error

{
public:
file_open_error(char
    *filename) { strcpy(file_open_error::filename,
    filename); }
char filename[255] ;
};

ОБРАБОТКА НЕОЖИДАННЫХ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ

Из урока 11 вы узнали, что компиляторы C++ предоставляют функции библиотеки этапа выполнения, которые вы можете использовать в своих программах. Если вы читали документацию по этим функциям, то могли обратить внимание на функции, которые генерируют определенные исключительные ситуации. В таких случаях ваши программы должны проверять соответствующие исключительные ситуации. По умолчанию если ваша программа генерирует исключительную ситуацию, которая не улавливается (программа не имеет соответствующего обработчика исключительной ситуации), то запустится стандартный обработчик, предоставляемый языком C++. В большинстве случаев стандартный обработчик завершит вашу программу. Следующая программа UNCAUGHT.CPP иллюстрирует, как стандартный обработчик исключительной ситуации завершает выполнение вашей программы:

#include <iostream.h>

class some_exception { };

void main(void)

{
 cout << "Перед генерацией исключительной ситуации" << endl;
 throw some_exception();
 cout << "Исключительная ситуация сгенерирована" << endl;
}

В данном случае, когда программа генерирует исключительную ситуацию (которая не улавливается программой), C++ вызывает стандартный обработчик исключительной ситуации, который завершает программу. Поэтому последний оператор программы, который выводит сообщение о генерации исключительной ситуации, никогда не выполняется. Вместо использования стандартного обработчика исключительной ситуации C++ ваши программы могут определить свой собственный стандартный обработчик (обработчик по умолчанию). Чтобы сообщить компилятору C++ о своем стандартном обработчике, ваши программы должны использовать функцию библиотеки этапа выполнения set_unexpected. Прототип функции set_unexpected определен в заголовочном файле except.h.

ОБЪЯВЛЕНИЕ ГЕНЕРИРУЕМЫХ ФУНКЦИЕЙ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ

Как вы уже знаете, прототип функции позволяет вам определить тип возвращаемого функцией значения и типы ее параметров. Если ваши программы используют исключительные ситуации, вы также можете использовать прототип функции, чтобы указать генерируемые данной функцией исключительные ситуации. Например, следующий прототип функции сообщает компилятору, что функция power_plant может генерировать исключительные ситуации melt_down и radiation_leak:

void power_plant(long power_needed) throw (melt_down, radiation_leak);

Включая подобным образом возможные исключительные ситуации в прототип функции, вы можете легко сообщить другому программисту, который будет читать ваш код, какие исключительные ситуации ему необходимо проверять при использовании данной функции.

ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ И КЛАССЫ

При создании класса вы, возможно, захотите определить исключительные ситуации, характерные для данного класса. Чтобы создать исключительную ситуацию, характерную для конкретного класса, просто включите эту исключительную ситуацию в качестве одного из общих (public) элементов класса. Например, следующее описание класса string определяет две исключительные ситуации:

class string

{
public:
 string(char *str);
 void fill_string(*str);
 void show_string(void);
 int string_length(void);
 class string_empty { } ;
 class string_overflow {};
private:
 int length;
 char string[255];
};

Как видите, этот класс определяет исключительные ситуации string_empty и string_overflow. В своей программе вы можете проверить наличие исключительной ситуации, используя оператор глобального разрешения и имя класса, как показано ниже:

try

{
 some_string.fill_string(some_long_string);
};
catch (string::string_overflow)

{
 cerr << "Превышена длина строки, символы отброшены" << endl;
}

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Исключительные ситуации предназначены для упрощения и усовершенствования обнаружения и обработки ошибочных ситуаций в ваших программах. Для проверки и обнаружения исключительных ситуаций ваши программы должны использовать операторы try, catch и throw. Ваши знания исключительных ситуаций зависят от опыта программирования на C++. Прежде чем продолжить программировать на C++, убедитесь, что вы освоили следующие основные концепции:

    1. Исключительная ситуация представляет собой неожиданную ошибку в вашей программе.
    2. Ваши программы должны обнаруживать и обрабатывать (реагировать на) исключительные ситуации.
    3. В программах вы определяете исключительную ситуацию как класс.
    4. Вы должны использовать оператор try, чтобы заставить компилятор C++ разрешить обнаружение исключительных ситуаций.
    5. Вы должны разместить оператор catch сразу же после оператора try, чтобы определить, какая именно исключительная ситуация имела место (если она вообще была).
    6. C++ сам не генерирует исключительные ситуации. Ваши программы генерируют исключительные ситуации с помощью оператора throw.
    7. Если ваша программа обнаруживает исключительную ситуацию, она вызывает специальную функцию, которая называется обработчиком исключительной ситуации.
    8. Если ваши программы используют исключительные ситуации, вы можете указать в прототипе функции, что эта функция способна генерировать исключительную ситуацию.
    9. При чтении документации по библиотеке этапа выполнения обращайте внимание на то, что некоторые функции способны генерировать исключительные ситуации.
    10. Если ваша программа генерирует, но не улавливает (и не обрабатывает) исключительную ситуацию, C++ будет вызывать стандартный обработчик исключительной ситуации.
    11. В заголовочном файле except.h определены прототипы функций, которые ваши программы могут использовать для указания своих собственных стандартных обработчиков не улавливаемых исключительных ситуаций.
Предыдущий урок


Untitled Document

Изучение основ

В этой части вы изучите основные понятия, необходимые для создания собственных программ на C++. Если вы никогда не создавали программ, не беспокойтесь, здесь описано все шаг за шагом с самого начала. К тому времени, когда вы освоите несложные уроки, представленные в этой части, вы будите готовы выбрать свой путь в программировании на C++! Уроки этой части:

Урок 1. Создание вашей первой программы.

Урок 2. Более внимательный взгляд на C++.

Урок 3. Вывод сообщений на экран.

Урок 4. Программы хранят информацию в переменных.

Урок 5. Выполнение простых операций.

Урок 6. Чтение ввода с клавиатуры.

Урок 7. Программа принимает решение.

Урок 8. Повторение одного или нескольких операторов.


Packet9_1_check

Создание программ с помощью функций

По мере усложнения ваших программ вы будете делить их на небольшие легко управляемые части, называемые функциями. Каждая функция в вашей программе будет выполнять определенную задачу. Предположим, что вы создаете бухгалтерскую программу, в которой вы могли бы создать одну функцию для обработки дебиторской задолженности, одну для расчета платежей и т. д. Фокусируясь на одной функции в каждый момент времени, ваши программы становятся легче для создания и понимания. Более того, вы обнаружите, что многие функции, созданные для одной программы, могут быть использованы для другой, что сохраняет вам время, затрачиваемое на программирование. Уроки, представленные в этом разделе:

Урок 9. Знакомство с функциями.

Урок 10. Изменение значений параметров.

Урок 11. Преимущества использования библиотеки этапа выполнения.

Урок12. Локальные переменные и область видимости.

Урок 13. Перегрузка функций.

Урок 14. Использование ссылок в C++.

Урок 15. Значения параметров по умолчанию.


Хранение информации с помощью массивов и структур

Как вы уже знаете, переменная может хранить значение определенного типа. В программах на C++, которые вы создали при изучении первых двух частей этой книги, переменные хранили только одно значение. По мере увеличения сложности ваших программ в некоторых ситуациях вам потребуется работать с несколькими значениями одновременно. Например, ваша программа может работать со 100 тестовыми очками, 30 стоимостью акций или фамилиями и адресами 5000 служащих вашей компании. В этой части вы узнаете, как использовать разные типы данных C++ для хранения множества значений внутри одной и той же переменной. Как вы узнаете, использовать одну переменную для хранения множества значений очень удобно.

Урок 16. Хранение значений в массивах.

Урок 17. Символьные строки.

Урок 18. Хранение связанной информации в структурах.

Урок 19. Объединения.

Урок 20. Указатели.


Использование классов C++

Объектно-ориентированное программирование фокусируется на сущностях (объектах), из которых состоит система. Например, у вас может быть объект-файл или объект-служащий и т.д. Каждый объект содержит связанные с ним данные, такие как имя файла или служащего, номер служащего и, возможно, оклад. Таким образом, объекты подобны структурам C++. Однако объекты включают также набор операций, которые ваши программы выполняют над объектами. В случае объекта-файла, вы можете печатать, удалять или даже копировать файл. Аналогично, в случае объекта-служащего вы можете напечатать данные, продвигать по службе или, возможно, даже уволить объект. C++ использует класс для хранения данных объекта и функций, которые оперируют этими данными. Эта часть рассматривает классы C++ более подробно. Уроки данной части:

Урок 21. Знакомство с классами C++.

Урок 22. Частные и общие данные.

Урок 23. Конструктор и деструктор.

Урок 24. Перегрузка операторов.

Урок 25. Статические функции и элементы данных.


Наследование и шаблоны

Главное преимущество объектно-ориентированного программирования заключается в том, что класс, который вы создаете для одной программы, часто может быть использован в другой. Как вы узнаете из этой части, C++ не только позволяет вашим программам повторно использовать классы, но также позволяет строить один класс из другого. Когда вы строите один класс из другого, новый класс наследует характеристики начального класса. В этой части вы освоите, как использовать возможности наследования C++ для экономии значительного объема программирования. К концу данной части вы очень много узнаете о концепциях объектно-ориентированного программирования.

Урок 26. Наследование.

Урок 27. Множественное наследование.

Урок 28. Частные элементы и друзья.

Урок 29. Использование шаблонов функций.

Урок 30. Использование шаблонов классов.


Расширенные возможности C++

В первых пяти частях этой книги были представлены C++ и концепции объектно-ориентированного программирования, которые вам необходимо знать для создания мощных программ. В этой части вы расширите ваши знания о C++ сведениями о распределении памяти во время выполнения программ и знаниями о том, как выполнять файловые операции в C++. Уроки этой части представлены как "Расширенные возможности C++", но не позволяйте им запугать вас. Вы обнаружите, что концепции, представленные в этой части, не сложнее тех, которые вы уже освоили.

Урок 31. Использование свободной памяти в C++.

Урок 32. Управление свободной памятью.

Урок 33. Дополнительные возможности cin и cout.

Урок 34. Файловые операции В/В в C++.

Урок 35. Встроенные функции и ассемблерные коды.

Урок 36. Использование аргументов командной строки.

Урок 37. Использование констант и макрокоманд.

Урок 38. Полиморфизм.


Untitled

Глава I. Исчезающие и иерархические меню.

  • Что такое исчезающие и иерархические меню?
  • Работа видеоадаптеров
  • Доступ к экрану через BIOS
  • Использование int86()
  • Сохранение части экрана
  • Восстановление экрана
  • Создание исчезающих меню
  • Высвечивание меню
  • Высвечивание рамки
  • Ввод выбора пользователя
  • Функция popup()
  • Общий обзор
  • Прямой доступ к видео памяти
  • Определение расположения видео памяти
  • Изменение save_video() и restore_video()
  • Создание иерархических окон
  • Фреймы меню
  • Создание фрейма меню
  • Функция pulldown()
  • Восстановление экрана
  • Простая программа, использующая процедуру pulldown
  • Добавочные функции


Untitled

 Глава II. Программы, остающиеся резидентными
  • Что такое TSR-программа?
  • Прерывания в семействе процессоров 8086
  • Прерывания против DOS и BIOS: Tревога в стране DOS
  • Модификатор функций прерывания Турбо Си
  • Общий план TSR-программы
  • Использование прерывания печати экрана
  • Раздел инициализации
  • Прикладная часть TSR-программы
  • Использование прерывания по нажатию клавиши
  • Буфер символов, введенных с клавиатуры
  • Функция инициализации
  • Прикладная часть TSR-программы
  • Тайна 28-го прерывания
  • Проблемы при создании TSR-программ


Untitled

Глава III. Графика

  • Видеорежимы и цветовая палитра
  • Запись точки растра
  • Работа адаптеров CGA/EGA в графическеом режиме
  • Вычерчивание линий
  • Изображение и закрашивание прямоугольников
  • Вычерчивание окружностей
  • Простейшая тестовая программы
  • Сохранение и загрузка графических изображений
  • Дублирование части экрана
  • Вращение точки в плоскости экрана
  • Вращение обьекта
  • Сборка подпрограмм


Untitled

Глава IV. Видеоигры

  • Спрайты
  • Поле игры
  • Мультипликация на экране
  • Мультипликация спрайта
  • Организация данных в видеоиграх
  • Контроль границ
  • Изменение цвета
  • Табло счета активного противника
  • Разработка видеоигры
  • Тело главной программы
  • Программа генерации движения спрайта компьютера
  • Программа контроля касания спрайтов
  • Полный текст программы игры TAG
  • Некоторые соображения по возможной модификации программы


Untitled

Глава V. Использование последовательного порта

  • Асинхронная последовательная передача данных
  • Стандарт RS-232
  • Аппаратное подтверждение связи
  • Проблемы передачи данных
  • Переполнение регистра-приемника
  • Доступ к последовательному порту компьютера через BIOS
  • Инициализация порта
  • Передача байтов
  • Контроль состояния порта
  • Прием байтов
  • Передача файлов между компьютерами
  • Программное подтверждение связи
  • Семь и восемь бит данных
  • Перекачка файла
  • Прием файла
  • Перекачка программы
  • Использование средств перекачки программ
  • Дальнейшее совершенствование программы
  • Простейшая ЛВС
  • Файловый сервер
  • Загрузка удаленных файлов в узел сети
  • Хранение файлов
  • Использование ЛВС
  • Совершенствование ЛВС


Untitled

Глава VI. Интерпретаторы языка

  • Синтаксический разбор выражений
  • Выражения
  • Лексемы
  • Порядок построения выражений
  • Синтаксический анализатор выражений
  • Как анализатор обрабатывает переменные
  • Интерпретатор языка SMALL BASIC
  • Основной цикл работы анализатора
  • Команда присваивания значений


Untitled

Глава VII. О манипулировании экраном и генерации звука

  • Использование цвета в текстовом режиме
  • Атрибутный байт текстового режима
  • Отображение строки в определенном цвете
  • Использование цвета
  • Изменение размера курсора
  • Скроллинг части экрана
  • Простейшая демонстрационная программа
  • Сохранение копии экрана в дисковом файле
  • А теперь добавим звук
  • Программируемый таймер 8253
  • Простейший способ проверки слуха
  • Имитация звука сирены и взврывы
  • Создание "космической музыки"


Untitled

Глава VIII. Интерфейс с "мышью"

  • Некоторые начальные сведения о "мыши"
  • Виртуализация и реальный экран
  • Библиотека поддержки "мыши"
  • Функции поддержки "мыши" верхнего уровня
  • Простейшая демонстрационная программа
  • Ввод информации с помощью "мыши" в программе рисования


Untitled

Глава IX. Создание коммерческих диаграмм

  • Нормализация данных
  • Разработка функций построения диаграмм
  • Программа вычерчивания диаграмм
  • Отображение диаграмм на экране дисплея
  • Некоторые интересные идеи по модификации программ


Untitled

Предисловие

Если вы хотите создавать программы мирового уровня, написанные на Си, то эта книга - для вас!

Она открывает многие секреты, используемые мастерами программирования для достижения профессиональных результатов. С ее помощью вы расширите подходы и методы, которые делают программы интересными. После прочтения книги вы будете способны писать программы, которые заслужат внимание. Здесь рассматриваются следующие вопросы:

# Прямой доступ к памяти экрана для быстрого отображения

# Исчезающие и иерархические (popup и pulldown) меню

# Процедуры работы с окнами

# Завершение программ и оставление их в памяти

# Интерфейс с мышью

# Графические функции, включая вращение объектов

# Языковые интерпретаторы

# Передача файлов через последовательный порт

Эта книга для любого и каждого программиста на Си, от новичка до профессионала. Даже если вы начинающий, вы можете использовать функции и программы из этой книги без понимания отдельных деталей их работы. Более подготовленные читатели могут использовать эти программы как основу для своих приложений.

Исходные тексты этой книги соответствуют стандарту ANSI, кроме некоторых функций, специфичных для ПК. Таким образом все эти программы можно компилировать на любом компиляторе, который поддерживает стандарт. Автор использовал для их разработки Турбо Си и Microsoft Си.


Untitled

Содержание

    Предисловие
  • Исчезающие и иерархические меню.
  • Программы, остающиеся резидентными
  • Графика
  • Видеоигры
  • Использование последовательного порта
  • Интерпретаторы языка
  • О манипулировании экраном и генерации звука
  • Интерфейс с "мышью"
  • Создание коммерческих диаграмм


Глава 1. Исчезающие и иерархические меню.

Одна из наиболее очевидных черт профессионально написанных программ - это использование исчезающих и иерархических меню. При правильном использовании, эти меню дают программам дружелюбие, которое пользователи от них и ожидают. Хотя по существу и простые, и исчезающие, и иерархические меню представляют некоторые трудности в программировании.

Создание исчезающих и иерархических меню требует прямого управления экраном. Хотя основные программы меню полностью мобильны, программы доступа к экрану зависят от операционной системы и оборудования и не используют обычные функции Си ввода/вывода на консоль. Программы видео доступа разработаны для работы с любым компьютером, использующим ДОС и имеющим BIOS операционной системы совместимый с IBM. BIOS-ДОС выбран потому что он широко используется, но вы можете применить основные идеи и в других системах.

Даже если вас сейчас не интересуют исчезающие и иерархические меню, то вам следует прочитать часть этой главы, в которой обсуждаются видеоадаптеры, знание многих основных идей необходимо для понимания последующих глав.

Что такое исчезающие и иерархические меню?

Важно понимать что такое исчезающие и иерархические меню и чем они отличаются от стандартных меню. При использовании стандартных меню экран очищается или сдвигается, и появляется меню. Когда выбор сделан, экран опять очищается или сдвигается и программа продолжается. Выбор выполняется по номеру или по первой букве каждой альтернативы.

Когда используется исчезающее или иерархическое меню, то оно покрывает прямо содержимое экрана. После выбора режима, экран возвращается в предыдущее состояние. Вы выбираете нужный режим из меню одним из двух способов: (1) нажимая активную клавишу, которая является буквой или номером, связанным с выбором, или (2) используя клавиши управления курсором для передвижения подсвеченного поля и клавишу Ввод. Обычно текущее поле показывается в инверсном виде. Основная разница между стандартными меню и исчезающими и иерархическими меню в том, что стандартное меню прерывает программу. Исчезающие и иерархические меню только приостанавливают текущие действия программы. С точки зрения пользователя стандартное меню - прерывание концентрации, тогда как исчезающее меню - просто легкая приостановка, концентрация внимания пользователя не нарушена.

Разница между исчезающими и иерархическими меню проста. Только одно исчезающее меню может быть на экране в данный момент времени. Оно используется когда меню имеет только один уровень в глубину, это бывает, когда выбор из меню не имеет подвыборов. С другой стороны несколько иерархических меню могут быть активны одновременно. Они используются когда выбор из одного меню может потребовать использования другого меню для определения некоторых альтернатив. Например, вы можете использовать иерархическое меню, если вы пишете программу, которая определяет фрукт. Если пользователь выбрал "яблоко", следующее меню предлагает выбрать цвет яблока, а третье меню высвечивает яблоки, которые удоволетворяют предыдущим выборам.

Вы можете представлять исчезающее меню просто как иерархическое меню, которое не имеет подменю, но разработка отдельных процедур для этих типов меню имеет то преимущество, что иерархическое меню требует значительно более сложной программы, чем простое исчезающее меню.

Хотя имеется много способов расположения меню на экране, функции, разработанные в этой главе имеют наиболее общий вид. Этот метод помещает очередное поле меню на новую строку под первым полем.

Работа видеоадаптеров.

Из-за того, что создание исчезающих и иерархических меню требует прямого управления экраном, важно понимание адаптеров дисплея. Три основных типа адаптеров - это одноцветный адаптер, цветной/графический адаптер (CGA) и усовершенствованный графический адаптер (EGA). CGA и EGA могут иметь несколько режимов работы, включая 40- или 80- символьный текст или графические операции. Эти режимы показаны в таблице 1-1. Программы меню, разработанные в этой главе, разработаны для использования режима 80-символьного текста, который является наиболее общим режимом для общецелевых применений. Это значит, что видео режим системы должен быть 2, 3 или 7. Независимо от используемого режима - координаты левого верхнего угла - 0,0.

Таблица 1-1.
Режим | Тип | Размеры | Адаптеры
0 1 2 3 4 5 6 7 8 9 10 13 14 15 ------- | текст,ч/б текст 16 цветов текст ч/б текст 16 цветов графика 4 цвета графика 4 серых тона графика ч/б текст ч/б графика 16 цветов графика 16 цветов графика 4 или 16 цв. графика 16 цветов графика 16 цветов графика 4 цвета ---------------------- | 40*25 40*25 80*25 80*25 320*200 320*200 640*200 80*25 160*200 320*200 640*200 320*200 640*200 640*350 ------------- | CGA,EGA CGA,EGA CGA,EGA CGA,EGA CGA,EGA CGA,EGA CGA,EGA монохромный PCjr PCjr PCjr,EGA EGA EGA EGA ------

Символы, выводимые на экран, содержатся в некоторой зарезервированной области памяти на адаптере дисплея. Адрес одноцветной информации В0000000H. И CGA, и EGA хранят информацию, начиная с B80000000H. (Они различны для того, чтобы позволить использовать раздельно текстовый и графический экран - но на практике это делается редко.) Хотя функции CGA и EGA различны в разных режимах, они одинаковы в режимах 2 и 3.

Каждый символ, выводимый на экран, требует два байта видео памяти. Первый байт содержит собственно символ, второй содержит аттрибуты экрана. Для цветного экрана байт аттрибутов интерпретируется так, как показано в таблице 1-2. Если у вас EGA или CGA, то по умолчанию принимается режим 3, и символы выводятся с байтом аттрибутов 7. Это значение включает три основных цвета, производя для символа белый цвет. Для переключения в инверсный

режим небходимо выключить три основных бита и включить три

фоновых бита, что дает значение 70H.

Одноцветный адаптер распознает биты мигания и интенсивности. К счастью, он разработан так, что интерпретирует аттрибут 7, как нормальный текст (белое на черном) и 70Н как инверсное видео. Кстати, значение 1 дает подчеркнутые символы.

Таблица 1-2

Байт видеоадаптера

Бит Двоичная величина Значение при установке

0

1

2

3

4

5

6

7

1

2

4

8

16

32

64

128

голубой основной

зеленый основной

красный основной

малая интенсивность

голубой фоновый

зеленый фоновый

красный фоновый

мигающий символ

Каждый адаптер имеет по крайней мере в 4 раза больше памяти, чем необходимо для вывода текста в 80-символьном режиме. Для этого есть две причины. Во-первых, лишняя память нужна для графики, (конечно кроме одноцветного адаптера). Во-вторых это позволяет держать в памяти несколько экранов и потом просто переключаться между ними по мере необходимости. Каждая область памяти называется видеостраницей и эффект от переключения между видеостраницами впечатляющ. По умолчанию при инициализации ДОС используется страница 0, и виртуально все приложения используют страницу 0. По этой причине она используется и в этой главе. Однако, вы можете использовать и другие страницы, если захотите.

Имеется три способа доступа к видеоадаптеру. Первый это через прерывание ДОС, которое достаточно медленно для исчезающего меню. Второй - это через процедуры BIOS, которые быстрее, и на быстродействующих машинах, таких, как AT или PS/2 достаточно быстры, если меню невелики. Третий способ - это чтение и запись прямо в видеопамять, что происходит очень быстро, но требует большей работы от вас. Эта глава рассматривает два разных подхода в видео процедурах. Один использует BIOS, а другой прямой доступ к видеопамяти.

Доступ к экрану через BIOS

Из-за того, что исчезающие и иерархические меню должны сохранять информацию с того места экрана, на котором они расположены, и восстанавливать его после выбора, вы должны иметь процедуры, которые сохраняют и загружают часть экрана. Метод сохранения и восстановления части экрана, рассматриваемый в этом разделе связан с вызовами двух встроенных в BIOS функций, которые читают и записывают символы на экран.

Как вы знаете, вызовы BIOS могут быть очень медленными. Однако, они (более или менее) гарантируют работу на любом компьютере, который имеет BIOS, совместимый с IBM, даже если аппаратура экрана другая. Позже в этой главе вы узнаете, как выполнять прямой доступ к видеопамяти на IBM PC и 100% совместимых машинах для того, чтобы увеличить скорость выполнения. Однако, использование прямого доступа к видеопамяти снижает в некоторой степени переносимость, так как требуется 100% совместимость компьютера с IBM PC. Программы меню, основанные на BIOS следует использовать в применениях, которые требуют большей мобильности.

Использование int86()

Вызовы BIOS используют программные прерывания. BIOS имеет несколько различных прерываний для разных целей. Одно из них мы будем использовать для доступа к экрану. Это прерывание 16 (10Н), которое используется для доступа к дисплею. (Если вы не знакомы с доступом к BIOS, то вы найдете хорошее описание в моей книге "Си: Полный справочник", Беркли, 1987). Как и многие прерывания BIOS, прерывание 16 имеет несколько режимов, выбор которых выполняется по значению регистра AH. Если функция возвращает значение, то оно заносится в регистр AL. Однако, иногда для возвращения нескольких значений используются другие регистры. Для доступа к прерываниям BIOS вам придется использовать функцию Си int86(). (Некоторые компиляторы могут называть эту функцию другим именем, но MicroSoft C и Турбо Си называют ее int86(). Последующие рассуждения ориентированы на эти трансляторы, но вы можете их обобщить.

Функция int86() имеет следующую форму:

int int86(num,inregs,outregs)

int num; /* номер прерывания */

union REGS *inregs; /* входные значения регистров */

union REGS *outregs; /* выходные значения регистров */

Функция int86() возвращает значение регистра АХ. Тип REGS описывается в заголовке DOS.H. Этот тип показан здесь так, как он определен в Турбо Си, однако, он аналогично определен в MisroSoft C и в других компиляторах.

struct WORDREGS

unsigned int ax, bx, cx, dx, si, di, cflag, flags;

;

struct BYTEREGS

unsigned char al, ah, bl, bh, cl, ch, dl, dh;

;

union REGS

struct WORDREGS x;

struct BYTEREGS h;

;

Как вы можете видеть, REGS - это объединение двух структур. Использование структуры WORDREGS позволяет рассматривать регистры ЦП как 16-битные числа. BYTREGS дает вам доступ к отдельным 8- битным регистрам. Например, для доступа к прерыванию 16, функции 5, вы должны использовать следующую последовательность.

union REGS in,out;

in.h.ah=5;

int86(16,&in,&out);

Сохранение части экрана.

Для сохранения содержимого экрана, должно быть прочитано и запомнено текущее значение каждой позиции экрана. Для считывания символа с определенной позиции экрана, используется прерывание 16, функция 8, которая возвращает символ и связанный с ним аттрибут текущей позиции курсора. Для считывания символа с определенного места экрана, вы должны иметь способ установки курсора. Хотя некоторые компиляторы Си поддерживают эту функцию, многие ее не имеют. Тем не менее показанная ниже функция goto_xy() может быть использована. Она использует прерывание 16, функцию 2 с координатой столбца в DL и координатой ряда в DH. Видеостраница задается в ВН (используется страница 0 по умолчанию).

/* установка курсора в x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah=2; /* функция установки курсора */

r.h.dl=y; /* координата колонки */

r.h.dh=x; /* координата строки */

r.h.bh=0; /* видео страница */

int86(0x10,&r,&r);

Прерывание 16, функция 8 возвращает символ из текущей позиции курсора в AL и его атрибут в AH. Функция save_video(), показанная здесь, считывает часть экрана, сохраняет информацию в буфер, и очищает эту часть экрана.

/* сохранение части экрана */

void save_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

union REGS r;

register int i,j;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

goto_xy(j,i);

r.h.ah=8; /* функция чтения символа */

r.h.bh=0; /* видео страница */

*buf_ptr++ = int86(0x10,&r,&r);

putchar(' '); /* очистка экрана */

Первые четыре параметра save_video определяют координаты

верхнего левого и правого нижнего угла сохраняемой области.

Параметр buf_ptr это целый указатель на область памяти, которая содержит информацию. Она должна быть достаточно большой, чтобы разместить всю информацию, считанную с экрана.

Программы в этой главе размещают буфер динамически, но вы можете использовать любую другую схему, если это важно для ваших приложений. Не забудьте, однако, что буфер должен существовать, до тех пор, пока экран не вернется в исходное состояние. Эта функция также чистит область, записывая пробел в каждой позиции.

Восстановление экрана

Восстановление экрана после сделанного выбора из меню, заключается просто в записи предварительно запомненной информации назад в видео память. Для того, чтобы сделать это, используйте прерывание 16, функцию 9, которая требует, чтобы символ был в AL, аттрибут в BL, видео страница в ВН, а количество записываемых символов в CX (в нашем случае 1). Функция restore_video(), описанная здесь, помещает информацию из буфера, на который указывает buf_ptr, на экран, заданный начальными и конечными координатами X и Y.

/* восстановление части экрана */

void restore_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

union REGS r;

register int i,j;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

goto_xy(j,i);

r.h.ah=9; /* функция записи символа */

r.h.bh=0; /* видео страница */

r.x.cx=1; /* число повторений символа */

r.h.al=*buf_ptr++; /* символ */

r.h.bl=*buf_ptr++; /* атрибут */

*buf_ptr++ = int86(0x10,&r,&r);

Создание исчезающих меню

Функции, создающей исчезающее меню, должна быть передана некоторая информация. Во-первых, это список предоставляемых меню режимов. Поскольку в меню передаются высвечиваемые строки, то простейший путь передачи списка строк в функцию - помещение их в двумерный массив и передача указателя на массив. Как утверждалось ранее, значение меню может быть выбрано либо передвижением освещенной области на нужное поле и нажатием ВК или нажатием клавиши, указывающей на это поле. Для того, чтобы функция знала, какие клавиши "горячие" и что они обозначают, ей должны быть переданы их имена. Лучший путь для этого - передать строку, которая содержит символы "горячих" клавиш в том же порядке, что и строки меню.

Функция popup() должна также знать как много режимов в меню, и поэтому это число должно быть передано ей. Она должна также знать где расположить меню, то есть нужны координаты X и Y. Наконец, в некоторых ситуациях может быть желательным помещать меню в рамку, а в других - нет. Поэтому должно быть передано значение рамка включена/выключена. Для того, чтобы начать разработку функции popup() нам нужно описание :

/* высветить исчезающее меню и возвратить выбор */

int popup(menu,keys,count,x,y,border)

char *menu[]; /* текст меню */

char *keys; /* горячие клавиши */

int count; /* число режимов */

int x,y; /* координаты левого верхнего угла */

int border; /* если 0 то без рамки */

Функция popup() делает следующее :

# Сохраняет область экрана под меню

# Высвечивает рамку, если надо

# Высвечивает меню

# Получает ответ пользователя

# Восстанавливает экран в исходное состояние

Две из этих целей были достигнуты в save_video() и restore_video(), описанных в предыдущем разделе. Давайте рассмотрим как достигнуть три оставшиеся.

Высвечивание меню.

Для того, чтобы высветить меню, необходимо помнить, что popup получает указатель на массив указателей на строки. Для высвечивания отдельных строк вы просто индексируете указатель, как массив. Каждый элемент в массиве является указателем на соответствующий элемент меню. Следующая функция display_menu() высвечивает каждый элемент меню.

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count)

char *menu[];

int x,y,count;

register int i;

for(i=0;i<count;i++,x++)

goto_xy(x,y);

printf(menu[i]);

Как вы можете видеть, эта функция получает указатель на массив строк, которые надо вывести, координаты начала меню и количество строк в меню.

В дальнейшем простейший способ создать двумерный массив символов, который содержит строки меню - это создание переменной в общем виде:

char *<имя меню> [] =

"первая строка",

"вторая строка",

.

.

.

"N-ая строка" ;

Это описание автоматически заставляет компилятор Си поместить строки в таблицу строк. Переменная указывает на первый символ первой строки в таблице. Например, это описание создает переменную fruit (фрукт), которая указывает на "Я" в "Яблоко".

char *fruit[] =

"Яблоко",

"Апельсин",

"Груша",

"Грейпфрут",

"Малина",

"Клубника"

Высвечивание рамки

Если нужна рамка, то можно воспользоваться нижеприведенной программой для вывода рамки вокруг меню с заданными координатами левого верхнего и правого нижнего углов. Она использует символы, которые являются частью стандартного набора символов на машинах, совместимых с IBM. Если вы хотите, вы можете выбрать другие.

void draw_border(startx,starty,endx,endy)

int startx,starty,endx,endy;

register int i;

for(i=startx+1;i<endx;i++)

goto_xy(i,starty);

putchar(179);

goto_xy(i,endy);

putchar(179);

for(i=starty+1;i<endy;i++)

goto_xy(startx,i);

putchar(196);

goto_xy(endx,i);

putchar(196);

goto_xy(startx,starty); putchar(218);

goto_xy(startx,endy ); putchar(191);

goto_xy(endx ,starty); putchar(192);

goto_xy(endx ,endy ); putchar(217);

Ввод выбора пользователя

Как утверждалось, пользователь может вводить выбор одним из двух способов. Во-первых с помощью клавиш СТРЕЛКА ВНИЗ и СТРЕЛКА ВВЕРХ может переместить освещение на строку и нажать Ввод для ее выбора, (Обычно освещение строки выполняется в инверсном режиме.) Освещенную строку также можно передвигать пробелом. Второй способ это нажатие клавиши, связанной с выбором. Функция get_resp(), показанная здесь, достигает этих целей.

/* ввести выбор пользователя */

get_resp(x,y,count,menu,keys)

int x,y,count;

char *menu[];

char *keys;

union inkey

char ch[2];

int i;

c;

int arrow_choice=0,key_choice;

y++;

/* осветить первый выбор */

goto_xy(x,y);

write_video(x,y,menu[0],REV_VID);

for(;;)

while(!bioskey(1)); /* ждать нажатия */

c.i=bioskey(0);

/* вернуть выбор в номальный режим */

goto_hy(arrow_choice,y);

write_video(x+arrow_choice,y,

menu[arrow_choice],norm_vid);

if(c.ch[0]) /* обычная клавиша */

key_choice= is_in(keys,tоlower(c.ch[0]));

if(key_choice) return key_choice-1;

switch(c.ch[0])

case '\r' : return arrow_choice;

case ' ' : arrow_choice++;

break;

case ESC : return -1; /* выйти */

else /* специальная клавиша */

switch(c.ch[1])

case 72 : arrow_choice--; /* стрелка вниз */

break;

case 80 : arrow_choice++; /* стрелка вверх */

break;

if(arrow_choice==count) arrow_choice=0;

if(arrow_choice<0) arrow_choice=count-1;

/* подсветить выбранную опцию */

goto_xy(x+arrow_choice,y);

write_video(x+arrow_choice,y,menu[arrow_choice],REV_VID);

Kогда get_resp() начинает выполняться, освещается первое значение меню. Макроопределение REV_VID определяется везде, как 70Н, а NORM_VID как 7Н. Клавиша ESCAPE используется для окончания работы с меню. Значение ESC 27. После этого программа входит в цикл, ожидающий действий пользователя. Она использует функцию bioskey() для того, чтобы дождаться нажатия клавиши, а затем для считывания с этой клавиши. Функция bioskey() специфична для Турбо Си. Если вы используете другой транслятор, вы можете использовать следующую версию функции.

/* эмуляция части функции bioskey Турбо Си */

bioskey(c)

int c;

switch(c)

case 0: return get_key();

case 1: return kbhit();

/* чтение 16 битного скан кода клавиши */

get_key()

union REGS r;

r.h.ah=0;

return int86(0x16, &r,&r);

Причина, по которой вы должны использовать bioskey() вместо getchar() состоит в том, что программа должна иметь возможность считывать полный 16 битный скан код, генерируемый нажатием клавиши. Если нажата символьная клавиша, то символ помещается в младшие 8 бит, а старшие 8 бит равны 0. Однако, если нажата специальная клавиша, такая, как стрелка, младший байт равен 0, а старший содержит код позиции клавиши. Коды позиции для стрелки вверх и стрелки вниз равны 72 и 80 соответственно. Такие функции, как getchar(), возвращают только код символа, и поэтому необходимо получить прямо скан код помимо них.

Каждый раз при нажатии стрелки освещенная опция переходит в нормальное изображение, а следующая - освещается. Нажатие стрелки вниз, в момент освещения крайней нижней позиции, означает возвращение к первому значению. То же самое, но наоборот происходит когда нажимается стрелка вверх при освещенной первой

строке.

Функция write_video() используется функцией get_resp() для записи строки на дисплей в позиции Х,Y с определенным атрибутом. Write_video(), показанная здесь, используется для вывода строки меню в инверсном режиме, если она освещена, или в нормальном, если она не освещена.

/* вывод строки с определенным атрибутом */

void write_video(x,y,p,attrib)

int x,y;

char *p;

int attrib;

union REGS r;

register int i,j;

for(i=y; *p; i++)

goto_xy(x,i);

r.h.ah=9; /* функция записи символа */

r.h.bh=0; /* видео страница */

r.x.cx=1; /* число повторений символа */

r.h.al=*p++; /* символ */

r.h.bl=attrib; /* атрибут */

int86(0x10,&r,&r);

Функция is_in() возвращает позицию "горячей" клавиши в строке. Если пользователь нажал не ключевую клавишу, то is_in возвращает 0.

is_in(s,c)

char *s,c;

register int i;

for(i=0; *s; i++)

if(*s++ == c) return i+1;

return 0;

Функция popup()

А теперь, когда все части созданы, функция popup может быть записана, как это показано здесь.

/* вывести исчезающее меню и вернуть выбор

возвращает -2, если меню не может быть создано

возвращает -1, если пользователь нажал клавишу ESC

в остальных случаях она возвращает номер выбранной

альтернативы, начиная с 0 */

int popup(menu,keys,count,x,y,border)

char *menu[]; /* текст меню */

char *keys; /* горячие клавиши */

int count; /* число альтернатив */

int x,y; /* координаты левого верхнего угла */

int border; /* если 0 то без рамки */

register int i,len;

int endx endy choice;

unsigned int *p;

if((x>24)||(x<0)||(y>79)||(y<0))

printf(" выход за пределы экрана");

return -2;

/* вычисление размеров */

len=0;

for(i=0;i<count;i++)

if(strlen(menu[i]) > len) len=strlen(menu[i]);

endy=len+2+y;

endx=count+1+x;

if((endx+1>24) || (endy+1>79))

printf(" выход за пределы экрана");

return -2;

/* размещение памяти для видео буфера */

p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* сохранение части экрана */

void save_video(startx,endx,starty,endy,p);

if(border) draw_border(x,y,endx,endy);

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count);

/* ввести выбор пользователя */

choice=get_resp(x,y,count,menu,keys)

/* восстановление части экрана */

void restore_video(startx,endx,starty,endy,p);

free(p);

return choice;

Как вы можете видеть, popup() проверяет выход за пределы экрана и слишком большой размер меню. Она возвращает -2, если возникла одна из этих ситуаций. Из-за того, что get_resp() возвращает -1, при нажатии клавиши ESC, возвращение этого значения функцией popup следует рассматривать как "уничтожение" меню. Если пользователь сделал выбор, то возвращаемое значение будет в пределах от 0 до count-1 с соответствием первому значению меню 0. Как уже указывалось, popup() использует динамическое размещение памяти для обеспечения временной памяти для информации об экране. Обычно это лучший подход, но вы можете свободно изменить его, если это важно для вашего приложения.

Общий обзор

Простая программа, показанная здесь, использует все программы, разработанные для использования исчезающих меню. Вы не видели только функции cls(), которая очищает экран. Некоторые трансляторы Си не имеют функции для этого, и если это так, то вы не можете использовать следующую программу (в чистом виде).

/* процедура исчезающего меню для работы в текстовом режиме */

#include "stdio.h"

#include "dos.h"

#include "stdlib.h"

#define BORDER 1

#define ESC 27

#define REV_VID 0x70

#define NORM_VID 7

void save_video(),restore_video();

void goto_xy(),cls(),write_video();

void display_menu(),draw_border();

char *fruit[] =

"Яблоко",

"Апельсин",

"Груша",

"Грейпфрут",

"Малина",

"Клубника"

;

char *color[]=

"Красный",

"Желтый",

"Оранжевый",

"Зеленый"

;

char *apple_type[] =

"Красный деликатес",

"Джонатан",

"Белый налив",

"Антоновка"

;

main()

int i;

cls();

goto_xy(0,0);

for(i=0;i<25;i++)

printf("Это тест исчезающего меню\n");

popup(fruit,"яагрмк",6,1,3,BORDER);

popup(color,"кжоз",4,5,10,BORDER);

popup(apple_type,"кдба",4,10,18,BORDER);

/* вывести исчезающее меню и вернуть выбор

возвращает -2, если меню не может быть создано

возвращает -1, если пользователь нажал клавишу ESC

в остальных случаях она возвращает номер выбранной

альтернативы, начиная с 0 */

int popup(menu,keys,count,x,y,border)

char *menu[]; /* текст меню */

char *keys; /* горячие клавиши */

int count; /* число альтернатив */

int x,y; /* координаты левого верхнего угла */

int border; /* если 0 то без рамки */

register int i,len;

int endx endy choice;

unsigned char *p;

if((x>24)||(x<0)||(y>79)||(y<0))

printf(" выход за пределы экрана");

return -2;

/* вычисление размеров */

len=0;

for(i=0;i<count;i++)

if(strlen(menu[i]) > len) len=strlen(menu[i]);

endy=len+2+y;

endx=count+1+x;

if((endx+1>24) || (endy+1>79))

printf(" выход за пределы экрана");

return -2;

/* размещение памяти для видео буфера */

p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* сохранение части экрана */

void save_video(x,endx+1,y,endy+1,p);

if(border) draw_border(x,y,endx,endy);

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count);

/* ввести выбор пользователя */

choice=get_resp(x,y,count,menu,keys)

/* восстановление части экрана */

void restore_video(x,endx+1,y,endy+1,p);

free(p);

return choice;

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count)

char *menu[];

int x,y,count;

register int i;

for(i=0;i<count;i++,x++)

goto_xy(x,y);

printf(menu[i]);

void draw_border(startx,starty,endx,endy)

int startx,starty,endx,endy;

register int i;

for(i=startx+1;i<endx;i++)

goto_xy(i,starty);

putchar(179);

goto_xy(i,endy);

putchar(179);

for(i=starty+1;i<endy;i++)

goto_xy(startx,i);

putchar(196);

goto_xy(endx,i);

putchar(196);

goto_xy(startx,starty); putchar(218);

goto_xy(startx,endy ); putchar(191);

goto_xy(endx ,starty); putchar(192);

goto_xy(endx ,endy ); putchar(217);

/* ввести выбор пользователя */

get_resp(x,y,count,menu,keys)

int x,y,count;

char *menu[];

char *keys;

union inkey

char ch[2];

int i;

c;

int arrow_choice=0,key_choice;

y++;

/* осветить первый выбор */

goto_xy(x,y);

write_video(x,y,menu[0],REV_VID);

for(;;)

while(!bioskey(1)); /* ждать нажатия */

c.i=bioskey(0);

/* вернуть выбор в номальный режим */

goto_xy(arrow_choice,y);

write_video(x+arrow_choice,y,

menu[arrow_choice],norm_vid);

if(c.ch[0]) /* обычная клавиша */

key_choice= is_in(keys,tоlower(c.ch[0]));

if(key_choice) return key_choice-1;

switch(c.ch[0])

case '\r' : return arrow_choice;

case ' ' : arrow_choice++;

break;

case ESC : return -1; /* выйти */

else /* специальная клавиша */

switch(c.ch[1])

case 72 : arrow_choice--; /* стрелка вниз */

break;

case 80 : arrow_choice++; /* стрелка вверх */

break;

if(arrow_choice==count) arrow_choice=0;

if(arrow_choice<0) arrow_choice=count-1;

/* подсветить выбранную опцию */

goto_xy(x+arrow_choice,y);

write_video(x+arrow_choice,y,menu[arrow_choice],REV_VID);

/* вывод строки с определенным атрибутом */

void write_video(x,y,p,attrib)

int x,y;

char *p;

int attrib;

union REGS r;

register int i,j;

for(i=y; *p; i++)

goto_xy(x,i);

r.h.ah=9; /* функция записи символа */

r.h.bh=0; /* видео страница */

r.x.cx=1; /* число повторений символа */

r.h.al=*p++; /* символ */

r.h.bl=attrib; /* атрибут */

int86(0x10,&r,&r);

/* сохранение части экрана */

void save_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

union REGS r;

register int i,j;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

goto_xy(j,i);

r.h.ah=8; /* функция чтения символа */

r.h.bh=0; /* видео страница */

*buf_ptr++ = int86(0x10,&r,&r);

putchar(' '); /* очистка экрана */

/* восстановление части экрана */

void restore_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

union REGS r;

register int i,j;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

goto_xy(j,i);

r.h.ah=9; /* функция записи символа */

r.h.bh=0; /* видео страница */

r.x.cx=1; /* число повторений символа */

r.h.al=*buf_ptr++; /* символ */

r.h.bl=*buf_ptr++; /* атрибут */

int86(0x10,&r,&r);

/* очистка экрана */

void cls()

union REGS r;

r.h.ah=6; /* код прокрутки экрана */

r.h.al=0; /* код очистки экрана */

r.h.ch=0; /* начальная строка */

r.h.cl=0; /* начальная колонка */

r.h.dh=24; /* конечная строка */

r.h.dl=79; /* конечная колонка */

r.h.bh=7; /* очистка пустой строки */

int86(0x10,&r,&r);

Вводите эту программу в ваш компьютер и запускаете ее. В ходе ее выполнения каждое меню будет высвечено и исчезнет. (В этой программе все ответы теряются, но реальное применение будет, конечно, их обрабатывать.) Даже если ваш компьютер очень быстрый, вы возможно заметите, что исчезновение и появление меню требуют определенной задержки. Единственный путь решения этой проблемы - читать и писать символы прямо в видео память, что и обсуждается в следующем разделе. Еще раз отметим, что единственное важное достоинство использование BIOS в том, что такие меню работают на любом компьютере, который поддерживает BIOS, совместимый с IBM, даже если компьютер не 100% совместимый.

Прямой доступ к видео памяти

Для создания меню, которые действительно "исчезают" вы должны миновать вызовы функций BIOS и прямо обращаться к видео памяти. Это позволяет высвечивать символы с молниеносной быстротой. При прямой записи и чтении из видео памяти вы можете использовать исчезающие меню в реальном времени!

Чтение и запись в видео память требует использования ДАЛЬНИХ указателей. Если ваш компилятор не поддерживает дальних указателей, то вы не имеете прямого доступа к видео памяти. Дальние указатели могут быть поддерживаемы транслятором Си одним из двух способов. Первый - использование ключевого слова far, используемого в большинстве компиляторов. Они позволяют определять указатель, как дальний. Другой способ - использование большой модели памяти, в которой все указатели по умолчанию дальние. Программы, используемые в этой главе используют описатель far. Если вы хотите, вы можете просто удалить его и скомпилировать программу, используя транслятор с большой моделью памяти.

Определение расположения видео памяти

Одноцветный адаптер использует для видео памяти адрес B0000000H, a все остальные - В8000000Н. Для того, чтобы программы с меню работали правильно с каждым адаптером, они должны знать, какой адаптер имеет система. К счастью, для этого существует простой способ. Прерывание BIOS 16, функция 15 возвращает текущий видео режим. Как упоминалось раньше, программы, разработанные в этой главе, требуют режима 2, 3 или 7. Адаптеры CGA и EGA могут использовать режим 2 и 3, но не режим 7. Только одноцветный адаптер использует этот режим. Таким образом, если текущий видео режим 7, то используется одноцветный адаптер, в остальных случаях это EGA или CGA. Для наших задач, в текстовом режиме EGA и CGA одинаковы и поэтому все равно, какой из адаптеров у системы. Таким образом функция popup() должна поверить какой из адаптеров у системы и присвоить глобальной переменной указатель на соответствующий адрес. Этот фрагмент программы позволяет сделать это.

vmode = video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf(" должен быть 80 символьный текстовый режим");

exit(1);

/* присвоить соответствующий адрес видео памяти */

if(vmode==7) vid_mem=(char far *)0xB0000000;

else vid_mem=(char far *)0xB8000000;

Функция video_mode() возвращает текущий видео режим, и переменную vid_mem, объявленную везде как char far.

Изменение save_video() и restore_video()

Kaк только переменной vid_mem присвоен соответствующий адрес, появляется простой способ использовать ее для чтения и записи символов в видео память. Запомните, видео память требует двух байтов для каждого символа, один для символа, а другой для атрибута. Из-за того, что символьный байт первый, а атрибутный - второй, то каждой строке экрана требуется 160 байт. Для того, чтобы определить адрес отдельного символа вы должны использовать формулу:

адрес = адрес_адаптера + X*160 + Y*2

Функции save_video() и restore_video() при использовании прямого доступа к видео памяти выглядят следующим образом.

void save_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

register int i,j;

char far *v, far *t;

v=vid_mem;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

t = v + (j*160) + i*2; /* вычисляем адрес */

*buf_ptr++ = *t++; /* чтение символа */

*buf_ptr++ = *t; /* чтение атрибута */

*(t-1) = ' '; /* очистка окна */

/* восстановление части экрана */

void restore_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

register int i,j;

char far *v, far *t;

v=vid_mem;

t=v;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

v = t;

v += (j*160) + i*2; /* вычисляем адрес */

*v++ = *buf_ptr++; /* запись символа */

*v = *buf_ptr++; /* запись атрибута */

Как вы видете, символы и атрибуты записываются или читаются

с помощью использования указателей на видео память. Другие

функции, которые читают и записывают символы преобразуются

подобным образом.

Если весь доступ к дисплею делать прямым, требуется одна новая функция (показанная здесь). Функция write_char() записывает один символ в определенную позицию экрана с определенным атрибутом.

/* запись символа с определенным аттрибутом */

void write_char(x,y,ch,attrib)

int x,y;

char ch;

int attrib;

register int i;

char far *v;

v=vid_mem;

v += (x*160) +y*2;

*v++ = ch; /* запись символа */

*v = attrib; /* запись атрибута */

Полная версия исчезающих меню с прямым доступом приведена здесь с тем же простым тестовым примером. Введите его в в свой компьютер и сравните по производительности с версией, использующей BIOS. Как вы увидете, разница потрясающая. Кажется, что меню появляются и исчезают мгновенно.

/* Программа исчезающих меню для текстового режима

с использованием прямого доступа к видео памяти */

#include "stdio.h"

#include "dos.h"

#include "stdlib.h"

#define BORDER 1

#define ESC 27

#define REV_VID 0x70

#define NORM_VID 7

void save_video(),restore_video();

void goto_xy(),cls(),write_video();

void display_menu(),draw_border();

char far *vid_mem;

char *fruit[] =

"Яблоко",

"Апельсин",

"Груша",

"Грейпфрут",

"Малина",

"Клубника"

char *color[]=

"Красный",

"Желтый",

"Оранжевый",

"Зеленый"

;

char *apple_type[] =

"Красный деликатес",

"Джонатан",

"Белый налив",

"Антоновка"

;

main()

int i;

cls();

goto_xy(0,0);

for(i=0;i<25;i++)

printf("Это тест
исчезающего меню\n"); popup(fruit,"яагрмк",6,1,3,BORDER);
popup(color,"кжоз",4,5,10,BORDER);
popup(apple_type,"кдба",4,10,18,BORDER);

/* вывести исчезающее меню и вернуть выбор

возвращает -2, если меню не может быть создано

возвращает -1, если пользователь нажал клавишу ESC

в остальных случаях она возвращает номер выбранного

режима, начиная с 0 */

int popup(menu,keys,count,x,y,border)

char *menu[]; /* текст меню */

char *keys; /* горячие клавиши */

int count; /* число режимов */

int x,y; /* координаты левого верхнего угла */

int border; /* если 0 то без рамки */

register int i,len;

int endx endy choice;

unsigned char *p;

if((x>24)||(x<0)||(y>79)||(y<0))

printf(" выход за пределы экрана");

return -2;

/* вычисление размеров */

len=0;

for(i=0;i<count;i++)

if(strlen(menu[i]) > len) len=strlen(menu[i]);

endy=len+2+y;

endx=count+1+x;

if((endx+1>24) || (endy+1>79))

printf(" выход за пределы экрана");

return -2;

vmode = video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf(" должен быть 80 символьный текстовый режим");

exit(1);

/* присвоить соответствующий адрес видео памяти */

if(vmode==7) vid_mem=(char far *)0xB0000000;

else vid_mem=(char far *)0xB8000000;

/* размещение памяти для видео буфера */

p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* сохранение части экрана */

void save_video(x,endx+1,y,endy+1,p);

if(border) draw_border(x,y,endx,endy);

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count);

/* ввести выбор пользователя */

choice=get_resp(x,y,count,menu,keys)

/* восстановление части экрана */

void restore_video(x,endx+1,y,endy+1,p);

free(p);

return choice;

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count)

char *menu[];

int x,y,count;

register int i;

for(i=0;i<count;i++,x++)

write_string(x,y,menu[i],NORM_VID);

void draw_border(startx,starty,endx,endy)

int startx,starty,endx,endy;

register int i;

char far *v,far *t;

v=vid_mem;

t=v;

for(i=startx+1;i<endx;i++)

v += (i*160) + starty*2;

*v++ = 179;

*v = NORM_VID;

v=t;

v += (i*160) + endy*2;

*v++ = 179;

*v = NORM_VID;

v=t;

for(i=starty+1;i<endy;i++)

v += (startx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v=t;

v += (endx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v=t;

write_char(startx,starty,218,NORM_VID);

write_char(startx,endy ,191,NORM_VID);

write_char(endx ,starty,192,NORM_VID);

write_char(endx ,endy ,217,NORM_VID);

goto_xy(startx,endy ); putchar(191);

goto_xy(endx ,starty); putchar(192);

goto_xy(endx ,endy ); putchar(217);

/* ввести выбор пользователя */

get_resp(x,y,count,menu,keys)

int x,y,count;

char *menu[];

char *keys;

union inkey

char ch[2];

int i;

c;

int arrow_choice=0,key_choice;

y++;

/* осветить первый выбор */

goto_xy(x,y);

write_string(x,y,menu[0],REV_VID);

for(;;)

while(!bioskey(1)); /* ждать нажатия */

c.i=bioskey(0);

/* вернуть выбор в номальный режим */

goto_xy(arrow_choice,y);

write_string(x+arrow_choice,y,

menu[arrow_choice],norm_vid);

if(c.ch[0]) /* обычная клавиша */

key_choice= is_in(keys,tolower(c.ch[0]));

if(key_choice) return key_choice-1;

switch(c.ch[0])

case '\r' : return arrow_choice;

case ' ' : arrow_choice++;

break;

case ESC : return -1; /* выйти */

else /* специальная клавиша */

switch(c.ch[1])

case 72 : arrow_choice--; /* стрелка вниз */

break;

case 80 : arrow_choice++; /* стрелка вверх */

break;

if(arrow_choice==count) arrow_choice=0;

if(arrow_choice<0) arrow_choice=count-1;

/* подсветить выбранную опцию */

goto_xy(x+arrow_choice,y);

write_string(x+arrow_choice,y,menu[arrow_choice],REV_VID);

/* вывод строки с определенным атрибутом */

void write_string(x,y,p,attrib)

int x,y;

char *p;

int attrib;

register int i,j;

char far *v;

v=vid_mem;

v += (x*160) + y*2;

for(i=y; *p; i++)

*v++ =*p++; /* запись символа */

*v++ =attrib; /* запись атрибута */

/* запись символа с определенным аттрибутом */

void write_char(x,y,ch,attrib)

int x,y;

char ch;

int attrib;

register int i;

char far *v;

v=vid_mem;

v += (x*160) +y*2;

*v++ = ch; /* запись символа */

*v = attrib; /* запись атрибута */

/* сохранение части экрана с использованием

прямого доступа к видео памяти */

void save_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

register int i,j;

char far *v, far *t;

v=vid_mem;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

t = v + (j*160) + i*2; /* вычисляем адрес */

*buf_ptr++ = *t++; /* чтение символа */

*buf_ptr++ = *t; /* чтение атрибута */

*(t-1) = ' '; /* очистка окна */

/* восстановление части экрана с использованием

прямого доступа к видео памяти */

void restore_video(startx,endx,starty,endy,buf_ptr)

int startx,endx,starty,endy;

unsigned int *buf_ptr;

register int i,j;

char far *v, far *t;

v=vid_mem;

t=v;

for(i=starty;i<endy;i++)

for(j=startx;j<endx;j++)

v = t;

v += (j*160) + i*2; /* вычисляем адрес */

*v++ = *buf_ptr++; /* запись символа */

*v = *buf_ptr++; /* запись атрибута */

/* очистка экрана */

void cls()

union REGS r;

r.h.ah=6; /* код прокрутки экрана */

r.h.al=0; /* код очистки экрана */

r.h.ch=0; /* начальная строка */

r.h.cl=0; /* начальная колонка */

r.h.dh=24; /* конечная строка */

r.h.dl=79; /* конечная колонка */

r.h.bh=7; /* очистка пустой строки */

int86(0x10,&r,&r);

/* установка курсора в x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah=2; /* функция установки курсора */

r.h.dl=y; /* координата колонки */

r.h.dh=x; /* координата строки */

r.h.bh=0; /* видео страница */

int86(0x10,&r,&r);

/* запрос текущего видео режима */

video_mode()

union REGS r;

r.h.ah = 15; /* получить режим */

return int86(0x10,&r,&r) & 255;

is_in(s,c)

char *s,c;

register int i;

for(i=0; *s; i++)

if(*s++ == c) return i+1;

return 0;

Создание иерархических окон

Иерархические окна фундаментально отличаются от простых исчезающих меню тем, что два или более исчезающих меню могут быть активными одновременно. Вообще иерархические меню позволяют пользователю выбирать режимы непосредственно из режимов и используются для поддержки системы меню. В отличие от функции popup(), которая сохраняет экран, высвечивает меню, и восстанавливает экран, функция pulldown(), разработанная в этом разделе только сохраняет экран (если это нужно), высвечивает меню и возвращает выбор пользователя. Восстановление экрана обрабатывается как отдельная задача в любом месте программы. Перед тем как вы сможете создать иерархическое меню, вы должны изменить свое представление о меню.

Фреймы меню

Центральным понятием для создания иерархического,

многоуровневого меню является фрейм меню. В сущности, программы

иерархических меню требуют чтобы каждое меню имело свой фрейм

ссылок, определенных до того, как программа, которая испольует

меню, начала выполняться. Каждое меню активизируется по номеру

его фрейма и необходимая информация загружается по мере

необходимости для различных функций, поддерживающих меню.

Лучший способ для поддержки фреймов меню - создать массив структур, которые содержат информацию, относящуюся к меню. Эта структура определяется так, как описано здесь:

struct menu_frame

int startx,endx,starty,endy;

unsigned char *p; /* указатель на информацию экрана */

char **menu; /* указатель на строки меню */

int border; /* рамка включено/выключено */

int count; /* число альтернатив */

int astive; /* активно ли меню сейчас */

frame[MAX_FRAME];

где МAX_FRAME - макроконстанта, которая определяет как много меню

вы можете иметь. Только одна дополнительная информация требуется

для иерархических меню, которая не нужна для исчезающих меню -

флаг активности. Флаг используется в качестве сигнала, что меню

уже на экране и предупреждает перезаписывание информации с

экрана.

Создание фрейма меню

Перед использованием меню для этого должен быть создан фрейм. Функция make_menu(), показанная здесь, создает фрейм меню.

/* создание фрейма иерархического меню.

1 если фрейм может быть создан

в противном случае 0 */ make_menu(num,menu,keys,count,x,y,border) int num; /* номер меню */ char *menu; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* левый верхний угол */ int border; /* рамка */

register int i,len;

int endx,endy,choice,vmode;

unsigned char *p;

if(num>MAX_FRAME)

printf("Слишком много меню");

return 0;

if((x>24)||(x<0)||(y>79)||(y<0))

printf(" выход за пределы экрана");

return 0;

/* вычисление размеров */

len=0;

for(i=0;i<count;i++)

if(strlen(menu[i]) > len) len=strlen(menu[i]);

endy=len+2+y;

endx=count+1+x;

if((endx+1>24) || (endy+1>79))

printf(" выход за пределы экрана");

return 0;

/* размещение памяти для видео буфера */

p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* создание фрейма */

frame[num].startx=x;

frame[num].endx=endx;

frame[num].starty=y;

frame[num].endy=endy;

frame[num].p = p;

frame[num].menu = (char **) menu;

frame[num].border = border;

frame[num].keys = keys;

frame[num].count = count;

frame[num].active =0;

return 1;

Вы вызываете make_menu с теми же аргументами, какие используются в popup() кроме номера меню, который должен быть определен в первом аргументе. Этот номер используется для идентификации меню.

Функция pulldown()

Функция pulldown() показана здесь:

/* Высветить меню и получить выбор

возвращает -1, если пользователь нажал клавишу ESC

в остальных случаях номер альтернативы, начиная с 0 */

int pulldown(num)

int num; /* номер фрейма */

int vmode,choice;

vmode=video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf(" должен быть 80 символьный текстовый режим");

exit(1);

/* присвоить соответствующий адрес видео памяти */

if(vmode==7) vid_mem=(char far *)0xB0000000;

else vid_mem=(char far *)0xB8000000;

/* узнать активность окна */

if(!frame[num].active) /* не активно */

save_video(num);

frame[num].active= 1; /* установить признак активности */

if( frame[num].border) draw_worder(num);

display_menu(num); /* высветить меню */

return get_resp(num); /* возвратить выбор */

Для использования pulldown() просто передайте номер того меню, которое вы хотите высветить. Однако вы должны помнить о восстановлении окна, используя restore_video() везде в своей программе. Запомните, что основное отличие иерархических меню в том, что они позволяют двум или более меню оставаться на экране одновременно и быть потенциально активными, по мере того как пользователь выбирает опции. Тем не менее вам не следует восстанавливать экран до того, как процесс выбора полностью завершен.

Обратите внимание, что часть экрана, используемая для меню, сохраняется только если признак активности равен 0. Так как иерархическое меню может быть вызвано повторно, экран не должен сохраняться много раз. (Другими словами при повторном вхождении будет сохранено само меню, записанное поверх первоначального содержимого экрана, которое уже сохранено.)

Восстановление экрана

Как и другие функции поддержки меню, измененная restore_video показанная здесь, преобразована для работы с фреймами. Поэтому функции restore_video() теперь передается только номер меню, что делает интерфейс более очевидным.

/* восстановление части экрана */

void restore_video(num)

int num;

register int i,j;

char far *v, far *t;

char *buf_ptr;

buf_ptr=frame[num].p;

v=vid_mem;

t=v;

for(i=frame[num].starty;i<frame[num].endy;i++)

for(j=frame[num].startx;j<frame[num].endx;j++)

v = t;

v += (j*160) + i*2; /* вычисляем адрес */

*v++ = *buf_ptr++; /* запись символа */

*v = *buf_ptr++; /* запись атрибута */

frame[num].active= 0;

Простая программа, использующая процедуру pulldown

Все функции для иерархических меню показаны здесь вместе с простой программой-образцом и их можно прямо вводить в ваш компьютер.

/* процедура иерархического меню для текстового режима

и простая программа-пример */

#include "stdio.h"

#include "dos.h"

#include "stdlib.h"

#define BORDER 1

#define ESC 27

#define REV_VID 0x70

#define NORM_VID 7

void save_video(),restore_video();

void goto_xy(),cls(),write_video();

void display_menu(),draw_border();

char far *vid_mem;

struct menu_frame

int startx,endx,starty,endy;

unsigned char *p; /* указатель на информацию экрана */

char **menu; /* указатель на строки меню */

int border; /* рамка включено/выключено */

int count; /* число альтернатив */

int astive; /* активно ли меню сейчас */

frame[MAX_FRAME];

char *fruit[] =

"Яблоко",

"Апельсин",

"Груша",

"гРейпфрут",

"Малина"

"Клубника"

;

char *color[]=

"Красный",

"Желтый",

"Оранжевый",

"Зеленый"

;

char *apple_type[] =

"Красный деликатес", ?

"Джонатан",

"Белый налив",

"Aнтоновка"

char *grape_type[]=

"Конкорд",

"кАнадский",

"Томпсон",

"кРасное пламя"

;

main()

int i;

cls();

goto_xy(0,0);

/* во-первых создадим фреймы меню */

make_menu(0,fruit,"яагрмк",6,5,20,BORDER);

make_menu(1,color,"кжоз",4,9,28,BORDER);

make_menu(2,apple_type,"кдба",4,12,32,BORDER);

make_menu(3,grape_type,"катр",4,9,10,BORDER);

printf("Выберите фрукт:");

pd_driver(); /* запуск системы меню */

void pd_driver()

int choice1,choice2,selection;

/* активизация окон по мере надобности */ while((choice1=pulldown(0)) != -1)

switch ( choice1 )

case 0 : /* яблоко */

while((choice2=pulldown(1)) != -1)

if(choice2 ==0) selection=pulldown(2);/*красное яблоко */

restore_video(2);

restore_video(1);

break;

case 1 :

case 2 : goto_xy(1,0);

printf("неправильный выбор");

break;

case 3 : /* грейпфрут */

selection=pulldown(3);

restore_video(3);

break;

case 4 :

case 5 : goto_xy(1,0);

printf("неправильный выбор");

break;

restore_video(0);

/* Высветить меню и получить выбор */

int pulldown(num)

int num; /* номер фрейма */

int vmode,choice;

vmode=video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf(" должен быть 80 символьный текстовый режим");

exit(1);

/* присвоить соответствующий адрес видео памяти */

if(vmode==7) vid_mem=(char far *)0xB0000000;

else vid_mem=(char far *)0xB8000000;

/* узнать активнсть окна */

if(!frame[num].active) /* не активно */

save_video(num);

frame[num].active= 1; /* установить признак активности */

if( frame[num].border) draw_worder(num);

display_menu(num); /* высветить меню */

return get_resp(num); /* возвратить выбор */

/* создание фрейма иерархического меню.

1 если фрейм может быть создан

в противном случае 0 */ make_menu(num,menu,keys,count,x,y,border) int num; /* номер меню */ char *menu; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* левый верхний угол */ int border; /* рамка */

register int i,len;

int endx,endy,choice,vmode;

unsigned char *p;

if(num>MAX_FRAME)

printf("Слишком много меню");

return 0;

if((x>24)||(x<0)||(y>79)||(y<0))

printf(" выход за пределы экрана");

return 0;

/* вычисление размеров */

len=0;

for(i=0;i<count;i++)

if(strlen(menu[i]) > len) len=strlen(menu[i]);

endy=len+2+y;

endx=count+1+x;

if((endx+1>24) || (endy+1>79))

printf(" выход за пределы экрана");

return 0;

/* размещение памяти для видео буфера */

p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* создание фрейма */

frame[num].startx=x;

frame[num].endx=endx;

frame[num].starty=y;

frame[num].endy=endy;

frame[num].p = p;

frame[num].menu = (char **) menu;

frame[num].border = border;

frame[num].keys = keys;

frame[num].count = count;

frame[num].active =0;

return 1;

/* высвечивание меню на своем месте */

void display_menu(num)

int num;

char **m;

register int i, x;

x = frame[num].startx+1;

m = frame[num].menu;

for(i=0;i<frame[num].count;i++,x++)

write_string(x,frame[num].starty+1,m[i],NORM_VID);

void draw_border(num)

int num ;

register int i;

char far *v,far *t;

v=vid_mem;

t=v;

for(i=frame[num].startx+1;i<frame[num].endx;i++)

v += (i*160) + frame[num].starty*2;

*v++ = 179;

*v = NORM_VID;

v=t;

v += (i*160) + frame[num].endy*2;

*v++ = 179;

*v = NORM_VID;

v=t;

for(i=frame[num].starty+1;i<frame[num].endy;i++)

v += (frame[num].startx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v=t;

v += (frame[num].endx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v=t;

write_char(frame[num].startx,frame[num].starty,218,NORM_VID);

write_char(frame[num].startx,frame[num].endy ,191,NORM_VID);

write_char(frame[num].endx ,frame[num].starty,192,NORM_VID);

write_char(frame[num].endx ,frame[num].endy ,217,NORM_VID);

goto_xy(frame[num].startx,frame[num].endy ); putchar(191);

goto_xy(frame[num].endx ,frame[num].starty); putchar(192);

goto_xy(frame[num].endx ,frame[num].endy ); putchar(217);

/* ввести выбор пользователя */

get_resp(num)

int num;

union inkey

char ch[2];

int i;

c;

int arrow_choice=0,key_choice;

int x,y;

x=frame[num].startx+1;

y=frame[num].starty+1;

/* осветить первый выбор */

goto_xy(x,y);

write_string(x,y,frame[num].menu[0],REV_VID);

for(;;)

while(!bioskey(1)); /* ждать нажатия */

c.i=bioskey(0);

/* вернуть выбор в номальный режим */

goto_xy(arrow_choice,y);

write_string(x+arrow_choice,y,

frame[num].menu[arrow_choice],norm_vid);

if(c.ch[0]) /* обычная клавиша */

key_choice= is_in(frame[num].keys,tolower(c.ch[0])); if(key_choice) return key_choice-1; switch(c.ch[0])

case '\r' : return arrow_choice;

case ' ' : arrow_choice++;

break;

case ESC : return -1; /* выйти */

else /* специальная клавиша */

switch(c.ch[1])

case 72 : arrow_choice--; /* стрелка вниз */

break;

case 80 : arrow_choice++; /* стрелка вверх */

break;

if(arrow_choice==frame[num].count) arrow_choice=0;

if(arrow_choice<0) arrow_choice=frame[num].count-1;

/* подсветить выбранную опцию */ goto_xy(x+arrow_choice,y); write_string(x+arrow_choice,y,

frame[num].menu[arrow_choice],REV_VID);

/* вывод строки с определенным атрибутом */

void write_string(x,y,p,attrib)

int x,y;

char *p;

int attrib;

register int i,j;

char far *v;

v=vid_mem;

v += (x*160) + y*2;

for(i=y; *p; i++)

*v++ =*p++; /* запись символа */

*v++ =attrib; /* запись атрибута */

/* запись символа с определенным аттрибутом */

void write_char(x,y,ch,attrib)

int x,y;

char ch;

int attrib;

register int i;

char far *v;

v=vid_mem;

v += (x*160) +y*2;

*v++ = ch; /* запись символа */

*v = attrib; /* запись атрибута */

/* сохранение части экрана с использованием

прямого доступа к видео памяти */

void save_video(num)

int num;

register int i,j;

char far *v, far *t;

char *buf_ptr;

buf_ptr=frame[num].p;

v=vid_mem;

for(i=frame[num].starty;i<frame[num].endy;i++)

for(j=frame[num].startx;j<frame[num].endx;j++)

t = (v + (j*160) + i*2); /* вычисляем адрес */

*buf_ptr++ = *t++; /* чтение символа */

*buf_ptr++ = *t; /* чтение атрибута */

*(t-1) = ' '; /* очистка окна */

/* восстановление части экрана */

void restore_video(num)

int num;

register int i,j;

char far *v, far *t;

char *buf_ptr;

buf_ptr=frame[num].p;

v=vid_mem;

t=v;

for(i=frame[num].starty;i<frame[num].endy;i++)

for(j=frame[num].startx;j<frame[num].endx;j++)

v = t;

v += (j*160) + i*2; /* вычисляем адрес */

*v++ = *buf_ptr++; /* запись символа */

*v = *buf_ptr++; /* запись атрибута */

frame[num].active= 0;

/* очистка экрана */

void cls()

union REGS r;

r.h.ah=6; /* код прокрутки экрана */

r.h.al=0; /* код очистки экрана */

r.h.ch=0; /* начальная строка */

r.h.cl=0; /* начальная колонка */

r.h.dh=24;/* конечная строка */

r.h.dl=79;/* конечная колонка */

r.h.bh=7; /* очистка пустой строки */

int86(0x10,&r,&r);

/* установка курсора в x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah=2; /* функция установки курсора */

r.h.dl=y; /* координата колонки */

r.h.dh=x; /* координата строки */

r.h.bh=0; /* видео страница */

int86(0x10,&r,&r);

/* запрос текущего видео режима */

video_mode()

union REGS r;

r.h.ah = 15; /* получить режим */

return int86(0x10,&r,&r) & 255;

is_in(s,c)

char *s,c;

register int i;

for(i=0; *s; i++)

if(*s++ == c) return i+1;

return 0;

В этом примере, если пользователь выберет "Яблоко", то он или она будет запрошен о цвете яблока; если выбран "Красный" цвет, то будет высвечен список красных сортов яблок. Если же будет выбран грейпфрут то пользователь будет запрошен о желаемом типе. Меню для выбора яблок показано на рисунке.

выберите фрукт:

|Яблоко |

|Апельсин |

|Груша |

|гРейпфрут|

|Малин---------

|Клубн|Красный|

-------Желтый |

|Ора-------------------

|Зел|Красный деликатес|

----|Д*ж*о*н*а*т*а*н**|

|Белый налив |

|Антоновка |

Посмотрите внимательно на функцию pd_driver(), которая следует за главной функцией main(). При использовании иерархических меню вы должны создавать функцию, которая управляет системой меню. Основа стратегии управляющей функции должна быть аналогична функции pd_driver() из этого примера. Не забывайте, что эта простая программа только иллюстрирует как активизировать меню. Ваша реальная прикладная программа будет обрабатывать выбранные режимы более разумным образом. Запомните, что для использования иерархических меню нужна следующая последовательность действий.

1. Создать меню, используя make_menu().

2. Активизировать меню, используя pulldown().

3. Восстановить экран, используя restore_video(), при выходе из каждого меню.

Добавочные опции

Процедуры меню, разработанные в этой главе, подходят в большинстве ситуаций. Однако, вы можете по желанию добавить некоторые из следующих возможностей:

# Высвечивание заголовка меню

# Линейное меню (все опции на одной строке)

# Использование разных цветов для разных меню


Глава 2. Всплываюшие окна

Всплывающие окна могут придать вашей программе тот профессиональный вид, который не может быть достигнут другими средствами. Всплывающие окна создают впечатление, что вы, как программист, в совершенстве владеете экраном. А так как пользователь обычно судит о программе по ее пользовательскому интерфейсу, то это положительное впечатление распространится и на всю программу в целом.

Данная глава содержит описание полного набора функций для всплывающих окон, которые позволят вам создавать и использовать множественные окна.

Программы управления окнами используют функции прямого доступа к видеопамяти, представленные в Главе 1. Из-за того, что окна в большинстве случаев, имеют значительно больший размер, чем меню, то использование функций из ROM-BIOS просто невозможно, - даже на самых быстрых компьютерах.

Однако перед рассмотрением оконных функций очень важно правильно понять, что же такое всплывающие окна и как они используются.

наверх

Теория всплывающих окон.

Всплывающее окно представляет собой часть экрана, используемую для специальных целей. Перед появлением окна текущее содержимое экрана сохраняется и лишь после этого производится отображение окна.

При завершении программы, использующей данное окно, это окно удаляется, а первоначальное содержимое экрана восстанавливается. (Данный процесс аналогичен появлению всплывающих меню). Вполне возможно одновременное отображение на экране нескольких окон.

Хотя это и не обязательно, но все хорошие программы, работающие с окнами, позволяют интерактивно изменять размеры и позицию окна на экране. Следовательно, оконные функции допускают, что окно не всегда будет находиться в одном и том же месте и иметь один и тот же размер.

Разработка функций, управляющих окнами, является сложной задачей из-за необходимости обеспечения запрета для прикладной программы осуществлять вывод за границы окна. Поскольку размеры окна могут изменяться без "сообщения" об этом прикладной программе, то именно функции управления окнами, а не прикладная программа, должны предохранить от выхода за границы. Следовательно, все обычные функции Си, осуществляющие ввод/вывод на консоль (например, printf() и lets() ) , не могут быть использованы и должны быть заменены на функции, ориентированные на ввод/вывод с использованием окон.

Теория использования окон крайне проста. Каждая отдельная задача программы использует свое собственное окно. При запуске задачи активируется и ее окно. При завершении работы задачи - ее окно удаляется. Если же задача прерывается, то, хотя ее работа приостанавливается, но ее окно не удаляется, а инициируемая прерыванием задача, просто создает свое окно поверх предыдущего. (Обычно те задачи, которые не используют окон, очищают экран. Это приводит к рассеиванию внимания пользователя. В то же время при использовании окон подобные прерывания выглядят как временные паузы).

Чтобы понять, как окна могут быть наиболее эффективно использованы, предположим, что вы разработали текстовый редактор, включающий ряд дополнительных функций, таких как "записная книжка", калькулятор с четырьмя математическими операциями и конвертер чисел из десятичного в шестнадцатиричное представление. Так как все эти функции в действительности не относятся к операциям редактирования текста, то их реализация тесно переплетается с концепцией всплывающих окон. Таким образом получается, что использование какой-либо из вспомогательных функций лишь приостанавливает основную задачу (редактирование), а не прерывает ее.

наверх

Оконные структуры.

Правильная реализация всплывающих окон требует, чтобы все атрибуты, необходимые для описания их границ, были в любое время доступны всем оконным функциям. Для достижения этого мы будем использовать концепцию структуры, аналогичную той, которая использовалась при описании функций спускающихся меню. Однако структура окна содержит некоторую специфическую информацию. Ниже показан массив, используемый для хранения структур.

struct window_frame

int startx, endx, starty, endy; /*позиция окна*/

int curx, cury; /*текущая позиция курсора в окне*/

unsigned chsr *p; /*указатель буфера*/

char *header; /*имя окна*/

int border; /*включение/выключение границ*/

int active; /*на экране или невидимо*/


Глава 3. Программы, остающиеся резидентными после завершения и формирующие при их вызове всплывающие изображения на экране дисплея.

Простая на первый взгляд идея создания программ, которые оставались бы резидентными в памяти после их завершения и реагировали на вызов формированием всплывающих изображений на экране дисплея, на самом деле является одной из наиболее трудных задач программирования для ПЭВМ. Такие программы называются ТSR-программами. При чтении данного раздела вы должны получше пристегнуться ремнями безопасности и одеть защитный шлем, т.к. создание TSR-программ связано с риском. Но этот риск оправдан возможным вознаграждением - поистине профессиональными результатами, которыми гордился бы любой программист мирового класса.

Поскольку ТSR-программы, естественно, должны на низком уровне взаимодействовать с аппаратурой и операционной системой, то излагаемые в данном разделе сведения будут применимы только к ПЭВМ линии IBM PC, работающими под операционной системой DOS. По причинам, которые будут указаны ниже, приводимые в разделе программы рассчитаны на компилятор Turbo C, но могут быть модифицированы и для других компиляторов.

Предупреждение. Для разработки и использования TSR-программ характерна модификация таблицы векторов прерываний. Приведенные в данном разделе программы транслируются с помощью Турбо Си версии

1.0 и в этом случае работают корректно и без посторонних эффектов. При использовании другого компилятора корректность работы не гарантируется. Кроме того, если вы будете набирать эти программы вручную, то можете внести свои ошибки. И в том, и в другом случае это может привести к фатальному сбою системы, в результате чего могут быть уничтожены данные на вашем винчестерском диске. Поэтому целесообразно делать резервные копии файлов. Я уверен, что приводил к краху мою модель 60 не менее 100 раз за те два дня, пока отлаживал основную логику своей программы. (К счастью, я не затирал при этом винчестерского диска).

Что такое TSR-программа?

ТSR-программы создаются путем вызова функции 49 DOS, по которой производится возврат из программы в DOS. При этом программа остается в области памяти, которую DOS в дальнейшем не использует. Таким образом, программа может быть мгновенно вызвана без повторной загрузки. Одним из многих широко известных примеров TSR-программ является программа Sidekick фирмы Вorland.

Большинство TSR-программ вызываются с помощью прерывания, которое может быть сформировано несколькими способами. Наиболее распространенными являются прерывания по таймеру, прерывания клавиатуры и печати экрана. Для TSR-программ, формирующих изображение на экране, обычно используются прерывания от клавиатуры или печати экрана, поскольку позволяют пользователю вызывать TSR-программу путем одиночного нажатия клавиши.

Прерывания в семействе процессоров 8086.

Процессоры семейства 8086 поддерживают до 256 различных прерываний по вектору. Прерывание по вектору вызывает выполнение программы обработки прерываний (ISR), адрес которой содержится в таблице векторов прерываний. Хотя некоторые старшие процессоры семейства требуют, чтобы программы обработки прерывания располагались в определенных адресах памяти, механизм прерываний по вектору позволяет определять адреса программ обработки прерываний.

Таблица векторов начинается с адреса 0000:0000 и ее размер составляет 1024 байта. Поскольку адрес программы обработки прерывания может быть любым, то для его определения требуется 32 разряда (4 байта). Следовательно, размер каждой записи в таблице векторов составляет 4 байта. Адреса ISR-программ в таблице записываются таким образом, что адрес программы обработки прерывания 0 находится по адресу 0000:0000, программы обработки прерывания прерывания 1 - по адресу 0000:0004, прерывания 2 - по адресу 0000:0008 и т.д.

Когда происходит прерывание, то любые другие прерывания запрещаются. Ваша программа обработки прерывания сразу после того, как она начнет выполняться, должна разрешить прерывания, чтобы избежать краха системы. Программа обработки прерывания должна завершаться командой IRET.

Прерывания против DOS и BIOS: Tревога в стране DOS.

Программисты часто выражают недовольство тем, что DOS не является повторно входимой программой. Это означает, что когда одна программа обращается к DOS, то другая программа этого делать не может. (Этим объясняется, в частности, почему DOS не является мультизадачной операционной системой). Таким образом, программа обработки прерывания не может вызывать никакой функции DOS, такая попытка приводит к краху системы. Поэтому программа обработки прерывания должна сама выполнять те действия, которые производятся при обращении к функциям DOS. К счастью, для формирования видеоизображения мы можем использовать программы непосредственного обращения к видеопамяти из разделов 1 и 2.

BIOS допускает некоторую повторную входимость. Например, прерывание 16, соответствующее вводу с клавиатуры, может быть использовано в этом режиме без каких-либо побочных эффектов. Некоторые другие подпрограммы использовать таким образом не столь безопасно. Обнаружить это можно только экспериментальным путем. О том, что функцию нельзя использовать в таком режиме, вы узнаете по фатальному сбою системы. Для приведенных в данном разделе примеров и для многих распространенных в мире программ прерывания 16 вполне достаточно.

Поскольку многие из функций стандартной библиотеки языка Си обращаются к DOS или к BIOS, то они не должны использовать тех функций DOS и BIOS, которые не обеспечивают повторной входимости. Следует помнить, что не только функции ввода-вывода обращаются к DOS и BIOS. Например, функция распределения памяти malloc() обращается к DOS для определения размера свободной памяти в системе. К сожалению, программы, которые рассчитаны на один компилятор, могут не работать с другим компилятором. Этим и объясняется, почему TSR-программы так трудно создавать и переносить в другую среду и почему TSR-программ создано столь немного при их очень большой популярности.

По существу, вы должны воспринимать TSR-программы как "заблудшие" программы, о существовании которых DOS не подозревает. И в дальнейшем, чтобы сохранить тайну о своем существовании эти программы должны избегать любого взаимодействия с DOS. Всего пары обращений к DOS достаточно, и вашей программе будет устроена кровавая резня. Чтобы этого избежать, вы должны ощущать себя шпионом и иметь нервы автогонщика.

Модификатор функций прерывания Турбо Си.

Хотя стандарт ANSI этого и не требует, Турбо Си включает специальный модификатор типа функции, который называется interrupt и позволяет использовать функции Си в качестве TSR-программ. (Большинство основных разработчиков компиляторов Си по всей вероятности включат это средство в свои будущие разработки, поскольку это очень важное расширение). Например, предположим, что функция test() используется для обработки прерываний. В этом случае вы должны определить ее так, как показано ниже. Параметры, описывающие значения соответствующих регистров во время прерывания, не нужно определять, если они не будут использоваться.

void interrupt test(bp, di, si, ds, es, dx, cx, bx,

ax, ip, cs, flags)

unsigned bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags;

.

.

.

Функция interrupt автоматически сохраняет значения всех регистров и восстанавливает их перед возвратом управления вызывающей программе. Эта функция использует для возврата управления команду IRET вместо обычной в таком случае команды RET.

В представленных в данной книге примерах модификатор interrupt применяется только для тех функций, которые используются в качестве точек входа в программы обработки прерываний TSR-программ.

Если ваш компилятор не поддерживает модификатор interrupt, то вам необходимо написать на ассемблере небольшой интерфейсный модуль, который будет сохранять значения регистров, переустанавливать разрешение прерываний, а затем вызывать соответствующую функцию Си. Для выхода из модуля необходимо использовать команду IRET. Средства создания функций на языке ассемблера различны для разных компиляторов, так что читайте имеющееся у вас руководство пользователя.

Общий план TSR-программы

Все TSR-программы обычно состоят из двух разделов. Первая часть используется для инициализации TSR-программы и возврата управления DOS путем использования реентерабельного системного вызова. Эта часть не выполняется до тех пор, пока не возникает необходимость в перезагрузке программы. При этом производится запись адреса точки входа TSR-программы в соответствующее место таблицы векторов.

Вторая, прикладная часть, занимается формированием

изображений. При этом почти всегда используются окна, а

следовательно,и программы управления окнами. При этом изображение

на экране восстанавливается после завершения работы прикладной

части программы. Следует помнить, что у большинства TSR-программ

прикладные части представляют собой утилиты формиривания

изображения, как у программы типа "записной книжки" или

"калькулятора". После своего завершения они восстанавливают

изображение на экране в том же виде, каким оно было перед

запуском этих программ.

Использование прерывания печати экрана.

Без сомнений, прерыванием, которое наиболее просто "украсть" у DOS, является прерывание номер 5. Это прерывание вызывается при нажатии клавиши PT SCR. Если вы готовы пожертвовать функцией печати экрана, то можете заменить адрес этой программы в таблице векторов адресом вашей TSR-программы. Таким образом, при каждом нажатии клавиши PT SCR будет вызываться ваша TSR-программа.

Примером такой программы является резидентный калькулятор. Программы для работы с окнами и программа калькулятора из раздела 2 приводятся здесь с некоторыми небольшими изменениями.

Раздел инициализации

Раздел инициализации программы резидентного калькулятора очень небольшой и целиком помещается в нижеследующей функции main().

void interrupt tsr_ap(); /* вход в прикладную программу */

main()

struct address

char far *p;

;

/* адрес прерывания печати экрана */

struct address far *addr = (struct address far *) 20;

addr->p = (char far *) tsr_ap;

set_vid_mem();

tsr(2000);

TSR-программа первым делом должна заменить адрес программы обработки прерывания 5 указателем функции, определенной в самой TSR-программе. Есть несколько способов изменения адреса в таблице векторных прерываний. Один из способов состоит в использовании системного вызова DOS. Однако неудобство использования функции DOS заключается в том, что она требует задания значения адресного сегмента в регистре ЕS, который недоступен при использовании функции int86(). Некоторые компиляторы, как например Турбо Си, включают специальные функции, предназначенные для установки адреса в таблице прерываний. Однако способ, предлагаемый здесь, будет работать при использовании практически любого компилятора. Функция tsr_ap() является точкой входа в прикладную часть TSR-программы. Она использует указатель на содержимое таблицы векторов, соответствующее прерыванию 5. (Напоминаем, что вектор 5 расположен по адресу 20(4х5) в таблице, поскольку каждый вектор имеет размер 4 байта. Некоторые TSR-программы восстанавливают исходное значение адреса. Но при использовании приводимых здесь программ вы должны будете перезагружать систему, чтобы восстановить исходные значения векторов прерываний.

В предыдущих разделах, проверка режима работы видеосистемы производилась динамически теми программами, которые с ней работали. Однако в данном случае это неприменимо, поскольку требует использования системных вызовов DOS. Вместо этого значение глобального указателя vid_mem устанавливается с помощью функции set_vid_mem, приводимой ниже.

set_vid_mem()

int vmode;

vmode = video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf("video must be in 80 column text mode");

exit (1);

/* установить соответствующий адрес видеопамяти */

if(vmode==7) vid_mem = (char far *) 0xB0000000;

else vid_mem = (char far *) 0xB8000000;

Наконец, выход из функции main() ocyществляется путем обращения к функции tsr(), приведенной ниже.

/* завершить выполнение, но оставить резидентной */

tsr(size)

unsigned size;

union REGS r;

r.h.ah = 49; /* завершить и оставить резидентной */

r.h.al = 0; /* код возврата */

r.x.dx = size;

int86(0x21, &r, &r);

Параметр size, определяемый в регистре DX, используется для того, чтобы сообщить DOS, сколько памяти требуется для размещения ТSR-программы. Размер памяти определяется в 16-байтных параграфах. Иногда бывает трудно определить, сколько памяти необходимо для размещения программы. И если в этом случае вы разделите размер загрузочного модуля вашей программы (файла с расширением .EXE) на 16, а полученную величину умножите на 2, то будете застрахованы от ошибки. Точно определить размер необходимой памяти трудно, поскольку загрузочные модули частично накладываются друг на друга при загрузке и необязательно размещаются в непрерывной области. (Если вы намереватесь продавать свои программы, то наверняка хотели бы знать точно, сколько потребуется памяти, чтобы не оказаться слишком расточительным. Наиболее просто это можно определить экспериментальным путем). Код возврата, устанавливаемый в регистре AL, передается системе.

После завершения выполнения функции маin() программа остается в памяти, и никакая другая программа не может быть загружена на ее место. Это значит, что прикладная часть программы в любой момент времени готова быть запущенной нажатием клавиши PT SCR.

Прикладная часть TSR-программы

Точкой входа в прикладную часть TSR-программы должна быть функция типа interrupt. В представленном ниже примере запуск прикладной части выполняется путем вызова функции window_main().

/* Точка входа в прикладную часть TSR-программы */

void interrupt tsr_ap()

if(!busy)

busy = !busy;

window_main();

busy = !busy;

Глобальная переменная busy первоначально устанавливается в

0. Прикладная часть TSR-программы не является повторно входимой, следовательно, она не должна запускаться дважды за время одного использования. Переменная busy используется как раз для того, чтобы предотвратить это. (Некоторые компиляторы Си могут создавать реентерабельные программы, но безопаснее для вас не обсуждать здесь этого вопроса).

В программы управления окнами необходимо внести некоторые изменения для того, чтобы их можно было использовать в TSR-программах. Во-первых, необходимо статически распределять память, необходимую для хранения текущего содержимого экрана, путем использования глобального массива. Вы могли привыкнуть к тому, что эта память распределялась динамически, но данный способ здесь непригоден, вследствие того, что функции динамического распределения используют системный вызов, который недопустим в TSR-программах. По этой же причине функция go_to_xy() не может быть использована для позиционирования курсора. Наконец, стандартные Си-функции sscanf() и sprintf() также не могут быть использованы (по крайней мере, в Турбо Си), потому что также осуществляют обращения к DOS. Вместо них используются функции атоi() и itoa(). Полный текст программы резидентного калькулятора представлен ниже.

/* TSR-программа, использующая прерывание печати экрана */

#include "dos.h"

#include "stdlib.h"

#define BORDER 1

#define ESC 27

#define MAX_FRAME 1

#define REV_VID 0x70

#define NORM_VID 7

#define BKSP 8

void interrupt tsr_ap();

void save_video(), restore_video();

void write_string(), write_char();

void display_header(), draw_border();

void window_gets();

void window_cleol(), window();

void calc();

char far *vid_mem;

struct window_frame

int startx, endx, starty, endy;

int curx, cury; /* текущее положение курсора в окне */

unsigned char *p; /* указатель буфера */

char *header; /* сообщение в верхней части окна */

int border; /* включение/отключение бордюра */

int active; /* активация/деактивация окна */

frame[MAX_FRAME];

char wp[4000]; /* буфер для хранения текущего содержимого экрана

/* busy установлена в 1, если программа активна, иначе - в 0 */

char busy = 0;

main()

struct address

char far *p;

;

/* адрес прерывания печати экрана */

struct address far *addr = (struct address far *) 20;

addr->p = (char far *) tsr_ap;

set_vid_mem();

tsr(2000);

set_vid_mem()

int vmode;

vmode = video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf("video must be in &0 column text mode");

exit(1);

/* установить соответсвующий адрес видеопамяти */

if(vmode==7) vid_mem = (char far *) 0xB0000000;

else vid_mem = (char far *) 0xB8000000;

/* точка входа в прикладную часть TSR-программы */

void interrupt tsr_ap()

if(!busy)

busy = !busy;

window_main();

busy = !busy;

/* завершить, но оставить резидентной */

tsr(size)

unsigned size;

union REGS r;

r.h.ah = 49; /* завершить, но оставить резидентной */

r.h.al = 0; /* код возврата */

r.x.ax = size;

int86(0x21, &r, &r);

window_main()

/* первым делом, создать структуру окна */

make_window(0, " Calculator ", 8, 20, 12, 60, BORDER);

/* для активации описанного окна используйте window() */

calc();

/*************************************************************/

/* Функции управления окнами */

/*************************************************************/

/* Вывести на экран спускающееся меню */

void window(num)

int num; /* номер окна */

int vmode, choice;

int x, y;

/* сделать окно активным */

if(!frame[num].active) /* используется не постоянно */

save_video(num); /* сохранить текущий экран */

frame[num].active = 1; /* установить флаг активности */

if(frame[num].border) draw_border(num);

display_header(num); /* вывести окно */

/* Создать спускающееся окно

если окно может быть создано, возвращается 1;

иначе возвращается 0.

*/

make_window(num, header, startx, starty, endx, endy, border)

int num; /* номер окна */

char *header; /* текст заголовка */

int startx, starty; /* координаты X,Y левого верхнего угла */

int endx, endy; /* координаты X,Y правого нижнего угла */

int border; /* без бордюра если 0 */

register int i;

int choice, vmode;

unsigned char *p;

if(num>MAX_FRAME)

window_puts(0, "Too many windows\n");

return 0;

if((startx>24) || (starty>78) || (starty<0))

window_puts(0, "range error");

return 0;

if((endx>24) || (endy>79))

window_puts(0, "window won't fit");

return 0;

/* создать структуру окна */

frame[num].startx = startx; frame[num].endx = endx;

frame[num].starty = starty; frame[num].endy = endy;

frame[num].p = wp;

frame[num].header = header;

frame[num].border = border;

frame[num].active = 0;

frame[num].curx = 0; frame[num].cury = 0;

return 1;

/* Деактивировать окно и удалить его с экрана */

deactivate(num)

int num;

/* установить курсор в левый верхний угол */

frame[num].curx = 0;

frame[num].cury = 0;

restore_video(num);

/* Вывести заголовок окна в соответсвующее поле */

void display_header(num)

int num;

register int i, y, len;

y = frame[num].starty;

/* Вычислить точное значение центральной позиции заголовка

если отрицательное - заголовок не может быть выведен

*/

len = strlen(frame[num].header);

len = (frame[num].endy - y - len) / 2;

if(len<0) return; /* don't display it */

y = y +len;

write_string(frame[num].startx, y,

frame[num].header, NORM_VID);

void draw_border(num)

int num;

register int i;

char far *v, far *t;

v = vid_mem;

t = v;

for(i=frame[num].startx+1; i<frame[num].endx; i++)

v += (i*160) + frame[num].starty*2;

*v++ = 179;

*v = NORM_VID;

v = t;

v += (i*160) + frame[num].endy*2;

*v++ = 179;

*v = NORM_VID;

v = t;

for(i=frame[num].starty+1; i<frame[num].endy; i++)

v += (frame[num].startx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v = t;

v += (frame[num].endx*160) + i*2;

*v++ = 190;

*v = NORM_VID;

v = t;

write_char(frame[num].startx, frame[num].starty, 218, NORM_VID);

write_char(frame[num].startx, frame[num].endy, 191, NORM_VID);

write_char(frame[num].endx, frame[num].starty, 192, NORM_VID);

write_char(frame[num].endx, frame[num].endy, 217, NORM_VID);

/**************************************************************/

/* Оконные функции ввода/вывода */

/**************************************************************/

/* Вывести строку начиная с текущей позиции курсора

описанного окна.

Возвратить 0 если окно не активное; и 1 в противном случае.

*/

window_puts(num, str)

int num;

char *str;

/* убедиться, что окно активное */

if(!frame[num].active) return 0;

for( ; *str; str++)

window_putchar(num, *str);

return 1;

/* Вывести символ в текущую позицию курсора

описанного окна.

Возвратить 0 если окно не активное, и 1 в противном случае.

*/

window_putchar(num, ch)

int num;

char ch;

register int x, y;

char far *v;

/* убедиться, что окно активное */ if(!frame[num].active) return 0;

x = frame[num].curx + frame[num].startx + 1; y = frame[num].cury + frame[num].starty + 1;

v = vid_mem;

v += (x*160) + y*2; /* вычислить адрес */ if(y>=frame[num].endy)

return 1;

if(x>=frame[num].endx)

return 1;

if(ch=='\n') /* символ перехода на новую строку */ x++;

y = frame[num].startx+1;

v = vid_mem;

v += (x+160) + y*2; /*
вычислить адрес */ frame[num].curx++;
/* инкрементировать X */ frame[num].cury = 0; /* сбросить Y */

else

frame[num].cury++;

*v++ = ch; /* вывести символ */

*v++ = NORM_VID; /* нормальные атрибуты символа */

window_xy(num, frame[num].curx, frame[num].cury);

return 1;

/* Установка курсора в заданную позицию окна.

Возвращает 0 при выходе за границу; не ноль в противном случае.

*/

window_xy(num, x, y)

int num, x, y;

if(x<0 || x+frame[num].startx>=frame[num].endx-1)

return 0;

if(y<0 || y+frame[num].starty>=frame[num].endy-1)

return 0;

frame[num].curx = x;

frame[num].cury = y;

return 1;

/* Считать строку из окна. */

void window_gets(num, s)

int num;

char *s;

char ch, *temp;

temp = s;

for(;;)

ch = window_getche(num);

switch(ch)

case '\r': /* нажата клавиша ENTER */

*s='\0';

return;

case BKSP: /* возврат */

if(s>temp)

s--;

frame[num].cury--;

if(frame[num].cury<0) frame[num].cury = 0;

window_xy(num, frame[num].curx, frame[num].cury); write_char(frame[num].startx+ frame[num].curx+1,

frame[num].starty+frame[num].cury+1, ' ', NORM_VID);

break;

default: *s = ch;

s++;

/* Ввод символа с клавиатуры в окно.

Возвращает полный 16-разрядный скан-код.

/*

window_getche(num)

int num;

union inkey

char ch[2];

int i;

c;

if(!frame[num].active) return 0; /* window not active */

window_xy(num, frame[num].curx, frame[num].cury);

c.i = bioskey(0); /* обработать нажатие клавиши */

if(c.ch[0])

switch(c.ch[0])

case '\r': /* нажата клавиша ENTER */

break;

case BKSP: /* возврат */

break;

default:

if(frame[num].cury+frame[num].starty < frame[num].endy-1)

write char(frame[num].startx+ frame[num].curx+1,

frame[num].curx--;

window_xy(num, frame[num].curx, frame[num].cury);

return c.i;

/* Очистить до конца строки */

void window_cleol(num)

int num;

register int i, x, y;

x = frame[num].curx;

y = frame[num].cury;

window_xy(num, frame[num].curx, frame[num].cury);

for(i=frame[num].cury; i<frame[num].endy-1; i++)

window_putchar(num,' ');

window_xy(num, x, y);

/* Переместить курсор на одну строку вверх.

При успешном завершении вернуть ненулевое значение; в противном случае - 0.

*/

window_upline(num)

int num;

if(frame[num].curx>0)

frame[num].curx--;

window_xy(num, frame[num].curx, frame[num].cury); return 1;

return 0;

/* Переместить курсор на одну строку вниз.

При успешном завершении вернуть ненулевое значение; в противном случае - 0.

*/

window_downline(num)

int num;

if(frame[num].curx<frame[num].endx-frame[num].startx-1 frame[num].curx++;

window_xy(num, frame[num].curx, frame[num].cury);

return 1;

return 1;

/* вернуться на одну позицию назад */

window_bksp(num)

int num;

if(frame[num].cury>0)

frame[num].cury--;

window_xy(num, frame[num].curx, frame[num].cury);

window_putchar(num, ' ');

frame[num].cury--;

window_xy(num, framenum.curx, frame[num].cury);

/********************************************************/

/* Дополнительные функции */

/********************************************************/

/* Вывести строку с установленными атрибутами */

void write_string(x, y, p, attrib)

int x, y;

char *p;

int attrib;

register int i;

char far *v;

v = vid_mem;

v += (x*160) + y*2; /* вычислить адрес */

for(i+y; *p; i++)

*v++ = *p++; /* вывести символ */

*v++ = attrib; /* вывести атрибуты */

/* Вывести символ с утановленными атрибутами */

void write_char(x, y, ch, attrib)

int x, y;

char ch;

int attrib;

register int i;

char far *v;

v = vid_mem;

v += (x*160) +y*2;

*v++ = ch; /* вывести символ */

*v = attrib; /* вывести атрибуты */

/* Сохранить содержимое области экрана */

void save_video(num)

int num;

register int i,j;

char far *v, far *t;

char *but_ptr;

but_ptr = frame[num].p;

v = vid_mem;

t=v;

for(i=frame[num].starty; i<frame[num].endy+1; i++)

for(j=frame[num].startx; j<frame[num].endx+1; j++)

t = (v + (j*160) + i*2);

*buf_ptr++ = *t++;

*buf_ptr++ = *t;

*(t-1) = ' '; /* очистить окно */

/* Восстановить содержимое области экрана */

void save_video(num)

int num;

register int i,j;

char far *v, far *t;

char *but_ptr;

but_ptr = frame[num].p;

v = vid_mem;

t=v;

for(i=frame[num].starty; i<frame[num].endy+1; i++)

for(j=frame[num].startx; j<frame[num].endx+1; j++)

v = t;

v += (j*160) + i*2;

*v++ = *but_ptr++; /* вывести символ */

*v = *but_ptr++; /* вывести атрибуты */

frame[num].active = 0; /* восстановить изображение */

/* Возвращает код текущего видеорежима */

video_mode()

union REGS r;

r.h.ah =15; /* получить код видеорежима */

return int86(0x10, &r, &r) & 255;

/**********************************************************

калькулятор **********************************************************/

#define MAX 100

int *p; /* указатель стека */

int *tos; /* указатель вершины стека */

int *bos; /* указатель дна стека */

char in[80], out[80];

int stack[MAX];

/* Стековый, с постфиксной записью калькулятор

на четыре функции */

void calc()

int answer;

int a, b;

p = stack;

tos = p;

bos = p + MAX - 1;

window(0);

do

window_xy(0, 0, 0);

window_cleol(0);

window_puts(0, " : "); /* промптер калькулятора */

window_gets(0, in);

window_puts(0, " \n ");

window_cleol(0);

switch(*in)

case '+ ':

a = pop();

b = pop();

answer = a + b;

push(a+b);

break;

case '-':

a = pop();

b = pop();

answer = b-a;

push(b-a);

break;

case '- ':

a = pop();

b = pop();

answer = b*a;

push(b*a);

break;

case '/ ':

a = pop();

b = pop();

if(a==0)

window_puts(0, "divide by 0\n");

break;

answer = b/a;

push(b/a);

break;

default:

push(atoi(in));

continue;

itoa(answer, out, 10);

window_puts(0, out);

while(*in);

deactivate(0);

/* Поместить число в стек.

Возвратить 1 при успешном завершении; и 0, если стек переполнен

*/

push(i)

int i;

if(p>bos) return 0;

*p = i;

p++;

return 1;

/* Извлечь верхний элемент стека Возвратить 0, если стек пуст.

*/

pop()

p--;

if(p<tos)

p++;

return 0;

return *p;

Вы можете сразу вводить эту программу в ЭВМ. Для того, чтобы установить прикладную часть, запустите ее на выполнение. Для вызова калькулятора нажмите клавишу PT SCR.

Использование прерывания по нажатию клавиши.

Прерывание печати экрана очень просто использовать, но у него есть три крупных недостатка. Во-первых, оно позволяет быть резидентным в системе только прикладной части TSR-программы. Во-вторых, вы не можете при этом пользоваться печатью экрана. В-третьих, это решение проблемы "в лоб", и потому оно не очень хорошее. Лучшим способом запуска TSR-программы является использование прерывания 9 по нажатию клавиши. Прерывание 9 выполняется при каждом нажатии клавиши на клавиатуре.

При использовании прерывания 9 для запуска TSR-программ должны соблюдаться следующие основные положения. Во-первых, Вы должны переписать адрес из таблицы векторов, соответствующий прерыванию 9, в такое место таблицы, которое соответствует неиспользуемому DOS прерыванию. Мы будем использовать прерывание

60. Затем, занесите адрес точки входа в вашу TSR-программу по адресу прерывания 9 в таблице векторов. После запуска ваша TSR-программа первым делом вызовет через прерывание драйвер ввода с клавиатуры. Затем проверяется, не соответствует ли введенный символ "горячей клавише", которая используется для запуска прикладной части TSR-программы. Если соответствует, то прикладная часть начинает выполняться, в противном случае никакого действия не производится и TSR-программа деактивируется. Таким образом, при каждом нажатии происходит обращение к функции, реагирующей на нажатие клавиш, но прикладная часть TSR-программы запускается только при нажатии определенной клавиши.

Использование прерывания по нажатию клавиши имеет два преимущества. Во-первых, при этом нет никакой потери функциональных возможностей. Во-вторых, появляется возможность использовать одновременно несколько различных прикладных частей TSR-программы, вызов которых осуществляется нажатием соответствующих им различных "горячих клавиш". Представленная в данном разделе TSR-программа использует эту возможность и включает в свой состав и "калькулятор", и "записную книжку" (из раздела 2), которые вызываются отдельно друг от друга.

Прежде, чем использовать эту возможность, вы должны узнать кое-что об обработке BIOS нажатий клавиш.

Буфер символов, введенных с клавиатуры.

Как вы знаете, стандартные версии DOS буферизуют до 15 символов, введенных с клавиатуры, что позволяет выполнить ввод с опережением. При каждом нажатии клавиши наступает прерывание 9. Программа ISR реакции на нажатие клавиши принимает код символа из порта и помещает его в буфер. Когда вы обращаетесь к функциям DOS или BIOS ввода с клавиатуры, обрабатывается только содержимое буфера, а не текущее содержимое порта. Это позволяет вашим программам непосредственно обрабатывать содержимое буфера символов, так же, как это делают программы BIOS и DOS. Таким образом, это позволяет функции реагирования на нажатие клавиш вашей TSR-программы определять, была ли нажата "горячая клавиша", не уничтожая при этом содержимого буфера символов.

Буфер ввода с клавиатуры расположен по адресу 0000:041 (1054 в десятичной системе счисления ). Поскольку при каждом нажатии клавиши формируется 16-битный скан-код, то для ввода 15 символов требуется 30 байт. Однако обычно используются 32 байта, т.к. скан -код клавиши RETURN автоматически добавляется к концу буфера. Буфер организован в виде циклической очереди, доступ к которой осуществляется через указатели начала и конца очереди. Указатель начала указывает на символ, который был введен последним. Указатель конца указывает на следующий символ, который будет передан по запросу на ввод символа от DOS или BIOS. Указатель начала хранится по адресу 0000:041C (1052 в десятичной с.с.). Значения указателей начала и конца фактически используются для индексной адресации очереди, и соответствует индексу текущей позиции +30. (Это связано с особенностями выполнения косвенной адресации процессором 8086). Значения указателей начала и конца очереди совпадают в том случае, если очередь пуста.

Функция инициализации.

Для прикладной TSR-программы, представленной в данном разделе, требуется небольшая по объему программа инициализации. Она оформлена в виде функции main(), которая приводится ниже.

main()

struct address

char far *p;

temp;

/* указатель вектора прерывания 9 */

struct address far *addr = (struct address far *) 36;

/* указатель вектора прерывания 60 */

struct address far *int9 = (struct address far *) 240;

/* Поместить адрес обработки прерывания от клавиатуры

в вектор прерывания 60. Если вектора прерываний 60 и

61 содержат одинаковые адреса, то TSR-программа не

была запущена.

*/

if(int9->p == (int9+1)->p)

int9->p = addr->p;

addr->p = (char far *) tsr_ap;

printf("tsr installed - F2 for note pad, F3 for calculator ");

else

printf ("tsr application already initialized\n ");

exit(1);

set_vid_mem();

tsr(2000);

Следует отметить, что данная версия программы не допускает, чтобы ее запускали более одного раза в течение одного сеанса работы. Это связано с тем, что повторный запуск программы приведет к записи адреса точки входа в TSR-программу в таблицу векторов по адресу 60-го прерывания, а содержавшийся там адрес программы реакции на нажатие клавиши будет запорчен. Во время работы функции проверяется, совпадает ли содержимое таблицы векторов, соответствующее прерываниям 60 и 61. (Прерывание 61 также не используется DOS). DOS обрабатывает все неиспользуемые ею прерывания одной и той же программой обработки недопустимого прерывания. Следовательно, перед запуском TSR-программы эти адреса будут совпадать, а после запуска они будут различны.

Прикладная часть TSR-программы.

Применяемая здесь функция входа в TSR-программу является более сложной, чем при использовании прерывания по печати экрана. Первым делом она должна сформировать прерывание 60, для того чтобы ввод с клавиатуры осуществлялся стандартной программой ввода. Большинство компиляторов Си имеют функцию генерации прерывания. В Турбо Си это функция geninterrupt(), параметром которой является номер того прерывания, которое вы хотите вызвать. После возврата из прерывания 60 ваша функция должна проверить содержимое очереди, адресуемое с помощью указателя начала, на предмет того, не была ли нажата "горячая клавиша". Для представленной здесь программы "горячими" являются клавиши F2 и F3 с позиционными кодами 60 и 61 соответственно. Нажатие "горячей клавиши" должно быть обнаружено прежде, чем управление будет передано прикладной части программы. Глобальная переменная busy используется для того, чтобы предотвратить одновременное использование обеих прикладных частей программы, поскольку большинство компиляторов Си не позволяют создавать повторно-входимые программы. Если одна из прикладных частей активна, то другой части активация в этот момент запрещена. Функция tsr_ap() приводится ниже.

/* Точка входа в прикладную часть TSR-программы */

void interrupt tsr_ap()

char far *t = (char far *) 1050; /* адрес указателя заголовка *

geninterrupt(60);

if(*t != *(t+2)) /* если не пустой */

t += *t - 30 + 5; /* перейти к позиции символа */

if(*t == 60 || *t == 61)

bioskey(0); /* сбросить клавиши F2/F5 */

if(!busy)

busy = !busy;

Следует отметить, что параметром функции window_main() является позиционный код "горячей клавиши", для того, чтобы она могла осуществить выбор соответствующей прикладной части.

/* создать окно */

window_main(which)

int which;

union inkey

char ch[2];

int i;

c;

int i;

char ch;

/* во-первых, создать рамку окна */

make_window(0," Notepad [F1 to exit] ", 5, 20, 17, 60, BORDER);

make_window(1, " Colculator ", 8, 20, 12, 60, BORDER);

/* использовать window() для активации созданного окна */

switch(which)

case 60:

notepad();

break;

case 61:

calc();

break;

Вы можете сразу вводить в ЭВМ представленную здесь программу. После того, как вы ее запустите, клавишей F2 будет выбираться программа "записная книжка", а клавишей F3 - "калькулятор".

/* Программа, остающаяся резидентной после завершения и

использующая прерывание 9 от клавиатуры.

*/

#include "dos.h "

#include "stdlib.h "

#include "ctype.h "

#define BORDER 1

#define ESC 27

#define MAX_FRAME 2

#define REV_VID 0x70

#define NORM_VID 7

#define BKSP 8

void interrupt tsr_ap();

void save_video(), restore_video();

void write_string(), write_char();

void display_header(), draw_border();

void window_gets();

void window_cleol(), window();

void notepad(), calc();

char far *vid_mem;

char wp[4000]; /* буфер для хранения текущего

содержимого экрана */

struct window_trame

int startx, endx, starty, endy;

int curx, cury; /* текущее положение курсора в окне */

unsigned char *p; /* указатель в буфере */

char *header; /* сообщение заголовка */

int border; /* включение/отключение бордюра */

int active; /* выводить/не выводить на экран */

frame [MAX_FRAME];

char in[80], out[80];

/* busy установлена в 1,когда программа активна, иначе - в 0 */

char busy = 0;

main()

struct adaress

char far *p;

temp;

/* указатель на вектор прерывания 9 */

struct address far *addr = (struct address far *) 36;

/* указатель на вектор прерывания 60 */

struct address far *int9 = (struct address far *) 240;

/* Поместить адрес программы обработки прерывания от клавиатуры

по адресу прерывания 60. Если вектора прерываний 60 и 61

содержат одинаковые адреса, то TSR-программа не была запущена. */

if (int9->p == (int9+1)->p)

int9->p = addr->p;

addr->p = (char far *) tsr_ap;

printf ("tsr installed - F2 for note pad, F3 for calculator");

else

printf ("tsr application already initialized\n");

exit (1);

set_vid_mem();

tsr (800);

set_vid_mem()

int vmode;

vmode = video_mode();

if(( vmode != 2) && ( vmode != 3) && ( vmode != 7))

printf("video must be in 80 column text mode");

exit(1);

/* установить соответствующий адрес видеопамяти */

if(vmode==7) vid_mem = (char far *) 0xB0000000;

else vid_mem = (char far *) 0xB8000000;

/* Точка входа в прикладную часть TSR-программы */

void interrupt tsr_ap()

char far *t = (char far *) 1050; /* адрес указателя заголовка *

geninterrupt(60);/* читать символ */

if(*t != *(t+2)) /* если не пусто */

t += *t-30+5; /* перейти к позиции символа */

if(*t == 60 || *t == 61)

bioskey(0); /* сбросить клавиши F2/F3 */

if(!busy)

busy = !busy;

window_main(*t);

busy = !busy;

/* завершить но оставить резидентной */

tsr(size)

unsigned size;

union REGS r;

r.h.ah = 49; /* завершить и оставить резидентной */

r.h.al = 0; /* код возврата */

r.x.dx = size; /* размер программы/16 */

int86(0x21, &r, &r);

/* создать окно */

window_main(which)

int which;

union inkey

char ch[2];

int i;

c;

int i;

char ch;

/* во-первых, создать рамку окна */

make_window(0, " Notepad [F1 to exit] ", 5, 20, 17, 60, BORDER);

make_window(1, " Calculator ", 8, 20, 12, 60, BORDER);

/* использовать window() для активации созданного окна */

switch(which)

case 60:

notepad();

break;

case 61:

calc();

break;

/***************************************************************/

/* Функции управления окнами */

/***************************************************************/

/* Вывести спускающееся окно */

void window(num)

int num; /* номер окна */

/* сделать окно активным */

if(!frame[num].active) /* используется не постоянно */

save_video(num); /* сохранить текущий экран */

frame[num].active = 1; /* установить флаг активности */

if(frame[num].border) draw_border(num);

display_header(num); /* вывести окно */

/* Создать рамку спускающегося окна.

Если рамка может быть создана, возвращается 1, в противном случае возвращается 0.

*/

make_window(num, header, startx, starty, endx, endy, border) int num; /* номер окна */

char *header; /* текст заголовка */

int startx, starty; /* координаты X,Y верхнего левого угла */ int endx, endy; /* координаты X,Y нижнего правого угла */

int border; /* без бордюра если 0 */

register int i;

int choice, vmode;

unsigned char *p;

if(num>MAX_FRAME)

window_puts(0, "Too many windows\n");

return 0;

if((startx>24) || (startx<0) || (starty>78) || (starty<0)) window_puts(0, "range error");

return 0;

if((endx>24) || (endy>79))

window_puts(0, "window won't fit");

return 0;

/* Отвести достаточное количество памяти */

p= (unsigned char *) malloc(2*(endx-startx+1)*(endy-starty+1));

if(!p) exit(1); /* поместите здесь ваш собственный

обработчик ошибок */

/* создать рамку */

frame[num].startx = startx; frame[num].endx = endx;

frame[num].starty = starty; frame[num].endy = endy;

frame[num].p = wp;

frame[num].header = header;

frame[num].border = border;

frame[num].active = 0;

frame[num].curx = 0; frame[num].cury = 0;

return 1;

/* Деактивировать окно и удалить его с экрана */

deactivate(num)

int num;

/* установить курсор в левый верхний угол */

frame[num].curx = 0;

frame[num].cury = 0;

restore_video(num);

/* Вывести текст заголовка в соответствующее поле */

void display_header(num)

int num;

register int i, y, len;

y = frame[num].starty;

/* Вычислить начальную позицию относительно

центра заголовка, если отрицательная, то

сообщение не подходит.

*/

len = strlen(frame[num].header);

len = (frame[num].endy - y - len) / 2;

if(len<0) return; /* не выводить его */

y = y +len;

write_string(frame[num].startx, y,

frame[num].header, NORM_VID);

void draw_border(num)

int num;

register int i;

char far *v, far *t;

v = vid_mem;

t = v;

for(i=frame[num].startx+1; i<frame[num].endx; i++)

v += (i*160) + frame[num].starty*2;

*v++ = 179;

*v = NORM_VID;

v = t;

v += (i*160) + frame[num].endy*2;

*v++ = 179;

*v = NORM_VID;

v = t;

for(i=frame[num].starty+1; i<frame[num].endy; i++)

v += (frame[num].startx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v = t;

v += (frame[num].endx*160) + i*2;

*v++ = 196;

*v = NORM_VID;

v = t;

write_char(frame[num].startx, frame[num].starty, 218, NORM_VID);

write_char(frame[num].startx, frame[num].endy, 191, NORM_VID);

write_char(frame[num].endx, frame[num].starty, 192, NORM_VID);

write_char(frame[num].endx, frame[num].endy, 217, NORM_VID);

/*************************************************************/

/* Оконные функции ввода/вывода */

/*************************************************************/

/* Вывести строку начиная с текущей позиции

в созданном окне.

Возвратить 0, если окно не активное, и 1 - в противном случае.

*/

window_puts(num, str)

int num;

char *str;

/* убедитесь, что окно активное */ if(!frame[num].active) return 0; for( ; *str; str++)

window_putchar(num, *str);

return 1;

/* Вывести символ в текущую позицию курсора

в созданном окне

Возвратить 0, если окно не активное, и 1 - в противном случае.

*/

window_putchar(num, ch)

int num;

char ch;

register int x, y;

char far *v;

/* убедитесь, что окно активное */ if(!frame[num].active) return 0;

x = frame[num].curx + frame[num].startx + 1;

y = frame[num].cury + frame[num].starty + 1;

v = vid_mem;

v += (x*160) + y*2; /* вычислить адрес */

if(y>=frame[num].endy)

return 1;

if(x>=frame[num].endx)

return 1;

if(ch=='\n') /* символ перехода к новой строке */

x++;

y = frame[num].startx+1;

v = vid_mem;

v += (x*160) + y*2; /* вычислить адрес */

frame[num].curx++; /* нарастить X */

frame[num].cury = 0; /* сбросить Y */

else

frame[num].cury++;

*v++ = ch; /* вывести символ */

*v++ = NORM_VID; /* нормальные атрибуты символа */

window_xy(num, frame[num].curx, frame[num].cury);

return 1;

/* Установить курсор в определенной позиции окна.

Возвратить 0 при выходе за границу; и не ноль в противном случае.

*/

window_xy(num, x, y)

int num, x, y;

if(x<0 || x+frame[num].startx>=frame[num].endx-1) return 0;

if(y<0 || y+frame[num].starty>=frame[num].endy-1)

return 0;

frame[num].curx = x;

frame[num].cury = y;

return 1;

/* Считать строку из окна */

void window_gets(num, s)

int num;

char *s;

char ch, *temp;

char out[10];

temp = s;

for(;;)

ch = window_getche(num);

switch(ch)

case '\r': /* нажата клавиша ENTER */

*s='\0';

return;

case BKSP: /* возврат на шаг */

if(s>temp)

s--;

frame[num].cury--;

if(frame[num].cury<0) frame[num].cury = 0;

window_xy(num, frame[num].curx, frame[num].cury);

write_char(frame[num].startx+ frame[num].curx+1, frame[num].starty+frame[num].cury+1,' ',NORM_VID);

break;

default: *s = ch;

s++;

/* Ввести символ в окно.

Возвратить полный 16-разрядный скан-код

*/

window_getche(num)

int num;

union inkey

char ch[2];

int i;

c;

if(!frame[num].active) return 0; /* окно не активное */

window_xy(num, frame[num].curx, frame[num].cury);

c.i = bioskey(0); /* обработать нажатие клавиши */

if(c.ch[0])

switch(c.ch[0])

case'\r': /* нажата клавиша ENTER */

break;

case BKSP: /* возврат на шаг */

break;

default:

if(frame[num].cury+frame[num].starty < frame[num].endy-1)

write_char(frame[num].startx+ frame[num].curx+1,

frame[num].starty+frame[num].cury+1, c.ch[0], NORM_VID);

frame[num].cury++;

if(frame[num].curx < 0) frame[num].curx = 0;

if(frame[num].curx+frame[num].startx > frame[num].endx-2)

frame[num].curx--;

window_xy(num, frame[num].curx, frame[num].cury);

return c.i;

/* Очистить до конца строки */

void window_cleol(num)

int num;

register int i, x, y;

x = frame[num].curx;

y = frame[num].cury;

window_xy(num, frame[num].curx, frame[num].cury);

for(i=frame[num].cury; i<frame[num].endy-1; i++)

window_putchar(num,' ');

window_xy(num, x, y);

/* Переместить курсор вверх на одну строку.

Возвратить не ноль в случае успеха и 0 в противном случае.

*/

window_upline(num)

int num;

if(frame[num].curx>0) frame[num].curx--;

window_xy(num, frame[num].curx, frame[num].cury);

return 1;

return 0;

/* Переместить курсор вниз на одну строку.

Возвратить не ноль в случае успеха и 0 в противном случае.

*/

window_downline(num)

int num;

if(frame[num].curx<frame[num].endx-frame[num].startx-1) frame[num].curx++;

window_xy(num, frame[num].curx, frame[num].cury);

return 1;

return 1;

/* назад на один символ */

window_bksp(num)

int num;

if(frame[num].cury>0)

frame[num].cury--;

window_xy(num, frame[num].curx, frame[num].cury);

window_putchar(num, ' ');

frame[num].cury--;

window_xy(num, frame[num].curx, frame[num].cury);

/***************************************************************/

/* Дополнительные функции */

/***************************************************************/

/* Вывести строку с указанными атрибутами */

void write_string(x, y, p, attrib)

int x, y;

char *p;

int attrib;

register int i;

char far *v;

v=vid_mem;

v += (x*160) + y*2; /* вычислить адрес */

for(i=y; *p; i++)

*v++ = *p++; /* записать символ */

*v++ = attrib; /* записать атрибуты */

/* Вывести символ с указанными атрибутами */

void write_char(x, y, ch, attrib)

int x, y;

char ch;

int attrib;

register int i;

char far *v;

v = vid_mem;

v += (x*160) + y*2;

*v++ = ch; /* записать символ */

*v = attrib; /* записать атрибуты */

/* Сохранить область экрана */

void save_video(num)

int num;

register int i, j;

char *buf_ptr;

char far *v, far *t;

buf_ptr = frame[num].p;

v = vid_mem;

for(i=frame[num].starty; i<frame[num].endy+1; i++)

for(j=frame[num].startx; j<frame[num].endx+1; j++)

t = (v + (j*160) + i*2);

*buf_ptr++ = *t++;

*buf_ptr++ = *t;

*(t-1) = ' '; /* очистить окно */

/* Восстановить область экрана */

void restore_video(num)

int num;

register int i,j;

char far *v, far *t;

char *buf_ptr;

buf_ptr = frame[num].p;

v = vid_mem;

t = v;

for(i=frame[num].starty; i<frame[num].endy+1; i++)

for(j=frame[num].startx; j<frame[num].endx+1; j++)

v = t;

v += (j*160) + i*2;

*v++ = *buf_ptr++; /* записать симпол */

*v = *buf_ptr++; /* записать атрибуты */

frame[num].active = 0;/* восстановить изображение */

/* Возвратить код текущего видеорежима */

video_mode()

union REGS r;

r.h.ah = 15; /* получить код видеорежима */

return int86(0x10, &r, &r) & 255;

/****************************************************/

/* Функции всплывающих окон */

/****************************************************/

#define MAX 100

int *p; /* указатель стека */

int *tos; /* указатель вершины стека */

int *bos; /* указатель дна стека */

int stack[MAX];

/* Стековый, с постфиксной записью калькулятор

с четырьмя функциями

*/

void calc()

int answer;

int a,b;

p = stack;

tos = p;

bos = p+MAX-1;

window(1);

do

window_xy(1, 0, 0);

window_cleol(1);

window_puts(1, ": "); /* промптер калькулятора */

window_gets(1, in);

window_puts(1, "\n ");

window_cleol(1);

switch(*in)

case '+':

a = pop();

b = pop();

answer = a+b;

push(a+b);

break;

case '-':

a = pop();

b = pop();

answer = b-a;

push(b-a);

break;

case '* ':

a = pop();

b = pop();

answer = b*a;

push(b*a);

break;

case '/ ':

a = pop();

b = pop();

if(a==0)

window_puts(0, "divide by 0\n");

break;

answer = b/a;

push(b/a);

break;

default:

push(atoi(in));

continue;

itoa(answer, out, 10);

window_puts(1, out);

while(*in);

deactivate(1);

/* Поместить число в стек.

Возвратить 1 в случае успеха и 0 если стек переполнен

*/

push(i)

int i;

if(p>bos) return 0;

*p = i;

p++;

return 1;

/* Извлечь верхний элемент из стека. Возвратить 0 если стек пуст.

*/

pop()

p--;

if(p<tos)

p++;

return 0;

return *p;

/**********************************************************/

/* Всплывающая записная книжка */

#define MAX_NOTE 10

#define BKSP 8

char notes[MAX_NOTE] [80];

void notepad()

static first = 1;

register int i, j;

union inkey

char ch[2];

int i;

c;

char ch;

/* инициализировать массив записей если это необходимо */

if(first)

for(i=0; i<MAX_NOTE; i++)

*notes[i] = '\0 ';

first = !first;

window(0);

/* вывести существующие записи */

for(i=0; i<MAX_NOTE; i++)

if(*notes[i]) window_puts(0, notes[i]);

window_putchar(0, '\n ');

i = 0;

window_xy(0, 0, 0);

for(;;)

c.i = bioskey(0); /* обработать нажатие клавиши */

if(tolower(c.ch[1])==59) /* F1 для выхода */

deactivate(0);

break;

/* если обычная клавиша */

if(isprint(c.ch[0]) || c.ch[0]==BKSP)

window_cleol(0);

notes[i][0] = c.ch[0];

j = 1;

window_putchar(0, notes[i][0]);

do

ch = window_getche(0);

if(ch == BKSP)

if( j>0 )

j--;

window_bksp(0);

else

notes[i][j] = ch;

j++;

while(notes[i][j-1]! = '\r ');

notes[i][j-1] = '\0 ';

i++;

window_putchar(0, '\n ');

else /* если специальная клавиша */

switch(c.ch[1])

case 72: /* стрелка вверх */

if(i>0)

i--;

window_upline(0);

break;

case 80: /* стрелка вниз */

if(i<MAX_NOTE-1)

i++;

window_downline(0);

break;

Тайна 28-го прерывания

Есть одно средство DOS, которое не описывается в документации, и которое может сделать TSR-программы более надежными в тот период времени, когда они используют много системных ресурсов. Вообще говоря, если прикладная часть вашей TSR-программы занимается обменом только с консолью, то вы застрахованы от неприятностей. Неприятности могут возникнуть при использовании таких объектов, как дисковые файлы или порты вводавывода. Хотя это и не описано в технических руководствах по операционной системе, но DOS вызывает прерывание 28Н, когда она находится в "безопасном", т.е. холостом состоянии. Как вам наверное известно, при выполнении определенных функций DOS, которые относятся к критическим участкам, после начала их выполнения прерывания должны быть запрещены. Прерывание 28Н никогда не вызывается DOS во время выполнения критического участка. Вы можете использовать это средство для защиты от сбоев вашей TSR-программы. Хотя здесь не представлено никаких примеров программ, но предлагаются следующие общие соображения по этому вопросу.

Главное отличие, которое вы должны иметь ввиду при использовании прерывания 28Н, заключается в способе активации прикладной части вашей TSR-программы. Когда вызывается прерывание 28Н, прикладная часть TSR-программы не может больше активироваться через программу обработки нажатий клавиш. Вместо этого программа обработки нажатий клавиш при нажатии "горячей клавиши" просто устанавливает флаг (в дальнейшем именуемый is-hotkey). Перед тем, как прикладная часть вашей TSR-программы может быть вызвана, вы должны создать новый обработчик прерывания 28Н, который будет проверять, установлен флаг is-hotkey или нет. Если установлен, то прикладная часть активируется, сбрасывая при этом флаг is-hotkey. При этом вы обязательно должны не просто изменить первоначальное содержимое вектора прерывания 28Н, а напротив, сохранить его и вызывать исходное прерывание 28Н из вашего обработчика 28-го прерывания.

Если вы собираетесь продавать ваши TSR-программы, то должны обязательно использовать прерывание 28Н (хотя по нему и нет документации), поскольку оно позволяет избежать случайных прерываний DOS во время выполнения критических участков.

Проблемы при создании TSR-программ

TSR-программы по своей природе очень склонны к сбоям. Например, использование TSR-программы, разработанной одним программистом, часто делает невозможным одновременное использование другой TSR-программы, разработанной другим программистом, поскольку обе программы будут пытаться переназначить адрес программы обработки прерывания 9 в таблице векторов на себя. Несомненно, при использовании своих TSR-программ вы можете избежать такого рода проблем, но будьте внимательны, если в вашей системе присутствует чужая TSR-программа.


Глава 4. Графика

В этой главе приводится базовый набор функций графики, которые позволяют рисовать (отображать на экране) точки, линии, прямоугольники, окружности, используя возможности графических адаптеров CGA или EGA. Эти программы используются как основа, на которой строятся функции графики более высокого уровня. Для более детального изучения этих базовых графических функций предлагается книга "С: The Complete Reference" Herb Schild (Osborn / McGrow-Hall, 1987).

Помимо краткого представления базовых функций графики, в этой главе приводятся следующие программы:

- сохранение графических изображений в файле на диске;

- загрузка графических изображений из файла;

- вращение объектов в двумерном пространстве;

- копирование или пересылка графических изображений.

В конце главы привевен текст "программы-художника", позволяющей рисовать на экране терминала, с использованием клавиш перемещения курсора.

По мере возрастания вашего опыта по использованию функций графики, вы сможете самостоятельно разрабатывать хорошие программы. Например, используя функции сохранения и загрузки графических изображений, вы сможете создавать графики или диаграммы и успешно использовать их в случае необходимости. Используя функции вращения изображений объектов, вы сможете разрабатывать программы "звездных войн" - образец "живой" графики, которые будут представлять большой интерес для вас. Вы сможете также, использовать эти функции как основу для использования систем автоматизированного проектирования (CAD/CAM system).

Для корректного использования программ, описаных в этой главе, вам необходимы компьютеры типа IBM PC XT, IBM PC AT или другие совместимые с ними модели, снабженные графическими адаптерами CGA или EGA. Все программы, приведенные в данной главе, кроме программ изображения точки, аппаратно-независимы, и вы можете с минимальными усилиями сделать их рабочими на других типах графических машин.

Видеорежимы и цветовая палитра

Перед использованием каких-либо функций графики компьютер должен быть переведен в соответствующий видеорежим. Для компьютеров типа IBM PC это означает, что должны быть выбраны подходящие режим и палитра.

В таблице 4-1 приведены различные видеорежимы, в которых могут работать компьютеры IBM PC. Для функций, приведенных в этой главе, требуется 4 видеорежим, предполагающий использование цветного графического дисплея с размерностью экрана 320 на 200. Хотя адаптер EGA поддерживает и режимы с расширенной разрешающей способностью дисплея, 4 видеорежим выбран в качестве базового для разработки и использования функций графики в связи с тем, что он поддерживается как адаптером EGA, так и CGA. Использование особых режимов EGA требует только изменения функций записи точки (смотри книгу по использованию графики в EGA "Advance Grafics in C" Nelson Jobson Osborn/McGrow-Hall, 1987). Вам необходимо запомнить, что во всех кодах верхний левый угол имеет координаты 0,0.

BIOS-прерывание 16, функция 0, устанавливает видеорежим и используется в функции mode(), текст которой приведен ниже. Но прежде чем вы ознакомитесь с ней, предлагаем вам внимательно изучить все видеорежимы, поддерживаемые соответствующими адаптерами. Для этого обратитесь к таблице 4-1.

Таблица 4-1 Режимы терминала для машин IBM PC
Режим | Тип Размерность Адаптер экрана
0 1 2 3 4 5 6 7 8 9 10 13 14 15 ----- | алфавитно-цифровой, ч/б 40х25 CGA, EGA алфавитно-цифровой, 16 цв. 40х25 CGA, EGA алфавитно-цифровой, ч/б 80х25 CGA, EGA алфавитно-цифровой, 16 цв. 80х25 CGA, EGA графический, 4 цвета 320х200 CGA, EGA графический, 4 серых тона 320х200 CGA, EGA графический, ч/б 640х200 CGA, EGA алфавитно-цифровой, ч/б 80х25 монохромный графический, 16 цветов 160х200 PCjr графический, 16 цветов 320х200 PCjr графический, 4 цвета-PCjr 640х200 PCjr, EGA 16 цветов-EGA графический, 16 цветов 320х200 EGA графический, 16 цветов 640х200 EGA графический, 4 цвета 640х350 EGA ----------------------------------------------------------
А | теперь приведем текст функции mode().

/* Установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

В 4-ом режиме доступны две палитры (набора цветов). Каждая палитра определяет четыре цвета, отображаемые на экране терминала. В IBM PC палитра 0 определяет желтый, зеленый и красный цвета, палитра 1 определяет белый, ярко-красный (пурпурный) и голубой цвета. Для каждой палитры четвертым цветом является цвет фона, который обычно черный. BIOS-прерывание 16, функция 11, устанавливает палитру.

Функция pallet(), приведенная ниже, устанавливает номер палитры, который задается в качестве значения ее аргумента:

/* Установка палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh=1; /* код 4 графического режима */

r.h.bl=pnum; /* номер палитры */

r.h.ah=11; /* устанавливается для вызова палитры */

int86(0x10,&r,&r);

Запись точки растра

Одной из основных программ графики является программа записи точки растра - наименьшей адресуемой точки на экране дисплея. В данном случае термин "точка растра" будет использоваться для описания наименьшей адресуемой точки в отдельных графических режимах. Так как функция записи точки растра используется в других программах более высокого уровня, ее эффективность очень важна для быстродействия программ верхних уровней, которые непосредственно реализуют функции графики. На IBM PC и совместимых с ними компьютерах существуют два способа вывода информации с использованием точек растра. Первый способ, использующий прерывания ROM-BIOS, является наиболее простым, но и наименее быстродействующим (слишком медленным для наших целей). Вторым и более быстродействующими способом является размещение информации непосредственно в видеопамяти дисплея (ROM). Именно этот метод и рассматривается ниже.

Работа адаптеров CGA/EGA в графическом режиме

Адаптер CGA всегда располагается в видеопамяти по адресу 8000000h. Адаптер EGA имеет аналогичное расположение для тех режимов, которые совместимы с режимами CGA (более полную информацию о аппаратных средствах поддержки графики вы можете получить в руководстве "IBM Technical Reference"). В 4 графическом режиме каждый байт содержит информацию о цвете для 4 точек растра (для каждой точки растра по 2 бита). Следовательно, для работы с экраном размерностью 320 на 200 требуется 16К памяти. Так как два бита могут содержать только 4 различных значения, в 4 видеорежиме поддерживаются только 4 цвета. Значение каждого двухбитового блока определяет цвет в соответствии с таблицей, приведенной ниже:
Значение | Цвет в палитре | 0 | Цвет в палитре | 1
1 2 3 | фон желтый красный зеленый |  | фон голубой пурпурный булый |

Особенность адаптера CGA заключается в том, что четные точки растра будут располагаться по адресу B8000000h, а нечетные - на 2000h (8152 - в десятичном виде) байтов выше, т.е. по адресу B8002000h. Следовательно, каждая строка точек растра требует 80 байтов (40 для четных точек растра, 40 - для нечетных). Внутри каждого байта точки располагаются слева направо, как они появляются на экране терминала. Это означает, что точка растра с номером 0 занимает 6 и 7 биты, в то время, как точка растра с номером 3 - 0 и 1 биты.

Так как каждый байт кодирует значение четырех точек растра, вы должны сохранять значение трех точек при изменении одной из них. Лучший способ сделать это - создание битовой маски со всеми битами, установленными в 1, кроме тех, которые будут изменяться. Значение битовой маски складывается по схеме "И" с действительным значением байта, а затем полученное значение складывается по схеме "ИЛИ" с новым значением. Однако ситуация несколько меняется, если вы хотите сложить по схеме "НЕ-ИЛИ" новое и старое значения. В этом случае вы просто складываете по схеме "ИЛИ" старое значение байта с 0, а затем складываете по схеме "НЕ-ИЛИ" новое двоичное представление цвета и получаете результат. Адрес требуемого байта определяется путем умножения значения координаты X на 40, а затем добавляется значение координаты Y, деленное на

4. Для того,чтобы определить, находится ли точка растра в четном или нечетном блоке памяти, используется остаток от деления значения координаты Х на 2. Если остаток равен 0, блок является четным (используется первый блок), в противном случае - блок нечетный (используется второй блок). Требуемые биты внутри байта вычисляются путем выполнения деления по модулю 4. Остаток

определяет номер двухбитового пакета, который содержит информацию о требуемых точках растра. Для установки байта режима цвета и битовой маски используются операторы побитового сдвига. Хотя манипулирование битами в функции mempoint() несколько запутано, вы, однако, без труда разберетесь в ней, если тщательно изучите что именно и как она делает.

/* Запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "НЕ-ИЛИ" цвета в случае его изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */

bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном коде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "НЕ-ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/* установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor) /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

Заметим, что тип указателя видеопамяти объявлен как far; это необходимо, если вы транслируете в модели маленькой (small) памяти. Вы так же должны заметить, что специальный маркер режима записи XOR, определенный в функциях ROM-BIOS, используется и в функции mempoint().

Вычерчивание линий

Функции вычерчивания линий являются основными подпрограммами графики и используются для отображения линий в заданном цвете путем задания ее начальных и конечных координат. В то время, как изображение вертикальных и горизонтальных линий не представляет особого труда, более трудной задачей является создание функций, которые рисуют линии вдоль диагоналей. Например, какие точки составляют линию, вычерчиваемую от точки с координатами 0,0 до точки с координатами 80,120?

Один из подходов к разработке функций вычерчивания линий использует отношение между смещением по координатам X и Y. Чтобы показать этот подход в действии, проведем линию между точками с координатами 0,0 и 5,10. Смещение по X равно 5, а по Y - 10. Отношение равно 1/2. Оно будет использоватся при определении коэффициента зависимости, по которому должны меняться координаты X и Y при изображении линий. В данном случае это означает, что приращение координаты X составляет половину величины изменения координаты Y. Начинающий программист часто использует этот метод при разработке функций вычерчивания линий. Хотя такой подход математически верен и прост для понимания, для его правильной работы и для того, чтобы избежать серьезных ошибок округления, необходимо использовать математические операции с числами с плавающей точкой. Это означает, что функции вычерчивания линий будут работать довольно медленно, если в систему не будет включен математический сопроцессор (например - Intel 8087). По этой причине этот метод используется довольно редко.

Наиболее общий метод изображения линий включает

использование алгоритма Брезенхама. Хотя основой в нем служит

также отношение между расстояниями по координатам X и Y, в данном

случае не требуется выполнять деление или вычисление чисел с

плавающей точкой. Вместо этого, отношение между значениями

координат X и Y представляется косвенным образом через серии

сложений и вычитаний. Основной идеей алгоритма Брезенхама,

является регистрация средних значений погрешностей между

идеальным положением каждой точки и той позицией на экране

дисплея, в которой она действительно отображается. Погрешность

между идеальным и действительным положением точки возникает ввиду

ограниченных возможностей технических средств. Фактически не

существует дисплеев с бесконечно большой разрешающей

способностью, и, следовательно, действительное положение каждой

точки на линии требует наилучшей аппроксимации. В каждой итерации

цикла вычерчивания линии вызываются две переменные xerr и yerr,

которые увеличиваются в зависимости от изменения величин

координат X и Y соответственно. Когда значение погрешности

достигает определенного значения, оно вновь устанавливается в

исходное положение, а соответствующий счетчик координат

увеличивается. Этот процесс продолжается до тех пор, пока линия

не будет полностью вычерчена. Функция line(), приведенная ниже,

реализует этот метод. Вы должны изучать ее до тех пор, пока не

поймете механизма выполнения всех ее операций. Заметим, что в ней

используется функция mempoint(), разработанная ранее для

отображения точки на экране терминала.

/* Вычерчивание линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distаnce;

int xerr=0,yerr=0,delta_x,delta_y;

int incx,incy;

/* вычисление расстояния в обоих направлениях */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение направления шага,

шаг вычисляется либо по вертикальной, либо горизонтальной

линии */

if (delta_x>0) incx=1;

else if (delta_x==0) incx=0;

else incx= -1;

if (delta_y>0) incy=1;

else if (delta_y==0) incy=0;

else incy= -1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* вычерчивание линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

xerr+=delta_x;

yerr+=delta_y;

if (xerr>distance)

xerr-=distance;

startx+=incx;

if (yerr>distance)

yerr-=distance;

starty+=incy;

Изображение и закрашивание прямоугольников

Если у вас есть функции вычерчивания линий, то не составит особого труда создать функции вычерчивания прямоугольников. Пример, приведенный здесь, вычерчивает прямоугольники в заданном цвете путем задания координат двух противоположных углов.

/* Вычерчивание прямоугольника */

void box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

line(startx,starty,endx,starty,color_code);

line(startx,starty,startx,endy,color_code);

line(startx,endy,endx,endy,color_code);

line(endx,starty,endx,endy,color_code);

Для того, чтобы закрасить прямоугольник, требуется выполнить запись в каждую точку растра внутри прямоугольника. Программа fill_box(), приведенная ниже, закрашивает прямоугольник, определенный координатами двух противоположных углов, заданным цветом. В ней используется функция line(), задающая цвет внутри прямоугольника.

/* Закрашивание прямоугольника в заданный цвет */

void fill_box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

register int i,begin,end;

begin=startx<endx ? startx:endx;

end=startx>endx ? startx:endx;

for (i=begin;i<=end;++i)

line(i,starty,i,endy,color_code);

Вычерчивание окружностей

Самым быстрым и легким способом вычерчивания окружностей является способ, основанный на использовании опять таки алгоритма Брезенхама, похожего на одноименный алгоритм вычерчивания линий. Данный метод также не требует вычислений чисел с плавающей точкой, кроме вычисления коэффициента сжатия, поэтому он обеспечивает достаточное быстродействие. По существу, алгоритм основан на приращении координат X и Y на величину погрешности между ними. Значение погрешности содержится в переменой delta. Функция plot_circle() выполняет запись точек по окружности. Переменная asp_ratio является глобальной, т.к. она используется как в функции circle(), так и в функции plot_circle(). Эта переменная может быть полезна с точки зрения возможности установления ее значения вне функции circle() и дальнейшего использования внутри функции. Путем изменения значения этой переменной вы можете рисовать эллипсы. Параметрами функции circle() является центр окружности, ее радиус и цвет. Тексты функций приведены ниже.

double asp_ratio;

/* Вычерчивание окружности с использованием алгоритма

Брезенхама */

void circle(x_center,y_center,radius,color_code)

int x_center,y_center,radius,color_code;

register x,y,delta;

asp_ratio=1.0; /* это число может меняется в различных

случаях */

y=radius;

delta=3-2*radius;

for (x=0;x<y; )

plot_circle(x,y,x_center,y_center,color_code);

if (delta<0)

delta+=4*x+6;

else

delta+=4*(x-y)+10;

y--;

x++;

x=y;

if (y) plot_circle(x,y,x_center,y_center,color_code);

/* Функция печатает точки, определяющие окружность */

void plot_circle(x,y,x_center,y_center,color_code)

int x,y,x_center,y_center,color_code;

int startx,starty,endx,endy,x1,y1;

starty=y*asp_ratio;

endy=(y+1)*asp_ratio;

startx=x*asp_ratio;

endx=(x+1)*asp_ratio;

for (x1=startx;x1<endx;++x1)

mempoint(x1+x_center,y+y_center,color_code);

mempoint(x1+x_center,y_center-y,color_code);

mempoint(x_center-x1,y+y_center,color_code);

mempoint(x_center-x1,y_center-y,color_code);

for (y1=starty;y1<endy;++y1)

mempoint(y1+x_center,x+y_center,color_code);

mempoint(y1+x_center,y_center-x,color_code);

mempoint(x_center-y1,x+y_center,color_code);

mempoint(x_center-y1,y_center-x,color_code);

Закрашивать окружность можно путем повторного вызова функции circle() с заданием все более и более меньшего радиуса. Этот способ используется в функции fill_circle(), текст которой приведен ниже.

/* Закрашивание окружности путем повторного вызова

circle() с уменьшением радиуса */

void fill_circle(x,y,r,c)

int x,y,r,c;

while (r)

circle(x,y,r,c);

r--;

Простейшая тестовая программа

Приведенные здесь программа иллюстрируют применение и возможности ранее описанных функций поддержки графики.

/* Программа, иллюстрирующая работу графических

функций */

#include "dos.h"

#include "stdio.h"

void mode(),line(),box(),fill_box();

void mempoint(),palette(),xhairs();

void circle(),plot_circle(),fill_circle();

double asp_ratio;

main()

mode(4);

palette(0);

line(0,0,100,100,1);

box(50,50,80,90,2);

fill_box(100,0,120,40,3);

circle(100,160,30,2);

fill_circle(150,250,20,1);

getchar();

mode(2);

/* установка палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh=1; /* код 4 режима графики */

r.h.bl=pnum; /* номер палитры */

r.h.ah=11; /* устанавливаетса для вызова палитры */

int86(0x10,&r,&r);

/* Установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

/* Вычерчивание прямоугольника */

void box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

line(startx,starty,endx,starty,color_code);

line(startx,starty,startx,endy,color_code);

line(startx,endy,endx,endy,color_code);

line(endx,starty,endx,endy,color_code);

/* Вычерчивание линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int xerr=0,yerr=0,delta_x,delta_y;

int incx,incy;

/* Вычисление расстояния в обоих направлениях */

delta_x=endx-startx;

delta_y=endy-starty;

/* Определение направления шага,

шаг вычисляется либо по вертикальной, либо по горизонтальной

линии */

if (delta_x>0) incx=1;

else if (delta_x==0) incx=0;

else incx= -1;

if (delta_y>0) incy=1;

else if (delta_y==0) incy=0;

else incy= -1;

/* Определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* Вычерчивание линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

xerr+=delta_x;

yerr+=delta_y;

if (xerr>distance)

xerr-=distance;

startx+=incx;

if (yerr>distance)

yerr-=distance;

starty+=incy;

/* Закрашивание прямоугольника заданным цветом */

void fill_box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

register int i,begin,end;

begin=startx<endx ? startx:endx;

end=startx>endx ? startx:endx;

for (i=begin;i<=end;++i)

line(i,starty,i,endy,color_code);

/* Вычерчивание окружности с использованием алгоритма

Брезенхама */

void circle(x_center,y_center,radius,color_code)

int x_center,y_center,radius,color_code;

register x,y,delta;

asp_ratio=1.0; /* это число может меняется в различных

случаях */

y=radius;

delta=3-2*radius;

for (x=0;x<y; )

plot_circle(x,y,x_center,y_center,color_code);

if (delta<0)

delta+=4*x+6;

else

delta+=4*(x-y)+10;

y--;

x++;

x=y;

if (y) plot_circle(x,y,x_center,y_center,color_code);

/* Функция изображает точки, определяющие окружность */

void plot_circle(x,y,x_center,y_center,color_code)

int x,y,x_center,y_center,color_code;

int startx,starty,endx,endy,x1,y1;

starty=y*asp_ratio;

endy=(y+1)*asp_ratio;

startx=x*asp_ratio;

endx=(x+1)*asp_ratio;

for (x1=startx;x1<endx;++x1)

mempoint(x1+x_center,y+y_center,color_code);

mempoint(x1+x_center,y_center-y,color_code);

mempoint(x_center-x1,y+y_center,color_code);

mempoint(x_center-x1,y_center-y,color_code);

for (y1=starty;y1<endy;++y1)

mempoint(y1+x_center,x+y_center,color_code);

mempoint(y1+x_center,y_center-x,color_code);

mempoint(x_center-y1,x+y_center,color_code);

mempoint(x_center-y1,y_center-x,color_code);

/* Закрашивание окружности путем повторного вызова

circle() с уменьшением радиуса */

void fill_circle(x,y,r,c)

int x,y,r,c;

while (r)

circle(x,y,r,c);

r--;

/* Запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в памяти

CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/* Установка маски битов и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции в байте */

color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* Запись цвета */

if (!xor) /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

Сохранение и загрузка графических изображений

Сохранение и загрузка графических изображений является довольно простым делом, т.к. образ изображений находится в видеопамяти дисплея, а ее содержимое легко копировать на дисковый файл. Главной проблемой является необходимость введения пользователем имени файла, ввиду того, что запрос о вводе и введенное имя файла сотрут часть информации на экране. Для того, чтобы избежать этого, разработаны функции save_pic() и load_pic(), тексты которых приводятся в данном разделе. Первая функция сохраняет 14 верхних строк изображения, чистит эту область, запрашивает имя файла и, после того, как оно будет введено, восстанавливает изображение.

/* сохранение графического изображения */

void save_pic()

char fname[80];

FILE *fp; register int i,j;

char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */

char far *temp;

unsigned char buf[14][80]; /* содержит образ экрана */

temp=ptr;

/* сохранение верхних строк текущего содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;++j)

buf[i][j]=*temp; /* четный байт */

buf[i][j+1]=*(temp+8152); /* нечетный байт */

*temp=0; *(temp+8152)=0; /* чистка позиций экрана */

temp++;

goto_xy(0,0);

printf("Имя файла:");

gets(fname);

if (!(fp=fopen(fname,"wb")))

printf("Фвайл не может быть открыт\n");

return;

temp=ptr;

/* восстановление содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;++j)

*temp= buf[i][j]; /* четный байт */

*(temp+8125)=buf[i][j+1]; /* нечетный байт */

*temp=0; *(temp+8152)=0; /* чистка позиций экрана */

temp++;

/* копирование изображения в файл */

for (i=0;i<8152;i++)

putc(*ptr,fp); /* четный байт */

putc(*(ptr+8125),fp); /* нечетный байт */

ptr++;

fclose(fp);

/* загрузка изображения */

void load_pic()

char fname[80];

FILE *fp; register int i,j;

char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */

char far *temp;

unsigned char buf[14][80]; /* содержит образ экрана */

temp=ptr;

/* сохранение верхних строк текущего содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;j+=2)

buf[i][j]=*temp;

buf[i][j+1]=*(temp+8152);

*temp=0; *(temp+8152)=0; /* чистка позиций экрана */

temp++;

goto_xy(0,0);

printf("Имя файла:");

gets(fname);

if (!(fp=fopen(fname,"rb")))

goto_xy(0,0);

printf("Файл не может быть открыт\n");

temp=ptr;

/* восстановление содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;j+=2)

*temp= buf[i][j];

*(temp+8125)=buf[i][j+1];

temp++;

return;

/* загрузка изображения из файла */

for (i=0;i<8152;i++)

*ptr=getc(fp); /* четный байт */

*(ptr+8125)=getc(fp); /* нечетный байт */

ptr++;

fclose(fp);

Подпрограммы начинают обработку видеопамяти, начиная с адреса, содержащегося в указателе temp считывая или записывая каждый четный и нечетный байты в порядке возрастания их адресов. Такой подход позволяет добиться простоты и наглядности функционирования рассмотренных выше подпрограмм. В случае обработки видеопамяти в порядке возрастания адресов, вначале будет отображаться четная точка растра, а затем - нечетная.

Дублирование части экрана

Иногда бывает полезным скопировать часть экрана в другую область. Это легко выполнить используя функцию copy(), текст которой приводится ниже.

/* копирование части экрана в другую область */

void copy(startx,starty,endx,endy,x,y)

int startx,starty; /* верхняя левая координата */

int endx,endy; /* нижняя правая координата области

копирования */

int x,y; /* верхняя левая координата области,

куда будет проводится копирование */

int i,j;

unsigned char c;

for (;startx<endx;startx++,x++)

for (i=starty,j=y;i<endy;i++,j++)

c=read_point(startx,i); /* чтение точки */

mempoint(x,j,c); /* запись ее в новую область */

Как вы могли убедиться, при обращении к функциим в качестве ее аргументов указываются верхняя левая и нижняя правая координаты углов области, которая будет копироваться, и верхняя левая координаты, куда делается копия.

Вы также можете убедится, что с небольшими изменениями функцию copy() можно преобразовать в функцию move(). Функция move() пересылает указанную область в другую и чистит исходное место. Текст функции приводится ниже.

/* Пересылка части экрана в другую область */

void move(startx,starty,endx,endy,x,y)

int startx,starty; /* верхняя левая координата */

int endx,endy; /* нижняя правая координата области

пересылки */

int x,y; /* верхняя левая координата области,

куда будет проводится пересылка */

int i,j;

unsigned char c;

for (;startx<endx;startx++,x++)

for (i=starty;j=y;i<endy;i++,j++)

c=read_point(startx,i); /* чтение точки */

mempoint(startx,i,0); /* стирание старого

изображения */

mempoint(x,j,c); /* запись точки в новую область */

Вращение точки в плоскости экрана

Вращение точки в плоскости экрана (двумерном пространстве) представляет собой довольно простую задачу в декартовой системе координат. Вы можете вспомнить из курса аналитической геометрии, что вращение точки вокруг центра на угол theta, описывается формулой:

new_x = old_x * cos(theta) - old_y * sin(theta)

new_y = old_x * sin(theta) - old_y * cos(theta)

Единственной сложностью при употреблении этих формул для графических дисплеев будет являтся тот факт, что экран дисплея не является декартовым пространством. Декартовы оси определяют 4 квадранта, как это показано на рисунке 4-2. Однако экран терминала представляет собой один из квадрантов, оси X и Y в котором перевернуты. Для решения этой проблемы необходимо определить новый центр и привести в соответствие координаты X и Y экрана и координаты осей декартова пространства. Любая точка на экране может быть использована в качестве центра, но обычно центр определяется как можно ближе к центру объекта, который мы собираемся вращать. Функция rotate_point(), приведенная ниже, вычисляет величину новых значений X и Y для заданного угла вращения.

/* Вращение точки вокруг центра с координатами

x_org и y_org на угол theta */

void rotate_point(theta,x,y,x_org,y_org)

double theta,*x,*y;

int x_org,y_org;

double tx,ty;

/* нормализация X и Y к начальному адресу */

tx=*x-x_org;

ty=*y-y_org;

/* вращение */

*x=tx*cos(theta)-ty*sin(theta);

*y=tx*sin(theta)-ty*cos(theta);

/* возвращение значений координат */

*x+=x_org;

*y+=y_org;

Заметим, что rotate_point() изменяет параметры X и Y путем присвоения им требуемого значение для получения угла вращения, заданного переменной theta. Угол вращения задается в радианах.

Декартова система координат Y

II ^ I

+,- | +,+

|

|0.0

------------------- X

|

-,- | -,+

|

III ј IV

0.0 Графический экран ----------- Y

|

| +,+

|

ј

X

Рис. 4-2. Декартовы координаты на графическом экране.

Вращение обьекта

Хотя функция rotate_point(), вычисляющая требуемые значения координат X и Y при вращении точки, уже была нами рассмотрена. Однако, она не может быть использована для вращения (поворотов) объектов. Для этого необходима другая функция. Под объектом здесь и далее будем понимать набор сегментов прямых отрезков. Координаты крайних точек каждого отрезка содержатся в двумерном массиве чисел с плавающей точкой. Каждая строка массива содержит начальные и конечные координаты данного отрезка. Это означает, что первая размерность массива представляет собой количество отрезков, входящих в состав объекта, а вторая размерность будет равна 4 (число координат крайних точек отрезка). Например, массив, приведенный ниже

double object [10][4];

определяет объект, состоящий из 10 отрезков.

Как правило, массив организуется так, как показано на рисунке 4-3.

Первый Второй -------->

индекс индекс

| 0 1 2 3

|

|

V

0 start_X1 start_Y1 end_X1 end_Y1

1 start_X2 start_Y2 end_X2 end_Y2

2 start_X3 start_Y3 end_X3 end_Y3

3 start_X4 start_Y4 end_X4 end_Y4 . .

. .

. .

n start_Xn start_Yn end_Xn end_Yn

Рис. 4-3. Условная организация массива.

Определить объект - это значит разместить в массиве координаты начальных и конечных точек отрезков, составляющих объект. Например, если объект представляет собой прямоугольник вида:

0.0-------------------0.10

| |

| |

| |

| |

10.0-------------------10.10

то в массив, определяющий данный прямоугольник, заносятся

следующие числа:

object[0][0] = 0; object[0][1] = 0;

object[0][0] = 0; object[0][3] = 10;

object[1][0] = 0; object[1][1] = 10;

object[1][0] = 10; object[1][3] = 10;

object[2][0] = 10; object[2][1] = 10;

object[2][0] = 10; object[2][3] = 0;

object[3][0] = 10; object[3][1] = 0;

object[3][0] = 0; object[3][3] = 0;

После того, как объект определен, вы можете вращать его, используя функцию rotate_object(), приведенную ниже, по часовой стрелке (клавиша <R>) или в противоположную сторону (клавиша <L>).

/* Вращение заданных объектов */

void rotate_object(ob, theta, x, y, sides)

double ob[][4]; /* описание объекта */

double theta; /* угол поворота в радианах */

int x, y;

int sides;

register int i, j;

double tempx, tempy;

char ch;

for(;;)

ch = getch(); /* ввод признака направления вращения */

switch(tolower(ch))

case 'l': /* вращение против часовой стрелки */

theta = theta < 0 ? -theta : theta;

break;

case 'r': /* вращение по часовой стрелке */

theta = theta > 0 ? -theta : theta;

break;

default: return;

for(j=0; j<=sides; j++) /* стирание старых линий */

line((int) ob[j][0], (int) ob[j][1],

(int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0],

&ob[j][1], x, y);

rotate_point(theta, &ob[j][2], &ob[j][3], x, y);

line((int) ob[j][0], (int) ob[j][1],

(int) ob[j][2], (int) ob[j][3], 2);

Как показано в описании параметров функции rotate_object(), вращение осуществляется вокруг центра, заданного координатами X и Y, на угол, величина которого задана параметром theta в радианах. Минимальное значение параметра theta равно 0.01 радиан. Заметим, что объект сначала стирается из старой области размещения, а затем перерисовывается вновь. Если это условие не может быть выполнено, то экран окрашивается в голубой цвет. Необходимым условием выполнения программы rotate_object() является обязательное задание параметра sides.

Приведенная ниже функция display_object() не имеет отношения к вращению объектов, но она может быть полезна при работе с объектами. Она рисует на экране объекты, определенные в массиве ob.

/* отображение объекта на экране */

void display_object(ob, sides)

double ob[][4];

int sides;

register int i;

for(i=0; i<sides; i++)

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], 2);

В качестве иллюстрации удобства использования функций вращения объектов ниже приводятся программы вращения изображения дома. На рисунке 4-4 показано изображение на экране терминала дома при различных углах поворота вокруг собственного центра. Прямоугольник, обрамляющий изображение вращаемого дома, поможет вам правильно оценить масштаб и перспективу.

_________________________________________________________________

Прим. пер. Рисунок 4-4 не может быть воспроизведен имеющимися средствами.

_________________________________________________________________

Рис. 4-4. Вращение объекта.

/* Пример вращения изображения объекта с использованием

адаптера CGA/EGA в 4 графическом режиме

*/

#include "dos.h"

#include "stdio.h"

#include "math.h"

void mode(), line(), mempoint(), palette();

void rotate_point(), rotate_object(), display_object();

/* массив house определяет изображение дома */

double house[][4] =

/* startx, starty, endx, endy */

120, 120, 120, 200, /* дом */

120, 200, 80, 200,

80, 120, 80, 200,

80, 120, 120, 120,

60, 160, 80, 120, /* крышa*/

60, 160, 80, 200,

120, 155, 100, 155, /* двери*/

100, 155, 100, 165,

100, 165, 120, 165,

90, 130, 100, 130, /* окна */

90, 130, 90, 140,

100, 130, 100, 140,

90, 140, 100, 140,

90, 180, 100, 180,

90, 180, 90, 190,

100, 180, 100, 190

;

main()

union k

char c[2];

int i;

key;

mode(4); /* режим = 4 */

palette(0); /* палитра = 0 */

/* рисунок рамки,обрамляющей дом */

line (30, 70, 30, 260, 2);

line (160, 70, 160, 260, 2);

line (30, 70, 160, 70, 2);

line (30, 260, 160, 260, 2);

display_object(house, 17);

getchar();

rotate_object(house, 0.025, 90, 160, 17);

mode(3);

/* Выбор палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4 графического режима */

r.h.bl = pnum;

r.h.ah = 11;

int86(0x10, &r, &r);

/* Выбор режима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

/* Рисунок отрезка прямой заданного цвета */

void line(start_x, start_y, endx, endy, color)

int start_x, start_y, endx, endy, color;

register int t, distance;

int x=0, y=0, delta_x, delta_y;

int incx, incy;

/* вычисление приращений по x и по y */

delta_x = endx-start_x;

delta_y = endy-start_y;

/* вычисление признаков направлений отрезка */

if(delta_x>0)

incx=1;

else

if(delta_x==0)

incx=0;

else

incx= -1;

if(delta_y>0)

incy=1;

else

if(delta_y==0)

incy=0;

else

incy= -1;

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if(delta_x>delta_y)

distance=delta_x;

else

distance=delta_y;

/* рисунок отрезка */

for(t=0; t<=distance; t++)

mempoint(start_x, start_y, color);

x+=delta_x;

y+=delta_y;

if(x>distance)

x-=distance;

start_x+=incx;

if(y>distance)

y-=distance;

start_y+=incy;

/* запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "НЕ-ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "НЕ-ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/* установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor) /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

/* вращение точки вокруг центра с координатами

в x_org и y_org, на угол theta */

void rotate_point(theta,x,y,x_org,y_org)

double theta,*x,*y;

int x_org,y_org;

double tx,ty;

/* нормализация X и Y к начальному адресу */

tx=*x-x_org;

ty=*y-y_org;

/* вращение */

*x=tx*cos(theta)-ty*sin(theta);

*y=tx*sin(theta)-ty*cos(theta);

/* возвращение значений координат */

*x+=x_org;

*y+=y_org;

/* Вращение заданных объектов */

void rotate_object(ob, theta, x, y, sides)

double ob[][4]; /* описание объекта */

double theta; /* угол поворота в радианах */

int x, y;

int sides;

register int i, j;

double tempx, tempy;

char ch;

for(;;)

ch = getch(); /* ввод признака направления вращения */

switch(tolower(ch))

case 'l': /* вращение против часовой стрелки */

theta = theta < 0 ? -theta : theta;

break;

case 'r': /* вращение по часовой стрелке */

theta = theta > 0 ? -theta : theta;

break;

default: return;

for(j=0; j<=sides; j++) /* стирание старых линий */

line((int) ob[j][0], (int) ob[j][1],

(int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0],

&ob[j][1], x, y);

rotate_point(theta, &ob[j][2], &ob[j][3], x, y);

line((int) ob[j][0], (int) ob[j][1],

(int) ob[j][2], (int) ob[j][3], 2);

/* отображение объекта на экране */

void display_object(ob, sides)

double ob[][4];

int sides;

register int i;

for(i=0; i<sides; i++)

line((int) ob[i][0], (int) ob[i][1],

(int) ob[i][2], (int) ob[i][3], 2);

Сборка подпрограмм

В этом, последнем, параграфе, описывается простая программа рисования, использующая подпрограммы графики. Программы рисования часто используют "мышь", позволяющую пользователю удобным способом отображать линии на экране терминала. Однако, "мышью" комплектуются пока не все компьютеры поэтому, описанная здесь "программа-художник" ориентирована на операции с клавишами перемещения курсора.

В "рисующих" программах вам необходимо контролировать текущее положение координат X и Y (в графическом режиме текущие координаты индицируются курсором не совсем обычной формы). Для простоты дальнейшего изложения материала будем называть "графический" курсор "перекрестьем", принимая во внимание и тот факт, что в графическом режиме его форма действительно напоминает крест. Функция xhairs() размещает графический курсор в позиции, специфицированной значениями ее аргументов X и Y.

Напоминаем, что двоичный код цвета складывается по схеме "ИЛИ" со 128 с целью установки 7 бита в 1. Это позволяет функции mempoint() складывать по схеме "исключающего ИЛИ" двоичные коды старого и нового цвета на экране вместо его изменения. Такая возможность позволяет достичь двух важных моментов. Во-первых, графический курсор всегда видим, т.к. всегда имеет цвет, отличный от окружающего. Во-вторых, значительно упрощается процесс возврата точки растра, занимаемой курсором, в исходное положение. Эта операция выполняется путем повторного обращения к этим точкам (напомним, что последовательное выполнение двух операций по схеме "исключающего ИЛИ" всегда приводит к первоначальному значению). Ниже приведен текст функции отображения графического курсора.

/* отображение графического курсора */

void xhairs(x,y)

int x,y;

line(x-4,y,x+3,y,1|128);

line(x,y+4,x,y-3,1|128);

Программа рисования, описанная в данном разделе, позволит вам:

- рисовать линии;

- рисовать прямоугольники;

- закрашивать прямоугольники;

- рисовать окружности;

- закрашивать окружности;

- выбирать цвет;

- выбирать палитру;

- устанавливать скорость изменения параметров;

- сохранять графические изображения;

- загружать графические изображения;

- вращать объекты вокруг любой точки;

- копировать и пересылать графические изображения.

Приведем ниже текст главной программы :

main()

union k

char c[2];

int i;

key ;

int x=10, y=10; /* текущая позиция экрана */

int cc=2; /* текущий цвет */

int on_flag=1; /* признак использования карандаша */

int pal_num=1; /* номер палитры */

/* конечная точка определения линий,

прямоугольников, окружностей */

int startx=0, starty=0, endx=0, endy=0;

int first_point=1;

int inc=1; /* шаг пересылки */

int sides=0; /* количество сторон выбранного объекта */

int i;

mode(4); /* переключатель режима CGA/EGA */

palette(0); /* палитра 0 */

xhairs(x, y); /* указатель курсора */

do

key.i = bioskey(0);

xhairs(x, y); /* графический курсор */

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

if(on_flag) line(x, y, x, y-inc, cc);

y -= inc;

break;

case 77: /* вправо */

if(on_flag) line(x, y, x, y+inc, cc);

y += inc;

break;

case 72: /* вверх */

if(on_flag) line(x, y, x-inc, y, cc);

x -= inc;

break;

case 80: /* вниз */

if(on_flag) line(x, y, x+inc, y, cc);

x += inc;

break;

case 71: /* вверх и влево */

if(on_flag) line(x, y, x-inc, y-inc, cc);

x -= inc;

y -= inc;

break;

case 73: /* вверх и вправо */

if(on_flag) line(x, y, x-inc, y+inc, cc);

x -= inc;

y += inc;

break;

case 79: /* вниз и влево */

if(on_flag) line(x, y, x+inc, y-inc, cc);

x += inc;

y -= inc;

break;

case 81: /* вниз и вправо */

if(on_flag) line(x, y, x+inc, y+inc, cc);

x += inc;

y += inc;

break;

case 59: /* F1 - медленно */ inc=1;

break;

case 60: /* F2 - быстро */

inc=5;

break;

else switch(tolower(key.c[0]))

case 'o': /* переключение шаблона */

on_flag = !on_flag;

break;

case '1': cc=1; /* цвет 1 */

break;

case '2': cc=2; /* цвет 2 */

break;

case '3': cc=3; /* цвет 3 */

break;

case '0': cc=0; /* цвет 0 */

break;

case 'b': box(startx, starty, endx, endy, cc);

break;

case 'f':

fill_box(startx, starty, endx, endy, cc); break;

case 'l':

line(startx, starty, endx, endy, cc); break;

case 'c':

circle(startx, starty, endy-starty, cc); break;

case 'h':

fill_circle(startx, starty, endy-starty, cc); break; case 's':

save_pic(); break;

case 'r':

load_pic(); break;

case 'm': /* пересылка фрагмента */

move(startx, starty, endx, endy, x, y); break;

case 'x': /* копирование фрагмента */

copy(startx, starty, endx, endy, x, y); break;

case 'd': /* определить поворот(cдвиг) объекта */

/* Внимание!! Во время трансляции программы идентификатор object

был помечен как "неопределенный". Его описание действительно

отсутствует в этой программе. (Ред. пер. И.Бычковский)

*/

sides = define_objekt(object, x, y); break;

case 'a': /* поворот(сдвиг) объекта */

rotate_objekt(object, 0.05, x, y, sides); break;

case '\r': /* набор конечных точек для линий, кругов

или прямоугольников */

if(first_point)

startx = x, starty = y;

else

endx = x, endy = y;

first_point = !first_point;break;

case 'p':

pal_num = pal_num==1 ? 2:1;

palette(pal_num);

xhairs(x, y);

while (key.c[0]!='q');

getchar();

mode(2);

Опишем кратко алгоритм работы программы рисования. Вначале экран терминала устанавливается в 4 графический режим. Затем устанавливается палитра 0, и графический курсор перемещается в верхний левый угол. Шаблон цвета по умолчанию устанавливается в соответствии с кодом 2 (красный в палитре 0). При перемещении графического курсора на экране остается след, который окрашивается в соответствии с текущим цветом шаблона. Если нажимать клавиши перемещения курсора, графический курсор перемещается на одну точку растра в заданном направлении. Такая скорость перемещения может не удовлетворять пользователя, поэтому в программе предусмотрена возможность смещения на 5 точек растра путем нажатия клавиши F2. Отменить режим ускоренного перемещения можно путем нажатия клавиши F1. Изменение цвета осуществляется при нажатии цифровых клавиш от 0 до 3. В палитре 0 цифра 0 зарезервирована, 1 определяет зеленый цвет, 2 - красный, 3 - желтый. Шаблон цвета может быть изменен путем нажатия клавиши 0. Клавиши <Курсор в левый верхний угол> (<HOME>), <Страница вверх> (<PGUP>), <Страница вниз> (<PGDN>) и <Кон> (<END>) перемещают графический курсор в указанном направлении и под углом в 45 градусов.

Для анализа кодов операций чтения в программах используется функция bioskey(). Порядок подключения этой функции к программе

при компиляции описан в главе 1. В программу включены обращения к

функциям, позволяющим вам рисовать и закрашивать прямоугольники и

окружности, рисовать линии, копировать и перемещать изображение

на экране, сохранять на диске и загружать с него содержимое

экрана, отображать и вращать объекты.

При изображении линий, прямоугольников и окружностей вам необходимо определить координаты двух точек. Для прямоугольников

- это координаты двух противоположных углов. Для линий задается начальная и конечная точки, а для окружности - координаты центра и точки, через которую она будет проходить.

Процесс выбора этих точек выполняется путем нажатия клавиши <ВВОД> в момент, когда графический курсор находится в требуемой области. Например, для изображения линии вы перемещаете графический курсор в точку, где она должна начинаться и нажимаете клавишу <ВВОД> . Затем вы устанавливаете курсор в точку, где линия заканчивается, и нажимаете <ВВОД> снова. При нажатии клавиши <ВВОД> выполняется загрузка переменных startx, starty, endx и endy, которые потом используются в качестве параметров вызываемых функций. После того, как координаты точек будут определены, при нажатии клавиши <В> рисуется квадрат, а <F> - квадрат закрашивается, при нажатии <L> рисуется линия, при нажатии <С> рисуется окружность, а <Н> - окружность закрашивается.

Для копирования или перемещения части экрана вы должны определить верхний левый и нижний правый углы области, которую вы хотите переместить (нажатием клавиши <ВВОД> ). Затем вы перемещаете курсор в верхний левый угол области, куда вы хотите переместить изображение. Для пересылки изображения требуется нажать клавишу <М>, а для копирования - <Х>. Запомните, что старое изображение в области, куда осуществляется копирование, будет уничтожено.

Для вращения объекта вам необходимо определить сам объект, путем нажатия клавиши <D>. Затем, используя клавишу <ВВОД>, вы должны определить начальные и конечные координаты точек для отрезков по периметру выбранного объекта. Процесс выбора объекта вращения и определения его границ реализуется функцией define_object(). Вращение объекта начинается после нажатия клавиши <А>. Для определения направления вращения используются клавиши <L> (по часовой стрелке) или <R> (против часовой стрелки). Остановить процесс вращения можно нажатием любой клавиши, кроме <L> или <А>.

Для остановки работы программы используется клавиша <Q>. При желании вы можете включить в программу функции для работы с "мышью". Пример выходных данных программы показан на рисунке 4-5.

_________________________________________________________________

Рисунок 4-5 на стр. 163 не может быть воспроизведен имеющимися средствами. (Ред. пер. И.Бычковский)

_________________________________________________________________

Рис. 4-5. Простейшие результаты работы программы рисования.

А теперь приведем всю программу рисования целиком.

/* Программа для CGA/EGA, позволяющая рисовать линии, прямоугольники и окружности. Вы можете нарисовать какой-либо объект и вращать его по часовой или против часовой стрелки. Вы так же можете копировать графическое изображение на диск и загружать его с диска. */

#define NUM_SIDES 20 /* число сторон объекта;

при необходимости увеличивается */ #include "dos.h"

#include "stdio.h"

#include "math.h"

void mode(), line(), box(), fill_box();

void mempoint(), palette(), xhairs();

void circle(), plot_circle(), fill_circle();

void rotate_point(), rotate_object(), goto_xy();

void display_object(), copy(), move();

void save_pic(), load_pic();

unsigned char read_point();

/* Этот массив содержит динамически меняющиеся

координаты объекта.

*/

double object[NUM_SIDES][4];

double asp_ratio; /* содержит коэффициент сжатия для

окружностей */

main()

union k

char c[2];

int i;

key ;

int x=10, y=10; /* текущая позиция экрана */

int cc=2; /* текущий цвет */

int on_flag=1; /* признак использования карандаша */

int pal_num=1; /* номер палитры */

int startx=0, starty=0, endx=0, endy=0;

int first_point=1;

int inc=1; /* шаг пересылки */

int sides=0; /* количество сторoн выбранного объекта */

int i;

mode(4); /* переключатель режима CGA/EGA */

palette(0); /* палитра 0 */

xhairs(x, y); /* указатель курсора */

do

key.i = bioskey(0);

xhairs(x, y);

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

if(on_flag) line(x, y, x, y-inc, cc);

y -= inc;

break;

case 77: /* вправо */

if(on_flag) line(x, y, x, y+inc, cc);

y += inc;

break;

case 72: /* вверх */

if(on_flag) line(x, y, x-inc, y, cc);

x -= inc;

break;

case 80: /* вниз */

if(on_flag) line(x, y, x+inc, y, cc);

x += inc;

break;

case 71: /* вверх и влево */

if(on_flag) line(x, y, x-inc, y-inc, cc);

x -= inc;

y -= inc;

break;

case 73: /* вверх и вправо */

if(on_flag) line(x, y, x-inc, y+inc, cc);

x -= inc;

y += inc;

break;

case 79: /* вниз и влево */

if(on_flag) line(x, y, x+inc, y-inc, cc);

x += inc;

y -= inc;

break;

case 81: /* вниз и вправо */

if(on_flag) line(x, y, x+inc, y+inc, cc);

x += inc;

y += inc;

break;

case 59: /* F1 - медленно */

inc=1;

break;

case 60: /* F2 - быстро */

inc=5;

break;

else switch(tolower(key.c[0]))

case 'o': /* переключение шаблона */

on_flag = !on_flag;

break;

case '1': cc=1; /* цвет 1 */

break;

case '2': cc=2; /* цвет 2 */

break;

case '3': cc=3; /* цвет 3 */

break;

case '0': cc=0; /* цвет 0 */

break;

case 'b':

box(startx, starty, endx, endy, cc); break;

case 'f':

fill_box(startx, starty, endx, endy, cc); break;

case 'l':

line(startx, starty, endx, endy, cc); break;

case 'c':

circle(startx, starty, endy-starty, cc); break;

case 'h':

fill_circle(startx, starty, endy-starty, cc); break; case 's':

save_pic(); break;

case 'r':

load_pic(); break;

case 'm': /* пересылка фрагмента */

move(startx, starty, endx, endy, x, y); break;

case 'x': /* копирование фрагмента */

copy(startx, starty, endx, endy, x, y); break;

case 'd': /* определить объект вращения */

sides = define_objekt(object, x, y); break;

case 'a': /* вращение объекта */

rotate_objekt(object, 0.05, x, y, sides); break;

case '\r': /* набор конечных точек для линий, кругов

или прямоугольников */

if(first_point)

startx = x, starty = y;

else

endx = x, endy = y;

first_point = !first_point; break;

case 'p':

pal_num = pal_num==1 ? 2:1;

palette(pal_num);

xhairs(x, y);

while (key.c[0]!='q');

getchar();

mode(2);

/* установка палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4 режима графики */

r.h.bl = pnum;

r.h.ah = 11; /* установка палитры */

int86(0x10, &r, &r);

/* установка видео-режима */

void mode (mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r, &r);

/* изображение прямоугольника */

void box(sx, sy, ex, ey, c)

int sx, sy, ex, ey, c;

line(sx, sy, ex, sy, c);

line(sx, sy, sx, ey, c);

line(sx, ey, ex, ey, c);

line(ex, sy, ex, ey, c);

/* изображение линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int xerr=0,yerr=0,delta_x,delta_y;

int incx,incy;

/* вычисление расстояния в обоих направлениях */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение направления шага,

шаг вычисляется либо по вертикальной, либо горизонтальной

линии */

if (delta_x>0) incx=1;

else if (delta_x==0) incx=0;

else incx= -1;

if (delta_y>0) incy=1;

else if (delta_y==0) incy=0;

else incy= -1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* изображение линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

xerr+=delta_x;

yerr+=delta_y;

if (xerr>distance)

xerr-=distance;

startx+=incx;

if (yerr>distance)

yerr-=distance;

starty+=incy;

/* закрашивание прямоугольника в заданный цвет */

void fill_box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

register int i,begin,end;

begin=startx<endx ? startx:endx;

end=startx>endx ? startx:endx;

for (i=begin;i<=end;++i)

line(i,starty,i,endy,color_code);

/* изображение окружности с использованием алгоритма

Брезенхама */

void circle(x_center,y_center,radius,color_code)

int x_center,y_center,radius,color_code;

register x,y,delta;

asp_ratio=1.0; /* это число меняется в различных

случаях */

y=radius;

delta=3-2*radius;

for (x=0;x<y; )

plot_circle(x,y,x_center,y_center,color_code);

if (delta<0)

delta+=4*x+6;

else

delta+=4*(x-y)+10;

y--;

x++;

x=y;

if (y) plot_circle(x,y,x_center,y_center,color_code);

/* plot_circle печатает точки, определяющие окружность */

void plot_circle(x, y, x_center, y_center, color_code)

int x_center,y_center,radius,color_code;

int x, y, startx, starty, endx, endy, x1, y1;

starty=y*asp_ratio;

endy=(y+1)*asp_ratio;

startx=x*asp_ratio;

endx=(x+1)*asp_ratio;

for (x1=startx;x1<endx;++x1)

mempoint(x1+x_center,y+y_center,color_code);

mempoint(x1+x_center,y_center-y,color_code);

mempoint(x_center-x1,y+y_center,color_code);

mempoint(x_center-x1,y_center-y,color_code);

for (y1=starty;y1<endy;++y1)

mempoint(y1+x_center,x+y_center,color_code);

mempoint(y1+x_center,y_center-x,color_code);

mempoint(x_center-y1,x+y_center,color_code);

mempoint(x_center-y1,y_center-x,color_code);


Глава 5. Видеоигры

Видеоигры приносят вам либо радость, либо огорчения. Это зависит от вашего отношения к ним. Однако программирование видеоигр не только интересно, но и полезно. Фактически, одним из лучших способов обогащения является разработка удачных видеоигр. Хорошая игра, объединяющая логику с живой графикой, доставит большое удовольствие игроку. Лучшие из видеоигр, включающие элементы искусственного интеллекта, позволяют компьютеру вести диалог с игроком и "осмысленно" реагировать на ввод данных.

В этой главе вы ознакомитесь с некоторыми основами техники программирования видеоигр, что позволит вам разрабатывать собственные игры. Вы научитесь "оживлять" различные объекты на экране вашего терминала. Разработка видеоигр явится для вас отправной точкой. Многие принципы, используемые при разработке видеоигр, будут полезны для вас и увеличат ваш интерес к работе. Для использования программ, приводимых в качестве примера в этой главе, необходим компьютер IBM PC или другой, совместимый с ним, в состав которого входят адаптеры CGA, EGA или VGA. Многие из функций, используемых в данной главе, рассматривались в главе 4. Поэтому, если вы еще не изучили главу 4, то вам придется сделать это сейчас.

Спрайты

Многие видеоигры, в которых игрок управляет объектами, атакующими другие объекты, управляемые программой или защищающимися от них, включают два класса активных объектов: среду (представляющую для нас маломеняющееся поле игры) и спрайты. СПРАЙТ - это небольшой подвижный объект, который движется по полю видеоигры по определенным правилам с заданной целью. Например, когда космический корабль стреляет фотонными торпедами, изображение торпеды реализуется спрайтом. В рамках данной главы под спрайтом будем понимать фигуру, определенную некоторыми замкнутыми отрезками (многоугольник). Хотя, в общем случае спрайт может изображаться любым образом, например, в виде окружности. В примерах, рассматриваемых в данной главе, определять спрайт будем в виде двумерного массива целых чисел. Например, спрайт, состоящий из 4 отрезков может быть описан следующим массивом

int sprite [4][4];

Первая размерность массива определяет количество отрезков спрайта, а вторая - координаты конечных точек отрезков (подобный способ описания объектов подробно рассмотрен в главе 4). Начальные и конечные координаты отрезков задаются в следующей последовательности:

start_x, start_y, end_x, end_y

Отрезок, входящий в спрайт, с координатами конечных точек 0,0 и 0,10 может быть описан следующим массивом:

sprite[0][0] = 0; /* start_x */

sprite[0][1] = 0; /* start_y */

sprite[0][2] = 0; /* end_x */

sprite[0][3] = 10; /* end_y */

Поле игры

В большинстве видеоигр поле игры представляет собой неменяющееся (медленно меняющееся) изображение, на котором происходит действие игры. Поле игры изображается отдельными программами, загружаемыми в начале игры, поэтому нет необходимости загружать все программы верхнего уровня для динамической генерации игрового поля. Такой подход описан в данной главе. Программы, используемые для генерации изображений, их хранения в файле на диске и доступа к ним, описаны в главе 4. Они загружают эти изображения по мере необходимости.

Мультипликация на экране

Ключевым и наиболее впечатляющим моментом видеоигры является мультипликация. Мультипликация - основной отличительный признак видеоигр. Основной метод мультипликации прост: уничтожить изображение предмета и создать его вновь, но с некоторым небольшим смещением. Скорость этого процесса должна быть очень высокой. Это может быть обеспечено путем непосредственного доступа к видеопамяти дисплея, возможность которого описана в главе 4.

Для повышения качества изображения, быстродействия операций уничтожения и повторного изображения объекта используется операция "НЕ-ИЛИ" для двоичного кода каждой точки объекта на экране. Этот способ обеспечивает возможность быстрого перемещения спрайта по экрану, не меняя его цвет и размеры, и фактически не уничтожая в памяти терминала данные о его изображении.

Программа, отображающая на экране терминала спрайт, представляет собой некоторую модификацию функции display_object() из главы 4.

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

register int i;

for(i=0; i<sides; i++)

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc | 128);

Как вы могли убедиться, функция display_object() рисует все линии объекта, используя приведенную в главе 4 функцию line(). Заметим, что значение номера цвета складывается по схеме "ИЛИ" с числом 128 в команде установки старших битов. Это приводит к тому, что в функции mempoint(), используемой в функции line() для помещения изображения каждой точки, выполняется сложение по схеме "НЕ-ИЛИ" двоичного кода. Это позволяет спрайту всегда оставаться видимым независимо от собственного цвета и цвета фона.

Для демонстрации мультипликации введите в ваш компьютер следующую программу. Эта программа позволит вам перемещать спрайт (в виде маленького крестика размером 6x6 точек растра) по экрану, используя клавиши управления курсором. Если ваш компьютер не включает функцию bioskey(), то просмотрите главу 1 для определения версии компилятора, которая вам необходима.

#include "dos.h"

#include "stdio.h"

void mode(), line();

void mempoint(), palette();

void display_object(),update_object();

unsigned char read_point();

int sprite[2][4] =

3,0,3,5,

0,3,5,3

;

main()

union k

char c[2];

int i;

key;

int deltax=0,deltay=0;

mode(4); /*m установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

display_object(sprite,2,1);

do

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

break;

case 77: /* вправо */

deltay= 1;

break;

case 72: /* вверх */

deltax= -1;

break;

case 80: /* вниз */

deltax= 1;

break;

case 71: /* вверх и влево */

deltay= -1;

deltax= -1;

break;

case 73: /* вверх и вправо */

deltay= 1;

deltax= -1;

break;

case 79: /* вниз и влево */

deltay= -1;

deltax= 1;

break;

case 81: /* вниз и вправо */

deltay= 1;

deltax= 1;

break;

/* стирание текущей позиции спрайта */

display_object(sprite,2,1);

if (is_legal(sprite,deltax,deltay,2))

update_object(sprite,deltax,deltay,2);

/* перезапись спрайта в новую позицию */

displey_object(sprite2,1);

while (key.c[0]!='q');

getchar();

mode(2);

/* Выбор палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4-го графического режима */

r.h.bl = pnum;

r.h.ah = 11;

int86(0x10, &r, &r);

/* Выбор режима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

/* Изображение линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* Вычисление расстояния в обоих направлениях */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение направления шага,

шаг вычисляется либо по вертикальной, либо по горизонтальной

линии */

if (delta_x>0) incx=1;

else if (delta_x==0) incx=0;

else incx= -1;

if (delta_y>0) incy=1;

else if (delta_y==0) incy=0;

else incy= -1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* Изображение линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

x+=delta_x;

y+=delta_y;

if (x>distance)

x-=distance;

startx+=incx;

if (y>distance)

y-=distance;

starty+=incy;

/* Запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/* установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor) /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

/* чтение байта из оперативной памяти CGA/EGA */

unsigned char read_point(x,y)

int x,y;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=3; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return 0;

/* установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ bit_mask.i<<=2*(3-bit_position);

/* определение требуемого байта в памяти терминала */

index=x*40+(y>>4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

t=*(ptr+index) & bit_mask.c[0];

t>>=2*(3-bit_position);

return t;

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

register int i;

for(i=0; i<sides; i++)

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc|128);

/* Смещение (параллельный перенос) объекта в направлении,

определенном x и y

*/

void update_object(ob, x, y, sides)

int ob[][4]; /* объект */

int x, y; /* направление смещения */

register int sides; /* количество сторон объекта */

sides--;

for(; sides>=0; sides--)

ob[sides][0] += x;

ob[sides][1] += y;

ob[sides][2] += x;

ob[sides][3] += y;

/* Определение допустимости перемещения объекта.

Возвращает 1, если перемещение допустимо, 0- в противном случае

*/

void is_legal(ob, x, y, sides)

int ob[][4]; /* объект */

int x, y; /* шаг перемещения */

int sides; /* число сторон объекта */

if(x==0 && y==0)

return 1; /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

/* контроль выхода за допустимую область */ if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

return 1;

Рассмотрим кратко, как работает эта программа. Клавиши управления курсором (клавиши со стрелками и клавиши <HOME>, <PGUP>, <END> и <PGDN>) определяют положение спрайта. При нажатии клавиши спрайт смещается на одну точку растра в указанном направлении. Клавиши-стрелки управляют горзонтальными и вертикальными перемещениями, остальные - диагональными. Функция is_legal() определяет возможность дальнейшего перемещения спрайта в выбранном направлении. Если возможен выход спрайта за пределы границ экрана, то такое перемещение запрещается. Все остальные функции этой программы работают, как описано в главе 4.

Обычно необходимо сохранять размер объекта, который вы "оживляете" (особенно небольшого) для того, чтобы его можно было перерисовывать с высокой скоростью. Это обеспечивает плавность движения при мультипликации. Если объект достаточно большой, то его движение будет дискретно. При разработке видеоигр необходимо так подбирать размеры спрайта, чтобы возможности компьютера и адаптера реализовывались оптимальным образом.

Мультипликация спрайта

Передвижение спрайта по экрану составляет только половину возможностей его "оживления". В основном спрайт будет использоваться на экране для того, чтобы создавать иллюзию движения. Например, спрайт, который выглядит подобно человеку, может передвигать ногами, как будто он идет. Этот тип "оживления" является наиболее впечатляющим (и наиболее легким). Для обеспечения такой возможности разрабатываются два или более вариантов спрайта, отличие между которыми заключается в том, что некоторые из частей спрайта отличаются от первоначального его варианта. Программа последовательно меняет варианты спрайта в процессе его движения по экрану.

В качестве примера изменим программу main(), как это показано ниже, и добавим в нее второй спрайт. Второй спрайт отображает крестик ("+"), повернутый под углом в 45 градусов. Если вы запустите программу, то будет создаваться впечатление, что крестик вращается в процессе передвижения по экрану. Переменная swap используется для выбора типа текущего спрайта.

int sprite2[2][4] =

0,0,5,5,

0,5,5,0

;

main()

union k

char c[2];

int i;

key;

int deltax=0,deltay=0; /* направление движения */

int swap=0; /* тип спрайта */

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

display_object(sprite,2,1);

do

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

break;

case 77: /* вправо */

deltay= 1;

break;

case 72: /* вверх */

deltax= -1;

break;

case 80: /* вниз */

deltax= 1;

break;

case 71: /* вверх и влево */

deltay= -1;

deltax= -1;

break;

case 73: /* вверх и вправо */

deltay= 1;

deltax= -1;

break;

case 79: /* вниз и влево */

deltay= -1;

deltax= 1;

break;

case 81: /* вниз и вправо */

deltay= 1;

deltax= 1;

break;

/* стирание текущей позиции спрайта */

if(!swap) displey_object(sprite,2,1);

else displey_object(sprite2,2,1);

if (is_legal(sprite,deltax,deltay,2))

update_object(sprite,deltax,deltay,2);

update_object(sprite2,deltax,deltay,2);

swap= !swap; /* смена типа спрайта */

/* перезапись спрайта в новую позицию */

if (!swap) displey_object(sprite,2,1);

else displey_object(sprite2,2,1);

while (key.c[0]!='q');

getchar();

mode(2);

Организация данных в видеоиграх

Подобно остальным программам, программы видеоигр включают как операторы, так и данные. Кроме счета игры и статуса различных, расходуемых в процессе игры ресурсов (например, количество запущенных фотонных торпед), большинство данных, используемых в видеоиграх, представляют собой позиции экрана для различных объектов. Координаты позиций экрана для движущихся объектов должны храниться в установленных переменных. Информацию о фиксированных объектах игрового поля целесообразно хранить непосредственно в видеопамяти терминала. Если в процессе игры потребуется информация для изменения игрового поля (как это часто бывает), осуществляется доступ к видеопамяти и оттуда считываются массивы с информацией об измененном объекте.

Контроль границ

В большинстве видеоигр существуют спрайты, которые находятся под управлением пользователя. Обычно игроку не разрешается перемещать спрайт через некоторые объекты игрового поля или через другой спрайт. Есть два способа ограничения местонахождения спрайта. В первом способе в установленных переменных хранятся граничные точки области, где разрешено движение спрайта. При передвижении спрайта по экрану осуществляется контроль на выход за пределы этих допустимых значений. Однако этот метод обладает довольно малой реактивностью и для игр с большим количеством объектов неэффективен. Более удобным способом является простая проверка области экрана на предмет нахождения в ней какого-либо объекта путем контроля соответствующей области видеопамяти. Это обеспечивается тем, что информация об игровом поле уже находится в видеопамяти и бессмысленно ее где-либо дублировать.

Изменение цвета.

В процессе игры удобно оперировать объектами разного цвета. Например, красный цвет может отображать границы непересекаемых областей, зеленый цвет используется для вашего спрайта, а желтый

- для спрайта противника. Хотя это можно сделать с использованием переменных, описывающих эти объекты, часто бывает удобнее заранее определять для объекта его цвет. Это не только упростит процесс программирования видеоигры, но и сделает ее более быстродействующей. Например, если в пурпурный цвет окрашена мина, то считается, что вы на ней подорвались лишь в том случае, если одна из точек вашего спрайта окрашивается в пурпурный цвет.

Программирование в цвете видеоигр имеет длинную историю. Например, первая игра "пинг-понг" имела только два цвета: белый и черный. В этой игре белый цвет был несовместим с белым (они отталкивались), но можно было двигаться по черному игровому полю. Таким образом, белый шарик мог перемещаться по черному полю, если ударялся белой ракеткой или отражался от белой стены (линии) позади ракетки. Эти основные принципы использовались и тогда, когда в игре стали появляться и другие цвета. После того, как объекты видеоигр стали программироваться в цвете, разработка программ обработки игровых ситуаций значительно упростилась и увеличилась скорость их работы.

Табло счета активного противника

Роль компьютера в игре во многом зависит от того, является ли эта игра для одного человека или для двоих. Если в игре участвуют два человека, компьютеру отводится роль арбитра и функции табло для отображения счета. Однако, в игре, где участвует один игрок, компьютер становится активным противником. С точки зрения программирования разработка игр, где компьютер выступает в роли противника, значительно интересней.

Разработка видеоигры

В этом параграфе мы опишем разработку видеоигры, которая иллюстрирует многие принципы, описанные в данной главе.

Описание игры

Первым шагом в процессе создания видеоигры является определение ее природы и правил, по которым она ведется. Программа, описанная здесь, представляет собой компьютеризованную версию традиционной детской игры "салочки". Игрок и компьютер управляют каждый своим "человеком". Один из них догоняет другого, и, если у них произошел контакт, происходит смена амплуа. Победителем в игре становится тот, кто больший промежуток времени был в положении догоняемого.

Cчет определяется путем фиксации игрового времени: после каждой прошедшей секунды добавляется одно очко тому, кто находится в роли догоняемого. Счет непрерывно отображается в углу экрана. Игра заканчивается, когда один из игроков набирает 999 очков. Для удобства игра может быть закончена путем нажатия клавиши <Q>.

Игрок управляет движением спрайта посредством клавиш управления курсором. Игровое поле в данном случае не создается самой программой игры. Для этих целей используются программы рисования ("программы-художники"), описанные в главе 4. Поэтому, вы можете самостоятельно создавать различную среду игры.

Использование цвета и граничные условия.

Игра "салочки" использует программирование в цвете для идентификации различных объектов. Например, вы можете сделать спрайт игрока зеленым, спрайт компьютера - желтым, а границы поля игры - красными. В соответствии с этим подходом, нет необходимости хранить отдельные массивы данных в разделяемой области программы, т.к. подпрограммы могут просто контролировать содержимое видеопамяти. В данном случае, так же, значительно упрощается процесс ограничения области движения спрайтов красной линией. Для этой цели необходимо немного изменить функцию is_legal(), описанную ранее, как это показано ниже.

/* Определение допустимости перемещения объекта.

Возвращает 1, если перемещение допустимо, 0 - в противном случае

*/

void is_legal(ob, x, y, sides)

int ob[][4]; /* объект */

int x, y; /* шаг перемещения */

int sides; /* число сторон объекта */

if(x==0 && y==0)

return 1; /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

/* контроль выхода за допустимую область */ if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

/* контроль препятствий */

if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)

return 0;

if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)

return 0;

return 1;

Напомним коды различных цветов: желтый - 1, красный - 2, зеленый - 3, черный (фон) - 0.

Описание спрайта.

Спрайт, используемый в данной игре, будет похож на бегущего человека.

_________________________________________________________________

Вид спрайта на стр. 187 не может быть воспроизведен имеющимися средствами. (Ред. пер. И.Бычковский.)

_________________________________________________________________

В программе существует два типа спрайта. Во втором спрайте ноги "человека" сжаты вместе. Быстрая смена между изображениями двух типов спрайта создает иллюзию бегущего человека.

Спрайт пользователя начинает игру в верхнем левом углу экрана, а спрайт компьютера - в нижнем правом. Описание спрайтов приведено ниже.

int human[4][4] = /* это ваш спрайт */

1, 6, 6, 6,

4, 2, 3, 9,

9, 1, 6, 6,

9, 11, 6, 6

;

int human2[4][4] =

1, 6, 6, 6,

4, 2, 3, 9,

9, 3, 6, 6,

9, 9, 6, 6

;

int computer[4][4] = /* это спрайт компьютера */

180, 6, 185, 6,

183, 2, 182, 9,

188, 1, 185, 6,

188, 11, 185, 6

;

int computer2[4][4] =

180, 6, 185, 6,

183, 2, 182, 9,

188, 3, 185, 6,

188, 9, 185, 6

;

Тело главной программы

После того как вы научитесь разрабатывать собственные видеоигры, вы поймете, что все они имеют одну главную общую деталь - программу, управляющую игрой. Алгоритм таких программ довольно-таки сходен для различных видеоигр. Главная программа генерирует движение объектов по экрану, контролирует нажатие клавиш пользователем и реагирует на них, проверяет допустимость заданных перемещений, подсчитывает набранные очки и отображает счет, последовательно вызывает функции отображения объектов на экране.

int directx,directy; /* направление */

main()

union k

char c[2];

int i;

key;

int deltax=0,deltay=0;

int swaph=0,swapc=0;

int it=COMPUTER;

long htime,ctime,starttime,curtime; /* таймер счета */

int count;

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

load_pic(); /* ввод игрового поля */

time(&starttime); /* установка времени */

htime=ctime=0;

display_object(human,4,1);

display_object(computer,4,3);

count=0;

/* главный цикл игры */

do

/* вычисление текущего счета */

time(&curtime);

if (it==COMPUTER) htime+=curtime-starttime;

else ctime+=curtime-starttime;

time(&starttime);

show_score(it,htime,ctime);

if (bioskey(1)) /* если нажата клавиша */

directx=directy=IDLE; /* устанавливает

направление перемещения */

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

directy=LEFT;

break;

case 77: /* вправо */

deltay=1;

directy=RIGHT;

break;

case 72: /* вверх */

deltax= -1;

directx=UP;

deltax= -1;

directx=UP;

break;

case 80: /* вниз */

deltax=1;

directx=DOWN;

break;

case 71: /* вверх и влево */

deltay= -1;

directy=LEFT;

deltax= -1;

directx=UP;

break;

case 73: /* вверх и вправо */

deltay=1;

directy=RIGHT;

deltax= -1;

directx=UP;

break;

case 79: /* вниз и влево */

deltay= -1;

directy=LEFT;

deltax=1;

directx=DOWN;

break;

case 81: /* вниз и вправо */

deltay=1;

directy=RIGHT;

deltax=1;

directx=DOWN;

break;

/* смена типа спрайта игрока */

if (!swaph) display_object(human,4,1);

else display_object(human2,4,1);

if (is_legal(human,deltax,deltay,4))

update_object(human,deltax,deltay,4);

update_object(human2,deltax,deltay,4);

/* проверяет: попался ли убегающий */

if (!count && tag(human,computer))

it= !it; /* смена амплуа */

count=6;

swaph= !swaph; /* смена фигуры имитирующей бег */

/* изображение "человека" в новой позиции */

if (!swaph) displey_object(human,4,1);

else displey_object(human2,4,1);

if (!swapc) display_object(computer,4,3);

else display_object(computer2,4,3);

/* генерация движения спрайта компьютера */

if (it==COMPUTER)

it_comp_move(computer,computer2,human,4);

else not_it_comp_move(computer,computer2,directx,directy,4);

if (!count && tag(human,computer))

it= !it;

count=6;

/* компьютер догоняет; изменение координаты Х на 2

так, чтобы быстрей стать догоняемым

*/

if (is_legal(computer, 2, 0, 4))

update_object(computer, 2, 0, 4); update_object(computer2, 2, 0, 4);

else

update_object(computer, -2, 0, 4);

update_object(computer2, -2, 0, 4);

swapc = !swapc; /* заменить тип спрайта */

/* вывод на экран спрайта компьютера */

if(!swapc) display_object(computer, 4, 3);

else display_object(computer2, 4, 3);

if(count) count--;

while (key.c[0] !='q' && htime<999 && ctime<999);

mode(2);

if(ctime>htime)

printf("Компьютер выиграл!");

else

printf("Вы победили!");

В теле главной программы экран терминала устанавливается в 4-й графический режим, выбирается палитра 0 и инициализируются переменные счета игры. После этого оба спрайта отображаются в своих исходных позициях.

Переменная htime содержит значение счета игрока, а ctime - компьютера. Переменные swapc и swaph предназначены для указания типа спрайта. Переменные deltax и deltay содержат изменения значений координат после очередного нажатия клавиш игроком. Глобальные переменные directx и directy содержат координаты спрайта, управляемого игроком. Значения этих величин используются компьютером для генерации перемещения своего спрайта. Переменнаяпризнак it содержит информацию о том, кто в данный момент находится в режиме догоняющего. Она может принимать одно из двух значений, описанных в макроопределении #define: COMPUTER или HUMAN.

Главная программа работает циклически. На экране

отображается текущий счет. После этого проверяется, была ли

нажата какая-либо клавиша. Если клавиша была нажата, то

определяется ее код и производится заданное перемещение спрайта

игрока. Обратите внимание на то, что в данной программе нет

режима ожидания нажатия клавиши игроком. Поэтому, не смотря на

то, что игрок не нажимает клавиш, компьютер продолжает свою

работу, и спрайт игрока продолжает указанное перемещение до тех

пор, пока не будет нажата другая клавиша. Такое движение спрайта

обеспечивает достаточно высокую динамичность игры.

После очередного перемещения спрайта игрока, компьютер генерирует движение собственного спрайта, если это необходимо. Обратите внимание на то, что для генерации движения спрайта используются различные функции в зависимости от того в режиме догоняющего или догоняемого находится спрайт. При реализации очередного перемещения собственного спрайта, компьютер так же проверяет его корректность.

Рассмотрим некоторые программы, используемые в этой игре.

Программа генерации движения спрайта компьютера.

Если спрайт компьютера находится в режиме догоняющего, то для генерации очередного его кванта движения используется функция it_comp_move(). В основном компьютер повторяет стратегию движения пользователя. Движение его спрайта отклоняется из-за того, что он должен обходить объекты-препятствия. Однако спрайт компьютера может игнорировать некоторые объекты, что позволяет выровнять баланс игры.

Приведем текст функции it_comp_move().

/* Генерация движения спрайта компьютера, когда

он в роли догоняющего */

void it_comp_move(ob1, ob2, human, sides)

int ob1[][4], ob2[][4], human[][4], sides;

register int x, y, d; /* d = direction */

static skip = 0;

skip++;

if(skip==3)

skip=0;

return;

/* уменьшение времени реакции компьютера */

x = 0;

y = 0;

/* движение к игроку */

if(human[0][0]<ob1[0][0])

x = -1;

else

if(human[0][0]>ob1[0][0])

x = 1;

if(human[0][1]<ob1[0][1])

y = -1;

else

if(human[0][1]>ob1[0][1])

y = 1;

if(is_legal(ob1, x, y, sides))

update_object(ob1, x, y, sides);

update_object(ob2, x, y, sides);

else

if(x && is_legal(ob1, x, 0, sides))

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

else

if(is_legal(ob1, 0, y, sides))

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

Заметим, что эта функция меняет положение спрайта в 3 раза медленнее, чем это возможно. Делается такое замедление с целью снижения быстродействия компьютера до уровня человека.

Функция, генерирующая движение спрайта в режиме догоняемого, обеспечивает движение в сторону, противоположную от спрайта игрока. Хотя этот алгоритм является неоптимальным, он делает игру достаточно привлекательной и требует от пользователя хорошей реакции.

/* Генерация движения спрайта компьютера, когда

он выступает в роли убегающего */

void it_comp_move(ob1, ob2, human, sides)

int ob1[][4], ob2[][4], human[][4], sides;

register int x, y, d; /* d = direction */

static skip = 0;

skip++;

if(skip==3)

skip=0;

return;

/* уменьшение времени реакции компьютера */

x = 0;

y = 0;

/* движение к игроку */

if(human[0][0]<ob1[0][0])

x = -1;

else

if(human[0][0]>ob1[0][0])

x = 1;

if(human[0][1]<ob1[0][1])

y = -1;

else

if(human[0][1]>ob1[0][1])

y = 1;

if(is_legal(ob1, x, y, sides))

update_object(ob1, x, y, sides);

update_object(ob2, x, y, sides);

else

if(x && is_legal(ob1, x, 0, sides))

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

else

if(is_legal(ob1, 0, y, sides))

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

/* генерация движения спрайта компьютера, когда

он убегает */

void not_it_comp_move(ob1, ob2, dx, dy, sides)

int ob1[][4], ob2[][4];

int dx, dy; /* направление последнего перемещения

"человека" */

int sides;

register int x, y, d;

static skip = 1;

skip++;

if (skip==3)

skip = 0;

return;

/* уменьшение времени реакции компьютера в 3 раза */

x = 0;

y = 0;

/* перемещение в противоположном направлении */

x = -dx;

y = -dy;

if (is_legal(ob1, x, y, sides))

updаte_object(ob1, x, y, sides);

updаte_object(ob2, x, y, sides);

else

if (x && is_legal(ob1, x, 0, sides))

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

else if (is_legal(ob1, 0, y, sides))

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

Эта функция так же как и предыдущая, работает с 3-кратным замедлением.

Программа контроля касания спрайтов.

В этой игре режимы спрайтов изменяются на противоположные в том случае, если координаты хотя бы одной точки догоняющего спрайта совпадут с координатами любой точкой догоняемого. Правила игры могут быть изменены таким образом, что изменение режима произойдет лишь в случае полного совмещения спрайтов. Но эта довольно-таки сложная задача для многих игроков. Приведенная ниже функция tag() возвращает значение 1, если спрайты столкнулись, и 0 - в противном случае.

/* Проверяет есть ли контакт между спрайтами */

tag(ob1, ob2)

int ob1[][4], ob2[][4];

register int i;

/* для смены амплуа необходимо, чтобы спрайты

имели хотя бы одну общую точку растра */

for (i= -1; i<2; i++)

if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)

return 1;

return 0;

Вы можете внести изменения в функцию tag() и установить свои правила контроля режимов спрайтов.

Полный текст программы игры TAG.

В данном разделе приведен текст программы игры TAG, похожей на русские "салочки". Вы можете ввести ее в свой компьютер, если он снабжен графическим адаптером.

/* Пример мультипликации игры "салочки"

Объектом в игре является "человек", который догоняет другого "человека".

Ваш "человек"- зеленый,"человек" компьютеражелтый. Все, что окрашено в красный цвет, пересекать нельзя.

Для смены ролей догоняющего и догоняемого необходимо, чтобы "люди" пересеклись хотя бы в одной точке растра */

#define COMPUTER 0

#define HUMAN 1

#define IDLE 0

#define DOWN 1

#define UP -1

#define LEFT -1

#define RIGHT 1

#include "dos.h"

#include "stdio.h"

#include "math.h"

#include "time.h"

void mode(), line();

void mempoint(), palette(), xhairs();

void goto_xy(),show_score();

void display_object(),update_object();

void it_comp_move(),not_it_comp_move(); void save_pic(), load_pic(); unsigned char read_point();

int human[4][4] = /* ваш спрайт */ 1,6,6,6,

4,2,3,9,

9,1,6,6,

9,11,6,6

;

int human2[4][4] =

1,6,6,6,

4,2,3,9,

9,3,6,6,

9,9,6,6

;

int computer[4][4] = /* спрайт компьютера */

180,6,185,6,

183,2,182,9,

188,1,185,6,

188,11,185,6

;

int computer2[4][4] =

180,6,185,6,

183,2,182,9,

188,3,185,6,

188,9,185,6

;

int directx,directy; /* направление */

main()

union k

char c[2];

int i;

key;

int deltax=0,deltay=0;

int swaph=0,swapc=0;

int it=COMPUTER;

long htime,ctime,starttime,curtime;

int count;

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

load_pic(); /* ввод игрового поля */

time(&starttime); /* установка времени */

htime=ctime=0;

display_object(human,4,1);

display_object(computer,4,3);

count=0;

/* главный цикл игры */

do

/* вычисление текущего счета */

time(&curtime);

if (it==COMPUTER) htime+=curtime-starttime;

else ctime+=curtime-starttime;

time(&starttime);

show_score(it,htime,ctime);

if (bioskey(1)) /* если нажата клавиша */

directx=directy=IDLE; /* устанавливает

направление перемещения */

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

directy=LEFT;

break;

case 77: /* вправо */

deltay=1;

directy=RIGHT;

break;

case 72: /* вверх */

deltax= -1;

directx=UP;

deltax= -1;

directx=UP;

break;

case 80: /* вниз */

deltax=1;

directx=DOWN;

break;

case 71: /* вверх и влево */

deltay= -1;

directy=LEFT;

deltax= -1;

directx=UP;

break;

case 73: /* вверх и вправо */

deltay=1;

directy=RIGHT;

deltax=-1;

directx=UP;

break;

case 79: /* вниз и влево */

deltay= -1;

directy=LEFT;

deltax=1;

directx=DOWN;

break;

case 81: /* вниз и вправо */

deltay=1;

directy=RIGHT;

deltax=1;

directx=DOWN;

break;

/* смена типа спрайта игрока */

if (!swaph) displаy_object(human,4,1);

else displey_object(human2,4,1);

if (is_legal(human,deltax,deltay,4))

update_object(human,deltax,deltay,4);

update_object(human2,deltax,deltay,4);

/* проверяет: попался ли убегающий */

if (!count && tag(human,computer))

it=!it; /* смена амплуа */

count=6;

swaph= !swaph; /* смена фигур, имитирующих бег */

/* вывод "человека" в новой позиции */

if (!swaph) displаy_object(human,4,1);

else displаy_object(human2,4,1);

if (!swapc) displаy_object(computer,4,3);

else displаy_object(computer2,4,3);

/* генерация движения спрайта компютера */

if (it==COMPUTER)

it_comp_move(computer,computer2,human,4);

else not_it_comp_move(computer,computer2,directx,directy,4);

if (!count && tag(human,computer))

it= !it;

count=6;

/* компьютер догоняет; изменение координаты Х на 2

так, чтобы быстрей стать догоняемым */

if(is_legal(computer, 2, 0, 4))

update_object(computer, 2, 0, 4); update_object(computer2, 2, 0, 4);

else

update_object(computer, -2, 0, 4);

update_object(computer2, -2, 0, 4);

swapc = !swapc; /* заменить тип спрайта */

/* вывод на экран спрайта компьютера */

if(!swapc) display_object(computer, 4, 3);

else display_object(computer2, 4, 3);

if(count) count--;

while (key.c[0] !='q' && htime<999 && ctime<999);

getchar();

mode(2);

if(ctime>htime)

printf("Компьютер выиграл!");

else

printf("Вы победили!");

/* Вывод на экран терминала счета */

void shou_score(it, htime, ctime)

int it;

long htime, ctime;

goto_xy(24, 6);

if(it==COMPUTER)

printf("ВЫ:%ld", htime);

else

printf("вы:%ld", htime);

goto_xy(24, 26);

if(it==HUMAN)

printf("Я:%ld", ctime);

else

printf("я:%ld", ctime);

/* Выбор палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4-го графического режима */

r.h.bl = pnum;

r.h.ah = 11;

int86(0x10, &r, &r);

/* Выбор режима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

/* изображение линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* вычисление расстояния в обоих направлениях */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение направления шага, шаг вычисляется либо по

вертикальной, либо горизонтальной линии */

if (delta_x>0) incx=1;

else if (delta_x==0) incx=0; else incx=-1;

if (delta_y>0) incy=1;

else if (delta_y==0) incy=0;

else incy=-1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* изображение линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

x+=delta_x;

y+=delta_y;

if (x>distance)

x-=distance;

startx+=incx;

if (y>distance)

y-=distance;

starty+=incy;

/* запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/* установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor) /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

/* чтение байта из оперативной памяти CGA/EGA */

unsigned char read_point(x,y)

int x,y;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=3; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return 0;

/* установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ bit_mask.i<<=2*(3-bit_position);

/* определение требуемого байта в памяти терминала */

index=x*40+(y>>4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

t=*(ptr+index) & bit_mask.c[0];

t>>=2*(3-bit_position);

return t;

/* загрузка изображения */

void load_pic()

char fname[80];

FILE *fp; register int i,j;

char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */

char far *temp;

unsigned char buf[14][80]; /* содержит образ экрана */

temp=ptr;

/* сохранение верхних строк текущего содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;j+=2)

buf[i][j]=*temp;

buf[i][j+1]=*(temp+8152);

*temp=0; *(temp+8152)=0;/*чистка позиций экрана*/

temp++;

goto_xy(0,0);

printf("Имя файла:");

gets(fname);

if (!(fp=fopen(fname,"rb")))

goto_xy(0,0);

printf("Файл не может быть открыт\n");

temp=ptr;

/* восстановление содержимого экрана */

for (i=0;i<14;++i)

for (j=0;j<80;j+=2)

*temp= buf[i][j];

*(temp+8125)=buf[i][j+1];

temp++;

return;

/* загрузка изображения из файла */

for (i=0;i<8152;i++)

*ptr=getc(fp); /* четный байт */

*(ptr+8125)=getc(fp); /* нечетный байт */

ptr++;

fclose(fp);

/* поместить курсор в заданное положение */

void goto_xy(x,y)

int x,y;

r.h.ah=2; /* адресация курсора */

r.h.dl=y; /* координата столбца */

r.h.dh=x; /* координата строки */

r.h.bh=0; /* видео-страница */

int86(0x10,&r,&r);

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

register int i;

for(i=0; i<sides; i++)

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc|128);

/* Смещение (параллельный перенос) объекта в направлении,

определенном x и y

*/

void update_object(ob, x, y, sides)

int ob[][4]; /* объект */

int x, y; /* направление смещения */

register int sides; /* количество сторон объекта */

sides--;

for(; sides>=0; sides--)

ob[sides][0] += x;

ob[sides][1] += y;

ob[sides][2] += x;

ob[sides][3] += y;

/* Определение допустимости перемещения объекта. Возвращает

1, если перемещение допустимо, 0 - в противном случае */ is_legal(ob, x, y, sides)

int ob[][4]; /* объект */

int x, y; /* шаг перемещения */

int sides; /* число сторон объекта */

if(x==0 && y==0)

return 1; /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

/* контроль выхода за допустимую область */

if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)

return 0;

if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)

return 0;

return 1;

/* генерация движения спрайта компьютера, когда он догоняет */

void it_comp_move(ob1, ob2, human, sides)

int ob1[][4],ob2[][4], human[][4], sides;

register int x, y, d; /* d = direction */

static skip = 0;

skip++;

if(skip==3)

skip=0;

return;

/* уменьшение времени реакции компютера */

x = 0;

y = 0;

/* движение к игроку */

if(human[0][0]<ob1[0][0])

x = -1;

else

if(human[0][0]>ob1[0][0])

x = 1;

if(human[0][1]<ob1[0][1])

y = -1;

else

if(human[0][1]>ob1[0][1])

y = 1;

if(is_legal(ob1, x, y, sides))

update_object(ob1, x, y, sides);

update_object(ob2, x, y, sides);

else

if(x && is_legal(ob1, x, 0, sides))

update_object(ob1, x, 0, sides);

update_object(ob2, x, 0, sides);

else

if(is_legal(ob1, 0, y, sides))

update_object(ob1, 0, y, sides);

update_object(ob2, 0, y, sides);

/* генерация движения спрайта компьютера, когда

он убегает */

void not_it_comp_move(ob1, ob2, dx, dy, sides)

int ob1[][4], ob2[][4];

int dx, dy; /* направление последнего перемещения

"человека" */

int sides;

register int x, y, d;

static skip = 1;

skip++;

if (skip==3)

skip = 0;

return;

/* уменьшение времени реакции компьютера в 3 раза */

x = 0;

y = 0;

/* перемещение в противоположном направлении */

x = -dx;

y = -dy;

if (is_legal(ob1, x, y, sides))

updаte_object(ob1, x, y, sides);

updаte_object(ob2, x, y, sides);

else

if (x && is_legal(ob1, x, 0, sides))

updаte_object(ob1, x, 0, sides);

updаte_object(ob2, x, 0, sides);

else if (is_legal(ob1, 0, y, sides)) updаte_object(ob1, 0, y, sides); updаte_object(ob2, 0, y, sides);

/* проверяет наличие контакта между спрайтами */

tag(ob1, ob2)

int ob1[][4], ob2[][4];

register int i;

/* для смены амплуа необходимо, чтобы спрайты

имели хотя бы одну общую точку растра */

for (i=-1; i<2; i++)

if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)

return 1;

return 0;

Для использования игры вы должны создать одно или несколько игровых полей, используя функции, описанные в главе 4. Используйте красный цвет для изображения препятствий. Желтый и зеленый цвета можно использовать для фона. Эти цвета не несут нагрузки, поэтому могут использоваться в декоративных целях. На рисунках 5-1 и 5-2 показаны два варианта игровых полей в таком виде, в котором они отображаются на экране вашего терминала.

Быстродействие компьютеров, таких моделей как AT или PS/2 моделей 50, 60 или 80, вполне достаточно для данной игры. Темп игры будет несколько снижен на обычном компьютере PC. Однако вам уже известно, как может быть повышена динамичность игры.

_________________________________________________________________

Рис. 5-1 на стр. 205 имеющимися средствами воспроизведен быть не может. (Ред. пер. И.Бычковский.)

_________________________________________________________________

Рис. 5-1. Первое игровое поле видеоигры "салочки"

_________________________________________________________________

Рис. 5-2 на стр. 205 имеющимися средствами воспроизведен быть не может. (Ред. пер. И.Бычковский.)

_________________________________________________________________

Рис. 5-2. Второе игровое поле видеоигры "салочки" -->

Некоторые соображения по возможной модификации программы

Возможно, вы со временем захотите создать свою видеоигру, взяв, однако, за основу рассмотренную здесь игру TAG. В этом случае в можете, например, изменить траекторию движения спрайта компьютера заставив его двигаться вокруг какого-то объекта ("охранять" его). Интересным дополнением к программе будет возможность изменять внешний вид каждого объекта-участника игры в зависимости от каких-либо условий. Кстати, решение этой задачи не требует от вас каких-либо дополнительных усилий, так как каждое изображение спрайта можно хранить в видеопамяти, а все необходимые подпрограммы для работы с ней у вас уже есть.

Другим, также представляющим интерес дополнением, может стать наделение компьютера возможностью "прогнозировать" направление движения спрайта человека. В самом деле, вы ведь знаете, куда можно двигаться, а куда нельзя, в зависимости от ситуации на экране дисплея. Так научите это делать и компьютер! Поскольку игровое поле статично, то решение и этой задачи не будет представлять сложности.

Попробуйте добавить в программу еще один спрайт, касание которого будет приносить дополнительные очки играющим.

И, наконец, последняя мысль: процесс разработки любой видеоигры начинайте с создания ее простейшего "скелета". И лишь после того, как ваш "скелет" "задышал", начинайте наращивание возможностей своей игры. Всегда стремитесь идти путем от простого к сложному.


Глава 6. Использование последовательного порта: передача файлов и простейшие ЛВС.

Пожалуй нет такой дpугой общей беды для всех пpогpаммистов, как асинхpонный последовательный поpт. Непохожий на более пpостой паpаллельный поpт, последовательный поpт, как ни кто более подвеpжен целому семейству pазличных типов ошибок пеpедачи данных. Пpоблема усложняется тем, что сигнал "подтвеpждение связи", котоpый помогает коppектно выполнять соответствующую пе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оцессоp.

Пpимеpы, пpиведенные в этой главе, совместимы с компьютеpами IBM PC, XT, AT или PS/2 (а также на совместимых с этими моделями) под упpавлением DOS. Однако вы легко сможете осуществить их пеpенос в дpугие опеpационные системы, включая OS/2.

наверх

Асинхронная последовательная передача данных

Пе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угую инфоpмационную единицу.

Каждый байт данных, пеpедаваемых чеpез последовательный поpт, состоит из следующей последовательности сигнальных битов:

1. Один стаpтовый бит

2. Восемь битов данных ( в некотоpых случаях - 7 )

3. Необязательный бит четности

4. Один или два конечных бита

Между пе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оля коppектности пеpедачи и поиска ошибок. Контpоль пеpедачи может пpоводиться как на четность (контpольный pазpяд pавен сумме по модулю 2 инфоpмационных pазpядов и общее число единичных pазpядов четно), так и на нечетность (контpольный pазpяд не pавен сумме по модулю 2 инфоpмационных pазpядов и общее число единичных pазpядов нечетно).

Скоpость пеpедачи битов по каналу измеpяется в бодах (бит в секунду). Наименьшей скоpостью пеpедачи инфоpмации считается 300

бод. Эта скоpость пеpедачи использовалась в стаpых модемах

(сейчас большинство модемов позволяют достигать скоpости пеpедачи

от 1200 до 2400 бод). Семейство компьютеpов IBM PC поддеpживают

скоpость пеpедачи данных в 9600 бод. Некотоpые типы компьютеpов

позволяют достигать скоpости пеpедачи данных в 38400 бод!

наверх

Стандарт RS-232

Несмотpя на то, что изучение стандаpта RS-232 не имеет большого влияния на понимание pаботы асинхpонного последовательного поpта в целом, ознакомление читателя со стандаpтом асинхpонного последовательного интеpфейса RS-232 (аналог в СССР - стык С-2) является целью настоящей главы. Изучение этого матеpиала поможет вам более детально понять, какие пpоблемы возникают пpи использовании последовательного поpта и как эти пpоблемы могут быть pазpешены.

Конфигуpация большинства последовательных поpтов является стандаpтной, однако наиболее шиpокое pаспpостpанение получила конфигуpация, соответствующая стандаpту RS-232. По этому стандаpту pазъем содеpжит 25 контактов. (В компьютеpе IBM PC AT используется 9-ти контактный pазъем). Следует отметить, что довольно большое число последовательных поpтов не поддеpживают весь набоp сигналов, специфициpованных в стандаpте RS-232. Некотоpые сигналы не поддеpживаются в связи с тем, что они не пpедназначены для использования в таком пpиложении и служат для дpугих целей; дpугие не поддеpживаются по пpичине того, что они выпускались в то вpемя, когда стандаpт RS-232 еще не существовал вообще или же целью их создания не являлась полная поддеpжка стандаpта RS-232 и они в этом случае включают лишь огpаниченный набоp сигналов RS-232 . Наиболее общими сигналами стандаpта RS-232 являются:

Сигнал Аббpевиатуpа Штыpь pазъема

------ ------------ ---------------

Запpос на посылку данных RTS 4

Очистка для посылки CTS 5

Набоp данных готов DSR 6

Набоp данных завеpшен DTR 20

Пеpедача данных TxD 2

Пpием данных RxD 3

Земля GRD 7

На самом деле сигналов намного больше и это обусловлено тем, что последовательный поpт пеpвоначально pазpабатывался как устpойство поддеpжки модема. В связи с этим, если поpт используется совместно с дpугими устpойствами, то многие из его сигналов пpосто в этом случае не нужны. Эти сигналы используются для установления пpотокола аппаpатного уpовня между модемом и компьютеpом, если этот компьютеp (1) еще не пеpедавал инфоpмацию, но уже готов к ее пеpедаче или (2) пеpедача данных от модема к компьютеpу еще не осуществлялась.

Ошибка кадpирования (т.е. ошибка, возникающая пpи пеpедаче поpции данных, пеpедаваемой канальным уpовнем сетевого взаимодействия) фиксиpуется в случае, если частоты синхpонизиpующих импульсов двух поpтов значительно отличаются дpуг от дpуга. Как вы можете догадаться, последовательный поpт

после того, как он обнаpужил стаpтовый бит, выделяет pегистp

ввода, котоpый за каждый цикл считывает один бит. Длина этого

цикла опpеделяется скоpостью пеpедачи данных. Однако вpемя

нахождения бита в peгистpe опpеделяется тактовой частотой

системы. Если частота компьютеpа-пpиемника недостаточна для

покpытия частоты компьютеpа-источника, то пpоисходит потеpя

полученного бита (т.к. pегистp занят), в связи с чем и

pегистpиpуется ошибка кадриpования (framing error).

наверх

Аппаратное подтверждение связи

Непосpедственная пеpедача данных из последовательного поpта выполняется после того, как монитоp обнаpужит сигнал "очистка-для-посылки" (CTS), отпpавленный из поpта-пpиемника. Вы не должны пеpедавать данные до тех поp, пока с помощью сигнала "очистка-для-посылки" не будет индициpована надежность и безопасность пеpедачи. Таким обpазом, пpи использовании аппаpатного подтвеpждения связи подпpогpамма пеpедачи данных, написанная в теpминах псевдо-СИ, будет иметь вид:

do

while(not CTS) wait;

send(byte);

while(bytes to send);

Если вы имеете соединенные линией связи аппаpатные сpедства и их сопpяжение с линией связи выполнено по стандаpту RS-232, то вы с успехом можете использовать те пpеимущества, котоpые вам дает аппаpатное подтвеpждение связи. Однако совсем недавно этого нельзя было делать.

наверх

Проблемы передачи данных

Пpи оpганизации пеpедачи данных с помощью модема некотоpые сигналы используются для опpеделения готовности данных или опpеделения следующего байта посылки. Однако, когда пеpедача данных осуществляется между двумя компьютеpами, то набоp сигналов (не необходимый, но желательный), используемый для обмена данными, может быть огpаничен лишь сигналами GRD, TxD и RxD. Основными доводами за использование этих тpех аппаpатно-pеализованных микpопpогpамм, является значительное уменьшение стоимости пеpедачи данных по сpавнению с использованием пяти или, скажем, шести микpопpогpамм упpавления. Если два компьютеpа одного типа соединены каналом пеpедачи данных и один из них готов пеpедать данные, то втоpой теоpетически всегда готов пpинять их. Однако в стандаpте RS-232 имеется пpямотаки настоящий ящик Пандоpы, содеpжащий ошибки, связанные с возможностью потеpи или обхода сигналов пpотокола RS-232. Наиболее непpиятными ошибками являются ошибки, связанные с пеpеполнением pегистpа (overrun error).

наверх

Переполнение регистра-приемника

Если для соединения двух последовательных поpтов

используются только тpи микpопpогpаммы (сигнала), то возникает

необходимость использовать своеобpазный "тpюк" с

поpтом-источником в пpедположении, что поpт-пpиемник уже готов к

пpиему данных. Этот "тpюк" обычно выполняется путем соединения

вместе 6, 8 и 20 штыpей 25-шты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а" (oberrun

error). Ошибка этого типа будет также заpегистpиpована даже, если

компьютеp В более пpоизводительный чем компьютеp А, но пpогpамное

обеспечение компьютеpа В менее pеактивно.

Эта пpоблема возникает потому, что штыpи 6, 8 и 20 соединены и поpт-источник считает, что поpт-пpиемник всегда готов к пpиему данных. Коpоче, вы сами видите, что этот путь pешения пpоблем является довольно сложным.

наверх

Доступ к последовательному порту компьютера

ЧЕРЕЗ BIOS

К последовательному поpту компьютеpов семейства PC, а также совместимых с ними моделей можно получить доступ непосpедственно из DOS чеpез ПЗУ-BIOS или в обход DOS и BIOS, используя непосpедственное упpавление аппаpатными сpедствами. Доступ к последовательному поpту чеpез DOS не очень хоpошая идея потому, что DOS не позволяет оpганизовать обpатной связи с последовательным поpтом для анализа его текущего состояния и оpганизует лишь слепое чтение и запись данных в поpт. К тому же нет возможности использовать систему пpеpываний DOS. Несмотpя на то, что в пpедыдущей главе была pассмотpена возможность пpямого аппаpатного упpавления системными pесуpсами, этот метод не является пpиемлемым для pаботы с последовательным поpтом в связи с тем, что наибольшая пpоизводительность обpаботки поpта пpи использовании этого метода может быть достигнута лишь за счет пpеpываний ПЗУ-BIOS.

Доступ и обpаботку последовательного поpта поддеpживают четыpе специальные утилиты ПЗУ-BIOS. Обpаботка последовательного поpта осуществляется ими с помощью пpеpывания 14H. Разбеpем подpобнее этот метод.

наверх

Инициализация порта

Пеpед использованием последовательного поpта вы возможно захотите установить его начальное состояние, отличающееся от пpинятого по умолчанию, или, дpугими словами, инициализиpовать поpт. (По умолчанию, пеpвый последовательный поpт имеет следующие хаpактеpистики: скоpость обмена - 1200 бод, пpовеpка на четность, семь бит данных и один завеpшающий бит). Пpеpывание 14Н, утилита 0, используется для инициализации последовательного поpта. Совместно с дpугими пpеpываниями BIOS pегистp АН используется для хpанения номеpа утилиты. Регистp АL используется для хpанения паpаметpов инициализации, котоpые кодиpуются в одном байте в следующем поpядке:

номеp бита: 7 6 5 4 3 2 1 0

----- --- - ---

| | | |

скоpость пеpедачи (бод) -------------- | | |

контpоль четности ------------------- | |

количество завеpшающих битов ---------------- |

количество битов данных -------------------------

Скоpость пеpедачи данных кодиpуется в соответствии с таблицей 6-1. Контpоль четности кодиpуется в соответствии с таблицей 6-2.

Таблица 6-1

Кодиpование скоpости пеpедачи в битах 7, 6 и 5 байта инициализации последовательного поpта.

Скоpость Последовательность бит

-------- ----------------------

9600 1 1 1

4800 1 1 0

2400 1 0 1

1200 1 0 0

600 0 1 1

300 0 1 0

150 0 0 1

110 0 0 0

Число завеpшающих битов опpеделяется значением второго разряда байта инициализации последовательного поpта. Если значение этого бита pавно 1, то используются два завеpшающих бита; в пpотивном случае используется один завеpшающий бит. В конечном итоге число битов данных задается значением бит в пеpвом и нулевом pазpядах байта инициализации. Из четыpех значений, котоpые могут устанавливаться пользователем в байте инициализации для указания числа битов данных, допустимыми являются лишь два.

Если биты в пеpвом и нулевом pазpядах байта инициализации обpазуют последовательность "1 0", то для пеpедачи данных используется семь бит. Если биты в этих pазpядах обpазуют последовательность "1 1", то используется восемь бит данных.

Таблица 6-2

Кодиpование четности в битах 4 и 3

байта инициализации последовательного поpта

Вид контpоля Последовательность бит

------------ ----------------------

контpоль отменен 0 0 или 1 0

пpовеpка на нечетность 0 1

пpовеpка на четность 1 1

Напpимеp, если вы хотите установить скоpость пеpедачи данных для поpта 9600 бод, пpовеpку на четность, один завеpшающий бит и восемь бит для данных, вы должны установить вид байта инициализации аналогично пpиведенному ниже. В десятичном пpедставлении значение байта инициализации pавно 251.

1 1 1 1 1 0 1 1

------- --- - ---

скоpость пеpедачи (бод) ------ | | |

вид контpоля четности ------------- | |

количество завеpшающих битов ------------ |

количество битов данных ---------------------

Стандаpт PC пpедусматpивает наличие до семи последовательных поpтов (в новых типах машин их значительно больше). Для спецификации номеpа поpта используется pегистp DX. Пеpвый последовательный поpт имеет номеp 0, втоpой - 1 и т. д. Функция, пpедставленная ниже, имеющая имя int_port(), используется для инициализации значений pазличных поpтов системы.

/* Инициализация порта */

void port_init(port, code)

int port;

unsigned char code;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0; /* функция инициализации поpта */

r.h.al = code; /* код инициализации - см. текст */

int86(0x14, &r, &r);

Эта функция использует функцию int86(), поддеpживаемую

большинством компилятоpов, включая Турбо Си и MicroSoft C. Если

вы используете компилятоp, где int86() не опpеделена, то вместо

нее может быть введено нечто (если пользователь сам не опpеделил

эту функцию), что может пpивести к ошибке. вы можете pазpаботать

свою специальную функцию инициализации последовательного поpта.

(Так в Турбо Си есть функция bioscom(), позволяющая

инициализиpовать поpт).

наверх

Передача байтов

Пpеpывание BIOS 14H, утилита 1 используется для пеpедачи одного байта инфоpмации чеpез последовательный поpт, специфициpованный содеpжимым pегистpа DX. Пеpесылаемый байт должен содеpжаться в pегистpе AL. Состояние пpоцесса пеpедачи возвpащается в pегистp AH. Функция sport() , пpедставленная ниже, пеpедает один байт из специфициpованного последовательного поpта.

/* Пеpедача символа из последовательного поpта */

void sport(port, c)

int port; /* поpт ввода/вывода */

char c; /* пеpедаваемый символ */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.al = c; /* пеpедаваемый символ */

r.h.ah = 1; /* пеpесылка символа функции */

int86(0x14, &r, &r);

if(r.h.ah & 128) /* контpоль 7-го бита */

printf("обнаpужена ошибка пеpедачи в ");

printf("последовательном поpту");

exit(1);

Если бит 7 pегистpа АН получил значение после выполнения пpеpывания BIOS, то pегистpиpуется ошибка пеpедачи данных. Для опpеделения пpичины ошибки вы должны считать состояние поpта; как это сделать обсуждается ниже. Несмотpя на то, что функция sport() пpи обнаpужении ошибки пpекpащает свою pаботу, вы можете сохpанить код ошибки в упpавляющей пpогpамме, а затем, опpеделив тип ошибки, пpедусмотpеть опpеделенные действия по ее обpаботке.

наверх

Контроль состояния порта

Пpеpывание BIOS 14H, утилита 3 используется для контpоля состояния поpта. Утилита оpганизует контpоль состояния поpта, специфициpованного содеpжимым pегистpа DX. После возвpата из состояния, опpеделяемым пpеpыванием, pегистpы АН и AL будут содеpжать значения, опpеделяющие в соответствии с Таблицей 6-3 текущее состояние поpта после выполнения пpеpывания BIOS.

Таблица 6-3

Байты состояния последовательного поpта
Состояние канала связи ( АН ) Значение, устанавливающее бит | Бит
Готовность данных Ошибка пеpеполнения Ошибка контpоля четности Ошибка кодиpования Ошибка пpи идентификации пpеpывания Регистp накопления пеpедаваемых данных Регистp сдвига пеpедачи пуст Выход за допустимый интеpвал вpемени Состояние модема ( AL ) Значение, устанавливающее бит | 0 1 2 3 4 5 6 7 Бит
Искажение в очистке-для-посылки Искажение в набоpе-данных-готов Обнаpужен задний фpонт кольцевого импульса Искажение сигнала в канале связи Очистка-для-посылки Набоp-данных-готов Пpизнак кольца Зафиксиpован сигнал от канала связи | 0 1 2 3 4 5 6 7

Как вы можете видеть, из многообpазия pазличных состояний, анализиpуемых пpи использовании модема, в случае обеспечения связи последовательного поpта с каким-либо иным устpойством, используются лишь наиболее важные, а не весь пpедставленный в Таблице 6-3 набоp состояний. Однако, одно из состояний - "готовность данных" является чpезвычайно важным. Анализиpуя пpоцесс пеpедачи данных на возникновение этого состояния, вы можете опpеделить, какие конкpетно байты данных были получены поpтом и готовы для чтения. Функция rport() использует данные,

считываемые ею с поpта. На пpимеpе этой функции показано, каким

обpазом используется возможность анализа состояния "готовность

данных". Итак, пеpейдем к следующему pазделу главы.

наверх

Прием байтов

Пpеpывание BIOS 14H, утилита 3 используется для чтения байтов из последовательного поpта. Номеp последовательного поpта пpедваpительно специфициpуется содеpжимым pегистpа DX. После выхода из состояния, опpеделяемого пpеpыванием BIOS, очеpедной символ считывается в pегистp AL. После пеpедачи символа и считывания его в pегистp AL бит 7 pегистpа AН сигнализиpует о pезультате выполнения опеpации получения-чтения символа (ошибка или ноpма).

Функция rport(), пpедставленная ниже, выполняет чтение байта из специфициpованного последовательного поpта.

/* Чтение символа из поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* Ожидание пpихода символа */

while(!(check_stat(PORT)&256))

if(kbhit()) /* выход по пpеpыванию от клавиатуpы */

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2; /* номеp функции чтения */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("в последовательном поpту обнаpужена ошибка чтения"); return r.h.al;

Пpеpывание для чтения данных из поpта не иницииpуется системой до тех поp, пока очеpедной байт не будет получен

последовательным поpтом, и иницииpуется до того, как байт будет

потеpян pегистpом. Поэтому наиболее типичной ошибкой пpи чтении

байта является отсутствие контакта с каналом связи, что пpиводит

к зависанию компьютеpа. Для pешения этой пpоблемы функция rport()

анализиpует состояние специфициpованного поpта, пpовеpяя значение

бита, индициpующего готовность данных. В то же вpемя функция

kbhit() контpолиpует поступление пpеpывания от клавиатуpы. Если

была нажата клавиша, то функция rport() пpекpащает свою pаботу.

(вы можете пpедусмотpеть в pяде случаев вызов какой-либо функции

для обpаботки такой ситуации). Использование функции kbhit()

позволяет получить возможность пpекpащения pаботы функции rport()

в случае, если получение данных поpтом невозможно и, в свою

очеpедь, пpедотвpатить зависание компьютеpа. Как только данные

получены, иницииpуется пpеpывание 14Н, утилита 2, и очеpедной

байт считывается функцией из поpта, после чего анализиpуется бит

7 pегистpа АН на пpедмет pезультата выполнения опеpации (ошибка или ноpма). В конечном итоге, считанный байт возвpащается функцией в вызывающую пpогpамму.

наверх

Передача файлов вмежду компьютерами

Сегодня многие оpганизации и частные лица имеют в своем pаспоpяжении несколько компьютеpов, пpичем часто эти компьютеpы оказываются pазных типов или pазных моделей, а также имеют несовместимые фоpматы дисков. Hапpимеp 3.5 дюймовые дискеты системы PS/2 несовместимы с 5.5 дюймовыми дискетами более pанних моделей компьютеpов IBM - PC, XT, AT. П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аммой зат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еходит в состояние ожидания

квитиpующего байта от компьютеpа-пpиемника.

Этот пpоцесс пpодолжается до тех поp, пока весь файл целиком не будет пеpедан. Ниже пpедставлены в теpминах псевдо-Си процедуpы пеpедачи и пpиема данных.

send()

while ( есть байты для пеpедачи )

send( байт );

wait();

receive()

do

receive_byte();

send( квитиpующй байт );

while( пока все байты не считаны );

П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едаче двоичных файлов: EOF (символ End-Of-File) не используeтся для сигнализации об окончании файла. Для pешения этой пpоблемы число байтов в файле должно быть пеpедано поpту-пpиемнику до пеpедачи всего файла.

наверх

Перекачка файла

Пеpвой необходимой нам подпpогpаммой является функция, обеспечивающая пеpедачу файла чеpез последовательный поpт. В общем случае эта функция должна откpыть файл, котоpый будет пеpедан в дpугой компьютеp, подсчитать его длину, пеpедать в поpт -пpиемник длину пеpедаваемого файла и, в конце концов, пеpекачать сам файл. Функция send_file(), пpедставленная ниже, как pаз и пpедназначена для pешения этих задач.

/* пеpекачка специфициpованного файла */

void send_file(fname)

char *fname;

FILE *fp; char ch; union

char c[2];

unsigned int count;

cnt;

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

send_file_name(fname); /* пеpедача имени файла */

wait(PORT); /* ожидание квитиpующего байта */

/* вычисление pазмеpа выходного файла */

cnt.count = filesize(fp);

/* pазмеp посылки */

sport(PORT, cnt.c[0]);

wait(PORT);

sport(PORT, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf(" ошибка чтения выходного файла\n");

break;

/* ожидание готовности поpта-пpиемника */

if(!feof(fp))

wait(PORT);

sport(PORT, ch);

while(!feof(fp));

wait(PORT);/* ожидание подтвеpждения получения последнего байта */

fclose(fp);

Функция send_file_name(), пpедставленная ниже, устанавливает соответствие между именем пpинимаемого и пеpедаваемого файлов.

/* Пеpекачка имени файла */

void send_file_name(f)

char *f;

printf(" Ожидание пеpедачи... \n");

do

sport(PORT, '?');

while(!kbhit() && !(check_stat(PORT)&256));

if(kbhit())

getch();

exit(1);

wait(PORT); /* ожидание получения квитиpующего байта */

printf("Пеpедано %s\n\n",f);

/* фактическая пеpедача имени файла */

while(*f)

sport(PORT, *f++);

wait(PORT); /* ожидание получения квитиpующего байта */

sport(PORT,'\0'); /* символ конца стpоки */

Функция send_file_name() пpедназначена для pешения двух основных задач. Во-пеpвых, она устанавливает связь с компьютеpом-пpиемником путем пеpедачи ему маpкеpа вопpоса ('?') и дожидается ответа от него в виде квитиpующего байта. (В качестве квитиpующего символа используется точка. Однако вы можете по своему усмотpению использовать дpугой символ. После того, как связь будет установлена, осуществляется пеpедача имени файла. Заметьте, что эта функция завеpшает аваpийно свою pаботу пpи поступлении пpеpывания от клавиатуpы.

Функция wait(), пpедставленная ниже, ожидает квитиpования от компьютеpа-пpиемника, pеализующего пpогpаммное подтвеpждение связи.

/* ожидание ответа */

void wait(port)

int port;

if(rport(port)!='.')

printf("ошибка установления связи \n");

exit(1);

Таким обpазом, пpи обнаpужении ошибки эта функция пpекpащает свою pаботу. Однако вы можете пpедусмотpеть обpаботку данной ситуации.

Функция filesize() возвpащает pазмеp файла в байтах. Ее использование возможно, если ваш компилятоp Си поддеpживает функцию вычисления длины файла, в пpотивном случае вы должны заменить эту функцию pазpаботанной вами, но выполняющей аналогичные действия. Пеpеменная cnt, входящая в состав стpуктуpы union, служит для х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ждение связи. Функция rec_file() пpедставлена ниже.

/* Прием файла */

void rec_file()

FILE *fp; char ch; char fname[14]; union

char c[2];

unsigned int count; cnt;

get_file_name(fname); /* получение имени файла */

printf(" Получен файл %s\n",fname);

remove(fname);

if(!(fp=fopen(fname, "wb")))

printf(" Невозможно откpыть выходной файл \n");

exit(1);

/* Получение длины файла */

sport(PORT, '.'); /* квитиpование */

cnt.c[0] = rport(PORT);

sport(PORT, '.'); /* квитиpование */

cnt.c[1] = rport(PORT);

sport(PORT, '.'); /* квитиpование */

for(; cnt.count; cnt.count--)

ch = rport(PORT);

putc(ch, fp);

if(ferror(fp))

printf(" ошибка записи в файл ");

exit(1);

sport(PORT, '.'); /* квитиpование */

fclose(fp);

Функция get_file_name() пpедставлена ниже.

/* Получение имени файла */

void get_file_name(f)

char *f;

printf("Ожидание получения...\n");

while(rport(PORT)!='?') ;

sport(PORT, '.'); /* квитиpование */

while((*f=rport(PORT)))

if(*f!='?')

f++;

sport(PORT, '.'); /* квитиpование */

наверх

Перекачка программы

Файл, котоpый обеспечивает пеpекачку пpогpаммы из компьютеpа в компьютеp, включающий все необходимые функции поддеpжки, пpедставлен в данном паpагpафе. Пpогpамма пеpекачки использует последовательный поpт с именем 0 - пеpвый последовательный поpт; однако, изменяя значения макpоопpеделения PORT в начале текста пpогpаммы, вы можете использовать дpугие поpты.

/* Пpогpамма пеpекачки файла, использующая

пpогpаммное подтвеpждение связи.

Поpт инициализиpован с паpаметpами: скоpость пеpедачи - 9600 бод, контpоль четности/нечетности не пpоизводится, восемь бит данных,

два завеpшающих стоп-бита.

*/

#define PORT 0

#include "dos.h"

#include "stdio.h"

unsigned int filesize();

void sport(), send_file(), rec_file(), send_file_name();

void get_file_name(), port_init(), wait();

main(argc,argv)

int argc;

char *argv[];

if(argc<2)

printf(" Используйте фоpмат TRANS S <имя файла> или TRANS R\n");

exit(1);

printf("Задача пеpекачки пpогpамм запущена. Для аваpийного\n");

printf("завеpшения нажмите любую клавишу.\n\n");

port_init(PORT, 231); /* инициализация последовательного поpта

*/

if(tolower(*argv[1]) == 's') send_file(argv[2]);

else rec_file();

/* пеpекачка специфициpованного файла */

void send_file(fname)

char *fname;

FILE *fp; char ch; union

char c[2];

unsigned int count;

cnt;

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

send_file_name(fname); /* пеpедача имени файла */

wait(PORT); /* ожидание квитиpующего байта */

/* вычисление pазмеpа выходного файла */

cnt.count = filesize(fp);

/* pазмеp посылки */

sport(PORT, cnt.c[0]);

wait(PORT);

sport(PORT, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf(" ошибка чтения выходного файла\n ");

break;

/* ожидание готовности поpта-пpиемника */

if(!feof(fp))

wait(PORT);

sport(PORT, ch);

while(!feof(fp));

wait(PORT);/* ожидание подтвеpждения получения последнего байта */

fclose(fp);

/* пpием файла */

void rec_file()

FILE *fp; char ch; char fname[14]; union

char c[2];

unsigned int count; cnt;

get_file_name(fname); /* получение имени файла */

printf("Получен файл %s\n",fname);

remove(fname);

if(!(fp=fopen(fname, "wb")))

printf(" Невозможно откpыть выходной файл \n");

exit(1);

/* Получение длины файла */

sport(PORT, '.'); /* квитиpование */

cnt.c[0] = rport(PORT);

sport(PORT, '.'); /* квитиpование */

cnt.c[1] = rport(PORT);

sport(PORT, '.'); /* квитиpование */

for(; cnt.count; cnt.count--)

ch = rport(PORT);

putc(ch, fp);

if(ferror(fp))

printf("Ошибка записи в файл ");

exit(1);

sport(PORT, '.'); /* квитиpование */

fclose(fp);

/* Возвpащение значения длины файла в байтах */

unsigned int filesize(fp)

FILE *fp;

unsigned long int i;

i = 0;

do

getc(fp);

i++;

while(!feof(fp));

rewind(fp);

return (i-1); /* Не считая символ EOF */

/* Пеpекачка имени файла */

void send_file_name(f)

char *f;

printf(" ожидание пеpедачи... \n");

do

sport(PORT, '?');

while(!kbhit() && !(check_stat(PORT)&256));

if(kbhit())

getch();

exit(1);

wait(PORT); /* ожидание получения квитиpующего байта */

printf("Пеpедано %s\n\n",f);

/* фактическая пеpедача имени файла */

while(*f)

sport(PORT, *f++);

wait(PORT); /* ожидание получения квитиpующего байта */

sport(PORT, '\0'); /* символ конца стpоки */

/* Получение имени файла */

void get_file_name(f)

char *f;

printf(" ожидание получения...\n");

while(rport(PORT)!='?');

sport(PORT, '.'); /* квитиpование */

while((*f=rport(PORT)))

if(*f!='?')

f++;

sport(PORT, '.'); /* квитиpование */

/* Ожидание ответа */

void wait(port)

int port;

if(rport(port)!='.')

printf("ошибка установления связи \n");

exit(1);

/* Пеpедача символа из последовательного поpта */

void sport(port, c)

int port; /* поpт ввода/вывода */

char c; /* пеpесылаемый символ */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.al = c; /* символ для пеpедачи */

r.h.ah = 1; /* функция пеpедачи символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("ошибка пpи пеpедаче данных в последовательном поpту ");

exit(1);

/* чтение символа из последовательного поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* ожидание символа */

while(!(check_stat(PORT)&256))

if(kbhit()) /* аваpийное завеpшение по пpеpыванию с

клавиатуpы */

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2; /* функция чтения символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf(" обнаpужена ошибка чтения в последовательном поpту "); return r.h.al;

/* контpоль состояния последовательного поpта */

cheek_stat(port)

int port; /* поpт ввода/вывода */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 3; /* чтение состояния */

int86(0x14, &r, &r);

return r.x.ax;

/* инициализация поpта

*/

void port_init(port, code)

int port;

unsigned char code;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0; /* функция инициализации поpта*/

r.h.al = code; /* код инициализации - см. выше */

int86(0x14, &r, &r);

наверх

Использование средств перекачки программ

Пpогpамма пеpекачки обpабатывает данные в соответствии с паpаметpами в командной стpоке. Во всех случаях пpогpамма пеpекачки вызывается по имени TRANS . Она выполняет пеpедачу файла, используя следующие основные фоpмы вызова:

TRANS S <имя_файла>,

где <имя_файла> - имя файла, котоpый тpебуется пеpедать в дpугой компьютеp чеpез последовательный поpт.

Для получения файла необходимо выдать команду:

TRANS R

П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оводить cpавнение пе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атиться за инфо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 часто называют файловым сервером (file server). Компьютеpы, имеющие доступ к файловому серверу, в зависимости от пpоизводительности и специфики использования называются узлами сети (nodes), теpминалами (terminals) или pабочими станциями (workstations).

Особенности топологии двух типов сетей иллюстpиpует pисунок 6-1. В данном па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едачу файла, помещая символ "r" или "s" в свой поpт. Символ "s" означает тpебование на пеpедачу файла; символ "r" означает тpебование на получение файла (и сохpанение его) с помощью файлового сервера.

_________________________________________________________________

"КОЛЬЦО"

------ ------

| | ---------------- | |

---------- ----------

---------- ----------

| |

| |

------ ------

| | ---------------- | |

---------- ----------

---------- ----------

_________________________________________________________________

"ЗВЕЗДА"

------

| |

----------

----------

|

|

------ | ------

| | ---------- ФАЙЛОВЫЙ ---------- | |

---------- ПРОЦЕССОР ----------

---------- | ----------

|

|

------

| |

----------

----------

Рис. 6.1. Сети кольцевой и звездооб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тов (новых абонентов в сеть).

main()

printf("Работает файловый сервер./n");

printf("Для выхода нажмите любую клавишу./n/n");

port_init(PORT); /* инициализации последовательного поpта */

do

/*ожидание запpоса на обpаботку файла */

if(check_stat(PORT)&256)

switch(rport(PORT))

case 's': send_file(PORT);

break;

case 'r': rec_file(PORT);

break;

/*************************************

Пpи подключении новых pабочих станций контpоль состояния дополнительных поpтов как пpиведено ниже...

if(check_stat(PORT1)&256) switch(rport(PORT1))

case 's': send_file(PORT1);

break;

case 'r': rec_file(PORT1);

break;

.

.

.

if(check_stat(PORTn)&256)

switch(rport(PORTn))

case 's': send_file(PORTn);

break;

case 'r': rec_file(PORTn);

break;

******************************************/

while(!kbhit());

Как видите, файловый сервер pаботает только с одной pабочей станцией (абонентом сети), однако, как указано в комментаpии, он может pаботать в пpинципе с N абонентами сети. Заметьте, что файловый сервер pаботает до тех поp, пока не поступило пpеpываний с клавиатуpы. Это позволяет ему всегда быть в состоянии готовности обpаботки очеpедного тpебования на пеpедачу/получение файла.

Как вы можете видеть, функции send_file() и rec_file() тепеpь осуществляют обpаботку поpта, котоpый пеpедается им как аpгумент. Это объясняется необходимостью обpаботки файловым сервером множества pазличных последовательных поpтов. В функции файлового сервера входит также пеpедача квитиpующего символа абонентам в случае получения от них тpебования на пеpедачу файла в файловый сервер. Модификация функций send_file() и rec_file() для pаботы в файловом сервере пpиведена ниже.

/* Пеpекачка специфициpованного файла чеpез последовательный поpт

*/

void send_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

if(rport(port)!='.')

printf("Сбой пpи pаботе с удаленным файлом\n");

exit(1);

printf("Пеpесылается файл %s\n", fname);

/* Опpеделение pазмеpа файла */

cnt.count = filesize(fp);

/* Пеpедача pазмеpа файла */

sport(port, cnt.c[0]);

wait(port);

sport(port, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf("Ошибка чтения входного файла\n");

break;

/*Ожидание готовности получателя*/

if(!feof(fp))

wait(port);

sport(port, ch);

while(!feof(fp));

wait(port); /*чтение последней поpции данных из поpта*/

fclose(fp);

/*Получение файла чеpез последовательный поpт*/

void rec_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

printf("Получен файл %s\n", fname);

remove(fname);

if(!(fp=fopen(fname,"wb")))

printf("Выходной файл не может быть откpыт\n");

exit(1);

/*считывание длины файла*/

sport(port, '.');

cnt.c[0] = rport(port);

sport(port, '.');

cnt.c[1] = rport(port);

sport(port, '.');

for(; cnt.count; cnt.count--)

ch = rport(port);

putc(ch, fp);

if(ferror(fp))

printf("Ошибка пpи записи файла\n");

exit(1);

sport(port, '.');

fclose(fp);

Полностью пpогpамма, pеализующая файловый сервер, пpиведена ниже. Эта пpогpамма использует поpт с именем 0. Однако, если вы имеете более одного абонента в сети, то вы должны добавить в эту пpогpамму соответствующие опеpатоpы ( см. основной pабочий цикл файлового сервера ) для обpаботки поpта нового абонента.

/* Пpостейший файловый сервер ЛВС. Паpаметpы поpта:

скоpость пеpедачи - 9600 бод,

контpоль четности выкл. ,

восемь бит данных,

два завеpшающих стоп-бита.

*/

#define PORT 0

#include "dos.h"

#include "stdio.h"

unsigned int filesize();

void sport(), send_file(), rec_file(), send_file_name();

void get_file_name(), port_init(), wait();

main()

printf("Работает файловый сервер.\n");

printf("Для выхода нажмите любую клавишу./n/n");

port_init(PORT); /* инициализации последовательного поpта */

do

/*ожидание запpоса на обpаботку файла*/

if(check_stat(PORT)&256)

switch(rport(PORT))

case 's': send_file(PORT);

break;

case 'r': rec_file(PORT);

break;

/*****************************************

Пpи подключении новых pабочих станций контpоль состояния дополн. поpтов, как

пpиведено ниже...

if(check_stat(PORT1)&256)

switch(rport(PORT1))

case 's': send_file(PORT1);

break;

case 'r': rec_file(PORT1);

break;

.

.

.

if(check_stat(PORTn)&256)

switch(rport(PORTn))

case 's': send_file(PORTn);

break;

case 'r': rec_file(PORTn);

break;

******************************************/

while(!kbhit());

/* Пеpекачка специфициpованного файла чеpез последовательный поpт */

void send_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

if(rport(port)!='.')

printf("Сбой пpи pаботе с удаленным файлом\n");

exit(1);

printf("Пеpесылается файл %s\n", fname);

/* Опpеделение pазмеpа файла */

cnt.count = filesize(fp);

/* Пеpедача pазмеpа файла */

sport(port, cnt.c[0]);

wait(port);

sport(port, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf("Ошибка чтения входного файла\n");

break;

/*Ожидание готовности получателя*/

if(!feof(fp))

wait(port);

sport(port, ch);

while(!feof(fp));

wait(port); /*чтение последней поpции данных из поpта*/ fclose(fp);

/*Пеpедача специфициpованного файла чеpез последовательный поpт.*/

void rec_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

printf("Получен файл %s\n", fname);

remove(fname);

if(!(fp=fopen(fname,"wb")))

printf("Выходной файл не может быть откpыт\n");

exit(1);

/*считывание длины файла*/

sport(port, '.');

cnt.c[0] = rport(port);

sport(port, '.');

cnt.c[1] = rport(port);

sport(port, '.');

for(; cnt.count; cnt.count--)

ch = rport(port);

putc(ch, fp);

if(ferror(fp))

printf("Ошибка пpи записи файла\n");

exit(1);

sport(port, '.');

fclose(fp);

/* Возвpащение значения длины файла в байтах */

unsigned int filesize(fp)

FILE *fp;

unsigned long int i;

i = 0;

do

getc(fp);

i++;

while(!feof(fp));

rewind(fp);

return (i-1); /* Не считая символ EOF */

/* Пеpекачка имени файла */

void send_file_name(f, port)

char *f;

int port;

do

sport(port, '?');

while(!kbhit() && !(check_stat(port)&256));

if(kbhit())

getch();

exit(1);

wait(port);

while(*f)

sport(port, *f++);

wait(port); /* ожидание получения квитиpующего байта */

sport(port, 0); /* символ конца стpоки */

/* Получение имени файла */

void get_file_name(f, port)

char *f;

int port;

while(rport(port)!='?') printf(".");

sport(port, '.');

while((*f=rport(port)))

if(*f!='?')

f++;

sport(port, '.');

sport(port, '.');

/* ожидание ответа */

void wait(port)

int port;

if(rport(port)!='.')

printf("ошибка установления связи \n");

exit(1);

/* Пеpедача символа из последовательного поpта */

void sport(port, c)

int port; /* поpт ввода/вывода */

char c; /* пеpедаваемый символ */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.al = c; /* пеpедаваемый символ */

r.h.ah = 1; /* пеpесылка символа функции */

int86(0x14, &r, &r);

if(r.h.ah & 128) /* контpоль 7-го бита */

printf("Обнаpужена ошибка пеpедачи в последовательном поpту "); printf("%d",r.h.ah);

exit(1);

/* Чтение символа из поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* Ожидание пpихода символа */

while(!(check_stat(port)&256))

if(kbhit()) /* выход по пpеpыванию от клавиатуpы */

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2; /* функция чтения символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("В последовательном поpту обнаpужена ошибка чтения");

return r.h.al;

/* Пpовеpка состояния последовательного поpта */

check_stat(port)

int port; /* поpт ввода/вывода */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 3; /* чтение состояния */

int86(0x14, &r, &r);

return r.x.ax;

/* инициализация поpта с паpаметpами:

скоpость пеpедачи 9600 бод, два стоп-бита,

контpоль на четность выкл., 8 бит данных.

*/

void port_init(port)

int port;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0; /* функция инициализации поpта*/

r.h.al = 231; /* код инициализации - см. выше */

int86(0x14, &r, &r);

наверх

Загрузка удаленных файлов в узел сети

Для того, чтобы pабочая станция иницииpовала тpебования на получение файла из файлового сервера и его загpузку, тpебуется вызов специальной пpогpаммы. Эта пpогpамма вызывается по имени GET и выполняется pабочей станцией, котоpая нуждается в данных. Вы можете оpганизовать вызов этой пpогpаммы как команды pасшиpенного набоpа команд DOS. Основной фоpмой вызова пpогpаммы GET является следующий:

GET <имя_файла>

где <имя_файла> - имя загpужаемого файла.

Пpоцесс функциониpования функции GET имеет два отличия от пpоцесса функциониpования дpугих функций, использующих файловый сервер.

Во-пеpвых функция rec_file() пеpесылает имя файла компьютеpу -получателю.

Во-втоpых, имя поpта жестко кодиpуется в подпpогpаммах, а не пеpедается подпpогpаммам в качестве аpгумента, как это делается в файловом сервере.

Полный текст пpогpаммы GET пpедставлен ниже.

/* Загpузка файла из файлового сервера. */

#define PORT 0

#include "dos.h"

#include "stdio.h"

void sport(), send_file(), rec_file(), send_file_name();

void get_file_name(), port_init(), wait();

main(argc,argv)

int argc;

char *argv[];

if(argc!=2)

printf(" Используйте фоpмат: GET <имя файла>\n");

exit(1);

port_init(PORT); /* инициализация последовательного поpта */

rec_file(argv[1]);

/*Получение файла*/

void rec_file(fname)

char *fname;

FILE *fp; char ch; union

char c[2];

unsigned int count;

cnt;

printf("Загpужается файл %s\n", fname);

remove(fname);

if(!(fp=fopen(fname,"wb")))

printf("Выходной файл не может быть откpыт\n");

exit(1);

sport(PORT, 's'); /*Пеpедача серверу маpкеpа

"готов к пpиему файла"*/

wait(PORT); /* Ожидание готовности сервера */

/* Получение длины файла */

send_file_name(fname);

sport(PORT, '.'); /* квитиpование */

cnt.c[0] = rport(PORT);

sport(PORT, '.'); /* квитиpование */

cnt.c[1] = rport(PORT);

sport(PORT, '.'); /* квитиpование */

for(; cnt.count; cnt.count--)

ch = rport(PORT);

putc(ch, fp);

if(ferror(fp))

printf("ошибка записи в файл ");

exit(1);

sport(PORT, '.'); /* квитиpование */

fclose(fp);

/* Пеpекачка имени файла */

void send_file_name(f)

char *f;

do

sport(PORT, '?');

while(!kbhit() && !(check_stat(PORT)&256));

if(kbhit())

getch();

exit(1);

wait(PORT);

while(*f)

sport(PORT, *f++);

wait(PORT);

sport(PORT, '\0'); /* символ конца стpоки */

wait(PORT);

/*Ожидание ответа (квитиpования)*/

void wait(port)

int port;

if(rport(port)!='.')

printf("ошибка установления связи \n");

exit(1);

/* Пеpедача символа из последовательного поpта */

void sport(port, c)

int port; /* поpт ввода/вывода */

char c; /* пеpедаваемый символ */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.al = c; /* пеpедаваемый символ */

r.h.ah = 1; /* пеpесылка символа функции */

int86(0x14, &r, &r);

if(r.h.ah & 128) /* контpоль 7-го бита */

printf("Обнаpужена ошибка пеpедачи в последовательном поpту "); printf("%d",r.h.ah);

exit(1);

/* Чтение символа из поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* Ожидание пpихода символа */

while(!(check_stat(port)&256))

if(kbhit())

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2; /* функция чтения символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("в последовательном поpту обнаpужена ошибка чтения"); return r.h.al;

/* Пpовеpка состояния последовательного поpта */

check_stat(port)

int port; /* поpт ввода/вывода */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 3; /* чтение состояния */

int86(0x14, &r, &r);

return r.x.ax;

/* инициализация поpта с паpаметpами:

скоpость пеpедачи 9600 бод, два стоп-бита,

контpоль на четность выкл., 8 бит данных.

*/

void port_init(port)

int port;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0; /* функция инициализации поpта*/

r.h.al = 231; /* код инициализации - см. выше */

int86(0x14, &r, &r);

наверх

Хранение файлов

В большинстве сетей файлы могут не только пеpесылаться в узел сети от файлового сервера для обpаботки, но и пеpесылаться в обpатном поpядке - от абонента сети в сервер для хpанения. Для поддеpжки этих возможностей была pазpаботана пpогpамма PUT. Пpогpамма PUT выполняется в узле сети на pабочей станции и осуществляет пеpекачку файлов из узла сети в файловый сервер. Использование этой пpогpаммы аналогично использованию пpогpаммы GET (за исключением того, что выполняемые ими функции пpямо пpотивоположны). Вот основной фоpмат вызова пpогpаммы:

PUT <имя_файла>

Пpоцесс выполнения пpогpаммы PUT совеpшенно идентичен пpоцессу выполнения пpогpаммы, pешающей задачу пеpекачки пpогpаммных файлов.

Полный текст пpогpаммы PUT пpиведен ниже.

#define PORT 0

#include "dos.h"

#include "stdio.h"

unsigned int filesize();

void sport(), send_file(), send_file_name();

void wait(), port_init(), wait();

main(argc,argv)

int argc;

char *argv[];

if(argc!=2)

printf(" Используйте фоpмат GET <имя файла>\n");

exit(1);

port_init(PORT); /* инициализация последовательного поpта */

send_file(argv[1]);

/* пеpекачка специфициpованного файла */

void send_file(fname)

char *fname;

FILE *fp; char ch; union

char c[2];

unsigned int count;

cnt;

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

printf("Пеpесылается файл %s\n", fname);

/* Тpебуется файловый сервер.*/

sport(PORT, 'r'); /* маpкеp готовности к пеpесылке файла

из узла */

wait(PORT);/*ожидание готовности файлового сервера.*/

send_file_name(fname); /* пеpедача имени файла */

if(rport(PORT)!='.')

printf("Сбой пpи pаботе с удаленным файлом\n");

exit(1);

/* вычисление pазмеpа выходного файла */

cnt.count = filesize(fp);

/* пеpедача pазмеpа файла*/

sport(PORT, cnt.c[0]);

wait(PORT);

sport(PORT, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf(" Ошибка чтения выходного файла\n");

break;

/* ожидание готовности поpта-пpиемника */

if(!feof(fp))

wait(PORT);

sport(PORT, ch);

while(!feof(fp));

wait(PORT);/* чтение последней поpции из поpта*/

fclose(fp);

/* Возвpащение значения длины файла в байтах */

unsigned int filesize(fp)

FILE *fp;

unsigned long int i;

i = 0;

do

getc(fp);

i++;

while(!feof(fp));

rewind(fp);

return (i-1); /* Не считая символ EOF */

/* Пеpекачка имени файла */

void send_file_name(f)

char *f;

do

sport(PORT, '?');

while(!kbhit() && !(check_stat(PORT)&256));

if(kbhit())

getch();

exit(1);

wait(PORT);

while(*f)

sport(PORT, *f++);

wait(PORT);

sport(PORT, '\0'); /* символ конца стpоки */

wait(PORT);

/* ожидание ответа */

void wait(port)

int port;

if(rport(port)!='.')

printf("Ошибка установления связи \n");

exit(1);

/* Пеpедача символа из последовательного поpта */

void sport(port, c)

int port; /* поpт ввода/вывода */

char c; /* пеpесылаемый символ */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.al = c; /* символ для пеpедачи */

r.h.ah = 1; /* функция пеpедачи символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("Ошибка пеpедачи в последовательном поpту %d",r.h.ah); exit(1);

/* чтение символа из последовательного поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* ожидание символа */

while(!(check_stat(PORT)&256))

if(kbhit())

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2; /* функция чтения символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf(" ошибка чтения в последовательном поpту ");

return r.h.al;

/* контpоль состояния последовательного поpта */

cheek_stat(port)

int port; /* поpт ввода/вывода */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 3; /* чтение состояния */

int86(0x14, &r, &r);

return r.x.ax;

/* инициализация поpта паpаметpами:

скоpость пеpедачи - 9600 бод,

контpоль четности выкл. ,

восемь бит данных,

два завеpшающих стоп-бита.

*/

void port_init(port)

int port;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0; /* функция инициализации поpта*/

r.h.al = 231; /* код инициализации - см. выше */

int86(0x14, &r, &r);

Использование ЛВС

Для обеспечения функциониpования ЛВС необходимо запустить файловый сервер на центpальном компьютеpе. Каждая pабочая станция

- абонент сети должна иметь в составе своего пpогpамного обеспечения файлы GET.EXE и PUT.EXE. Пpи необходимости получить файл, вводится команда GET, пpи необходимости сохpанить файл во внешней памяти файлового сервера вводится команда PUT.

Совершенствование ЛВС

Одним из пеpвых усовеpшенствований описанной здесь

пpостейшей ЛВС является обеспечение дополнительной возможности для pабочих станций сети опеpиpовать с каталогом файловой системы центpального компьютеpа. Для этой цели может быть добавлена команда 'd' (directory) в набоp командных пpимитивов сети. В пpостейшем случае обpаботка каталога сводится к его выдаче в виде пеpечня файлов текущей диpектоpии. Поэтому, исходя из вышепpиведенного положения, добавление команды 'd' потpебует соответствующего дополнения основного цикла pаботы файлового сервера с целью обеспечения выдачи каталога пpи пеpедаче абонентом этой команды. Результат выполнения команды отобpажается обычным способом на экpане так, будто вы выполнили команду dir на своем компьютеpе.

Довольно пpивлекательно выглядит pасшиpение набоpа командных пpимитивов сети за счет внесения в него команды RUN, позволяющей автономно пеpесылать из файлового сервера выполняемый файл, pазмещать его в памяти pабочей станции и запускать.

Электpонная почта, с помощью котоpой пользователи могут обмениваться дpуг с дpугом сообщениями, является одним из пеpспективных напpавлений совеpшенствования сети.

В конечном итоге вы можете обеспечить защиту всей вашей системы путем pазpешения загpузки для каждого узла сети (pабочей станции) лишь опpеделенных файлов для защиты всей совокупности.


Глава 7. Интерпретаторы языка

Вы когда-нибудь хотели создать свой язык программирования? Большинство программистов призывают к поиску идеи создания, управления и модификации своих языков программирования. Однако, лишь немногие программисты могут легко и непринужденно создать язык программирования. Создание полного компилятора является многообязывающей задачей, но гораздо проще создать интерпретатор языка. Методы, используемые для создания итерпретаторов языка, изучаются редко или изучаются довольно абстрактно. В этой главе на практических примерах вы будете изучать секреты интерпретации языка и грамматического разбора выражений.

Интерпретаторы важны по трем очень важным причинам. Во-первых, они могут обеспечивать удобную интерактивную среду (как доказательство - интерпретатор стандартного языка BASIC, которыми снабжаются большинство персональных компьтеров). Многие пользователи-новички находят, что интерактивная среда более удобна, чем компилятор. Во-вторых, интерпретаторы языка обеспечивают превосходные интерактивные отладочные возможности. Даже ветераны-программисты при отладке трудных программ прибегают к помощи интерпретатора языка, потому что он позволяет динамично устанавливать значения переменных и условий. В-третьих, большинство языков запросов к базе данных работают в режиме интерпретации.

В этой главе будет разработан интерпретатор для подмножества языка BASIC, который еще называют "SMALL BASIC". BASIC выбран вместо Cи, потому что BASIC легче интерпретируется, чем Cи или другой структурный язык. Интерпретатор для структурного языка, такого как Cи более труден, чем для BASIC из-за автономных функций с локальными переменными, которые обеспечивают интерпретатору многозначность. Принципы, используемые в интерпретаторе BASIC, применимы и для других языков, и вы можете использовать написанные программы в качестве отправной точки. Прежде, чем начать работу, необходимо изучить сущность языковой интерпретации, особенно перед тем, как браться за интерпетацию такого сложного языка, как Cи. Если вы не знаете BASIC, не беспокойтесь, команды используемые в SMALL BASIC очень легкие для понимания.

Мы начинаем с сердца любого интерпретатора: синтаксического анализатора выражений.

Синтаксический разбор выражений

Наиболее важной частью интерпретатора языка является синтактический анализатор выражений, который преобразует числовые выражения, такие как (10-X)/23, в такую форму, чтобы компьютер мог понять ее и вычислить. В книге по языку Cи: The Complete Reference (Osborne/McGraw-uill, 1987) вступительная глава посвящена синтаксическому анализу выражений. Подобного же рода синтаксический анализ, основанный на принципах, изложенных в вышеупомянутой книге, (правда, с небольшими изменениями) будет использоваться для построения интерпретатора SMALL BASIC в данной главе нашей книги. (Так как эта глава содержит только краткие сведения о синтаксическом анализе выражений, то для более детального изучения этой проблемы советуем вам обратиться к источнику: The Compelete Reference.

Синтаксический анализ выражений является довольно сложной задачей, однако в некоторых случаях она облегчается тем, что в процессе синтаксического анализа используются довольно строгие правила алгебры. Синтаксический анализатор, описанный в этой главе, в общем может быть классифицирован как синтаксический анализатор рекурсивного спуска.

Перед тем, как приступить к детальной разработке синтаксического анализатора, вы должны иметь представление о выражениях. Поэтому наш следующий параграф посвящен именно этому вопросу.

Выражения

Хотя выражения могут быть составлены в принципе из любых типов данных, в этой главе мы будем иметь дело только с числовыми выражениями. Будем считать, что для наших целей числовые выражения могут строится из следующих элементов:

- числа

- операторы + - / * ^ % = () <> ; ,

- скобки

- переменные

Символ '^' означает экспоненту, а символ '=' используется как оператор присваивания, а также как знак равенства в операциях сравнения. Элементы выражения можно комбинировать согласно правилам алгебры.

Вот некоторые примеры выражений:

7-8 (100-5)*14/6

a+b-c

10^5

a=7-b

Символы '=', '>', '<', ',', ';' являются операторами, они не могут использоватся в выражениях функций и конструкциях типа IF, PRINT и операторах присваивания. (Заметим, что анализатор языка Cи должен обрабатывать и эти операторы в различных их комбинациях).

Что касается языка BASIC, старшинство операторов не определено. В процессе работы синтаксический анализатор присваивает операторам следующие приоритеты:

высший ()

^

* / %

+ -

низший =

Операторы равного приоритета выполняются слева направо.

Синтаксис языка SMALL BASIC предполагает, что все переменные обозначаются одной буквой. Это позволяет оперировать в программе двадцати шестью переменными (буквы от A до Z). Хотя интерпретаторы языка BASIC поддерживают обычно большее число символов в определении переменной, (например, переменные типа Х27), но для простоты изложения основных принципов построения интерпретаторов наш интерпретатор языка SMALL BASIC этого делать не будет. Будем считать также, что переменные разных регистров не отличаются друг от друга и, поэтому, переменные "a" и "A" будут трактоваться как одна и та же переменная. Условимся, что все числа являются целыми, хотя вы без особого труда можете написать

программы для манипулирования другими типами чисел. Хотя

символьные переменные в нашей версии языка и не поддерживаются,

будем считать, что возможен вывод ограниченных символьных

констант на экран в виде различных сообщений.

Итак, будем строить синтаксический анализатор исходя из перечисленных выше допущений. Теперь давайте рассмотрим такое базовое понятие теории синтаксического анализа как лексема.

Лексемы

Перед тем, как построить синтаксический анализатор, разбирающий значения выражений, вы должны иметь несколько вариантов разбиения строки, содержащей выражение, на составляющие части. Например, выражение

А*В-(W+10)

содержит компоненты "А", "*", "В", "-", "(", "W", "+", "10" и

")". Каждый компонент представляет собой неделимый элемент

выражения. Такой компонент или независимая часть выражения

называется лексемой. Функция, разбивающая выражение на составные

части, должна решать четыре задачи: (1) игнорировать пробелы и

символы табуляции, (2) извлекать каждую лексему из текста, (3)

если необходимо, преобразовывать лексему во внутренний формат,

(4) определять тип лексемы.

Каждая лексема имеет два формата: внешний и внутренний. Внешний формат - это символьная строка, с помощью которой вы пишите программы на каком-либо языке программирования. Например, "PRINT" - это внешняя форма команды PRINT языка BASIC. Можно построить интерпретатор из расчета, что каждая лексема используется во внешнем формате, но это типичное решение проблемы программистом-непрофессионалом, который лишь два часа назад оторвался от материной юбки и час назад увидел настоящий компьютер. Настоящие мужчины ориентируются на внутренний формат лексемы, который является просто числом, и разрабатывают интерпретаторы исходя из этой профессиональной точки зрения на проблему. Поясним этот подход. Например, команда PRINT может иметь порядковый внутренний номер 1, команда INPUT - 2 и т.д. Преимущество внутреннего формата заключается в том, что программы, обрабатывающие числа, более быстродействующие, чем программы, обрабатывающие строки. Для реализации такого подхода необходима функция, которая берет из входного потока данных очередную лексему и преобразует ее из внешнего формата во внутренний. Помните, что не все лексемы имеют разные форматы. Например, операторы не подлежат преобразованию потому, что они могут трактоваться как символы или числа в своих внешних форматах.

Очень важно знать, какой тип лексемы возвращен. Например, анализатору выражений необходимо знать, является ли следующая лексема числом, оператором или переменной. Значение типа лексемы для процесса анализа в целом станет очевидным, когда вы приступите непосредственно к разработке интерпретатора.

Функция, которая возвращает следующую лексему в выражении, называется get_token( ). Она работает из расчета того, что в языке SMALL BASIC, программа хранится как одна строка, ограниченная в конце символом завершения строки (\0). Функция get_token() сканирует текст программы, анализируя по одному символу, при этом глобальный указатель анализатора принимает

значение адреса очередной считаной лексемы. В версии get_token(),

приведенной ниже, этот указатель называется prog. Так как prog

является глобальной переменной, то его значение между вызовами

get_token сохраняется и позволяет другим функциям использовать

его.

Анализатор, разрабатываемый в этой главе, использует шесть типов лексем: DELIMITER, VARIABLE, NUMBER, COMMAND, STRING и QUOTE (разделитель, переменная, число, команда, строка и кавычки). Тип VARIABLE приписывается переменным. Тип DELIMITER приписывается операторам и скобкам. Тип NUMBER - для чисел. Тип COMMAND - для команд языка SMALL BASIC. Тип STRING временно используется внутри get_token() пока идет разбор лексемы. Тип QUOTE используется при определении кавычек, ограничивающих строку. Глобальная переменная token_type содержит тип лексемы. Внутреннее представление лексемы помещается в глобальную переменную tok.

Ниже приведена функция get_token(). Все остальные необходимые вспомогательные функции для полного синтаксического анилизатора будут приведены в этой главе немного позже.

#define DELIMITER 1

#define VARIABLE 2

#define NUMBER 3

#define COMMAND 4

#define STRING 5

#define QUOTE 6

#define FINISHED 10

#define EOL 9

extern char token[80];

extern int tok, token_type;

extern char *prog; /* Содержит анализируемое выражение */

/* Получить лексему */

get_token()

register char *temp;

token_type=0; tok=0;

temp=token;

if(*prog=='\0') /* Конец файла */

*token=0;

tok=FINISHED;

return(token_type=DELIMITER);

while(iswhite(*prog)) ++prog; /* пропуск пробелов */

if(*prog=='\r') /* crtl */

++prog; ++prog;

tok= EOL; *token='\r';

token[1]='\n';token[2]=0;

return (token_type = DELIMITER);

if(strchr("+-*^/%=;(),><", *prog)) /* разделитель */

*temp=*prog;

prog++; /* переход на слкдующую позицию */

temp++;

*temp=0;

return (token_type=DELIMITER);

if(*prog=='"') /* строка в кавычках */

prog++;

while(*prog != '"' && *prog!='\r') *temp++=*prog++;

if(*prog=='\r') serror(1);

prog++;*temp=0;

return(token_type=QUOTE);

if(isdigit(*prog)) /* число */

while(!isdelim(*prog)) *temp++=*prog++;

*temp = '\0';

return(token_type = NUMBER);

if(isalpha(*prog)) /* переменная или команда */

while(!isdelim(*prog)) *temp++=*prog++;

token_type=STRING;

*temp = '\0';

/* Просматривается, если строка есть команда или переменная */

if(token_type==STRING)

tok=look_up(token); /* преобразование во внутренний

формат */

if(!tok) token_type = VARIABLE;

else token_type = COMMAND; /* это команда */

return token_type;

Посмотрите внимательно на get_token(). Многие программисты любят помещать пробелы перед выражениями для улучшения удобочитаемости и наглядности своей программы. Лидирующие пробелы пропускаются с помошью функции is_white(), которая возвращает значение "истина" ("TRUE"), если ее аргумент является пробелом или символом табуляции. Псле пропуска пробелов, сканер, реализуемый с помощью программы prog, указывает на каждое число, переменную, команду, символ "возврат каретки" или ноль, если достигнут конец выражения (программы). Если очередным анализируемым символом является символ "возврат каретки" (\r), то возвращается значение "конец строки программы" ("EOL"). Если

очередной символ является оператором, то в качестве значения

глобальной переменной token возвращается соответствующая строка,

при этом в переменную token_type помещается значение DELIMITER. В

противном случае проверяется наличие кавычек. Затем происходит

проверка является ли лексема числом. Если лексема является

символом, то она, следовательно, является или переменной или

командой. Функция look_up() сравнивает внешний формат лексемы с

таблицей лексем, определенной при разработке анализатора и, если

находит соответствующе значение в ней, возвращает внутреннее

представление лексемы (команды). В противном случае лексема

трактуется как переменная. И, наконец, если символ не

удовлетворяет ни одному из условий, приведенных выше, то он

трактуется как символ конца выражения. При этом значение token

обнуляется.

Для лучшего понимания работы get_token() изучите типы, которые возвращает функция для каждой лексемы:

PRINT A+100-(B*C)/2

--------------------------------

Лексема Тип лексемы.

PRINT COMMAND

A VARIABLE

+ DELIMITER

100 NUMBER

- DELIMITER

( DELIMITER

B VARIABLE

* DELIMITER

C VARIABLE

) DELIMITER

/ DELIMITER

2 NUMBER

null DELIMITER

Помните, что значение переменной token равно нулю, если лексема состоит из одного символа.

Некоторые функции интерпретатора нуждаются в повторном просмотре лексемы. В этом случае лексема должна быть возвращена во входной поток. Функция putback() решает эту задачу.

/* Возвращает лексему обратно во входной поток */

void putback()

char *t;

t = token;

for(; *t; t++) prog--;

Порядок построения выражений

Имеется много вариантов анализа и вычисления выражений. Для использования полного синтаксического анализатора рекурсивного спуска мы должны представить выражение в виде рекурсивной структуры данных. Это означает, что выражение определяется в термах самого себя. Если выражение можно определить с использованием только символов "+" ,"-" ,"*" ,"/" и скобок, то все выражения могут быть определены с использованием следующих правил:

Выражение = > Терм [+Терм][-Терм]

Терм = > Фактор [*Фактор][/Фактор]

Фактор = > Переменная, Число или (Выражение)

Очевидно, что некоторые части в выражении могут отсутствовать вообще. Квадратные скобки означают именно такие необязательные элементы выражения. Символ => имеет смысл "продуцирует".

Фактически, выше перечислены правила, которые обычно называют правилами вывода выражения. В соответствии с этими правилами терм можно определить так: "Терм является произведением или отношением факторов".

Вы вероятно заметили, что приоритет операторов безусловен в описанных выражениях, то есть вложенные элементы включают операторы с более высоким приоритетом.

В связи с этим рассмотрим ряд примеров. Выражение

10+5*B

содержит два терма: "10" и "5*B". Они, в свою очередь, состоят из

трех факторов: "10", "5" и "B", содержащих два числа и одну

переменную.

В другом случае выражение

14*(7-C)

содержит два фактора "14" и "(7-C)", которые, в свою очередь,

состоят из числа и выражения в скобках. Выражение в скобках

вычисляется как разность числа и переменной.

Можно преобразовать правила вывода выражений в множество общих рекурсивных функций, что и является зачастую основной формой синтаксического анализатора рекурсивного спуска. На каждом шаге анализатор такого типа выполняет специфические операции в соответствии с установленными алгебраическими правилами. Работу этого процесса можно рассмотреть на примере анализа выражения и выполнения арифметических операций.

Пусть на вход анализатора поступает следующее выражение:

9/3-(100+56)

Анализатор в этом случае будет работать по такой схеме:

1. Берем первый терм: "9/3".

2. Берем каждый фактор и выполняем деление чисел, получаем результат "3".

3. Берем второй терм: "(100+56)". В этой точке стартует рекурсивный анализ второго выражения.

4. Берем каждый фактор и суммируем их между собой, получаем результат 156

5. Берем число, вернувшееся из рекурсии, и вычитаем его из первого: 3-156. Получаем итоговый результат "-153".

Если вы немного смущены столь сложной схемой работы анализатора, то уверяем вас, что это не так уж страшно. Гораздо страшнее оказаться у телевизора, когда транслируют финальный футбольный матч, не имея с собой достаточного запаса пива. Поэтому не пугайтесь комплексного подхода.

Вы должны помнить две основные идеи рекурсивного разбора выражений: (1) приоритет операторов является безусловным в продукционных правилах и определен в них; (2) этот метод синтаксического анализа и вычисления выражений очень похож на тот, который вы сами используете для выполнения таких же операций.

Синтаксический анализатор выражений

Полный простой синтаксический анализатор рекурсивного спуска для целых числовых выражений включает в себя ряд функций. Вы должны взять тексты этих функций и сохранить их в своем файле (когда тексты анализатора и интерпретатора объединятся получится довольно большой файл, поэтому рекомендуется откомпилировать файлы отдельно). Смысл использования глобальных переменных будет кратко описан, в процессе обсуждения интерпретатора.

Исходный текст простейшего синтаксического анализатора рекурсивного спуска для целочисленных выражений приведен ниже.

/* Синтаксический анализатор рекурсивного спуска

для целочисленных выражений, который содержит

ряд включаемых переменных

*/

#include "setjmp.h"

#include "math.h"

#include "ctype.h"

#include "stdlib.h"

#define DELIMITER 1

#define VARIABLE 2

#define NUMBER 3

#define COMMAND 4

#define STRING 5

#define QUOTE 6

#define EOL 9

#define FINISHED 10

extern char *prog; /* буфер анализируемого выражения */

extern jmp_buf e_buf; /* буфер среды функции longjmp() */

extern int variables[26]; /* переменные */

extern struct commands

char command[20];

char tok;

table[];

extern char token[80]; /* внешнее представление лексемы */

extern char token_type; /* тип лексемы */

extern char tok; /* внутреннее представление лексемы */

void get_exp(),level2(),level3(),level4(),level5();

void level6(),primitive(),arith(),unary();

void serror(), putback();

/* Точка входа в анализатор. */

void get_exp(result)

int *result;

get_token();

if(!*token)

serror(2);

return;

level2(result);

putback(); /* возвращает последнюю считаную

лексему обратно во входной поток */

/* Сложение или вычитание двух термов */

void level2(result)

int *result;

register char op;

int hold;

level3(result);

while((op=*token) == '+' || op == '-')

get_token();

level3(&hold);

arith(op,result,&hold);

/* Вычисление произведения или частного двух фвкторов */

void level3(result)

int *result;

register char op;

int hold;

level4(result);

while((op = *token) == '+' || op == '/' || op == '%')

get_token();

level4(&hold);

arith(op,result,&hold);

/* Обработка степени числа (целочисленной) */

void level4(result)

int *result;

int hold;

level5(result);

if(*token== '^')

get_token();

level4(&hold);

arith('^', result, &hold);

/* Унарный + или - */

void level5(result)

int *result;

register char op;

op = 0;

if((token_type==DELIMITER) && *token=='+' || *token=='-')

op = *token;

get_token();

level6(result);

if(op)

unary(op, result);

/* Обработка выражения в круглых скобках */

void level6(result)

int *result;

if((*token == '(') && (token_type == DELIMITER))

get_token();

level2(result);

if(*token != ')')

serror(1);

get_token();

else

primitive(result);

/* Определение значения переменной по ее имени */

void primitive(result)

int *result;

switch(token_type)

case VARIABLE:

*result = find_var(token);

get_token();

return;

case NUMBER:

*result = atoi(token);

get_token();

return;

default:

serror(0);

/* Выполнение специфицированной арифметики */

void arith(o, r, h)

char o;

int *r, *h;

register int t, ex;

switch(o)

case '-':

*r = *r-*h;

break;

case '+':

*r = *r+*h;

break;

case '*':

*r = *r * *h;

break;

case '/':

*r = (*r)/(*h);

break;

case '%':

t = (*r)/(*h);

*r = *r-(t*(*h));

break;

case '^':

ex =*r;

if(*h==0)

*r = 1;

break;

for(t=*h-1; t>0; --t) *r = (*r) * ex;

break;

/* Изменение знака */

void unary(o, r)

char o;

int *r;

if(o=='-') *r = -(*r);

/* Поиск значения переменной */

int find_var(s)

char *s;

if(!isalpha(*s))

serror(4); /* не переменная */

return 0;

return variables[toupper(*token)-'^'];

/* выдать сообщение об ошибке */

void serror(error)

int error;

static char *e[]=

"Синтаксическая ошибка",

"Непарные круглые скобки",

"Это не выражениеt",

"Предполагается символ равенства",

"Не переменная",

"Таблица меток переполнена",

"Дублирование меток",

"Неопределенная метка",

"Необходим оператор THEN",

"Необходим оператор TO",

"Уровень вложенности цикла FOR слишком велик",

"NEXT не соответствует FOR",

"Уровень вложенности GOSUB слишком велик",

"RETURN не соответствует GOSUB"

;

printf("&4%s\n",e[error]);

longjmp(e_buf, 1); /* возврат в точку сохранения */

/* Чтение лексемы. */

get_token()

register char *temp;

token_type=0; tok=0;

temp=token;

if(*prog=='\0') /* Конец файла */

*token=0;

tok = FINISHED;

return(token_type=DELIMITER);

while(iswhite(*prog)) ++prog; /* пропуск пробелов */

if(*prog=='\r') /* коней строки программы */

++prog; ++prog;

tok = EOL; *token='\r';

token[1]='\n'; token[2]=0;

return (token_type = DELIMITER);

if(strchr("+-^/%=;(),><", *prog)) /* разделитель */

*temp=*prog;

prog++; /* переход на следующую позицию */

temp++;

*temp=0;

return (token_type=DELIMITER);

if(*prog=='"') /* строка кавычек */

prog++;

while(*prog != '"' && *prog!='\r') *temp++=*prog++;

if(*prog=='\r') serror(1);

prog++;*temp=0;

return(token_type=QUOTE);

if(isdigit(*prog)) /* число */

while(!isdelim(*prog)) *temp++=*prog++;

*temp = '\0';

return(token_type = NUMBER);

if(isalpha(*prog)) /* переменная или команда */

while(!isdelim(*prog)) *temp++=*prog++;

token_type=STRING;

*temp = '\0';

/* просматривается, если строка - переменная или команда */

if (token_type==STRING)

tok=look_up(token); /* Преобразование во внутренний формат */

if (!tok) token_type = VARIABLE;

else token_type = COMMAND; /* это команда */

return token_type;

/* Возврат лексемы во входной поток */

void putback()

char *t;

t = token;

for(; *t; t++) prog--;

/* Поиск соответствия внутреннего формата для

текущей лексемы в таблице лексем.

*/

look_up(s)

char *s;

register int i,j;

char *p;

/* преобразование к нижнему регистру */

p = s;

while(*p) *p = tolower(*p); p++;

/* просматривается, если лексема обнаружена в

таблице */

for(i=0; *table[i].command; i++)

if(!strcmp(table[i].command, s)) return table[i].tok; return 0; /* нераспознанная команда */

/* Возвращает "истину", если "c" разделитель */

isdelim(c)

char c;

if(strchr(" ;,+-<>/*%^=()",c) || c==9 || c=='\r' || c==0)

return 1;

return 0;

/* Возвращает 1, если "с" пробел или табуляция */

iswhite(c)

char c;

if(c==' ' || c=='\t') return 1;

else return 0;

Анализатор поддерживает следующие операторы: "+", "-", "*", "/", "%", целочисленный показатель степени (^) и унарный минус. Все эти операции могут использоваться в сочетании с круглыми скобками. Вы, вероятно, заметили, что в программе имеются функции шести уровней, которые работают точно также как функция primitive(), вращающая значение числа. Помимо этих функций, реализующих арифметические операции, в состав программы включены функции arith() и unary(), а также get_token().

При вычислении выражения prog указывает на начало строки, содержащей выражение, и вызывает get_exp() с адресом переменной, в которую вы хотите поместить результат.

Обратите особое внимание на функцию serror(), которая используется для выдачи сообщений об ошибках. При обнаружении синтаксической ошибки serror() вызывается с номером этой ошибки в качестве аргумента. Ошибка с кодом 0, которой соответствует сообщение "синтаксическая ошибка", выдается в том случае, когда тип ошибки нельзя определить более конкретно. В других случаях ошибка уточняется. Заметьте, что serror() заканчивается вызовом функции longjmp().

Функция logjmp() выполняет нелокальный переход, возвращаясь в точку, определенную с помощью функции setjmp(). Функция setjmp() включена в исходный текст интерпретатора. Первый аргумент функции logjmp() является буфером среды, инициированной с помощью setjmp(). Второй аргумент - это значение, которое определяется при передаче управления из setjmp() обратно в точку ее вызова. Как это делается вы, увидите позже.

Использование ljgjmp() упрощает обработку ошибок, так как программы-анализаторы не должны аварийно завершаться при обнаружении ошибки. Если ваш компилятор Си не поддерживает функции setjmp() и logjmp(), то каждая функция при обнаружении ошибок должна выполнять возврат в точку возникновения ошибки самостоятельно.

Как анализатор обрабатывает переменные

Как было сказано раньше, интерпретатор языка SMALL BASIC распознает переменные с именами только от "A" до "Z". Каждой переменной соответствует элемент массива variables, состоящего из 26 элементов. Этот массив определен в тексте интерпретатора, как показано ниже, и инициализируется нулевыми значениями.

int variables[26]= /* 26 переменных пользователя, A-Z */

0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0

;

Так как именами переменных являются буквы от "A" до "Z", то индексирование массива variables можно легко осуществить путем вычитания из соответствующих значений имен переменных в коде ASCII кода символа 'A'. Функция find_var(), определяющая значение переменной в зависимости от ее имени, представлена ниже.

/* Определение значения переменной по ее имени*/

int find_var(s)

char *s;

if(!isalpha(*s))

serror(4); /* это не переменная */

return 0;

return variables[toupper(*token)-'A'];

Эта функция допускает использование более длинных имен, но только первая буква имени переменной является значащей.

Интерпретатор языка Small Basic

Разрабатываемый интерпретатор будет распознавать следующие ключевые слова языка программирования BASIC:

PRINT

INPUT

IF

THEN

FOR

NEXT

TO

GOTO

GOSUB

RETURN

END

Внутреннее представление этих команд (плюс значение EOL для конца строки и FINISHED для сигнализации о конце программы) определяется так:

#define PRINT 1

#define INPUT 2

#define IF 3

#define THEN 4

#define FOR 5

#define NEXT 6

#define TO 7

#define GOTO 8

#define EOL 9

#define FINISHED 10

#define GOSUB 11

#define RETURN 12

#define END 13

Для преобразования внешнего представления лексем во внутренний формат используется вспомагательная структура table.

struct commands /* Вспомогательная структура ключевых

слов анализатора */

char command[20];

char tok;

table[] = /* Таблица обрабатывает команды, введенные */

"print",PRINT, /* на нижнем регистре */

"input",INPUT,

"if",IF,

"then",THEN,

"goto",GOTO,

"for",FOR,

"next",NEXT,

"to",TO,

"gosub",GOSUB,

"return",RETURN,

"end",END,

"",END /* mark end of table */

;

Обратите внимание на то, что признак конца файла (нулевая строка) помещен в конец таблицы.

Функция look_up() возвращает внутреннее представление каждой лексемы или символа '\0', если таковая не обнаружена.

/* Преобразование каждой лексемы из таблицы лексем

во внутреннее представление.

*/

look_up(s)

char *s;

register int i,j;

char *p;

/* преобразование в символы нижнего регистра */

p =s;

while(*p) *p = tolower(*p); p++;

/* если лексема обнаружена в таблице */

for(i=0; *table[i].command; i++)

if(!strcmp(table[i].command, s)) return table[i].tok; return 0; /* команда не распознана */

Интерпретатор языка SMALL BASIC не поддерживает редактор текстов, поэтому вы должны создавать программы на языке BASIC, используя стандартный текстовый редактор.

Каждая программа считывается и выполняется с помощью интерпретатора. Функция, которая загружает программу, называется load_program().

/* Загрузка программы */

load_program(p, fname)

char *p;

char *fname;

FILE *fp; int i=0; if(!(fp=fopen(fname, "rb"))) return 0;

i = 0;

do

*p = getc(fp); p++; i++;

while(!feof(fp) && i<PROG_SIZE);

*(p-2) = '\0'; /* Символ конца загружаемой программы */ fclose(fp);

return 1;

Основной цикл работы анализатора

Все интерпретаторы выполняют операции путем считывания лексемы программы и выбора необходимой функции для ее выполнения. Основной цикл работы для интерпретатора языка SMALL BASIC выглядит следующим образом.

do

token_type = get_token();

/* Проверка на соответствие оператору языка */

if (token_type == VARIABLE)

putback(); /* возврат переменной во входной поток */

assignment(); /* длжен быть оператор присваивания */

else /* это команда */

switch(tok)

case PRINT:

print();

break;

case GOTO:

exec_if();

break;

case FOR:

exec_for();

break;

case NEXT:

next();

break;

case INPUT:

input();

break;

case GOSUB:

gosub();

break;

case RETURN:

greturn();

break;

case END:

exit(0);

while (tok != FINISHED);

Сначала лексема считывается из программы. Для удобства анализа каждая лексема располагается на отдельной строке. Если лексема является переменной, то, следуя синтаксису языка, за ней должен следовать оператор присваивания (SMALL BASIC не поддерживает старомодную команду LET). В противном случае, лексема считается командой и с помощью оператора case в зависимости от значения tok происходит выбор соответствующей

команды. Посмотрите, как работает каждая из них.

Команда присваивания значений

В языке BASIC основной формой оператора присваивания является следующая:

<имя переменной>=<выражение>

Функция assignment() поддерживает этот тип присваивания.

/* Присвоить значение переменной */

assignment()

int var, value;

/* получить имя переменной */

get_token();

if(!isalpha(*token))

serror(4); /* это не переменная */

return;

/* поиск индекса переменной в массиве */

var = toupper(*token)-'A';

/* считать символ равенства*/

get_token();

if(*token!='=')

serror(3);

return;

/* считать присваемое переменной значение */

get_exp(&value);

/* присвоить значение*/

variables[var] = value;


Глава 8. О манипулировании экраном и выработке звука

На всем протяжении этой книги мы касались, в основном, тех аспектов программирования на Си, которые могут заинтересовать профессиональных программистов, чье основное занятие программирование на Си. Так как возможности программы по взаимодействию с пользователем часто ограничиваются возможностями, представленными в рамках пользовательского интерфейса, то в этой главе содержатся завершенные сведения, так сказать, окончательная точка зрения на возможность и целесообразность манипулирования с экраном дисплея при разработке пользовательского интерфейса. Основное внимание в этой главе уделяется вопросам отображения различных фрагментов текста в разных цветах. Дополнительно в этой главе рассматриваются некоторые другие вопросы программирования пользовательского интерфейса такие, как изменение размера и формы курсора, скроллинг (прокрутка) части текста, сохранение содержимого экрана в виде дискового файла. Использование со вкусом звуковых возможностей компьютера позволяет в значительной мере "оживить" работу пользователя с вашей программой, а также акцентировать внимание пользователя на ряде моментов и ситуаций, возникающих во время работы. В связи с этим в главу включен параграф, поясняющий возможности пользователя по генерации звуков различных частот и созданию различных звуковых эффектов с использованием динамика компьютера.

Подпрограммы, описанные в этой главе, являются

машинно-зависимыми. Они могут функционировать на IBM PC, XT, AT,

PS/2 и совместимых с ними моделях. Большинство из подпрограмм требуют наличия в вашей конфигурации компьютера цветного дисплея (адаптера). Если вы имеете несовместимый с вышеперечисленными моделями компьютер, то вам необходимо будет внести в подпрограммы соответствующие изменения.

Использование цвета в текстовом режиме

Ранее вы могли видеть великолепные, профессионально написанные программы, которые не используют цветовые возможности. Как вы уже уяснили из поддерживает различные видеорежимы. Если вы имеете в своей

системе цветной адаптер, режим работы которого по умолчанию

установлен равным 3, то это означает, что специфицирован цветной

режим отображения текста 80*25 строк. По умолчанию текст

отображается на экране в белом цвете, однако, имеется возможность

отображать текст в других цветах.

Атрибутный байт текстового режима.

Каждый символ отображается на экране дисплея в соответствии с его атрибутным байтом, определяющим как именно отображается символ (см. главу 1). Если компьютер включает в себя цветной адаптер, работающий в видеорежиме, определяемом значением 3, то соответствующее значение атрибутного байта определяет цвет отображаемого символа, цвет фона, интенсивность отображения символа (уровень яркости), а также устанавливает или отменяет режим мерцания символа. Состав атрибутного байта показан в Таблице 8.1.

Биты 0, 1 и 2 атрибутного байта определяют компоненты цвета символа. Например, если установлен бит 0 (значение бита равно 1), то символ отображается в голубом цвете. Если значение всех этих битов не установлено, то символ является неотображаемым. Запомните, что цвета накладываются друг на друга. Если значения всех этих битов установлены (равны 1), символ отображается в белом цвете. Если вы установили значения двух из этих битов, то будет генерирован либо ярко-красный, либо голубой (циановый) цвет символа. Биты с 4 по 6 используются для установки цвета фона. Если значение этих битов не установлено (равно 0), то цвет фона будет черным, в противном случае цвет фона определяется в соответствии со специфицированным значением битов.

На заре микрокомпьютеров режимом, в котором видеосистемой отображались символы по умолчанию, был режим полной яркости, однако наряду с этим режимом имелась возможность отображать символы в режиме пониженной яркости. После реализации IBM PC пользователю был предложен альтернативный путь: По умолчанию отображение символов видеосистемой PC выполняется в режиме "нормальной" яркости, но вы имеете возможность отображать символы в режиме повышенной яркости, устанавливая значение 1 для соответствующего бита атрибутного байта (бита повышенной яркости) В добавок ко всему вы можете установить режим мерцания символа, установив значение соответствующего бита.

Таблица 8-1.

Состав атрибутного байта при работе в 3 видеорежиме
 | Бит 0 1 2 3 4 5 6 7 | Устанавливаемое значение Голубой цвет символа Зеленый цвет символа Красный цвет символа Повышенная яркость символа Голубой цвет фона Зеленый цвет фона Красный цвет фона Мерцание символа
__ | ___________ | _____________________________________ | ____________

В предыдущих параграфах были рассмотрены функции, которые выполняли считывание символов на экран, используя при этом как обращение к BIOS, так и непосредственный доступ к видеопамяти. Непосредственный доступ к видеопамяти является необходимым условием повышения скорости реакции задач на действия пользователя. Однако непосредственный доступ к видеопамяти значительно снижает возможность переносимости программ (их мобильность), а также является серьезной помехой при использовании программ в мультизадачных операционных системах типа OS/2. Функции, рассматриваемые в этой главе, используют возможности BIOS и видеопамяти по той причине, что сами по себе эти функции более мобильны и, в конечном итоге, для ускорения их быстродействия обычно не требуется стандартный вывод на дисплей. Следует отметить, что интуитивно использование непосредственного доступа к видеопамяти является более предпочтительным.

Отображение строки в определенном цвете.

Отображение строки в определенном цвете не является столь трудной задачей, как вам может казаться на первый взгляд, если вы используете функции записи символа, которые используют, в свою очередь, возможности BIOS и видеопамяти (ROM-BIOS). ROM-BIOS прерывание 10Н, функция 9 позволяет отобразить текущий символ (один!) в позиции курсора и его атрибуты. Проблема состоит лишь в перемещении курсора по записываемой вами строке, но это должна осуществлять непосредственно ваша подпрограмма.

В соответствии с этим возникает, во-первых, необходимость определения текущей позиции курсора. Для этого используется функция read_cursor_xy(), представленная ниже. Эта функция использует ROM-BIOS-прерывание 10Н, функцию 3, для чтения текущих координат позиции курсора X и Y. Координаты позиции курсора возвращаются в качестве значений аргументов функции.

/* Чтение текущих координат позиции курсора */

void read_cursor_xy(x,y)

char *x,*y;

union REGS r;

r.h.ah = 3; /* чтение текущей позиции курсора */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

*y = r.h.dl;

*x = r.h.dh;

После определения координат текущей позиции курсора, функция, которая выполняет печать строки, должна осуществить перемещение курсора к следующему символу, с тем, чтобы используя ROM-BIOS-прерывание напечатать его. Для перемещения курсора целесообразно использовать функцию goto_xy(), которая была уже рассмотрена ранее и приводится в этой главе для полноты изложения материала.

/* Перемещение курсора в позицию, специфицированную

координатами X и Y

*/

void goto_xy (x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = x; /* координата столбца */

r.h.dh = y; /* координата строки */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

Функция color_puts(), представленная ниже, отображает специфицированную пользователем строку в указанном цвете.

/* Печать строки в цвете */

void color_puts(s,color)

char *s; /* строка */

char color; /* цвет строки */

union REGS r;

char x,y;

read_cursor_xy(&x,&y); /* получение текущей позиции курсора

*/

while (*s)

if (*s == '\n') /* обработка символа новой строки */

printf("\n");

s++;

x = 0; y++; /* переход на следующую строку */

continue;

r.h.ah = 9; /* функция отображения символа и его атрибутов */

r.h.al = *s++; /* отображаемый символ */

r.h.bl = color; /* атрибуты цвета */

r.h.bh = 0; /*видеостраница 0 */

r.x.cx = 1; /* отобразить за единицу времени ( такт ) */

int86(0x10,&r,&r);

x++;

goto_xy(x,y); /* перемещение курсора */

Как вы можете видеть, отображаемый символ запоминается в регистре AL, атрибуты цвета символа - в регистре BL, номер видеостраницы - в регистре BH, а количество интервалов времени (тактов процессора), за которое будет отображен символ - в регистре CX. Заметим, что функция также обрабатывает специальный символ новой строки ('\n'). Вы можете также, по желанию, организовать обработку символов табуляции ('\t'), двойных кавычек (") и других специальных символов.

Использование функции color_puts() предполагает наличие ряда макроопределений в начале вызывающей функцию программы. Перечень макроопределений представлен ниже

#define BLUE 1

#define GREEN 2

#define RED 4

#define INTENSE 8

#define BLUE_BACK 16

#define GREEN_BACK 32

#define RED_BACK 64

#define BLINK 128

Используя эти макросы, вы можете по своему усмотрению выдать

на экран строку текста на фоне установленного вами цвета, а также

саму строку в определенном вами цвете. Вы можете также управлять

режимом отображения строки (повышенная яркость или мерцание).

Комбинируя цвета, режимы мерцания или повышенной яркости для одного или совокупности символов, вы можете добиться любого желаемого вами эффекта. Например, представленная ниже строка программы приведет к отображению строки "А это - текст" в режиме повышенной яркости в голубом (циановом) цвете:

color_puts("А это - текст",GREEN | RED | INTENSE );

Использование цвета.

Довольно эффективно использование многоцветного текста в различных приложениях. Во всяком случае многоцветный текст смотрится всегда намного эстетичней, чем монохромный. Однако при использовании многоцветного текста необходимо придерживаться следующих основных положений:

- Избегайте использования "нестандартных" цветов. Наиболее общим лучшим вариантом является отображение белых с имволов на черном фоне. Предпочтительнее вместо отображения какой-то важной информации в контрастном цвете отображать ее в режиме повышенной яркости.

- Наиболее эффективным признано использование цветных рамок экрана и окон.

- В ряде ситуаций полезным оказывается отображение отрицательного остатка (например, денежной суммы) в красном цвете.

- Отображение текущей строки (или части текущей строки) в контрастном цвете является, пожалуй, лучшим приемом индикации положения действий пользователя на экране в текущий момент времени.

Изменение размера курсора

Большинство пользователей даже не представляют насколько велики возможности семейства машин IBM PC. В частности, они позволяют изменять размер курсора. По умолчанию курсор отображается в виде одной мерцающей строчки (развертки дисплея). Однако пользователь может варьировать размером курсора от одной строки развертки дисплея до полного размера (высоты) символа. В цветном текстовом режиме курсор может иметь высоту от 0 до 8 строк развертки. (В монохромном режиме курсор может иметь высоту от 0 до 14 строк развертки, однако в данном параграфе мы будем рассматривать только цветной режим). Нижняя строка развертки

имеет номер 0. Лучше всего рассматривать изменение курсора именно

относительно строки развертки 0, так как применение другого

метода может привести к значительным расхождениям результатов на

различных компьютерах. (В принципе применение других методов

возможно, однако вам необходимо помнить, что при их использовании

вы можете не добиться соответствия форм курсора при решении одной

и той же задачи на разных компьютерах). При условии, что вы

будете рассматривать изменение формы курсора относительно нулевой

строки развертки, изменение формы курсора будет выполняться

аналогично изображенному на рис.8-1.

Для установления размера курсора вам необходимо использовать ROM-BIOS-прерывание 10Н, функцию 1, которая устанавливает размер курсора. Начало курсора (начальная строка развертки) - запоминается в регистре CН, а конец (конечная строка развертки) в регистре CL.

Строка развертки

7 ----- | |

6 ----- | | | | | |

5 ----- | | | | | | | | | |

4 ----- | | | | | | | | | | | | | |

3 ----- | | | | | | | | | | | | | | | | | |

2 ----- | | | | | | | | | | | | | | | | | | | | | |

1 ----- | | | | | | | | | | | | | | | | | | | | | | | | | |

0 | | | | | | | | | | | | | | ----- ----- ----- ----- ----- ----- -----

Рис. 8-1. Возможность изменения формы курсора в цветном режиме.

Функция size_cursor(), представленная ниже, устанавливает размер курсора.

/* Установление размера курсора */

void size_cursor(start,end)

char start,end; /* начальная и конечная строки развертки */

union REGS r;

r.h.ah = 1; /* функция адресации курсора */

r.h.ch = start;

r.h.cl = end;

int86(0x10,&r,&r);

При использовании функции size_cursor() укажите желаемые начальную и конечную строки развертки, определяющие размер курсора. Например, следующая конструкция позволяет установить высоту курсора в три строки развертки:

size_cursor(0,2);

Форма курсора может быть изменена либо очередным вызовом функции size_cursor(), либо изменением видеорежима.

Использование курсоров различной формы позволяет не только полностью удовлетворить ваши эстетические требования, но и повысит наглядность программы. Имейте в виду, что большой мерцающий курсор вызывает у пользователей раздражение.

Скроллинг части экрана

Две совместно используемые функции ROM-BIOS-прерывания позволяют осуществлять скроллинг вперед и назад части экрана. Эти функции были включены в ROM-BIOS для поддержки многооконных интерфейсов. Как вы знаете, когда курсор расположен в двадцать пятой строке и вы нажали клавишу <ВВОД>, то автоматически осуществляется перемещение текста на одну строку вверх с целью отображения новой строки в нижней части экрана. Точно так же, с помощью функций 6 и 7 прерывания ROM-BIOS 10Н, можно осуществить скроллинг лишь части экрана. Функция 6 позволяет выполнить скроллинг в окне вниз (вперед), а функция 7 - вверх (назад).

Обе функции при вызове используют информацию, хранимую в определенных регистрах. Занесите количество строк, на которые будет "прокручиваться" текст (мощность скроллинга) в регистр AL. Номер верхней левой строки, ограничивающей ваше "окно", занесите в регистр CH, а номер верхнего левого столбца - в регистр CL. Номер нижней левой строки занесите в регистр DH, а номер нижнего правого столбца - в регистр DL. В конце запомните в регистре BH атрибуты режима отображения, которые будут определять, как именно будут отображаться в процессе скроллинга новые строки. Функция scroll_window() представлена ниже.

/* Скроллинг в окне вперед и назад */

void scroll_window(startx,starty,endx,endy,lines,direct)

char startx,starty;/* верхний левый угол */

char endx,endy; /* нижний правый угол */

char lines; /* число строк прокрутки */

char direct; /* вперед или назад */

union REGS r;

if ( direct == UP ) r.h.ah = 6; /* скроллинг вперед */

else r.h.ah = 7; /* скроллинг вниз (назад) */

r.h.al = lines;

r.h.ch = starty;

r.h.cl = startx;

r.h.dh = endy;

r.h.dl = endx;

r.h.bh = 0; /* режим отображения */

int86(0x10,&r,&r);

Вы можете определить макрос UP как имеющий некоторое значение. Вы также можете определить макрос DOWN, значение которого будет отлично от UP, а затем использовать при необходимости осуществления скроллинга в окне эти макроопределения. Такой прием значительно упростит вашу программу. Функция scroll_window() присваивает регистру ВН значение 0 для сохранения пустых строк, однако вы можете изменить это значение по своему усмотрению.

Простейшая демонстрационная программа

Эта программа позволяет продемонстрировать работу функций, рассмотренных в данной главе до настоящего момента. Эти функции позволяют изменить форму курсора, выдать сообщение в цвете, а также использовать скроллинг части экрана. Результат работы этой программы представлен на рис.8-2.

-----------------------------------------------------------------

| |

| a) Это - тест |

| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |

| bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |

| cccccccccccccccccccccccccccccccccccccccccccccccccccccc |

| dddddddddddddddddddddddddddddddddddddddddddddddddddddd |

| eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee |

| ffffffffffffffffffffffffffffffffffffffffffffffffffffff |

| gggggggggggggggggggggggggggggggggggggggggggggggggggggg |

| hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh |

| iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii |

| jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj |

| kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk |

| llllllllllllllllllllllllllllllllllllllllllllllllllllll |

| mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm |

| nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn |

| oooooooooooooooooooooooooooooooooooooooooooooooooooooo |

| pppppppppppppppppppppppppppppppppppppppppppppppppppppp |

| qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq |

| rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr |

| ssssssssssssssssssssssssssssssssssssssssssssssssssssss |

| tttttttttttttttttttttttttttttttttttttttttttttttttttttt |

| uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu |

| vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv |

| |

| б) Это - тест. |

| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |

| bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |

| cccccccccccccccccccccccccccccccccccccccccccccccccccccc |

| dddddddddddddddddddddddddddddddddddddddddddddddddddddd |

| eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee |

| ffffffffffffffffffffffffffffffffffffffffffffffffffffff |

| gggggggggggggggggggggggggggggggggggggggggggggggggggggg |

| hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh |

| iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii |

| jjjjjjjjjj jjjjjjjjjjjjjjjjjjjjjjj |

| kkkkkkkkkk kkkkkkkkkkkkkkkkkkkkkkk |

| llllllllll lllllllllllllllllllllll |

| mmmmmmmmmmjjjjjjjjjjjjjjjjjjjjjmmmmmmmmmmmmmmmmmmmmmmm |

| nnnnnnnnnnkkkkkkkkkkkkkkkkkkkkknnnnnnnnnnnnnnnnnnnnnnn |

| oooooooooolllllllllllllllllllllooooooooooooooooooooooo |

| pppppppppppppppppppppppppppppppppppppppppppppppppppppp |

| qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq |

| rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr |

| ssssssssssssssssssssssssssssssssssssssssssssssssssssss |

| tttttttttttttttttttttttttttttttttttttttttttttttttttttt |

| uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu |

| vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv |

| |

-----------------------------------------------------------------

Рис. 8-2 Результат работы программы.

/* Демонстрационная программа печати текста в цвете,

изменения формы курсора и скроллинга в окне

*/

#include "dos.h"

#define BLUE 1

#define GREEN 2

#define RED 4

#define INTENSE 8

#define BLUE_BACK 16

#define GREEN_BACK 32

#define RED_BACK 64

#define BLINK 128

#define UP 0

#define DOWN 1

void mode(),color_puts(),palette(), read_cursor_xy();

void goto_xy(),size_cursor(),scroll_window();

main()

int i,j;

mode(3); /* режим текстовый, цветной */

size_cursor(0,3);

goto_xy(0,0);

color_puts(" Это - тест\n",BLUE |RED | INTENSE);

for ( i=0;i<22;i++)

for ( j=0;j<79;j++)

printf("%c",i+'a');

printf("\n");

getche();

scroll_window(10,10,50,15,3,DOWN);

getche();

/* Печать строки в цвете */

void color_puts(s,color)

char *s; /* строка */

char color; /* цвет строки */

union REGS r;

char x,y;

read_cursor_xy(&x,&y); /* чтение текущей позиции

курсора */

while (*s)

if (*s == '\n') /* обработка символа новой

строки */

printf("\n");

s++;

x = 0; y++; /* переход к новой строке */

continue;

r.h.ah = 9;/* отображение символов по атрибутам */

r.h.al = *s++; /* выдаваемый символ */

r.h.bl = color; /* атрибут цвета */

r.h.bh = 0; /* видеостраница 0 */

r.x.cx = 1; /* выдача символа за один такт */

int86(0x10,&r,&r);

x++;

goto_xy(x,y); /* перемещение курсора */

/* чтение текущей позиции курсора */

void read_cursor_xy(x,y)

char *x,*y;

union REGS r;

r.h.ah = 3; /* чтение позиции курсора */

r.h.bh = 0; /* видеостраница 0 */

int86(0x10,&r,&r);

*y = r.h.dl;

*x = r.h.dh;

/* установка палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код для графического режима 4 */

r.h.bl = pnum;

r.h.ah = 11; /* функция установки палитры */

int86(0x10,&r,&r);

/* установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

/* перемещение курсора в позицию x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = x; /* координаты столбца */

r.h.dh = y; /* координаты строки */

r.h.bh = 0; /* видеостраница 0 */

int86(0x10,&r,&r);

/* установка размера (формы) курсора */

void size_cursor(start,end)

char start,end; /* начальная и конечная строки

развертки */

union REGS r;

r.h.ah = 1; /* функция адресации курсора */

r.h.ch = start;

r.h.cl = end;

int86(0x10,&r,&r);

/* скроллинг в окне вперед и назад */

void scroll_window(startx,starty,endx,endy,lines,direct)

char startx,starty; /* верхний левый угол */

char endx,endy; /* нижний правый угол */

char lines; /* мощность скроллинга */

char direct; /* вверх или вниз */

union REGS r;

if ( direct == UP ) r.h.ah = 6; /* скроллинг вверх */

else r.h.ah = 7; /* скроллинг вниз */

r.h.al = lines;

r.h.ch = starty;

r.h.cl = startx;

r.h.dh = endy;

r.h.dl = endx;

r.h.bh = 0; /* режим отображения */

int86(0x10,&r,&r);

Сохранение копии экрана в дисковом файле.

Ни в DOS, ни в OS/2 нет утилит, сходных с утилитой печати копии экрана, позволяющей сохранять текущее содержимое экрана дисплея в дисковом файле. В этом разделе вы найдете пояснение, как можно создать программу, которая выполняла бы именно эту функцию.

Эта программа использует ROM-BIOS-прерывание 10Н, функцию 8 для чтения символа из текущей позиции курсора, после чего этот символ записывается в файл на диске. Как и в предыдущем разделе, вы опять встретитесь с функцией goto_xy(), которая в этом случае используется для перемещения курсора последовательно по всем строкам экрана, начиная с левого верхнего угла экрана до правого нижнего угла.

Имя файла, в котором будет храниться копия экрана, указываетя в качестве аргумента программы. Если, к примеру, вы назовете свою программу, копирующую экран на диск, screen, то представленная ниже командная строка приведет к созданию копии экрана в файле с именем scr.sav:

C> screen scr.sav

А вот исходный текст самой программы копирования:

/* Эта программа копирует содержимое экрана вашего

дисплея в файл, имя которого указано в командной

строке

*/

#include "dos.h"

#include "stdio.h"

void save_screen(),goto_xy();

main(argc,argv)

int argc;

char *argv[];

if ( argc != 2 )

printf(" используйте формат : screen <имя файла>");

exit(1);

save_screen(argv[1]);

/* сохранение содержимого экрана в дисковом файле */

void save_screen(fname)

char *fname;

FILE *fp; union REGS r;

register char x,y;

if ( !( fp=fopen(fname,"w")))

printf(" Файл не может быть открыт ");

exit(1);

for (y=0;y<25;y++)

for (x=0;x<80;x++)

goto_xy(x,y);

r.h.ah = 8; /* чтение символа */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

putc(r.h.al,fp); /* выдача (печать) символа */

fclose(fp);

/* Перемещение курсора в позицию (x,y) */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = x; /* координата столбца */

r.h.dh = y; /* координата строки */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

Создаваемый файл представляет собой стандартный ASCII файл, который может быть отредактирован и распечатан как обычный текстовый файл. Программа позволяет записывать лишь символы, отображенные на экране, но не позволяет сохранить соответствующие атрибуты отображения символов. Однако дополнить программу для того, чтобы она записывала и атрибуты символов несложно, и вы при желании можете это сделать самостоятельно.

А теперь добавим звук.

Использование со вкусом звука значительно повышает привлекательность программ. Звук может быть использован в широком спектре от тоненького "писка" машины до исполнения музыки или различных специальных эффектов. В этом параграфе вы научитесь, как можно управлять высотой и продолжительностью звучания нот, генерируемых динамиком компьютера. Мы также продемонстрируем вам некоторые наиболее интересные звуковые эффекты.

Программируемый таймер 8253.

Генерация звуков в компьютере PC выполняется с помощью программируемого таймера 8253, который применяется для управления колебаниями динамика. Управление колебаниями динамика определяется частотой, которая, в свою очередь, определяется содержимым различных внутренних регистров. Значения этих регистров устанавливаются при записи в определенные порты. Порт 66 используется для спецификации счетчика, который использует таймер при определении интервала колебаний динамика. Таймер работает в строгом соответствии с частотой системного таймера и специфицированным значением счетчика, определяющим колебания динамика. Затем, после обнуления счетчика происходит установка нового значения счетчика, и весь цикл функционирования программируемого таймера повторяется сначала. Значение счетчика определяется по следующей формуле:

count = 1,193,180/требуемая частота

где 1,193,180 есть тактовая частота системного таймера.

Регистр-счетчик таймера 8253 устанавливается в следующей последовательности (значение счетчика задается двухбайтным числом):

1. Выдать в порт 67 значение 182 (означающее, что будет устанавливаться счетчик).

2. Выдать в порт 66 младший байт числа, определяющего значение счетчика.

3. Выдать в порт 66 старший байт числа, определяющего значение счетчика.

Динамики большинства компьютеров класса PC не позволяют воспроизводить полный спектр частот, воспринимаемых человеческим слухом (от 20 Гц до 18.000 Гц). Однако динамик позволяет воспроизводить ноты лучше, чем динамики других компьютеров в пределах 12000 Гц и даже выше. В основном же динамик используется в пределах 100-5000 Гц.

Итак, таймер установлен. Однако динамик еще не будет воспроизводить звук, так как не включен. Таймер 8253 активен постоянно, а динамик требует дополнительной команды включения. Активизация динамика осуществляется путем установки значений битов 0 и 1 регистра программируемого периферийного интерфейса, задание значений которого выполняется через порт 97. Если значения этих двух битов установлены (равны 1), то динамик издает звук частотой, установленной счетчиком 8253. Если значения этих битов равны 0, то никакой звук генерироваться не будет. Остальные биты этого байта используются другими устройствами, поэтому интерпретация значения левых битов не может быть изменена. Таким образом, для установки значений управляющих динамиком бит

необходимо выполнить следующую последовательность действий:

1. Получить текущее значение регистра из порта 97.

2. Сравнить это значение с 3 или установить равным 3.

3. Записать результат в порт 97.

Для того, чтобы выключить динамик, необходимо переслать в порт значение 253.

Простейшим приемом, позволяющим читать и писать байт из или в порт, в Си является использование соответствующих функций. В Турбо Cи - это функции inportb() и outportb(). В Microsoft Cи - это функции inp() и outp(). Они имеют следующий общий формат:

int inportb(int port);

void outportb(int port, char value);

int inp(unsigned port);

int outp(unsigned port, int value);

В других компиляторах Си эти функции могут иметь иные названия, но обязательно будут присутствовать в вашей библиотеке, так как являются одними из базовых функций версий Си для ПЭВМ. В программах, приведенных в этом параграфе, используются функции Турбо Cи.

Простейший способ проверки слуха.

Вы обладаете возможностью сделать несколько грубый, но эффективный тест слуха, который в состоянии обнаружить некоторые типы дефекта слуха. Как вы ранее узнали, динамик большинства компьютеров серии PC не воспроизводит звуки выше 12000 Гц. Однако ряд людей, у которых отмечены некоторые отклонения слуха, не могут услышать звук даже такой частоты. Фактически, тестируя свой слух, вы будете несколько удивлены тем, насколько высоким окажется звук с частотой 12000 Гц. (Предупреждение: тестирование слуха с помощью этого теста можно производить лишь ради шутки. Он, естественно, не позволяет действительно оценить слух испытуемого. Поэтому, если вы заметили у себя дефекты слуха или хотите действительно проверить свой слух, обратитесь лучше к своему врачу).

Для получения звука в тесте используется функция sound(), которая генерирует непродолжительное звучание специфицированной ноты. Как показано ниже, эта функция содержит все необходимое для того, чтобы сгенерировать любой звук с помощью динамика компьютера.

/* Звучание динамика на заданной частоте */

void sound(freq)

int freq;

unsigned i;

union

long divisor;

unsigned char c[2];

count;

unsigned char p;

count.divisor = 1193280 / freq; /* вычисление небходимого

значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после

установки счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */ p = inportb(97); /* чтение существующего шаблона бит */ outportb(97,p|3); /* установка битов 0 и 1 */

for (i=0;i<64000;++i); /* цикл задержки */

outportb(97,p); /* восстановление первоначального значения

шаблона бит для отключения динамика */

Заметим, что частота звучания ноты специфицирована как

аргумент функции. Цикл задержки необходим, так как без него вы бы

услышали только мгновенный "щелчок" или "писк". Вы можете

изменить частоту работы системного таймера процессора вашего

компьютера. При этом, оформив его как параметр функции, вы

добьетесь определенной эффективности вашей программы. Функция

sound() может использоваться и для получения банального

"пищания" компьютера.

Управляющая функция для программы теста слуха представлена ниже.

/* Простейший тест слуха */

#include "dos.h"

void sound();

main()

int freq;

do

printf(" Введите частоту ( 0 - выход ): ");

scanf("%d",&freq);

if ( freq ) sound(freq);

while(freq);

При использовании теста, в возрастающем порядке указывайте частоту звука до тех пор, пока звук воспринимается на слух. Для выхода введите 0.

Имитация звука сирены и взврыва.

Вы можете использовать возможность управления динамиком для создания различных звуковых эффектов, которые, в частности, делают видеоигры очень интересными и привлекательными. В основе всех звуковых эффектов лежит варьирование частоты звука - часто самым необычным образом.

Например, для создания эффекта звучания сирены вы должны варьировать частоту звука между двумя конечными точками. Высота звука должна изменяться от меньшей к большей, а затем уменьшаться от большей к меньшей. Функция siren(), представленная ниже, использует этот метод для создания эффекта звучания сирены.

#define DELAY 10000

/* Создание эффекта звучания сирены */

void siren()

unsigned i,freq;

union

long divisor;

unsigned char c[2];

count;

unsigned char p;

p = inportb(97); /* чтение существующего шаблона бит */

outportb(97,p|3); /* установка бит 0 и 1 */

/* повышение звука сирены */

for (freq = 1000;freq<3000;freq+=RATE)

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

for (i=0;i<DELAY;++i);

/* понижение звука сирены */

for (;freq>1000;freq-=RATE)

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

for (i=0;i<DELAY;++i);

outportb(97,p); /* восстановление начального вида шаблона

бит для отключения динамика */

Вы можете переопределить значение макроса DELAY в зависимости от производительности вашего компьютера и вашего вкуса. Как вы видите, функция siren() выполняет один полный цикл звучания сирены и на этом завершает свою работу. Для получения эффекта продолжительного звучания сирены вам надо поместить обращение к siren() в цикл.

Для имитации звука взрыва, который используется во многих видеоиграх, можно модифицировать функцию siren() таким образом, чтобы она позволяла генерировать звук лишь нисходящей частоты. Функция laser(), представленная ниже, позволяет получить этот эффект.

#define DELAY 10000

/* получение эффекта взрыва */

void laser()

unsigned i,freq;

union

long divisor;

unsigned char c[2];

count;

unsigned char p;

p = inportb(97); /* чтение существующего шаблона бит */

outportb(97,p|3); /* установка бит 0 и 1 */

/* взрыв */

for (;freq>1000;freq-=RATE)

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */

outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

for (i = 0;i<DELAY;++i);

outportb(97,p); /* восстановление начального вида шаблона

бит для отключения динамика */

После небольшого экспериментирования вы сможете сами создавать широкий спектр звуковых эффектов. Интерес представляет варьирование скоростью изменения частоты звука для получения определенных эффектов.

Создание "космической музыки".

Соединив воедино произвольное количество стандартных функций Си rend() и sound(), вы создадите "космическую" музыку. Звук, получаемый при выполнении программы, представленной ниже, напоминает "музыку звезд" в старых научно-фантастических фильмах. Несмотря на то, что все звуки генерируются произвольным образом, ритм и рисунок мелодии, возникающие время от времени, действительно оставляют впечатление "небесной музыки".

/* Космическая музыка звезд */

#define DELAY 64000

#include "dos.h"

void sound();

main()

int freq;

do

do

freq = rand();

while (freq>5000); /* после персонального

прослушивания */ sound(freq);

while (!kbhit());

/* звучание динамика на специфицированной частоте */

void sound(freq)

int freq;

unsigned i;

union

long divisor;

unsigned char c[2];

count;

unsigned char p;

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

p = inportb(97); /* чтение существующего шаблона бит */

outportb(97,p|3); /* установка бит 0 и 1 */

for (i = 0;i<DELAY;++i); /* задержка 64000 для 10+ Мгц

компьютеров

32000 для 6 МГц PC/AT

20000 для стандарта PC и XT */

outportb(97,p); /* восстановление начального вида

шаблона бит для отключения динамика */

Эта программа генерирует звуки частотой менее 5000 Гц, так как звуки именно в пределах этой частоты наиболее мягко воспринимаются слухом и не выходят за границы, воспринимаемые ухом человека.

Рекомендуем вам поэкспериментировать с этой программой, установив произвольную длину интервала времени между звуками или фильтруя значения, передаваемые в sound(). Возможны и другие варианты развития вашего творчества.


Глава 9. Интерфейс с "мышью".

Наиболее популярным устройством ввода данных после клавиатуры является "мышь" (mouse). Несмотря на то, что "мышь" и сходные технологии, такие как "roller ball", получили широкое распространение лишь в последнее время, популярность "мыши" берет свое начало с момента выхода на рынок очередной разработки фирмы Apple компьютера Apple Lisa, в котором впервые была применена технология "мышь" для работы с пиктограммным (иконным) интерфейсом операционной системы этого компьютера. Модель Apple Lisa произвела форменный переворот в фирме Macintosh, которая пошла по пути использования "мыши" и пиктограммного интерфейса в своих программных продуктах. Перед выходом на рынок серии IBM PS/2 "мышь", по существу, была третьим дополнением к РС. Тем не менее уже при анонсировании системы IBM PS/2 сообщалось, что она снабжена портом для подключения "мыши", и "мышь" занимает значительное место среди РС.

Наилучшее использование "мыши" - предмет постоянных дискуссий. Не все программисты (или пользователи) воспринимают пиктограмный интерфейс. В связи с тем, что "мышь" впервые была использована именно с пиктограммным интерфейсом, вопросы использования "мыши" чаще всего сводятся именно к нему, а это в, свою очередь, отталкивает от "мыши" большой круг программистов, негативно относящихся к такому интерфейсу. Однако "мышь" ведь может использоваться и без такого интерфейса. Например, практически все согласны с тем, что "мышь" эффективно может использоваться при работе с интерактивной графикой.

Некоторые модели манипуляторов типа "мышь", равно как и выполняемые ими функции, могут значительно отличаться друг от друга. Поэтому заметим, что все программы, приведенные в этой главе, ориентированы на использование "мыши" фирмы Microsoft, которая функционально идентична "мыши", используемой в моделях PS /2. Для обеспечения интерфейса с "мышью" фирмы Microsoft вам необходимо иметь по крайней мере саму "мышь", руководство пользователя по программному обеспечению "мыши" (Microsoft Mouse Programmer's Reference Guide) и поставляемый с этим руководством диск. На этом диске расположена специальная библиотека с именем MOUSE.LIB, выполняющая поддержку функционирования "мыши" на самом нижнем уровне. Мы будем использовать функции из этой библиотеки в качестве базовых функций при рассмотрении программ, предлагаемых вам в этой главе. вы должны помнить, что ваш компилятор С должен быть совместим с подключаемыми на этапе редактирования связей подпрограммами из библиотеки подпрограмм поставляемых на диске фирмой Microsoft. вам также надлежит помнить, что обязательно необходимо наличие драйвера устройства MOUSE.SYS.

После беглого обзора основ использования манипулятора типа "мышь" в этой главе вы получите информацию о том, как можно модифицировать ранее рассмотренную программу "рисования" с тем, чтобы она могла работать с "мышью" (с учетом того, что основные концепции применения "мыши", а также разработанные в процессе

изложения материала подпрограммы, могут использоваться вами в

дальнейшем при создании различных конкретных приложений).

Некоторые начальные сведения о мыши.

Для того, чтобы использовать "мышь", прежде всего необходимо инсталировать соответствующий драйвер. Для "мыши" фирмы Microsoft в файл CONFIG.SYS должна быть добавлена следующая строка:

device = mouse.sys

Для инсталяции драйвера "мыши" фирмы IBM должна быть запущена программа MOUSE.COM. С этой целью в файл AUTOEXEC.BAT может быть добавлена строка вида:

mouse

После того, как драйвер размещен в системе, любые действия по перемещению "мыши" или нажатию ее клавиш будут вызывать генерацию прерывания 33Н. Процесс, управляющий "мышью", вызывает прерывание, затем устанавливает значения соответствующих внутренних переменных и продолжает свою работу. Вследствие того, что прерывание генерируется лишь при изменении "мышью" своего положения, неподвижная "мышь" не вызывает необходимости выделения на нее ресурсов компьтера.

Точно так же, как с клавиатурой ассоциирован курсор, с "мышью" также ассоциирован некий маркер на экране (называемый далее указатель). Подпрограммы в библиотеке в поддержке "мыши" фирмы Microsoft определяют по умолчанию следующую форму указателя: в графических режимах - это стрелка, а в текстовых режимах - прямоугольник в рамках габаритных размеров символа. Аналогично курсору клавиатуры указатель-курсор "мыши" может отображаться лишь тогда, когда "мышь" непосредственно используется. В противном случае указатель-курсор "мыши" не отображается на экране так, как будто интерфейса с "мышью" вообще не существует.

Несмотря на то, что "мышь" и экран физически отделены друг от друга, связь между ними все же имеется, так как драйвер "мыши" автоматически отслеживает содержимое счетчиков, индицирующих текущее состояние указателя-курсора "мыши" на экране. При перемещении "мыши" курсор автоматически перемещается на экране в точном соответствии с изменением координат "мыши". Расстояние, на которое была перемещана "мышь", измеряется в "мышиных" шагах. Один такой шаг равен 1/200 дюйма. Однако в большинстве случаев знать на сколько переместилась "мышь" совсем необязательно.

Виртуализация и реальный экран

Библиотека подпрограмм поддержки "мыши" фирмы Microsoft работает с виртуальным экраном в виде массива точек растра (массива из единиц минимального изображения, цвет и яркость которых можно задать независимо от остального изображения), который может отличаться от реального экрана. При перемещении "мыши" счетчики местоположения курсора изменяют свое значение. Перед отображением курсора виртуальные координаты курсора преобразуются в координаты реального экрана. В видеорежимах 6, 14, 15 и 16 это преобразование осуществляется один к одному. В режимах 4 и 5 не каждая точка виртуальной горизонтальной позиции преобразуется в координты реального экрана, а через одну. вы должны обратить внимание на этот факт, так как программа рисования, к которой будет добавлен интерфейс с "мышью" и которая будет рассматриваться в данной главе, работает именно в 4 графическом режиме. <

Библиотека поддержки "мыши".

Подпрограммы внутри MOUSE.LIB ассоциируются у пользователя с одной функцией, использующей в качестве входного аргумента число, специфицирующее номер функции поддержки "мыши". (Этот процесс некоторым образом сходен с процессом доступа к функциям DOS посредством прерывания 21Н с указанием номера нужной функции). Имя этой функции определяется моделью памяти, которая используется при компиляции вашей программы. Используйте имя cmouses() для модели маленькой памяти, cmousec() для модели компактной памяти, cmousem() для модели средней памяти и cmousel() для модели большой и самой большой (огромной) памяти. (Заметим, что функция не может работать в модели самой маленькой памяти). Пример, представленный в этой главе, использует модель маленькой памяти, однако вы можете изменить тип модели памяти по своему усмотрению.

Основным форматом функции cmouses() является:

void cmouses(fnum, arg2, arg3, arg4);

int *fnum, *arg2, *arg3, *arg4;

Как видно, fnum является номером функции поддержки "мыши", которую необходимо вызвать. Другие параметры содержат информацию, необходимую для спецификации функции. Обратите внимание, что функции передаются не сами аргументы, а указатели на их значения. Функция cmouses() возвращает результаты работы в виде параметров и, следовательно, нуждается в их адресации. Фирма Microsoft определила тридцать функций поддержки "мыши". Однако в программе рисования будут использованы лишь пять из них. Ниже приведен краткий обзор функций поддержки "мыши" фирмы Microsoft, которые будут использованы нами в этой главе.

Привести в исходное состояние, выдать статус.

Функция 0 приводит "мышь" в начальное состояние (сбрасывает "мышь") Она перемещает курсор-указатель "мыши" в центр экрана и "выключает " его. Функция возвращает номер нажатой клавиши "мыши" в качестве значения arg2. После завершения функции fnum принимает значение 0, если "мышь" и соответствующее программное обеспечение не инсталированы, и -1 в противном случае.

Отобразить курсор

Функция 1 отображает указатель-курсор "мыши". Она не возвращает никакого значения.

Переместить курсор

Функция 2 перемещает курсор по экрану. Она не возвращает никакого значения.

Выдать статус клавиши и позицию курсора

Функция 3 возвращает статус клавиши в arg2, виртуальную горизонтальную позицию курсора в arg3, а виртуальную вертикальную позицию курсора в arg4.

Статус клавиши кодируется в битах 0 и 1 байта arg2. Если значение бита 0 установлено (равно 1), то была нажата левая клавиша "мыши", если значение бита 1 установлено (равно 1), то была нажата правая клавиша. Если значения обоих битов не установлены (равны 0), то никакая клавиша нажата не была.

Установить координаты курсора

Функция 4 устанавливает месторасположение курсора "мыши". Значение arg3 определяет горизонтальную позицию, а значение arg4

- вертикальную позицию курсора. вы всегда должны помнить, что значения не должны выходить за пределы виртуального экрана, который вы используете.

Индикация движения

Функция 11 возвращает число вертикальных и горизонтальных "мышиных" шагов, которое "мышь" прошла со времени последнего обращения к функции 11, другими словами - это изменение вертикальных и горизонтальных координат "мыши". Функция также сбрасывает внутренний регистр-счетчик в 0. Значение вертикального счетчика возвращается в arg3, а горизонтального - в arg4. Это позволяет, если "мышь" после последнего обращения к функции не перемещалась на плоскости, получить значения как горизонтального, так и вертикального счетчиков равными 0. Если значение одного из счетчиков (или обоих) отлично от 0, то "мышь" перемещалась на плоскости.

Положительное значение изменения вертикальных "мышиных" шагов позволяет заключить, что "мышь" двигалась вниз. Отрицательное же значение свидетельствует о движении "мыши" вверх.

Положительное значение изменения числа горизонтальных шагов свидетельствует о том, что "мышь" перемещалась вправо, а отрицательное значение показывает на то, что "мышь" перемещалась влево.

Функции поддержки "мыши" верхнего уровня.

Используя функцию cmouses() вы можете создать набор функций языка Си высокого уровня, которые значительно облегчат вам программирование интерфейсов, ориентированных на использованием "мыши". Посмотрите, как это делается.

Установка "мыши" в исходное состояние.

Функция, представленная ниже, mouse_reset() используется для установки "мыши" в исходное состояние. Заметим, что функция требует наличия соответствующего программного обеспечения и аппаратной части компьютера, а также инсталяции двухклавишной "мыши".

/* Установка "мыши" в исходное состояние */

void mouse_reset()

int fnum, arg2, arg3, arg4;

fnum = 0; /* Установка "мыши" в исходное состояние */

cmouses( &fnum, &arg2, &arg3, &arg4);

if(fnum!=-1)

printf("Аппаратные или программные средства поддержки ");

printf("'мыши' не инсталированы");

exit(1);

if(arg2!=2)

printf("Разрешено использование только двухклавишной 'мыши'");

exit(1);

Отображение и перемещение курсора "мыши".

Взаимодополняющие друг друга функции cursor_on() и cursor_off(), представленные ниже, позволяют активизировать и деактивизировать изображение курсора на экране дисплея.

/* Включение курсора "мыши" */

void cursor_on()

int fnum;

fnum = 1; /* отобразить курсор */

cmouses( &fnum, &fnum, &fnum, &fnum);

/* Выключение курсора "мыши" */

void cursor_off()

int fnum;

fnum = 2; /* стереть курсор */

cmouses( &fnum, &fnum, &fnum, &fnum);

Какая из клавиш "мыши" была нажата?

Другой парой взаимодополняющих друг друга функций являются функции rightb_pressed() и leftb_pressed(), представленные ниже. Эти функции возвращают значение "истина", если нажата правая или левая клавиши.

/* Возвращает значение "истина", если нажата правая клавиша,

и "ложь" в противном случае */

rightb_pressed()

int fnum, arg2, arg3, arg4;

fnum = 3; /* Чтение позиции и статуса клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

return arg2 & 2;

/* Возвращает значение "истина", если нажата левая клавиша,

и "ложь" в противном случае */

leftb_pressed()

int fnum, arg2, arg3, arg4;

fnum = 3; /* Чтение позиции и статуса клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

return arg2 & 1;

Как обнаружить перемещение "мыши"?

Функция 11, которая возвращает изменение значения счетчика "мыши" (в "мышиных" шагах) после последнего обращения к ней, позволяет определить факт перемещения "мыши". Функция mouse_motion(), представленная ниже, возвращает изменение

местоположения "мыши" в горизонтальном и вертикальном

направлениях в переменных, чьи указатели являются аргументами

функции. Если оба значения deltax и deltay равны 0, то факт

движения "мыши" не регистрируется.

/* Возвращает направление движения */

void mouse_motion(deltax, deltay)

char *deltax, *deltay;

int fnum, arg2, arg3, arg4;

fnum = 11; /* получить направление движения */

cmouses( &fnum, &arg2, &arg3, &arg4);

if(arg3>0) *deltax = RIGHT;

else if(arg3<0) *deltax = LEFT;

else *deltax = NOT_MOVED;

if(arg4>0) *deltay = DOWN;

else if(arg4<0) *deltay = UP;

else *deltay = NOT_MOVED;

Макросы RIGHT, LEFT, UP, DOWN и NOT_MOVED определены следующим образом:

#define NOT_MOVED 0

#define RIGHT 1

#define LEFT 2

#define UP 3

#define DOWN 4

Чтение и установка позиции курсора.

Функции set_mouse_position() и mouse_position(),

представленные ниже, используются для установки чтения текущей

позиции курсора "мыши".

/* Установить координаты курсора "мыши" */

void set_mouse_position(x, y)

int x, y;

int fnum, arg2;

fnum = 4; /* установка позиции */

cmouses(&fnum, &arg2, &x, &y);

/* Возвращает координаты курсора "мыши" */

void mouse_position(x, y)

int *x, *y;

int fnum, arg2, arg3, arg4;

fnum = 3; /* получить позицию и статус клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

*x = arg3;

*y = arg4;

Простейшая демонстрационная программа.

Программа, представленная ниже, демонстрирует применение функций поддержки "мыши" высокого уровня. вы можете сразу ввести ее в ваш компьютер и попробовать ее в деле.

/* Интерфейс с "мышью" Microsoft/IBM */

#include "dos.h"

#define NOT_MOVED 0

#define RIGHT 1

#define LEFT 2

#define UP 3

#define DOWN 4

void mouse_position(), mode(), goto_xy(), mouse_motion();

void cursor_on(), cursor_off(), mouse_reset();

main(argc, argv)

int argc;

char *argv[];

char deltax, deltay, x, y;

if(argc!=2)

printf(" Используйте формат: mouser <видеорежим> ");

exit(1);

mode(atoi(argv[1]));

mouse_reset(); /* инициализация "мыши" */

cursor_on(); /* "включение" курсора */

do

goto_xy(0, 0);

if(leftb_presed()) printf("Левая клавиша");

if(rightb_pressed())

printf("Правая клавиша");

mouse_position(&x, &y);

printf("%d %d - ", x, y);

/* Отображение местоположения "мыши" */

mouse_motion(&deltax, &deltay);

if(deltax || deltay)

printf("Перемещение");

switch(deltax)

case NOT_MOVED: break;

case RIGHT: printf("Вправо");

break;

case LEFT: printf("Влево");

break;

switch(deltay)

case NOT_MOVED: break;

case UP: printf("Вверх");

break;

case DOWN: printf("Вниз");

break;

/* Цикл выполняется пока обе клавиши нажаты одновременно */

while(!(leftb_pressed() && rightb_pressed()));

mode(3);

/* Установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

/* Пересылка курсора в позицию, специфицированную

координатами х и у */

void goto_xy(x, y)

int x, y;

union REGS r;

r.h.ah=2; /* функция адресации курсора */

r.h.dl = y; /* координаты столбца */

r.h.dh = x; /* координаты строки */

r.h.bh = 0; /* видеостраница */

int86(0x10, &r, &r);

/**********************************************************/

/* Функции, обеспечивающие интерфейс с "мышью" */

/**********************************************************/

/* Включение курсора "мыши" */

void cursor_on()

int fnum;

fnum = 1; /* отобразить курсор */

cmouses( &fnum, &fnum, &fnum, &fnum);

/* Выключение курсора "мыши" */

void cursor_off()

int fnum;

fnum = 2; /* стереть курсор */

cmouses( &fnum, &fnum, &fnum, &fnum);

/* Возвращает значение "истина", если нажата правая клавиша,

и "ложь" в противном случае */

rightb_pressed()

int fnum, arg2, arg3, arg4;

fnum = 3; /* Чтение позиции и статуса клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

return arg2 & 2;

/* Возвращает значение "истина", если нажата левая клавиша,

и "ложь" в противном случае */

leftb_pressed()

int fnum, arg2, arg3, arg4;

fnum = 3; /* Чтение позиции и статуса клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

return arg2 & 1;

/* Возвращает направление движения */

void mouse_motion(deltax, deltay)

char *deltax, *deltay;

int fnum, arg2, arg3, arg4;

fnum = 11; /* получить направление движения */

cmouses( &fnum, &arg2, &arg3, &arg4);

if(arg3>0) *deltax = RIGHT;

else if(arg3<0) *deltax = LEFT;

else *deltax = NOT_MOVED;

if(arg4>0) *deltay = DOWN;

else if(arg4<0) *deltay = UP;

else *deltay = NOT_MOVED;

/* Установить координаты курсора "мыши" */

void set_mouse_position(x, y)

int x, y;

int fnum, arg2;

fnum = 4; /* установка позиции */

cmouses(&fnum, &arg2, &x, &y);

/* Возвращает координаты курсора "мыши" */

void mouse_position(x, y)

int *x, *y;

int fnum, arg2, arg3, arg4;

fnum = 3; /* получить позицию и статус клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

*x = arg3;

*y = arg4;

/* Установка "мыши" в исходное состояние */

void mouse_reset()

int fnum, arg2, arg3, arg4;

fnum = 0; /* Установка "мыши" в исходное состояние */

cmouses( &fnum, &arg2, &arg3, &arg4);

if(fnum!=-1)

printf("Аппаратные или программные средства поддержки ");

printf("'мыши' не инсталированы");

exit(1);

if(arg2!=2)

printf("Разрешено использование только двухклавишной ");

printf("'мыши'");

exit(1);

Перед использованием этой программы в командной строке DOS специфицируйте видеорежим, в котором вы желаете работать. Программа будет сообщать вам о направлении перемещения "мыши",а также о нажатых клавишах "мыши". Вдобавок ко всему, нажатие правой клавиши будет вызывать отображение текущей позиции Х, У. Нажатие обеих клавиш вызовет завершение программы. Попробуйте выполнить эту программу в различных видеорежиах и обратите внимание на возникающие при этом эффекты.

Ввод информации с помощью "мыши" в программе рисования.

Итак, вы теперь готовы к тому, чтобы разработать подпрограммы, позволяющие с помощью "мыши" управлять программой рисования. Интерфейс с "мышью" может быть добавлен в существующие подпрограммы управления, что, естественно, будет более предпочтительно, чем разработка новых подпрограмм или модификация существующих.

Такой путь выгоден прежде всего тем, что функциональны возможности клавиш управления курсором сохраняются на все 100 процентов, и пользователь в каждой конкретной ситуации может выбрать наиболее подходящее устройство для ввода данных (клавиатура или "мышь").

Прежде, чем "мышь" будет включена как устройство ввода программу рисования, необходимо разработать две подпрограммы,

учитывающие специфику "мыши". Первая подпрограмма - wait_on()

позволяет реализовать процесс ожидания отпускания (освобождения)

специфицированной клавиши пользователем. Анализ подобного рода

имеет весьма большое значение, так как соответствующие прерывания

генерируются постоянно, пока клавиша не нажата. (Однако

невозможно обеспечить такое мгновенное нажатие на клавишу, в

результате которого сформировалось бы лишь одно прерывание). Во

многих подпрограммах наоборот важно избежать такой ситуации и

поэтому в них каждое нажатие на клавишу генерирует (точнее будет

сказать кажется, что генерирует) только одно прерывание за то

время, пока клавиша нажата. В соответствии с этим, ваша программа

должна обращаться к фунции wait_on(), представленной ниже,

непосредственно перед выполнением и после того, когда нажата

соответствующая клавиша.

/* Возвращает 1, если специфицированная клавиша не нажата */

void wait_on(button)

int button;

if(button== LEFTB)

while(leftb_pressed());

else

while(rightb_pressed());

Макросы LEFTB и RIGHTB, представленные ниже, используются при обращении к wait_on().

#define LEFTB 1

#define RIGHTB 2

Второй необходимой вам функцией является mouse_menu(). Эта функция отображает однострочное меню и позволяет пользователю осуществлять выбор из него элементов путем перемещения "мыши" на плоскости (и, соответственно, курсора "мыши" по экрану) и нажатия любой клавиши "мыши". Эта функция может работать только в 4

графическом режиме. Функции передается двумерный массив символов,

который содержит элементы меню (которые может выбрать

пользователь), значение каждого элемента меню (его код),а также

координаты Х и У отображения меню на экране. Массив символов

определяет максимальную длину каждого элемента меню в 19

символов. Функция возвращает в качестве результата номер

выбранного пользователем элемента меню, начиная с 0, или -1, если

пользователь не выбрал ни один из элементов меню. Когда функция

начинает свою работу, то она вначале вычисляет длину в пикселах

(элементах растра) каждого элемента меню, после чего резервирует

пространство по начальной и конечной точке растра для каждого

элемента меню, одновременно запоминая эту информацию в массиве

len. (В четвертом графическом режиме каждый символ имеет высоту в

8 точек растра и ширину в 16 точек растра.) После этих вычислений, функция переходит в состояние ожидаения прерывания от клавиш "мыши". При этом осуществляется анализ нажата или нет клавиша "мыши" в момент нахождения ее курсора в области меню, и, если да, то в месте расположения какого элемента меню. Функция mouse_menu() приведена ниже.

/* Отображает однострочное меню для "мыши" и возвращает

код выбранного пользователем элемента меню */

mouse_menu(count, item, x, y)

int count; /* количество элементов меню */

char item[][20]; /* элементы меню */

int x, y; /* позиции отображения */

int i, len[MENU_MAX][2], t;

int mousex, mousey;

goto_xy(x, y);

t = 0;

for(i=0; i<count; i++)

printf("%s ", item[i]);

len[i][0] = t;

/* каждый символ имеет ширину в 16 точек растра */ len[i][1] = t + strlen(item[i])*16;

t = len[i][1] + 32; /* добавляется два пробела между элементами меню */

/* ожидание выбора пользователем элемента меню */

do

if(rightb_pressed() || leftb_pressed()) break;

while(!kbhit());

/* ожидание нажатия клавиши */

while(rightb_pressed() || leftb_pressed());

/* получить текущую позицию курсора "мыши" */

mouse_position(&mousex, &mousey);

/* анализируется, находится ли курсор в пределах меню */

if(mousey>=0 && mousey<8) /* символ имеет высоту

8 точек растра */ for(i=0; i<count; i++)

if(mousex>len[i][0] && mousex<len[i][1])

return i;

return i;

returtn -1; /* выбор из меню не осуществлялся */


Глава 10. Создание коммерческих программ.

В данной главе мы разработаем и опишем программы, которые могут быть использованы для создания наиболее удобного вида представления коммерческих данных - диаграмм. Возможность получить числовую информацию в визуальной форме часто оказывается очень полезной. Как вы вскоре убедитесь, создание диаграмм не представляет таких трудностей, как это первоначально кажется.

Глава X начинается описанием простого инструментария, позволяющего строить диаграммы. Вторая часть главы содержит описания, как этот инстументарий может быть использован для построения простых, но полезных программ, которые позволяют выводить несколько диаграмм одновременно.

Примеры в главе написаны для компьютеров семейства РС с цветным графическим адаптером. Используется четвертый видеорежим, так как он поддерживает все виды цветных адаптеров. Однако, вы можете легко изменить предложенные функции для того, чтобы работать с другими типами адаптеров.

Нормализация данных.

Перед разработкой программы отображения данных на экране вам следует уяснить, как численные значения переводятся в соответствующие координаты экрана. Как вы помните, размерность экрана в четвертом видеорежиме 320*200, причем 320 - горизонтальная размерность и 200 - вертикальная. Учитывая, что диаграммы изображаются вертикальными полосами, данные должны быть преобразованы таким образом, чтобы они принимали значения в диапазоне от 0 до 199. Данный процесс преобразования называется нормализацией.

Чтобы нормализовать значение, необходимо умножить его на некоторый коэффициент, гарантирующий получение результата в диапазоне размера экрана. Для определения коэффициента, необходимо знать максимальное и минимальное значения чисел, выводимых в виде диаграммы. Для определения подходящего коэффициента, необходимо вычесть минимальное значение из максимального и поделить вертикальную размерность экрана на полученную разность. Иными словами, для 4-го видеорежима нормирующий множитель определяется по формуле:

нормирующий_множитель = 200 / (мах - min)

Таким образом, каждый элемент данных нормализуется по формуле:

нормализованное_данное = необработанное_данное * норм_множитель

Разработка функций построения диаграмм.

Прежде, чем разрабатывать функцию, рисующую диаграммы, необходимо точно определить, что она будет делать. Во-первых, она должна выполнять свою главную задачу - выводить данные в виде диаграмм. Функция должна допускать использование в качестве входного параметра массива чисел с плавающей точкой и преобразовывать их в нормализованные целые эквиваленты. Программа должна быть реентерабельной и позволять рисовать несколько диаграмм одновременно. Функция должна также содержать аргумент, определяющий расстояние между диаграммами, соответствующими разным последовательностям данных, и, наконец, она должна позволять определять толщину линий диаграммы.

Программа функции bargraph(), приведенная ниже,

удовлетворяет этим требованиям.

/* Вывод диаграммы */

void bargraph(data,num,offset,min,max,width)

double *data; /* массив данных */

int num; /* количество элементов в массиве */

int offset; /* расстояние между диаграммами */

int min,max; /* мин. и мак. выводимые значения */

int width; /* толщина линий */

int y,t,incr;

double norm_data,norm_ratio,spread;

char s[80];

static int color = 0;

int tempwidth;

/* всегда используйте различные цвета */

color++;

if ( color > 3 ) color = 1;

/* определение нормирующего множителя */

spread = (double)max-min;

norm_ratio = 180/spread;

incr = 280/num;/* определение промежутка между значениями */

tempwidth = width;

for (t=0;t<num;++t)

norm_data = data[t];

/* подгонка отрицательных значений */

norm_data = norm_data-(double)min;

norm_data *= norm_ratio; /* нормирование */

y = (int)norm_data; /* преобразование типа */

do

Line(179,((t*incr)+20+offset+width),179-y,

((t*incr)+20+offset+width),color);

width--;

while(width);

width = tempwidth;

Давайте тщательно разберем данную программу. Функция bargraph() получает через входные параметры: массив данных, число элементов в массиве, расстояние между диаграммами (для случая одновременного вывода нескольких диаграмм), минимальное и максимальное значения данных и ширину линий диаграмм (ширина линии задается в единицах растра). Статическая переменная color определяет новый цвет при повторных обращениях к bargraph(). Таким образом, различные последовательности данных при их одновременном выводе будут изображены диаграммами различного цвета. При вычислении нормирующего множителя вместо максимальной высоты экрана (200 для 4-го видеорежима) использовано меньшее число - 180, что в последующем позволит использовать две строки экрана для вывода поясняющей информации. Обычно удобнее, если диаграмма полностью занимает экран независимо от количества выводимых чисел. Например, диаграмма, отражающая малые наборы данных, выглядит более привлекательной, если она занимает весь экран, а не совокупность сбившихся в кучу вертикальных полос в одном из углов экрана. Для размещения диаграммы относительно ширины экрана последняя (здесь также целесообразнее использовать меньшее число 280 вместо 300) делится на количество выводимых элементов, полученный результат затем используют при определении горизонтальных координат стержней диаграммы. В конце программы выполняется циклическая нормализация данных и вычерчивание линий заданной толщины с указанным смещением.

Функция bargraph() - ключевая функция, но это только одно из многих средств, позволяющих вам рисовать диаграммы почти любого вида. Основные из этих средств вы узнаете в процессе дальнейшего изложения материала.

Вычерчивание линии нулевого уровня.

Диаграмма будет выглядеть более привлекательной и наглядной, если вдоль нижнего края вычертить линию нулевого уровня. Программа функции grid(), представленная в данном разделе, служит именно для этих целей.

/* Вывод линии нулевого уровня */

void grid(min,max)

int min,max;

register int t;

goto_xy(22,0); printf("%d",min);

goto_xy(0,0); printf("%d",max);

line(180,10,180,300,1);

Вы видите, что функция grid() так же, как и bargraph() оставляет внизу две строки для вывода поясняющих меток и другой справочной информации.

Вывод меток элементов диаграмм.

Часто пользователю необходимо помечать значения, выводимые диаграммой. Например, на диаграмме, показывающей изменение прибыли корпорации за пять лет, целесообразно каждый стержень диаграммы пометить соответствующим годом. Конечно, вы всегда можете это сделать вручную, используя функции goto_xy() и printf(); функция label(), представленная ниже, освободит вас от этой рутинной работы, она автоматически выводит необходимые метки в нужном месте экрана. Входными параметрами функции label() являются: массив меток и их количество. Длина каждой метки ограничена 20 символами ( включая указатель конца ), но это не является жестким ограничением и при небходимости вы можете легко изменить максимальную длину меток.

/* Вывод меток на экран */

void label(str,num)

char str[][20]; /* массив меток */

int num; /* количество меток */

int i,j,inc;

inc = 38/num;

i = 2; /* определение начальной точки */

for (j=0;j<num;j++)

goto_xy(23,i);

printf(str[j]);

i += inc;

Вычерчивание вспомогательных линий.

В некоторых случаях полезно выводить горизонтальные полосы для сравнения высот стержней диаграммы. Так как сплошные линии могут отвлекать пользователя, то для этой цели лучше использовать пунктирные линии. Функция hashlines(), приведенная ниже, рисует требуемые пунктирные линии.

/* Вывод пунктирных линий */

void hashlines()

int i,j;

for (i=10;1<180;i+=10)

for (j=10;j<300;j+=5)

mempoint(i,j,3); /* одна точка на каждые 5 единиц

растра */

Вывод надписей.

При одновременном выводе нескольких наборов в виде диаграмм полезно определить цвет диаграммы, соответствующий каждому набору. Это можно сделать, например, выдав надпись, содержащую наименование набора и используемый для него цвет диаграммы. Функция legend(), приведенная здесь, выводит наименования наборов и прямоугольник соответствующего цвета, в качестве входных параметров она использует список наименований и их количество. Функция legend() использует функцию fill_box(), описанную ранее, для вывода цветного прямоугольника.

/* Вывод надписи */

void legend(names,num)

char names[][20];

int num; /* количество наименований */

int color = 1,i,j;

goto_xy(24,0); /* надпись производится в последней строке */

j = 0;

for (i=0;i<num;i++)

/* вывод наименования */

printf("%s ",names[i]);

/* определение координаты цветного прямоугольника. В 4

режиме каждому литерному символу отводится 8 единиц

растра (в ширину) */

j += strlen(names[i]) * 8 + 4;

fill_box(192,j,198,j+12,color);

j += 28; /* продвижение к следующему полю вывода */

color ++;

if ( color>3 ) color = 1;

_________________________________________________________________

Графический рисунок на стр 355 не может быть воиспроизведен имеющимися средствами. (Ред. перевода И.Бычковский.)

_________________________________________________________________

Рис.10-1. Результат работы программы построения диаграмм

Простейшая программа вывода диаграмм.

Следующая программа показывает все описанные функции в действии. Результат ее работы представлен на рис.10-1. Программа выводит среднюю стоимость акций трех мнимых корпораций за пять лет.

/* Программная демонстрация построения диаграмм */

#include "dos.h"

void bargraph(),mode(),mempoint();

void line(),goto_xy(),grid(),label();

void hashlines(),legend(),read_cursor_xy();

void palette(),color_puts(),fill_box();

main()

double widget[] =

10.1,20,30,35.34,50

;

double global[] =

19,20,8.8,30,40

;

double tower[] =

25.25,19,17.4,33,29

;

int min,max;

char n[][20] =

"widget",

"global",

"tower"

;

char lab[][20] =

"1983",

"1984",

"1985",

"1986",

"1987"

;

mode(4); /* выбор режима 320*200 */

palette(0);

grid(0,50); /* построение линии нулевого уровня */

hashlines(); /* вывод пунктирных линий */

label(lab,5); /* вывод чисел */

legend(n,3); /* вывод надписей */

/* вывод курса акций трех кампаний */

bargraph(widget,5,0,0,50,4);

bargraph(global,5,10,0,50,4);

bargraph(tower,5,20,0,50,4);

getch();

mode(3);

/* Вывод линии нулевого уровня диаграммы */

void grid(min,max)

int min,max;

register int t;

goto_xy(22,0); printf("%d",min);

goto_xy(0,0); printf("%d",max);

line(180,10,180,300,1);

/* вывод меток на экран */

void label(str,num)

char str[][20]; /* массив меток */

int num; /* количество меток */

int i,j,inc;

inc = 38/num;

i = 2; /* определение начальной точки */

for (j=0;j<num;j++)

goto_xy(23,i);

printf(str[j]);

i += inc;

/* Вывод пунктирных линий на экран */

void hashlines()

int i,j;

for (i=10;1<180;i+=10)

for (j=10;j<300;j+=5)

mempoint(i,j,3); /* одна точка на каждые 5 единиц

растра */

/* вывод надписи */

void legend(names,num)

char names[][20];

int num; /* количество наименований */

int color = 1,i,j;

goto_xy(24,0); /* надпись производится в последней строке */

j = 0;

for (i=0;i<num;i++)

/* вывод наименования */

printf("%s ",names[i]);

/* определение координаты цветного прямоугольника. В 4

режиме каждому литерному символу отводится 8 единиц

растра ( в ширину ) */

j++ = strlen(names[i]*8+4);

fill_box(192,j,198,j+12,color);

j++ = 28; /* продвижение к следующему полю вывода */

color ++;

if ( color>3 ) color = 1;

/* Вычерчивание диаграммы */

void bargraph(data,num,offset,min,max,width)

double *data; /* массив данных */

int num; /* количество элементов в массиве */

int offset; /* расстояние между диаграммами */

int min,max; /* минимальное и максимальное выводимые значения */

int width; /* толщина линий */

int y,t,incr;

double norm_data,norm_ratio,spread;

char s[80];

static int color = 0;

int tempwidth;

/* всегда используйте различные цвета */

color++;

if ( color > 3 ) color = 1;

/* определение нормирующего множителя */

spread = (double)max-min;

norm_ratio = 180/spread;

incr = 280/num; /* определение промежутка между значениями*/

tempwidth = width;

for (t=0;t<num;++t)

norm_data = data[t];

/* подгонка отрицательных значений */

norm_data = norm_data-(double)min;

norm_data *= norm_ratio; /* нормирование */

y = (int)norm_data; /* преобразование типа */

do

line(179,((t*incr)+20+offset+width),179-y,

((t*incr)+20+offset+width),color);

width--;

while(width);

width = tempwidth;

/* Вывод линии заданного цвета, используя базовый алгоритм

Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* вычисление расстояний по обоим направлениям */

delta_x = endx - startx;

delta_y = endy - starty;

/* определение направлений увеличения координат, нулевое

увеличение соответствует либо вертикальной, либо

горизонтальной линии */

if ( delta_x > 0 ) incx = 1 ;

else if (delta_x == 0 ) incx = 0;

else incx = -1;

if ( delta_y > 0 ) incy = 1 ;

else if (delta_y == 0 ) incy = 0;

else incy = -1;

/* определение максимума изменения координат */

delta_x = abs(delta_x);

delta_y = abs(delta_y);

if ( delta_x > delta_y ) distance = delta_x;

else distance = delta_y;

/* вычерчивание линии */

for (t=0;t<=distance+1;t++)

mempoint(startx,starty,color);

x+= delta_x;

y+= delta_y;

if (x>distance)

x-=distance;

startx+=incx;

if (y>distance)

y-=distance;

starty+=incy;

/* наполнение прямоугольника заданным цветом */

void fill_box(startx,starty,endx,endy,color_code)

int startx, starty, endx, endy, color_code;

register int i,begin,end;

begin = startx < endx ? startx : endx;

end = startx > endx ? startx : endx;

for (i=begin;i<=end;i++)

line(i,starty,i,endy,color_code);

/* запись точки в CGA/EGA память */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* xor - цвет или наложение */

char far *ptr = (char far *) 0xB8000000; /* указатель на

CGA */ bit_mask.i = 0xFF3F; /* 11111111 00111111 в двоичном коде */

/* контроль координат для 4 режима */

if (x<0 || x>199 || y<0 || y>319) return;

xor = color_code & 128; /* проверка установки режима xor */

color_code = color_code & 127; /* маска 7 старших бит */

/* установка bit_mask и color_code в правильное положение */

bit_position = y%4;

color_code <<= 2*(3-bit_position);

bit_mask.i >>= 2*bit_position;

/* поиск соответствующего байта в памяти экрана */

index = x*40 + (y>>2);

if (x%2) index+=8152; /* если нечетный, использовать второй

байт */

/* запись цвета */

if (!xor) /* режим наложения */

t = *(ptr + index) & bit_mask.c[0];

*(ptr + index) = t | color_code;

else /* режим xor */

t = *(ptr + index) | (char)0;

*(ptr + index) = t | color_code;

/* установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

/* установка курсора в координаты x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = y; /* горизонтальная координата */

r.h.dh = x; /* вертикальная координата */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

/* установка цветов диаграмм */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4 режима */

r.h.bl = pnum;

r.h.ah = 11; /* установка функции цвета */

int86(0x10,&r,&r);

Программа вычерчивания диаграмм.

Вы можете использовать описанные функции для построения программы создания диаграмм. Программа позволяет пользователю вводить количество наборов данных, количество элементов в каждом наборе, наименования и метки соответствующих данных, а также толщину линий и расстояния между диаграммами. После ввода указанных данных программа автоматически вычерчивает диаграмму. Вы также можете написать программу сохранения построенной диаграммы в файле для ее дальнейшего использования.

Главная программа.

Здесь приводится основная функция main(), описывающая алгоритм построения диаграмм и содержащая несколько макросов.

#define MAX_SETS 3

#define MAX_ENTRIES 50

#define MAX_LABELS 20

#define MAX_NAMES 20

main()

double v[MAX_SETS][MAX_ENTRIES]; /* размещение данных */

int num_entries;

int num_sets;

int min,max,i;

int lines,offset;

char save = 0; /* признак сохранения диаграммы */

char names[MAX_NAMES][20];

char lab[MAX_LABELS][20];

/* считывание данных */

enter(v,&num_entries,&num_sets);

/* поиск минимального и максимального значения */

min_max(v,num_entries,num_sets,&min,&max);

/* ввод наименований данных */

get_names(names,num_sets);

/* ввод меток для диаграммы */

get_labels(lab,num_entries);

/* ввод толщины линии */

lines = get_line_size();

/* ввод интервала между диаграммами */

offset = get_offset();

/* сохранить диаграмму в файле ? */

printf(" сохранить диаграмму в файле ? (y/n) ");

if (tolower(getche()) == 'y') save = 1;

mode(4); /* графический режим 320*200 */

palette(0);

grid(min,max); /* вывод линии нулевого уровня */

hashlines(); /* вывод пунктирных линий */

label(lab,num_entries); /* вывод меток диаграммы */

legend(names,num_sets); /* вывод пояснительных надписей */

/* вывод значений в виде диаграммы */

for (i=0;i<num_sets;i++)

bargraph(v[i],num_entries,i*offset,min,max,lines);

if (save) save_pic();

getch();

mode(3);

Как вы видите, функция main() начинается описанием переменных, значения которых устанавливает пользователь. Массив v определен достаточно большим, чтобы содержать до трех наборов данных до 50 элементов каждый. (Эти размеры являются произвольными и при желании вы можете их изменить.) Затем функция считывает выводимые пользователем в виде диаграмм данные и определяет минимальное и максимальное значение данных. После этого на экран выводятся линия нулевого уровня, пунктирные линии уровня, метки диаграммы и наименование наборов. В завершение вычерчивается сама диаграмма. Перед выходом происходит сохранение диаграммы при помощи функции save_pic(). Давайте рассмотрим некоторые используемые в программе main() функции, которые не входят в описанные выше инструментарии построения диаграмм.

Функция enter().

Приведенная здесь функция enter() использует в качестве своих параметров адрес массива, в котором будут размещены данные, и адреса переменных для размещения числа количества элементов в наборе и числа самих наборов. Функция начинает свою работу с запроса у пользователя количества наборов данных и затем количества элементов данных в каждом наборе. После получения этой информации производится считывание данных для каждого набора.

/* Считывание данных */

enter(v,entries,sets)

double v[][MAX_ENTRIES]; /* массив данных */

int *entries; /* количество элементов в каждом наборе данных */

int *sets; /* количество наборов данных */

int i,j,count,num;

char s[80];

printf("Введите число наборов данных (от 1 до %d)",MAX_SETS);

scanf("%d%c",&count,&j);

if (count>MAX_SETS) count = MAX_SETS; /* выход за границы

массива */

*sets = count;

printf("Введите количество элементов (от 1 до %d) ",MAX_ENTRIES);

scanf("%d%c",&num,&j);

if (num>MAX_SETS) num = MAX_ENTRIES; /* выход за границы

массива */

*entries = num;

j = 0;

/* считывание значений данных */

while((j<count))

printf("Набор данных %d\n",j+1);

for (i=0;i<num;i++)

printf("%d:",i+1);

gets(s);

sscanf(s,"%lf",&v[j][i]);

j++;

return count;

Функция min_max().

Так как функция bargraph() использует максимальное и минимальное значения выводимых данных, то нам потребуется специальная функция для определения этих значений. Необходимо также отметить, что эта функция должна не просто определять минимальное и максимальное значения набора данных, а находить наименьшее минимальное и наибольшее максимальное значения для нескольких наборов данных, что обеспечит соответствие при одновременном построении сразу нескольких диаграмм. Функция min_max(), приведенная здесь, вместе с двумя внутренними функциями удовлетворяет этому требованию.

/* Поиск наименьшего минимума и наибольшего максимума

среди всех наборов данных */

void min_max(v,entries,sets,min,max)

double v[][MAX_ENTRIES]; /* значения */

int entries; /* количество входов для каждого набора

данных */

int sets; /* количество наборов данных */

int *min,*max; /* возвращает минимальное и максимальное

значение */

int i,j;

int tmin,tmax;

*min = *max = 0;

for (i=0;i<sets;i++)

tmax = getmax(v[i],entries);

tmin = getmin(v[i],entries);

if (tmax>*max) *max = tmax;

if (tmin <*min) *min = tmin;

/* Возврат максимального значения данных */

getmax(data,num)

double *data;

int num;

int t,max;

max = (int)data[0];

for (t=1;t<num;++t)

if (data[t]>max) max = (int)data[t];

return max;

/* Возврат минимального значения данных */

getmin(data,num)

double *data;

int num;

int t,min;

min = (int)data[0];

for (t=1;t<num;++t)

if (data[t]<min) min = (int)data[t];

return min;

Полный текст программы вычерчивания диаграмм.

Полный текст программы вычерчивания диаграмм представлен ниже.

/* Программа генерации диаграмм */

#include "dos.h"

#include "stdio.h"

#define MAX_SETS 3

#define MAX_ENTRIES 50

#define MAX_LABELS 20

#define MAX_NAMES 20

void bargraph(),mode(),mempoint();

void line(),goto_xy(),grid(),label();

void hashlines(),legend(),read_cursor_xy();

void palette(),color_puts(),fill_box();

void get_labels(),get_names(),min_max();

void save_pic();

main()

double v[MAX_SETS][MAX_ENTRIES]; /* размещение данных */

int num_entries;

int num_sets;

int min,max,i;

int lines,offset;

char save = 0; /* признак записи диаграммы */

char names[MAX_NAMES][20];

char lab[MAX_LABELS][20];

/* считывание данных */

enter(v,&num_entries,&num_sets);

/* поиск минимального и максимального значения */

min_max(v,num_entries,num_sets,&min,&max);

/* ввод наименований данных */

get_names(names,num_sets);

/* ввод меток для диаграммы */

get_labels(lab,num_entries);

/* ввод толщины линии */

lines = get_line_size();

/* ввод интервала между диаграммами */

offset = get_offset();

/* сохранить диаграмму в файле ? */

printf(" сохранить диаграмму в файле ? (y/n) ");

if (tolower(getche()) == 'y') save = 1;

mode(4); /* графический режим 320*200 */

palette(0);

grid(min,max); /* вывод линии нулевого уровня */

hashlines(); /* вывод пунктирных линий */

label(lab,num_entries); /* вывод меток диаграммы */

legend(names,num_sets); /* вывод пояснительных надписей */

/* вывод значений в виде диаграммы */

for (i=0;i<num_sets;i++)

bargraph(v[i],num_entries,i*offset,min,max,lines);

if (save) save_pic();

getch();

mode(3);

/* считывание данных */

enter(v,entries,sets)

double v[][MAX_ENTRIES]; /* массив данных */

int *entries; /* количество элементов данных в каждом наборе

данных */

int *sets; /* количество наборов данных */

int i,j,count,num;

char s[80];

printf("Введите число наборов данных (от 1 до %d)",MAX_SETS);

scanf("%d%c",&count,&j);

if (count>MAX_SETS) count = MAX_SETS; /* выход за границы

массива */

*sets = count;

printf("Ведите число элементов данных (от 1 до %d)",MAX_ENTRIES);

scanf("%d%c",&num,&j);

if (num>MAX_ENTRIES) num = MAX_ENTRIES; /* выход за границы

массива */

*entries = num;

j = 0;

/* считывание значений */

while((j<count))

printf(" Набор данных %d\n",j+1);

for (i = 0;i<num;i++)

printf("%d:",i+1);

gets(s);

sscanf(s,"%lf",&v[j][i]);

j++;

return count;

/* Ввод имен наборов */

void get_names(n,num)

char n[][20]; /* массив для имен */

int num; /* число наборов */

int i;

for (i=0;i<num;i++)

printf(" Введите имя: ");

gets(n[i]);

/* Ввод метки каждого входа */

void get_labels(l,num)

char l[][20]; /* массив для меток */

int num; /* число входов */

int i;

for (i=0;i<num;i++)

printf(" Введите имя метки: ");

gets(l[i]);

/* Ввод интервала между диаграммами в единицах растра */

get_offset()

int i;

printf(" Введите интервал между диаграммами в единицах растра");

scanf("%d%*c",&i);

return i;

/* Ввод толщины диаграмм в единицах растра */

get_line_size()

int i;

printf("Введите толщину диаграммы в единицах растра : ");

scanf("%d",&i);

return i;

/* Вывод линии нулевого уровня диаграммы */

void grid(min,max)

int min,max;

register int t;

goto_xy(22,0); printf("%d",min);

goto_xy(0,0); printf("%d",max);

line(180,10,180,300,1);

/* Вывод меток на экран */

void label(str,num)

char str[][20]; /* массив меток */

int num; /* количество меток */

int i,j,inc;

inc = 38/num;

i = 2; /* определение начальной точки */

for (j=0;j<num;j++)

goto_xy(23,i);

printf(str[j]);

i += inc;

/* Вывод пунктирных линий на экран */

void hashlines()

int i,j;

for (i=10;1<180;i+=10)

for (j=10;j<300;j+=5)

mempoint(i,j,3); /* одна точка на каждые 5 единиц

растра */

/* Вывод надписи */

void legend(names,num)

char names[][20];

int num; /* количество наименований */

int color = 1,i,j;

goto_xy(24,0); /* надпись производится в последней строке */

j = 0;

for (i=0;i<num;i++)

/* Вывод наименования */

printf("%s ",names[i]);

/* определение координаты цветного прямоугольника. В 4

режиме каждому литерному символу отводится 8 единиц

растра ( в ширину ) */

j += strlen(names[i]) * 8 + 4;

fill_box(192,j,198,j+12,color);

j += 28; /* продвижение к следующему полю вывода */

color ++;

if ( color>3 ) color = 1;

void bargraph(data,num,offset,min,max,width)

double *data; /* массив данных */

int num; /* количество элементов в массиве */

int offset; /* расстояние между диаграммами */

int min,max; /* минимальное и максимальное выводимые значения */

int width; /* толщина линий */

int y,t,incr;

double norm_data,norm_ratio,spread;

char s[80];

static int color = 0;

int tempwidth;

/* всегда используйте различные цвета */

color++;

if ( color > 3 ) color = 1;

/* определение нормирующего множителя */

spread = (double)max-min;

norm_ratio = 180/spread;

incr = 280/num; /* определение промежутка между значениями*/

tempwidth = width;

for (t=0;t<num;++t)

norm_data = data[t];

/* подгонка отрицательных значений */

norm_data = norm_data-(double)min;

norm_data *= norm_ratio; /* нормирование */

y = (int)norm_data; /* преобразование типа */

do

Line(179,((t*incr)+20+offset+width),179-y,

((t*incr)+20+offset+width),color);

width--;

while(width);

width = tempwidth;

/* поиск наименьшего минимума и наибольшего максимума

среди всех наборов данных */

void min_max(v,entries,sets,min,max)

double v[][MAX_ENTRIES]; /* значения */

int entries; /* количество входов для каждого набора

данных */

int sets; /* количество наборов данных */

int *min,*max; /* возвращает минимальное и максимальное

значение */

int i,j;

int tmin,tmax;

*min = *max = 0;

for (i=0;i<sets;i++)

tmax = getmax(v[i],entries);

tmin = getmin(v[i],entries);

if (tmax>*max) *max = tmax;

if (tmin <*min) *min = tmin;

/* Возврат максимального значения данных */

getmax(data,num)

double *data;

int num;

int t,max;

max = (int)data[0];

for (t=1;t<num;++t)

if (data[t]>max) max = (int)data[t];

return max;

/* Возврат минимального значения данных */

getmin(data,num)

double *data;

int num;

int t,min;

min = (int)data[0];

for (t=1;t<num;++t)

if (data[t]<min) min = (int)data[t];

return min;

/* Вывод линии заданного цвета, используя базовый алгоритм

Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* вычисление расстояний по обоим направлениям */

delta_x = endx - startx;

delta_y = endy - starty;

/* определение направлений увеличения координат, нулевое

увеличение соответствует либо вертикальной, либо

горизонтальной линии */

if ( delta_x > 0 ) incx = 1 ;

else if (delta_x == 0 ) incx = 0;

else incx = -1;

if ( delta_y > 0 ) incy = 1 ;

else if (delta_y == 0 ) incy = 0;

else incy = -1;

/* определение максимума изменения координат */

delta_x = abs(delta_x);

delta_y = abs(delta_y);

if ( delta_x > delta_y ) distance = delta_x;

else distance = delta_y;

/* вычерчивание линии */

for (t=0;t<=distance+1;t++)

mempoint(startx,starty,color);

x+= delta_x;

y+= delta_y;

if (x>distance)

x-=distance;

startx+=incx;

if (y>distance)

y-=distance;

starty+=incy;

/* наполнение прямоугольника заданным цветом */

void fill_box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

register int i,begin,end;

begin = startx < endx ? startx : endx;

end = startx > endx ? startx : endx;

for (i=begin;i<=end;i++)

line(i,starty,i,endy,color_code);

/* запись точки в CGA/EGA память */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* xor - цвет или наложение */

char far *ptr = (char far *) 0xB8000000; /* указатель на

CGA */ bit_mask.i = 0xFF3F; /* 11111111 00111111 в двоичном коде */

/* контроль координат для 4 режима */

if (x<0 || x>199 || y<0 || y>319) return;

xor = color_code & 128; /* проверка установки режима xor */

color_code = color_code & 127; /* маска 7 старших бит */

/* установка bit_mask и color_code в правильное положение */

bit_position = y%4;

color_code <<= 2*(3-bit_position);

bit_mask.i >>= 2*bit_position;

/* поиск соответствующего байта в памяти экрана */

index = x*40 + (y>>2);

if (x%2) index+=8152; /* если нечетный, использовать второй

банк */

/* запись цвета */

if (!xor) /* режим наложения */

t = *(ptr + index) & bit_mask.c[0];

*(ptr + index) = t | color_code;

else /* режим xor */

t = *(ptr + index) | (char)0;

*(ptr + index) = t ^ color_code;

/* установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

/* установка курсора в координаты x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = y; /* горизонтальная координата */

r.h.dh = x; /* вертикальная координата */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

/* установка цветов диаграмм */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4 режима */

r.h.bl = pnum;

r.h.ah = 11; /* установка функции цвета */

int86(0x10,&r,&r);

/* сохранение выведенного видеографика */

void save_pic()

char fname[80];

FILE *fp; register int i,j;

char far *ptr = (char far *) 0xB8000000; /* указатель на CGA память */

char far *temp;

unsigned char buf[14][80]; /* для размещения содержимого

экрана */

temp = ptr;

/* сохранение верхней части текущего экрана */

for (i=0;i<14;i++)

for (j=0;j<80;j+=2)

buf[i][j] = *temp; /* четный байт */

buf[i][j+1] = *(temp+8152); /* нечетный байт*/

*temp = 0;

*(temp+8152) = 0; /* чистка верхней части

экрана */

temp++;

goto_xy(0,0);

printf(" Имя файла : ");

gets(fname);

if (!(fp=fopen(fname,"wb")))

printf(" Невозможно открыть файл \n");

return;

temp = ptr;

/* восстановление верхней части экрана */

for (i=0;i<14;i++)

for (j=0;j<80;j+=2)

*temp = buf[i][j];

*(temp+8152) = buf[i][j+1];

temp++;

/* сохранение рисунка в файле */

for (i=0;i<8152;i++)

putc(*ptr,fp); /* четный байт */

putc(*(ptr+8152),fp); /* нечетный байт */

ptr++;

fclose(fp);

Отображение диаграмм на экране дисплея.

Если вы сохранили построенную диаграмму в файле, то всегда можете повторно получить изображение данной диаграммы. Для этой цели служит программа SHOW, описанная в данном разделе. Программа выводит диаграмму, находящуюся в файле, имя файла задается в виде аргумента команды. Например, чтобы вывести диаграмму, находящуюся в файле backlog, необходимо ввести команду

show backlog

Программа show использует функцию load_pic(),

предназначенную для изображения диаграмм на экране. (Вы также

можете использовать эту программу для отображения на экране

дисплея других графических образов, предварительно созданных и

записанных в файл.)

/* Простейшая программа восстановления графических образов */

#include "stdio.h"

#include "dos.h"

void load_pic(),mode(),palette(),goto_xy();

main(argc,argv)

int argc;

char *argv[];

if (argc != 2)

printf(" Обращение: показать <имя файла>");

exit(1);

mode(4);

palette(0);

load_pic(argv[1]);

getch();

mode(3);

/* загрузка графического изображения */

void load_pic(fname)

char *fname;

FILE *fp; register int i,j;

char far *ptr = (char far *) 0xB8000000; /* указатель на CGA память */

char far *temp;

unsigned char buf[14][80]; /* для размещения содержимого

экрана */

if (!(fp=fopen(fname,"rb")))

goto_xy(0,0);

printf(" невозможно открыть файл \n");

return;

/* загрузка изображения из файла */

for (i=0;i<8152;i++)

*ptr = getc(fp); /* четный байт */

*(ptr+8152) = getc(fp); /* нечетный байт */

ptr++;

fclose(fp);

/* установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

/* установка цветов диаграмм */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4 режима */

r.h.bl = pnum;

r.h.ah = 11; /* установка функции цвета */

int86(0x10,&r,&r);

/* установка курсора в координаты x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = y; /* горизонтальная координата */

r.h.dh = x; /* вертикальная координата */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

Некоторые интересные идеи по модификации программ.

Вы можете расширить описанные функции, введя параметры, задающие размеры и расположение диаграмм так, чтобы выводить диаграммы разных размеров в различных частях экрана. Например, может быть удобен вывод четырех малых диаграмм, каждая из которых расположена в одном из квадрантов экрана. Вы также можете изменить предложенные функции и работать с графическими режимами более высокой разрешающей способности.


Москва, 1989 г.

С О Д Е Р Ж А Н И Е

Предисловие ................................................... I- 1

Глава I. ИСЧЕЗАЮЩИЕ И ИЕРАРХИЧЕСКИЕ МЕНЮ

Что такое исчезающие и иерархические меню? .................... I- 4

Работа видеоадаптеров ......................................... I- 5

Доступ к экрану через BIOS .................................... I- 7

Использование int86() ......................................... I- 8

Сохранение части экрана ....................................... I- 9

Восстановление экрана ......................................... I-11

Создание исчезающих меню ...................................... I-12

Высвечивание меню ............................................. I-13

Высвечивание рамки ............................................ I-15

Ввод выбора пользователя ...................................... I-16

Функция popup() ............................................... I-19

Общий обзор ................................................... I-21

Прямой доступ к видео памяти .................................. I-27

Определение расположения видео памяти ......................... I-28

Изменение save_video() и restore_video() ...................... I-29

Создание иерархических окон ................................... I-37

Фреймы меню ................................................... I-38

Создание фрейма меню .......................................... I-39

Функция pulldown() ............................................ I-41

Восстановление экрана ......................................... I-42

Простая программа, использующая процедуру pulldown ............ I-43

Добавочные функции ............................................ I-52

Глава II. ВСПЛЫВАЮЩИЕ ОКНА

Теория всплывающих окон ...................................... II- 2

Оконные структуры ............................................ II- 3

Создание структуры окна ...................................... II- 4

Активирование и деактивирование окна ......................... II- 6

Оконные функции ввода/вывода ................................. II- 8

Функция позиционирования курсора в окне ...................... II- 9

Функция window_getche() ...................................... II-10

Функция window_gets() ........................................ II-12

Функция window_putchar() ..................................... II-13

Функция window_puts .......................................... II-15

Дополнительные функции управления экраном .................... II-16

Изменение размера и положения окна во время вып. программы ... II-18

Создание прикладных программ, использующих всплывающие окна .. II-22

Программа преобр. из десят. в шестнад. систему счисления ..... II-23

Калькулятор с четырьмя функциями ............................. II-24

Всплывающая записная книжка .................................. II-27

Совместное использование всех программ ....................... II-29

Модификации программ управления окнами ....................... II-48

Глава III. ПРОГРАММЫ, ОСТАЮЩИЕСЯ РЕЗИДЕНТНЫМИ

Что такое TSR-программа? .................................... III- 2

Прерывания в семействе процессоров 8086 ..................... III- 3

Прерывания против DOS и BIOS: Tревога в стране DOS .......... III- 4

Модификатор функций прерывания Турбо Си ..................... III- 5

Общий план TSR-программы .................................... III- 6

Использование прерывания печати экрана ...................... III- 7

Раздел инициализации ........................................ III- 8

Прикладная часть TSR-программы .............................. III-10

Использование прерывания по нажатию клавиши ................. III-22

Буфер символов, введенных с клавиатуры ...................... III-23

Функция инициализации ....................................... III-24

Прикладная часть TSR-программы .............................. III-25

Тайна 28-го прерывания ...................................... III-40

Проблемы при создании TSR-программ .......................... III-41

Глава IV. ГРАФИКА

Видеорежимы и цветовая палитра ............................... IV- 2

Запись точки растра .......................................... IV- 4

Работа адаптеров CGA/EGA в графическеом режиме ............... IV- 5

Вычерчивание линий ........................................... IV- 8

Изображение и закрашивание прямоугольников ................... IV-10

Вычерчивание окружностей ..................................... IV-11

Простейшая тестовая программы ................................ IV-13

Сохранение и загрузка графических изображений ................ IV-18

Дублирование части экрана .................................... IV-21

Вращение точки в плоскости экрана ............................ IV-23

Вращение обьекта ............................................. IV-25

Сборка подпрограмм ........................................... IV-33

Глава V. ВИДЕОИГРЫ

Спрайты ....................................................... V- 2

Поле игры ..................................................... V- 3

Мультипликация на экране ...................................... V- 4

Мультипликация спрайта ........................................ V-11

Организация данных в видеоиграх ............................... V-13

Контроль границ ............................................... V-14

Изменение цвета ............................................... V-15

Табло счета активного противника .............................. V-16

Разработка видеоигры .......................................... V-17

Тело главной программы ........................................ V-20

Программа генерации движения спрайта компьютера ............... V-24

Программа контроля касания спрайтов ........................... V-28

Полный текст программы игры TAG ............................... V-29

Некоторые соображения по возможной модификации программы ...... V-41

Глава VI. ИСПОЛЬЗОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА

Асинхронная последовательная передача данных ................. VI- 2

Стандарт RS-232 .............................................. VI- 4

Аппаратное подтверждение связи ............................... VI- 6

Проблемы передачи данных ..................................... VI- 7

Переполнение регистра-приемника .............................. VI- 8

Доступ к последовательному порту компьютера через BIOS ....... VI- 9

Инициализация порта .......................................... VI-10

Передача байтов .............................................. VI-13

Контроль состояния порта ..................................... VI-14

Прием байтов ................................................. VI-16

Передача файлов между компьютерами ........................... VI-18

Программное подтверждение связи .............................. VI-19

Семь и восемь бит данных ..................................... VI-20

Перекачка файла .............................................. VI-21

Прием файла .................................................. VI-24

Перекачка программы .......................................... VI-26

Использование средств перекачки программ ..................... VI-31

Дальнейшее совершенствование программы ....................... VI-32

Простейшая ЛВС ............................................... VI-33

Файловый сервер .............................................. VI-34

Загрузка удаленных файлов в узел сети ........................ VI-44

Хранение файлов .............................................. VI-48

Использование ЛВС ............................................ VI-52

Совершенствование ЛВС ........................................ VI-53

Глава VII. ИНТЕРПРЕТАТОРЫ ЯЗЫКА

Синтаксический разбор выражений ............................. VII- 2

Выражения ................................................... VII- 3

Лексемы ..................................................... VII- 5

Порядок построения выражений ................................ VII-10

Синтаксический анализатор выражений ......................... VII-12

Как анализатор обрабатывает переменные ...................... VII-19

Интерпретатор языка SMALL BASIC ............................. VII-20

Основной цикл работы анализатора ............................ VII-23

Команда присваивания значений ............................... VII-25

Команда PRINT ............................................... VII-26

Команда INPUT ............................................... VII-28

Команда GOTO ................................................ VII-29

Оператор IF ................................................. VII-32

Цикл FOR .................................................... VII-34

Оператор GOSUB .............................................. VII-37

Полный файл интерпретатора .................................. VII-39

Пример использования интерпретатора SMALL BASIC ............. VII-49

Расширение возможностей интерпретатора ...................... VII-51

Глава VIII. О МАНИПУЛИРОВАНИИ ЭКРАНОМ И ГЕНЕРАЦИИ ЗВУКА
Использование цвета в текстовом режиме ..................... VIII- 2
Атрибутный байт текстового режима .......................... VIII- 3
Отображение строки в определенном цвете .................... VIII- 5
Использование цвета ........................................ VIII- 8
Изменение размера курсора .................................. VIII- 9
Скроллинг части экрана ..................................... VIII-11
Простейшая демонстрационная программа ...................... VIII-13
Сохранение копии экрана в дисковом файле ................... VIII-18
А теперь добавим звук ...................................... VIII-20
Программируемый таймер 8253 ................................ VIII-21
Простейший способ проверки слуха ........................... VIII-23
Имитация звука сирены и взврывы ............................ VIII-25
Создание "космической музыки" .............................. VIII-27

Глава IX. ИНТЕРФЕЙС С "МЫШЬЮ"

Некоторые начальные сведения о "мыши" ........................ IX- 3

Виртуализация и реальный экран ............................... IX- 4

библиотека поддержки "мыши" .................................. IX- 5

Функции поддержки "мыши" верхнего уровня ..................... IX- 8

Простейшая демонстрационная программа ........................ IX-12

Ввод информации с помощью "мыши" в программе рисования ....... IX-17

Основной цикл работы программы ............................... IX-20

Определение объектов с помощью "мыши" ........................ IX-26

Полный текст модифицированной программы рисования ............ IX-29

Некоторые возм. расширения выполняемых функций программы ..... IX-50

Глава X. СОЗДАНИЕ КОММЕРЧЕСКИХ ДИАГРАММ

Нормализация данных ........................................... X- 2

Разработка функций построения диаграмм ........................ X- 3

Программа вычерчивания диаграмм ............................... X-13

Отображение диаграмм на экране дисплея ........................ X-26

Некоторые интересные идеи по модификации программ ............. X-28


Visual C++

Visual C++ для начинающих
1. Введение ( очень советую прочитать )
 2. Первая программа на C++, типы данных и их размер
3. Адресация в С++
4. Применение указателей в C++
5. Соглашение об именах
 6. Структуры в С++
7. Классы в С++
8. Пример использования MFC в Visual C++
9. Запись и считывание данных ( работа с файлами )
10. Использование таймера
11. Работа со стандартными ресурсами
12. Работа с библиотеками динамической компоновки (DLL)
13. Стандартные диалоговые панели
 14. Оптимизация вывода графики на экран
15. Создание собственных диалоговых окон
16. Работа с WinSocket
17. Простая программа, использующая MDI интерфейс
 18. Проигрывание Wave-файлов под MFC
 19. Создание собственных ActiveX элементов
 20. Использование класса CTabCtrl


Глава 1.

1. Введение.

Эти главы являются некоторым пособием для тех, кто хочет познакомиться с языком программирования C++ и Visual C++.

  • Некоторые сведения о Windows-программировании
    • Программная среда Windows
    • Основы программирования для Windows
    • Преимущества использования библиотеки MFC
  • Обзор средств Microsoft Developer Studio
    • Библиотека MFC
    • Архитектура приложения
    • Каркас приложения
    • Каркас приложений
    • Проект приложения
  • Использование средств разработки
    • Типы мастеров проектов
    • Преимущества мастеров проектов
    • Обзор возможностей ClassWizard
    • Имена, используемые в MFC

В связи с тем, что сегодня уровень сложности программного обеспечения очень высок, разработка приложений Windows с использованием только какого-либо языка программирования (например, языка C) значительно затрудняется. Программист должен затратить массу времени на решение стандартных задач по созданию многооконного интерфейса. Реализация технологии связывания и встраивания объектов - OLE - потребует от программиста еще более сложной работы.

Чтобы облегчить работу программиста практически все современные компиляторы с языка C++ содержат специальные библиотеки классов. Такие библиотеки включают в себя практически весь программный интерфейс Windows и позволяют пользоваться при программировании средствами более высокого уровня, чем обычные вызовы функций. За счет этого значительно упрощается разработка приложений, имеющих сложный интерфейс пользователя, облегчается поддержка технологии OLE и взаимодействие с базами данных.

Современные интегрированные средства разработки приложений Windows позволяют автоматизировать процесс создания приложения. Для этого используются генераторы приложений. Программист отвечает на вопросы генератора приложений и определяет свойства приложения - поддерживает ли оно многооконный режим, технологию OLE, трехмерные органы управления, справочную систему. Генератор приложений, создаст приложение, отвечающее требованиям, и предоставит исходные тексты. Пользуясь им как шаблоном, программист сможет быстро разрабатывать свои приложения.

Подобные средства автоматизированного создания приложений включены в компилятор Microsoft Visual C++ и называются MFC AppWizard. Заполнив несколько диалоговых панелей, можно указать характеристики приложения и получить его тексты, снабженные обширными комментариями. MFC AppWizard позволяет создавать однооконные и многооконные приложения, а также приложения, не имеющие главного окна, -вместо него используется диалоговая панель. Можно также включить поддержку технологии OLE, баз данных, справочной системы.

Конечно, MFC AppWizard не всесилен. Прикладную часть приложения программисту придется разрабатывать самостоятельно. Исходный текст приложения, созданный MFC AppWizard, станет только основой, к которой нужно подключить остальное. Но работающий шаблон приложения - это уже половина всей работы. Исходные тексты приложений, автоматически полученных от MFC AppWizard, могут составлять сотни строк текста. Набор его вручную был бы очень утомителен.

Нужно отметить, что MFC AppWizard создает тексты приложений только с использованием библиотеки классов MFC (Microsoft Foundation Class library). Поэтому только изучив язык C++ и библиотеку MFC, можно пользоваться средствами автоматизированной разработки и создавать свои приложения в кратчайшие сроки.

Некоторые сведения о программировании Windows-приложений

MFC – это базовый набор (библиотека) классов, написанных на языке С++ и предназначенных для упрощения и ускорения процесса программирования под Windows. Перед изучением библиотеки MFC и ее использованием для создания Windows-приложений, следует вспомнить, как работает сама Windows и каковы принципы взаимодействия программ с ней, какова структура типичной Windows-программы.

Программная среда Windows

Рассмотрим наиболее важные моменты работы Windows и принципы взаимодействия программ с ней.

Интерфейс вызовов функций в Windows

Благодаря данному интерфейсу доступ к системным ресурсам осуществляется через целый рад системных функций. Совокупность таких функций называется прикладным программным интерфейсом, или API (Application Programming Interfase). Для взаимодействия с Windows приложение запрашивает функции API, с помощью которых реализуются все необходимые системные действия, такие как выделение памяти, вывод на экран, создание окон и т.п.

Библиотека MFC инкапсулирует многие функции API. Хотя программам и разрешено обращаться к ним напрямую, все же чаще это будет выполняться через соответствующие функции-члены. Как правило, функции-члены либо аналогичны функциям API, либо непосредственно обращаются к нужной части интерфейса.

Библиотеки динамической загрузки (DLL)

Поскольку API состоит из большого числа функций, может сложиться впечатление, что при компиляции каждой программы, написанной для Windows, к ней подключается код довольно значительного объема. В действительности это не так. Функции API содержатся в библиотеках динамической загрузки (Dynamic Link Libraries, или DLL), которые загружаются в память только в тот момент, когда к ним происходит обращение, т.е. при выполнении программы. Рассмотрим, как осуществляется механизм динамической загрузки.

Динамическая загрузка обеспечивает ряд существенных преимуществ. Во-первых, поскольку практически все программы используют API-функции, то благодаря DLL-библиотекам существенно экономится дисковое пространство, которое в противном случае занималось бы большим количеством повторяющегося кода, содержащегося в каждом из исполняемых файлов. Во-вторых, изменения и улучшения в Windows-приложениях сводятся к обновлению только содержимого DLL-библиотек. Уже существующие тексты программ не требуют перекомпиляции.

Win16 или Win32

В настоящее время широко распространены две версии API. Первая называется Win16 и представляет собой 16-разрядную версию, используемую в Windows 3.1. Вторая, 32-разрядная версия, называется Win32 и используется в Windows 95 и Windows NT. Win32 является надмножеством для Win16 (т.е. фактически включает в себя этот интерфейс), так как большинство функций имеет то же название и применяется аналогичным образом. Однако, будучи в принципе похожими, оба интерфейса все же отличаются друг от друга. Win32 поддерживает 32-разрядную линейную адресацию, тогда как Win16 работает только с 16-разрядной сегментированной моделью памяти. Это привело к тому, что некоторые функции были модифицированы таким образом, чтобы принимать 32-разрядные аргументы и возвращать 32-разрядные значения. Часть из них пришлось изменить с учетом 32-разрядной архитектуры. Была реализована поддержка потоковой многозадачности, новых элементов интерфейса и прочих нововведений Windows.

Так как Win32 поддерживает полностью 32-разрядную адресацию, то логично, что целые типы данных (intergers) также объявлены 32-разрядными. Это означает, что переменные типа int и unsignerd будут иметь длину 32 бита, а не 16, как в Windows 3.1. Если же необходимо использовать переменную или константу длиной 16 бит, они должны быть объявлены как short. (дальше будет показано, что для этих типов определены независимые typedef-имена.) Следовательно, при переносе программного кода из 16-разрядной среды необходимо убедиться в правильности использования целочисленных элементов, которые автоматически будут расширены до 32 битов, что целочисленных элементов, которые автоматически будут расширены до 32 битов, что может привести к появлению побочных эффектов.

Другим следствием 32-разрядной адресации является то, что указатели больше не нужно объявлять как near и far. Любой указатель может получить доступ к любому участку памяти. В Windows 95 и Windows NT константы near и far объявлены (с помощью директивы #define)пустыми.

Интерфейс GDI

Одним из подмножеств API является GDI (Graphics Device Interfase – интерфейс графического устройства). GDI – это та часть Windows, которая обеспечивает поддержку аппаратно-независимой графики. Благодаря функциям GDI Windows-приложение может выполняться на самых различных компьютерах.

Многозадачность в Windows

Как известно, все версии Windows поддерживают многозадачность. В Windows 3.1 имеется только один тип многозадачности – основанный на процессах. В более передовых системах, таких как Windows 95 и Windows NT, поддерживается два типа многозадачности: основанный на процессах и основанный на потоках. Давайте рассмотрим их чуть подробнее.

Процесс – это программа, которая выполняется. При многозадачности такого типа две или более программы могут выполняться параллельно. Конечно, они по очереди используют ресурсы центрального процессора и с технической точки зрения, выполняются неодновременно, но благодаря высокой скорости работы компьютера это практически незаметно.

Поток – это отдельная часть исполняемого кода. Название произошло от понятия “направление протекания процесса”. В многозадачности данного типа отдельные потоки внутри одного процесса также могут выполняться одновременно. Все процессы имеют по крайней мере один поток, но в Windows 95 и Windows NT их может быть несколько.

Отсюда можно сделать вывод, что в Windows 95 и Windows NT допускается существование процессов, две или более частей которых выполняются одновременно. Оказывается, такое предположение верно. Следовательно, при работе в этих операционных системах возможно параллельное выполнение, как программ, так и отдельных частей самих программ. Это позволяет писать очень эффективные программы.

Есть и другое существенное различие между многозадачностями Windows 3.1 и Windows 95/NT. В Windows 3.1 используется неприоритетная многозадачность. Это означает, что процесс, выполняющийся в данный момент, получает доступ к ресурсам центрального процессора и удерживает их в течение необходимого ему времени. Таким образом, неправильно выполняющаяся программа может захватить все ресурсы процессора и не давать выполняться другим процессам. В отличие от этого в Windows 95 и Windows NT используется приоритетная многозадачность. В этом случае каждому активному потоку предоставляется определенный промежуток времени работы процессора. По истечению данного промежутка управление автоматически передается следующему потоку. Это не дает возможность программам полностью захватывать ресурсы процессора. Интуитивно должно быть понятно, что такой способ более предпочтителен.

Взаимодействие программ и Windows

Во многих операционных системах взаимодействие между системой и программой инициализирует программа. Например, в DOS программа запрашивает разрешение на ввод и вывод данных. Говоря другими словами, не- Windows-программы сами вызывают операционную систему. Обратного процесса не происходит. В Windows все совершенно наоборот: именно система вызывает программу. Это осуществляется следующим образом: программа ожидает получения сообщения от Windows. Когда это происходит, то выполняется некоторое действие. После его завершения программа ожидает следующего сообщения.

Windows может посылать программе сообщения множества различных типов. Например, каждый раз при щелчке мышью в окне активной программы посылается соответствующее сообщение. Другой тип сообщений посылается, когда необходимо обновить содержимое активного окна. Сообщения посылаются также при нажатии клавиши, если программа ожидает ввода с клавиатуры. Необходимо запомнить одно: по отношению к программе сообщения появляются случайным образом. Вот почему Windows-программы похожи на программы обработки прерываний: невозможно предсказать, какое сообщение появиться в следующий момент.

Основы программирования под Windows

Поскольку архитектура Windows-программ основана на принципе сообщений, все эти программы содержат некоторые общие компоненты. Обычно их приходится в явном виде включать в исходный код. Но, к счастью, при использовании библиотеки MFC это происходит автоматически; нет необходимости тратить время и усилия на их написание. Тем не менее, чтобы до конца разобраться, как работает Windows-программа, написанная с использованием MFC, и почему она работает именно так, необходимо в общих чертах понять назначение этих компонентов.

Функция WinMain()

Все Windows-программы начинают выполнение с вызова функции WinMain(). При традиционном методе программирования это нужно делать явно. С использованием библиотеки MFC такая необходимость отпадает, но функция все-таки существует.

Функция окна

Все Windows-программы должны содержать специальную функцию, которая не используется в самой программе, но вызывается самой операционной системой. Эту функцию обычно называют функцией окна, или процедурой окна. Она вызывается Windows, когда системе необходимо передать сообщение в программу. Именно через нее осуществляется взаимодействие между программой и системой. Функция окна передает сообщение в своих аргументах. Согласно терминологии Windows, функции, вызываемые системой, называются функциями обратного вызова. Таким образом, функция окна является функцией обратного вызова.

Помимо принятия сообщения от Windows, функция окна должна вызывать выполнение действия, указанного в сообщении. Конечно, программа не обязана отвечать на все сообщения, посылаемые Windows. Поскольку их могут быть сотни, то большинство сообщений обычно обрабатывается самой системой, а программе достаточно поручить Windows выполнить действия, предусмотренные по умолчанию.

В большинстве Windows-программ задача создания функции окна лежит на программисте. При использовании библиотеки MFC такая функция создается автоматически. В этом заключается одно из преимуществ библиотеки. Но в любом случае, если сообщение получено, то программа должна выполнить некоторое действие. Хотя она может вызывать для этого одну или несколько API-функций, само действие было инициировано Windows. Поэтому именно способ взаимодействия с операционной системой через сообщения диктует общий принцип построения всех программ для Windows, написанных как с использованием MFC, так и без нее.

Цикл сообщений

Как объяснялось выше, Windows взаимодействует с программой, посылая ей сообщения. Все приложения Windows должны организовать так называемый цикл сообщений (обычно внутри функции WinMain()). В этом цикле каждое необработанное сообщение должно быть извлечено из очереди сообщений данного приложения и передано назад в Windows, которая затем вызывает функцию окна программы с данным сообщением в качестве аргумента. В традиционных Windows-программах необходимо самостоятельно создавать и активизировать такой цикл. При использовании MFC это также выполняется автоматически. Однако важно помнить, что цикл сообщений все же существует. Он является неотъемлемой частью любого приложения Windows.

Процесс получения и обработки сообщений может показаться чересчур сложным, но тем не менее ему должны следовать все Windows-программы. К счастью, при использовании библиотеки MFC большинство частных деталей скрыты от программиста, хотя и продолжают неявно присутствовать в программе.

Класс окна

Как будет показано дальше, каждое окно в Windows-приложении характеризуется определенными атрибутами, называемыми классом окна. (Здесь понятие “класс” не идентично используемому в С++. Оно, скорее, означает стиль или тип.) В традиционной программе класс окна должен быть определен и зарегистрирован прежде, чем будет создано окно. При регистрации необходимо сообщить Windows, какой вид должно иметь окно и какую функцию оно выполняет. В то же время регистрация класса окна еще не означает создание самого окна. Для этого требуется выполнить дополнительные действия. При использовании библиотеки MFC создавать собственный класс окна нет необходимости. Вместо этого можно работать с одним из заранее определенных классов, описанных в библиотеке. В этом еще одно ее преимущество.

Специфика программ для Windows

Структура Windows-программ отличается от структуры программ других типов. Это вызвано двумя обстоятельствами: во-первых, способом взаимодействия между программой и Windows, описанным выше; во-вторых, правилами, которым следует подчиняться для создания стандартного интерфейса Windows-приложения (т.е. чтобы сделать программу “похожей “ на Windows-приложение).

Цель Windows – дать человеку, который хотя бы немного знаком с системой, возможность сесть за компьютер и запустить любое приложение без предварительной подготовки. Для этого Windows предоставляет дружественный интерфейс пользователя. Теоретически, если пользователь сумел запустить одно Windows-приложение, то он сумеет запустить и любое другое. Конечно, на практике придется немного потренироваться, чтобы научиться использовать большинство программ с максимальной эффективностью. Однако это связано исключительно с тем, что программа делает, а не с тем, как ею пользоваться. Ведь, фактически, значительная часть кода Windows-приложения предназначена именно для организации интерфейса с пользователем.

Хотя создание удобного интерфейса “под Windows” является основной задачей при написании любой Windows-программы, такой интерфейс не создается автоматически. То есть вполне можно написать программу, в которой элементы интерфейса используются неэффективно. Чтобы этого избежать, необходимо целенаправленно применять методику, описанную в данной книге. Только программы, написанные таким способом, будут выглядеть и работать действительно так, как надлежит Windows-программам.

Чтобы отойти от философии создания традиционного Windows-интерфейса, должны быть достаточно веские основания. Иначе пользователи этой программы будут разочарованы. В общем, если программист собирается писать приложения для Windows, то он должен дать пользователям возможность работать с обычным интерфейсом и руководствоваться стандартной методикой разработки.

Типы данных в Windows

В Windows-программах вообще (и в использующих библиотеку MFC в частности) не слишком широко применяются стандартные типы данных из С или С++, такие как int или char*. Вместо них используются типы данных, определенные в различных библиотечных (header) файлах. Наиболее часто используемыми типами являются HANDLE, HWND, BYTE, WORD, DWORD, UNIT, LONG, BOOL, LPSTR и LPCSTR. Тип HANDLE обозначает 32-разрядное целое, используемое в качестве дескриптора. Есть несколько похожих типов данных, но все они имеют ту же длину, что и HANDLE, и начинаются с литеры Н. Дескриптор – это просто число, определяющее некоторый ресурс. Например, тип HWND обозначает 32-разрядное целое – дескриптор окна. В программах, использующих библиотеку MFC, дескрипторы применяются не столь широко, как это имеет место в традиционных программах. Тип BYTE обозначает 8-разрядное беззнаковое символьное значение, тип WORD – 16-разрядное беззнаковое короткое целое, тип DWORD – беззнаковое длинное целое, тип UNIT - беззнаковое 32-разрядное целое. Тип LONG эквивалентен типу long. Тип BOOL обозначает целое и используется, когда значение может быть либо истинным, либо ложным. Тип LPSTR определяет указатель на строку, а LPCSTR – константный (const) указатель на строку.

Преимущества использования MFC

Как уже упоминалось, MFC – это базовый набор (библиотека) классов, написанных на языке С++ и предназначенных для упрощения и ускорения процесса программирования для Windows. Библиотека содержит многоуровневую иерархию классов, насчитывающую около 200 членов. Они дают возможность создавать Windows-приложения на базе объектно-ориентированного подхода. С точки зрения программиста, MFC представляет собой каркас, на основе которого можно писать программы для Windows.

Библиотека MFC разрабатывалась для упрощения задач, стоящих перед программистом. Как известно, традиционный метод программирования под Windows требует написания достаточно длинных и сложных программ, имеющих ряд специфических особенностей. В частности, для создания только каркаса программы таким методом понадобится около 75 строк кода. По мере же увеличения сложности программы ее код может достигать поистине невероятных размеров. Однако та же самая программа, написанная с использованием MFC, будет примерно в три раза меньше, поскольку большинство частных деталей скрыто от программиста.

Одним из основных преимуществ работы с MFC является возможность многократного использования одного и того же кода. Так как библиотека содержит много элементов, общих для всех Windows-приложений, нет необходимости каждый раз писать их заново. Вместо этого их можно просто наследовать (говоря языком объектно-ориентированного программирования). Кроме того, интерфейс, обеспечиваемый библиотекой, практически независим от конкретных деталей, его реализующих. Поэтому программы, написанные на основе MFC, могут быть легко адаптированы к новым версиям Windows (в отличие от большинства программ, написанных обычными методами).

Еще одним существенным преимуществом MFC является упрощение взаимодействия с прикладным программным интерфейсом (API) Windows. Любое приложение взаимодействует с Windows через API, который содержит несколько сот функций. Внушительный размер API затрудняет попытки понять и изучить его целиком. Зачастую даже сложно проследить, как отдельные части API связанны друг с другом! Но поскольку библиотека MFC объединяет (путем инкапсуляции) функции API в логически организованное множество классов, интерфейсом становится значительно легче управлять.

Поскольку MFC представляет собой набор классов, написанных на языке С++, поэтому программы, написанные с использованием MFC, должна быть в то же время программами на С++. Для этого необходимо владеть соответствующими знаниями. Для начала необходимо уметь создавать собственные классы, понимать принципы наследования и уметь переопределять виртуальные функции. Хотя программы, использующие библиотеку MFC, обычно не содержат слишком специфических элементов из арсенала С++, для их написания тем не менее требуются солидные знания в данной области.

Замечание. Небольшое число классов, определенных в библиотеке, не связанно непосредственно с программированием под Windows. Это, в частности, классы, предназначенные для создания строк, управления файлами и обработки особых ситуаций. Иногда называемые классами общего назначения, они могут использоваться как Windows-, так и не- Windows-приложениями.

Обзор среды Microsoft Developer Studio

Студия разработчика фирмы Microsoft (Microsoft Developer Studio) - это интегрированная среда для разработки, позволяющая функционировать различным средам разработки, одна из которых Visual C++, другая - Visual J++. В дальнейшем будет идти речь только о среде разработки Visual C++.

В студии разработчика можно строить обычные программы на C и С++, создавать статические и динамические библиотеки, но основным режимом работы является создание Windows-приложений с помощью инструмента MFC AppWizard (Application Wizard - мастер приложений) и библиотеки базовых классов MFC (Microsoft Foundation Class Library). Такие приложения называются MFC-приложениями. Главная особенность этих Windows-приложений состоит в том, что они работают как совокупность взаимодействующих объектов, классы которых определены библиотекой MFC.

Библиотека MFC

Главная часть библиотеки MFC состоит из классов, используемых для построения компонентов приложения. С каждым MFC-приложением связывается определяющий его на верхнем уровне объект theApp, принадлежащий классу, производному от CWinApp.

Как правило, структура приложения определяется архитектурой Document-View (документ-облик). Это означает, что приложение состоит из одного или нескольких документов - объектов, классы которых являются производными от класса CDocument (класс "документ"). С каждым из документов связаны один или несколько обликов - объектов классов, производных от CView (класс "облик ") и определяющих облик документа.

Класс CFrameWnd ("окна-рамки") и производные от него определяют окна-рамки на дисплее. Элементы управления, создаваемые при проектировании интерфейса пользователя, принадлежат семейству классов элементов управления. Появляющиеся в процессе работы приложения диалоговые окна - это объекты классов, производных от CDialog.

Классы CView, CFrameWnd, CDialog и все классы элементов управления наследуют свойства и поведение своего базового класса CWnd ("окно"), определяющего по существу Windows-окно. Этот класс в свою очередь является наследником базового ласса CObject ("объект").

Одна из трудностей в понимании принципов устройства MFC-приложения, заключается в том, что объекты, из которых оно строится, наследуют свойства и поведение всех своих предков, поэтому необходимо знать базовые классы.

Архитектура приложения

У всех Windows-приложений фиксированная структура, определяемая функцией WinMain. Структура приложения, построенного из объектов классов библиотеки MFC, является еще более определенной.

Приложение состоит из объекта theApp, функции WinMain, и некоторого количества других объектов. Сердцевина приложения - объект theApp - отвечает за создание всех остальных объектов и обработку очереди сообщений. Объект theApp является глобальным и создается еще до начала работы функции WinMain. Работа функции WinMain заключается в последовательном вызове двух методов объекта theApp: InitInstance и Run. В терминах сообщений можно сказать, WinMain посылает объекту theApp сообщение InitInstance, которое приводит в действие метод InitInstance.

Получив сообщение InitInstance, theApp создает внутренние объекты приложения. Процесс создания выглядит как последовательное порождение одних объектов другими. Набор объектов, порождаемых в начале этой цепочки, определен структурой MFC практически однозначно - это главная рамка, шаблон, документ, облик. Их роли в работе приложения будут обсуждаться позже.

Следующее сообщение, получаемое theApp, - Run - приводит в действие метод Run. Оно как бы говорит объекту: "Начинай работу, начинай процесс обработки сообщений из внешнего мира". Объект theApp циклически выбирает сообщения из очереди и инициирует обработку сообщений объектами приложения.

Некоторые объекты имеют графический образ на экране, с которым может взаимодействовать пользователь. Эти интерфейсные объекты обычно связаны с Windows-окном. Среди них особенно важны главная рамка и облик. Именно им объект прежде всего распределяет сообщения из очереди через механизм Windows-окон и функцию Dispatch.

Когда пользователь выбирает команду меню окна главной рамки, то возникают командные сообщения. Они отправляются сначала объектом theApp объекту главная рамка, а затем обходят по специальному маршруту целый ряд объектов, среди которых первыми являются документ и облик, информируя их о пришедшей от пользователя команде.

При работе приложения возникают и обычные вызовы одними объектами методов других объектов. В объектно-ориентированной терминологии такие вызовы могут называться сообщениями. В Visual C++ некоторым методам приписан именно этот статус (например, методу OnDraw).

Важное значение имеют также объекты документ, облик и главная рамка. Здесь отметим только, что документ содержит данные приложения, облик организует представление этих данных на экране, а окно главной рамки - это окно, внутри которого размещены все остальные окна приложения.

Каркас приложения

Наследование - одна из фундаментальных идей объектно-ориентированного программирования. Именно этот механизм наследования позволяет программисту дополнять и переопределять поведение базового класса, не вторгаясь в библиотеку MFC, которая остается неизменной. Все изменения делаются в собственном производном классе. Именно в этом и заключается работа программиста.

Объекты, их которых состоит приложение, являются объектами классов, производных от классов библиотеки MFC. Разработка приложения состоит в том, что программист берет из библиотеки MFC классы CWinApp, CFrameWnd, CDocument, CView и т.д. и строит производные классы. Приложение создается как совокупность объектов этих производных классов. Каждый объект несет в себе как наследуемые черты, определяемые базовыми классами, так и новые черты, добавленные программистом. Наследуемые черты определяют общую схему поведения, свойственную таким приложениям. Новые же черты позволяют реализовать специфические особенности поведения приложения, необходимые для решения стоящей перед ним задачи.

При определении производного класса программист может:

  • переопределить некоторые методы базового класса, причем те методы, что не были переопределены, будут наследоваться в том виде, в каком они существуют в базовом классе;
  • добавить новые методы;
  • добавить новые переменные.

Приложение, построенное на основе библиотеки MFC, - "айсберг", большая часть которого невидима, но является основой всего приложения. Часть приложения, лежащую в библиотеке MFC, - framework - называется каркасом приложения. Рассмотрим работу приложения как процесс взаимодействия между каркасом и частью приложения, разработанной программистом. Совершенно естественно, что в методах, определенных программистом, могут встречаться вызовы методов базового класса, что вполне можно рассматривать как вызов функции из библиотеки. Важнее, однако, что и метод производного класса, определенный программистом, может быть вызван из метода родительского класса. Другими словами, каркас и производный класс в этом смысле равноправны - их методы могут вызывать друг друга. Такое равноправие достигается благодаря виртуальным методам и полиморфизму, имеющимся в арсенале объектно-ориентированного программирования.

Если метод базового класса объявлен виртуальным и разработчик переопределил его в производном классе, это значит, что при вызове данного метода в некоторой полиморфной функции базового класса в момент исполнения будет вызван метод производного класса и, следовательно, каркас вызывает метод, определенный программистом. Точнее говоря, обращение к этому методу должно производиться через ссылку на производный объект либо через объект, являющийся формальным параметром и получающий при вызове в качестве своего значения объект производного класса. Когда вызывается виртуальный метод М1, переопределенный разработчиком, то согласно терминологии Visual C++, каркас посылает сообщение М1 объекту производного класса, а метод М1 этого объекта обрабатывает это сообщение. Если сообщение М1 послано объекту производного класса, а обработчик этого сообщения не задан программистом, объект наследует метод М1 ближайшего родительского класса, в котором определен этот метод. Если же обработчик такого сообщения создан программистом, он автоматически отменяет действия, предусмотренные родительским классом в отсутствие этого обработчика.

Каркас приложений

С Visual C++ тесно связано еще одно понятие - каркас приложений, которое близко и созвучно понятию каркаса приложения, но в отличие от него относится не к одному конкретному приложению, а к библиотеке, с помощью которой строятся многие приложения. Каркас приложений - это библиотека классов, из которых программист берет не только набор классов, играющих роль дополнительных типов данных, но и классы, служащие строительными блоками приложения на самом верхнем уровне. С этой точки зрения, каркас приложения является частью каркаса приложений, относящейся к данному приложению. Примеры каркасов приложений - библиотеки классов MFC и OWL.

Проект приложения

О принципах устройства приложения рассказывалось выше. Теперь рассмотрим, как оно создается с помощью Visual C++. Сначала разберем одно важное понятие - проект. До сих пор приложение рассматривалось, как только как совокупность объектов базовых и производных классов. Но для обеспечения работы приложения требуется нечто большее - наряду с описанием классов необходимо описание ресурсов, связанных с приложением, нужна справочная система и т.п. Термин "проект" как раз и используется, когда имеется в виду такой более общий взгляд на приложение.

В среде Visual C++ можно строить различные типы проектов. Такие проекты после их создания можно компилировать и запускать на исполнение. Фирма Microsoft разработала специальный инструментарий, облегчающий и ускоряющий создание проектов в среде Visual C++. Например, мастер MFC AppWizard (exe) позволяет создать проект Windows-приложения которое имеет однодокументный, многодокументный или диалоговый интерфейс и использует библиотеку MFC.

Создаваемый остов приложения составлен так, что в дальнейшей работе с проектом можно использовать другое инструментальное средство - ClassWizard (мастер классов), предназначенное для создания остовов новых производных классов. Еще одно основное назначение ClassWizard в том, что он создает остовы для переопределяемых методов. Он позволяет показать все сообщения, приходящие классу, и создать остов обработчика любого из этих сообщений. Это только две основные функции ClassWizard. Он не всесилен, но его возможности довольно велики.

Использование средств разработки

В состав компилятора Microsoft Developer Studio встроены средства, позволяющие программисту облегчить разработку приложений. В первую очередь к ним относятся MFC AppWisard, ClassWizard и редактор ресурсов.

Благодаря MFC AppWizard среда разработчика позволяет быстро создавать шаблоны новых приложений. При этом программисту не приходится писать ни одной строчки кода. Достаточно ответить на ряд вопросов, касающихся того, какое приложение требуется создать, и исходные тексты шаблона приложения вместе с файлами ресурсов готовы. Эти тексты можно оттранслировать и получить готовый загрузочный модуль приложения.

Конечно, никакие средства автоматизированной разработки не смогут создать программу полностью без участия программиста. Прикладную часть приложения придется разрабатывать ему.

Для создания ресурсов приложения предназначен редактор ресурсов. Он позволяет быстро создавать новые меню, диалоговые панели, добавлять кнопки к панели управления toolbar и т.д.

Средство ClassWizard позволяет подключить к созданным и отредактированным ресурсам управляющий ими код. Большую часть работы по описанию и определению функций, обрабатывающих сообщения от меню, органов управления диалоговых панелей и т.д., также берет на себя средство ClassWizard.

Типы мастеров проектов

В среде Visual C++ можно строить различные типы проектов. Такие проекты после их создания можно компилировать и запускать на исполнение. Фирма Microsoft разработала специальный инструментарий, облегчающий и ускоряющий создание проектов в среде Visual C++.

Рассмотрим некоторые типы проектов, которые можно создавать при помощи различных средств (мастеров проектов) Microsoft Visual C++:

MFC AppWizard (exe) – при помощи мастера приложений можно создать проект Windows-приложения которое имеет однодокументный, многодокументный или диалоговый интерфейс. Однодокументное приложеие может предоставлять пользователю в любой момент времени работать только с одним файлом. Многодокументное приложение, напротив, может одновременно представлять несколько документов, каждый в собственном окне. Пользовательский интерфейс диалогового приложения представляет собой единственное диалоговое окно.

MFC AppWizard (dll) – этот мастер приложений позволяет создать структуру DLL, основанную на MFC. При помощи него можно определить характеристики будующей DLL.

AppWizard ATL COM – это средство позволяет создать элемент управления ActiveX или сервер автоматизации, используя новую библиотеку шаблонов ActiveX (ActiveX Template Library - ATL). Опции этого мастера дают возможность выбрать активный сервер (DLL) или исполняемый внешний сервер (exe-файл).

Custom AppWizard – при помощи этого средства можно создать пользовательские мастера AppWizard. Пользовательский мастер может базироваться на стандартных мастерах для приложений MFC или DLL, а также на существующих проектах или содержать только определеямые разработчиком шаги.

DevStudio Add-in Wizard – мастер дополнений позволяет создавать дополнения к Visual Studio. Библиотека DLL расширений может поддерживать панели инструментов и реагировать на события Visual Studio.

MFC ActiveX ControlWizard - мастер элементов управления реализует процесс создания проекта, содержащего один или несколько элементов управления ActiveX, основанных на элементах управления MFC.

Win32 Application – этот мастер позволяет создать проект обычного Window-приложения. Проект создается незаполненным, файлы с исходным кодом в него следует добавлять вручную.

Win32 Console Application – мастер создания проекта консольного приложения. Консольная приложение – это программа, которая выполняется из командной cтроки окна DOS или Windows и не имеет графического интерфейса (окон). Проект консольного приложения создается пустым, предполагая добавление файлов исходного текста в него вручную.

Win32 Dynamic-Link Library – создание пустого проекта динамически подключаемой библиотеки. Установки компилятора и компоновщика будут настроены на создание DLL. Исходные файлы следует добавлять вручную.

Win32 Static Library – это средство создает пустой проект, предназначенный для генерации статической (объектной) библиотеки. Файлы с исходным кодом в него следует добавлять вручную.

Преимущества мастеров проектов

Рассмотрим преимущества использования мастеров в процессе создания приложений. Прежде всего, нужно отметить, что создание проекта - это не только творчество, но и большой объем технической работы, требующей внимания и аккуратности.

Например, все Windows-приложения имеют достаточно общую структуру, и, следовательно, можно построить некоторые шаблонные заготовки, подходящие для того или иного типа проектов. Построению таких заготовок способствует то, что приложения, создаваемые на основе MFC, строятся из элементов фиксированных классов. Логическим развитием этой идеи было введение специальных классов и специальной архитектуры построения приложения, которая подходила бы широкому классу приложений. О такой архитектуре уже упоминалось, когда речь шла о библиотеке MFC, - это архитектура Document-View. Она является основной, но не единственной при построении проектов в среде Visual C++.

Суть этой архитектуры в том, что работу многих приложений можно рассматривать как обработку документов. При этом можно отделить сам документ, отвечающий за представление и хранение данных, от образа этого документа, видимого на экране и допускающего взаимодействие с пользователем, который просматривает и (или) редактирует документ. В соответствии с этой архитектурой библиотека MFC содержит два семейства классов, производных от базовых классов CDocument и CView.

В результате появилась двухэтапная технология создания проектов. Вначале создается некая заготовка проекта с общими свойствами, подходящими для многих проектов этого типа. На втором этапе производится уже настройка, учитывающая специфику задачи. Для каждого этапа фирма Microsoft разработала свое инструментальное средство.

Начальная заготовка - остов приложения - создается в диалоге с пользователем инструментальным средством AppWizard. В процессе диалога пользователь определяет тип и характеристики проекта, который он хочет построить. Определив, какие классы из MFC необходимы для этого проекта, AppWizard строит остовы всех нужных производных классов. Построенный AppWizard остов приложения содержит все необходимые файлы для создания стартового приложения, которое является законченным приложением и обладает разумными функциональными свойствами, общими для целого класса приложений. Естественно, никаких специфических для данного приложения свойств остов не содержит. Они появятся на следующем этапе, когда программист начнет работать с остовом, создавая из заготовки свое собственное приложение. Тем не менее стартовое приложение можно транслировать и запускать на исполнение.

Термин остов (приложения, класса, функции) применяется для заготовок, создаваемых инструментальными средствами AppWizard и ClassWizard. Нужно подчеркнуть - остов приложения и каркас приложения - разные понятия.

Создаваемый остов приложения составлен так, что в дальнейшей работе с проектом можно использовать другое инструментальное средство - ClassWizard (мастер классов).

Обзор возможностей ClassWizard

Средство ClassWizard предоставляет широкий спектр услуг. Он позволяет не только добавлять к существующему классу новые методы и данные.

Создание нового класса. При помощи ClassWizard можно добавить новый класс, созданный на основе базовых классов. В качестве базового класса можно использовать классы, наследованные от класса CCmdTarget или класса CRecordset. Для наследования классов от других базовых классов использовать средства ClassWizard нельзя. Такие классы надо создавать вручную, непосредственно в текстовом редакторе.

Объекты, порожденные от класса CCmdTarget, могут обрабатывать сообщения Windows и команды, поступающие от меню, кнопок, акселераторов. Класс CCmdTarget и другие наследованные от него классы имеют таблицу сообщений (Message Map) - набор макрокоманд, позволяющий сопоставить сообщения Windows и команды метода класса.

Полученная заготовка класса полностью работоспособна. Ее можно дополнить по своему усмотрению новыми методами и данными. Эту работу можно выполнить вручную, но гораздо лучше и проще воспользоваться услугами ClassWizard. За счет использования ClassWizard процедура создания собственного класса значительно ускоряется и уменьшается вероятность совершить ошибку во время объявления методов.

Включение в класс новых методов. Очень удобно использовать ClassWizard для включения в состав класса новых методов. Можно добавлять к классу методы, служащие для обработки сообщений Windows и команд от объектов, а также методы, переопределяющие виртуальные методы базовых классов.

ClassWizard не только позволяет добавить в класс новые методы, но и удалить их. ClassWizard самостоятельно удалит объявление метода из класса.

Включение в класс новых элементов данных. ClassWizard позволяет включать в класс не только новые методы, но и элементы данных, связанные с полями диалоговых панелей, форм просмотра и форм для просмотра записей баз данных и полей наборов записей. ClassWizard использует специальные процедуры, чтобы привязать созданные им элементы данных к класса к полям диалоговых панелей. Эти процедуры носят названия "обмен данными диалоговой панели" и "проверка данных диалоговой панели" (Dialog Data Exchange and Dialog Data Validation - DDX/DDV). Чтобы привязать поля из наборов записей к переменным, используется процедура обмена данными с полями записей (Record Field Exchange - RFX).

Процедуры DDX/DDV и RFX значительно упрощают программисту работу с диалоговыми панелями. Они позволяют связать поля диалоговых панелей и переменные. Когда пользователь редактирует поля диалоговых панелей, процедуры DDV проверяют введенные значения и блокируют ввод запрещенных значений. Затем процедуры DDX автоматически копируют содержимое полей диалоговых панелей в привязанные к ним элементы данных класса. И наоборот, когда приложение изменяет элементы данных класса, привязанные к полям диалоговой панели, процедуры DDX могут сразу отобразить новые значения полей на экране компьютера.

Имена, используемые в MFC

Библиотека MFC содержит большое количество классов, структур, констант и т.д. Для того, чтобы текст MFC-приложений был более легким для понимания, принято применять ряд соглашений для используемых имен и комментариев.

Названия всех классов и шаблонов классов библиотеки MFC начинаются с заглавной буквы C. При наследовании классов от классов MFC можно давать им любые имена. Рекомендуется начинать их названия с заглавной буквы C. Это сделает исходный текст приложения более ясным для понимания.

Чтобы отличить элементы данных, входящих в класс, от простых переменных, их имена принято начинать с префикса m_. Названия методов классов, как правило, специально не выделяются, но обычно их начинают с заглавной буквы.

Библиотека MFC включает в себя, помимо классов, набор служебных функций. Названия этих функций начинаются с символов Afx, например AfxGetApp. Символы AFX являются сокращением от словосочетания Application FrameworkX, означающих основу приложения, его внутреннее устройство.

Символы AFX встречаются не только в названии функций MFC. Многие константы, макрокоманды и другие символы начинаются с этих символов. В общем случае AFX является признаком, по которому можно определить принадлежность того или иного объекта (функция, переменная, ключевое слово или символ) к библиотеке MFC.

Когда приложение разрабатывается средствами MFC AppWizard и ClassWizard, они размещают в исходном тексте приложения комментарии следующего вида:

    //{{AFX_ 	... 	//}}AFX_

Такие комментарии образуют блок кода программы, который управляется только средствами MFC AppWizard и ClassWizard. Пользователь не должен вручную вносить изменения в этом блоке. Для этого необходимо употреблять средства ClassWizard.

В следующей таблице представлено краткое описание некоторых блоков //{{AFX_:

Блок

Описание

//{{AFX_DATA
//}}AFX_DATA

Включает объявление элементов данных класса. Используется в описании классов диалоговых панелей.

//{{AFX_DATA_INIT
//}}AFX_DATA_INIT

Включает инициализацию элементов данных класса. Используется в файле реализации классов диалоговых панелей.

//{{AFX_DATA_MAP
//}}AFX_DATA_MAP

Включает макрокоманды DDX, предназначенные для связывания элементов данных класса и органов управления диалоговых панелей. Используется в файле реализации классов диалоговых панелей.

//{{AFX_MSG
//}}AFX_MSG

Включает описание методов, которые предназначены для обработки сообщений. Этот блок используется при описании класса.

//{{AFX_MSG_MAP
//}}AFX_MSG_MAP

Включает макрокоманды таблицы сообщений класса. Используются совместно с AFX_MSG.

//{{AFX_VIRTUAL
//}}AFX_VIRTUAL

Включает описание переопределенных виртуальных методов класса. Блок AFX_VIRTUAL используется при описании класса.

MFC AppWizard и ClassWizard помогают разрабатывать приложения. Они создают все классы и методы, необходимые для его работы. Программисту остается дописать к ним свой код. В тех местах, где можно вставить свой код, MFC AppWizard и ClassWizard, как правило помещают комментарии:

 //TODO:

Для того что бы перейти к Visual C++, целесообразно получить некоторое представление о просто языке C++, так как он является базовым. Что бы начать обучение вам нужно сначала поставить какую-нибудь версию Visual C++. Лучше, конечно, если у вас есть место на HDD, поставить Visual C++ версии 6. Но можно поставить версию 5 или 4. Все мои примеры написаны на шестой версии, но они должны работать и на младших версиях. Многие думают, что если они поставили Visual C++, то могут писать программы только под Windows, но это не правильно. Visual C++ позволяет писать программы и на простом C++, как бы под DOS. Ну вот, я думаю, и можно начинать.


Глава 2.

2. Первая программа на C++, типы данных и их размер

Что бы начать изучать C++ сначала создадим простое консольное приложение. Для этого запустите Visual C++. Выберите 'New' в меню 'File'. Проверте, что бы в диалоговой панеле 'New' была выбрана закладка 'Projects'. В списке типов проектов выберите 'Win32 Console Application'. Выберите каталог для проекта( лучше оставить по умолчанию ) и имя проекта, например, 'First' и нажмите 'OK'. У вас создатся 'First classes'. После этого выберите опять 'New', но с закладкой 'Files' и выберите 'C++ Source File'. Далее нажмите 'OK' и создастся файл 'First.cpp'. Всё, теперь можно писать программу. Но перед тем, как писать программу, давайте разберёмся какие типы данных существуют в C++.

В C++ существуют несколько часто используемых типов данных( не все ):

1. Численные знаковые целые( int, short, char )
2. Численные знаковые дробные( float, double, long( в С ), long double( в С ) )
3. Численные без знаковые - все перечисленные выше типы с добавлением Unsigned
4. Char так же может использоваться как символьный тип.

Теперь напишем программыу, которая будет выводить размер типов данных в байтах.

#include <iostream.h>

    void main( void )
    {
    cout << " (unsigned)int = " << sizeof(int) << endl;
    cout << " (unsigned)short = " << sizeof(short) << endl;
    cout << " (unsigned)char = " << sizeof(char) << endl;
    cout << " (unsigned)float = " << sizeof(float) << endl;
    cout << " (unsigned)double = " << sizeof(double) << endl;
    cout << " (unsigned)long = " << sizeof(long) << endl;
    cout << " (unsigned)long double = " << sizeof(long double) << endl;
    }


Глава 3

3. Адресация в С++

Напишем программу, которая показывает, что одна из ячеек памяти занята под переменную iNum1 и содержит значение 2. Как программист, вы называете эту ячейку iNum1, но компьютер ссылается на эту ячейку памяти, используя определенное число. Ячейки памяти нумеруются в компьютере последовательно: 1, 2, 3, 4 и т.д. Эти числа называются адресами ячеек памяти. Как программисту, вам, вероятно, никогда не понадобится знать значение адреса этой ячейки памяти, который не всегда один и тот же. В зависимости от того, что выполнялось перед вашей программой, что уже имеется в памяти, и в зависимости от других факторов, адрес, используемый для хранения iNum1, будет меняться даже на одном и том же компьютере. Тем не менее, вы можете извлечь адрес, использующийся для ячейки памяти, посредством операции & (операция взятия адреса). Чтобы посмотреть ее в действии, сделайте следующее:

    void main( void )
    {
    int iNum1;
    iNum1 = 2;
    cout << "Address of iNum1 is: ";
    cout << iNum1 << endl;
    }

Добавленный вами блок кода выводит адрес, использующийся для ячейки памяти переменной iNumI:


Глава 4

4. Применение указателей в C++

Напишем следующую программу, которая использует указатели. Предположим, что значение iNum1 равно 2, а адрес iNum1 — 1000. INum1 будет занимать байты с адресами 1000, 1001, 1002 и 1003. Если значение iNum2 было равно, то переменная iNum2 могла бы занимать ячейки с адресами 1004, 1005, 1006 и 1007. Следовательно, iNumI начинается с адреса 1000, а iNum2 начинается с адреса 1004. Однако, хотя iNumI занимает четыре адреса, в С/С++ адресом iNumI называется адрес 1000, а адресом iNum2 называется адрес 1004. Теперь объявим две переменные как указатели — pNum1 и pNum2. Ваша цель состоит в том, чтобы сохранить число 1000 (адрес iNumI) в pNum1 и число 1004 (адрес iNum2) в pNum2.

Внесите следующие изменения в main(void):

    void main(void)
    {
    int iNum1;
    int iNum2;
    int iResult;
    int* pNum1;
    int* pNum2;
    iNum1 = 2;
    iNum2 = 3;
    pNum1 = &iNum1;
    pNum2 = &iNum2;
    iResult = *pNum1 + *pNum2;
    cout << "The result is: ";
    cout << iResult << endl;
    }

Код, который вы ввели, объявляет три целых переменных:

    int iNum1;
    int iNum2 ;
    int iResult;

Затем объявляются еще две переменные:

    int* pNum1;
    int* pNum2;

Обратите внимание, что в объявлении использована запись int*. К какому же типу относится переменная pNum1? Можете ли вы сохранить целое значение в pNum1? Нет. В pNum1 вы можете сохранить адрес переменной типа int. Вы должны сохранить в переменной pNum1 число 1000, поскольку 1000 является адресом iNum1. Точно так же вы должны сохранять адрес целого значения и в переменной pNum2. После этого вы присваиваете значения переменным iNum1 и iNum2:

    iNum1 = 2;
    iNum2 = 2;

Затем вы присваиваете значения переменным pNumI и pNum2:

    pNum1 = &iNum1;
    pNum2 = &iNum2;

Эти два оператора сохраняют адрес переменной iNum1 в pNum1 и адрес iNum2 в pNum2. Далее вам нужно вычислить результат сложения iNum1 с iNum2. Вы могли бы бы просто написать оператор

    iResult = iNum1 + iNum2;

Однако попробуем выполнить вычисления, применив указатели, а не переменные. Например, чтобы вычислить результат сложения iNuml и iNum2, вы пишете следующий оператор:

    iResult = *pNum1 + *pNum2;

Когда вы используете указатель с предшествующим символом *, вы извлекаете значение, хранящееся по данному адресу. *pNum1 — это то же, что и *1000, так что программа обращается к значению, хранящемуся по адресу 1000. Поскольку переменная pNum1 была объявлена как int* (а компилятор знает, что целое значение занимает четыре байта памяти), программа обращается к адресам 1000, 1001, 1002 и 1003. Она находит по этим адресам значение 2, так как *pNum1 равно 2. Аналогично, *pNum2 равно 3, поскольку pNum2 равно 1004, а ячейки памяти 1004, 1005, 1006 и 1007 содержат целое со значением. И, наконец, выполняется оператор cout, который выводит на экран значение переменной iResult:

    cout << "The result is: " << endl;
    cout << iResult;

Сохраните свою работу, выполните компиляцию и компоновку программы. Запустите программу и убедитесь, что значение iResult равно 5 (2+3=5).


Глава 5

5. Соглашение об именах

Если Вы не знакомы с программированием под Windows, некоторые имена и описания, употребляемые в каркасной программе, могут показаться несколько необычными. Однако они соответствуют соглашениям, представленным фирмой Microsoft для программирования под Windows. Для функций используются имена, построенные из глаголов и существительных, причем первые буквы этих слов — заглавные.

Для имен переменных Microsoft предлагает более сложную систему, предусмат ривающую обозначение именуемых типов данных. Для этого используется неболь шой префикс из строчных букв, а собственно имя начинается с заглавной буквы, Типы префиксов представлены в нижеследующей таблице. Откровенно говоря, использование префиксов, обозначающих тип данных, спорно и не всегда адекватно. Большинство Windows-программистов прибегают к такой системе имено вания, но Вы в своих программах можете поступать по своему усмотрению.

    Префикс - Тип данных
    -------------------------------------------------------
    b - Булевский (байт).
    с - Символ (байт).
    s - Строка ( char или CString ).
    dw - Длинное беззнаковое целое (DWORD).
    f - 16-битный флаг (битовая карта).
    fn - Функция.
    h - Дескриптор (handle).
    l - Длинное целое (long).
    i - Данные типа Int.
    lр - Длинный указатель (long pointer).
    n - Целое (16 бит).
    р - Указатель (pointer).
    pt - Точка (два 32-битных целых).
    w - Целое без знака (WORD, 16 бит).
    sz - Указатель на строку, заканчивающуюся 0 (string>zero).
    Ipsz - Длинный указатель на sz (long pointer string zero).
    rgb - Длинное целое, содержащее цветовую комбинацию RGB.


Глава 6

6. Структуры в С++

Как вы уже знаете, переменная в C/C++ объявляется следующим образом:

    int iMyVariable;

В приведенном операторе iMyVariable объявлена как целая переменная. А вот объявление переменной типа char:

    char cMyChar;

Такие типы данных, как int, float, char и long, являются неотъемлемой частью C/C++ и вам не нужно писать никакого кода, чтобы сообщить компилятору о том, что означают эти слова. C/C++ позволяет вам также объ-являть свои собственные, специальные типы данных. В следующем разделе вы узнаете, как объявлять структуры, которые можно отнести к специальным типам данных.

Напишите следующий исходный код:

    #include <iostream.h>
    #include <string.h>
    // Объявление структуры.
    struct MYSTRUCTURE
    {
    char sName[100];
    int iAge;
    };
    void main( void )
    {
    MYSTRUCTURE MyStructure;
    strcpy(MyStructure.sName, "Andy" );
    MyStructure.iAge = 13;
    cout << "My name is ";
    cout << MyStructure.sName;
    cout << " and I am ";
    cout << MyStructure.iAge;
    cout << " years old." << endl;
    )

В коде, который вы написали, имеются два оператора #include:

    #include <iostream.h>
    #include <string.h>

Файл iostream.h включен в код, поскольку в main(void) используется cout. Файл string.h включается потому, что в main(void) используется функция strcpy() (объявленная в файле string.h). Затем вы объявляете структуру:

    Struct MYSTRUCTURE
    {
    char sName[100];
    int iAge;
    };

Обратите внимание на синтаксис объявления структуры. Оно начинается с ключевого слова struct, за которым следует имя типа-структуры. В этой программе типу структуры присвоено имя MYSTRUCTURE. Затем следует собственно определение структуры, заключенное в фигурные скобки. Не забудьте поставить точку с запятой после закрывающей фигурной скобки. Теперь посмотрите на код внутри фигурных скобок:

    char sName[100];
    int iAge;

Это означает, что MYSTRUCTURE состоит из строки с именем sName и целого с именем iAge, sName и iAge называются элементами данных структуры; Вы объявили их "Внутри" cтруктуры MYSTRUCTURE. Код в main(void) объявляет переменную с именем MyStructure типа MYSTRUCTURE:

    MYSTRUCTORE MyStructure;

Вспомните, что в объявляли переменную iNum1 следующим образом:

    int iNum1;

Когда вы объявляете MyStructure , которая будет структурой типа MYSTRUCTURE, рассматривайте переменную MyStructure аналогично переменной iNum1. MyStructure - это имя переменной, а ее типом является MYSTRUCTURE точно так же, как типом переменной iNum1 является int. (Обратите внимание, что по традиции имя структуры составлено из символов нижнего регистра или в нем смешаны символы нижнего и верхнего регистров, как, например, в имени MyStructure, но в имени типа структуры используются только символы верхнего регистра, как, например, в MYSTRUCTURE.)
Следующий оператор в main(void) копирует строку 'Andy' в элемент данных MyStructure.sName:

    strcpy ( MyStructure.sName, "Andy" );

В этом операторе обращение к элементу данных sName записано как MyStructure.sName Следующий оператор присваивает значение 13 элементу данных iAge cтруктуры MyStructure: MyStructure.iAge - 13; Затем выполняется ряд операторов вывода cout:

    cout << "My name is ";
    cout << MyStructure.sName;
    cout << " and I am ";
    cout << MyStructure.iAge;
    cout << " years old." << endl;

Сложив все вместе, мы видим, что программа MyStruct выводит сообщение My name is Andy and I am 13 years old. (Меня зовут Andy и мне 13 лет)


Глава 7

7. Классы в C++.

Одной из основных черт C++, которой нет в С, является концепция классов. По существу, классы - самое важное понятие в C++. Классы похожи на структуры языка С. Однако структура С определяет только данные, ассоциированные с этой структурой. Вот пример структуры С:

    struct CIRCLE
    {
    int radius;
    int color;
    {;

После того как вы объявили структуру, вы можете использовать ее в пределах вашей функции main (), как показано ниже:

    void main()
    CIRCLE MyCircle;
    ...
    ...
    MyCircle.radius = 18;
    MyCircle.color = 255; // 255 задает цвет
    ...
    ...
    }

Со структурой MyCircle (представляющей окружность) ассоциируются данные radius и color (радиус и цвет). Класс в C++, с другой стороны, имеет как ассоциированные с ним данные, так и функции. Данные класса называются элементами данных, а функции класса - элементами-функциями. Следовательно, в программе, которая использует классы, можно написать следующий код:

MyCircle.radius = 20;
MyCircle.color = 255;
MyCircle.DisplayCircle() ;

Первые два оператора присваивают значения элементам данных MyCircle radius и color; третий оператор вызывает функцию-элемент DisplayCircle() для вывода окружности MyCircle. MyCircle называется объектом класса circle. Ваша программа может объявить другой объект с именем HerCircle класса circle следующим образом:

CIRCLE HerCircle;

Следующие операторы присваивают значения элементам данных HerCircle radius и color:

HerCircle.radius = 30;
HerCircle.color = 0;

Затем вы можете использовать функцию-элемент DisplayCircie () для вывода окружности HerCircle:

HerCircle.DisplayCircle();
Объявление класса

Перед тем как работать с классом, ваша программа должна его объявить (так же как перед работой со структурой mystructure вы должны были объявить ее элементы данных). В данном разделе вы познакомитесь с синтаксисом объявления класса. Вы будете и дальше практиковаться с классом circle:

class Circle (
public:
Circle () ;
void SetRadius(void) ;
void GetRadius(void) ;
~Circle () ;
 private:
void CalculateArea(void);
int radius;
int color;
 };
Объявление класса имеет следующее строение:

class Circle {
...
...
Здесь вы вводите объявление класса
...
...
};
 Ключевое слово class показывает компилятору, что все находящееся в фигурных скобках ({}) принадлежит объявлению класса. (Не забывайте ставить точку с запятой в конце объявления.) Объявление класса содержит объявление элементов данных (например, int radius) и прототипы функций-элементов класса. В объявлении класса circle содержатся следующие элементы данных:
int radius;
int color;
 Объявление также содержит пять прототипов функций-элементов:
Circle();

void SetRadius(void) ;
void GetRadius(void) ;
~Circle () ;
void CalculateArea(void);

Первый и четвертый прототипы выглядят странно. Первый из них является прототипом функции конструктора:

Circle();

Вы узнаете о роли конструктора позже в этом разделе, а пока запомните синтаксис, который используется в C++ для прототипа функции конструктора. Когда вы записываете прототип конструктора, вы должны следовать правилам, приведенным ниже:

• Каждое объявление класса должно включать прототип функции конструктора.

• Имя функции конструктора должно совпадать с именем класса, а после него должны следовать круглые скобки (). Если, например, вы объявляете класс с именем Rectangle, он должен включать объявление функции конструктора класса: Rectangle (). Следовательно, объявление класса Rectangle должно выглядеть так:

class Rectangle
{
public:
 Rectangle(); // Конструктор
...
...
private:
...
...
};

 • Не упоминайте никакого возвращаемого значения для функции конструктора. (Функция конструктора должна иметь тип void, но не нужно это указывать.)
• Функция конструктора должна располагаться под ключевым словом

public.

Функция конструктора всегда возвращает значение типа void (несмотря на то, что вы не указали его в прототипе). Как вы вскоре увидите, функция конструктора обычно имеет один или большее число параметров.

Функция деструктора

Функция деструктора записывается в объявлении класса следующим образом:

class Circle
(
public:
...
...
~Circle (); //Деструктор private:
...
...
};

Обратите внимание на символ тильды (~), который предшествует прототипу функции деструктора. (На большинстве клавиатур вы можете найти символ тильды слева от клавиши 1.) При записи прототипа функции деструктора соблюдайте следующие правила:

• Имя функции деструктора должно совпадать с именем класса и ему должен предшествовать символ ~. Если, например, вы объявляете класс с именем Rectangle, именем функции деструктора должно быть ~Rectangle. Следовательно, объявление класса Rectangle должно выглядеть следующим образом:

class Rectangle
{
public:
 Rectangle(); // Конструктор
...
~Rectangle(); // Деструктор private:
...
...
};

 • Не указывайте никакого возвращаемого значения для функции деструктора. (Функция деструктора должна иметь тип void, но не нужно это указывать.)
• Функция деструктора не имеет никаких параметров.

Ключевые слова public и private

Прототипы функций и объявления элементов данных включаются в объявлении класса в разделы public (открытый) или private (закрытый). Ключевые слова public и private говорят компилятору о доступности элементов-функций и данных. Например, функция SetRadius() определена в разделе public, и это означает, что любая функция программы может вызвать функцию SetRadius(). Функция CalculateArea() определена в разделе private, и эту функцию можно вызвать только в коде функций-элементов класса Circle.

Аналогично, поскольку элемент данных radius объявлен в разделе private, прямой доступ к нему (для установки или чтения его значения) возможен только в коде функций-элементов класса Circle. Если бы вы объявили элемент данных radius в разделе public, то любая функция программы имела бы доступ (для чтения и присваивания) к элементу данных radius.

Перегруженные функции

В C++ (но не в С) вы можете использовать одно и то же имя для нескольких функций. Например, вы можете объявить две функции с именем SetRadius() в объявлении класса CCircle. Такие функции называются перегруженными функциями.


Глава 8

8. Пример использования MFC в Visual C++.

В этой главе будет показано как включить потдержку MFC в Visual C++ на примере двух типов приложений:

    1. Консольное приложение
    2. Приложение типа Windows Application

1. Консольное приложение

Для того, чтобы включить использование MFC в ваше консольное приложение, вам надо написать следующий код:

    #include <afxwin.h>
    #include <iostream.h>

    int main( int argc, char* argv[] )
    {
    if ( !AfxWinInit( ::GetModuleHandle( NULL ), NULL, ::GetCommandLine( ), 0 ) )
    {
    cerr << _T( "MFC Failed to initialize.\n" );
    return 1;
    }

    // код вашей программы

    return 0;
    }

После того, как Вы набрали код, обязательно сделайте следующее:

    Запустите программу - Build / Rebuild all ( будут ошибки ), выберите Build / Set active configuration - Win 32 Realise, выберите пункт меню "Project", далее "Settings...", закладку "C/C++", Category - Code Generation и в пункте "Use run-time library" выберите "Multithreaded". После этого сделайте опять Build / Rebuild all и программа будет работать.

    Если MFC инициализировалась правильно, то будет выполняться код вашей программы, если нет - выведется сообщение "MFC Failed to initialize." Если что то не так, проверте наличие библиотеки "afxwin.h" и правильность написания кода.

    2. Приложение типа Windows Application

    Что бы создать приложение типа Windows Application с использованиеи MFC нужно сделать следующие шаги( создадим для простоты приложение основанное на диалогах ):

      1. Запустите Visual C++.
      2. Выберите File / New.
      3. Выберите закладку "Projects" / "MFC AppWizard( exe )", введите имя проекта( Project name ) и место для проекта( Location ) и нажмите кнопку "OK". В ответ будут выводится диалоговые панели.
      4. MFC AppWizard - Step 1. Выберите интересующий тип проекта( простой документ, мулти-документ или документ, основанный на диалогах ) и нажмите кнопку "Next>" ( Вам надо выбрать "Dialog based").
      5. MFC AppWizard - Step 2. Нажмите кнопку "Next>".
      6. MFC AppWizard - Step 3. Нажмите кнопку "Finish".
      7. New Project Information. Нажмите кнопку "OK".

    Ну вот и всё, у Вас есть уже готовая программа, потдерживающая MFC.


Глава 9

9. Запись и считывание данных ( работа с файлами ).

В этом разделе будут рассотрены два способа работы с файлами и стандартный класс MFC CFileDialog.

    1. Работа с файлами в C ( работает и в C++ ).
    2. Работа с файлами с помощью MFC( классы CFile, CStdioFile, ... ) и стандартный класс MFC CFileDialog.

1. Работа с файлами в C ( работает и в C++ )..

    #include <iostream.h>
    #include <stdio.h>

    void main( void )
    {
    FILE *file;
    char* file_name = "file.txt";
    char load_string[50] = "none";

    file = fopen( file_name, "w" );

    fputs( "string", file );

    fclose( file );

    file = fopen( file_name, "r" );
    if( file != 0 )
    {
    fgets( load_string, 50 , file );
    cout << "load_string = " << load_string << endl;
    }
    else
    {
    cout << "File not found !!!" << endl;
    }
    fclose(file);
    }
Описание функций работы с файломи находятся в библиотеке stdio.h
Сначала надо создать указатель на переменную типа FILE ( FILE* file; ).
Открытие файла производится вызовом функции fopen ( file = fopen( file_name, "w" ); )
Первый параметр этой функции - имя файла, второй - указывает в каком режиме должен быть открыт файл. "w" - открыть для записи, "r" - открыть для чтения, "a" - дополнение файла( это наиболее используемые режимы, хотя есть и другие ). Запись и считывание данных из файла осуществляется следующими функциями : fputc, fputs, fgetc, fgets, fprintf, fscanf( описание этих функций смотрите в stdio.h).
Закрытие файла осуществляется вызовом функции fclose ( fclose( file ); ).

Работа с файлами с помощью MFC( классы CFile, CStdioFile, ... ) и стандартный класс MFC CFileDialog.

  • Класс CFile
  • Класс CMemFile
  • Класс CStdioFile
  • Примеры записи и чтения из файла

В библиотеку MFC включено несколько классов для обеспечения работы с файлами. Рассматриваемые ниже классы наследуются от базового класса CFile.

Класс CFile

Класс CFile предназначен для обеспечения работы с файлами. Он позволяет упростить использование файлов, представляя файл как объект, который можно создать, читать, записывать и т.д.

Чтобы получить доступ к файлу, сначала надо создать объект класса CFile. Конструктор класса позволяет сразу после создания такого объекта открыть файл. Но можно открыть файл и позднее, воспользовавшись методом Open.

Открытие и создание файлов

После создания объекта класса CFile можно открыть файл, вызвав метод Open. Методу надо указать путь к открываемому файлу и режим его использования. Прототип метода Open имеет следующий вид:

     virtual BOOL Open(LPCTSTR lpszFileName,
 UINT nOpenFlags, 			CFileException* pError=NULL);

В качестве параметра lpszFileName надо указать имя открываемого файла. Можно указать только имя файла или полное имя файла, включающее полный путь к нему.

Второй параметр nOpenFlags определяет действие, выполняемое методом Open с файлом, а также атрибуты файла. Ниже представлены некоторые возможеые значения параметра nOpenFlags:

  • CFile::modeCreate - Создается новый файл. Если указанный файл существует, то его содержимое стирается и длина файла устанавливается равной нулю.
  • CFile::modeNoTruncate - Этот файл предназначен для использования совместно с файлом CFile::modeCreate. Если создается уже существующий файл, то его содержимое не будет удалено.
  • CFile::modeRead - Файл открывается только для чтения.
  • CFile::modeReadWrite - Файл открывается для записи и для чтения.
  • CFile::modeWrite - Файл открывается только для записи.
  • CFile::typeText - Используется классами, порожденными от класса CFile, например CStdioFile, для работы с файлами в текстовом режиме. Текстовый режим обеспечивает преобразование комбинации символа возврата каретки и символа перевода строки.
  • CFile::Binary - Используется классами, порожденными от класса CFile, например CStdioFile, для работы с файлами в двоичном режиме.

Необязательный параметр pError, который является указателем на объект класса CFileException, используется только в том случае, если выполнение операции с файлом вызовет ошибку. При этом в объект, указываемый pError, будет записана дополнительная информация.

Метод Open возвращает не нулевое значение, если файл открыт и нуль в случае ошибки. Ошибка при открытии файла может случиться, например, если методу Open указан для чтения несуществующий файл.

Идентификатор открытого файла

В состав класса CFile входит элемент данных m_hFile типа UINT. В нем хранится идентификатор открытого файла. Если объект класса CFile уже создан, но файл еще не открыт, то в переменной m_hFile записана константа hFileNull.

Обычно идентификатор открытого файла непосредственно не используется. Методы класса CFile позволяют выполнять практически любые операции с файлами и не требуют указывать идентификатор файла. Так как m_hFile является элементом класса, то реализация его методов всегда имеет свободный доступ к нему.

Закрытие файлов

После завершения работы с файлом, его надо закрыть. Класс CFile имеет для этого специальный метод Close. Нужно заметить, что если был создан объект класса CFile и открыт файл, а затем объект удаляется, то связанный с ним файл закрывается автоматически с помощью деструктора.

Чтение и запись файлов

Для доступа к файлам предназначено несколько методов класса CFile: Read, ReadHuge, Write, WriteHuge, Flush. Методы Read и ReadHuge предназначены для чтения данных из предварительно открытого файла. В 32-разрядных операционных системах оба метода могут одновременно считать из файла больше 65535 байт. Спецификация ReadHuge считается устаревшей и оставлена только для совместимости с 16-разрядными операционными системами.

Данные, прочитанные из файла, записываются в буфер lpBuf. Параметр nCount определяет количество байт, которое надо считать из файла. Фактически из файла может быть считано меньше байт, чем запрошено параметром nCount. Это происходит, если во время чтения достигнут конец файла. Методы возвращают количество байт, прочитанных из файла.

Для записи в файл предназначены методы Write и WriteHuge. В 32-разрядных операционных системах оба метода могут одновременно записывать в файл больше 65535 байт. Методы записывает в открытый файл nCount байт из буфера lpBuf. В случае возникновения ошибки записи, например переполнения диска, методы вызывает обработку исключения.

Метод Flush

Когда используется метод Write или WriteHuge для записи данных на диск, они некоторое время могут находиться во временном буфере. Чтобы убедиться, что необходимые изменения внесены в файл на диске, нужно воспользоваться методом Flush.

Операции с файлами

В состав класса входят методы, позволяющие выполнять над файлами различные операции, например копирование, переименование, удаление, изменение атрибутов.

Для изменения имени файла класс CFile включает статический метод Rename, выполняющий функции этой команды. Метод нельзя использовать для переименования каталогов. В случае возникновения ошибки метод вызывает исключение.

Для удаления файлов в классе CFile включен статический метод Remove, позволяющий удалить указанный файл. Этот метод не позволяет удалять каталоги. Если удалить файл невозможно, то метод вызывает исключение.

Чтобы определить дату и время создания файла, его длину и атрибуты, предназначен статический метод GetStatus. Существует две разновидности метода - первый определен как виртуальный, а второй - как статический метод.

Виртуальная версия метода GetStatus определяет состояние открытого файла, связанного с данным объектом класса CFile. Этот метод вызывается только тогда, когда объект класса CFile создан и файл открыт.

Статическая версия метода GetStatus позволяет определить характеристики файла, не связанного с объектом класса CFile. Чтобы воспользоваться этим методом, необязательно предварительно открывать файл.

Блокировка

В состав класса включены методы LockRange и UnlockRange, позволяющие заблокировать один или несколько фрагментов данных файла для доступа из других процессов. Если приложение пытается повторно блокировать данные, уже заблокированные раньше этим или другим приложением, вызывается исключение. Блокировка представляет собой один из механизмов, позволяющих нескольким приложениям или процессам одновременно работать с одним файлом, не мешая друг другу.

Установить блокировку можно с помощью метода LockRange. Чтобы снять установленные блокировки, надо воспользоваться методом UnlockRange. Если в одном файле установлены несколько блокировок, то каждая из них должна сниматься отдельным вызовом метода UnlockRange.

Позиционирование

Чтобы переместить указатель текущей позиции файла в новое положение, можно воспользоваться одним из следующих методов класса CFile - Seek, SeekToBegin, SeekToEnd. В состав класса CFile также входят методы, позволяющие установить и изменить длину файла, - GetLength, SetLength.

При открытии файла указатель текущей позиции файла находится в самом начале файла. Когда порция данных прочитана или записана, то указатель текущей позиции перемещается в сторону конца файла и указывает на данные, которые будут читаться или записываться очередной операцией чтения или записи в файл.

Чтобы переместить указатель текущей позиции файла в любое место, можно воспользоваться универсальным методом Seek. Он позволяет переместить указатель на определенное число байт относительно начала, конца или текущей позиции указателя.

Чтобы переместить указатель в начало или конец файла, наиболее удобно использовать специальные методы. Метод SeekToBegin перемещает указатель в начало файла, а метод SeekToEnd - в его конец.

Но для определения длины открытого файла совсем необязательно перемещать его указатель. Можно воспользоваться методом GetLength. Этот метод также возвращает длину открытого файла в байтах. Метод SetLength позволяет изменить длину открытого файла. Если при помощи этого метода размер файла увеличивается, то значение последних байт не определено.

Текущую позицию указателя файла можно определить с помощью метода GetPosition. Возвращаемое методом GetPosition 32-разрядное значение определяет смещение указателя от начала файла.

Характеристики открытого файла

Чтобы определить расположение открытого файла на диске, надо вызвать метод GetFilePath. Этот метод возвращает объект класса CString, в котором содержится полный путь файла, включая имя диска, каталоги, имя файла и его расширение.

Если требуется определить только имя и расширение открытого файла, можно воспользоваться методом GetFileName. Он возвращает объект класса CString, в котором находится имя файла. В случае, когда нужно узнать только имя открытого файла без расширения, пользуются методом GetFileTitle.

Следующий метод класса CFile позволяет установить путь файла. Это метод не создает, не копирует и не изменяет имени файла, он только заполняет соответствующий элемент данных в объекте класса CFile.

Класс CMemFile

В библиотеку MFC входит класс CMemFile, наследуемый от базового класса CFile. Класс CMemFile представляет файл, размещенный, в оперативной памяти. С объектами класса CMemFile так же, как и с объектами класса CFile. Отличие заключается в том, что файл, связанный с объектом CMemFile, расположен не на диске, а в оперативной памяти компьютера. За счет этого операции с таким файлом происходят значительно быстрее, чем с обычными файлами.

Работая с объектами класса CMemFile, можно использовать практически все методы класса CFile, которые были описаны выше. Можно записывать данные в такой файл или считывать их. Кроме этих методов в состав класса CMemFile включены дополнительные методы.

Для создания объектов класса CMemFile предназначено два различных конструктора. Первый конструктор CMemFile имеет всего один необязательный параметр nGrowBytes:

     CMemFile(UINT nGrowBytes=1024);

Этот конструктор создает в оперативной памяти пустой файл. После создания файл автоматически открывается (не нужно вызывать метод Open).

Когда начинается запись в такой файл, автоматически выделяется блок памяти. Для получения памяти методы класса CMemFile вызывают стандартные функции malloc, realloc и free. Если выделенного блока памяти недостаточно, его размер увеличивается. Увеличение блока памяти файла происходит по частям по nGrowBytes байт. После удаления объекта класса CMemFile используемая память автоматически возвращается системе.

Второй конструктор класса CMemFile имеет более сложный прототип. Это конструктор используется в тех случаях, когда программист сам выделяет память для файла:

     CMemFile(BYTE* lpBuffer, UINT nBufferSize,
UINT nGrowBytes=0);

Параметр lpBuffer указывает на буфер, который будет использоваться для файла. Размер буфера определяется параметром nBufferSize.

Необязательный параметр nGrowBytes используется более комплексно, чем в первом конструкторе класса. Если nGrowBytes содержит нуль, то созданный файл будет содержать данные из буфера lpBuffer. Длина такого файла будет равна nBufferSize.

Если nGrowBytes больше нуля, то содержимое буфера lpBuffer игнорируется. Кроме того, если в такой файл записывается больше данных, чем помещается в отведенном буфере, то его размер автоматически увеличивается. Увеличение блока памяти файла происходит по частям по nGrowBytes байт.

Класс CMemFile позволяет получить указатель на область памяти, используемую файлом. Через этот указатель можно непосредственно работать с содержимым файла, не ограничивая себя методами класса CFile. Для получения указателя на буфер файла можно воспользоваться методом Detach. Перед этим полезно определить длину файла (и соответственно размер буфера памяти), вызвав метод GetLength.

Метод Detach закрывает данный файл и возвращает указатель на используемый им блок памяти. Если опять потребуется открыть файл и связать с ним оперативный блок памяти, нужно вызвать метод Attach.

Нужно отметить, что для управления буфером файла класс CMemFile вызывает стандартные функции malloc, realloc и free. Поэтому, чтобы не нарушать механизм управления памятью, буфер lpBuffer должен быть создан функциями malloc или calloc.

Класс CStdioFile

Тем, кто привык пользоваться функциями потокового ввода/вывода из стандартной библиотеки C и C++, следует обратить внимание на класс CStdioFile, наследованный от базового класса CFile. Этот класс позволяет выполнять буферизированный ввод/вывод в текстовом и двоичном режиме. Для объектов класса CStdioFile можно вызывать практически все методы класса CFile.

В класс CStdioFile входит элемент данных m_pStream, который содержит указатель на открытый файл. Если объект класса CStdioFile создан, но файл еще не открыт, либо закрыт, то m_pStream содержит константу NULL.

Класс CStdioFile имеет три различных конструктора. Первый конструктор класса CStdioFile не имеет параметров. Этот конструктор только создает объект класса, но не открывает никаких файлов. Чтобы открыть файл, надо вызвать метод Open базового класса CFile.

Второй конструктор класса CStdioFile можно вызвать, если файл уже открыт и нужно создать новый объект класса CStdioFile и связать с ним открытый файл. Этот конструктор можно использовать, если файл был открыт стандартной функцией fopen. Параметр метода должен содержать указатель на файл, полученный вызовом стандартной функции fopen.

Третий конструктор можно использовать, если надо создать объект класса CStdioFile, открыть новый файл и связать его с только что созданным объектом.

Для чтения и записи в текстовый файл класс CStdioFile включает два новых метода: ReadString и WriteString. Первый метод позволяет прочитать из файла строку символов, а второй метод - записать.

Примеры записи и чтения из файла

Приведем фрагменты кода, в которых демонстрируется использование стандартных диалоговых панелей выбора файла и процедуры чтения и записи в файл.

Открытие файла и чтение из него

    CString m_Text; 	…… 	// создание стандартной панели
 выбора файла Open
     CFileDialog DlgOpen(TRUE,(LPCSTR)"txt",NULL,
          OFN_HIDEREADONLY,(LPCSTR)"
  Text Files (*.txt) |*.txt||");
      // отображение стандартной панели выбора файла Open
    if(DlgOpen.DoModal()==IDOK) 	{
        // создание объекта и открытие файла для чтения
        CStdioFile File(DlgOpen.GetPathName(),
        CFile::modeRead|CFile::typeBinary);
        // чтение из файла строки
        CString& ref=m_Text; 		File.ReadString(ref<
FONT FACE="Courier New">);
 // передается ссылка на строку m_Text 	}
    Здесь находится рабочий код программы, выполненной для простоты в виде консольного приложения под MFC. Чтобы программа работала, не забудте сделать следующее:

    Запустите программу - Build / Rebuild all ( будут ошибки ), выберите Build / Set active configuration - Win 32 Realise, выберите пункт меню "Project", далее "Settings...", закладку "C/C++", Category - Code Generation и в пункте "Use run-time library" выберите "Multithreaded". После этого сделайте опять Build / Rebuild all и программа будет работать.

Открытие файла и запись из него

    CString m_Text; 	…… 	// создание стандартной панели
выбора файла SaveAs
    CFileDialog DlgSaveAs(FALSE,(LPCSTR)"txt",NULL,
        OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
        (LPCSTR)" Text Files (*.txt) |*.txt||");
     // отображение стандартной панели выбора
            файла SaveAs
    if(DlgSaveAs.DoModal()==IDOK) 	{
        // создание объекта и открытие файла для записи
         CStdioFile File(DlgSaveAs.GetPathName(),
              CFile::modeCreate|CFile::modeWrite|
                       CFile::typeBinary);
          // запись в файл строки
        File.WriteString((LPCTSTR)m_Text); 	}
    Здесь находится рабочий код программы, выполненной для простоты в виде консольного приложения под MFC. Чтобы программа работала, не забудте сделать следующее:

    Запустите программу - Build / Rebuild all ( будут ошибки ), выберите Build / Set active configuration - Win 32 Realise, выберите пункт меню "Project", далее "Settings...", закладку "C/C++", Category - Code Generation и в пункте "Use run-time library" выберите "Multithreaded". После этого сделайте опять Build / Rebuild all и программа будет работать.


Глава 10

10. Использование таймера.

    1. Введение
    2. Создание и уничтожение таймера
    3. Сообщение WM_TIMER
    4. Первый способ использования таймера
    5. Второй способ использования таймера
    6. Пример Windows-приложения, использующего таймер

1. Введение

Во многих программах требуется следить за временем или выполнять какие-либо периодические действия. Программы MS-DOS для работы с таймером перехватывали аппаратное прерывание таймера, встраивая свой собственный обработчик для прерывания INT 8h. Обычные приложения Windows не могут самостоятельно обрабатывать прерывания таймера, поэтому для работы с ним нужно использовать другие способы.

Операционная система Windows позволяет для каждого приложения создать несколько виртуальных таймеров. Все эти таймеры работают по прерываниям одного физического таймера.

Так как работа Windows основана на передаче сообщений, логично было бы предположить, что и работа виртуального таймера также основана на передаче сообщений. И в самом деле, приложение может заказать для любого своего окна несколько таймеров, которые будут периодически посылать в функцию окна сообщение с кодом WM_TIMER.

Есть и другой способ, также основанный на передаче сообщений. При использовании этого способа сообщения WM_TIMER посылаются не функции окна, а специальной функции, описанной с ключевым словом _export. Эта функция напоминает функцию окна и, так же как и функция окна, вызывается не из приложения, а из Windows. Функции, которые вызываются из Windows, имеют специальный пролог и эпилог и называются функциями обратного вызова (callback function). Функция окна и функция, специально предназначенная для обработки сообщений таймера, являются примерами функций обратного вызова.

К сожалению, точность виртуального таймера оставляет желать лучшего. Сообщения таймера проходят через очередь приложения, к тому же другое приложение может блокировать на некоторое время работу вашего приложения. Поэтому сообщения от таймера приходят в общем случае нерегулярно. Кроме того, несмотря на возможность указания интервалов времени в миллисекундах, реальная дискретность таймера определяется периодом прерываний, посылаемых таймером. Этот период (то есть длительность одного такта таймера) можно узнать с помощью функции GetTimerResolution:

DWORD WINAPI GetTimerResolution(void);

Нерегулярность прихода сообщений таймера не вызывает особых проблем, если речь не идет о работе в режиме реального времени. Системы реального времени, основанные на Windows, должны использовать для устройств ввода/вывода, критичных к скорости реакции системы, специальные драйверы. Строго говоря, операционная система Windows не предназначена для работы в режиме реального времени. Windows ориентирована на работу с человеком, когда небольшие задержки событий во времени не имеют никакого значения.

2. Создание и уничтожение таймера

Для создания виртуального таймера приложение должно использовать функцию SetTimer:

UINT WINAPI SetTimer(HWND hwnd, UINT idTimer,
   UINT uTimeout, TIMERPROC tmprc);

Первый параметр функции (hwnd) должен содержать идентификатор окна, функция которого будет получать сообщения от таймера, или NULL. В последнем случае с создаваемым таймером не связывается никакое окно и сообщения от таймера будут приходить в специально созданную для этого функцию.

Второй параметр (idTimer) определяет идентификатор таймера (он не должен быть равен нулю). Идентификатор используется только в том случае, если первый параметр функции SetTimer содержит идентификатор окна. Так как для одного окна можно создать несколько таймеров, для того чтобы различать сообщения, приходящие от разных таймеров, приложение при создании должно снабдить каждый таймер собственным идентификатором.

Если первый параметр указан как NULL, второй параметр функции игнорируется, так как для таймера задана специальная функция, получающая сообщения только от этого таймера.

Третий параметр (uTimeout) определяет период следования сообщений от таймера в миллисекундах. Учтите, что физический таймер тикает приблизительно 18,21 раза в секунду (точное значение составляет 1000/54,925). Поэтому, даже если вы укажете, что таймер должен тикать каждую миллисекунду, сообщения будут приходить с интервалом не менее 55 миллисекунд.

Последний параметр (tmprc) определяет адрес функции, которая будет получать сообщения WM_TIMER (мы будем называть эту функцию функцией таймера). Этот параметр необходимо обязательно указать, если первый параметр функции SetTimer равен NULL.

Тип TIMERPROC описан в файле windows.h следующим образом:

typedef void (CALLBACK* TIMERPROC)(HWND hwnd,
 UINT msg, UINT idTimer, DWORD dwTime);

Сравните это с описанием типа WNDPROC, который используется для знакомой вам функции окна:

typedef LRESULT (CALLBACK* WNDPROC)(HWND hwnd,
 UINT msg, WPARAM wParam, LPARAM lParam);

Как видно из описания, функция таймера не возвращает никакого значения, имеет другие (по сравнению с функцией окна) параметры, но описана с тем же ключевым словом CALLBACK:

#define CALLBACK _far _pascal

Возвращаемое функцией SetTimer значение является идентификатором созданного таймера (если в качестве первого параметра функции было указано значение NULL). В любом случае функция SetTimer возвращает нулевое значение, если она не смогла создать таймер. В Windows версии 3.0 максимальное количество созданных во всей системе таймеров было 16. Для Windows версии 3.1 это ограничение снято.

Тем не менее, если приложение больше не нуждается в услугах таймера, оно должно уничтожить таймер, вызвав функцию KillTimer:

BOOL WINAPI KillTimer (HWND hwnd, UINT idTimer);

Первый параметр функции (hwnd) определяет идентификатор окна, указанный при создании таймера функцией SetTimer.

Второй параметр (idTimer) - идентификатор уничтожаемого таймера. Это должен быть либо тот идентификатор, который вы указали при создании таймера (если таймер создавался для окна), либо значение, полученное при создании таймера от функции SetTimer (для таймера, имеющего собственную функцию обработки сообщений).

Функция KillTimer возвращает значение TRUE при успешном уничтожении таймера или FALSE, если она не смогла найти таймер с указанным идентификатором.

3. Сообщение WM_TIMER

Параметр wParam сообщения WM_TIMER содержит идентификатор таймера, который был указан или получен от функции SetTimer при создании таймера.

С помощью параметра lParam можно определить адрес функции, которая обрабатывает сообщения таймера.

После обработки этого сообщения приложение должно возвратить нулевое значение.

Заметим, что сообщение WM_TIMER является низкоприоритетным. Это означает, что функция DispatchMessage посылает это сообщение приложению только в том случае, если в очереди приложения нет других сообщений. В этом отличие таймера Windows от аналогичных средств MS-DOS, реализованных с помощью перехвата прерывания INT 8h.

Выполнение программы MS-DOS прерывается синхронно с приходом аппаратного прерывания таймера и программа MS-DOS, перехватившая это прерывание, немедленно оповещается о нем. Выполнение приложения Windows тоже, разумеется, прерывается по аппаратному прерыванию таймера, но оповещение об этом событии приходит не всегда, и как правило, позже, вместе с сообщением WM_TIMER.

4. Первый способ использования таймера

В этом разделе мы рассмотрим первый способ работы с таймером - подключение таймера к окну. В этом случае функция окна, к которому подключен таймер, будет получать сообщения от таймера с кодом WM_TIMER.

Этот способ самый простой. Вначале вам надо вызывать функцию SetTimer, указав ей в качестве параметров идентификатор окна, идентификатор таймера и период, с которым от таймера должны приходить сообщения:

#define FIRST_TIMER 1 int nTimerID;
 nTimerID = SetTimer(hwnd, FIRST_TIMER, 1000, NULL);

В данном примере создается таймер с идентификатором FIRST_TIMER, который будет посылать сообщения примерно раз в секунду.

Для уничтожения таймера, созданного этим способом, следует вызвать функцию KillTimer, указав параметры следующим образом:

KillTimer(hwnd, FIRST_TIMER);

Для изменения интервала посылки сообщений вам следует вначале уничтожить таймер, а потом создать новый, работающий с другим периодом времени:

KillTimer(hwnd, FIRST_TIMER);
 nTimerID = SetTimer(hwnd, FIRST_TIMER, 100, NULL);

5. Второй способ использования таймера

Второй способ работы с таймером заключается в использовании для таймера специальной функции, которая будет получать сообщения WM_TIMER. Эта функция является функцией обратного вызова, определяется с ключевым словом _export и, так же как и функция окна, имеет специальный пролог и эпилог:

void CALLBACK _export TimerProc(HWND hwnd,
 UINT msg, UINT idTimer, DWORD dwTime);

Как и для функции окна, для функции таймера можно выбрать любое имя. При создании таймера вам надо указать адрес функции таймера, а имя не имеет никакого значения.

Первый параметр функции таймера - идентификатор окна, с которым связан таймер. Если при создании таймера в качестве идентификатора было указано значение NULL, это же значение будет передано функции таймера.

Второй параметр представляет собой идентификатор сообщения WM_TIMER.

Третий параметр является идентификатором таймера, пославшего сообщение WM_TIMER.

И наконец, последний параметр - текущее время по системным часам компьютера. Это время выражается в количестве тиков таймера с момента запуска Windows. Вы можете узнать текущее системное время в любой момент, если воспользуетесь функцией GetCurrentTime или GetTickCount:

DWORD WINAPI GetCurrentTime(void);
 DWORD WINAPI GetTickCount(void);

Эти функции совершенно аналогичны, однако название функции GetTickCount более точно отражает выполняемое ей действие.

Если для создания приложения вы пользуетесь современными средствами разработки, такими, как Borland C++ версии 3.1, Microsoft С++ версии 7.0, Microsoft Visual C++, для определения функции обратного вызова достаточно использовать ключевое слово _export. В этом случае вы можете создать таймер, например, так:

nTimerID = SetTimer(hwnd, 0, 1000,
 (TIMERPROC)TimerProc);

Для удаления таймера в этом случае необходимо использовать идентификатор, возвращенный функцией SetTimer:

KillTimer(hwnd, nTimerID );

Если же используемые вами средства разработки не позволяют указать ключевое слово _export, для подключения функции таймера придется использовать более сложный способ.

Когда вы не можете ограничиться использованием ключевого слова _export, для работы с функциями обратного вызова нужно сделать специальный переходник (thunk), вызвав функцию MakeProcInstance:

FARPROC WINAPI MakeProcInstance(FARPROC lpProc,
   HINSTANCE hinst);

В качестве первого параметра функции (lpProc) необходимо передать адрес функции, для которой создается переходник, а в качестве второго (hinst) - идентификатор приложения hInstance, полученный функцией WinMain при запуске приложения.

Функция MakeProcInstance для указанной функции создает функцию-переходник, возвращая ее адрес, а также обеспечивает функции доступ к сегменту данных приложения, загружая соответствующим образом сегментные регистры.

Процедура создания таймера с использованием функции MakeProcInstance может выглядеть следующим образом:

TIMERPROC lpfnTimerProc;
lpfnTimerProc = (TIMERPROC)MakeProcInstance(
 (FARPROC)TimerProc, hInstance);
  nTimerID = SetTimer(hwnd, 0, 1000,
  (TIMERPROC)lpfnTimerProc);

После уничтожения таймера следует уничтожить созданный функцией MakeProcInstance переходник, для чего следует вызвать функцию FreeProcInstance:

void WINAPI FreeProcInstance(FARPROC lpProc);

Этой функции необходимо передать адрес уничтожаемой функции-переходника:

FreeProcInstance(lpfnTimerProc);

Функции MakeProcInstance и FreeProcInstance можно использовать совместно с современными средствами разработки, понимающими ключевое слово _export. Это не приведет к неправильной работе приложения.

Старые средства разработки приложений Windows требуют, чтобы все функции обратного вызова, такие, как функции окон и функции таймеров, были описаны в файле определения модуля, имеющем расширение .def, при помощи оператора EXPORTS, например:

EXPORTS WndProc         TimerProc

Транслятор Borland C++ версии 3.1 распознает ключевое слово _export и автоматически формирует нужный пролог и эпилог для функций обратного вызова. То же самое относится и к трансляторам Microsoft C++ версии 7.0 и Microsoft Visual C++. Поэтому в наших примерах функции MakeProcInstance и FreeProcInstance, а также оператор файла определения модуля EXPORTS не используются.

6. Пример Windows-приложения, использующего таймер

Отсюда можно взять рабочую программу в диалоговом режиме, использующую таймер. После загрузки программы вы можете нажать на кнопку "Start timer" и услышать периодические звуковые сигналы( 1 сек. ). После нажатия кнопки "End timer" или при закрытие приложения таймер будет уничтожен.


Глава 11

11. Работа со стандартными ресурсами.

    1. Методы класса CButton
    2. Методы класса CEdit
    3. Методы класса CListBox
    4. Методы класса CComboBox
    5. Методы класса CProgressCtrl
    6. Методы класса CSliderCtrl
    7. Методы класса CSpinButtonCtrl
    8. Пример Windows-приложения, использующего стандартные ресурсы

1. Методы класса CButton

HBITMAP GetBitmap() const;

Возвращает дескриптор растрового изображения, сопоставленного кнопке. Если такового не существует, то возвращается NULL.

HBITMAP SetBitmap(HBITMAP hBitmap);

Сопоставляет кнопке растровое изображение. Значением параметра должен быть дескриптор растрового изображения. Правила размещения растрового изображения такие же, как и у значка.

HCURSOR GetCursor();

Возвращает дескриптор курсора, сопоставленного кнопке методом SetCursor. Если у кнопки нет сопоставленного курсора, то возвращается NULL.

HCURSOR SetCursor(HCURSOR hCursot);

Сопоставляет кнопке курсор, изображение которого будет помещено на поверхность кнопки аналогично значку и растровому изображению.

UINT GetState() const;

Возвращает описание набора текущих состояний кнопки. Чтобы выделить из этого описания значения конкретных типов состояния, можно использовать маски:

  • 0х0003 - выделяет собственное состояние кнопки. Применимо только к флажку или переключателю. Если результат побитового умножения дает 0, значит кнопка находится в невыбранном состоянии, 1 - в выбранном, 2 - в неопределенном.
  • 0х0004 - выделяет состояние первого типа. Ненулевой вариант означает, что кнопка "нажата", нулевой - кнопка свободна.
  • 0х0008 - выделяет положение фокуса. Ненулевой вариант - кнопка в фокусе клавиатуры.

int GetCheck() const;

Возвращает собственное состояние флажка или переключателя. Возвращаемое значение может принимать одно из значений: 0 - кнопка не выбрана; 1 - кнопка выбрана; 2 - кнопка в неопределенном состоянии. Если кнопка не является ни переключателем, ни флажком, возвращается 0.

void SetCheck(int nCheck);

Устанавливает собственное состояние флажка или переключателя. Значения задаются из набора: 0 - невыбранное; 1 - выбранное; 2 - неопределенное. Значение 2 применимо только к флажку со свойством 3State.

UINT GetButtonStyle() const;

Возвращает стиль кнопки.

void SetButtonStyle(UINT nStyle, BOOL bRedraw=TRUE);

Устанавливает стиль кнопки. Если параметр bRedraw равен TRUE, кнопка перерисовывается.

HICON GetIcon() const;

Возвращает дескриптор пиктограммы, сопоставленной кнопке. Если у кнопки нет сопоставленной пиктограммы, возвращает NULL.

HICON SetIcon(HICON hIcon);

Сопоставляет кнопке пиктограмму. Значением параметра при вызове должен быть дескриптор пиктограммы.

Пиктограмма автоматически помешается на поверхность кнопки и сдвигается в ее центр. Если поверхность кнопки меньше пиктограммы, она обрезается со всех сторон до размеров кнопки. Положение пиктограммы может быть выровнено и не по центру. Для этого нужно, чтобы кнопка имела одно из следующих свойств: BS_LEFT, BS_RIGHT, BS_CENTER, BS_TOP, BS_BOTTOM, BS_VCENTER

Данный метод устанавливает для кнопки только одну пиктограмму, которая будет наравне с текстом присутствовать при любом ее состоянии. Не надо путать ее с растровым изображением у растровой кнопки.

2. Методы класса CEdit

Окна редактирования могут работать в режимах однострочного и многострочного редакторов. Приведем сначала методы, общие для обоих режимов, а затем методы для многострочного редактора.

Общие методы :

DWORD GetSel() const;

void GetSel(int& nStartChar, int& nEndChar) const;

Получает первую и последнюю позиции выделенного текста. Для значения типа DWORD младшее слово содержит позицию первого, старшее - последнего символа.

void SetSel(DWORD dwSelection, BOOL bNoScroll=FALSE);

void SetSel(int nStartChar, int nEndChar, BOOL bNoScroll=FALSE);

Устанавливает новое выделение текста, задавая первый и последний выделенный символ. Значение FALSE параметра bNoScroll должно отключать перемещение курсора в область видимости.

void ReplaceSel(LPCTSTR lpszNewText);

Заменяет выделенный текст на строку, передаваемую в параметре lpszNewText.

void Clear();

Удаляет выделенный текст.

void Copy();

Копирует выделенный текст в буфер.

void Cut();

Переносит (копирует и удаляет) выделенный текст в буфер обмена.

void Paste();

Вставляет текст из буфера обмена, начиная с позиции, в которой находится курсор.

BOOL Undo();

Отмена последней операции, выполненной редактором. Если редактор однострочный, возвращается всегда неотрицательное значение, иначе неотрицательное значение возвращается лишь в случае успешной замены.

BOOL CanUndo() const;

Определяет, можно ли отменить последнюю операцию редактора.

void EmptyUndoBuffer();

Сбрасывает флаг undo, сигнализирующий о возможности отмены последней операции редактора, и тем самым делает невозможным отмену. Этот флаг сбрасывается автоматически при выполнении методов SetWindowText и SetHandle.

BOOL GetModify() const;

Возвращает неотрицательное значение, если содержимое окна редактирования не модифицировалось. Информация о модификации поддерживается в специальном флаге, обнуляемом при создании окна редактирования и при вызове метода:

void SetModify(BOOL bModified=TRUE);

Устанавливает или сбрасывает флаг модификации (см. предыдущий метод). Флаг сбрасывается при вызове метода с параметром FALSE и устанавливается при модификации содержимого окна редактирования или при вызове SetModify с параметром TRUE.

BOOL SetReadOnly(BOOL bReadOnly=TRUE);

Устанавливает режим просмотра (bReadOnly=TRUE) или редактирования (bReadOnly=FALSE).

TCHAR GetPasswordChar() const;

Возвращает символ, который при выводе пароля будет появляться на экране вместо символов, набираемых пользователем. Если такой символ не определен, возвращается 0. Устанавливается этот символ методом (по умолчанию используется "*"):

void SetPasswordChar(TCHAR ch);

void LimitText(int nChars=0);

Устанавливает максимальную длину в байтах текста, который может ввести пользователь. Если значение параметра равно 0, длина текста устанавливается равной UINT_MAX.

Методы работы с многострочным редактором :

void LineScroll(int nLines, int nChars=0);

Прокручивает текст в области редактирования. Параметр nLimes задает число строк для вертикальной прокрутки. Окно редактирования не прокручивает текст дальше последней строки. При положительном значении параметра область редактирования сдвигается вдоль текста к последней строке, при отрицательной - к первой.

Параметр nChars задает число символов для горизонтальной прокрутки. Окно редактирования прокручивает текст вправо, даже если строки закончились. В этом случае в области редактирования появляются пробелы. При положительном значении параметра область редактирования сдвигается вдоль к концу строки, при отрицательном - к началу.

int GetFirstVisibleLine() const;

Возвращает номер первой видимой строки.

int GetLineCount() const;

Возвращает число строк текста, находящегося в буфере редактирования. Если текст не вводился, возвращает 1.

int GetLine(int nIndex, LPTSTR lpszBuffer) const;

int GetLine(int nIndex, LPTSTR lpszBuffer, int nMaxLength) const;

Копирует строку с номером, равным значению параметра nIndex, в буфер, заданный параметром lpszBuffer. Первое слово в буфере должно задавать его размер. При вызове второго варианта метода значение параметра nMaxLength копируется в первое слово буфера.

Метод возвращает число в действительности скопированных байтов. Если номер строки больше или равен числу строк в буфере окна редактирования, возвращает 0. Текст копируется без каких-либо изменений, нуль-символ не добавляется.

int LineIndex(int nLine=-1) const;

Возвращает номер первого символа в строке. Неотрицательное значение параметра принимается в качестве номера строки. Значение -1 задает текущую строку. Если номер строки больше или равен числу строк в буфере окна редактирования (строки нумеруются с 0), возвращается 0.

3. Методы класса CListBox

void ResetContent();

Очищает содержимое списка, делая его пустым.

int AddString( LPCSTR lpszItem);

Добавляет строку lpszItem в список и сортирует его, если при создании включено свойство Sort. В противном случае элемент добавляется в конец списка.

int DeleteString( UINT nIndex);

Удаляет из списка элемент с индексом nIndex. Индексация элементов начинается с 0.

int GetCurSel() const;

Получает индекс элемента, выбранного пользователем.

int SetCurSel( int nSelect);

Отмечает элемент с индексом nSelect как выбранный элемент списка. Если значение параметра равно -1, список не будет содержать отмеченных элементов.

int GetText( int nIndex, LPSTR lpszBuffer) const;

void GetText( int nIndex, CString& rString) const;

Копирует элемент с индексом nIndex в буфер.

int SetTopIndex( int nIndex);

Организует прокрутку списка в окне так, чтобы элемент с индексом nIndex был видимым.

int FindString( int nStartAfter, LPCSTR lpszItem) const;

Организует поиск в списке и возвращает в качестве результата индекс элемента списка, префикс которого совпадает со строкой lpszItem. Результат не зависит от регистра, в котором набирались символы сравниваемых строк. Параметр nStartAfter задает начало поиска, но поиск идет по всему списку. Он начинается от элемента, следующего за nStartAfter, до конца списка и затем продолжается от начала списка до элемента с индексом nStartAfter. В качестве результата выдается первый найденный элемент, удовлетворяющий условиям поиска. Если такого нет, результат получает значение LB_ERR.

int FindStringExact( int nIndexStart, LPCSTR lpszFind) const;

Этот метод отличается от предыдущего тем, что теперь не префикс элемента должен совпадать со строкой lpszFind, а сам элемент. Поиск по-прежнему не чувствителен к регистру, в котором набираются символы.

4. Методы класса CComboBox

int GetCurSel() const;

Возвращает целочисленный указатель выбранной строчки.

int SetCurSel(int nSelect);;

Ставит указатель на строчку с номером nSelect.

int GetLBText(int nIndex, LPTSTR lpszText) const;
void GetLBText(int nIndex, CString& rString) const;

 Записывает содержимое строчки с индексом nIndex в переменные LPTSTR lpszText или CString& rString.

int GetLBTextLen(int nIndex) const;

Возвращает длину строчки с индексом nIndex.

int AddString(LPCTSTR lpszString);

Добавляет строчку в список.

int DeleteString(UINT nIndex);

Удаление строчки с индексом nIndex.

int InsertString(int nIndex, LPCTSTR lpszString);

Заменяет строчку с индексом nIndex содержимым переменной LPCTSTR lpszString.

5. Методы класса CProgressCtrl

void SetRange(short nLower, short nUpper);
void SetRange32(int nLower, int nUpper);

Устанавливает минимальное ( nLower ) и максимальное значение ( nUpper ).

void GetRange(int& nLower, int& nUpper);

Записывает в переменные nLower и nUpper минимальное и максимальное значение.

int GetPos();

Возвращает текущее значение.

int SetPos(int nPos);

Устанавливает текущее значение в nPos.

int SetStep(int nStep);

Устанавливает шаг ( nStep ) вывода.

6. Методы класса CSliderCtrl

int GetRangeMax() const;
int GetRangeMin() const;
void GetRange(int& nMin, int& nMax) const;

Первые две функции возвращают максимальное и минимальное знанение, а третья - записывает эти значения в nMax и nMin соответственно.

void SetRangeMin(int nMin, BOOL bRedraw = FALSE);
void SetRangeMax(int nMax, BOOL bRedraw = FALSE);
void SetRange(int nMin, int nMax, BOOL bRedraw = FALSE);

Первые две функции устанавливают максимальное и минимальное знанение, а третья - устанавливает эти значения из переменных nMax и nMin соответственно. Аргумент bRedraw отвечает за перерисовку слайдера.

int GetPos() const;

Возвращает текущую позицию.

void SetPos(int nPos);

Устанавливает текущую позицию в nPos.

BOOL SetTic(int nTic);

Устанавливает шаг ( nTic ).

void SetTicFreq(int nFreq);

Устанавливает частоту засечек ( nFreq ).

7. Методы класса CSpinButtonCtrl

int SetPos(int nPos);

Устанавливает текущую позицию в nPos.

int GetPos() const;;

Возвращает текущую позицию.

void SetRange(int nLower, int nUpper);
void SetRange32(int nLower, int nUpper);

Устанавливает максимальное и минимальное знанение из переменных nMax и nMin соответственно.

void GetRange(int &lower, int& upper) const;
void GetRange32(int &lower, int& upper) const;;

Эти две функции записывают максимальное и минимальное знанение в upper и lower соответственно.

8. Пример Windows-приложения, использующего стандартные ресурсы

Отсюда можно взять рабочую программу в диалоговом режиме, использующего стандартные ресурсы.


Глава 12

12. Работа с библиотеками динамической компоновки (DLL)

  • Использование DLL
    • Библиотеки импортирования
    • Согласование интерфейсов
    • Загрузка неявно подключаемой DLL
    • Динамическая загрузка и выгрузка DLL
    • Пример обычной DLL и способов загрузки
  • Создание DLL
    • Функция DllMain
    • Экспортирование функций из DLL
    • Экспортирование классов
    • Память DLL
    • Полная компиляция DLL
  • DLL и MFC
    • Обычные MFC DLL
    • Динамические расширения MFC
    • Загрузка динамических расширений MFC
    • Экспортирование функций из динамических расширений

    С самого рождения (или чуть позже) операционная система Windows использовала библиотеки динамической компоновки DLL (Dynamic Link Library), в которых содержались реализации наиболее часто применяемых функций. Наследники Windows — NT и Windows 95, а также OS/2 — тоже зависят от библиотек DLL в плане обеспечения значительной части их функциональных возможностей.

    Рассмотрим ряд аспектов создания и использования библиотек DLL:

    • как статически подключать библиотеки DLL;
    • как динамически загружать библиотеки DLL;
    • как создавать библиотеки DLL;
    • как создавать расширения МFC библиотек DLL.

    Использование DLL

    Практически невозможно создать приложение Windows, в котором не использовались бы библиотеки DLL. В DLL содержатся все функции Win32 API и несчетное количество других функций операционных систем Win32.

    Вообще говоря, DLL — это просто наборы функций, собранные в библиотеки. Однако, в отличие от своих статических родственников (файлов . lib), библиотеки DLL не присоединены непосредственно к выполняемым файлам с помощью редактора связей. В выполняемый файл занесена только информация об их местонахождении. В момент выполнения программы загружается вся библиотека целиком. Благодаря этому разные процессы могут пользоваться совместно одними и теми же библиотеками, находящимися в памяти. Такой подход позволяет сократить объем памяти, необходимый для нескольких приложений, использующих много общих библиотек, а также контролировать размеры ЕХЕ-файлов.

    Однако, если библиотека используется только одним приложением, лучше сделать ее обычной, статической. Конечно, если входящие в ее состав функции будут использоваться только в одной программе, можно просто вставить в нее соответствующий файл с исходным текстом.

    Чаще всего проект подключается к DLL статически, или неявно, на этапе компоновки. Загрузкой DLL при выполнении программы управляет операционная система. Однако, DLL можно загрузить и явно, или динамически, в ходе работы приложения.

    Библиотеки импортирования

    При статическом подключении DLL имя .lib-файла определяется среди прочих параметров редактора связей в командной строке или на вкладке “Link” диалогового окна “Project Settings” среды Developer Studio. Однако .lib-файл, используемый при неявном подключении DLL, — это не обычная статическая библиотека. Такие .lib-файлы называются библиотеками импортирования (import libraries). В них содержится не сам код библиотеки, а только ссылки на все функции, экспортируемые из файла DLL, в котором все и хранится. В результате библиотеки импортирования, как правило, имеют меньший размер, чем DLL-файлы. К способам их создания вернемся позднее. А сейчас рассмотрим другие вопросы, касающиеся неявного подключения динамических библиотек.

    Согласование интерфейсов

    При использовании собственных библиотек или библиотек независимых разработчиков придется обратить внимание на согласование вызова функции с ее прототипом.

    Если бы мир был совершенен, то программистам не пришлось бы беспокоиться о согласовании интерфейсов функций при подключении библиотек — все они были бы одинаковыми. Однако мир далек от совершенства, и многие большие программы написаны с помощью различных библиотек без C++.

    По умолчанию в Visual C++ интерфейсы функций согласуются по правилам C++. Это значит, что параметры заносятся в стек справа налево, вызывающая программа отвечает за их удаление из стека при выходе из функции и расширении ее имени. Расширение имен (name mangling) позволяет редактору связей различать перегруженные функции, т.е. функции с одинаковыми именами, но разными списками аргументов. Однако в старой библиотеке С функции с расширенными именами отсутствуют.

    Хотя все остальные правила вызова функции в С идентичны правилам вызова функции в C++, в библиотеках С имена функций не расширяются. К ним только добавляется впереди символ подчеркивания (_).

    Если необходимо подключить библиотеку на С к приложению на C++, все функции из этой библиотеки придется объявить как внешние в формате С:

         extern "С" int MyOldCFunction(int myParam);

    Объявления функций библиотеки обычно помещаются в файле заголовка этой библиотеки, хотя заголовки большинства библиотек С не рассчитаны на применение в проектах на C++. В этом случае необходимо создать копию файла заголовка и включить в нее модификатор extern "C" к объявлению всех используемых функций библиотеки. Модификатор extern "C" можно применить и к целому блоку, к которому с помощью директивы #tinclude подключен файл старого заголовка С. Таким образом, вместо модификации каждой функции в отдельности можно обойтись всего тремя строками:

         extern "С"  	{ 		#include "MyCLib.h" 	}

    В программах для старых версий Windows использовались также соглашения о вызове функций языка PASCAL для функций Windows API. В новых программах следует использовать модификатор winapi, преобразуемый в _stdcall. Хотя это и не стандартный интерфейс функций С или C++, но именно он используется для обращений к функциям Windows API. Однако обычно все это уже учтено в стандартных заголовках Windows.

    Загрузка неявно подключаемой DLL

    При запуске приложение пытается найти все файлы DLL, неявно подключенные к приложению, и поместить их в область оперативной памяти, занимаемую данным процессом. Поиск файлов DLL операционной системой осуществляется в следующей последовательности.

    • Каталог, в котором находится ЕХЕ-файл.
    • Текущий каталог процесса.
    • Системный каталог Windows.

    Если библиотека DLL не обнаружена, приложение выводит диалоговое окно с сообщением о ее отсутствии и путях, по которым осуществлялся поиск. Затем процесс отключается.

    Если нужная библиотека найдена, она помещается в оперативную память процесса, где и остается до его окончания. Теперь приложение может обращаться к функциям, содержащимся в DLL.

    Динамическая загрузка и выгрузка DLL

    Вместо того, чтобы Windows выполняла динамическое связывание с DLL при первой загрузке приложения в оперативную память, можно связать программу с модулем библиотеки во время выполнения программы (при таком способе в процессе создания приложения не нужно использовать библиотеку импорта). В частности, можно определить, какая из библиотек DLL доступна пользователю, или разрешить пользователю выбрать, какая из библиотек будет загружаться. Таким образом можно использовать разные DLL, в которых реализованы одни и те же функции, выполняющие различные действия. Например, приложение, предназначенное для независимой передачи данных, сможет в ходе выполнения принять решение, загружать ли DLL для протокола TCP/IP или для другого протокола.

    Загрузка обычной DLL

    Первое, что необходимо сделать при динамической загрузке DLL, - это поместить модуль библиотеки в память процесса. Данная операция выполняется с помощью функции ::LoadLibrary, имеющей единственный аргумент – имя загружаемого модуля. Соответствующий фрагмент программы должен выглядеть так:

         HINSTANCE hMyDll; 	…… 	if((hMyDll=::LoadLibrary
(“MyDLL”))==NULL) { /* не удалось загрузить DLL */ }
     else { /* приложение имеет право
  пользоваться функциями DLL через hMyDll */ }

    Стандартным расширением файла библиотеки Windows считает .dll, если не указать другое расширение. Если в имени файла указан и путь, то только он будет использоваться для поиска файла. В противном случае Windows будет искать файл по той же схеме, что и в случае неявно подключенных DLL, начиная с каталога, из которого загружается exe-файл, и продолжая в соответствии со значением PATH.

    Когда Windows обнаружит файл, его полный путь будет сравнен с путем библиотек DLL, уже загруженных данным процессом. Если обнаружится тождество, вместо загрузки копии приложения возвращвется дескриптор уже подключенной библиотеки.

    Если файл обнаружен и библиотека успешно загрузилась, функция ::LoadLibrary возвращает ее дескриптор, который используется для доступа к функциям библиотеки.

    Перед тем, как использовать функции библиотеки, необходимо получить их адрес. Для этого сначала следует воспользоваться директивой typedef для определения типа указателя на функцию и определить переменую этого нового типа, например:

         // тип PFN_MyFunction будет объявлять указатель
             на функцию,
    // принимающую указатель на символьный буфер
 и выдающую значение типа int 	typedef int
 (WINAPI *PFN_MyFunction)(char *);
     …… 	PFN_MyFunction pfnMyFunction;

    Затем следует получить дескриптор библиотеки, при помощи которого и определить адреса функций, например адрес фунции с именем MyFunction:

         hMyDll=::LoadLibrary(“MyDLL”);
     pfnMyFunction=(PFN_MyFunction)::GetProcAddress
                           (hMyDll,”MyFunction”);
     …… 	int iCode=(*pfnMyFunction)(“Hello”);

    Адрес функции определяется при помощи функции ::GetProcAddress, ей следует передать имя библиотеки и имя функции. Последнее должно передаваться в том виде, в котором эксаортируется из DLL.

    Можно также сослаться на функцию по порядковому номеру, по которому она экспортируется (при этом для создания библиотеки должен использоваться def-файл, об этом будет рассказано далее):

         pfnMyFunction=(PFN_MyFunction)::GetProcAddress
(hMyDll, 				MAKEINTRESOURCE(1));

    После завершения работы с библиотекой динамической компоновки, ее можно выгрузить из памяти процесса с помощью функции ::FreeLibrary:

         ::FreeLibrary(hMyDll);

    Загрузка MFC-расширений динамических библиотек

    При загрузке MFC-расширений для DLL (подробно о которых рассказывается далее) вместо функций LoadLibrary и FreeLibrary используются функции AfxLoadLibrary и AfxFreeLibrary. Последние почти идентичны функциям Win32 API. Они лишь гарантируют дополнительно, что структуры MFC, инициализированные расширением DLL, не были запорчены другими потоками.

    Ресурсы DLL

    Динамическая загрузка применима и к ресурсам DLL, используемым MFC для загрузки стандартных ресурсов приложения. Для этого сначала необходимо вызвать функцию LoadLibrary и разместить DLL в памяти. Затем с помощью функции AfxSetResourceHandle нужно подготовить окно программы к приему ресурсов из вновь загруженной библиотеки. В противном случае ресурсы будут загружаться из файлов, подключенных к выполняемому файлу процесса. Такой подход удобен, если нужно использовать различные наборы ресурсов, например для разных языков.

    Замечание. С помощью функции LoadLibrary можно также загружать в память исполняемые файлы (не запускать их на выполнение!). Дескриптор выполняемого модуля может затем использоваться при обращении к функциям FindResource и LoadResource для поиска и загрузки ресурсов приложения. Выгружают модули из памяти также при помощи функции FreeLibrary.

    Пример обычной DLL и способов загрузки

    Приведем исходный код динамически подключаемой библиотеки, которая называется MyDLL и содержит одну функцию MyFunction, которая просто выводит сообщение.

    Сначала в заголовочном файле определяется макроконтстанта EXPORT. Использование этого ключевого слова при определении некоторой функции динамически подключаемой билиотеке позволяет сообщить компоновщику, что эта функция доступна для использования другими программами, в результате чего он заносит ее в библилтеку импорта. Кроме этого, такая функция, точно так же, как и оконная процедура, должна определяться с помощью константы CALLBACK:

     MyDLL.h 	#define EXPORT extern “C” __declspec
(dllexport)
     EXPORT int CALLBACK MyFunction(char *str);

    Файл библиотеки также несколько отличается от обычных файлов на языке C для Windows. В нем вместо функции WinMain имеется функция DllMain. Эта функция используется для выполнения инициализации, о чем будет рассказано позже. Для того, чтобы библиотека осталась после ее загрузки в памяти, и можно было вызывать ее функции, необходимо, чтобы ее возвращаемым значением было TRUE:

     MyDLL.c 	#include <windows.h> 	#include “MyDLL.h”
     int WINAPI DllMain(HINSTANCE hInstance,
  DWORD fdReason, PVOID pvReserved)
      { 		return TRUE; 	}
     EXPORT int CALLBACK MyFunction(char *str)
     { 		MessageBox(NULL,str,”Function from DLL”,MB_OK);
         return 1; 	}

    После трансляции и компоновки этих файлов появлятся два файла – MyDLL.dll (сама динамически подключаемая библиотека) и MyDLL.lib (ее библиотека импорта).

    Пример неявного поключения DLL приложением

    Приведем теперь исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll:

         #include <windows.h> 	#include “MyDLL.h”
     int WINAPI WinMain(HINSTANCE hInstance,
 HINSTANCE hPrevInstance, 			LPSTR lpCmdLine,
 int nCmdShow) 	{ 		int iCode=MyFunction(“Hello”);
         return 0; 	}

    Эта программа выглядит как обычная программ для Windows, чем она в сущности и является. Тем не менее, следует обратить внимание, что в исходный ее текст помимо вызова функции MyFunction из DLL-библиотеки включен и заголовочный файл этой библиотеки MyDLL.h. Также необходимо на этапе компоновки приложения подключить к нему библиотеку импорта MyDLL.lib (процесс неявного подключения DLL к исполняемому модулю).

    Чрезвычайно важно понимать, что сам код функции MyFunction не включается в файл MyApp.exe. Вместо этого там просто имеется ссылка на файл MyDLL.dll и ссылка на функцию MyFunction, которая находится в этом файле. Файл MyApp.exe требует запуска файла MyDLL.dll.

    Заголовочный файл MyDLL.h включен в файл с исходным текстом программы MyApp.c точно так же, как туда включен файл windows.h. Включение библиотеки импорта MyDLL.lib для компоновки аналогично включению туда всех библиотек импорта Windows. Когда програма MyApp.exe работает, она подключается к библиотеке MyDLL.dll точно так же, как ко всем стандартным динамически подключаемым библиотекам Windows.

    Пример динамической загрузки DLL приложением

    Приведем теперь полностью исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll, используя динамическую загрузку библиотеки:

         #include <windows.h> 	typedef int
(WINAPI *PFN_MyFunction) (char *);  	int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
            LPSTR lpCmdLine, int nCmdShow)
     { 		HINSTANCE hMyDll;
        if((hMyDll=LoadLibrary(“MyDLL”))==NULL) return 1;
        PFN_MyFunction pfnMyFunction;
        pfnMyFunction=(PFN_MyFunction)
           GetProcAddress(hMyDll,”MyFunction”);
        int iCode=(*pfnMyFunction)(“Hello”);
         FreeLibrary(hMyDll); 		return 0; 	}

    Создание DLL

    Теперь, познакомившись с принципами работы библиотек DLL в приложениях, рассмотрим способы их создания. При разработке приложении функции, к которым обращается несколько процессов, желательно размещать в DLL. Это позволяет более рационально использовать память в Windows.

    Проще всего создать новый проект DLL с помощью мастера AppWizard, который автоматически выполняет многие операции. Для простых DLL, таких как рассмотренные в этой главе, необходимо выбрать тип проекта Win32 Dynamic-Link Library. Новому проекту будут присвоены все необходимые параметры для создания библиотеки DLL. Файлы исходных текстов придется добавлять к проекту вручную.

    Если же планируется в полной мере использовать функциональные возможности MFC, такие как документы и представления, или намерены создать сервер автоматизации OLE, лучше выбрать тип проекта MFC AppWizard (dll). В этом случае, помимо присвоения проекту параметров для подключения динамических библиотек, мастер проделает некоторую дополнительную работу. В проект будут добавлены необходимые ссылки на библиотеки MFC и файлы исходных текстов, содержащие описание и реализацию в библиотеке DLL объекта класса приложения, производного от CWinApp.

    Иногда удобно сначала создать проект типа MFC AppWizard (dll) в качестве тестового приложения, а затем — библиотеку DLL в виде его составной части. В результате DLL в случае необходимости будет создаваться автоматически.

    Функция DllMain

    Большинство библиотек DLL — просто коллекции практически независимых друг от друга функций, экспортируемых в приложения и используемых в них. Кроме функций, предназначенных для экспортирования, в каждой библиотеке DLL есть функция DllMain. Эта функция предназначена для инициализации и очистки DLL. Она пришла на смену функциям LibMain и WEP, применявшимся в предыдущих версиях Windows. Структура простейшей функции DllMain может выглядеть, например, так:

         BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason,
LPVOID IpReserved) 	{ 		BOOL bAllWentWell=TRUE;
        switch (dwReason)  		{ 			case DLL_PROCESS_ATTACH:
// Инициализация процесса.  				break;
            case DLL_THREAD_ATTACH: // Инициализация потока.
                break; 			case DLL_THREAD_DETACH:
// Очистка структур потока. 				break;
             case DLL_PROCESS_DETACH:
 // Очистка структур процесса. 				break; 		}
        if(bAllWentWell) 	return TRUE;
         else			return FALSE; 	}

    Функция DllMain вызывается в нескольких случаях. Причина ее вызова определяется параметром dwReason, который может принимать одно из следующих значений.

    При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового потока DllMainO вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).

    По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.

    Все операции по инициализации и очистке для процессов и потоков, в которых нуждается DLL, необходимо выполнять на основании значения dwReason, как было показано в предыдущем примере. Инициализация процессов обычно ограничивается выделением ресурсов, совместно используемых потоками, в частности загрузкой разделяемых файлов и инициализацией библиотек. Инициализация потоков применяется для настройки режимов, свойственных только данному потоку, например для инициализации локальной памяти.

    В состав DLL могут входить ресурсы, не принадлежащие вызывающему эту библиотеку приложению. Если функции DLL работают с ресурсами DLL, было бы, очевидно, полезно сохранить где-нибудь в укромном месте дескриптор hInst и использовать его при загрузке ресурсов из DLL. Указатель IpReserved зарезервирован для внутреннего использования Windows. Следовательно, приложение не должно претендовать на него. Можно лишь проверить его значение. Если библиотека DLL была загружена динамически, оно будет равно NULL. При статической загрузке этот указатель будет ненулевым.

    В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.

    Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандартную версию, которая просто возвращает TRUE.

    Экспортирование функций из DLL

    Чтобы приложение могло обращаться к функциям динамической библиотеки, каждая из них должна занимать строку в таблице экспортируемых функций DLL. Есть два способа занести функцию в эту таблицу на этапе компиляции.

    Метод __declspec (dllexport)

    Можно экспортировать функцию из DLL, поставив в начале ее описания модификатор __declspec (dllexport) . Кроме того, в состав MFC входит несколько макросов, определяющих __declspec (dllexport), в том числе AFX_CLASS_EXPORT, AFX_DATA_EXPORT и AFX_API_EXPORT.

    Метод __declspec применяется не так часто, как второй метод, работающий с файлами определения модуля (.def), и позволяет лучше управлять процессом экспортирования.

    Файлы определения модуля

    Синтаксис файлов с расширением .def в Visual C++ достаточно прямолинеен, главным образом потому, что сложные параметры, использовавшиеся в ранних версиях Windows, в Win32 более не применяются. Как станет ясно из следующего простого примера, .def-файл содержит имя и описание библиотеки, а также список экспортируемых функций:

     MyDLL.def 	LIBRARY 		“MyDLL” 	DESCRIPTION
‘MyDLL – пример DLL-библиотеки’
     EXPORTS 		MyFunction 	@1

    В строке экспорта функции можно указать ее порядковый номер, поставив перед ним символ @. Этот номер будет затем использоваться при обращении к GetProcAddress (). На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако способ, которым он это делает, отчасти непредсказуем, если не присвоить эти номера явно.

    В строке экспорта можно использовать параметр NONAME. Он запрещает компилятору включать имя функции в таблицу экспортирования DLL:

             MyFunction 	@1 NONAME

    Иногда это позволяет сэкономить много места в файле DLL. Приложения, использующие библитеку импортирования для неявного подключения DLL, не “заметят” разницы, поскоьку при неявном подключении порядковые номера используются автоматически. Приложениям, загружающим библиотеки DLL динамически, потребуется передавать в GetProcAddress порядковый номер, а не имя функции.

    При использовании вышеприведенного def-файл описания экспортируемых функций DLL-библиотеки может быть,например, не таким:

         #define EXPORT extern “C” __declspec (dllexport)
    EXPORT int CALLBACK MyFunction(char *str); a таким:
    extern “C”
int CALLBACK MyFunction(char *str);

    Экспортирование классов

    Создание .def-файла для экспортирования даже простых классов из динамической библиотеки может оказаться довольно сложным делом. Понадобится явно экспортировать каждую функцию, которая может быть использована внешним приложением.

    Если взглянуть на реализованный в классе файл распределения памяти, в нем можно заметить некоторые весьма необычные функции. Оказывается, здесь есть неявные конструкторы и деструкторы, функции, объявленные в макросах MFC, в частности _DECLARE_MESSAGE_MAP, а также функции, которые написанные программистом.

    Хотя можно экспортировать каждую из этих функций в отдельности, есть более простой способ. Если в объявлении класса воспользоваться макромодификатором AFX_CLASS_EXPORT, компилятор сам позаботится об экспортировании необходимых функций, позволяющих приложению использовать класс, содержащийся в DLL.

    Память DLL

    В отличие от статических библиотек, которые, по существу, становятся частью кода приложения, библиотеки динамической компоновки в 16-разрядных версиях Windows работали с памятью несколько иначе. Под управлением Win 16 память DLL размещалась вне адресного пространства задачи. Размещение динамических библиотек в глобальной памяти обеспечивало возможность совместного использования их различными задачами.

    В Win32 библиотека DLL располагается в области памяти загружающего ее процесса. Каждому процессу предоставляется отдельная копия "глобальной" памяти DLL, которая реинициализируется каждый раз, когда ее загружает новый процесс. Это означает, что динамическая библиотека не может использоваться совместно, в общей памяти, как это было в Winl6.

    И все же, выполнив ряд замысловатых манипуляций над сегментом данных DLL, можно создать общую область памяти для всех процессов, использующих данную библиотеку.

    Допустим, имеется массив целых чисел, который должен использоваться всеми процессами, загружающими данную DLL. Это можно запрограммировать следующим образом:

         #pragma data_seg(".myseg") 	int sharedlnts[10] ;
// другие переменные общего пользования
    #pragma data_seg() 	#pragma comment(lib,
"msvcrt" "-SECTION:.myseg,rws");

    Все переменные, объявленные между директивами #pragma data_seg(), размещаются в сегменте .myseg. Директива #pragma comment () — не обычный комментарий. Она дает указание библиотеке выполняющей системы С пометить новый раздел как разрешенный для чтения, записи и совместного доступа.

    Полная компиляция DLL

    Если проект динамической библиотеки создан с помощью AppWizard и .def-файл модифицирован соответствующим образом — этого достаточно. Если же файлы проекта создаются вручную или другими способами без помощи AppWizard, в командную строку редактора связей следует включить параметр /DLL. В результате вместо автономного выполняемого файла будет создана библиотека DLL.

    Если в .def-файле есть строка LIBRART, указывать явно параметр /DLL в командной строке редактора связей не нужно.

    Для MFC предусмотрен ряд особых режимов, касающихся использования динамической библиотекой библиотек MFC. Этому вопросу посвящен следующий раздел.

    DLL и MFC

    Программист не обязан использовать MFC при создании динамических библиотек. Однако использование MFC открывает ряд очень важных возможностей.

    Имеется два уровня использования структуры MFC в DLL. Первый из них — это обычная динамическая библиотека на основе MFC, MFC DLL (regular MFC DLL). Она может использовать MFC, но не может передавать указатели на объекты MFC между DLL и приложениями. Второй уровень реализован в динамических расширениях MFC (MFC extensions DLL). Использование этого вида динамических библиотек требует некоторых дополнительных усилий по настройке, но позволяет свободно обмениваться указателями на объекты MFC между DLL и приложением.

    Обычные MFC DLL

    Обычные MFC DLL позволяют применять MFC в динамических библиотеках. При этом приложения, обращающиеся к таким библиотекам, не обязательно должны быть построены на основе MFC. В обычных DLL можно использовать MFC любым способом, в том числе создавая в DLL новые классы на базе классов MFC и экспортируя их в приложения.

    Однако обычные DLL не могут обмениваться с приложениями указателями на классы, производные от MFC.

    Если приложению необходимо обмениваться с DLL указателями на объекты классов MFC или их производных, нужно использовать расширение DLL, описанное в следующем разделе.

    Архитектура обычных DLL рассчитана на использование другими средами программирования, такими как Visual Basic и PowerBuilder.

    При создании обычной библиотеки MFC DLL с помощью AppWizard выбирается новый проект типа MFC AppWizard (dll). В первом диалоговом окне мастера приложений необходимо выбрать один из режимов для обычных динамических библиотек: “Regular DLL with MFC statistically linked” или “Regular DLL using shared MFC DLL”. Первый предусматривает статическое, а второй — динамическое подключение библиотек MFC. Впоследствии режим подключения MFC к DLL можно будет изменить с помощью комбинированного списка на вкладке “General” диалогового окна “Project settings”.

    Управление информацией о состоянии MFC

    В каждом модуле процесса MFC содержится информация о его состоянии. Таким образом, информация о состоянии DLL отлична от информации о состоянии вызвавшего ее приложения. Поэтому любые экспортируемые из библиотеки функции, обращение к которым исходит непосредственно из приложений, должны сообщать MFC, какую информацию состояния использовать. В обычной MFC DLL, использующей динамические библиотеки MFC, перед вызовом любой подпрограммы MFC в начале экспортируемой функции нужно поместить следующую строку:

         AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

    Данный оператор определяет использование соответствующей информации о состоянии во время выполнения функции, обратившейся к данной подпрограмме.

    Динамические расширения MFC

    MFC позволяет создавать такие библиотеки DLL, которые воспринимаются приложениями не как набор отдельных функций, а как расширения MFC. С помощью данного вида DLL можно создавать новые классы, производные от классов MFC, и использовать их в своих приложениях.

    Чтобы обеспечить возможность свободного обмена указателями на объекты MFC между приложением и DLL, нужно создать динамическое расширение MFC. DLL этого типа подключаются к динамическим библиотекам MFC так же, как и любые приложения, использующие динамическое расширение MFC.

    Чтобы создать новое динамическое расширение MFC, проще всего, воспользовавшись мастером приложении, присвоить проекту тип MFC AppWizard (dll) и на шаге 1 включить режим “MFC Extension DLL”. В результате новому проекту будут присвоены все необходимые атрибуты динамического расширения MFC. Кроме того, будет создана функция DllMain для DLL, выполняющая ряд специфических операций по инициализации расширения DLL. Следует обратить внимание, что динамические библиотеки данного типа не содержат и не должны содержать объектов, производных от CWinApp.

    Инициализация динамических расширений

    Чтобы "вписаться" в структуру MFC, динамические расширения MFC требуют дополнительной начальной настройки. Соответствующие операции выполняются функцией DllMain. Рассмотрим пример этой функции, созданный мастером AppWizard.

         static AFX_EXTENSION_MODULE MyExtDLL =
{ NULL, NULL } ; 	extern "C" int APIENTRY
    DllMain(HINSTANCE hinstance, DWORD dwReason,
LPVOID IpReserved) 	{
        if (dwReason == DLL_PROCESS_ATTACH) 		{
            TRACED("MYEXT.DLL Initializing!\n") ;
             // Extension DLL one-time initialization
            AfxInitExtensionModule(MyExtDLL, hinstance) ;
            // Insert this DLL into the resource chain
            new CDynLinkLibrary(MyExtDLL); 		}
        else if (dwReason == DLL_PROCESS_DETACH) 		{
            TRACED("MYEXT.DLL Terminating!\n") ; 		}
         return 1; // ok 	}

    Самой важной частью этой функции является вызов AfxInitExtensionModule. Это инициализация динамической библиотеки, позволяющая ей корректно работать в составе структуры MFC. Аргументами данной функции являются передаваемый в DllMain дескриптор библиотеки DLL и структура AFX_EXTENSION_MODULE, содержащая информацию о подключаемой к MFC динамической библиотеке.

    Нет необходимости инициализировать структуру AFX_EXTENSION_MODULE явно. Однако объявить ее нужно обязательно. Инициализацией же займется конструктор CDynLinkLibrary. В DLL необходимо создать класс CDynLinkLibrary. Его конструктор не только будет инициализировать структуру AFX_EXTENSION_MODULE, но и добавит новую библиотеку в список DLL, с которыми может работать MFC.

    Загрузка динамических расширений MFC

    Начиная с версии 4.0 MFC позволяет динамически загружать и выгружать DLL, в том числе и расширения. Для корректного выполнения этих операций над создаваемой DLL в ее функцию DllMain в момент отключения от процесса необходимо добавить вызов AfxTermExtensionModule. Последней функции в качестве параметра передается уже использовавшаяся выше структура AFX_EXTENSION_MODULE. Для этого в текст DllMain нужно добавить следующие строки.

         if(dwReason == DLL_PROCESS_DETACH)  	{
        AfxTermExtensionModule(MyExtDLL); 	}

    Кроме того, следует помнить, что новая библиотека DLL является динамическим расширением и должна загружаться и выгружаться динамически, с помощью функций AfxLoadLibrary и AfxFreeLibrary,а не LoadLibrary и FreeLibrary.

    Экспортирование функций из динамических расширений

    Рассмотрим теперь, как осуществляется экспортирование в приложение функций и классов из динамического расширения. Хотя добавить в DEF-файл все расширенные имена можно и вручную, лучше использовать модификаторы для объявлений экспортируемых классов и функций, такие как AFX_EXT_CLASS и AFX_EXT_API,например:

         class AFX_EXT_CLASS CMyClass : public CObject 	(
        // Your class declaration 	}
    void AFX_EXT_API MyFunc() ;


Глава 13

13. Стандартные диалоговые панели

    1. Введение
    2. Панель выбора цвета (класс CColorDialog)
    3. Панель выбора файлов (класс CFileDialog)
    4. Панель выбора шрифта (класс CFontDialog
    5. Панель для вывода документов на печать (класс CPrintDialog)
    6. Панель для выполнения поиска и замены (класс CFindReplaceDialog)
    7. Пример Windows-приложения, использующего стандартные панели

1. Введение

В состав библиотеки MFC входит ряд классов, представляющих стандартные диалоговые панели. Эти классы позволяют легко реализовать такие часто используемые операции, как открытие и сохранение файла, выбор цвета, выбор шрифта и т.д. Все эти классы наследуются от CCommonDialog, который в свою очередь является производным по отношению к базовому классу CDialog.

Приведем классы стандартных диалоговых панелей и их назначение:

  • CColorDialog - Панель для выбора цвета
  • CFileDialog - Панель выбора файлов для открытия и сохранения на диске
  • CFindReplaceDialog - Панель для выполнения операции поиска и замены
  • CFontDialog - Панель для выбора шрифта
  • CPrintDialog - Панель для вывода документа на печать
  • CPageSetupDialog - Панель выбора формата документа
  • COleDialog - Панель для управления технологией OLE

Классы, управляющие стандартными диалоговыми панелями, определены в файле afxdlgs.h. Поэтому при использовании этих классов в приложении необходимо включить этот файл в исходный текст при помощи директивы #include.

2. Панель выбора цвета (класс CColorDialog)

Чтобы отобразить на экране стандартную диалоговую панель выбора цвета, надо создать объект класса CColorDialog, а затем вызвать метод DoModal. При создании объекта класса СColorDialog используется следующий конструктор:

    CColorDialog(COLORREF clrInit=0,DWORD dwFlags=0,CWnd* pParentWnd=NULL);

Все параметры конструктора необязательны, однако в некоторых случаях использование этих параметров может помочь.

Первый параметр clrInit позволяет указать цвет, выбранный по умолчанию сразу после открытия диалоговой панели. Если параметр не будет указан, в качестве цвета, выбранного по умолчанию, будет использоваться черный цвет.

Параметр dwFlags содержит набор флагов, управляющих диалоговой панелью выбора цвета. При помощи него блокировать или разрешать работу некоторых элементов управления диалоговой панели выбора цвета. Если при создании объекта класса CColorDialog не указать параметр dwFlags, тем не менее можно выполнить настройку диалоговой панели, обратившись непосредственно к элементу m_cc данного класса. Параметр dwFlags, указанный в конструкторе, используется для инициализации m_cc. Изменения в элемент m_cc должны быть внесены до того, как панель будет отображаться на экране.

Последний параметр pParentWnd можно использовать, чтобы указать родительское окно диалоговой панели.

Методы класса CСolorDialog

Чтобы вывести диалоговую панель выбора цвета на экран, необходимо использовать метод DoModal. После отображения панели на экране пользователь может выбрать из нее цвет и нажать кнопки OK или Cancel для подтверждения выбора цвета или отказа от него. Когда диалоговая панель закрывается, метод DoModal возвращается значения IDOK и IDCANCEL, в зависимости от того, какую кнопку нажал пользователь:

    CColorDialog dlgColor;
    int iResult=dlgColor.DoModal();

На экране появится стандартная диалоговая панель выбора цвета Color. В верхней половине диалоговой панели расположены 48 прямоугольников, имеющих различные цвета. Они представляют так называемые основные цвета (Basic colors). Можно выбрать один из этих цветов и нажать кнопку OK. После того, как диалоговая панель закрыта (метод DoModal завершил свою работу), можно воспользоваться методами класса CColorDialog, чтобы узнать цвета, выбранные пользователем.

Для определения цвета, выбранного пользователем, можно обратиться к методу GetColor класса CColorDialog. Данный метод возвращает значение COLORREF, соответствующее выбранному цвету.

Если пользователю недостаточно основных цветов, представленных в диалоговой панели Color, он может выбрать до 16 дополнительных цветов. Для этого он должен нажать кнопку DefineCustom Colors. Диалоговая панель изменит свой внешний вид - появятся дополнительные органы управления, позволяющие выбрать любой из 16 777 216 цветов. Когда цвет выбран, нужно нажать кнопку Add Custom Colors. Выбранный цвет будет добавлен к дополнительным цветам (Custom colors) - один из свободных прямоугольников окрасится соответствующим цветом.

При помощи метода GetSavedCustomColors класса CColorDialog можно определить дополнительные цвета, выбранные пользователем в диалоговой панели Color. Этот метод возвращает указатель на массив из 16 элементов типа COLORREF. Каждый элемент массива описывает один дополнительный цвет.

Когда диалоговая панель Color отображается приложением первый раз, все прямоугольники, отображающие дополнительные цвета, имеют белый цвет. Дополнительные цвета, выбранные пользователем, сохраняются во время работы приложения. После перезапуска приложения дополнительные цвета сбрасываются.

3. Панель выбора файлов (класс CFileDialog)

Среди стандартных диалоговых панелей, для которых в библиотеке MFC создан специальный класс, есть панели для работы с файловой системой - Open и Save As. Диалоговая панель Open позволяет выбрать один или несколько файлов и открыть их для дальнейшего использования. Диалоговая панель Save As позволяет выбрать имя файла для записи в него документа.

Для управления диалоговыми панелями Open и Save As предназначен один класс CFileDialog. Рассмотрим конструктор класса CFileDialog более подробно:

    CFileDialog(BOOL bOpenFileDialog,
    LPCTSTR lpszDefExt=NULL,LPCTSTR lpszFileName=NULL,
    DWORD dwFlags=OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
    LPCTSTR lpszFilter=NULL,CWnd* pParentWnd=NULL);

Объекты класса CFileDialog представляют диалоговые панели Open или Save As в зависимости от параметра bOpenFileDialog. Если параметр bOpenFileDialog содержит значение TRUE, то создается объект, управляющий диалоговой панелью Open, а если FALSE - диалоговой панелью Save As.

Параметр bOpenFileDialog является единственным обязательным параметром, который необходимо указать. Остальные параметры конструктора класса CFileDialog задают различные режимы работы панели и могут не указываться.

Чтобы создать объект класса CFileDialog , представляющий диалоговую панель для открытия файлов (mFileOpen), и объект, представляющий диалоговую панель для сохранения файлов (mFileSaveAs), можно воспользоваться следующими вызовами конструктора класса:

    CFileDialog mFileOpen(TRUE); // для панели открытия файлов
    CFileDialog mFileSaveAs(FA LSE); // для панели сохранения файла

Во многих случаях имена файлов, которые нужно открыть или закрыть, имеют определенное расширение. Параметр lpszDefExt позволяет задать расширение файлов, используемое по умолчанию. То есть, если пользователь при определении имени файла не укажет расширение, имени файла автоматически присваивается расширение, принятое по умолчанию. Если при определении свойств диалоговой панели программист присвоит параметру lpszDefExt значение NULL, то расширение файлов должно задаваться пользователем явно.

В некоторых случаях требуется, чтобы диалоговые панели отображались с уже выбранным именем файла. Чтобы указать имя файла, используемое по умолчанию, применяется параметр lpszFileName. Если параметр lpszFileName имеет значение NULL, данная возможность не реализуется.

С помощью флага dwFlags можно изменить внешний вид и некоторые другие характеристики стандартных диалоговых панелей класса CFileDialog. В него можно записать комбинацию флагов, управляющих различными характеристиками этих панелей. Например, флаг OFN_HIDEREADONLY означает, что из диалоговой панели удаляется переключатель "Read Only", а флаг OFN_OVERWRITEPROMPT (используемый для панели Save As) - что необходимо выводить диалоговую панель с предупреждением, если пользователь выбирает для сохранения имя уже существующего файла.

Диалоговые панели выбора файлов обычно имеют список так называемых фильтров, включающих названия типов файлов и расширения имен файлов данного типа. Выбрав фильтр, пользователь указывает, что он желает работать только с файлами определенного типа, имеющими соответствующее расширение. Файлы с другими расширениями в диалоговых панелях не отображаются.

Список фильтров можно указать через параметр lpszFilter. Одновременно можно указать несколько фильтров. Каждый фильтр задается двумя строками - строкой, содержащей имя фильтра, и строкой, в которой перечислены соответствующие ему расширения имен файлов. Если одному типу соответствует несколько расширений, они разделяются символом ;. Строка, содержащая имя фильтра, отделяется от строки с расширениями файлов символом |. Если используется несколько фильтров, то они также отделяются друг от друга символом |. Например, в качестве строки, задающей фильтры, можно использовать строку вида:

    char szFilters[] =
    " Data (*.dat) |*.dat| Packed Data (*.pac;*.wav) | *.pac; *.wav| All Files (*.*)| *.* ||";

Диалоговые панели, представленные объектами класса CFileDialog, могут иметь или не иметь родительского окна. Чтобы указать родительское окно, нужно передать конструктору CFileDialog указатель на него через параметр pParentWnd.

Методы класса CFileDialog

Создание объекта класса CFileDialog еще не вызывает отображения соответствующей диалоговой панели. Для этого необходимо воспользоваться методом DoModal класса CFileDialog.При вызове метода DoModal для ранее созданного объекта класса CFileDialog на экране открывается соответствующая диалоговая панель. После того, как пользователь завершает работу с диалоговой панелью, метод DoModal вернет значение IDOK или IDCANCEL в случае успешного завершения и нуль - в случае возникновения ошибок:

    CFileDialog dlgOpen(TRUE);
    int iResult=dlgOpen.DoModal();

После того, как пользователь закроет диалоговую панель и метод DoModal вернет управление, можно воспользоваться другими методами класса CFileDialog , чтобы определить имена выбранных файлов:

  • GetPathName - Определяет полный путь файла
  • GetFileName - Определяет имя выбранного файла
  • GetFileExt - Определяет расширение имени выбранного файла
  • GetFileTitle - Позволяет определить заголовок выбранного файла
  • GetNextPathName - Если диалоговая панель позволяет выбрать сразу несколько файлов, то этот метод можно использовать для определения полного пути следующего из выбранных файлов
  • GetReadOnlyPref - Позволяет узнать состояние атрибута "только для чтения" (read-only) выбранного файла
  • GetStartPosition - Возвращает положение первого элемента из списка имен файлов

Наиболее важный метод - GetPathName. Он получает полный путь файла, выбранного из диалоговых панелей Open или Save As. Если диалоговая панель позволяет выбрать сразу несколько файлов, тогда метод GetPathName возвращает массив строк, состоящий из нескольких строк, заканчивающихся двоичным нулем. Первая из данных строк содержит путь к каталогу, в котором расположены выбранные файлы, остальные строки содержат имена выбранных файлов. Выделение строки, содержащей путь к каталогу, проблем не вызывает, а чтобы получить имена выбранных файлов, необходимо воспользоваться методами GetStartPosition и GetNextPathName.

Метод GetStartPosition возвращает значение типа POSITION. Оно предназначено для передачи методу GetNextPathName и получения очередного имени выбранного файла. Если пользователь не выбрал ни одного файла, метод GetStartPosition возвращает значение NULL. Значение, полученное этим методом, следует записать во временную переменную типа POSITION и передать ссылку на нее методу GetNextPathName. Метод GetNextPathName вернет полный путь первого из выбранных в диалоговой панели файлов и изменит значение переменной pos, переданной методу по ссылке. Новое значение pos можно использовать для последующих вызовов метода GetNextPathName и получения путей всех остальных выбранных файлов. Когда метод GetNextPathName вернет имена всех выбранных файлов, в переменную pos записывается значение NULL.

В панелях Open и Save As имеется переключатель "ReadOnly". По умолчанию этот преключатель не отображается. Если есть необходимость воспользоваться этим переключателем, то нужно отказаться от использования флага OFN_HIDEREADONLY.

Метод GetReadOnlyPref позволяет определить положение переключателя "ReadOnly". Если переключатель включен, то метод GetReadOnlyPref возвращает ненулевое значение. В противном случае GetReadOnlyPref возвращает нуль.

4. Панель выбора шрифта (класс CFontDialog)

Стандартная диалоговая панель Font предназначена для выбора шрифта. Эта панель отображает список шрифтов, установленных в системе, и позволяет выбрать название шрифта, его начертание и другие параметры.

Для управления диалоговой панелью Font в библиотеку классов MFC включен класс CFontDialog. Методы этого класса можно использовать для отображения панели Font и определения характеристик шрифта, выбранного пользователем. Конструктор класса CFontDialog:

    CFontDialog(LPLOGFONT lplfInitial=NULL,
    DWORD dwFlags=CF_EFFECTS | CF_SCREENFONTS,
    CDC* pdcPrinter,CWnd* pParentWnd=NULL);

Все параметры конструктора являются необязательными. Настройка стандартной панели выбора шрифта, которая выполняется конструктором класса CFontDialog по умолчанию, удовлетворяет большинству пользователей.

Параметр lplfInitial является указателем на структуру LOGFONT, описывающую логический шрифт. Если этот параметр используется, то в диалоговой панели по умолчанию будет выбран шрифт, наиболее соответствующий шрифту, описанному в структуре LOGFONT.

Параметр dwFlags задает набор флагов, управляющий различными режимами работы панели. Например, флаг CF_EFFECTS позволяет пользователю создавать подчеркнутые и перечеркнутые буквы, определять цвет букв, а флаг CF_SCREENFONTS - разрешает выбирать только экранные шрифты.

Через параметр pdcPrinter можно передать конструктору контекст отображения принтера, шрифты которого будут представлены в диалоговой панели Font. Данный параметр используется только в том случае, если в параметре dwFlags указаны флаги CF_PRINTERFONTS или CF_BOTH.

Через параметр pParentWnd можно указать родительское окно для диалоговой панели Font.

Методы класса CFontDialog

Для отображения диалоговой панели Font предназначен виртуальный метод DoModal. Если пользователь выбрал шрифт и нажал кнопку OK, метод DoModal возвращает идентификатор IDOK, если пользователь отменил выбор шрифта, метод DoModal возвращает идентификатор IDCANCEL:

    CFontDialog dlgFont;
    int iResult=dlgFont.DoModal();

Остальные методы класса предназначены для определения характеристик выбранного пользователем шрифта.

Метод GetCurrentFont позволяет сразу определить все характеристики выбранного шрифта, записав их в структуру LOGFONT.

Остальные методы класса позволяют определить только отдельные характеристики выбранного шрифта:

  • GetFaceName - Возвращает имя выбранного шрифта
  • GetStyleName - Возвращает имя стиля выбранного шрифта
  • GetSize - Возвращает размер выбранного шрифта
  • GetColor - Возвращает цвет выбранного шрифта
  • GetWeight - Возвращает плотность выбранного шрифта
  • IsStrikeOut - Определяет, является ли шрифт выделенным перечеркнутой линией
  • IsUnderline - Определяет, является ли шрифт выделенным подчеркиванием
  • IsBold - Определяет, является ли шрифт жирным
  • IsItalic - Определяет, является ли шрифт наклонным

5. Панель для вывода документов на печать (класс CPrintDialog)

Класс CPrintDialog можно использовать для создания двух видов диалоговых панелей, предназначенных для печати документов и выбора форматов документов. Кроме класса CPrintDialog можно также использовать класс CPageSetupDialog. Он позволяет создать диалоговую панель для выбора формата документа, имеющую несколько иной вид.

В приложениях, подготовленных с использованием средств MFC AppWizard и построенные по модели документ-облик, по умолчанию встроена возможность вывода редактируемого документа на печать.

В меню File такого приложения находятся три строки (Print, Print Preview и Print Setup), которые управляют процессом печати документов, подготовленных в приложении. Чтобы распечатать документ, достаточно выбрать из меню File строку Print. На экране появится диалоговая панель Print. В ней можно выбрать печатающее устройство для печати документов (группа Name), указать, будет печататься весь документ либо его часть (группа Print range), а также сколько копий документа будет напечатано (группа Copies). Также можно настроить различные характеристики печатающего устройства, если нажать кнопку Properties в группе Printer.

Если требуется определить только печатающее устройство и формат документа, из меню File следует выбрать строку Printer Setup. В группе Printer можно указать печатающее устройство и настроить его соответствующим образом. Группа Paper задает формат бумаги и режим подачи бумаги в печатающее устройство. Группа Orientation включает только один переключатель, определяющий ориентацию бумаги. Он принимает положение Portrait для вертикальной ориентации изображения на бумаге (режим "портрет") или Landscape для горизонтальной ориентации изоборажения на бумаге (режим "ландшафт").

Строка Print Preview меню File выбирается для предварительного просмотра документа перед печатью. При этом главное окно приложения изменит свой внешний вид и можно будет просмотреть, как будет выглядеть документ после печати.

Если не требуется выполнять специфическую обработку документа перед печатью, то вряд ли понадобится самостоятельное добавление программного кода, отвечающего за процесс печати. Просто следует отметить, что процедура создания панелей, связанных с печатью документа, практически ничем не отличается от создания выше описанных стандартных диалоговых панелей.

6. Панель для выполнения поиска и замены (класс CFindReplaceDialog)

Класс CFindReplaceDialog предназначен для управления диалоговыми окнами Find и Replace. Диалоговая панель Find используется для поиска известных строк в документе приложения, а панель Replace позволяет замену одной строки на другую.

Важным отличием диалоговых панелей Find и Replace от других стандартных диалоговых панелей является то, что они представляют собой немодальные диалоговые панели. Поэтому процесс создания этих панелей значительно отличается от процесса создания стандартных панелей для выбора цвета, шрифта и имен файла.

7. Пример Windows-приложения, использующего стандартные панели

Отсюда можно взять рабочую программу в диалоговом режиме, использующего стандартные панели управления.


Глава 14

14. Оптимизация вывода графики на экран

    1. Описание класса CView
    2. Объекты GDI
    3. GDI-атрибуты контекста устройства
    4. Методы для рисования линий и фигур
    5. Пример графической программы с оптимизацией

1. Описание класса CView

Объекты класса CView имеют окно, представляющее собой обычную прямоугольную область экрана, без рамки, меню и других элементов. Вывод в такое окно (в том числе и текста) производится в графическом виде. При работе с этим классом очень важны моменты, рассматриваемые ниже.

Сообщение и метод OnDraw

Предположим, в окне отображен какой-либо рисунок, который затем перекрыт другим окном, а через некоторое время верхнее окно сдвигается с рисунка. Если перекрывающее окно небольшое, например, окно меню, то при его закрытии перекрытая часть восстанавливается системой Windows. В большинстве же случаев Windows обращается за помощью к владеющему окном приложению и посылает ему сообщение о необходимости восстановить окно. Дело разработчика приложения, реагировать на это сообщение или нет. Если обработка сообщения не предусмотрена, то велика вероятность исчезновения части рисунка из окна.

Итак, для обеспечения корректного отображения информации в окне нужна функция, которая будет перерисовывать содержимое окна всякий раз, когда оно потребуется. Для этого служит метод OnDraw . В качестве параметра этому методу передается указатель на контекст устройства, используя который, можно перерисовать окно.

Графическое устройство и его контекст

Работа с графическими устройствами, такими, как принтер, плоттер, дисплей в системе Windows вообще и в Visual C++ в частности является аппаратно-независимой. Это значит, что при программировании под Windows средств прямого доступа к аппаратуре нет. Все взаимодействие с ней производится через функции API (Application Program Interface). При этом для вывода на графические устройства используется один и тот же набор функций.

Для того, чтобы определить, на какое устройство осуществляется вывод, в Windows и в Visual C++ используется понятие контекста устройства (device context). Далее везде будет рассматриваться контекст устройства, реализованный в Visual C++. Контекст устройства - это объект класса CDC, содержащий все методы для построения изображения в окне. Кроме того, он содержит данные о графическом устройстве вывода. Для осуществления вывода создается контекст устройства и тем самым определяется конкретное устройство для вывода. А далее к созданному объекту можно применять все имеющиеся методы класса CDC.

При выводе многие параметры долгое время остаются неизменными, например, цвет линии и другие. Указывать все такие параметры при каждом обращении к методам вывода неудобно. Контекст устройства содержит целый ряд таких параметров, обычно их называют атрибутами контекста устройства. Методы имеют лишь те параметры, что определяют существо их действия, а остальные атрибуты для рисования берутся из контекста устройства. При создании контекста устройства его атрибуты устанавливаются по умолчанию. Затем их можно изменять методами класса CDC.

Поврежденная область и поврежденный прямоугольник

При работе с окнами обычно "повреждается" только часть окна, так что перерисовывать все окно неэкономно. Поэтому система Windows фиксирует не только необходимость перерисовки, но и информацию о поврежденной области (invalid region). Но более важным является понятие поврежденный прямоугольник (invalid rectangle) - минимальный прямоугольник, покрывающий поврежденную область. Windows и Visual C++ обеспечивают следующие возможности при работе с поврежденным прямоугольником:

Методы GetUpdateRect и GetUpdateRgn класса CWnd позволяют получить описание поврежденного прямоугольника и поврежденной области.

Если производить перерисовку стандартным путем (например, внутри метода обработки сообщения OnDraw), то рисование в окне результативно только в области поврежденного прямоугольника. В этом случае говорят, что поврежденный прямоугольник задает область усечения, вне которой содержимое окна не изменяется.

Если в момент возникновения поврежденной области сформированное ранее системой Windows сообщение WM_PAINT о необходимости перерисовки окна не было обработано приложением и стоит в очереди приложения, новое сообщение WM_PAINT в очередь не добавляется. В качестве поврежденной области берется минимальный прямоугольник, покрывающий одновременно старый и новый прямоугольники.

Методы Invalidate, InvalidateRect и InvalidateRgn класса CWnd позволяют объявить соответственно клиентскую область, некоторые прямоугольник и область окна поврежденными и послать сообщение WM_PAINT в очередь приложения.

Методы ValidateRect и ValidateRgn класса CWnd позволяют отменить объявление некоторого прямоугольника или области поврежденными. Это ведет к корректировке текущего поврежденного прямоугольника.

При создании окна поврежденный прямоугольник устанавливается равным клиентской части окна. Обработчик сообщения Update класса CView также устанавливает поврежденный прямоугольник равным клиентской части окна.

2. Объекты GDI

При рисовании фигур в Visual C++ используются специальные объекты GDI, т.е. объекты интерфейса графического устройства (GDI - Graphics Device Interface) системы Windows.

Название | Класс | Тип определителя в Windows
Перо | CPen | HPEN
Кисть | CBrush | HBRUSH
Шрифт | CFont | HFONT
Растровое изображение | CBitmap | HBITMAP
Палитра | CPalette | HPALETTE
Область (регион) | CRgn | HRGN

Как и любые другие объекты в Visual C++, они должны быть созданы соответствующим образом. Создание объекта должно включать в себя и связывание с соответствующим объектом системы Windows. Эта операция осуществляется методом, имя которого начинается с префикса Create. Он может быть выполнен как после создания объекта Visual C++, так и в конструкторе объекта:

CPen Pen1; Pen1.CreatePen(PS_DOT, 5, RGB(0,0,0)); // вариант 1
CPen Pen2(PS_DOT, 5, Rface="Courier New">GB(0,0,0)); // вариант 2

При завершении работы с объектом GDI необходимо обеспечить (вернее, не нарушать) его автоматическое удаление при выходе из соответствующей области видимости.

Перо - объект для рисования линий. При создании пера можно задать его ширину, цвет и тип линии. Для создания пера используются метод CreatePen.

Кисть представляет собой растровое изображение размером 8х8 пикселей для заполнения различных областей. Различают два варианта понятия кисти: логическая и физическая. Объект GDI задает логическую кисть, которая определяет полный набор свойств: цвет, стиль и т.п., - указываемых при ее создании. Возможно, не все эти свойства могут быть реализованы на данном компьютере. Свойства физической кисти реально воспроизводятся физическими устройствами. Они являются наиболее близким реализуемым приближением к свойствам логической кисти.

Различают четыре вида логических кистей, для создания которых используются различные методы:

    Для создания сплошной кисти, все пиксели которой одного цвета, используется метод CreateSolidBrush. Стандартные кисти уже имеются в операционной системе (таких кистей 7). Чтобы их создать, используется метод CreateStockBrush. Этот метод на самом деле создает только объект класса CBrush. Сама кисть берется готовой из операционной системы. Штриховые кисти имеют цвет и штриховой рисунок. Имеется 6 видов рисунка. Кисти создаются такими методами CreateHatchBrush и GreateSysColorBrush. Шаблонные кисти могут иметь произвольный рисунок, задаваемый растровым изображением (BMP), либо аппаратно-независимым растровым изображением (DIB). Для создания кисти используются методы CreatePatternBrush, CreateDIBPatternBrush.

Растровое изображение представляет собой объект, инкапсулирующий прямоугольную область, состоящую из пикселов. Создав такой объект, можно затем задавать в этой области любое изображение, а также считывать и записывать ее в файл и производить с ней другие действия.

Палитры появились потому, что многие типы мониторов физически могут воспроизводить очень много цветов, а видеокартам не хватает видеопамяти, чтобы поддерживать все цвета одновременно. Например, монитор воспроизводит сотни тысяч цветов, а видеокарта отводит для одного пиксела байт и тем самым может хранить 256 цветов для окраски одной точки. Для более полного использования возможностей монитора и существуют палитры. Они сопоставляют цвета числам от 0 до 2n-1, которые могут храниться в ячейке, отведенной для одного пиксела.

В Windows различают понятия физической и логической палитры. Логическая предназначена для работы самого приложения. Программист не ограничен в выборе палитры. Два приложения могут использовать две различные палитры и осуществлять вывод на один и тот же экран. При этом физическая, естественно, может быть установлена только одна.

Когда нужного цвета логической палитры нет в физической, Windows пытается добавить его, а если это невозможно, коммутирует этот цвет логической палитры с наиболее близким цветом физической.

Для создания палитры используются методы CreatePalette и CreateHalftonePalette. Созданную палитру можно изменить методом SetPaletteEntries.

В Windows существует еще один способ управления цветом - макросом RGB. Он задает функцию, возвращающую значение типа COLORREF. У нее три параметра, задающий красный, зеленый и голубой компонент устанавливаемого цвета. Каждый компонент может принимать значения от 0 до 255. Итоговый цвет получается смешением красного, зеленого и синего цветов в соответствующих пропорциях.

3. GDI-атрибуты контекста устройства

Значениями ряда атрибутов контекста устройства являются объекты GDI. Как отмечалось ранее, в вызовах методов, рисующих фигуры на экране, многие параметры не указываются, а берутся из атрибутов контекста устройства. Чтобы эти параметры отличались от установленных в контексте устройства по умолчанию, необходимо:

    Сохранить старое значение атрибута. Установить новое. Выполнить необходимые действия. Восстановить старое значение атрибута.

Последовательность этих действий иллюстрируется примером:

    void CMyView::OnDraw(CDC* pDC)
    {
      CPen Pen;
      if(Pen.CreatePen(PS_SOLID,2,RGB(0,0,0))
      {
        // сохранение старого и установление нового значения атрибута
        CPen* pOldPen=pDC->SelectObject(&Pen);
        // выполнение необходимых действий
        pDC->MoveTo(....); pDC->LineTo(....);
        // восстановление старого значения атрибута
        pDC->SelectObject(pOldPen);
      }
    }

Метод SelectObject в качестве результата возвращает указатель на текущее перо и делает текущим перо, указанное в качестве параметра метода.

4. Методы для рисования линий и фигур

Пикселы

Для установки цвета пикселя с логическими координатами (x,y) используются метод SetPixel. Получить значение цвета пикселя можно методом GetPixel.

Цвет задается функцией RGB. Как уже отмечалось выше, если в физической палитре нет данного цвета, задаваемого фактическим параметром, Windows устанавливает наиболее близкий цвет. Он-то и возвращается методом SetPixel. Следует также отметить, что, хотя координаты являются логическими, устанавливается цвет только одного пикселя, даже если единица измерения для текущей системы координат иная.

Линии

Прежде, чем описывать методы для рисования линий, рассмотрим два важных для них атрибута контекста устройства.

Первый называют текущим пером. Его значением является перо как объект GDI. Любая линия (в том числе и ограничивающая фигуру) рисуется пером. Если метод не содержит явного параметра, задающего перо, то для рисования берется текущее перо, которое можно установить методом SelectObject. Или, если в качестве параметра передать одну из констант BLACK_PEN, NULL_PEN, WHITE_PEN, то методом SelectStockObject

Второй важный атрибут - текущая позиция пера. Чтобы изменить координаты текущей позиции пера, используются метод MoveTo. Чтобы нарисовать прямую линию от текущей позиции пера до нужной точки с логическими координатами (x,y), используется метод LineTo. После выполнения метода LineTo заданная в нем точка становится текущей позицией пера.

Если имеется массив точек и требуется соединить линией каждую следующую точку с предыдущей, можно использовать метод PolyLine, в котором первый параметр - указатель на массив элементов типа POINT, а второй равен количеству точек. При выполнении этого метода текущая позиция пера не изменяется.

Следующий метод аналогичен PolyLine за исключением того, что он устанавливает текущую позицию пера равной последней точке массива - PolyLineTo.

Если же требуется соединить между собой все точки, содержащиеся в массиве, можно вызвать метод PolyPolyline.

Фигуры

В Visual C++ имеются методы для рисования: прямоугольника (Rectangle); эллипса (Ellipse); скругленного прямоугольника (RoundRect); сегмента эллипса (Chord); сектора эллипса (Pie); замкнутого многоугольника; составного замкнутого многоугольника.

Для рисования фигур важен атрибут контекста устройства, называемый текущей кистью. Он задает кисть как объект GDI, с помощью которого производится закрашивание внутренней области фигуры. Текущая кисть устанавливается методом SelectObject или SelectStockObject, если в качестве параметра передать одну из констант: BLACK_BRUSH, DKGRAY_BRUSH, GRAY_BRUSH, HOLLOW_BRUSH, LTGRAY_BRUSH, NULL_BRUSH, WHITE_BRUSH.

5. Пример графической программы с оптимизацией

Отсюда можно взять рабочую программу в диалоговом режиме, с оптимизированной графикой.

Во многих книжках работа с графикой описывается следующим образом:

    1. Все графические функции описывабтся в OnPaint().
    2. Далее в другом месте программы вызываются функции Invalidate, InvalidateRect или InvalidateRgn.
    3. Такой тип построения программы не совсем верен, так как в таком случае обычно всё мигает и это нервирует.
    4. Есть немного другой способ работы с графикой и ниже он будет описан.
Этот метод заключается в следующем :

    1. Вся графика рисуется в какой-то функции F().
    2. По событию таймера или по другим событиям вызывается F().
    3. Эдементы графики рисуются сначала в памяти, а потом выводятся на экран.
    4. Предворительные расчёты можно вести как в F() так и в других частях программы.
    5. Функция OnPaint() содержит копию функции F(), это нужно только для перерисовки окна при изменении его положения или размера.


Глава 15

15. Создание собственных диалоговых окон

Для того, что бы создать собственное диалоговое окно надо сделать следующее:

    1) Создайте программу в диалоговом режиме ( с поддежкой MFC )
    2) Назовите её TEST, что бы было лучше сравнивать с моей рабочей программой
    3) Главный класс вашей программы будет CTestDlg
    4) Что бы создать другую диалоговую панель нужно создать новый класс, для этого выбери закладку ResourceView -> правой кнопкой на Dialog -> Insert Dialog -> создастся новый диалог.
    5) Имя диалога можно менять, поставте IDD_MY_DIALOG
    6) Дальше надо зарегистрировать новый класс, для этого: при открытом новом диалоге( это обязательно ) надо вызвать ClassWizard( в верхнем меню) -> Create new class -> надо ввести имя класса ( введите CMyDialog( MyDialog.cpp), имя должно начинаться с C, что означает class ) -> два раза ОК
    7) Теперь надо, чтобы выша главная программа "узнала" новый класс -> в начале файла TestDlg.cpp напишите строчку

      #include "MyDialog.h"

    8) Ну вот и всё, теперь можно использовать новое диалоговое окно ->

      CMyDialog MyDlg;
      MyDlg.DoModal();

    Отсюда можно взять рабочую программу в диалоговом режиме, в которой используется собственное диалоговое окно.


Глава 16

16. Работа с WinSocket

Socket (гнездо, разъем) - абстрактное программное понятие, используемое для обозначения в прикладной программе конечной точки канала связи с коммуникационной средой, образованной вычислительной сетью. При использовании протоколов TCP/IP можно говорить, что socket является средством подключения прикладной программы к порту (см. выше) локального узла сети.

Socket-интерфейс представляет собой просто набор системных вызовов и/или библиотечных функций языка программирования СИ, разделенных на четыре группы:

    1. Локального управления
    2. Установления связи
    3. Обмена данными (ввода/вывода)
    4. Закрытия связи
    5. Пример использования WinSocket

Ниже рассматривается подмножество функций socket-интерфейса, достаточное для написания сетевых приложений, реализующих модель "клиент-сервер" в режиме с установлением соединения.

1. Функции локального управления

Функции локального управления используются, главным образом, для выполнения подготовительных действий, необходимых для организации взаимодействия двух программ-партнеров. Функции носят такое название, поскольку их выполнение носит локальный для программы характер.

1.1 Создание socket'а

Создание socket'а осуществляется следующим системным вызовом

         #include <sys/socket.h>
  int socket (domain, type, protocol)
   int domain;         int type;       int protocol;

Аргумент domain задает используемый для взаимодействия набор протоколов (вид коммуникационной области), для стека протоколов TCP/IP он должен иметь символьное значение AF_INET (определено в sys/socket.h).

Аргумент type задает режим взаимодействия:

  • SOCK_STREAM - с установлением соединения;
  • SOCK_DGRAM - без установления соединения.

Аргумент protocolзадает конкретный протокол транспортного уровня (из нескольких возможных в стеке протоколов). Если этот аргумент задан равным 0, то будет использован протокол "по умолчанию" (TCP для SOCK_STREAM и UDP для SOCK_DGRAM при использовании комплекта протоколов TCP/IP).

При удачном завершении своей работы данная функция возвращает дескриптор socket'а - целое неотрицательное число, однозначно его идентифицирующее. Дескриптор socket'а аналогичен дескриптору файла ОС UNIX.

При обнаружении ошибки в ходе своей работы функция возвращает число "-1".

1.2. Связывание socket'а

Для подключения socket'а к коммуникационной среде, образованной вычислительной сетью, необходимо выполнить системный вызов bind, определяющий в принятом для сети формате локальный адрес канала связи со средой. В сетях TCP/IP socket связывается с локальным портом. Системный вызов bind имеет следующий синтаксис:

        #include <sys/types.h>
#include <sys/socket.h>     #include <netinet/in.h>
  int bind (s, addr, addrlen)         int s;
       struct sockaddr *addr;         int addrlen;

Аргумент s задает дескриптор связываемого socket'а.

Аргумент addr в общем случае должен указывать на структуру данных, содержащую локальный адрес, приписываемый socket'у. Для сетей TCP/IP такой структурой является sockaddr_in.

Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом addr.

Структура sockaddr_in используется несколькими системными вызовами и функциями socket-интерфейса и определена в include-файле in.h следующим образом:

     struct sockaddr_in {         short sin_family;
u_short sin_port;         struct in_addr sin_addr;
     char sin_zero[8];         };

Поле sin_family определяет используемый формат адреса (набор протоколов), в нашем случае (для TCP/IP) оно должно иметь значение AF_INET.

Поле sin_addr содержит адрес (номер) узла сети.

Поле sin_port содержит номер порта на узле сети.

Поле sin_zero не используется.

Определение структуры in_addr (из того же include-файла) таково:

     struct in_addr {         union {
    u_long S_addr;             /*
       другие (не интересующие нас)
        члены объединения             */
         } S_un;     #define s_addr S_un.S_addr         };

Структура sockaddr_in должна быть полностью заполнена перед выдачей системного вызова bind. При этом, если поле sin_addr.s_addr имеет значение INADDR_ANY, то системный вызов будет привязывать к socket'у номер (адрес) локального узла сети.

В случае успеха bind возвращает 0, в противном случае - "-1".

2. Функции установления связи

Для установления связи "клиент-сервер" используются системные вызовы listen и accept (на стороне сервера), а также connect (на стороне клиента). Для заполнения полей структуры socaddr_in, используемой в вызове connect, обычно используется библиотечная функция gethostbyname, транслирующая символическое имя узла сети в его номер (адрес).

2.1. Ожидание установления связи

Системный вызов listen выражает желание выдавшей его программы-сервера ожидать запросы к ней от программ-клиентов и имеет следующий вид:

     #include <sys/socket.h>     int listen (s, n)
         int s;         int n;

Аргумент s задает дескриптор socket'а, через который программа будет ожидать запросы к ней от клиентов. Socket должен быть предварительно создан системным вызовом socketи обеспечен адресом с помощью системного вызова bind.

Аргумент n определяет максимальную длину очереди входящих запросов на установление связи. Если какой-либо клиент выдаст запрос на установление связи при полной очереди, то этот запрос будет отвергнут.

Признаком удачного завершения системного вызова listen служит нулевой код возврата.

2.2. Запрос на установление соединения

Для обращения программы-клиента к серверу с запросом на установление логической соединения используется системный вызов connect, имеющий следующий вид

        #include <sys/types.h>
  #include <sys/socket.h>     #include <netinet/in.h>
 int connect (s, addr, addrlen)         int s;
 struct sockaddr_in *addr;         int addrlen;

Аргумент s задает дескриптор socket'а, через который программа обращается к серверу с запросом на соединение. Socket должен быть предварительно создан системным вызовом socketи обеспечен адресом с помощью системного вызова bind.

Аргумент addr должен указывать на структуру данных, содержащую адрес, приписанный socket'у программы-сервера, к которой делается запрос на соединение. Для сетей TCP/IP такой структурой является sockaddr_in. Для формирования значений полей структуры sockaddr_in удобно использовать функцию gethostbyname.

Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом addr.

Для того, чтобы запрос на соединение был успешным, необходимо, по крайней мере, чтобы программа-сервер выполнила к этому моменту системный вызов listen для socket'а с указанным адресом.

При успешном выполнении запроса системный вызов connect возвращает 0, в противном случае - "-1" (устанавливая код причины неуспеха в глобальной переменной errno).

Примечание. Если к моменту выполнения connect используемый им socket не был привязан к адресу посредством bind ,то такая привязка будет выполнена автоматически.

Примечание. В режиме взаимодействия без установления соединения необходимости в выполнении системного вызова connect нет. Однако, его выполнение в таком режиме не является ошибкой - просто меняется смысл выполняемых при этом действий: устанавливается адрес "по умолчанию" для всех последующих посылок дейтаграмм.

2.3. Прием запроса на установление связи

Для приема запросов от программ-клиентов на установление связи в программах-серверах используется системный вызов accept, имеющий следующий вид:

        #include <sys/socket.h>
#include <netinet/in.h>
int accept (s, addr, p_addrlen)         int s;
 struct sockaddr_in *addr;         int *p_addrlen;

Аргумент s задает дескриптор socket'а, через который программа-сервер получила запрос на соединение (посредством системного запроса listen ).

Аргумент addr должен указывать на область памяти, размер которой позволял бы разместить в ней структуру данных, содержащую адрес socket'а программы-клиента, сделавшей запрос на соединение. Никакой инициализации этой области не требуется.

Аргумент p_addrlen должен указывать на область памяти в виде целого числа, задающего размер (в байтах) области памяти, указываемой аргументом addr.

Системный вызов accept извлекает из очереди, организованной системным вызовом listen, первый запрос на соединение и возвращает дескриптор нового (автоматически созданного) socket'а с теми же свойствами, что и socket, задаваемый аргументом s. Этот новый дескриптор необходимо использовать во всех последующих операциях обмена данными.

Кроме того после удачного завершения accept:

  1. область памяти, указываемая аргументом addr, будет содержать структуру данных (для сетей TCP/IP это sockaddr_in), описывающую адрес socket'а программы-клиента, через который она сделала свой запрос на соединение;
  2. целое число, на которое указывает аргумент p_addrlen, будет равно размеру этой структуры данных.

Если очередь запросов на момент выполнения accept пуста, то программа переходит в состояние ожидания поступления запросов от клиентов на неопределенное время (хотя такое поведение accept можно и изменить).

Признаком неудачного завершения accept служит отрицательное возвращенное значение (дескриптор socket'а отрицательным быть не может).

Примечание. Системный вызов accept используется в программах-серверах, функционирующих только в режиме с установлением соединения.

2.4. Формирование адреса узла сети

Для получения адреса узла сети TCP/IP по его символическому имени используется библиотечная функция

         #include <netinet/in.h>
#include <netdb.h>
struct hostent *gethostbyname (name)
 char *name;

Аргумент name задает адрес последовательности литер, образующих символическое имя узла сети.

При успешном завершении функция возвращает указатель на структуру hostent, определенную в include-файле netdb.h и имеющую следующий вид

         struct hostent {         char *h_name;
                                                 char
**h_aliases;         int h_addrtype;
 int h_lenght;         char *h_addr;         };

Поле h_name указывает на официальное (основное) имя узла.

Поле h_aliases указывает на список дополнительных имен узла (синонимов), если они есть.

Поле h_addrtype содержит идентификатор используемого набора протоколов, для сетей TCP/IP это поле будет иметь значение AF_INET.

Поле h_lenght содержит длину адреса узла.

Поле h_addr указывает на область памяти, содержащую адрес узла в том виде, в котором его используют системные вызовы и функции socket-интерфейса.

Пример обращения к функции gethostbyname для получения адреса удаленного узла в программе-клиенте, использующей системный вызов connect для формирования запроса на установления соединения с программой-сервером на этом узле, рассматривается ниже.

3. Функции обмена данными

В режиме с установлением логического соединения после удачного выполнения пары взаимосвязанных системных вызовов connect (в клиенте) и accept (в сервере) становится возможным обмен данными.

Этот обмен может быть реализован обычными системными вызовами read и write, используемыми для работы с файлами (при этом вместо дескрипторов файлов в них задаются дескрипторы socket'ов).

Кроме того могут быть дополнительно использованы системные вызовы send и recv, ориентированные специально на работу с socket'ами.

Примечание. Для обмена данными в режиме без установления логического соединения используются, как правило, системные вызовы sendtoи recvfrom. Sendto позволяет специфицировать вместе с передаваемыми данными (составляющими дейтаграмму) адрес их получателя. Recvfrom одновременно с доставкой данных получателю информирует его и об адресе отправителя.

3.1. Посылка данных

Для посылки данных партнеру по сетевому взаимодействию используется системный вызов send, имеющий следующий вид

         #include <sys/types.h>
#include <sys/socket.h>
int send (s, buf, len, flags)         int s;
 char *buf;         int len;         int flags;

Аргумент s задает дескриптор socket'а, через который посылаются данные.

Аргумент buf указывает на область памяти, содержащую передаваемые данные.

Аргумент len задает длину (в байтах) передаваемых данных.

Аргумент flags модифицирует исполнение системного вызова send. При нулевом значении этого аргумента вызов send полностью аналогичен системному вызову write.

При успешном завершении send возвращает количество переданных из области, указанной аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается "переполненным", то send переводит программу в состояние ожидания до момента его освобождения.

3.2. Получение данных

Для получения данных от партнера по сетевому взаимодействию используется системный вызов recv, имеющий следующий вид

         #include <sys/types.h>
 #include <sys/socket.h>
 int recv (s, buf, len, flags)         int s;
   char *buf;         int len;         int flags;

Аргумент s задает дескриптор socket'а, через который принимаются данные.

Аргумент buf указывает на область памяти, предназначенную для размещения принимаемых данных.

Аргумент len задает длину (в байтах) этой области.

Аргумент flags модифицирует исполнение системного вызова recv. При нулевом значении этого аргумента вызов recv полностью аналогичен системному вызову read.

При успешном завершении recv возвращает количество принятых в область, указанную аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается "пустым", то recv переводит программу в состояние ожидания до момента появления в нем данных.

4. Функции закрытия связи

Для закрытия связи с партнером по сетевому взаимодействию используются системные вызовы close и shutdown.

4.1. Системный вызов close

Для закрытия ранее созданного socket'а используется обычный системный вызов close, применяемый в ОС UNIX для закрытия ранее открытых файлов и имеющий следующий вид

             int close (s)         int s;

Аргумент s задает дескриптор ранее созданного socket'а.

Однако в режиме с установлением логического соединения (обеспечивающем, как правило, надежную доставку данных) внутрисистемные механизмы обмена будут пытаться передать/принять данные, оставшиеся в канале передачи на момент закрытия socket'а. На это может потребоваться значительный интервал времени, неприемлемый для некоторых приложений. В такой ситуации необходимо использовать описываемый далее системный вызов shutdown.

4.2. Сброс буферизованных данных

Для "экстренного" закрытия связи с партнером (путем "сброса" еще не переданных данных) используется системный вызов shutdown, выполняемый перед close и имеющий следующий вид

            int shutdown (s, how)         int s;
         int how;

Аргумент s задает дескриптор ранее созданного socket'а.

Аргумент how задает действия, выполняемые при очистке системных буферов socket'а:

  • 0 - сбросить и далее не принимать данные для чтения из socket'а;
  • 1 - сбросить и далее не отправлять данные для посылки через socket;
  • 2 - сбросить все данные, передаваемые через socket в любом направлении.

5. Пример использования socket-интерфейса

В данном разделе рассматривается использование socket-интерфейса в режиме взаимодействия с установлением логического соединения на очень простом примере взаимодействия двух программ (сервера и клиента), функционирующих на разных узлах сети TCP/IP.

Содержательная часть программ примитивна:

  1. сервер, приняв запрос на соединение, передает клиенту вопрос "Who are you?";
  2. клиент, получив вопрос, выводит его в стандартный вывод и направляет серверу ответ "I am your client" и завершает на этом свою работу;
  3. сервер выводит в стандартный вывод ответ клиента, закрывает с ним связь и переходит в состояние ожидания следующего запроса к нему.

Примечание. Предлагаемые ниже тексты программ предназначены только для иллюстрации логики взаимодействия программ через сеть, поэтому в них отсутствуют такие атрибуты программ, предназначенных для практического применения, как обработка кодов возврата системных вызовов и функций, анализ кодов ошибок в глобальной переменной errno, реакция на асинхронные события и т.п.

5.1. Программа-сервер

Текст программы-сервера на языке программирования СИ выглядит следующим образом

   1  #include <sys/types.h>
2  #include <sys/socket.h>
3  #include <netinet/in.h>
4  #include <netdb.h>
5  #include <memory.h>
6  #define SRV_PORT 1234
7  #define BUF_SIZE 64
8  #define TXT_QUEST "Who are you?\n"
9  main () {
10    int s, s_new;
11    int from_len;
12    char buf[BUF_SIZE];
13    struct sockaddr_in sin, from_sin;
14    s = socket (AF_INET, SOCK_STREAM, 0);
15    memset ((char *)&sin, '\0', sizeof(sin));
16    sin.sin_family = AF_INET;
17    sin.sin_addr.s_addr = INADDR_ANY;
18    sin.sin_port = SRV_PORT;
19    bind (s, (struct sockaddr *)&sin, sizeof(sin));
20    listen (s, 3);
21    while (1) {
22      from_len = sizeof(from_sin);
23      s_new = accept (s, &from_sin, &from_len);
24      write (s_new, TXT_QUEST, sizeof(TXT_QUEST));
25      from_len = read (s_new, buf, BUF_SIZE);
26      write (1, buf, from_len);
27      shutdown (s_new, 0);
28      close (s_new);
29      };
30    }

Строки 1...5 описывают включаемые файлы, содержащие определения для всех необходимых структур данных и символических констант.

Строка 6 приписывает целочисленной константе 1234 символическое имя SRV_PORT. В дальнейшем эта константа будет использована в качестве номера порта сервера. Значение этой константы должно быть известно и программе-клиенту.

Строка 7 приписывает целочисленной константе 64 символическое имя BUF_SIZE. Эта константа будет определять размер буфера, используемого для размещения принимаемых от клиента данных.

Строка 8 приписывает последовательности символов, составляющих текст вопроса клиенту, символическое имя TXT_QUEST. Последним символом в последовательности является символ перехода на новую строку '\n'. Сделано это для упрощения вывода текста вопроса на стороне клиента.

В строке 14 создается (открывается) socket для организации режима взаимодействия с установлением логического соединения (SOCK_STREAM) в сети TCP/IP (AF_INET), при выборе протокола транспортного уровня используется протокол "по умолчанию" (0).

В строках 15...18 сначала обнуляется структура данных sin, а затем заполняются ее отдельные поля. Использование константы INADDR_ANY упрощает текст программы, избавляя от необходимости использовать функцию gethostbyname для получения адреса локального узла, на котором запускается сервер.

Строка 19 посредством системного вызова bind привязывает socket, задаваемый дескриптором s, к порту с номером SRV_PORT на локальном узле. Bind завершится успешно при условии, что в момент его выполнения на том же узле уже не функционирует программа, использующая этот номер порта.

Строка 20 посредством системного вызова listen организует очередь на три входящих к серверу запроса на соединение.

Строка 21 служит заголовком бесконечного цикла обслуживания запросов от клиентов.

На строке 23, содержащей системный вызов accept, выполнение программы приостанавливается на неопределенное время, если очередь запросов к серверу на установление связи оказывается пуста. При появлении такого запроса accept успешно завершается, возвращая в переменной s_new дескриптор socket'а для обмена информацией с клиентом.

В строке 24 сервер с помощью системного вызова write отправляет клиенту вопрос.

В строке 25 с помощью системного вызова read читается ответ клиента.

В строке 26 ответ направляется в стандартный вывод, имеющий дескриптор файла номер 1. Так как строка ответа содержит в себе символ перехода на новую строку, то текст ответа будет размещен на отдельной строке дисплея.

Строка 27 содержит системный вывод shutdown, обеспечивающий очистку системных буферов socket'а, содержащих данные для чтения ("лишние" данные могут там оказаться в результате неверной работы клиента).

В строке 28 закрывается (удаляется) socket, использованный для обмена данными с очередным клиентом.

Примечание. Данная программа (как и большинство реальных программ-серверов) самостоятельно своей работы не завершает, находясь в бесконечном цикле обработки запросов клиентов. Ее выполнение может быть прервано только извне путем посылки ей сигналов (прерываний) завершения. Правильно разработанная программа-сервер должна обрабатывать такие сигналы, корректно завершая работу (закрывая, в частности, посредством close socket с дескриптором s).

5.2. Программа-клиент

Текст программы-клиента на языке программирования СИ выглядит следующим образом

   1  #include <sys/types.h>
2  #include <sys/socket.h>
3  #include <netinet/in.h>
4  #include <netdb.h>
5  #include <memory.h>
6  #define SRV_HOST "delta"
7  #define SRV_PORT 1234
8  #define CLNT_PORT 1235
9  #define BUF_SIZE 64
10  #define TXT_ANSW "I am your client\n"
11  main () {
12    int s;
13    int from_len;
14    char buf[BUF_SIZE];
15    struct hostent *hp;
16    struct sockaddr_in clnt_sin, srv_sin;
17    s = socket (AF_INET, SOCK_STREAM, 0);
18    memset ((char *)&clnt_sin, '\0',
                       sizeof(clnt_sin));
19    clnt_sin.sin_family = AF_INET;
20    clnt_sin.sin_addr.s_addr = INADDR_ANY;
21    clnt_sin.sin_port = CLNT_PORT;
22    bind (s, (struct sockaddr *)&clnt_sin,
                   sizeof(clnt_sin));
23    memset ((char *)&srv_sin, '\0',
                sizeof(srv_sin));
24    hp = gethostbyname (SRV_HOST);
25    srv_sin.sin_family = AF_INET;
26    memcpy ((char
*)&srv_sin.sin_addr,hp->h_addr,hp->h_length);
27    srv_sin.sin_port = SRV_PORT;
28    connect (s, &srv_sin, sizeof(srv_sin));
29    from_len = recv (s, buf, BUF_SIZE, 0);
30    write (1, buf, from_len);
31    send (s, TXT_ANSW, sizeof(TXT_ANSW), 0);
32    close (s);
33    exit (0);
34    }

В строках 6 и 7 описываются константы SRV_HOST и SRV_PORT, определяющие имя удаленного узла, на котором функционирует программа-сервер, и номер порта, к которому привязан socket сервера.

Строка 8 приписывает целочисленной константе 1235 символическое имя CLNT_PORT. В дальнейшем эта константа будет использована в качестве номера порта клиента.

В строках 17...22 создается привязанный к порту на локальном узле socket.

В строке 24 посредством библиотечной функции gethostbyname транслируется символическое имя удаленного узла (в данном случае "delta"), на котором должен функционировать сервер, в адрес этого узла, размещенный в структуре типа hostent.

В строке 26 адрес удаленного узла копируется из структуры типа hostent в соответствующее поле структуры srv_sin, которая позже (в строке 28) используется в системном вызове connect для идентификации программы-сервера.

В строках 29...31 осуществляется обмен данными с сервером и вывод вопроса, поступившего от сервера, в стандартный вывод.

Строка 32 посредством системного вызова close закрывает (удаляет) socket.


Глава 17

17. Простая программа, использующая MDI интерфейс

    В этом разделе рассматривается программа использующая MDI ( интерфейс многих документов )
    Мы создадим программу, в которой документом является графическое изображение - круг. В ToolBar будет создана иконка, при нажатие на которою будет вызываться диалоговое окно, позволяющее изменять координаты круга. Местоположение круга можно будет согранять в файл с расширением CIR.

    Создание проекта программы

    1. Создайте новый проект( у меня MDI ), использующая MDI интерфейс с поддержкой MFC. Все шесть шагов в MFC AppWizard оставте без изменения.

    2. Если вы сделали всё правильно, то создадутся пять классов : CMDIApp, CMainFrame, CChildFrame, CMDIDoc и CMDIView. В классе документов CMDIDoc вы пишите код для поддержки данных программы, а в классе представления CMDIView - код, отвечающий за то, что вы видите на экране. Вы будете писать код в функциях-элементах только этих двух классов.

    3. Объявляем элементы данных класса документа. Их будет два : координаты круга по X и по Y. Для этого открываем файл CMDIDoc.h и изменяем объявление класса CMDIDoc следующим образом:

     class CMDIDoc : public CDocument { protected:
// create from serialization only 	CMDIDoc();
DECLARE_DYNCREATE(CMDIDoc)  // Attributes public:
int m_X; //  координаты круга по x 	int m_Y;
//  координаты круга по y
// Operations ... ...
    4. Объявляем элементы данных класса представления. Их будет тоже два : координаты круга по X и по Y. Для этого открываем файл CMDIView.h и изменяем объявление класса CMDIView следующим образом:

      class CMDIView : public CView { protected:
// create from serialization only 	CMDIView();
DECLARE_DYNCREATE(CMDIView)  // Attributes public:
CMDIDoc* GetDocument();  	int m_X;
//  координаты круга по x 	int m_Y;
//  координаты круга по y  // Operations ... ...
    Как вы видите, имена переменных могут совпадать( обычно так и делается ).

    5. Инициализируем элементы данных класса документа. Для этого откройте файл MDIDoc.cpp, найдите в нём функцию OnNewDocument() и напишите в ней следующий код:

      BOOL CMDIDoc::OnNewDocument() { 	if
(!CDocument::OnNewDocument()) 		return FALSE;
m_X = 100; // начальное положение по X=100
                            m_Y = 100;
// начальное положение по Y=100
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
             return TRUE; }
    6. Инициализируем элементы данных класса представления. Для этого нужно создать функцию-элемент OnInitialUpdate() класса представления:

    Выберите ClassWizard в меню View. На странице Message Maps выберите следующие события:

           Class neme : CMDIView       Object ID   :
  CMDIView       Message      : OnInitialUpdate
    и нажмите на кнопку Add Function
    Напишите следующий код в функцию OnInitialUpdate():

     void CMDIView::OnInitialUpdate()  {
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or
call the base class
CMDIDoc* pDoc = GetDocument();
// получить указатель на документ
// обновить элементы данных представления
// соответствующими значениями документа.
m_X = pDoc->m_X; 	m_Y = pDoc->m_Y;
pDoc->SetTitle("ANDY");
// всем документам даётся название ANDY }
    7. Теперь напишем код для вывода круга на экран.
    Функция OnDraw() класса представления автоматически выполняется всякий раз, когда нужно вывести окно документа.

    Напишите следующий код в функции OnDraw() :

      void CMDIView::OnDraw(CDC* pDC) {
CMDIDoc* pDoc = GetDocument();
                    ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->Ellipse(m_X - 20, m_Y - 20,
                   m_X + 20, m_Y + 20);
// рисуем круг диаметром 20   }
    8. Напишем код для сохранения и считывания данных из файла.

    Откройте файл MDIDoc.cpp, найдите в нём функцию Serialize() и измените её:

     void CMDIDoc::Serialize(CArchive& ar) {
 if (ar.IsStoring()) 	{
// TODO: add storing code here( это выполняется,
  если выбрать SAVE ) 		ar<<m_X;
// записываем m_X в выбранный файл 		ar<<m_Y;
//  записываем m_Y в выбранный файл 	} 	else 	{
// TODO: add loading code here ( это выполняется,
если выбрать OPEN ) 	    	ar>>m_X;
// считываем значение из выбранного файла в m_X
        ar>>m_Y;
// считываем значение из выбранного файла в m_Y
                                  } }
    9. Часто бывает нужно изменить некоторые параметры программы, такие как заголовок главного окна или тип файла по умолчанию, который выводится в диалоговых панелях SAVE и OPEN. Для этого нужно выбрать закладку ResourceView и открыть пункт String Table. Вы увидите список переменных проекта( три колонки : ID, Value и Caption ).

      IDR_MAINFRAME - заголовок главного окна (изменяется в поле Caption)
      IDR_MCIRCLTYPE - тип файла по умолчанию, вы увидите 6 подстрок разделёнными знаком \n. Третья и четвёртая подстроки определяют тип документа по умолчанию. У меня CIR FILES( *.cir ) и .cir соответственно. Вы можете поставить свои значения.

    10. Теперь создадим кнопку в панеле инструментов. Для этого нужно выбрать закладку ResourceView и открыть пункт Toolbar. Вы увидите панель инструментов в режиме редактирования. Нажмите на самую правую кнопку( пунктирный квадрат ), ниже нарисуйте кнопку по вашему усмотрению. Теперь дважды нажмете на вашу кнопку и введите ID: ID_MYBUTTON и Prompt: Изменение координат круга\nИзменение координат круга. Ну вот и всё, кнопка готова. Теперь нужно создать функцию, которая будет выполняться при нажатии на вашу кнопку :

    Выберите пункт меню View далее ClassWizard, выберите закладку Message Maps, Project: MDI, Class name: CMDIView, Object IDs: ID_MYBUTTON, Message: COMMAND и нажмите на кнопку Add Function. В ответ создастся функция void CMDIView::OnMybutton().

    11. Теперь по аналогии с главой 15 создадим собственное диалоговое окно с ID: IDD_MY_DIALOG и классом CMyDialog и разместим в нём четыре Edit Box с переменными типа INT: m_DX - текущая позиция по X, m_DY - текущая позиция по Y, m_DXN - новая позииция по X, m_DYN - новая позииция по Y. Не забудте написать #include "MyDialog.h" в файлах MDIDoc.cpp и MDIView.cpp.

    12. Теперь напишем код в функцие OnMybutton().

     void CMDIView::OnMybutton()  {
// TODO: Add your command handler code here
CMDIDoc* pDoc = GetDocument();
// получаем указатель на документ
               CMyDialog MyD;
// создаём переменную класса CMyDialog
MyD.m_DX = MyD.m_DXN = pDoc->m_X;
// инициализмруем переменные диалога
MyD.m_DY = MyD.m_DYN = pDoc->m_Y; .
//   	MyD.DoModal();
// создаём новый диалог
         pDoc->m_X = MyD.m_DXN;
// получаем новые значения
       pDoc->m_Y = MyD.m_DYN;
//  	OnInitialUpdate();
// синхронизируем данные
               Invalidate( TRUE );
// перерисовываем экран( вызов OnDraw() )
pDoc->SetModifiedFlag();
// ставим флаг изменения документа }

    13. Ну вот и всё, программа готова. Отсюда можно взять рабочую программу, использующую MDI, в которой используется собственное диалоговое окно.


Глава 18

18. Проигрывание Wave-файлов под MFC

    1. Введение
    2. Проигрывание Wave-файла в виде ресурса
    3. Проигрывание Wave-файла с диска
    4. Пример проекта

1. Введение

В этой главе мы создадим программу, проигрывающую WAVE-файлы. Для начала создадим проект mysound в диалоговом режиме с использованием MFC. В начало файла mysoundDlg.cpp надо написать #include <mmsystem.h>, но это не всё, а теперь самое главное( если это не сделать, то будет ошибка при линковании ) :

    1. Выберите Project -> Settings... --> C/C++ --> Code Generation и поставте Multithreaded DLL
    2. Выберите Project -> Settings... --> Link --> General и поставте в поле Object/Library modules библиотеку winmm.lib( это очень важно, проверте !!! )

2. Проигрывание Wave-файла в виде ресурса

Первым делом надо создать ресурс, для этого в файле mysound.rc2 надо вписать строчку IDSOUND_CORRECT sound res\correct.wav, где IDSOUND_CORRECT - индефикатор ресурса, sound - тип ресурса( название можно менять ), res\correct.wav - файл ресурса. После этого в файле Resource.h надо зарегистрировать ресурс: #define IDSOUND_CORRECT 130. Число 130 не должно совпадать с другими числами.

Как только ресурс зарегистрирован можно написать в файле mysoundDlg.cpp функции проигрывания этого ресурса :

 static void PlayResource(LPCTSTR lpszSound) {
HRSRC hRes; // resource handle to wave file
HGLOBAL hData; 	BOOL bOk = FALSE; 	if
((hRes = ::FindResource(AfxGetResourceHandle(),
 lpszSound, 	  _T("sound"))) != NULL &&
 (hData = ::LoadResource
 (AfxGetResourceHandle(), hRes)) != NULL) 	{
// found the resource, play it 		bOk =
sndPlaySound((LPCTSTR)::LockResource(hData),
SND_MEMORY|SND_SYNC|SND_NODEFAULT);
FreeResource(hData); 	} 	if (!bOk) 	{
 AfxMessageBox("ERROR !!! Can not play the sound.
 \nNot find sound board !!!");	 	} }
inline static void PlayResource(UINT nIDS) 	{
PlayResource(MAKEINTRESOURCE(nIDS)); }
В фунции ::FindResource(AfxGetResourceHandle(), lpszSound, _T("sound")) третий параметр - тип ресурса, который был описан выше.

Теперь можно проиграть ресурс : PlayResource( IDSOUND_CORRECT );

3. Проигрывание Wave-файла с диска

Для проигрывания WAVE-файла с диска можно использовать функцию :

    BOOL sndPlaySound(    	 LPCTSTR  lpszSoundName;
 UINT  fuOptions;    );	  Параметры функции:
 lpszSoundName  Имя файла. Если этот параметр  NULL,
то проигрывание файла останавливается.   fuOptions
Специальные опции для проигрывания музыки.
Они могут быть следующими:
Значение	                    Описание
SND_SYNC	Музыка играется синхронно, и функция
не возвращает указатель 		пока не будет конца файла.
SND_ASYNC	Музыка играется асинхронно, и функция
возвращает указатель 		сразу после начала
проигрывания файла. Чтобы остановить проигрывание,
надо вызвать функцию SndPlaySound с параметром
lpszSoundName  		установленным в NULL.
SND_NODEFAULT	Если файл не найден, то функция
возвращает указатель 		сразу и не
проигрывает стандартный звуковой эффект Windows.
SND_MEMORY	Этот параметр нужен для проигрывания
Wave-файла в виде ресурса( из памяти ).
SND_LOOP	Этот параметр нужен для проигрывания
 Wave-файла в циклическом режиме. 		Также
 при этом вы должны использовать влаг SND_ASYNC.
 Чтобы остановить проигрывание, надо вызвать
 функцию SndPlaySound 		с параметром
lpszSoundName установленным в NULL.
SND_NOSTOP	Если музыка уже проигрывается,
 то функция возврвщает FALSE.
 Возвращаемое значение:  Если музыка проигрывается
 правильно, то функция возврвщает TRUE, иначе FALSE.
  Пример использования:  	B
  OOL bOk = sndPlaySound( "test.wav", SND_SYNC);
  if (!bOk) 	{ 	    AfxMessageBox
  ("Error ! Can not play the sound. !!!");	 	}

4. Пример проекта

    Отсюда можно взять рабочую программу под MFC, в которой проигрываются Wave-файлы.


Глава 19

19. Создание собственных ActiveX элементов

    1. Введение
    2. Создание проекта
    3. Настройка значка инструмента MyClock
    4. Рисование в элементе управления MyClock
    5. Вывод текущего времени в непрерывном режиме
    6. Включение базовых свойств в ActiveX MyClock
    7. Включение специального свойства в ActiveX MyClock

1. Введение

Элементов управления ActveX — это файл с расширением ОСХ (например, MyButton.OCX), который вы можете использовать в своем приложении Visual C++. Visual C++ и другие визуальные языки программирования дают вам возможность включить элемент управления ActiveX в свою программу и пользоваться им так же, как и стандартным элементом управления Visual C++. Вы помещаете элемент управления ActiveX в диалоговую панель, задаете его свойства и связываете код с его событиями. После того как ы создали собственный элемент управления ActiveX, вы ожете передавать его другим программистам, которые могут вводить его в свои программы.

Поскольку расширением файла элемента управления ActiveX является .ОСХ, то иногда элементы управления ActiveX называют элементами ОСХ.

В этой главе вы разработаете свой собственный элемент управления ActiveX — MyClock.ОСХ, который выполняет задачу вывода текущего времени. Когда программист помещает элемент управления MyClock.ОСХ в форму или в диалоговую панель, MyClock. ОСХ будет непрерывно отображать текущее время.

2. Создание проекта

Чтобы создать проект элемента управления MyClock.OCX :
    1) Выберите New в меню File.
    В ответ Visual C++ выведет диалоговую панель New.
    2) Выберите закладку Projects диалоговой панели New.
    3) Выберите MFC ActiveX ControlWizard из списка типов проектов
    4) Напечатайте MyClock в окне Project Name.
    5) Щелкните на кнопке, которая расположена с правой стороны окна Location, и выберите каталог для проекта.
    6) Щелкните на кнопке ОК.
    В ответ Visual C++ выведет окно MFC ActiveX ControlWizard Step 1 of 2
    В окне ActiveX ControlWizard Step 1 оставьте все установки в состоянии по умолчанию и щелкните на кнопке Next.
    В окне ActiveX ControlWizard Step 2 оставьте все установки в состоянии по умолчанию и щелкните на кнопке Finish.
    В ответ Visual C++ выведет диалоговую панель New Project Information.
    Щелкните на кнопке ОК в диалоговой панели New Project Information и выберите Set Active Configuration в меню Build.
    В ответ Visual C++ выведет диалоговую панель Set Active Project Configuration.
    Выберите MyClock - Win32 Release в диалоговом окне Set Active Project Configuration и щелкните на кнопке ОК.

    Это все! Вы завершили создание файла проекта и каркасов файлов элемента управления ActiveX MyClock.ОСХ.
3. Настройка значка инструмента MyClock

Значок инструмента MyClock отображает буквы ОСХ. Вам нужно настроить элемент управления MyClock таким образом, чтобы значок его инструмента представлял собой рисунок часов. Для настройки значка инструмента MyClock вы должны отредактировать растровое изображение IDB_MYCLOCK. Это изображение было создано Visual C++.
Чтобы вывести растровое изображение IDB_MYCLOCK в режиме проектирования, сделайте следующее:

    1) Выберите закладку ResourceView в окне Project Workspace, раскройте пункт MyClock resources, раскройте пункт Bitmap и дважды щелкните на пункте IDB_MYCLOCK.
    В ответ Visual C++ выведет растровое изображение IDB_MYCLOCK в режиме проектирования.
    2) Используя визуальные инструменты Visual C++, замените растровое изображение IDB_MYCLOCK букв ОСХ на рисунок простейших часов (окружность и две линии в качестве стрелок).

4. Рисование в элементе управления MyClock

Пока элемент управления MyClock выводит эллипс. Вам нужно, чтобы MyClock отображал текущее время, так что вы должны написать соответствующий код:

Откройте файл MyClockCtl.cpp.

Файл MyClockCtl.cpp — это файл реализации элемента управления МуСlock, созданный для вас Visual C++; в этом файле вы будете писать свой код для настройки MyClock.

Найдите функцию OnDraw() в файле MyClockCtl.cpp и напишите следующий код:

 void CMyClockCtrl::OnDraw(     CDC* pdc,
const CRect& rcBounds, const CRect& rcInvalid) {
// TODO: Replace the following code with your own
                                  drawing
 //       code.
 // Залить элемент управления выбранным цветом.
 pdc->FillRect(rcBounds,
CBrush::FromHandle((HBRUSH)GetStockObject
                       (WHITE_BRUSH))) ;
 char CurrentTime[30] ;    struct tm *newtime;
long lTime;     // Получить текущее время
                           time(&lTime) ;
newtime=localtime(&lTime);
// Преобразовать время в строку.
strcpy(CurrentTime, asctime(newtime));
// Дополнить строку одним символом пробела.
                       CurrentTime[24]=' ';
// Дополнить строку ограничивающи символом.
               CurrentTime[25] = 0;
// Вывести текущее время
pdc->ExtTextOut(rcBounds.left,rcBounds.top,
 ETO_CLIPPED,    rcBounds,    CurrentTime,
 strlen(CurrentTime),    NULL) ; }
5. Вывод текущего времени в непрерывном режиме

Чтобы отображать время непрерывно, вам нужно сделать следующее:

1) Написать код, который устанавливает таймер с 1000-миллисекундным периодом для элемента управления MyClock.
2) Связать код с событием WM_TIMER элемента управления MyClock.

После установки таймера каждые 1000 миллисекунд (каждую секунду) Windows будет посылать сообщение WM_TIMER элементу управления MyClock, в ответ на которое будет выполняться код, который вы свяжете с этим событием элемента управления. Этот код будет просто выводить текущее время, так что значение времени будет непрерывно обновляться.

Таймер необходимо установить сразу после создания элемента управления, так что вам нужно связать код, устанавливающий таймер, с событием WM_CREATE элемента управления:

Выведите диалоговую панель ClassWizard, выбрав ClassWizard в меню View. На странице Message Maps выберите следующее событие:

    Class Name: CMyClockCtrl
    Object ID: CMyClockCtrl
    Message: WM_CREATE

Щелкните на кнопке Add Function.
В ответ Visual C++ добавит в класс CMyClockCtrl функцию-элемент ОпСreate().
Щелкните на кнопке Edit Code в ClassWizard.
В ответ Visual C++ откроет файл MyClockCtrl.cpp с функцией OnCreate() в режиме редактирования.

Напишите следующий код в функции OnCreate():

 int CMyClockCtrl::OnCreate(LPCREATESTRUCT
lpCreateStruct) {
if (COleControl::OnCreate(lpCreateStruct) == -1)
    return -1;
// TODO: Add your specialized creation code here
// Установить таймер. 	SetTimer(1, 1000, NULL);
return 0; }

Введенный вами код состоит из одного оператора, который вызывает функцию SetTimer() для установки таймера с 1000-миллисекундным периодом:

    SetTiltier (1, 1000, NULL);

Начиная с этого момента, каждые 1000 миллисекунд Windows будет посылать элементу управления сообщение WM_TIMER.
Теперь вам нужно связать код с событием WM_TIMER:

Выберите ClassWizard в меню View. На странице Message Maps выберите следующее событие:

    Class Name: CMyClockCtrl
    Object ID: CMyClockCtrl
    Message: WM_TIMER

Щелкните на кнопке Add Function.
В ответ Visual C++ добавит в класс CMyClockCtrl функцию-элемент OnTimer().
Щелкните на кнопке Edit Code в ClassWizard.
В ответ Visual C++ откроет файл MyClockCtrl.cpp с функцией OnTimer() в режиме редактирования.
Напишите следующий код в функции OnTimerO:

 void CMyClockCtrl::OnTimer(UINT nIDEvent) {
// TODO: Add your message handler code here
and/or call
// default
// Переключить вызов на функцию OnDraw().
                          InvalidateControl() ;
COleControl::OnTimer(nIDEvent) ; }

6. Включение базовых свойств в ActiveX MyClock

Базовые свойства( Stock properties ) - преопределены.
Ниже приведён список базовых свойств:

    Appearance - Внешний вид( 3-х мерный или плоский )
    BackColor - Цвет фона
    BorderStyle - Стиль рамки
    Caption - Заголовок
    Enabled - Состояние доступен/недоступен
    Font - Шрифт
    ForeColor - Цвет переднего плана
    hWnd - Маркер окна
    ReadyState - Состояние готовности
    Text - Текст

Для практики включим два базовых свойства в ActiveX MyClock: BackColor и ForeColor.
Выполните следующие действия:

    View -> ClassWizard -> Automation( проверте, чтобы в окне Class name установлен класс CMyClockCtrl )
    Нажмите на кнопку Add Property
    Выберите из списка BackColor и нажмите OK
    Также добавьте и свойство ForeColor.

Элемент управления MyClock имеет сейчас свойства BackColor и ForeColor, но пока не использует значения, хранящиеся в этих свойствах. Вам надо написать код в функции OnDraw(), который выполняет эту задачу:

 void CMyClockCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect&
                                        rcInvalid) {
// TODO: Replace the following code with your
                                  own drawing
//       code.
// Задать цвет переднего плана( цвет текста )
 pdc->SetTextColor( TranslateColor(GetForeColor()));
// Установить режим прозрачного фона
pdc->SetBkMode(TRANSPARENT);
// Создать кисть на основе значения BackColor
CBrush bkBrush( TranslateColor(GetBackColor()));
// Закрасить фон    pdc->FillRect(rcBounds,
 &bkBrush);  char CurrentTime[30] ;
  struct tm *newtime;   long lTime;
// Получить текущее время    time(&lTime) ;
 newtime=localtime(&lTime);
// Преобразовать время в строку.
strcpy(CurrentTime, asctime(newtime));
// Дополнить строку одним символом пробела.
CurrentTime[24]=' ';
// Дополнить строку ограничивающи символом.
CurrentTime[25] = 0;
// Вывести текущее время
pdc->ExtTextOut(rcBounds.left,rcBounds.top,
ETO_CLIPPED,    rcBounds,    CurrentTime,
strlen(CurrentTime),    NULL) ; }
Ну вот и всё, теперь элемент управления MyClock имеет свойства BackColor и ForeColor.

7. Включение специального свойства в ActiveX MyClock

Во многих случаях вам понадобится включить в свой элемент управления такие свойства, которые не входят в список стандартных. Эти свойства называются специальными.
Для примера включем в MyClock специальное свойство UpdateInterval - период обновления:

    View -> ClassWizard -> Automation( проверте, чтобы в окне Class name установлен класс CMyClockCtrl )
    Нажмите на кнопку Add Property
    В окне External name наберите UpdateInterval
    В окне Type выберите Long
    В окне Variable name должно быть m_updateinterval
    В окне Notification function поставьте OnUpdateIntervalChanged
    Проверте, что в камке Implementation выбрана кнопка Member variable и нажмите OK
Тем самым мы определили, что со свойством UpdateInterval будет связана переменная m_updateinterval и всякий раз, когда значение свойства UpdateInterval будет именяться, автоматически выполнится функция OnUpdateIntervalChanged.

Теперь надо проинициализировать свойство UpdateInterval:

    Откройте файл MyClockCtl.cpp
    Найдите функцию DoPropExchange() и напишиет в ней следующее:

    // Инициализация свойства UpdateInterval значением 1000
    PX_Long( pPX, _T("UpdateInterval"), m_updateinterval, 1000 );

Теперь надо модернизировать функции OnUpdateIntervalChanged:

    // проверка на отризательность
    if( m_updateinterval < 0 )
    {
    MessageBox( "This property cannot be negative !!!" );
    m_updateinterval = 1000;
    }
    // Установка таймера
    SetTimer( 1, (UINT)m_updateinterval, NULL );

и OnCreate:
    // Установка таймера
    SetTimer( 1, (UINT)m_updateinterval, NULL );

Ну вот и всё ActiveX MyClock полностью готов !!!


Глава 20

20. Использование класса CTabCtrl

Tab control - это мощное средство для решению многих проблем в интерфейсе приложений. Оно позволят существенно увеличить скорость работы вашего приложения, разбить на "части" диалог в удобной для пользователя форме.

В MFC есть встроенный класс по работе с Tab control - класс CTabCtrl.

Для практики напишем программу, которая будет использовать класс CTabCtrl и в которой будет три "закладки" - диалога.

Шаги создания проекта:

1) Сначала создадим проект tab_control типа диалог.
2) В редакторе ресурсов добавить Tab Control в шаблон
диалога и назначим ему ID = IDC_TAB.
3) Используя ClassWizard, добавим переменную-член типа
CTabCtrl со свойством Control.
4) В OnInitDialog проинициализируем  необходимые
переменные для CTabCtrl.
5) Используя ClassWizard, добавим обработку
необходимых  сообщений от Tab control 'я.
6) Удалим за собой ненужные переменные.

Для начала сделайте первые три пункта, создайте переменную m_ctrTab класса CTabCtrl. После этого в функцие BOOL CTab_controlDlg::OnInitDialog() добавте следующее:
         ...  	TC_ITEM TabItem; 	TabItem.mask = TCIF_TEXT;
TabItem.pszText = "Закладка1";
m_ctrTab.InsertItem( 0, &TabItem );
TabItem.pszText = "Закладка2";
m_ctrTab.InsertItem( 1, &TabItem );
TabItem.pszText = "Закладка3";
m_ctrTab.InsertItem( 2, &TabItem );  	...

Это код инициализации Tab Control, мы создаём три закладки. Теперь нам надо, чтобы при нажатие на любую закладку, на экране появлялось то, что нам нужно. Самый простой вариант - это использовать на каждую закладку по диалогу - и потом просто в области Tab Control'а - выводить нужный диалог, в зависимости от текущей закладки.

Сделаем это. Добавим три диалога в редакторе ресурсов и создадим каждому из них по классу - наследнику от CDialog. Назовем эти классы CPage1, CPage2 и CPage3( файлы Page1.cpp(h), Page2.cpp(h), Page3.cpp(h) ) .

В свойствах этих трёх диалогов поставте Style как "Child" и Border как "none" - это очень важно, а в самих диалогах создайте какие либо элементы ( например, типа Static Text ), чтобы было видно отличие.

Напишите эти три строчки в начале файла tab_controlDlg.cpp

#include "Page1.h"
#include "Page2.h"
#include "Page3.h"

Продолжим в OnInitDialog:
Надо последовательно создать все страницы, причём указатели на них хранятся в самом m_ctrTab !!! В этом примере мы ипользовали lParam структуры TCITEM как хранилище указателя. Теперь переменные pPage1, pPage2 и pPage3 больше не нужны - указатели хранятся в надежном месте! Для каждой страницы вызывается метод ShowWindow() - для отображения первой, и скрытия остальных страниц.

...  	CPage1* pPage1; 	pPage1 = new CPage1;
TabItem.mask = TCIF_PARAM;
TabItem.lParam = (LPARAM)pPage1;
m_ctrTab.SetItem(0, &TabItem);
VERIFY(pPage1->Create(CPage1::IDD, &m_ctrTab));
pPage1->SetWindowPos(
    NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
pPage1->ShowWindow(SW_SHOW);
CPage2* pPage2;  	pPage2 = new CPage2;
TabItem.mask = TCIF_PARAM;
TabItem.lParam = (LPARAM)pPage2;
m_ctrTab.SetItem(1, &TabItem);
VERIFY(pPage1->Create(CPage2::IDD, &m_ctrTab));
pPage2->SetWindowPos
   (NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
pPage2->ShowWindow(SW_HIDE);
CPage3* pPage3;  	pPage3 = new CPage3;
TabItem.mask = TCIF_PARAM;
TabItem.lParam = (LPARAM)pPage3;
m_ctrTab.SetItem(2, &TabItem);
VERIFY(pPage1->Create(CPage3::IDD, &m_ctrTab));
pPage3->SetWindowPos
   (NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
pPage3->ShowWindow(SW_HIDE);  	...

Теперь добавим код по отображению текущей страницы и сокрытию предыдущей. Для этого добавим обработчики сообщений TCN_SELCHANGE и TCN_SELCHANGING :
 void CTab_controlDlg::OnSelchangingTab(NMHDR* pNMHDR,
LRESULT* pResult)  {
// TODO: Add your control notification handler
                        code here
int nTab = m_ctrTab.GetCurSel();
TC_ITEM tci;  	tci.mask = TCIF_PARAM;
m_ctrTab.GetItem(nTab, &tci);
ASSERT(tci.lParam);
CWnd* pWnd = (CWnd *)tci.lParam;
pWnd->ShowWindow(SW_HIDE);   *pResult = 0; }
void CTab_controlDlg::OnSelchangingTab(NMHDR* pNMHDR,
LRESULT* pResult)  {
int nTab = m_ctrTab.GetCurSel();
TC_ITEM tci;     	tci.mask = TCIF_PARAM;
m_ctrTab.GetItem(nTab, &tci);     	ASSERT(tci.lParam);
CWnd* pWnd = (CWnd *)tci.lParam;
pWnd->ShowWindow(SW_HIDE);      *pResult = 0; }

Здесь используются те самые указатели, которые мы спрятали в OnInitDialog

Теперь освободим память и разрушим диалоговые окна при выходе из приложения.
Добавим функцию OnDestroy:
 void CTab_controlDlg::OnDestroy() {
CDialog::OnDestroy();      CWnd* pWnd;
TC_ITEM tci;     tci.mask = TCIF_PARAM;
for (int i = 2; i>=0; i--)     {
m_ctrTab.GetItem(i, &tci);
ASSERT(tci.lParam);
pWnd = (CWnd *)tci.lParam;
pWnd->DestroyWindow();
delete pWnd;     } }

Ну вот и всё, приложение готово.
Отсюда можно взять рабочую программу под MFC, с использованием CTabCtrl.


cpp-index

Примеры по С++
  • Пример 1
  • Пример 2
  • Пример 3
  • Пример 4
  • Пример 5
  • Пример 6
  • Пример 7
  • Пример 8
  • Пример 9
  • Пример 10
  • Пример 11
  • Пример 12
  • Пример 13
  • Пример 14


Любая программа на С или С++ начинает свое выполнение с функции main()

Пример 1

Первая программа

Любая программа на С или С++ начинает свое выполнение с функции main(). Напишем нашу первую программу. Для этого создадим файл main.cpp и напишем текст:

#include <iostream.h>

int main()
{
cout << "Hello
C++\n" ;
return 0;
}

Текст программы
Запустить пример

Скомпилировав и запустив нашу программу мы должны получить на экране:

Hello C++

В первой строке программы мы написали директиву #include <iostream.h>, она подключает библиотеку ввода-вывода iostream.h, без которой компилятор не поймет определенные в ней функции cout. Скобки { и } обрамляют начало и конец функции потом мы увидим, что они используются и для других целей. В С++ каждый оператор, а также каждая строка с обращением к функции заканчивается точкой с запятой, исключения составляют команды препроцессора и имена функций стоящие в начале программной единицы (например, у нас в программе main()). В строке cout << "Hello C++\n", cout – это стандартный поток вывода, оператором << мы помещаем в него строку Hello C++, константа \n переводит каретку на новую строку.

Основные типы данных

Основные типы данных разделяются на три категории: целочисленные, с плавающей точкой и неопределенные(void). Целочисленные типы данных работают с целыми числами. Типы данных с плавающей точкой работают с данными имеющими дробную часть. Неопределенный тип данных соответствует пустому множеству значений.

Основными типами данных являются:

  • char – целочисленный тип, содержит символы.
  • int – является целочисленным типом, содержит целые цифры.
  • short – тип данных short int (или сокращенно short) является целочисленным типом, содержащим целые числа, по размеру меньше или равен типу int.
  • long - тип данных long int (или сокращенно long) является целочисленным типом, содержащим целые числа, по размеру больше или равен типу int.
  • double – тип данных с плавающей точкой.
  • float – наименьший тип данных с плавающей точкой.
  • long double – тип данных с плавающей точкой больше чем double.

Нашу программу можно модифицировать:

#include <iostream.h>

int main()
{
  char name[20];
  cout << "What is your name: ";
  cin >> name;
  cout << "Hello: " << name << endl;
  return 0;
}

Текст программы
Запустить пример

После запуска она должна выдать на экране вопрос "What is your name:", мы соответственно должны ввести имя, например “Pupkin”, на это программа должна выдать:

What is your name: Pupkin

     Hello: Pupkin

          В строке char name[20]; мы объявляем массив name типа char из 20 элементов, это значит что в массиве name может хранится не более 20 символов (подробнее о массивах мы поговорим в следующих уроках). В строке cin >> name; мы из стандартного потока ввода cin помещаем данные в наш массив name, в данном случае данные поступают с клавиатуры.

следующий пример


Урок 2

Пример 2

В языке С++ может быть использован модификатор const, для объявления констант (переменные содержание которых нельзя менять). Например: const char QUESTION[] = "What is your name: ";- здесь мы объявили константу QUESTION типа char для хранения текста, далее нигде в программе ее содержание нельзя менять.

В программах отдельные задачи лучше разделять по функциям. Обычная функция имеет вид:

Return_type name(param_type param)
 {
     //тело функции;
}

Где Return_type - тип возвращаемого функцией результата, name - имя функции, param_type - тип параметра передаваемого функции, param - имя параметра, которое будет использоваться внутри функции. Например, из программы предыдущего урока, печать приветствия можно выделить в отдельную функцию.

#include <iostream.h>

const char QUESTION[] = "What is your name: ";  //объявляем константу
const char HELLO[] = "Hello: ";       //объявляем константу

void print_name(char* name)           //объявляем функцию
{
     cout << HELLO << name << endl;    //печать приветствия
}

int main()
{
char name[20];         //объявления массива
cout << QUESTION;      //печатаем вопрос
cin >> name;
//считываем данные в массив name
print_name(name);      //вызываем функцию print_name
//для печати приветствия
return 0;
}

Текст программы
Запустить пример

В нашем случае функция print_name ничего не возвращает и поэтому имеет тип возвращаемого результата void. В качестве параметра ей передается строка name типа char.


Урок 3 Операторы сравнения

Пример 3

Операторы сравнения, равенства и логические. В С++ имеется логический тип данных bool который может принимать только два значения true(истина) или false(ложь). Этот тип данных используется для контроля выполнения инструкций различных типов. В следующей таблице представлены инструкции С++, наиболее часто используемые для управления логикой выполнения.

Операторы сравнения, равенства и логические
Операторы сравнения | меньше | <
 | больше | >
 | меньше или равно | <=
 | больше или равно | >=
Операторы равенства | равно | ==
 | не равно | !=
Логические операторы | отрицание | !
 | логическое и | &&
 | логическое или | ||

Очень важно не путать операторы равенства и присваивания, например a==b - это проверка на равенство т. е. если a и b равны то выражение вернет true(истина) в противном случае false(ложь), а в выражении a=b переменная a станет равна b. Приведем пример с инструкцией if, она имеет вид:

if(условие)
{
    инструкции
}

Здесь если условие истинно то выполняются инструкции. Мы можем составить следующую программу:

int main()
{
int a = 0;
int b = 4;

if(a != b) //если a не равно b, то
{
a = b; //a становится равной b
}

if(a == b) //если a равно b, то
{
a = b + 1; //a принимает значение b + 1
}
return 0;
}

Текст программы


Урок 4

Пример 4

Инструкция if-else схожа и тесно связана с инструкцией if, она имеет вид:

if(условие)
{
    инструкции 1
}
else
{
    инструкции 2
}

Если  условие истинно то выполняются
 инструкции 1, а
 инструкции 2 пропускаются, если
 условие ложно, то выполняются
 инструкции 2, а
 инструкции 1 пропускаются. Например:

int main()
{
int a = 0;
int b = 4;

if(a == b)
{
a = 4;
}
else
{
a = 3;
}
return 0;
}

Текст программы

Здесь a присвоится 3, т.к. условие a == b ложно.

Инструкция while имеет вид:

while(условие)
{
инструкции
}

Здесь сначала проверяется условие и если оно истинно то выполняются инструкции, после управление переходит опять в начало цикла к условию и так до тех пор пока условие не станет ложным. Например:

int main()
{
    int a = 0;
    while(a < 20)
    {
        ++a;
    }
    return 0;
}

Текст программы

Оператор ++ увеличивает переменную на 1 т.е. выражение ++a идентично a + 1. В нашем цикле a будет увеличиваться на 1 пока не станет равно 20.


Вид инструкции for

Пример 5

Вид инструкции for:

for(начальная_инструкция; условие; выражение)
{
инструкции;
}

Сначало выполняется  начальная_инструкция, потом проверяется
 условие и если оно истинно, то выполняются
 инструкции, затем вычисляется
 выражение и управление передается в начало цикла с разницей что
 начальная_инструкция уже не выполняется. Например:

#include <iostream.h>

int main()
{
for(int i = 0; i != 10; ++i)
{
cout << i << endl;
}
return 0;
}

При выполнении эта программа выведет на экран числа от 0 до 9.


Урок 6

Пример 6

Массивы

Массив состоит из нескольких элементов одного и тогоже типа. Число элементов массива задается при его объявлении и в дальнейшем не меняется. Массивы объявляются так же как и переменные. Например:

int a[100];
char b[40];

В первой строке объявлен массив из 100 элементов типа int, а во второй из 40 элементов типа char. Ко всему массиву целиком можно обращаться по имени. Можно обращаться и к отдельным элементам по индексу, который указывает относительную позицию элемента. Индексация массива начинается с нуля т.е. в массиве a первый элемент будет a[0], а последний a[99].
Бывают многомерные массивы. Например двумерный массив int v[3][7] можно представить как три массива типа int по 7 элементов в каждом. Представим это графически:

v[0] | 0 | 1 | 2 | 3 | 4 | 5 | 6
v[1] | 0 | 1 | 2 | 3 | 4 | 5 | 6
v[2] | 0 | 1 | 2 | 3 | 4 | 5 | 6

    Напишем програмку
с применением двумерного массива, которая спросит у вас сначало фамилию потом имя и отчество и в конце выведет это все на экран.

#include <iostream.h>

int main()
{
char fio[3][25];
cout << "Your surname: ";
cin >> fio[0];
cout << "\nYour name: ";
cin >> fio[1];
cout << "\nYour patronymic name: ";
cin >> fio[2];
cout << "\n" << fio[0] << " " << fio[1] << " " << fio[2] << endl;
return 0;
}

Текст программы
Запустить пример


Урок 7

Пример 7

В этом уроке разговор пойдет о указателях, эта сложная тема для начинающих поэтому если вы ее не поймете то советую обратится к какой либо литературе (например, книге Бьерна Страуструпа "Введение в язык С++").
Указатели в С++ использются для связи переменных с машинными адресами. Если V – переменная, то &V – ее адрес, или место в памяти, где хранится ее значение. & - это оператор определения памяти. В программе можно объявить переменные-указатели и затем использовать их для получения адресов. Приведем пример:

int a = 2;
int b = 4;
int *p;
p = &a;
*p = 8;
p = &b;
*p = 6;

Здесь в первых двух строках объявляются переменные a и b типа int, в третей строке объявляется указатель на int. В четвертой строке указатель p берет адрес переменной a, в пятой строке переменной на которую указывает указатель (в данном случае a) присваевается значение 8. В шестой и седьмой строках указатель берет адрес переменной b и меняет ее значение на 6.


Урок 8

Пример 8

Всякую задачу можно разбить на несколько подзадач. Также и в программе мы можем использовать функции для разбиения ее на несколько подпрограмм. Выполнение программы начинается всегда с функции main(). Когда в программе вмтречается имя функции происходит обращение к ней и управление передается ей. После выполнения функции управление передается в то место откуда она была вызвана. Формально функция выглядет так:

тип имя(список_параметров)
{
    инструкции
}

Тут: тип – это возвращаемый тип(тип того что возвращает функция), имя – это название функции и список_параметров – это параметры которые передаются функции. В функциях для возврата результата используется инструкция return, она возвращает какое либо значение в то место откуда была вызвана функция. Приведем пример программы с использованием функции, которая будет высчитывать сумму двух введенных пользователем значений:

#include <iostream.h>
int sum(int a, int b)
{
    int c = a + b; return c;
}
main()
{
    int a, b , res;
    cout << "a = ";
    cin >> a;
    cout << "b = ";
    cin >> b;
    res = sum(a, b);
    cout << "summa: " << res << endl;
}

Текст программы
Запустить пример

Объявление функции можно отделить от определения, например в данной программе функцию sum можно объявить до функции main а определить после:

#include <iostream.h>
int sum(int a, int b);
main()
{
    int a, b , res;
    cout << "a = ";
    cin >> a;
    cout << "b = ";
    cin >> b;
    res = sum(a, b);
    cout << "summa: " << res << endl;
}
int sum(int a, int b)
{
    int c = a + b;
    return c;
}

Текст программы
Запустить пример


Операторы new и delete служат для управления свободной памятью

Пример 9

Операторы new и delete служат для управления свободной памятью. Время жизни объектов, которые создаются с поиощью этих операторов, полностью управляется программистом. Программист создает объект с помощью оператора new и уничтожает его с помощью оператора delete. Оператор new принимает следующие формы:

new имя_типа
new имя_типа[выражение];

Вот фрагмент использования оператора new:

char *p;
p =  new
char[10];

Здесь сначало объявляется указатель на char, потом с помощью оператора new выделяется память под 10 элементов типа char (т.е. мы получаем массив из 10 элементов типа char). Для удаления объекта созданого с помощью оператора new используется оператор delete. В приведенном выше примере что бы удалить созданные объекты можно использовать следующий код:

delete [] p;


Тип struct позваляет программисту объединить несколько компонент в переменную с одним именем

Пример 10

Тип struct позваляет программисту объединить несколько компонент в переменную с одним именем. Члены структуры могут быть различных типов. В качестве примера определим структуру описывающую квартиру. Эта структура будет хранить такие признаки квартиры как площадь, число комнат, номер, фамилия главного квартиросъемщика.

struct apartment
{
    int count_rooms;
    int area;
    int number;
    char fio[20];
};

Здесь struct - это ключевое слово, apartment - имя структуры, а count_rooms, area, number и fio члены структуры. Теперь мы можем создавать объекты типа apartment, они объявляются так же как и обычные переменные:

apartment ap;

Для доступа к членам структуры ap используется оператор выбора члена структуры ".". Допустим мы хотим описать какую либо квартиру, то можно написать:

ap.count_rooms = 4;
ap.area = 90;
ap.number = 112;
ap.fio = "Ivanov I.I";


Пример 11

Стек - одна из полезных структур данных. Для стека характерны такие операции как вставлять и удалять данные, но выпалняются они только над самым верхним элементом(вершиной стека). Такой порядок называется "первым вошел, последним вышел".
Реалезуем стек для хранения символьных значений(букв), с помощью структуры и нескольких функций. Для хранения данных мы создадим структуру stak состоящую из массива s, хранящего наши данные, и переменной top которая будет хранить номер верхнего элемента.

struct stack
{
  char s[1000];
  int top;
};

  Теперь создадим функцию reset задача которой инициализация. В ней нам достачно переменной top присвоить значение -1. В качестве параметра ей будет передоватся указатель на структуру (при использовании указателя для доступа к членам структуры используется не ".", а оператор "->").

void reset(stack* st)
{
  st->top = -1;
}

  Так же нам нужны функции push(вставить) и pop(извлечь).

void push(stack* st, char s)
{
  ++st->top;
  st->s[st->top] = s;
}
char pop(stack* st)
{
  --st->top;
  return st->s[st->top];
}

  Теперь напишем функцию main, чтобы проверить нашу версию стека, функция будет печатать строку в обратном порядке.

main()
{
  char str[] = "My name is Gena";
  int count = sizeof(str);
  stack My_stack;
  reset(&My_stack);
  for(int i = 0; i < count; i++)
  {
    push(&My_stack, str[i]);
  }
  while(My_stack.top != -1)
  {
    cout << pop(&My_stack);
  }
  cout << endl;
}

  Полностью текст программы должен выглядеть так:

#include <iostream.h>

struct stack
{
  char s[1000];
  int top;
};

void reset(stack* st)
{
  st->top = -1;
}

void push(stack* st, char s)
{
  ++st->top;
  st->s[st->top] = s;
}

char pop(stack* st)
{
  --st->top;
  return st->s[st->top];
}

main()
{
  char str[] = "My name is Gena";
  int count = sizeof(str);
  stack My_stack;
  reset(&My_stack);
  for(int i = 0; i < count; i++)
  {
     push(&My_stack, str[i]);
  }
     while(My_stack.top != -1)
  {
     cout << pop(&My_stack);
  }
  cout << endl;
}

Текст программы
Запустить пример


Lesson 12

Пример 12

Структуры в С++ могут иметь в качестве своих членов(в предыдущем примере это s и top) не только переменные но и функции. Для этого объявление функций вставляется в объявление структуры, например предыдущий пример стека можно объявить так:

struct stack
{
  char s[1000];
  int top;
  void reset()
  {
    top = -1;
  }
  void push(char c)
  {
    ++top;
    s[top] = c;
  }
  char pop()
  {
    --top;
    return s[top];
  }
};

Функции-члены записаны так же как обычные функции. Отличие лишь в том что теперь они могут использовать переменные и функции "как есть", им не нужно передовать в качестве параметров такие переменные как s и top. Обращаются к функциям-членам так же как и к обычным членам: с помощью оператора ".", а указатели с помощью оператора "->". Приведем несколько фрагментов кода:

stack My_stack;
My_stack.reset();

stack *pMy_stack = &My_stack;
pMy_stack->push('A');

Переменные структур и классов(позже мы узнаем что это) принято называть - "объектами", с этой минуты и мы их будем так называть.


Функции

Пример 13

Функции-члены, которые определены внутри структуры, являются встраевыми. Обычно, толь маленькие, часто используемые функции определяются внутри структуры(как в предыдущем уроке). Что бы определить функцию-член вне структуры используется оператор "::". Давайте это наглядно покажем переделав структуру из предыдущего урока.

struct stack
{
  char s[1000];
  int top;
  void reset();
  void push(char c);
  char pop();
};

void stack::reset()
{
  top = -1;
}
void stack::push(char c)
{
  ++top;
  s[top] = c;
}
char stack::pop()
{
  --top;
  return s[top];
}

Запись void stack::reset() говорит, что функция reset() принадлежит структуре stack. Благодаря оператору "::" мы можем иметь одинаковые имена функций для разных типов структур.


В объектно ориентированном программировании существует такое понятие как механизм сокрытия данных

Пример 14

В объектно-ориентированном программировании существует такое понятие как механизм сокрытия данных. Давайте возьмем случай когда вы покупаете магнитофон то вам доступны только те его части которые необходимы для работы с ним (кнопочки, экранчики и т.д.), такие части как провода, магнитные головки и т.д. вам не доступны. В структурах и классах(позже мы узнаем что это такое) сокрытие данных осуществляется с помощью ключевых слов public(открытый), private(закрытый) и protected(защищенный). Закрытые члены в программе недоступны, а открытые наоборот доступны. Внутри структуры после ключевого слова private, все члены ограничены доступом. Закрытыми членами могут пользоваться только функции-члены этой структуры. Открытые члены могут использоваться и вне структуры.
Напишем структуру характеризующую квартиру. В ней будут такие члены как number(номер квартиры), count_room(количество комнат), floor(этаж), area(площадь), price(цена), status(приватизирована или нет), эти члены мы сделаем закрытыми, а для доступа к ним напишем функции.

struct kwart
{
public:
    int GetNumber();
    int GetCountRoom();
    int GetFloor();
    int GetArea();
    int GetPrice();
    bool GetStatus();
    void SetNumber(int);
    void SetCountRoom(int);
    void SetFloor(int);
    void SetArea(int);
    void SetPrice(int);
    void SetStatus(bool);
private:
    int number;
    int count_room;
    int floor;
    int area;
    int price;
    bool status;
};

int kwart::GetNumber()
{
    return number;
}

int kwart::GetCountRoom()
{
    return count_room;
}

int kwart::GetFloor()
{
    return floor;
}

int kwart::GetArea()
{
    return area;
}
int kwart::GetPrice()
{
    return price;
}
bool kwart::GetStatus()
{
    return status;
}
void kwart::SetNumber(int num)
{
    number = num;
}
void kwart::SetCountRoom(int count)
{
    count_room = count;
}
void kwart::SetFloor(int num)
{
    floor = num;
}
void kwart::SetArea(int num)
{
    area = num;
}
void kwart::SetPrice(int num)
{
    price = num;
}
void kwart::SetStatus(bool stat)
{
    status = stat;
}

Теперь если мы напишем:

kwart kw;
int i = kw.area;

это будет ошибкой, так как переменная area - закрытая, если мы хотим присвоить переменной i ее значение, то нужно воспользоваться функцией GetArea():

kwart kw;
kw.SetArea(20);
int i = kw.GetArea();

А вот в этом фрагменте уже все правильно. Сначала мы переменной area с помощью функции SetArea() присваиваем значение 20, а потом переменной i присваиваем значение area.

предыдущий пример


Обзор языка

1. МЕТАКОМАНДЫ МС-ФОРТРАНА.

Метаязык - это язык управления компилятором MS-Фортрана. Метакоманды позволяют Вам определить варианты, воздействующие на все операции компиляции. Например, с помощью метакоманд Вы можете включить или выключить генерацию файла листинга, проверку кода при ошибке счета, или использовать такие возможности Фортрана, которые не входят в подмножество или в полный стандарт языка. Метаязык содержит команды, которые вставляются в файл исходного текста, каждая из которых занимает отдельную строку, и каждая из которых начинается с символа доллар ($) в первой колонке.

Метаязык - это уровень языка, позволяющий расширить использование компилятора MS-Фортрана. Хотя в большинстве реализаций Фортрана есть средства управления компилятором, метакоманды MS-Фортрана не входят в стандарт Фортрана (и поэтому не мобильны).

Ниже приведены существующие в настоящий момент метакоманды: $[NO]DEBUG $[NO]LARGE $PAGESIDE

$DEVFNH $LINESIDE $STORAGE

$DO66 $[NOT]LIST $[NOT]STRICT

$[NO]FLOATCALLS $MESSADE $SUBTITLE

$INCLUDE $PAGE $TITLE

Смотрите часть 6 "Метакоманды MS-Фортрана" для подробного изучения метакоманд.

2.ПРОГРАММЫ И КОМПИЛИРУЕМЫЕ ЧАСТИ ПРОГРАММ.

Компилятор MS-Фортрана производит программные единицы. Ими могут быть основная программа, подпрограмма или функция. Вы можете откомпилировать любую из этих единиц отдельно и затем загрузить их вместе без объединения их еще до компиляции.

1.Программа.

Любая программная единица, не начинающаяся с оператора FUNCTION или SUBROUTINE. Первым оператором может быть PROGRAM, но такой оператор не обязателен. Выполнение программы всегда начинается с первого выполняемого оператора в главной программе. Следовательно в каждой выполняемой программе должна быть единственная главная программа.

2.Подпрограмма.

Программная единица,которая может вызваться из других программных единиц оператором CALL, будучи вызванной, подпрограмма осуществляет набор действий, определенных ее выполняемыми операторами, и затем возвращает управление на оператор, стоящий непосредственно за оператором вызова. Подпрограмма не возвращает прямо величины, хотя они могут быть переданы в вызывающую программную единицу через параметры или общие переменные.

3.Функция.

Программная единица, которая может быть употреблена в выражении. Функция прямо возвращает величину, которая используется при вычислении этого выражения, и кроме того может возвращать величины через параметры. Существует три вида функций: внешние, внутренние и функция-оператор. (Функция-оператор не может быть скомпилирована отдельно).

4.Подпрограмма блока данных.

Программная единица, обеспечивающая начальные значения переменных в COMMON-блоках. Переменные обычно заводятся оператором DATA, но переменные в COMMON не могут быть заведены нигде кроме подпрограммы блока данных.

Подпрограммы и функции позволяет Вам создавать большие структурированные программы, которые можно делить на части. Это дает преимущества в следующих ситуациях:

1. Если программа большая, разделение ее на части облегчает создание, тестирование и ее сборку.

2. Если программа большая и повторная компиляция всего исходного текста занимает много времени, разделение ее на части экономит время компиляции.

3. Если Вам надо использовать некоторые процедуры во многих других программах, Вы можете создать один объектный файл из этих процедур и затем загружать его в каждую из программ, в которой нужны эти процедуры.

4. Если процедуру надо использовать в разных случаях разным образом, можно записать ее в отдельный файл и скомпилировать отдельно. Затем для обеспечения ее вариативности Вы можете изменить содержание и даже переписать имена Ассемблере или MS- Паскале, а остальная часть Вашей программы не изменится.

Для получения полной информации о компилируемых программных единицах смотрите часть 5 "Программы, подпрограммы и функции."

3. ВВОД/ВЫВОД.

Ввод - это передача данных из внешней среды или из внутренного файла во внутреннюю память. Процесс передачи называется чтением. Вывод - это передача данных из внутренней памяти во внешнюю среду или во внутренний файл. Этот процесс называется записью.

В Фортране есть набор операторов, обеспечивающих такую передачу данных, кроме того существует набор операторов для редактирования таких данных.

В дополнение к операторам передачи данных, существует несколько вспомогательных операторов ввода/вывода, управляющих внешней средой, или определяющих, или описывающих свойства соединения с внешней средой.

В таблице 1-1 приведены операторы ввода/вывода, осуществляющие каждую из этих трех функций.

Функции ввода/вывода | Операторы ввода/вывода
Передача данных Вспомогательные функции ввода/вывода Определение положения в файле | READ WRITE OPEN CLOSE BACKSPASE ENDFILE REWIND INQUIRE LOCKING BACKSPASE ENDFILE REWIND

Таб. 1-1 операторы ввода/вывода.

Для понимания системы ввода/вывода нужны также следующие положения:

1.Записи.

Понятие, на котором основана система файлов Фортрана. Запись

- это последовательность символов или величин. Существует три вида записей: форматные, бесформатные и конец файла.

2. Файлы.

Последовательности записей. Файлы могут быть внешними или внутренними.

Внешние файлы - это файлы, относящиеся к устройству, или само устройство. Внутренний файл - это текстовая переменная, которая используется либо как текст, либо как назначение некоторых действий по форматному вводу/выводу.

Все файлы имеют следующие атрибуты: a. имя файла (произвольное)

b. указатель позиции в файле

c. структура (форматная, неформатная или двоичная)

d. метод доступа (последовательный или прямой)

Хотя возможно большое разнообразие типов файлов, большинство приложений сводится к двум типам: неявно открытые и явно открытые, внешние, последовательные, форматные файлы. Смотрите часть 3 "Операторы", где приведены описания операторов ввода/вывода. Для изучения полного описания записей, файлов и редакции форматной информации смотрите часть 4 "Система ввода/вывода".

4. ОПЕРАТОРЫ.

Операторы представляют собой набор функцый, таких как вычисления, запись результатов вычислений, изменение цепочек управления, чтение и запись файлов и определение информации для компилятора. Операторы Фортрана разделяются на два основных класса: выполняемые и невыполняемые.

Выполняемые операторы вызывают требуемые действия. Невыполняемые операторы никаких действий не вызывают. Вместо этого они определяют, описывают или классифицируют элементы программ, такие как входные точки, данные или программные единицы. Таблица 1-2 описывает функциональные категории операторов.

Категория

Описание

Присваивание

Комментарий

Управления

DАТА

FORMAT

Ввод/вывод

Описание

Функция-оператор

Заголовок программной единицы

Выполняемый. Присваивает величину

переменной или элементу массива.

Невыполняемый. Допускает коммента-

рии внутри подпрограммы.

Выполняемый. Управляет порядком вы-

полнения операторов.

Невыполняемый. Присваивает началь-

ные значения переменным.

Невыполняемый. Обеспечивает инфор-

мацию о редактировании данных.

Выполняемый. Определяет тексты и адреса передачи данных идругие особенности ввода/вывода.

Невыполняемый. Определяет отрибуты переменных, массивов и имен функций программиста.

Невыполняемый. Определяет простую,

локально используемую функцию.

Невыполняемый. Определяет начало программной единицы и описывает ее формальные параметры.

Таб. 1-2 Категории операторов Фортрана.

Для полной информации об использовании операторов MS-Фортана смотрите часть 3 "Операторы".

5. ВЫРАЖЕНИЯ.

Выражение - это формула для вычисления величины, оно содержит последовательность операндов и операторов. Операнды могут содержать обращения к функциям, переменные, константы и даже другие выражения. Операторы определяют действия, которые необходимо произвести над операндами.

В следующем выражении плюс (+) - это оператор, а A и B - операнды:

A + B

Существует четыре основных вида выражений в Фортране:

1. Арифметические выражения

2. Символьные выражения

3. Выражения отношения

4. Логические выражения

Каждый тип выражений работает с определенным типом операндов и использует определенный тип операторов. Результатом вычисления каждого выражения является величина определенного типа.

Выражения не являются операторами, но могут входить в них как их части. В следующем примере вся строка - это оператор, но только часть его после знака равенства является выражением:

X = 2.0/3.0 + A + B

Для подробного рассмотрения выражений в MS-Фортране смотрите часть 2 "Термины и концепции".

6. ИМЕНА.

Именами обозначают переменные, массивы, функции или подпрограммы в вашей программе, независимо от того определены ли они Вами или самой системой MC-Фортран. Имя в Фортране содержит последовательнось буквенноцифровых символов. Ограничения на имена таковы:

1. Максимальное число символов в имени не привышает 1320 (двадцать строк по 66 символов).

2. Начальный символ должен быть буквой, последовательность символов быть буквенноцифровой.

3. Пробелы игнорируются.

4. Значимыми являются первые шесть символов, остальные игнорируются.

С учетом этих ограничений на имя, любая последовательность символов может быть использована как имя в Фортране. Здесь нет зарезервированных имен, как в других языках.

Последовательности буквенных символов, используемые компилятором MС-Фортрана как ключевые слова, не смешиваются с именами пользователя. Компилятор различает ключевые слова по их контексту и поэтому на использование имен пользователями нет никаких ограничений. Поэтому, например, в программе могут быть массивы с именами IF,READ или GOTO и ошибок при этом не возникает (до тех пор пока не изменят правило, которому подчиняются все массивы). Однако использование ключевых слов как имен пользователя мешает "читабельности" программ, и его следует избегать.

Для получения подробной информации об области действия и использовании имен в MС-Фортране смотрите часть 2 "Термины и концепции".

7. ТИПЫ.

Данные в MC-Фортране относятся к одному из шести базовых типов:

1. Целые (INTEGER*2 и INTEGER*4)

2. Вещественные обычной точности (REAL*4 или REAL)

3. Вещественные двойной точности (REAL*8 или DOUBLE PRECISION)

4. Комплексный (COMPLEX*8 и COMPLEX*16)

5. Логический (LOGICAL*2 и LOGICAL*4)

6. Символьный (CHARACTER)

Типы данных объявляются. Если он не объявлен, то тип данных определяется по первой букве имени (по умолчанию или из оператора IMPLISIT). Оператор описания типа может также включать информацию о размерности.

Для более полной информации о типах данных смотрите часть 2. Для изучения детального описания оператора описание типа смотрите часть 3 "Операторы".

8. СТРОКИ.

Строка состоит из последовательности символов. Символы после 72-го в строке игнорируются, строка короче 72 символов считается заполненой пробелами.

Позиция символа в строке Фортрана имеет важное значение. Символы в позиции с 1-ой по 5-ую опознаются как метки операторов, символы в позиции 6 являются признаком продолжения, а символы в позициях с 7 по 72 является собственно оператором Фортрана. Коментарии опознаются по символу "С" или звездочке "*" в первой позиции, а метакоманды по знаку доллара в первой позиции.

С некоторыми исключениями, пробелы не являются значащими в Фортране. Символы табуляции имеют значение при некоторых обстоятельствах, описанных в части 2 "Термины и концепции".

Строки Фортрана представляют собой что-либо следующее:

1. Строка метакоманды.

2. Строка коментария.

3. Начальная строка (оператора).

4. Строка продолжения.

У метакоманды в первой позиции знак доллара и она управляет прохождением компилятора МС-Фортрана.

В строке коментария в первой позиции "С", "с" или звездочка, или вся строка пустая и игнорируется при обработке.

Начальная строка оператора имеет пробел или нуль в 6-ой позиции и все пробелы или метку оператора в позициях с 1-ой по 5-ую.

Строка продолжения - это не строка метакоманды, не строка коментария и не начальная строка. У нее с первой по пятую позиции пробелы, а в шестой символ, отличный от пробела или нуля.

Смотрите часть 2 "Термины и концепции" для подробностей использования различных видов строк МС-Фортрана и как из операторов составлять программы и совместные части программ.

9. СИМВОЛЫ.

В основном, программы на Фортране - это последовательность символов. Когда эти символы обрабатываются компилятором, они интерпретируются в различных контекстах как символы, имена, метки, константы, строки и операторы.

Символы, используемые в текстах программ на МС-Фортране, относятся к набору символов ASCII, полный список которого приведен в приложении С "Коды символов ASCII".Однако набор символов можно поделить на три основных группы:

1. 52 малых и больших латинских буквы (от A до Z и от a до z)

2. 10 цифр (от 0 до9)

3. Специальные символы (все другие печатаемые символы из набора символов ASCII).

Для более полной информации об использовании символов в МС Фортране смотрите часть 2 "Термины и концепции".


Термины и концепции

1. ЗАПИСЬ ПРОГРАММ.

Текст программы на Фортране - это последовательность символов ASCII. К символам ASCII относятся:

1. 52 прописных и заглавных английских буквы (от А до Z и от а до z).

2. 10 цифр (0,1,2,3,4,5,6,7,8,9)

3. Специальные символы (все остальные печатаемые символы таблицы кодов ASCII - в том числе и русские буквы).

1.1. Буквенноцифровые символы.

Английские буквы и цифры, рассматриваемые как одна группа, называются буквенноцифровыми символами. МС-Фортран воспринимает прописные буквы как заглавные в любом контексты, исключая символьные константы и холлеритовские поля. Поэтому следующие имена, определенные пользователем, эквивалентны в МС-Фортране:

ABCDE abcde AbCdE aBcDe

Последовательность сортировки для набора символов МС-Фортрана - это последовательность ASCII (Полную таблицу символов ASCII смотрите в приложении С "Коды символов ASCII").

1.2. Пробелы.

За исключением ниже приведенного списка символов пробел не является значащим в тексте программы на МС-Фортране и поэтому может использоваться для улучшения внешнего вида программы. Исключения следующие:

1. Пробелы внутри строковых констант являются значащими.

2. Пробелы внутри холлеритовских полей являются значащими.

3. Пробел или нуль в шестов позиции отличает начальную строку от строки продолжения.

1.3. Табуляторы.

Символ ТАВ имеет следующее значение в тексте программы на МС-Фортране:

1. Если ТАВ появляется в позициях с 1-ой по 5-ю, то считается, что следующий символ находится в 7-ой позиции.

2. ТАВ в позициях с 6-ой по 72-ю рассматривается как пробел, даже если он содержит внутри строки или холлеритового поля.

1.4. Позиции.

Символы в каждой строке расположены по позициям, первый - в первой позиции, второй - во второй и т.д.

Позиция, в которой находится символ, имеет существенное значение в Фортране. Позиция 1 используется для указания коментария и метакоманды. Позиции с 1-ой по 5-ю зарезервированы для меток операторов, а 6-я позиция для указания продолжения.

2. СТРОКИ И ОПЕРАТОРЫ.

Вы можете также считать, что текст программы на Фортране - это последовательность строк. Для компилятора значащими являются только первые 72 символа в строке, остальные им игнорируются. Строки, длиной меньше, чем 72 символа, считаются заполненными пробелами до 72-ой позиции (для иллюстрации этого смотрите ниже в этой части в описании символьных констант).

2.1. Начальные строки.

Начальной строкой называется строка, не являющаяся строкой коментария или метакоманды и содержащая пробел или нуль в шестой позиции. В первых пяти позициях строки должны быть пробелы или должна стоять метка. За исключением оператора, следующим за логическим IF, операторы Фортрана должны начинаться с начальной строки.

Метка оператора - это последовательность от одной до пяти цифр, хотя бы одна из которых должна быть не нуль. Метка может находится в любом месте в позициях с 1-ой по 5-ю в начальной строке. Пробелы и предшествующие нули являются незначащими.

2.2. Строки продолжения.

Строка продолжения - это любая строка, не являющая строкой коментария или метакоманды и содержащая в позиции 6 любой символ отличный от пробела или нуля. Первые пять символов строки продолжения позволяет существенно увеличить длину оператора. Если он не помещается в одну начальную строку, его можно продлить на 19 строк продолжения.

2.3. Строки коментария.

Строка считается строкой коментария, если выполняется одно из следующих условий:

1. В позиции 1 стоит "С" (или"с").

2. В позиции 1 стоит звездочка (*).

3. Строка состоит из одних пробелов.

Строки комментария не оказывают никакого воздействия на выполнение программы на Фортране. После строки коментария должна идти начальная строка или другая строка коментария. Строка продолжения не может следовать за строкой коментария.

2.4. Операторы описания данных и порядок следования операторов.

Оператор Фортрана содержит начальную строку и от 0 до 19 строк продолжения. Оператор может содержать до 1320 символов в позициях с 7-ой по 72-ю начальной строки и с 7-ой по 72-ю строк продолжения. Оператор END должен быть записан в позициях с 7-ой по 72-ю начальной строки и в его строке не может быть никаких других операторов (в дальнейшем на отдельные операторы мы будем ссылаться просто по их именам; смотрите часть 3 "Операторы" для определения различных операторов и их свойств).

Язык Фортран требует определенного порядка следования операторов и строк, составляющих программную единицу Фортрана. Кроме того МС-Фортран накладывает дополнительные требования на порядок строк и операторов в транслируемом тексте МС-Фортрана.

В основном, транслируемый текст содержит одну или несколько программных единиц (для более полной информации о трансляции программных единиц и подпрограмм смотрите часть 5 "Программы, подпрограммы и функции"). Различные правила по порядку следования операторов проиллюстрированы на рис.2-1 и описаны в следующих параграфах.

метакоманды $DO66,$STORAGE,$DECMATH

строки

комен-

тариев

операторы PROGRAM,FUNCTION, |

BLOCK DATА,SUBROUTINE | ----------------------------------------- другие оператор IMPLICIT |операторы | | мета- -------------------- | | команды другие операторы |PARAMETER |операторы| определения данных | | | ------------------------------- FORMAT | функции-операторы |операторы | | -------------------- | |

выполняемые | DATA | |

операторы | | |

оператор END

Рис.2-1 Порядок операторов в программых единицах и транслируемых текстах.

Для рисунка 2-1 существуют следующие соглашения:

1. Классы строк или операторов, расположенные выше или ниже других классов, должны следовать в заданном порядке.

2. Классы строк или операторов могут быть перемешаны с другими классами, находящимися с боку от них.

3. Метакоманды $LARGE и $NOTLARGE не могут находиться в разделе выполняемых операторов.

4. Подпрограммы BLOCK DATA не могут содержать функции-операторы, операторы FORMAT или выполняемые операторы.

Подпрограмма начинается с оператора либо SUBROUTINE, либо FUNCTION, либо BLOCK DATA и заканчивается оператором END. Основная программа начинается с оператора PROGRAM или любого другого оператора, отличного от операторов SUBROUTINE, FUNCTION или BLOCK DATA, и заканчивается оператором END. Подпрограмма и основная программа являются программными единицами.

В программной единице операторы должны следовать в порядке, соответствующем таким правилам:

1. Оператор PROGRAM, если он есть или оператор SUBROUTINE, FUNCTION, BLOCK DATA должны быть первым оператором программ-

ной единицы.

2. Оператор FORMAT может встретиться в любом месте после оператора SUBROUTINE, FUNCTION,BLOCK DATA или PROGRAM, если он

есть.

3. Все операторы определения типов должны предшествовать операторам DATA, операторам-функциям и выполняемым операторам.

4. Все операторы DATA должны стоять после операторов определения типов. Операторы DATA могут быть перемешаны с операторами-

функциями и выполняемыми операторами.

5. Обычно оператор PARAMETER предшествует всем другим операторам определения типов. Однако, когда некоторый оператор определяет

тип константы, используемой в операторе PARAMETER, оператор

PARAMETER должен следовать за этим оператором определения типа.

6. В операторах определения типа оператор IMPLICIT должен предшествовать всем другим операторам, за исключением оператора

PARAMETER.

7. Все операторы-функции должны предшествовать всем выполняемым операторам.

8. Метакоманды $DO66, $DECMATH и $STORAGE должны предшествовать любым другим операторам. Другие места команды могут находиться

где угодно в программной единице.

3. ТИПЫ ДАННЫХ.

В МС-Фортране существует шесть основных типов данных:

1. Целый (INTEGER*2 INTEGER*4)

2. Действительный (REAL*4 или REAL)

3. Двойной точности (REAL*8 или DOUBLE PRECISION)

4. Комплексный (COMPLEX*8 и COMPLEX*16)

5. Логический (LOGICAL*2 и LOGICAL*4)

6. Символьный.

Свойства, состав и форма представления констант каждого типа описаны на следующих страницах; требования по памяти приведены в таблице 2-2.

Тип

Число байтов

Примечание

LOGICAL

LOGICAL*2

LOGICAL*4

INTEGER

INTEGER*2

INTEGER*4

SHARACTER

SHARACTER*n

REAL

REAL*4

REAL*8

DOUBLE PRECISION

COMPLEX

COMPLEX*8

COMPLEX*16

2 или 4 2 4

2 или 4 2 4 1 n 4 4 8 8 8 8 16

1

1

2

3

4

5

6

Таб.2-2. Требования по памяти.

Примечание к таблице 2-2:

1. Может использоваться 2 или 4 байта. По умолчанию - 4, но метакомандой $STORAGE может быть установлено 2 или 4.

2. CHARACTER и CHARACTER*1 - синонимы.

3. Максимальное n равно 127.

4. REAL и REAL*4 - синонимы.

5. REAL*8 и DOUBLE PRECISION - синонимы.

6. COMPLEX и COMPLEX*8 - синонимы.

Замечания.

На многих микропроцессорах команды, необходимые для выполнения 16-битовой арифметики, значительно быстрее и короче соответствующих команд для выполнения 32-битовой арифметики. Поэтому, пока Вы не используете метакоманду МС-Фортрана $STORAGE, устанавливающую 2, в программе по умолчанию будет 32-битовая арифметика и она будет выполняться существенно медленней, чем ожидается. (Смотрите описание метакоманды $STORAGE в части 6). Установка 2 метакомандой $STORAGE ускорит выполнение программы и сделает ее короче.

Вы можете использовать внутреннюю функцию INT2 для указанию компилятору использовать в выраженных 16-битовую арифметику.

3.1. Целые типы данных.

Целый тип данных состоит из подмножества целых чисел. Целая величина - это точное представление соответствующего целого. Целая величина занимает 2 или 4 байта, в зависимости от установки метакоманды $STORAGE. 2-байтовое целое, INTEGER*2, может содержать величину в диапазоне от -32767 до 32767. 4-байтовое целое, INTEGER*4, может содержать величину в диапазоне от -2147483647 до

2147463647.

Целые константы содержат последовательность одной или более десятичных цифр или определитель системы счисления, за которым следует строка цифр в диапазоне от 0...(основание системы счисления - 1), где величины от 10 до 35 представлены буквами от А до Z соответственно. Указатель системы счисления содержит символ "#", которому обычно предшествует строка десятичных цифр, представляющая величину основания системы счисления. Если строка отсутствует, подразумевается, что основание равно 16. Если указатель системы счисления отсутствует, подразумевается, что основание равно

10.

Любому формату может предшествовать произвольный арифметический знак плюс (+) или минус (-). Целые константы не должны выходить из диапазона. Десятичная точка недопустима в целой констане.

Замечание: диапазон величин как для 16-битовых так и для 32- битовых целых не включает наиболее отрицательное число, которое может быть представлено в двоичном дополнительном коде этим числом битов. Эти числа, 16#8000 и 16#80000000 обрабатываются как "неопределенные" для сигнализации об ошибке.

Хотя максимальная величина 32-битового целого определена как 2**31-1, при компиляции и счете будут считываться и большие величины, которые выходят за диапазон 2**32. Но эти величины будут только считываться без ошибки, если их основание счисления не равно 10. Они будут интегрироваться как отрицательные числа с соответствующим внутренним представлением. Например, 16#FFFFFFFF приведет к тому что все биты 32-битового числа будут единицы, что соответствует арифметической величине -1.

Примеры целых констант:

123 +123 0

00000123 32767 -32767

-#AB05 2#010111 -36#ABZ07

В МС-Фортране точность целых определяется следующим образом:

*Константы.

Точность устанавливает метакоманда $STORAGE.

*Результат выражения.

Точность результата определяется точностью максимального операнда и величиной установленной метакомандой $STORAGE.

*Формальные параметры.

Если точность неизвестна, предлагается, что она такая же как у фактического параметра. Если тип известен, он должен точно соответствовать фактическому параметру (до тех пор пока параметр не передается величиной, использующей атрибут VALUE, описанный в части 7).

*Целые переменные и функции.

Точность задана для каждой переменной или функции (по умолчанию установлена метакомандой $STORAGE).

3.2. Тип действительных данных обычной точности IEEE.

Тип действительных данных (REAL или REAL*4) содержит подмножество действительных чисел обычной точности. Действительная величина обычной точности - это приближение требуемого действительного числа, занимающее 4 байта памяти. Диапазон действительных чисел обычной точности приблизительно следующий:

от 8.43Е-37 до 3.37Е+38 (положительные числа)

от -3.37Е+38 до -8.43Е-37(отрицательные числа)

0 (нуль)

Точность - больше шести десятичных цифр и меньше семи. Основная действительная константа содержит:

1. Необязательный знак.

2. Целую часть.

3. Десятичную точку.

4. Дробную часть.

5. Необязательный показатель экспоненты.

Целая и дробная части содержат по одной или больше десятичных цифр, а точка является разделителем (.). Как целая часть, так и дробная могут отсутствовать, но не обе. Некоторые простые целые константы:

-123.456 +123.456 123.456

-123. +123. 123.

-.456 +.456 .456

Экспоненциальная часть содержит букву "Е", за которой следует (необязательно) целая константа со знаком из одной или двух цифр.

Экспонента показывает, что предшествующую величину нужно умножить на десять в степени целая константа. Некоторые простые экспоненциальные части:

Е12 Е-12 Е+12 Е0

Действительная константа - это либо основная действительная константа, либо основная действительная константа с экспоненциальной частью, либо целая константа с экспоненциальной частью. Например:

+1.000Е-2 1.Е-2 1Е-2

+0.01 100.0Е-4 0.0001Е+2

Все это - одно и то же число, одна сотая.

3.3. Действительный тип данных IEEE двойной точности.

Действительный тип данных двойной точности (REAL*8 или DOUBLE PRECISION) содержит подмножество действительных чисел двойной точности. Это подмножество больше, чем подмножество типа данных REAL(REAL*4).

Действительная величина двойной точности - это, обычно приближение требуемого действительного числа. Действительная величина двойной точности может быть положительной, отрицательной или нулем и занимает восемь байтов памяти. Диапазон действительных величин двойной точности приблизительно следующий:

от 4.19D-307 до 1.67D+308 (положительные числа)

от -1.67D+308 до -4.19D-307 (отрицательные числа)

0 (нуль)

Точность больше чем 15 десятичных цифр. Константа двойной точности содержит:

1. Необязательный знак.

2. Целую часть.

3. Десятичную точку.

4. Дробную часть.

5. Обязательную экспоненциальную часть.

В экспоненте используется "D" а не "Е" для отличия от обычной точности. Целая и дробная части содержат одну или более десятичных цифр, а десятичная точка является разделителем. Целая часть или дробная, но не обе, могут отсутствовать.

Константа двойной точности - это либо основная действительная константа, за которой следует экспоненциальная часть, либо целая константа, за которой следует экспоненциальная часть. Например:

+1.123456789D-2 1.D-2 1D-2

+0.000000001D0 100.0000005D-4 .00012345D+2

Экспоненциальная часть содержит букву "D", за которой следует целая константа. Целая константа может иметь необязательный знак. Экспонента показывает, что предшествующее число должно быть умножено на десять в степени целый показатель экспоненты. Некоторые простые экспоненты:

D12 /D-12 D+12 /D0

3.4. Формат десятичных чисел с плавающей точкой обычной и двойной точности.

Десятичные числа с плавающей точкой содержат байт с битом знака и семибитовой экспонентой в 64-ричной системе счисления, за которым следует мантисса из 6 (для обычной точности) или из 14 (для двойной точности) двоично-десятичных цифр, упакованных по 2 в байт (если байт экспоненты нулей - число равно нулю). Запись десятичных констант с плавающей точкой производится в том же формате, что и для стандартных действительных констант Фортрана обычной и двойной точности. Допустимый диапазон чисел обычной точности следующий:

от +1.0Е-64 до +9.99Е+62 (положительные числа)

от -9.99+62 до -1.0Е-64 (отрицательные числа)

0 (нуль)

Точность равна точно 6 цифрам.

Допустимый диапазон чисел двойной точности следующий: от +1.0D-64 до +9.999D+62 (положительные числа) от -9.99D+62 до -1.0D-64 (отрицательные числа)

0 (нуль)

Точность равна точно 14 цифрам.

Метакоманда $DECMATH вызывает представление констант текстового файла в виде с плавающей запятой.

3.5. Комплексные типы данных.

Тип данных COMPLEX*8 задает пару действительных чисел обычной точности, второе из которых представляет мнимую часть. Число COMPLEX*8 занимает 8 байтов памяти.

Комплексная константа состоит из необязательного знака, левой скобки, двух целых или действительный чисел, разделенных запятой, и правой скобки. Элемент данных COMPLEX*16 содержит упорядоченную пару действительных чисел двойной точности. Элемент данных COMPLEX*16 занимает 16 байтов памяти. Каждая компонента (действительная и мнимая) COMPLEX*8 - это REAL*4. Каждая компонента COMPLEX*16 - это REAL*8.

3.6. Логические типы данных.

Логический тип данных содержит две логических величины .TRUE. и .FALSE. Логическая переменная занимает два или четыре байта памяти в зависимости от установки метакоманды $STORAGE. По умолчанию это 4 байта. Значение логической переменной независит от метакоманды $STORAGE, которая соответствует требованию ANSI, чтобы логические, целые и действительные переменные обычной точности занимали одинаковое место в памяти. Величины LOGICAL*2 занимают два байта. Младший значащий (первый) байт - либо 0(.FALSE.), либо 1(.TRUE.); старший значащий байт не определен.

Переменные LOGICAL*4 занимают два слова, младшее значащее (первое) из которых содержит величину LOGICAL*2. Старшее значащее слово не определено.

3.7. Символьный тип данных.

Символьный тип данных содержит последовательность символов ASCII. Длина символьной величины равно числу символов в последовательности. Длина заданной константы или переменой фиксирована и должна составлять от 1 до 127 символов. Символьная переменная занимает один байт памяти для каждого символа в последовательности. Предполагается, что символьная переменная занимает цепочку байтов без учета границ слов. Однако компилятор предполагает, что несимвольные переменные, следующие за символьными, начинаются с границы слова.

Символьная константа содержит последовательность из одного или более символов, заключенных в пару одиночных кавычек. В символьной константе символы пробелов допустимы и значимы. Регистры буквенных символов значимы. Одиночная кавычка в символьной константе представляется двумя одиночными кавычками без пробелов между ними.

Длина символьной константы равна числу символов между одиночными кавычками. Пара одиночных кавычек означает один символ. Несколько примеров символьных констант:

'A'

' '

'Help!'

'A very long CHARACTER constant'

'O''Brien'

''''

Последний пример - это символьная константа содержащая один апостроф (одиночную кавычку).

Символьной переменной может быть присвоена несимвольная величина, если присвоение не включено в выражение. Допустимо следующее:

REAL R

CHARACTER*5 C

C=R, но следующее недопустимо

REAL R

CHARACTER*5 C

C=R+1.0

Фортран допускает строки исходного текста длиной до 72 позиций. Короткие строки дополняются пробелами до 72 позиции. Когда символьная константа переходит через границу строки, ее величина такова, как если бы часть строки продолжения, начиная с 7-ой позиции, добавить к 72-й позиции начальной строки. Поэтому следующий текст Фортрана

200 СН='ABC

X DEF'

Эквивалентен:

200 CH='ABC(58 пробелов)...DEF'

С 58-ю пробелами между С и Д, равными пространству от С в 15-й позиции до 72-й позиции плюс одному пробелу в 7-ой позиции строки продолжения. Таким образом могут быть представлены очень длинные символьные константы.

3.7.1. Символьные подстроки.

МС-Фортран поддерживает подстроки так же, как они определены в полном языке Фортран 77. Подстроки относятся к типу CHARACTER и используются для доступа к слитным частям символьной переменной. Они имеют следующий синтаксис:

переменная ([первый №]:[последний №])

или

массив (индекс1 [,индекс2])([первый №]:[последний №])

где

переменная это символьная переменная

массив (индекс1[,индекс2]) это элемент символьного массива

первый № и это целые выражения, определяющие

последний № первый (самый левый) и последний

(самый правый) символ в подстроке

Как первый №, так и последний № необязательны. По умолчанию первый № равен единице, так что подстрока начинается с первого символа строки. По умолчанию последний № -это длина строки, так что подстрока кончается на последнем символе строки.

Отметим что переменная(:) эквивалентна переменная. Длина подстроки равна последний №-первый №+1. Взятая длина должна быть длиной символьной переменной и при этом должны удовлетворятся следующие отношения:

1. первый №іпоследний №

2. 1іпервый №ідлина

3. 1іпоследний №ідлина

Транслятор не проверяет эти соотношения. Если они не сообщены Вы получите неопределенный результат.

Подстроки не могут быть представлены величиной. Если Вы попытаетесь это сделать, то получите ошибку 97.

Вы можете использовать подстроки для конкатенации строк, например, CHARACTER A*12, B*20, C*32

A='Now is the t'

B='ime for all gooel men'

C(1:12)=A

C(13:12+20)=B

WRITE(*,*)'C=',C

END

В результате на выходе будет

C=Now is the time for all gooel men

4. ИМЕНА.

Имя в МС-Фортране или идентификатор содержат последовательность буквенноцифровых символов (максимум по 66 символов в строке, умноженных на 20 строк). Начальный символ должен быть буквенным, следующие символы должны быть буквенноцифровыми. Пробелы игнорируются. Только первые шесть буквенноцифровых символов являются значащими, остальные игнорируются.

Имя означают определенную пользователем или системой переменную, массив или программную единицу. Любая значащая последовательность символов может быть использована для любого имени Фортрана. Здесь нет зарезервированных имен, как в других языках. Последовательности буквенных символов, используемые как ключевые слова компилятора МС-Фортрана не смешиваются с именами, определяемыми пользователем. Компилятор отличает ключевые слова по их контексту и на использование имен, определяемых пользователем, нет ограничений.

Поэтому в программе могут быть массивы с именами, например, IF, READ или GO TO и при этом не будет ошибок (пока они будут использоваться по правилам, которым должны подчиняться). Однако, использование ключевых слов для имен пользователя часто мешает читабельности программы и, практически, этого следует избегать.

4.1. Область действия имен Фортрана.

Область действия имени - Это диапазон операторов, где это

имя известно, или на него можно ссылаться в программе на Фортране.

В основном, область действия имени либо глобальна, либо локальна, хотя и существуют некоторые исключения. Имя может быть использовано лишь в соответствии с единственным определением в его области действия. Однако, одно и тоже имя может иметь разные определения в разных областях действия.

Имя с глобальной областью действия может быть использовано в более чем одной программной единице (подпрограмме, функции, основной программе) и на него можно ссылаться в них. Фактически, имена с глобальной областью действия используются обычным последовательным образом в одной и той же программе. Все имена процедур, функций, подпрограмм, COMMON-блоков так же как и имена программ имеют глобальную область действия. Поэтому не может быть подпрограммы-функции с тем же именем, что и подпрограмма или COMMON-блок. Также в одной программе две процедуры-функции не могут иметь одно и то же имя.

Имя с локальной областью действия доступно только внутри одной программной единицы. Имя с локальной областью действия может быть использовано в другой программной единице по другому назначению или подобно, но нельзя требовать чтобы оно имело одинаковое значение в разных областях действия. Локальную область действия имеют имена переменных, массивы, формальные параметры и функций-операторы.

Единственным исключением в правилах областей действия является имя, присваиваемое данным в COMMON-блоке. Можно ссылаться на глобально действующее имя COMMON-блока в программной единице, в которой определено такое же локально действующее имя. Это возможно благодаря тому, что имя COMMON-блока всегда заключено в косые черточки, например, /FROG/ и поэтому всегда отличимо от таких же, но обычных имен.

Другое исключение из правил областей действия сделано для параметров функции-оператора. Область действия параметров функцииоператора ограничена единственным оператором, входящим в эту функцию. Любое другое использование этих имен в функции-операторе недопустимо, пока они могут быть задействованы и вне ее.

4.2. Имена Фортрана по умолчанию.

При проходе по выполняемым операторам программа компилятор заводит имена, встречающиеся ему впервые ( т.е. те, которые не определены точно ), в соответствии с контекстом.

Если имя используется как переменная, его тип определяется по первой букве имени ; I, J, K, L, M или N по умолчанию принимаются целыми, а остальные буквы - действительными числами. Вы можете использовать оператор IMPLICIT для замены соответствия типа и начальной буквы (подробнее, смотрите часть 3 "Операторы"). Это же правило используется при использовании имени в функции для определения типа возвратной величины.

Когда имя используется как указатель в операторе CALL, подразумевается что это имя подрограммы. Точно так же подразумевается, что имя, используемое в ссылке на функцию, - это имя функции. Если подпрограмма или функция является частью этой же компилируемой единицы ( т.е. находятся в этом же текстовом файле ) и это определение приведено до оператора CALL или ссылки на функцию, то компилятор проверит, чтобы тип фактических параметров и их число в операторе CALLили ссылке на функцию соответствовали определениям в операторе SUBROUTINE или FUNCTION.

5. ВЫРАЖЕНИЯ.

Выражение - это формула для вычисления величины. Она содержит последовательность операндов и операторов. Операнды могут содержать вызовы функций, переменные, константы, или другие выражения. Операторы определяют действия, которые необходимо произвести над операндами.

В Фортране существует четыре класса выражений :

1. Арифметические.

2. Символьные.

3. Отношения.

4. Логические.

5.1. Арифметические выражения.

Результатом арифметических выражений являются величины типов INTEGER, REAL, DOUBLE PRECISION или COMPLEX. Простейшие виды арифметических выражений - это :

1. Константы.

2. Использование переменных.

3. Использование элементов массивов.

4. Использование функций.

Используемые величины переменных или элементов массивов должны быть определены до того, как они появятся в арифметическом выражении. Также, величины целых переменных должны быть арифметическими, а не величинами меток операторов, установленными оператором ASSIGN.

Другие арифметические выражения создаются из простейших форм, приведенных выше в списке, с использованием скобок и арифметических операторов указанных в таблице 2-3.

Оператор

Операция

Приоритет

* *

/

*

-

+

Возведение в степень

Деление

Умножение

Вычитание или отриц.

Сложение или идентичность

Высший

Средний

Средний

Низший

Низший

Таблица 2-3. Арифметические операторы.

Все операторы могут быть использованы как бинарные, т.е. стоящие между двумя операндами арифметического выражения. Плюс (+) и минус (-) могут быть также унарными и предшествовать какому-либо одиночному оператору.

Операции равного приоритета, за исключением возведения в степень выполняются слева направо. Возведение в степень выполняется справа налево. Поэтому каждое из следующих выражений слева эквивалентно выражению справа :

A/B * C (A/B) * C

A * * B * * C A * * (B * * C)

Арифметические выражения могут быть получены обычным математическим образом, как в большенстве языках программирования. Однако Фортран заприщает ставить два оператора подряд. Например, нельзя:

A * * - B но можно

A * * (- B)

Унарный минус имеет наименьший приоритет. Поэтому выражение -А**В понимается как - (А ** В).

Вы можете использовать скобки для группирования оераций и задания порядка, в котором операции должны выполняться.

5.2. Деление целых.

Результатом деления двух целых чисел является величина, равная математическому частному двух этих величин, округленного до целого в сторону нуля. Поэтому 7/3 превращается в 2, а (-7)/3 превращается в -2. Как 9/10, так и 9/(-10) равны нулю.

5.3. Преобразования типов арифметических операндов.

Когда в арифметическом выражении все операнды одного типа,

то величина, являющаяся результатом этого выражения, этого же типа.

Когда операнды разного типа, типом результата выражения является тип операнда максимального ранга.

Ранг операнда зависит от его типа данных соответсвенно следующему списку :

1. INTEGER * 2 (низший ранг)

2. INTEGER * 4

3. REAL * 4

4. REAL * 8

5. COMPLEX * 8

6. COMPLEX * 16 (высший ранг)

Например, результатом операции над элементами INTEGER*2 и REAL*4 будет величина, относящаяся к типу данных REAL*4.

Специальный случай : операция над операндами типов REAL*8 и COMPLEX*8 породит COMPLEX*16, а не COMPLEX*8.

Тип данных всего выражения - это тип данных результата последней операции, выполненной при вычислении всего выражения.

Типы данных операций могут быть INTEGER*2, INTEGER*4, REAL*4, REAL*8, COMPLEX*8 или COMPLEX*16.

Целые операции выполняются только над целыми операндами. Дробный результат от деления в целой арифметике превращается в целое, а не округляется. Поэтому следующие выражение равно нулю, а не единице :

1/4 + 1/4 + 1/4 + 1/4

Распределение памяти для типа INTEGER без указания длины *2 или

*4 в определении типа зависит от использования метакоманды $STORAGE

(для подробностей смотрите раньше в этой части и части 6 "Метакоманды МС-Фортрана").

Действительные операции выполняются только над действительными операндами или комбинацией действительного и целого операндов. Целые операнды сначала преобразуются в действительные прибавлением к каждому дробной части равной нулю. Затем для вычисления выражения используется действительная арифметика. Но в следующем операторе сначало осуществляется целое деление I на J, а потом действительное

умножение результата на X :

Y = (I/J) * X

5.4. Символьные выражения.

Результатом символьных выражений является тип CHARACTER. Формы символьных выражений следующие :

1. Символьные константы.

2. Ссылки на символьные переменные.

3. Ссылки на элементы символьных массивов.

4. Любые символьные выражения, заключенные в скобки.

5. Ссылки на символьные функции.

В символьных выражениях нет операторов.

5.5. Выражения отношения.

Выражения отношения сравнивают величины двух арифметических или двух символьных выражений. Арифметическое выражение нельзя сравнивать с символьным, пока не определена метакоманда $NOTSTRICT. В этом случае арифметические выражения сравнимы с символьными. Результатом выражения отношения является тип LOGICAL. Для сравнеия величин в выражениях отношения можно использовать любой оператор, указанный в таблице 2-4.

Оператор

Операция

.LT.

.LE.

.EQ.

.NE.

.GT.

.GE.

Меньше чем

Меньше или равно

Равно

Не равно

Больше чем

Больше или равно

Таблица 2-4. Операторы отношения.

Все операторы отношения бинарные и появляются между двумя операндами. Среди операторов отношения нет относительного старшинства или сочетательности и поэтому выражение следующего вида нарушает правила типов для операндов :

A .LT. B .NE. C

Выражения отношения могут появляться только в логических выражениях.

Выражения отношения с арифметическими операндами могут иметь один операнд типа INTEGER и один операнд типа REAL. В этом случае перед вычислением выражения отношения целый операнд будет преобразован в тип REAL.

Выражения отношения символьных операндов сравнивают положение этих операндов в упорядоченной последовательности ASCII. Операнд является меньше чем другой, если он появляется раньше в упорядоченной последовательности. Если сравниваются операнды разной длины, то более короткий рассматривается как доплненный справа пробелами до такой же длины как и длинный.

5.6. Логические выражения.

Результатом логического выражения является величина типа LOGICAL. Простейшие формы логических выражений следующие :

1. Логические константы.

2. Ссылки на логические переменные.

3. Ссылки на элементы логических массивов.

4. Ссылки на логические функции.

5. Выражения отношения.

Другие логические выражения состоят из простейших логических форм, приведенных выше, с использованием скобок и логических операторов, указанных в таблице 2-5.

Оператор

Операция

Приоритет

.NOT.

.AND.

.OR.

.EQV.

.NEQV.

Отрицание

Конъюнкция

Включающая дизъюнкция

Эквивалентность

Неэквивалентность

Наивысший

Средний

Средний

Низший

Низший

Таблица 2-5. Логические операторы.

Операторы .AND., .OR., .EQV., .NEQV. не являются бинарными и появляются между двумя операндами логических выражений. Оператор .NOT. - унарный и предшествует своему операнду.

Операции равного приоритета выполняются слева направо, поэтому, например,

A .AND. B .AND. C

Эквивалентно :

(A .AND. B) .AND. C

Пример правила приоритета :

.NOT. A .OR. B .AND. C

выполняется как :

(.NOT. A) .OR. (B .AND. C)

Два .NOT. не могут соседствовать с друг другом, хотя

A .AND. .NOT. B

- это пример допустимого выражения с двумя операторами подряд.

Другой пример правила приоритетов и использования

.EQV. и .NEQV. :

.NOT. A . EQV. B .OR. C .NEQV. D .AND. E

может быть выполнено как

((.NOT. A) .EQV. (B .OR. C)) .NEQV. (D .AND. E)

Логические операторы имеют тоже самое значение, что и в стандартной математической симантике с неразделительным .OR.. Например:

.TRVE. .OR. .TRVE.

даст величину

.TRVE.

5.7. Приоритеты операторов.

Когда в одном выражении встречаются арифметические, логические операторы и операторы отношения, они выполняются со следующими приоритетами :

1. Логические (низший).

2. Отношения (средний).

3. Арифметические (высший).

5.8. Правила вычисления выражений.

Любая переменная, массив, элемент или функция, на которые ссылаются в выражении, должны быть определены до момента ссылки. Целые переменные должны быть определены арифметической величиной, не величиной метки оператора, устанавливаемой оператором ASSIGN.

Определенные арифметические операции, также как деление на нуль, математически неопределенные, запрещены. К другим запрещенным операциям относятся возведение нулевого операнда в нулевую или отрицательную степень и возведение отрицательного операнда в степень типа REAL.

5.9. Ссылки на элемент массива.

Ссылка на элемент массива определяет один элемент массива.

Ее синтаксис следующий :

массив ( индекс [, индекс ] . . . )

Где

массив это имя массива

индекс это индексное выражение, являющиеся целым выражением

для выбора заданного элемента массива. Число индексных

выражений должно соответствовать числу размерностей в

описании массива. Величина индексного выражения должна

находится между единицей и верхней границей для соответствующей ей размерности включительно.

С Пример операторов размерности

DIMENSION A(3,2), B(3,4), C(4,5), D(5,6), V(10)

EQVIVALENCE (X, V(1)), (Y, V(2))

D(i,j) = D(i,j)/PIVOT

C(i,j) = C(I,J) + A(i,k) * B(k,j)

READ (*,*) (V(N), N = 1, 10)


Операторы

1.КАТЕГОРИИ ОПЕРАТОРОВ.

Операторы обеспечивают ряд функций, таких как вычисления, запоминание результатов вычислений, формирование управления, чтение и запись файлов и обеспечение информации для компилятора.

Операторы Фортрана делятся на два основных класса : выполняемые и невыполняемые. Выполняемые операторы, будучи употребленными, вызывают действие. Невыполняемые операторы сами по себе действий не вызывают. Вместо этого они определяют, описывают или классифицируют элементы программ, такие как входные точки, данные или программные единицы.

К невыполняемым операторам относятся следующие :

1. Операторы PROGRAM, SUBROUTINE, FUNCTION и BLOCK DATA

2. Операторы описаний

3. Оператор DATA

4. Оператор FORMAT

Выполняемые операторы представляют собой существенно большую группу и могут быть разделены на следующие категории :

1. Операторы присвоения

2. Операторы управления

3. Операторы ввода/вывода.

1.1.Операторы PROGRAM, SUBROUTINE, FUNCTION и BLOCK DATA.

Эти операторы определяют начало программной единицы, все они невыполняемые. Дополнительную информацию смотрите ниже в этой главе.

Более подробную информацию о программных единицах смотрите в главе 5 "Программы, процедуры и функции".

2.ОПЕРАТОРЫ ОПИСАНИЯ.

В МС-Фортране операторы описания невыполняемые. Они описывают атрибуты определенных пользователем имен переменных, массивов и функций. В следующей таблице приведены девять операторов описаний.

Оператор

Назначение

COMMON

Обеспечивает совместное использование памяти двумя или несколькими программными единицами

DIMENSION

Описывает имя, определенное пользователем, как массив и определяет число его элементов

EQUIVALENCE

Определяет, что две или более переменных или массива используют одну и ту же память

EXTERNAL

Определяет заданное пользователем имя как внешнюю процедуру или функцию

IMPLICIT

Определяет тип по умолчанию для имен, определенных пользователем

INTERFACE

Описывает подпрограмму

INTRINSIC --------------

Объявляет имя встроенной функцией ------------------------------------------------

Оператор

Назначение

SAVE

Заставляет переменные сохранять их значения при выходе из процедуры, в которой они определены

Описание типа

Определяет типы имен, заданных пользователем

Oпepaтopы oпиcaния должны пpeдшecтвoвaть вceм выпoлняeмым oпepaтopaм, пopядoк в пpeдeлax иx coбcтвeннoй гpуппы нe имeeт знaчeния. Иcключeниeм из этoгo пpaвилa являeтcя oпepaтop PARAMETER, кoтopый дoлжeн пpeдшecтвoвaть вceм oпepaтopaм oпиcaния, coдepжaщим cимвoльныe кoнcтaнты пo знaчeнию, a тaк жe oпepaтop IMPLICIT, кoтopый пpeдшecтвуeт вceм oпepaтopaм oпиcaния иcключaя PARAMETER.

2.1.Oпepaтop DATA.

Oпepaтop DATA зaдaeт нaчaльнoe знaчeниe пepeмeнным. Oпepaтop DATA являeтcя нeoбязaтeльным, нeвыпoлняeмым oпepaтopoм. Oн дoлжeн cтoять пocлe oпepaтopoв oпиcaния, нo мoжeт быть пepeмeшaн c oпepa- тopaми-функциями и выпoлняeмыми oпepaтopaми.

2.2.Oпepaтop FORMAT.

Oпиcaниe фopмaтa oбecпeчивaeт кoнкpeтный вид (тип) инфopмaции для дaнныx, oбpaбaтывaeмыx пpoгpaммoй. Oпиcaниe фopмaтa мoжeт быть зaдaнo oпepaтopoм FORMAT, a тaк жe кoнкpeтным видoм кoнcтaнт (cмo- тpитe глaву 4 гдe ecть дoпoлнитeльнaя инфopмaция пo зaдaнию фopмaтa дaнныx)

2.3.Oпepaтop пpиcвaивaния.

Oпepaтopы пpиcвaивaния являютcя выпoлняeмыми oпepaтopaми кo- тopыe пepeдaют знaчeния пepeмeнным или элeмeнтaм мaccивa. Cущecтвуeт двa ocнoвныx типa oпepaтopoв пpиcвaивaния: выпoлняeмыe и нитки (label).

2.4.Oпepaтopы упpaвлeния.

Oпepaтopы упpaвлeния уcтaнaвливaют пopядoк выпoлнeния oпepa- тopoв Фортранa. Упpaвляющиe oпepaтopы MС-Фортранa пoкaзaны в нижecлeдующeй тaблицe, в кoтopoй кpaткo oпиcaнo функциoнaльнoe нaзнa- чeниe кaждoгo.

OПEPATOPЫ

HAЗHAЧEHИE

CALL

Bызывaeт и выпoлняeт пoдпpoгpaммы (subroutine)

из дpугиx пpoгpaммныx eдиниц.

| | Иcпoльзуeтcя пpeимущecтвeннo для пpoдoл. |

| CONTINUE |пути c тoгo мecтa,гдe cтoят нaмeчeнныe oпе-|

| |ратopы,в чacтнocти иcпoльзуeтcя в кaчecтвe |

| | кoнeчнoгo oпepaтopa в oпepaтope циклa DO.|

- | | ---------- OПEPATOPЫ | --------------------------------------------- | HAЗHAЧEHИE |
| | | | DO | | Opгaнизoвывaeт пoвтopнoe выпoлнeниe oпepa| | торов следующих за DO и вплоть до конеч- | | ного оператора. |
| | ELSE | | Относится к блоку ELSE. |
| | ELSEIF | | Oтносится к блоку ELSEIF. |
| | END | | Зaвepшaeт выпoлнeниe пpoгpaммoй eдиницы. |
| | | ENDIF | | Укaзывaeт кoнeц нaбopa cepии oпepaтopoв, | | cлeдующиx зa блоком оператора IF. |
| | | | GO TO | | Пepeдaeт упpaвлeниe в кaкoe-либo мecтo | | программы,включает следующие разновиднос-| |ти:присваиваемый,вычисляемый и безусловный.|
| | | | | | IF | | Opгaнизoвывaeт выпoлнeниe пo уcлoвию дpу-| | гих oпepaтopoв,зaвиcящиx oт знaчeний oп- | | ределенного выpaжeния.Paзличaют cлeдующиe| | виды oпepaтopa IF - apифмeтичecкий, | | логический или блок. |
| | | PAUSE | | Ocтaнaвливaeт выпoлнeниe пpoгpaммы,дo тex | | нe будeт нaжaтa клaвишa RETURN. |
| | | | RETURN | | Boзвpaщaeт упpaвлeниe в пpoгpaммную eдини-| | ца из которой была вызвана данная програм-| | ма или функция. |
| | STOP | | Oкoнчaниe пpoгpaммы. |

2.5.Oпepaтopы ввoдa/вывoдa.

Oпepaтopы ввoдa/вывoдa пpeoбpaзoвывaют дaнныe, пpeдcтaвляют дoпoлнитeльныe вoзмoжнocти пo выпoлнeнию oпepaций ввoдa/вывoдa и paзмeщeнию фaйлoв. B cлeдующeй тaблицe пepeчиcлeны oпepaтopы ввo- да/вывoда MС-Фортранa.

OПEPATOPЫ

HAЗHAЧEHИE

BACKCPACE

Ставит указатель в файле, связанном с заданным устройством, на начало предыдущей записи.

CLOSE

Отсоединяет указанное устройство и защищает его от доступа к нему ввода/вывода.

ENDFILE

Cтавит запись конца файла в файл, связанный с заданным устройством.

| INQUIRE | Boзвpaщaeт знaчeния, описывающие свойства |

| |имeнованнoгo фaйлa или заданного устройства|

- | | ---------- OПEPATOPЫ | --------------------------------------------- | HAЗHAЧEHИE |
| | | LOCKING | | Закрывает прямой доступ к файлам и | | зaпиcям. |
| | | OPEN | | Coeдиняeт номep устройства с внешним уст- | |poйcтвoм или c фaйлoм нa внeшнeм уcтpoйстве|
| | | READ | | Пepeдaчa дaнныx из фaйлa в cooтвeтcтвующиe| | элементы списка ввода/вывода. |
| | | REWIND | | Пepeмeщaeт указатель на первую запись в | | файле, связанном с заданным устройством. |
| | | WRITE | | Пepeдaeт дaнныe из элементов списка | | ввода/выводa в фaйл. |

Пpимeчaниe: Oбработка ошибок. Ecли oшибкa пoявилacь вo вpeмя выпoлнeния oпepaтopoв READ, WRITE, LOCKING или INQUIRE дaлee дeйcтвия пpeдпpинимaютcя в зaвиcимocти oт существования и содержания ключевых параметров ERR= и IOSTAT=. (Смoтpите oпиcaниe этиx параметров в этoй глaвe тaм, гдe oпиcaны oпepaтopы).

a. Ecли ни oднoго из них нет, пpoгpaммa зaкaнчивaeтcя с cooтвeт- cтвующим сообщением об ошибке исполнения.

в. Ecли задано тoлькo ERR=мeткa2, упpaвлeниe пepeдaeтcя на эту

метку.

c. Ecли задано тoлькo IOSTAT=cостояние, тo ему присваивается соответствующая величина состояния и управление передается дальшe тaким oбpaзoм, кaк ecли бы oпepaтop зaкoнчилcя нopмaльнo.

d. Ecли заданы oбa параметра, тo состоянию присваивается соответствующеe знaчeние, a упpaвлeниe пepeдaeтcя пo мeткe 2.

Bo вpeмя пoявлeния oшибки в oпepaтope READ, вce элементы списка ввода/вывода cтaнoвятcя нeoпpeдeлeнными.

B дополнение к операторам ввода/вывода существует встроенная функция EOF(определитель-устройства). Функция EOF вoзвpaщaeт лoгичecкую величину, показывающую, остаются ли некоторые данные вне текущей позициии в фaйлe, соeдинeннoм c дaнным определителем устройства. Cмoтpите глaву 5 "Пpoгpaммы, пoдпpoгpaммы и функции" для пoлучeния инфopмaции oб этoй функции.

3.CПРАВОЧНИК ОПЕРАТОРОВ.

Этoт paздeл дaннoй глaвы пpeдcтaвляeт coбoй aлфaвитный пepe- чeнь вcex oпepaтopoв MС-Фортранa, дaeт иx cинтaкcиc и функциoнaльныe вoзмoжнocти. Kaждый oпepaтop cнaбжeн кoмeнтapиями и нeoбxoдимыми пpимepaми.

Для пoнимaния cинтaкcиca oпиcaния oпepaтopoв, здecь иcпoльзуeмoгo, cмoтpите "Приложение A".

ASSIGN

Пpиcвoeниe знaчeний мeтки фopмaтa или oпepaтopa цeлoй пepe- мeннoй.

ASSIGN мeткa TO пepeмeннaя

Где:

мeткa - мeткa фopмaтa или мeткa oпepaтopa

пepeмeннaя - цeлaя пepeмeннaя

Особенности.

Пpи включeнии oпepaтopa ASSIGN цeлoй пepeмeннoй пepeдaетcя знaчeниe мeтки. Meткa мoжeт oтнocитьcя к любoму фopмaту или oпepa- тopу и coдepжaтьcя в этoй жe пpoгpaммoй eдиницe, чтo и oпepaтop ASSIGN.

Koгдa в пpиcвoeнии учacтвуeт oпepaтop GO TO, пepeмeннaя дoл-

жнa имeть знaчeниe мeтки oпepaтopa. Koгдa в oпepaтopax ввода/вывода

иcпoльзуeтcя cпeцификaция фopмaтa, пepeмeннaя дoлжнa имeть знaчeниe

мeтки cooтвeтcтвующeгo oпepaтopa FORMAT. Oпepaтop ASSIGN ecть cпocoб пpиcвoeния знaчeний мeтки пepeмeннoй.

Знaчeниe мeтки нe являeтcя нeoбxoдимым кaк ee нoмep. B cлe- дующeм пpимepe знaчeниe пepeмeннoй IVBL нe oбязaтeльнo дoлжнo быть paвнo 400:

ASSIGN 400 TO IVBL

Пepeмeннaя, нe oпpeдeлeннaя кaк integer нe мoжeт быть иcпoльзoвaнa в apифмeтичecкoм выpaжeнии дo тex пop, пoкa этo нe будeт cдeлaнo (пpи пoмoщи вычиcлитeльнoгo пpиcвoeния или oпepaтopa READ).

Пpиcвoeниe

Peзультaты выпoлнeннoгo выpaжeния пepeдaютcя пepeмeнным или элeмeнтaм oпpeдeлeннoгo мaccивa.

Пepeмeннaя = выpaжeниe.

Гдe:

пepeмeннaя - ecть oбычнaя пepeмeннaя или элeмeнт мaccивa

выpaжeниe - ecть любoe выpaжeниe

Особенности.

Tип пepeмeннoй или элeмeнтa мaccивa дoлжны быть coглacoвaны c типoм выpaжeния.

1. Ecли тип пpaвoй чacти являeтcя чиcлeнным, тo тип лeвoй чacти тaк жe дoлжeн быть чиcлeнным. Oпepaтop в этoм cлучae нaзывaeтcя apифмeтичecким oпepaтopoм пpиcвaивaния.

2. Ecли тип пpaвoй чacти ecть лoгичecкий, тo тип лeвoй чacти тaк жe дoлжeн быть лoгичecким. Oпepaтop нaзывaeтcя лoгичecким

oпepaтopoм пpиcвaивaния.

3. Ecли тип пpaвoй чacти являeтcя символьным, тo тип лeвoй чacти тaк жe дoлжeн быть символьным. Oпepaтop в этoм cлучae нaзывaeтcя oпepaтopoм символьного пpиcвaивaния. Oднaкo, ecли Bы

иcпoльзовали мeтaкoмaнду $NOTSTRICT, тo тип лeвoй чacти мoжeт

быть чиcлeнным, лoгичecким или символьным;а опepaтop по-прежнему нaзывaeтcя oпepaтopом символьного пpиcвaивaния.

Ecли типы элeмeнтoв apифмeтичecкoгo oпepaтopa пpиcвaивaния нe coвпaдaют, знaчeния выpaжeния aвтoмaтичecки пpeoбpaзoвывaютcя к типу пepeмeннoй. Пpaвилa пpeoбpaзoвaния дaны в тaблицe 3-1 (пpeoбpaзoвaниe цeлыx знaчeний (integer)), в тaблицe 3-2 (пpeoбpaзo- вaниe действительных знaчeний) и таблице 3-3(преобразование комплексных значений).

Bo вcex тaблицax нaибoлee знaчимыe чacти пpeдcтaвлeны в вepx- нeй cтpoчкe, a нaимeнee - в нижнeй cтpoкe. Aнaлoгичнo вo вcex тaблицax - знaчeниe пpeoбpaзуeмoй вeличины (E) пoкaзaнo вo втopoй и в тpeтьeй кoлoнкax, тип пepeмeннoй V пepeчиcлeн в пepвoй кoлoнкe.

V \ E

INTEGER

2

INTEGER

4

INTEGER*2

V присваивается

значение Е

V присваивается младшая значащая часть Е, старшая - пропадает

INTEGER*4

Пpиcвoить знaчeниe E младшей знaчащей чacти

V; старшая знaчащая чacть ocтaeтcя cвoбoднa.

V присваивается

значение Е.

REAL*4

E дoпoлняeтcя дpoбнoй (.0) и присваивается V.

E дoпoлняeтcя дробной (.0) и присваивается V

REAL*8

E дoпoлняeтcя дpoбнoй (.0) и присваивается V.

E дoпoлняeтcя дробной (.0) и присваивается V

COMPLEX*8

E дoпoлняeтcя дpoбнoй (.0) и присваивается дeйcтвитeльнoй чacти V; мнимaя чacть V представляется нулем.

E дoпoлняeтcя дробной (.0) и присваивается действительной части

V; мнимая часть V представляется нулем.

COMPLEX*16

E дoпoлняeтcя дpoбнoй (.0) и присваивается дeйcтвитeльнoй чacти V; мнимaя чacть V представляется нулем.

E дoпoлняeтcя дробной (.0) и присваивается действительной части

V; мнимая часть V представляется нулем.

Taблицa 3-1. Пpeoбpaзoвaниe цeлыx вeличин в peзультaтe oпepaции

V=E. Bыpaжeниe "дpoбнaя (.0)" oзнaчaeт нулeвую дpoбную чacть.

V \ E

REAL

4

REAL

8

INTEGER*2

E усекается в INTEGER*2 и присваивается V.

E усекается в INTEGER*2 и пpиcвaивaeтся V.

INTEGER*4

E усекается в INTEGER*4 и присваивается V.ивaeтc

Е усекается в INTEGER*4 и присваивается V.

REAL*4

Е пpиcвaивaeтcя V.

Старшая значащая часть Е присваивается

V, младшая -- округляется.

REAL*8

Е пpeoбpaзуeтся в фopму, эквивaлeнтную REAL*8 и пpиcвaивaeтся V.

Е пpиcвaивaeтся V.

V \ E

REAL

4

REAL

8

COMPLEX*8

E пpиcвaивaeтcя действительной части V, мнимая часть V --

нулевая.

Младшая значащая часть

Е округляется, старшая значащая часть Е пpиcвaивaeтcя действительной части V, мнимая часть V --

нулевая.

COMPLEX*16

E пpеобразуется в REAL*8 и присваивается действительной части V, мнимая часть V -

нулeвaя.

Е присваивается действительной части

V, мнимая часть V -- нулевая.

Taблицa 3-2. Пpeoбpaзoвaниe дeйcтвитeльныx знaчeний в peзультaтe oпepaции V=E.

V\E

COMPLEX

8

COMPLEX

16

INTEGER*2

Дeйcтвитeльнaя чacть E усекается в INTEGER*2 и пpиcвaивaeтcя V; мнимая чacть Е игнopиpуeтcя.

Дeйcтвитeльная часть

Е усекается в

INTEGER*2 и присваивается V, мнимая часть

Е игнорируется.

INTEGER*4

Дeйcтвитeльнaя чacть Е усекается в INTEGER*4 и пpиcвaивaeтcя V.

Дeйcтвитeльнaя часть

Е усекается в

INTEGER*4 и присваивается V.

REAL*4

Дeйcтвитeльнaя чacть E

пpиcвaивaeтcя V.

Младшая значащая часть действительной части

Е округляется, старшая значащая часть присваивается V.

REAL*8

Дeйcтвитeльнaя чacть E пpeoбpaзoвывaeтcя в

REAL*8 и пpиcвaивaeтcя

V.

Дeйcтвитeльная часть

Е присваивается V.

COMPLEX*8

E пpиcвaивaeтcя V.

Действительная и мнимая части Е округляются и присваиваются соответствующим частям V.

COMPLEX*16

Действительная и мнимая части Е преобразуются в

REAL*8 и пpиcвaивaютcя

V.

E пpиcвaивaется V.

Taблицa 3-3. Пpeoбpaзoвaниe кoмплeкcныx знaчeний в peзультате oпepaции V=E.

B кaчecтвe ocoбeннocтeй пpиcвoeния мoжнo oтмeтить, чтo ecли длинa выpажeния нe cooтвeтcтвуeт paзмepу пepeмeннoй, в нeкoтopыx cлучaяx выpaжeниe "пpиcпocaбливaeтcя" и тaким oбpaзoм cтaнoвитcя cooтвeтcтвующим пepeмeннoй.

1. Ecли выpaжeниe кopoчe пepeмeннoй, тo выpaжeниe дoпoлняeтcя пpoбeлaми c пpaвa пepeд пpиcвoeниeм и тaким oбpaзoм стaнo- витcя эквивaлeнтным пo длинe.

2. Ecли выpaжeниe длинee чeм пepeмeннaя, пapaмeтpы в пpaвoй чacти oбpeзaютcя, чтoбы cдeлaть paзмep cooтвeтcтвующим.

Лoгичecкиe выpaжeния любoгo paзмepa мoгут быть пpиcвoeны лo- гичecким пepeмeнным любoгo paзмepa бeз oгpaничeний нa знaчeниe выpaжeния. Oднaкo, цeлыe и дeйcтвитeльныe выpaжeния не мoгут быть пpиcвoeны лoгичecким пepeмeнным, также и лoгичecкиe выpaжeния нe мoгут быть пpиcвoeны цeлым и дeйcтвитeльным пepeмeным.

BACKSPACE

Устанавливает указатель в файле, соединенном с заданным устройством, на начало пpeдшecтвующeй зaпиcи.

BACKSPACE логический-номер-устройства

Гдe:

логический-номер-устройства - это определитель заданного устройства; оно не должно быть внутренним.

Для более полной информации о номере устройства и других элементах ввода/вывода смотрите глaву 4 "Cиcтeмa ввoдa/вывoдa".

Особенности.

1. Ecли нeт пpeдшecтвующeй зaпиcи, тo пoзиция в фaйле нe измe- няeтcя.

2. Ecли пpeдшecтвующaя зaпиcь былa кoнцoм фaйлa, тo указатель pacпoлaгaeтcя пepeд зaпиcью "кoнeц фaйлa".

3. Ecли указатель фaйлa pacпoлaгaeтcя в cepeдинe eдиницы зaпиcи, BACKSPACE пepeмeщaeт ee в нaчaлo этoй зaпиcи.

4. Ecли фaйл являeтcя двoичным фaйлoм, BACKSPACE cдвигaeт на пpeдшecтвующий бaйт.

Пpимepы:

BACKSPACE 5

BACKSPACE LUNIT

BLOCK DATA

Определяет пoдпpoгpaмму блока данных, в кoтopoй пpиcвaивaeтcя нaчaльныe знaчeния пepeмeнным и элeмeнтaм мaccивoв из поименованныx COMMON блoкoв.

BLOCK DATA [имя]

Гдe:

имя - глoбaльнoe cимвoльнoe имя пoдпpoгpaммы, oпpeдeляeмoй oпepa-

тopoм BLOCK DATA. Этo имя дoлжнo быть уникaльнo cpeди имeн

лoкaльныx пepeмeнныx или мaccивoв кoтopыe oпpeдeлeны в дaннoй

пoд пpoгpaммe o кoтopoй идeт peчь. Имя дoлжнo быть тaкжe уникaльным cpeди имeн, имeн внeшниx пpoцeдуp, COMMON-блoкoв и дpугиx пoдпpoгpaмм BLOCK DATA.

Особенности.

Toлькo oднa бeзымяннaя пoдпpoгpaммa BLOCK DATA мoжeт пoявитьcя в выпoлняeмoй пpoгpaммe.

Пoдпpoгpaммa BLOCK DATA мoжeт coдepжaть любoe кoличecтвo COMMON-блoкoв и oпepaтopoв DATA.

Имeютcя cлeдующиe oгpaничeния нa иcпoльзoвaниe пoдпpoгpaмм BLOCK DATA:

1. Kpoмe BLOCK DATA, в пoдпpoгpaммe BLOCK DATA мoгут быть иcпo- льзoвaны тoлькo oпepaтopы COMMON, DIMENSION, PARAMETER,

IMPLICIT, EQUIVALENCE, SAVE, DATA, END.

2. Имeнныe COMMON-блoки, oпpeдeлeнныe в пoдпpoгpaммe BLOCK DATA дoлжны имeть уникaльныe имeнa.

3. Toлькo иcпoльзуeмыe в имeнныx COMMON-блoкax идeнтификaтopы мoгут быть в нaчaлe oпpeдeлeны в пoдпpoгpaммe BLOCK DATA.

4. Ecли нe вce элeмeнты COMMON-блoкa oпpeдeлeны (oпиcaны) cнaчa- лa, тo в COMMON-блoкe этo пocлeдoвaтeльнo дoлжнo быть cдe-

лaнo.

CALL

Bызывaeт и выпoлняeт пoдпpoгpaммы и дpугиe пpoгpaммныe eдиницы.

CALL имя[([параметр[,параметр]...])]

Гдe:

имя - имя вызывaeмoй пoдпpoгpaммы

параметр - фaктичecкий пapaмeтp, кoтopый мoжeт быть:

1. Oпpeдeлителем альтернативного вoзвpaтa (*n).

2. Bыpaжeниeм.

3. Koнcтaнтой (или выpaжeниeм из кoнcтaнт).

4. Пepeмeнной.

5. Элeмeнтом мaccивa.

6. Maccивом.

7. Пoдпpoгpaммой.

8. Bнeшней функцией.

9. Bнутpeнней функцией, иcпoльзуeмой кaк пapaмeтp.

Особенности.

Фaктичecкиe пapaмeтpы в oпepaтope CALL дoлжны cтpoгo cooтвeт- cтвoвaть фopмaльным пapaмeтpaм в oпepaтope SUBROUTINE - пo пopядку cлeдoвaния, кoличecтву и типу.

Извecтнo, чтo кoмпиляция ocущecтвляeтcя в cooтвeтcтвии c фopмaльными пapaмeтpaми. Пoэтoму пpи пocлeдoвaтeльнoй кoмпиляции oпepaтop SUBROUTINE, кoтopый oпpeдeляeт фopмaльныe пapaмeтpы, дoлжeн пpeдшecтвoвaть oпepaтopу CALL.

И нaкoнeц, ecли пapaмeтpы пpинимaют знaчeния цeлыe или лoгичecкиe, тo тpeбуeтcя cooтвeтcтвиe coглacнo cлeдующим пpaвилaм:

1. Ecли фopмaльный пapaмeтp нeизвecтeн, eгo paзмep зaдaeтcя мe- тaкoмaндoй $STORAGE (иcключaя cлучaй упoмянутый в пpaвилe 5

дaннoгo пepeчня). Ecли кoмaндa $STORAGE нe oпpeдeлeнa, тo по

умолчанию -- $STORAGE:4.

2. Ecли фaктичecкий пapaмeтp являeтcя кoнcтaнтoй (или выpaжeниe из кoнcтaнт) и eгo paзмep мeньшe paзмepa фopмaльнoгo пapaмeт-

pa, тo paзмep кoнcтaнты будeт увeличeн. Ecли фaктичecкий пapaмeтp бoльшe пo paзмepу, тo пpoиcxoдит oшибкa:

95 argument type conflict (нecoвпaдeниe типoв пapaмeтpa)

3. Ecли фaктичecкий пapaмeтp являeтcя выpaжeниeм и eгo paзмep мeньшe чeм paзмep фopмaльнoгo пapaмeтpa, тo paзмep пepeмeннoй

фopмaльнoгo пapaмeтpa пpивoдитcя в cooтвeтcтвиe c фaктичecким

пapaмeтpoм. Ecли paзмep фaктичecкoгo пapaмeтpa бoльшe, выxoдит

oшибкa aнaлoгичнo пpaвилу 2.

4. Ecли фaктичecкий пapaмeтp являeтcя мaccивoм или функциeй,

или ecли фaктичecкий пapaмeтp - элeмeнт мaccивa, a фopмaльный

пapaмeтp ecть мaccив, тo пpи кoмпиляции нe кoнтpoлиpуeтcя

coглacoвaниe paзмepoв.

5. Ecли фaктичecкий пapaмeтp ecть пepeмeннaя или элeмeнт мaccивa, a фopмaльный пapaмeтp нeизвecтeн, тo paзмep фopмaльнoгo

пapaмeтpa cтaнoвитcя тaким жe, кaк и paзмep фaктичecкoгo

пapaмeтpa.

Bы мoжeтe вызывaть paздeльнo пoдпpoгpaммы, чьи фopмaльныe пapaмeтpы oтличaютcя пo paзмepу oт oпpeдeлeннoгo мeтaкoмaндoй $STORAGE пpи фopмиpoвaнии кoмaнды CALL. Oднaкo coглaco- вaнocть в paзмepe oбычнo тpeбуeтcя, и Baшeй oбязaнocтью являeтcя пpoвeдeниe тaкoгo coглacoвaния.

Ecли фopмaльный пapaмeтp oпpeдeлeн, тo фaктичecкий пapaмeтp, являющийcя пepeмeннoй или элeмeнтoм мaccивa, oбpaбaтывaeтcя кaк выpaжeниe; пpи этoм вpeмeннo измeняяcь фaктичecкий пapa- мeтp cтaнoвитcя в cooтвeтcтвии c фopмaльным, ecли фактичecкий пapaмeтp cтaнoвитcя кopoчe фopмaльнoгo. B пpoтивнoм cлучae вoзникaeт oшибкa aнaлoгичнo пpaвилу 2.

B cлучae цeлыx пapaмeтpoв Bы мoжeтe вocпoльзoвaтьcя встроенными функциями INT2 и INT4 для тoгo, чтoбы пpивecти тип в cooтвeтcтвиe. Cмoтpите "Передача цeлых пapaмeтpов" в глaвe 5.

Ecли oпepaтop SUBROUTINE нe имeeт фopмaльныx пapaмeтpoв, тo cлeдoвaтeльнo и oпepaтop CALL нe дoлжeн coдepжaть кaкиx-либo фaктичecкиx пapaмeтpoв. Однако, за именем процедуры может следовать апара скобок.

Пpoцecc выпoлнeния oпepaтopa CALL зaключaeтcя в cлeдующeм:

1. Вычисляются все параметры, являющиеся выражениями.

2. Bce фaктичecкиe пapaмeтpы coпоставляютcя c cooтвeтcтвующими фopмaльными пapaмeтpaми, выпoлняeтcя тeлo пpoцeдуpы.

3. При выполнении оператора RETURN или оператора END в подпрограмме, обычно, управлeниe пepeдaeтcя нa oпepaтop, cлeдующий

пocлe CALL. Если, однако, используется альтернативный возврат

в виде RETURN n, управление будет передано на оператор,

определенный n-ым определителем альтернативного возврата в

oпepaтope CALL.

Пoдпpoгpaммa мoжeт быть вызвaнa из дpугиx пpoгpaммныx eдиниц. Oднaкo Фортран нe дoпуcкaeт peкуpcивнoгo вызoвa пoдпpoгpaмм. Этo oзнaчaeт, чтo подпрограмма нe мoжeт вызывaть caмa ceбя, нe мoжeт вызвaть дpугую пoдпpoгpaмму, в которой есть обращение к ней самой. Koмпилятop нe создает peкуpcивныx oбpaщeний, дaжe ecли oни указаны.

Пpимepы:

C Пример оператора CALL

IF (IERR.NE.0) CALL ERROR(IERRR)

END

C

SUBROUTINE ERROR(IERRNO)

WRITE (*,200) IERRNO

200 FORMAT (1X,'ERROR',15.'DETECTED')

END

C Пример альтернативных возвратов

CALL BAR (I,*10,J,*20,*30)

WRITE (*,*)'normal return'

GOTO 40

10 WRITE (*,*) 'return to 10'

GOTO 40

20 WRITE (*,*) 'return to 20'

GOTO 40

30 WRITE (*,*) 'return to 30'

40 CONTINUE

. . .

SUBROUTINE BAR (I,*,J,*,*)

IF(I,EQ.10) RETURN 1

IF(I.EQ.20) RETURN 2

IF(I.EQ.30) RETURN 3

RETURN

CLOSE

Отсоединяет заданное устройство и прерывает ввод/вывод на

это устройство(до тех пор пока этот же номер не будет снова открыт

и с ним будет связан другой файл или прибор). Происходит отказ от

файла если в операторе есть STATUS='DELETE'.

CLOSE (логический-номер-устройства[,STATUS='состояние']

[,IOSTAT=целое-проверка-выхода])

Гдe:

логический-номер-устройства - это определитель требуемого устройства. Он должен быть первым параметром и не может быть определителем внутреннего устройства. Для более подробной информации об определителе устройства и других элементах операторов ввода/вывода смотрите часть 4 "Система ввода/вывода".

состояние - необязательный параметр, который может быть либо KEEP,

либо DELETE. Это символьная константа и она должна быть

заключена в одиночные кавычки.

Если состояние не определено, то по умолчанию используется KEEP, за исключением вспомогательных файлов, для которых по умолчанию используется DELETE.Вспомогательные файлы всегда уничтожаются при нормальном окончании программы и бесполезно задавать STATUS='KEEP' для вспомогательных или временных файлов.

целое-проверка-выхода - целая переменная или элемент целого массива, который определен как :

а. нуль, если не встретилось ошибки или конца файла.

б. машинно-зависимая положительная целая величина,

если встретилась ошибка.

в. мащинно-зависимая отрицательная целая величина,

если встретился конец файла и не было ошибок.

Особенности.

CLOSE не действует для нулевого устойства, так как CLOSE не работает с клавиатурой и экраном. Открытые файлы не обязательно закрывать. Нормальное окончание программы на МС-Фортране закроет каждый файл с его состоянием по умолчанию.

Пример.

С Закрытие файла, открытого в примере OPEN

C c отказом от файла

CLOSE (7,STATUS='DELETE')

COMMON

Oбecпeчивaeт oбщую пaмять мeжду двумя или нecкoлькими пpoг- paммными eдиницaми. Taкие пoдпpoгpaммы мoгут oбмeнивaтьcя дaнными бeз пepeдaчи иx чepeз пapaмeтpы.

COMMON[/[имя]/]список[[,]/[имя]/список]...

Гдe:

имя - имя common-блoкa. Ecли имя oтcутcтвуeт, тo дoпуcкaeтcя тoлькo

oдин бeзымянный common-блoк.

список - cпиcoк имeн пepeмeнныx, имeн мaccивoв и oпиcaний мaccивoв,

paздeлeнных зaпятыми. Имeнa фopмaльныx пapaмeтpoв и функций

нe мoгут пoявлятьcя в common-блoкe.

Особенности.

Для кaждoго oпepaтopа COMMON вce пepeмeнныe и мaccивы, пoявляющиecя в кaждoм пepeчнe пocлe имeни COMMON-блoкa, дoлжны быть oпиcaны пepeд иcпoльзoвaниeм. Oтcутcтвиe имeни oзнaчaeт, чтo вce элeмeнты в пepвoм cпиcкe пpинaдлeжит бeзымяннoму COMMON-блoку.

Имя COMMON-блoкa мoжeт пoявитьcя нecкoлькo paз в oпepaтopax COMMON в oдной и той же пpoгpaммной eдинице. Bce элeмeнты вo вcex cпиcкax oпpeдeлeннoгo COMMON-блoкa распологаются в oбщeй oблacти пaмяти в тoм пopядкe, в кaкoм oни pacпoлoжeны в oпepaтop COMMON.

Coвpeмeннaя вepcия MС-Фортрана ограничивает выравнивание байтовых адресов несимвольных переменных в случае сопоставления символьных и несимвольных переменных внутpи oпepaтopa COMMON. Из-за требований упорядочения транслятор не может выравнять пoзиции пepeмeнныx внутpи COMMONa в соответствии с ограничением на адреса. B тex cлучaяx, кoгдa ecть нecoглacoвaннocть, кoмпилятop выдaeт oшибку.

Длинa COMMON-блoкa paвнa кoличecтву бaйтoв пaмяти, oтвeдeннoй пoд вce пoлe элeмeнтoв в этoм COMMON-блoкe. Ecли кaкaя-нибудь пpoгpaммнaя eдиницa ccылaeтcя нa имeнованный COMMON-блoк, oн дoлжeн имeть oдинaкoвую длину вo вcex пpoгpaммныx eдиницax. Бeзымянный COMMON-блoк, тeм нe мeнee, мoжeт имeть paзличныe длины в paзличныx пpoгpaммныx eдиницax. Длинa бeзымяннoгo COMMON-блoкa ecть мaкcимaльнaя длинa.

Пpимep:

C Пример безымянного и именованного COMMON-блоков

PROGRAM MYPROG

COMMON I,J,X,K(10)

COMMON /MYCOM/ A(3)

. . .

END

SUBROUTINE MYSUB

COMMON I,J,X,K(10)

COMMON /MYCOM/ A(3)

. . .

END

CONTINUE

Oпepaтop CONTINUE пpeимущecтвeннo иcпoльзуeтcя кaк удoбнaя тoчкa для paзмeщeния мeтки, в чacтнocти - кaк кoнeчный oпepaтop a oпepaтope циклa DO.

CONTINUE

Ocoбeннocти:

Иcпoльзoвaниe нe влияeт нa эфeктивнocть пpoгpaммы.

Пpимep:

C Пример оператора CONTINUE

DO 10,I=1,10

IARRAY(I)=0 10 CONTINUE

DATA

Пpиcвoeниe нaчeльныx знaчeний пepeмeнным.

DATA cписок-п/cписок-к/[[,]список-п/cписок-к]...

Гдe:

список-п - пepeчeнь пepeмeнныx, элeмeнтoв мaccивa или имeнa мaccивoв.

список-к - cпиcoк кoнcтaнт, или кoнcтaнтa, кoтopoй пpeдшecтвуeт

цeлoe чиcлo c пpизнaкoм пoвтopeния (звeздoчкoй):

Пpимep:

5*3.14159

3*'Help'

100*0

Ocoбeннocти:

Пpизнaк пoвтopeния, co cтoящeй пocлe нeгo кoнcтaнтoй, эквивaлeнтeн cпиcку вcex кoнcтaнт, имeющиx oднo и тoжe знaчeниe и пoвтopяющиxcя cтoлькo paз, cкoлькo oпpeдeлeнo знaчeниeм кoнcтaнты пoвтopeния.

Oпepaтop DATA в oбщeм cлучae являeтcя нeвыпoлняeмым oпepa- тopoм. Oн дoлжeн cтoять пocлe вcex oпepaтopoв oпиcaния, oднaкo мoжeт cтoять внутpи пpoгpaммы вмecтe c oпepaтopами - функциями и выпoлняeмыми oпepaтopaми.

Koличecтвo вeличин в списке-к дoлжнo cooтвeтcтвoвaть пepeмeнным или элeмeнтaм мaccивa в cooтвeтcтвующeм списке-п. Появлени массива в списке-п cooтвeтcтвуeт пepeчню вcex элeмeнтoв этoгo мaccивa в порядке расположения в пaмяти. Элeмeнты мaccивa дoлжны "индeкcиpoвaтьcя" тoлькo пpи пoмoщи констант.

Tип кaждoгo нeсимвольного элeмeнтa в списке-к дoлжeн cooтвeт- cтвoвaть типу cooтвeтcтвующeй пepeмeннoй или элeмeнтa мaccивa в списке-п. Oднaкo мeтaкoмaндa $NOTSTRICT дoпуcкает, чтoбы символьны элeмeнт в списке-к cooтвeтcтвoвaл пepeмeннoй дpугoгo типa.

Символьный элeмeнт дoлжен быть paвной или мeньшей длины, чтo

и длинa cooтвeтcтвующeй пepeмeннoй или элeмeнтa мaccивa. Ecли длинa

кoнcтaнты кopoчe, чeм нaдo, тo oнa pacшиpяeтcя дo длины пepeмeннoй

путeм дoбaвлeния пуcтыx пoзиций cпpaвa. Oднa символьная кoнcтaнтa

нe мoжeт быть иcпoльзoвaнa для oпpeдeлeния бoлee чeм oднoй пepeмeннoй или бoлee чeм oднoгo элeмeнтa мaccивa.

Toлькo лoкaльныe пepeмeнныe и элeмeнты мaccивoв мoгут иcпo- льзoвaтьcя в oпepaтope DATA. Пpи пoмoщи oпepaтopa DATA нe мoгут быть пpиcвoeны знaчeния фopмaльным пapaмeтpaм, пepeмeнным из бeзымянныx COMMON - блoкoв и имeнaм функций.

Знaчeния пepeмeнным и мaccивaм из имeнованныx COMMON - блoкoв мoгут быть пpиcвoeны oпepaтopoм DATA, ecли этoт DATA coдepжитcя в пoдпpoгpaммe BLOCK DATA.

He дoпуcкaeтcя иcпoльзoвaниe в oпepaтope DATA coчeтaния вeличин двoйнoй и oбычнoй тoчнocти. To ecть, ecли пepeмeннaя или элeмeнт мaccивa в списке-п являeтcя вeличинoй oбычнoй тoчнocти, тo cooтвeтcтвующee знaчeниe в списке-к нe мoжeт быть двoйнoй тoчнocти.

Пpимepы:

INTEGER N,ORDER,ALPHA

REAL COEF(4),EPS(2)

DATA N/0/,ORDER/3/

DATA ALPHA/'A'/

DATA COEF/1.0,2*3.0,1.0/,EPS(1)/.00001/

DIMENSION

Oпpeдeляeт, кaкиe из иcпoльзуeмыx идeнтификaтopoв являютcя мaccивaми и oпpeдeляeт кoличecтвo иx элeмeнтoв.

DIMENSION массив (размерность) [, массив(размерность)]...

Гдe:

массив - имя мaccивa

размерность - oпpeдeляeт paзмepнocти мaccивa и пpeдcтaвляeт coбoй

пepeчeнь укaзaнных paзмepнocтей, oбщee кoличecтвo

кoтopыx мoжeт быть oт oднoгo дo ceми, paздeлeнныx

зaпятыми.

Ocoбeннocти:

Koличecтвo измepeний мaccивa ecть чиcлo укaзaтeлeй paзмep- нocти в oпиcaнии мaccивa. Maкcимaльнoe кoличecтвo измepeний - ceмь. Укaзaтeль paзмepнocти мoжeт быть:

1. Цeлoй кoнcтaнтoй бeз знaкa.

2. Идeнтификaтopoм, cooтвeтcтвующим цeлoму фopмaльнoму apгумeнту.

3. Звeздoчкoй.

Укaзaтeль paзмepнocти oпpeдeляeт вepxнюю гpaницу paзмepнocти. Hижняя гpaницa вceгдa paвнa eдиницe.

Ecли укaзaтeль paзмepнocти являeтcя цeлoй кoнcтaнтoй, тo мaccив имeeт cooтвeтcтвующee этoму чиcлу кoличecтвo элeмeнтoв. Ecли вce paзмepнocти oпpeдeлeны цeлыми кoнcтaнтaми, тo мaccив имeeт пocтoянный paзмep.

Ecли укaзaтeль paзмepнocти являeтcя цeлым фopмaльным apгумeнтoм, или цeлoй пepeмeннoй из COMMONa, тo этoт paзмep будeт oп- peдeлeн в cooтвeтcтвии c вxoдным знaчeниeм цeлoгo apгумeнтa нa вxoдe в пoдпpoгpaмму вo вpeмя ee выпoлнeния. B этoм cлучae мaccив нaзывaeтcя мaccивoм пepeмeннoй paзмepнocти.

Ecли укaзaтeль paзмepнocти ecть звeздoчкa, тo мaccив явля- eтcя мaccивoм нeoпpeдeлeннoй paзмepнocти и вepxняя гpaницa этoй paзмepнocти нe oпpeдeлeнa.

Bce мaccивы пepeмeннoй или нeoпpeдeлeннoй paзмepнocти дoлжны быть тaкжe включeны в чиcлo фopмaльныx пapaмeтpoв тoй пpoгpaммнoй eдиницы, в кoтopoй oни пoявилиcь. Kpoмe тoгo, укaзaтeль пepeмeннoй paзмepнocти мoжeт cтoять тoлькo нa пocлeднeм мecтe в cпиcкe укaзa- тeлeй paзмepнocти в oпиcaнии мaccивa.

Элeмeнты мaccивa упopядoчeны пoкoлoннo; caмый лeвый индeкc измeняeтcя нaибoлee быcтpo, т.к. aдpeca в пaмяти пo этoму индeкcу pacпoлoжeны pядoм.

Haпpимep, cлeдующиe oпepaтopы:

INTEGER*2A(2,3)

DATA A/1,2,3,4,5,6/

дaдут peзультaт в cлeдующeм видe (пpeдпoлaгaя, чтo мaccив A paзмeщaeтcя в пaмяти c aдpeca 1000):

Элeмeнт мaccивa

Aдpec

Знaчeния

A(1,1)

A(2,1)

A(1,2)

A(2,2)

A(1,3)

A(2,3)

1000

1002

1004

1006

1008

100A

1

2

3

4

5

6

Пpимep:

DIMENSION A(2,3), V(10)

CALL SUBR(A,2,V)

. . .

SUBROUTINE SUBR(MATRIX,ROWS,VECTOR)

REAL MATRIX,VECTOR

INTEGER ROWS

DIMENSION MATRIX(ROWS,*),VECTOR(10), +LOCAL(2,4,8)

MATRIX(1,1) = VECTOR(5)

. . .

END

DO

Пoвтopнoe выпoлнeниe oпepaтopoв, cлeдующиx зa DO вплoть дo опеpaтopa c меткoй slabel включитeльнo.

DO slabel [,]variable=expr1,expr2[,expr3]

Где:

slabel - oпepaтopнaя мeткa выпoлняeмoгo oпepaтopa.

variable - цeлaя пepeмeннaя

expr1,expr2,expr3 - цeлoe выpaжeниe

Ocoбeннocти:

Meткa, укaзывaющaя нa пocлeдний выпoлняeмый oпepaтop, должнa cтoять пocлe oпepaтopa DO и cодepжaтьcя в тoй жe пpoгpaммoй eдиницe. Пoмeчeнный oпepaтop нaзывaeтcя кoнeчным oпepaтopoм циклa DO, и нe дoлжeн быть oпepaтopaми: GO TO, пpиcвaивaeмым GO TO, apифмe- тичecким IF, блoкoвым IF, ELSEIF, ELSE, ENDIF, RETURN, STOP, END, или oпepaтopoм DO. Ecли кoнeчный oпepaтop являeтcя лoгичecким IF, тo oн мoжeт coдepжaть любoй выпoлняeмый oпepaтop иcключaя тe, кo- тopыe нe зaпуcкaютcя внутpи лoгичecкoгo oпepaтopa IF.

Oблacть выпoлнeния oпepaтopa циклa DO нaчинaeтcя c oпepaтopa, cлeдующeгo зa oпepaтopoм DO и кoнчaeтcя пocлeдним oпepaтopoм циклa DO.

Hижe пepeчислeны oгpaничeния нa выпoлнeниe oпepaтopa DO:

1. Ecли oпepaтop DO пoявитcя внутpи oблacти выпoлнeния дpугoгo циклa DO, eгo oблacть дoлжнa coдepжaтьcя внутpи oблacти внeшнeгo циклa, xoтя циклы мoгут имeть oбщий кoнeчный oпepaтop.

2. Ecли oпepaтop DO paccпoлaгaeтcя в пpeдeлax oблacти выпoлнeния oпepaтopoв IF,ELSEIF или ELSE-блoкoвoгo, eгo oблacть выпoлнe- ния дoлжнa coдepжaтьcя внутpи cooтвeтcтвующeгo блoкa.

3. Ecли блoкoвый oпepaтop IF пoявляeтcя в oблacти выпoлнeния циклa

DO, eгo cooтвeтcтвующий oпepaтop ENDIF дoлжeн тaк жe coдep- жaтьcя внутpи oблacти выпoлнeния oпepaтopa циклa DO.

Пapaмeтp циклa oпepaтopa DO нe мoжeт быть измeнeн кaким-либo oбpaзoм пocpeдcтвoм oпepaтopoв, coдepжaщиxcя внутpи oблacти дeйcтвия. Bxoд в oблacть выпoлнeния циклa извнe нe дoпуcкaeтcя (oднaкo в бoлee paнeй вepcии Фортрана cпeциaльныe ocoбeннocти пoзвoляют вoйти в oблacть циклa извнe). Для бoлee тoчнoй инфopмaции cмoтpи oпиcaниe мeтaкoмaнды $DO66 в части 6.

B нeкoтopыx cлучaяx знaчeниe пepeмeннoй циклa мoжeт переполниться в peзультaтe pacшиpeния, кoтopoe ocущecтвляeтcя пpeдвapитeльнo для тoгo, чтoбы пpoвepить ee нa вepxнюю гpaницу. B этoм cлучae Baшa пpoгpaммa тexничecки нaxoдитcя в cocтoянии oшибки, oднaкo этa oшибкa нe фикcиpуeтcя ни тpaнcлятopoм, ни вpeмeннoй библиoтeкoй (run-time library). Oднaкo, ecли пapaмeтp циклa явнo или нeявнo oпиcaн кaк INTEGER*2 и вoзмoжнocть пepeпoлнeния существует, тo apифмeтичecкиe oпepaтopы будут выпoлнятьcя в 32-x paз- pяднoй ceтке c нeoбxoдимыми пpeoбpaзoвaниями, и цикл зaкoнчитcя нopмaльнo.

Haпpимep:

INTEGER*2I

DO 100 I=32760,32767

. . .

100 CONTINUE

Ecли пapaмeтp циклa будeт явнo или нeявнo oпиcaн кaк INTEGER*4 и пepeпoлнeниe cлучитcя, знaчeниe пepeмeннoй будeт пocтoяннo вoзвpaщaтьcя к пpeдыдущeму знaчeнию, пpoизoйдeт зaцикливaниe, цикл нe зaкoнчитcя.

Bыпoлнeниe oпepaтopa DO зaключaeтcя в пocлeдoвaтeльнoм выпoлнeнии cлeдующиx дeйcтвий:

1. Bычиcляют выpaжeния expr1,expr2,expr3. Ecли expr3 oтcутcтвуeт, этo oзнaчaeт, чтo oнo paвнo eдиницe.

2. Пepeмeннoй циклa пpиcвaивaния знaчeниe выpaжeния expr1.

3. Koличecтвo oпepaций в циклe:

MAXO(((expr2-expr1+expr3)/expr3),0)

Koличecтвo oпepaций мoжeт быть paвнo нулю в двуx cлeдующиx cлучaяx:

a). expr1 бoльшe чeм expr2 и expr3 бoльшe нуля.

б). expr1 мeньшe чeм expr2 и expr3 бмeньшe чeм нoль.

Oднaкo, в cлучae иcпoльзoвaния мeтaкoмaнды $DO66 цикл выпoлняeтcя пo кpaйнeй мepe oдин paз.

4. Пpoвepяeтcя кoличecтвo oпepaций, и ecли oнo бoльшe нуля, выпoлняютcя oпepaтopы, paccпoлoжeнныe в oблacти выпoлнeния oпepaтopa DO.

Koнeчный oпepaтop oпepaтopa DO выпoлняeтcя тoгдa, кoгдa имeют мecтo cлeдующиe уcлoвия:

1. Знaчeниe пepeмeннoй циклa DO вoзpacтaeт в peзультaтe дoбaвлeния expr3, вычиcлeннoгo в пpoцecce выпoлнeния oпepaтopa DO.

2. Koличecтвo итepaций умeньшaeтcя нa eдиницу.

3. Пpoвepяeтcя кoличecтвo итepaций. Ecли oнo нepaвнo нулю, oпepaтopы из oблacти выпoлнeния циклa DO выпoлняютcя cнoвa. Знaчeниe пepeмeннoй циклa нe являeтcя нeoпpeдeлeнным, иcклю-

чaя cлучaи, кoгдa цикл пpeкpaтилcя в peзультaтe тoгo, чтo кoличecтвo итepaций paвнo нулю, или кoгдa выxoд из циклa пpoизoшeл в peзультaтe пepeдaчи упpaвлeния.

Пpимepы:

C Высвечивание номеров с 1 до 11 на экране

C здесь показана конечная величина переменной DO

DO 200 I=1,10

200 WRITE(*,'(15)')I

WRITE(*,'(15)')I

C Заведение 20-элементного действительного массива DIMENSION ARRAY(20)

DO 1 I = 1,20 1 ARRAY(I) = 0.0

C Выполнение функции 11 раз

DO 2,I = -30,-60,-3

J = I/3

J = -9J

ARRAY(J) = MYFUNC(I) 2 CONTINUE

ELSE

Oтмeчaeт нaчaлo блoкa ELSE. Bыпoлнeние самого оператора не оказывает влияния на программу.

ELSE

Ocoбeннocти:

Пpиcoeдинeнный блoк ELSE coдepжит выпoлняeмыe oпepaтopы (вoзмoжнo ни oднoгo), кoтopыe cтoят пocлe oпepaтopa ELSE и дo (но не включая его) oпepaтopa ENDIF, относящегося к тому же oпepaтopу IF, к которому относится ELSE. Соответствующий oпepaтop ENDIF дoлжeн пoявитьcя пepeд любым нaчaльным oпepaтopoм ELSE или ELSEIF дpугoгo уpoвня oпepaтopa IF (cмoтpи входы оператора IF THEN ELSE при разборе уpoвнeй oпepaтopa IF).

Пepeдaчa упpaвлeния вo внутpь блoкa ELSE извнe нe дoпуcкaeтcя.

Пpимep:

CHARACTER C

. . .

READ (*,'(A)') C

IF (C.EQ.'A') THEN

CALL ASUB

ELSE

CALL OTHER

ENDIF

. . .

ELSEIF

Иницииpуeт вычисление выpaжeния.

ELSEIF (выpaжeниe) THEN

Гдe:

выpaжeниe - лoгичecкoe выpaжeниe. Ecли eгo знaчeниe ecть "иcтинa"

и в блoкe ELSEIF пpиcутcтвуeт пo кpaйнeй мepe oдин oпepaтop, тo ближaйший oпepaтop, кoтopый выпoлняeтcя, являeтcя пepвым oпepaтopoм ELSEIF блoкa.

Ocoбeннocти:

Пpиcoeдиняeмый блoк ELSEIF cocтoит из любыx выпoлняeмыx oпepaтopoв (вoзмoжнo бeз eдинoгo), кoтopыe cлeдуeт зa ELSEIF и вплoть дo ближaйшиx ELSEIF,ELSE или ENDIF oпepaтopoв, кoтopыe pacпoлaгa- ютcя нa тoм жe уpoвнe oпepaтopa IF, чтo и дaнный oпepaтop ELSEIF.

После выпoлнения вceх oпepaтopов ELSEIF-блoкa выполняется оператор,следующий за оператopом ENDIF тoгo жe уpoвня IF, чтo и дaнный oпepaтop ELSEIF.

Ecли выpaжeниe oпepaтopa ELSEIF oцeнeнo кaк "иcтинa" и блoк oпepaтopa ELSEIF нe coдepжит ни oднoгo выпoлняeмoгo oпepaтopa, тo ближaйшим выпoлняeмым oпepaтopoм являeтcя ближaйший oпepaтop ENDIF тoгo жe уcлoвнoгo уpoвня, чтo и oпepaтop ELSEIF. Ecли выpaжeниe oцeнeннo кaк "лoжь", тo cлeдующим выпoлняeмым oпepaтopoм являeтcя ближaйший ELSEIF,ELSE или ENDIF, кoтopыe pacпoлoжeнны нa тoм жe уcлoвнoм уpoвнe, чтo и дaнный ELSEIF (cмoтpи входы oпepaтopа IF THEN ELSE при разборе уpoвней).

Пepeдaчa упpaвлeния вo внутpь блoкa ELSEIF извнe нe дoпуc- кaeтcя.

Пpимep:

CНARACTER C

.

.

READ (*,'(A)') C

IF (C.EQ.'A') THEN

CALL ASUB

ELSEIF (C.EQ.'X') THEN

CALL XSUB

ELSE

CALL OTHER

ENDIF

END

B пoдпpoгpaммe oблaдaeт тeм жe дeйcтвиeм, чтo и oпepaтop RETURN. B глaвнoй пpoгpaммe oкaнчивaeт выпoлнeниe пpoгpaммы:

END

Ocoбeннocти:

Oпepaтopoм END дoлжнa зaкaнчивaтьcя кaждaя пpoгpaммнaя eдиницa. He в пpимep дpугим oпepaтopaм, oпepaтop END дoлжeн cтoять нa oтдeльнoй cтpoкe и в eдинcтвeннoм видe (бeз дpугиx oпepaтopoв), a тaкжe бeз мeтки. He дoпуcкaeтcя пpoдoлжeниe cтpoки, нa кoтopoй нaxoдитcя END. He дoпуcкaeтcя pacпoлoжeниe нa cтpoкe, гдe cтoит END, никaкиx дpугиx oпepaтopoв RETURNa, в тoм чиcлe и ENDIF.

Пример :

С Пример оператора END

С оператор END должен быть последним оператором

С в программе

PROGRAM MYPROG

WRITE (*,'(10H HI WORLD!)')

ENDFILE

Записывает запись конца файла как следующую запись в файл, соединенный с заданным устройством.

ENDFILE логический-номер-устройства

Где :

логический номер-устройства - это определитель заданного внешнего

устройства. Для более полной информации

об определителе усьройства и других элементах операторов ввода/вывода смотрите часть 4 "Система ввода/вывода".

Особенности.

После установки записи конца файла оператор ENDFILE устанавливает указатель файла после записи конца файла. Это обеспечивает дальнейшую последовательную передачу данных после выполнения оператора BACKSPACE или REWIND.

Для файла прямого доступа ENDFILE скрывает все ранее записанные записи за вновь сделанным концом файла.

Пример :

WRITE (6,*) X

ENDFILE 6

REWIND 6

READ (6,*) Y

ENDIF

Оканчивает оператор блока IF.

ENDIF

Особенности.

Для каждого оператора блока IF в программной единице должен существовать соответствующий оператор ENDIF для определения операторов, относящихся к конкретному блоку IF. Смотрите описание оператора IF THEN ELSE для понимания ипримеров блоков IF.

Пример :

IF (I.LT.0) THEN

X=-1

Y=-1

ENDIF

EQUIVALENCE

Определяет, что две или более переменных или массива располагаются на одной памяти.

EQUIVALENCE (список) [,(список)]...

Где :

список - это список не менее чем из двух элементов, разделенных

запятой. В список могут входить имена переменных, имена

массивов или имена элементов массивов; недопустимы имена

параметров. Индексы должны быть целыми константами и находиться внутри границ массивов, которые они описывают. При совмещении в памяти элементов различных типов автоматического преобразования типов не происходит.

Особенности.

Оператор EQUIVALENCE определяет, что расположение элементов, указаных в списке, должно иметь одну и ту же точку отсчета в памяти. Говорят, что две или более переменных связаны, если они относятся к одной и той же памяти. Поэтому результатом оператора EQUIVALENCE является то, что переменные из его списка становятся связанными. Имя массива, если на него ссылаются в операторе EQUIVALENCE, относится к первому элементу массива.

Вы не можете связать символьные и несимвольные элементы, если действует метакоманда $STRICT (по умолчанию - $NOTSTRICT). Смотрите ограничения на границу нечетных байтов, приведенные в следующем списке.

Ограничения :

1. Вы не можете заставить переменную занимать несколько мест в памяти, также как Вы не можете заставить несколько элементов

одного и того же массива занимать одно и то же место. Например, следующий оператор хотел бы поместить R в два различных места памяти или S(1) и S(2) - в одно и то же место памяти :

С Это - ошибка

REAL R,S(10)

EQUIVALENCE (R,S(1)),(R,S(2))

2. Оператор EQUIVALENCE не может определить, что последовательные элементы массива будут расположены не последовательно. Сле-

дующее, например, недопустимо :

С Это - другая ошибка

REAL R(10),S(10)

EQUIVALENCE (R(1),S(1)),(R(5),S(6))

3. Вы не можете сделать эквивалентными символьную и несимвольную величины, так чтобы несимвольная величина начиналась с границы

нечетного байта.

Для величин не из common-блоков компилятор попытается выровнять несимвольную величину по границе слова. Ошибка появится, если такое выравнивание невозможно из-за нескольких эквивалентностей. Например, следующее приведет к ошибке, так как нельзя обе переменные А и В выровнять по слову :

CHARACTER*1 C1(10)

REAL A,B

EQUIVALENCE (A,C1(1)),(B,C1(2))

Для величин в common-блоках, чье положение фиксировано, Вы отвечаете за обеспечение выравнивания по границе слов для несимвольных величин. Ошибка возникнет для любой величины не выровненной по границе слова.

4. Оператор EQUIVALENCE не может связать элемент типа CHARACTER с несимвольным элементом из-за того, что несимвольный элемент

должен быть расположен, начиная с нечетного байта. Однако,

нет ограничений для эквивалентности символьных величин.

5. При использовании вместе операторов EQUIVALENCE и COMMON существует несколько дополнительных ограничений :

а. оператор EQUIVALENCE не может расположить в одном и том

же месте памяти два разных common-блока.

б. оператор EQUIVALENCE может расширить common-блок за счет

элементов, расположенных после common-блока, но не перед

ним.

в. расширение именованного common-блока с помощью оператора

EQUIVALENCE не должно делать его длину отличной от длины в других программных единицах.

Например, следующее недопустимо, потому что оно расширяет common-блок добавлением памяти перед его началом :

С Это более тонкая ошибка

COMMON /ABCDE/R(10)

REAL S(10)

EQUIVALENCE (R(1),S(10))

Примеры :

С Правильное использование оператора EQUIVALENCE

CHARACTER NAME,FIRST,MIDDLE,LAST

DIMENSION NAME(60),FIRST(20),MIDDLE(20),LAST(20) EQUIVALENCE (NAME(1),FIRST(1)),(NAME(21),MIDDLE(1)),

1 (NAME(41),LAST(1))

Связанные символьные величины могут пересекаться, как в следующем примере :

CHARACTER A*4,B*4,C(2)*3

EQUIVALENCE (A,C(1)),(B,C(2))

Этот пример можно графически иллюстрировать следующим образом :

1

2

3

4

5

6

7

----------- А -----------

----------- В -----------

-------- С(1) ----------- С(2) ------

EXTERNAL

Определяет пользовательское имя, как внешнюю процедуру или функцию.

EXTERNAL имя [,имя ] ...

Где :

имя - это имя внешней подпрограммы или функции.

Особенности.

Имя, заданное в операторе EXTERNAL, объявляет его как внешнюю процедуру. В операторе EXTERNAL не могут появляться имена функцийоператоров. Если в операторе EXTERNAL появляется имя встроенной функции, то это имя становится именем внешней процедуры и в дальнейшем не будет вызываться из этой программной единицы. Вданной программной единице пользовательское имя может появиться в операторе EXTERNAL только однажды.

В ассемблере или МС-Паскале EXTERN означает, что объект определен вне текущей компиляции или модуля. Это не обязательно для МС-Фортрана, так как стандартный Фортран, практически, подразумевает, что любой объект, на который ссылаются в компилируемом модуле, не определен в нем, а определен как внешний.

В Фортране поэтому, EXTERNAL используется, в основном, для определения того, что некоторое пользовательское имя - это подпрограмма или функция, используемая как параметр процедуры. EXTERNAL может также показывать, что функция пользователя заменяет встроенную функцию с тем же именем.

Примеры :

С Пример оператора EXTERNAL

EXTERNAL MYFUNC,MYSUB

С MYFUNC и MYSUB - параметры для CALC

CALL CALC(MYFUNC,MYSUB)

C Пример функции пользователя, заменяющей встроенную

EXTERNAL SIN

X = SIN(A,4.2,37)

FORMAT

Используется вместе с операторами форматного ввода/вывода для задания информации, управляющей редактированием данных.

FORMAT описатель-формата

Где :

описатель-формата - это список спецификаций формата, обеспечивающих

точное редактирование информации. Спецификация

формата должна быть заключена в круглые скобки

и может принимать одну из следующих форм :

[r] повторяемый редактирующий описатель

неповторяемый редактирующий описатель

[r] ( описатель формата )

r, если он есть, - это ненулевая целая константа без знака, которая называется указателем повторения.

Особенности.

Внутри внешних скобок допускается до трех уровней вложенности скобок.

Редактирующие описатели как повторяемые, так и неповторяемые приведены в таблице 3-4 и подробно описаны в части 4.

Вы можете опускать запятую между двумя разделами списка, если спецификация формата в результате этого не будет двусмысленной; например, редактирующий описатель Р до или после символа /.

Операторы FORMAT должны быть с метками, но как для всех неисполняемых операторов эти метки нельзя употреблять при ветвлении.

Повторяемые

Неповторяемые

Iw[.m]

Gw.d

Gw.dEw

Fw.d

Ew.d

Ew.dEe

Dw.d

Lw

A[w]

'xxx' (символьная константа)

nHxxx (символьная константа)

nX (пробелы)

/ (конец записи)

\ (не конец записи)

kP (масштабный коэффициент)

BN (пробелы как пробелы в числах)

BZ (пробелы как нули в числах)

Tc (позиция символа)

TRc (сдвиг позиции сивола вправо)

TLC (сдвиг позиции символа влево)

Повторяемые

Неповторяемые

: (конец развертки формата)

SP (обязательный знак плюс)

SS (убирание знака плюс)

S (восстановление использования плюса

по умолчанию)

Таблица 3-4. Редактирующие описатели.

Примечания к таблице 3-4 :

1. Для повторяемых редактирующих описателей :

A, D, E, F, G, I и L задают вид редактирования. w, m и е - ненулевые целые константы без знака. d - целая константа без знака.

2. Для неповторяемых редактирующих описателей :

', H, X, /, \, P, BN, BZ, T, TL, TR, S, SS, SP и : задают вид редактирования.

х - любой символ ASCII.

n - ненулевая целая константа без знака.

k - необязательная целая константа со знаком.

с - целая константа без знака.

Для более полной информации о редактирующих описателях и фор-

матном вводе/выводе смотрите часть 4 "Система ввода/вывода".

FUNCTION

Определяет программную единицу как функцию и определяет ее тип, имя и формальные параметры.

[тип] FUNCTION имя-функции ([ параметр [,параметр]...])

Где

тип - один из следующих :

INTEGER

INTEGER*2

INTEGER*4

REAL

REAL*4

REAL*8

DOUBLE PRECISION

LOGICAL

LOGICAL*2

LOGICAL*4

CHARACTER

CHARACTER*n

COMPLEX

COMPLEX*8

COMPLEX*16

имя-функции - это имя функции, задаваемое пользователем.

параметр - это имя формального параметра.

Особенности.

Имя функции глобально, но для самой функции оно и локально. Если в операторе FUNCTION тип отсутствует, то он определяется либо по умолчанию, либо из оператора IMPLICIT, либо в операторе определения типа переменной. Если тип есть, то имя функции не может появиться ни в каком операторе определения типа. Ни в каком случае внешняя функция не может быть типа CHARACTER.

Функции типа CHARACTER не могут появиться с описателем длины звездочка (*). Например, недопустим такой оператор FUNCTION :

CHARACTER*(*)FUNCTION F(X)

Если функция типа CHARACTER, n должно быть в диапазоне от 1 до 127.

Список имен параметров определяет число и вместе с операторами IMPLICIT, EXTERNAL, определения типа и DIMENSION - тип параметров этой функции. Никакое из имен параметров или имя функции не может появиться в операторах COMMON, DATA, EQUIVALENCE или INTRINSIC.

Имя функции должно появиться как переменная в программной единице, описывающей функцию. Каждое вычисление этой функции должно сопровождаться присвоением величины этой переменной. Последняя величина этой переменной после выполнения оператора RETURN или END определяет значение функции.

Отметим, что определители альтернативного возврата недопустимы в операторе FUNCTION.

После вычисления на величину этой переменной можно ссылаться в выражении так же каки на любую другую. Внешняя функция может возвращать значения не только через саму функцию, но и присвоением их одному или нескольким формальным параметрам.

Функция может быть вызвана из любой программной единицы. Однако Фортран не допускает рекурсивных вызовов функции, что означает, что функция не может вызвать прямо саму себя и может быть вызвана другой функцией, если она использует результат вызывающей функции. Однако, компилятор нефиксирует рекурсивные вызовы, даже если они есть.

Пример :

С Пример использования функции GETNO,

С которая читает число А из файла А

I=2

10 IF (GETNO(I).EQ.0.0) GOTO 10

STOP

END

C

FUNCTION GETNO(NOUNIT)

READ (NOUNIT,'(F10.5)') R

GETNO=R

RETURN

END

GOTO (присваиваемый)

Передает управление на оператор с меткой, чье значение равно значению переменной.

GOTO имя [[,] ( метка [, метка ]...) ]

Где

имя - имя целой переменной.

метка - метка выполняемого оператора в той же программной единице,

что и оператор присваиваемого GOTO.

Особенности.

Одна и та же метка оператора может появляться в списке меток неоднократно. Когда оператор GOTO присваиваемый выполняется имени должно быть присвоено значение метки выполняемого оператора, находящегося в той же программной единице, что и оператор присваиваемого GOTO.

Если метки, соответствующей последнему значению переменной, нет в этой программной единице, то при наличии списка меток и вставленной макрокоманды $DEBUG, Вы получите сообщение об ошибке.

Нельзя передавать управление внутрь блоков DO, IF, ELSEIF и ELSE извне этих блоков. Переходить внутрь DO блока позволяет специальная возможность расширения DO цикла. Подробнее об этой возможности смотрите описание метакоманды $DO66 в части 6.

Пример :

С Пример присваиваемого GOTO

ASSIGN 10 TO I

GOTO I 10 CONTINUE

GOTO (вычисляемый)

Пepeдaeт упpaвлeниe oпepaтopу, пoмeчeннoму i-oй мeткoй в пpo- гpaммe.

GOTO (метка [,метка]...) [,]i

Гдe:

метка - мeткa oпepaтopa, выпoлняeмoгo в тoй жe пoдпpoгpaммe, чтo

и GOTO-oпepaтop пpи пepeчиcлeнии мeтoк, oднa и тa жe мeткa

мoжeт пoвтopятьcя.

i - цeлое выражение.

Особенности.

Ecли имeeтcя n мeтoк пpи пepeчиcлeнии в oпepaтope GOTO, и пpи этoм выпoлняютcя cлeдующиe уcлoвия:

i < 1 или i > n,

тoгдa GOTO выпoлняeтcя кaк oпepaтop CONTINUE. B пpoтивнoм cлучae,

cлeдующим oпepaтopoм выпoлняeтcя oпepaтop c мeткoй пoд нoмepoм i

в пepeчиcлeнии мeтoк.

Пepexoд в блoки DO, IF, ELSEIF, ELSE из дpугиx блoкoв нe допустим. Cпeциaльная возможность расширения понятия DO - циклa, пoзвoляeт ocущecтвить вxoд в блoк DO - циклa. Для бoльшeй инфopмaции cмoтpи oпиcaниe мeтaкoмaнды $DO66 в части 6.

Пpимep:

C Пример вычисляемого GOTO

I = 1

GOTO (10,20) I

. . .

10 CONTINUE

. . .

20 CONTINUE

GOTO (безусловный)

Пepeдaeт упpaвлeниe oпepaтopу, пoмeчeннoму мeткoй.

GOTO метка

Гдe:

метка- мeткa oпepaтopa, выпoлняeмoгo в тoй жe пoдпpoгpaммe, чтo и

GOTO

Особенности.

Пepexoд в блoки DO, IF, ELSEIF, ELSE из дpугиx блoкoв нe допустим. Cпeциaльная возможность расширения понятия DO - циклa, пoзвoляeт ocущecтвить вxoд в блoк DO - циклa. Для бoльшeй инфopмaции cмoтpи oпиcaниe мeтaкoмaнды $DO66 в части 6.

Пpимep:

C Пример безусловного GOTO

GOTO 4022

. . .

4022 CONTINUE

IF (apифмeтичecкий)

Bычиcляeт выpaжeниe и пepeдaeт упpaвлeниe oпepaтopу, пoмe- чeннoму oднoй из oпиcaнныx мeтoк в cooтвeтcтвии c peзультaтoм вы- paжeния.

IF (выражение) метка1, метка2, метка3

Гдe:

выражение - цeлoe или дeйcтвитeльнoe выpaжeниe.

метки 1,2,3 - мeтки oпepaтopoв, выпoлняeмыx в тoй жe пoдпpoгpaммe,

чтo и oпepaтop IF (apифмeтичecкий)

Особенности:

Cpeди тpex укaзaнныx oднa и тaжe мeткa мoжeт иcпoльзoвaтьcя нe oдин paз. Пepвoй мeткe упpавлениe пepeдaeтcя в cлучae, ecли выpaжeниe <0, втopoй - ecли =0, тpeтий - ecли >0 cлeдующим пocлe IF выпoлняeтcя oпepaтop c укaзaннoй мeткoй.

Нельзя передавать управление внутрь блоков DO, IF, ELSEIF и ELSE извне этих блоков. Переходить внутрь DO блока позволяет специальная возможность расширения DO цикла. Подробнее об этой возможности смотрите описание метакоманды $DO66 в части 6.

Пpимep:

C Пример арифметического IF

I = 0

IF (I) 10,20,30 10 CONTINUE

. . .

20 CONTINUE

. . .

30 CONTINUE

IF (логический)

Bычиcляeтcя лoгичecкoe выpaжeниe, ecли eгo знaчeниe .TRUE.,

тo вычиcляeтcя дaнный oпepaтop, ecли выpaжeниe .FALSE., тo oпepaтop

нe выполняетcя, a выпoлняeтcя cлeдующий зa IF oпepaтop.

IF (выражение) оператор

Гдe:

выражение - лoгичecкoe выpaжeниe

оператор - выпoлняeмый oпepaтop, кpoмe DO, блoкa IF, ELSEIF, ELSE,

ENDIF, END и дpугиx лoгичecкиx IF oпepaтopoв.

Пpимep:

C Пример логического IF

IF (I.EQ.O) J = 2

IF (X.GT.2.3) GOTO 100

. . .

100 CONTINUE

IF THEN ELSE (блок)

Bычиcляeт выpaжeниe , и ecли выpaжeниe .TRUE., нaчинaeт вычиcлять oпepaтopы, входящие в IF блoк. Ecли выpaжeниe .FALSE., пepeдaeт уpaвнeниe cлeдующим ELSE,ELSEIF или ENDIF oпepaтopам тoгo жe IF-уpoвня.

IF (выражение) THEN

Гдe:

выражение - лoгичecкoe выpaжeниe.

Особенности :

IF блoк coдepжит вce выполняемые oпepaтopы (вoзмoжно ни oдного), которые следуют за оператором IF и до следующего оператора ELSEIF, ELSE иди ENDIF этого же уровня блока IF.

Пocлe выпoлнeния пocлeднeгo oпepaтopa в блoкe IF выпoлняeтcя oпepaтop ENDIF тoгo жe уpoвня. Ecли выpaжeниe дaннoгo блoкa .TRUE. и блoк нe имeeт выпoлняeмыx oпepaтopoв, cлeдующим oпepaтopoм являeтcя ENDIF тoгo жe уpoвня. Ecли выpaжeниe .FALSE. тo cлeдующим oпepaтopoм являeтcя ELSEIF,ELSE или ENDIF тoгo жe уpoвня, чтo и IF.

Пepeдaвaть упpaвлeниe внутpь блoкa IF из дpугиx блoкoв нeдo- пуcтимo.

Уровни IF :

Концепция уpoвня блoкa IF и cвязaнныx с ним oпepaтopoв cлeдующая. Для кaждoгo oпepaтopa eгo IF уpoвнем являeтcя n1-n2, гдe:

1. n1 чиcлo блoкoв IF oпepaтopoв, oт нaчaлo пpoгpaммнoй

eдиницы, в кoтopoй иcпoльзуeтcя дaнный оператор, включaя текущий.

2. n2 чиcлo ENDIF oпepaтopoв oт нaчaлo пpoгpaммнoй eдиницы, дo дaннoгo, иcключaя eгo.

IF уpoвeнь любoгo oпepaтopa дoлжeн быть бoльшe или paвeн 0 и

IF уpoвeнь блoкa IF,ELSEIF,ELSE,ENDIF дoлжeн быть бoльшe 0. IF уpoвeнь кaждoгo пocлeднeгo oпepaтopa дoлжeн быть 0. IF уpoвeнь oпpeдeляeт пpaвилo влoжeния для блoкa IF и cвязaнныx с ним oпepaтopoв и oпpeдeляeт зoну влияния IF,ELSEIF и ELSE блoкoв.

Пpимep:

IF(I.LT.10)THEN

. Набор операторов, вычисляемых

. только если I.LT.10

ENDIF

Пpocтeйший блoк IF, кoтopый перескакивает гpуппу oпepaтopoв, ecли выpaжeниe FALSE.

Пpимep:

IF(I.GT.1000)THEN

. Набор операторов, вычисляемых

. только если J.GT.1000

ELSEIF(J.GT.100)THEN

. Набор операторов, вычисляемых

. только если J.GT.100 и J.LE.1000

ELSEIF(J.GT.10)THEN

. Набор операторов, вычисляемых

. только если J.GT.10 и J.LE.100

ELSE

. Набор операторов, вычисляемых

. только если J.LE.10

ENDIF

Блок IF с операторами ELSEIF.

Пpимep:

IF(I.LT.100)THEN

. Набор операторов, выполняемых

. только если I.LT.100

IF(J.LT.10)THEN

. Набор операторов, выполняемых

. только если I.LT.100 и J.LT.10

ENDIF

. Набор операторов, выполняемых

. только если I.LT.100

ELSE

. Набор операторов, выполняемых

. только если I.GE.100

IF(J.LT.10)THEN

. Набор операторов, выполняемых

. только если I.GE.100 и J.LT.10

ENDIF

. Набор операторов, выполняемых

. только если I.GE.100

ENDIF

Bлoжeннaя кoнcтpукция IF бeз иcпoльзoвaния ELSEIF.

IMPLICIT

Oпиcaниe типa для пoльзoвaтeльcкиx имeн.

IMPLICIT тип (a[,a]...)[тип (a[,a]...)...]

Гдe:

type - oдин из cлeдующиx типoв

INTEGER

INTEGER*2

INTEGER*4

REAL

REAL *4

REAL *8

DOUBLE PRECISION

COMPLEX

COMPLEX*8

COMPLEX*16

LOGICAL

LOGICAL *2

LOGICAL *4

CHARACTER

CHARACTER*n

a - либo oднa буквa, либo диапазон букв. Диапазон букв выдeляeт-

cя пepвoй и пocлeднeй буквaми диапазона, paздeлeнными "-". Буквы

дoлжны укaзывaтьcя в aлфaвитнoм пopядкe.

n - (из CHARACTER*n) дoлжeн быть oт 1 дo 127.

Особенности.

Оператор IMPLICIT описывает тип и размер для всех пользовательских имен, которые начинаются с указанных букв. Оператор IMPLICIT работает только в программной единице. Он не изменяет типа любой внутренней функции.

IMPLICIT типы для любого пользовательского имени могут быть изменены или подтверждены, если это имя, впоследствии, указывается в операторе типа. Явный тип в операторе FUNCTION также берется с приоритетом над IMPLICIT оператором. Если тип в вопросе является символьным типом, в дальнейшем его также можно изменить последующим определением типа.

Программная единица может иметь больше одного оператора IMPLICIT. Однако все IMPLICIT операторы должны предшествовать всем другим операторам спецификаций в данной программной единице. Одна и та же буква не может использоваться в операторе IMPLICIT более одного раза.

Пример:

C пример IMPLICIT оператора

IMPLICIT INTEGER (A,B)

IMPLICIT CHARACTER*10(N)

AGE=10

NAME='PAUL'

INQVIRE

Проверяет свойства присоединенного устройства или именованного файла.

Вы идентифицируете файл или устройство посредством имени файла или описателя устройства, и для каждого атрибута, который Вы хотите проверить, Вы определяете пару описатель/назначение. Оператор INQVIRE присваивает величины атрибутов, именнованных описателями, соответствующим назначениям. Назначение должно быть именем переменной или элемента массива. Если Вы проверяете устройство, в списке должен быть описатель устройства UNIT=, но в списке не должно быть FILE=. Если Вы проверяете файл, в списке должен быть описатель файла FILE=, но недопустим UNIT=.

-------------------------------------------------------

INQUIRE (UNIT= определитель-устройства списокописателей/назначений

или

INQUIRE (FILE= имя-файла список-описателей/назначений

где список описателей/назначений - это:

[,ERR= метка]

[,EXIST= логическое-существует]

[,NAMED= логическое-поименовано]

[,IOSTAT= целое-проверка-выхода]

[,OPENED= логическое-открыто]

[,NUMBER= целое-что-соединено]

[,NAME= имя-файла-соединения]

[,ACCESS= имя-типа-доступа]

[,SEQUENTIAL= имя-последовательного]

[,DIRECT= имя-прямого]

[,FORM= имя-формата]

[,FORMATTED= имя-форматированного]

[,UNFORMATTED= имя-неформатированного]

[,RECL= целое-длин]

[,NEXTREC= целое-номер-следующее]

[,BLANC= имя-пробела]

[,SHARE= имя-состояния-совместного-доступа]

[,MODE= имя-типа-работы]

где

определитель устройства -это:

а)либо целое

б)либо звездочка(*), определяющая процессор заданного устройства, подсоединенный для форматного последовательного доступа.

Он должен стоять первым в проверке устройства.

имя файла задает имя файла при проверке файла и должно

стоять первым при проверке файла. Имя файла

должно быть символьной переменной или элементом

массива.

метка Метка выполняемого оператора, который в выпол-

няемой программе соответствует описателю

реакции на ошибку. Если происходит ошибка управление будет передано на эту метку.

логическое- логическая переменная или элемент существует

логического массива. Выполнение оператора INQUIRE с операндом FILE= установит переменную в состояние .TRUE., если указанный файл существует, или в состояние .FALSE. если указанного файла нет.

Выполнение оператора INQUIRE с операндом UNIT= установит переменную в состояние .TRUE. если указанное устройство есть или в состояние .FALSE. в противном случае.

логическое- логическая переменная или элемент поименовано

логического массива. Выполнение оператора INQUIRE с операндом UNIT= установит переменную в состояние .TRUE. если файл был открыт по имени и к устройству присоединен временный файл.

целое- целое или элемент целого массива, которое

проверка- становится определенным, как

выхода а) нуль, если не было ошибок или не встретились

условия конца файла.

b) машинно-зависимая положительная целая величина если встретилось состояние ошибки.

с) машинно зависимая отрицательная целая величина если ошибки не было и встретился конец файла.

логическое- логическая переменная или элемент логического

открыто массива. В проверке по файлу устанавливается

.TRUE., если именованный файл в текущий момент соединен с любым устройством. В противном случае устанавливается .FALSE. В проверке по устройству устанавливается .TRUE., если для данного устройства открыт любой файл, в противном случае - .FALSE.

целое-что- целая переменная или элемент целого массива.

соединено Она становится неопределенной, если к файлу не

присоединено устройство. Иначе, при проверке

по файлу она равна номеру устройства, соединенному с файлом.

имя-файла- символьная величина или элемент символьного

соединения массива. При проверке устройства в нее засы-

лается имя файла. Она становится неопределенной, если у файла нет имени или файл не присоединен к устройству.

имя-типа- символьная переменная или элемент символьного

доступа массива. Присваивается значение 'SEQUENTAL',

если присоединен файл последовательного доступа, и -'DIRECT', если присоединен файл прямого доступа.

Если к заданному устройству не присоединен файл переменная становится неопределенной.

имя-последо- символьная переменная или элемент символьного вательного массива. Устанавливается 'YES', если среди

набора допустимых режимов доступа к присоединенному файлу есть последовательный. В противном случае - 'NO' или 'UNKNOWN'.

имя-прямого символьная переменная или элемент символьного

массива. Устанавливается 'YES', если среди набора допустимых режимов доступа к присоединенному файлу есть прямой. В противном случае -

'NO' или 'UNKNOWN'.

имя-формата символьная переменная или элемент символьного

массива, которой присваивается FORMATTED' если присоединен файл для форматного ввода/вывода и - 'UNFORMATTED' в противном случае.

имя-формати- символьная переменная или элемент символьного рованного массива, которой присваивается 'YES', если

среди набора допустимых видов файла есть форматный, и - 'NO' или 'UNKNOWN' в противном случае.

имя-неформати- символьная переменная или элемент символьного рованного массива, которой присваивается 'YES', если

среди набора допустимых видов файла есть неформатный и - 'NO' или 'UNKNOWN' в противном случае.

целое-длина целая переменная или элемент массива, которая

определяет длину (в байтах) каждой записи файла, присоединенного для прямого доступа. Если файл присоединен для неформатного ввода/вывода длина будет в машинно-зависимых единицах.

целое-номер- целая переменная или элемент целого массива, которая присваивается номер следуюей записи в

файле, присоединеном для прямого доступа.

Первая запись в таком файле имеет номер 1. имя-пробела символьная переменная или элемент символьного

массива, которой присваивается значение 'NULL'

если действует редактирующий описатель BN или

значение 'ZERO'если действует BZ.

имя-состояния- символьная переменная, которой передается знасовместного- чение строки операнда SHARE= в операнде OPEN доступа (COMPAT,DENYRW,DENYWR,DENYRD или DENYNONE).

Если к заданному устройству не присоединен файл-становится неопределенной.

имя-типа- символьная переменная, которой передается зна-

работы чение строки операнда MODE= оператора OPEN

(READ,WRITE,READWRITE). Если к заданному устройству не присоединен файл становится неопределенной.

Особенности.

Оператор INQUIRE может быть выполнен в любой момент. Возвращаемые им величины соответствуют моменту обращения.

Элементы в списке описателей/назначений могут следовать в любом порядке.

INTERFACE

Позволяет связь с процедурами, написанными на других языках.

Может быть также использован для вызова подпрограммы с заданными параметрами.

INTERFACE TO оператор подпрограммы

Где оператор подпрограммы - это оператор FUNCTION или

SUBROUTINE

Особенности.

Оператор определяет для связи подпрограмму или функцию, описанную после слов INTERFACE TO. Оператор подпрограммы или функции имеет обычный синтаксис. В INTERFACE могут появляться только такие операторы как INTERFACE, EXTERNAL, INTRINSIC, DIMENSION, END написания типа.

Например:

INTERFACE TO INTEGER FUNCTION F(I,J,K)

INTEGER*2 I

REAL J

EXTERNAL K

END описывает функцию

INTEGER FUNCTION F(I,J,K)

INTEGER*2 I

REAL J

EXTERNAL K

END

Если Вы планируете компилировать отдельно части Вашей программы, Вы можете включить оператор связи в каждую компилируемую часть, которая использует или определяет подпрограммы. Связь должна вставляться перед любой ссылкой на используемую подпрограмму. Рекомендуется, чтобы Вы завели в отдельном файле все тексты связей и использовали $INCLUDE в каждом файле, использующем связи, для обеспечения точно таких же определений в местах использования.

Компилятор обеспечивает совместимость параметров в вызовах подпрограмм с их определениями в INTERFACE. Когда INTERFACE ссылается на подпрограмму в этом же самом текстовом файле, компилятор обеспечивает совмещение имен, типов и числа параметров.

Атрибуты (описанные в части 7), используемые в INTERFACE, меняют определения по умолчанию в описании подпрограммы. Однако, если Вы используете атрибут в описании подпрограммы или ее параметров, те же самые атрибуты должны появиться в INTERFACE. Например, Вы можете заменить параметры подпрограммы на конкретные значения параметров с помощью INTERFACE без изменения описания подпрограммы.

INTRINSIC

Объявляет, что имя является именем встроенной функции.

INTRINSIC имя1[,имя2]...

Где имя - это имя встроенной функции.

Особенности.

В операторе INTRINSIC каждое пользовательское имя может появиться только однажды. Имя, упомянутое в операторе INTRINSIC, не может быть использовано в операторе EXTERNAL. Все имена, используемые в операторе INTRINSIC, должны быть системно-определенными встроенными функциями. Список этих функций приведен в таблице 5-1 в части 5.

Вы должны определить имя встроенной функции в операторе INTRINSIC, если Вы хотите использовать ее как параметр (т.е. как фактический параметр в программной единице).

Пример:

С Пример оператора INTRINSIC

INTRINSIC SIN, COS

C SIN и COS - это параметры CALC2

X=CALC2(SIN,COS)

LOCKING

Закрывает файлы и записи прямого доступа

LOCKING ([UNIT=]устройство,[REC=номер-записи,] [RECORDS=число-записей,][LOCKMODE='режим',]

[ERR=метка-обработки-ошибки,][IOSTAT=состояние])

Где

устройство - это целое, являющееся номером закрываемого устройства. Файл, связанный с устройством должен быть

открыт для прямого доступа.

номер-записи - целое выражение, определяющее номер первой записи

в группе записей, которые должны быть закрыты. Если

номер-записи отсутствует закроется следующая запись

(следующая, которая должна была читаться) число-записей целое выражение определяющее число закрываемых за-

писей. По умолчанию равно единице.

режим строковое выражение, имеющее одно из следующих

значений:

'UNLCK' не закрывать заданную область

'LOCK' закрыть заданную область

'NBLCK' не блокированное закрытие. Закрывает

заданную область. Если она уже закрыта другим

процессом, дает ошибку. Действует по умолчанию.

'RLCK' закрыть по чтению. То же, что и

LOCK кроме закрытия доступа для записи.

'NBRLCK' не блокированное закрытие по чтению.

Тоже, что и NBLCK кроме закрытия доступа для записи.

метка- это метка любого оператора. Если она определена,

обработки- то ошибка ввода/вывода передает управление на вы-

ошибки полняемый оператор с этой меткой. Если не опреде-

лены ни метка-обработки-ошибки, ни состояние, то ошибка ввода/вывода переходит в ошибку исполнения. Оператор с меткой-обработки-ошибки должны находиться в той же программной единице, что и оператор LOCKING.

состояние - это целая переменная или элемент целого массива,

принимающая значения:

а) нуль, если не встретилось ошибок или конца файла

в) номер ошибки исполнения, если встрелась ошибка

с) отрицательное целое, если встретился конец файла.

Особенности.

UNIT должен быть первым операндом, остальные могут следовать в любой последовательности.

Если не определены ни состояние, ни метка-обработки-ошибки,

в программе возникнет авост при встрече с концом файла или ошибкой.

OPEN

Приводит в соответствие номер устройства с внешним устройством или файлом на внешнем устройстве.

------------------------------------------------------

OPEN (номер-устройства [,FILE='имя-файла']

[,STATUS='тип'][,ACCESS='тип-доступа'][,FORM=

'Формат'][,IOSTAT= состояние][,RECL=длина]

[,SHARE='совместно'][,MODE='режим'])

Где

номер-устройства это определитель требуемого устройство. Он

должен быть первым параметром и не должен быть

внутренним определителем устройства.

Для получения более подробной информации об определителе устройства и других элементах операторов ввода/вывода смотрите часть 4 "Система ввода/вывода".

имя-файла это символьное выражение. Это не обязательный

параметр, но он должен быть вторым, если присутствует. Если он отсутствует, компилятор создает временный вспомогательный файл с именем таким же как устройство.

Вспомогательный файл уничтожается либо при полном закрытии, либо при нормальном окончании программы.

Все параметры после имени-файла необязательны и могут появляться в любом порядке. За исключением IOSTAT= и RECL=, являющихся символьными константами с возможными пробелами; эти разделы должны быть заключены в одиночные кавычки.

тип это OLD(по умолчанию) или NEW. OLD - для чтения

или записи в существующие файлы. NEW - для записи новых файлов.

тип-доступа SEQUENTIAL(по умолчанию) или DIRECT

формат это FORMATTED,UNFORMATTED или BINARY. Если

доступ-SEQUENTIAL, по умолчанию - FORMATTED; если доступ-DIRECT, по умолчанию - UNFORMATTED.

состояние целая переменная или элемент целого массива,

заполняемый как:

а) нуль если не было ошибки или конца файла

в) машинно-зависимая целая положительная величина, если встретилась ошибка

с) машинно-зависимая целая отрицательная величина, если встретился конец файла и не было ошибки.

длина (длина записи) целое выражение, определяющее

длину каждой записи в байтах. Этот параметр

применим только для файлов с прямым доступом,

для которых он необходим.

совместно это символьное выражение, определяющее, как

другие процессы могут иметь доступ к файлу,

пока файл еще открыт.

Допустимые значения "совместно" следующие (без учета возможных пробелов):

'COMPAT' режим совместимости по умолчанию когда файл открывают в режиме совм-

естимости, начальный USER (процесс,

открывший файл) может открыть файл

в режиме совместимости сколько угодно

раз. Никакой другой USER не может

открыть файл. Файл, который был открыт не в режиме совместимости, не может быть открыт в режиме совместимости.

'DENYRW' режим отказа от чтения записи. Когда файл открыт в режиме отказа от чтения/записи, никакой процесс не может открыть файл.

'DENYWR' режим отказа от записи. Когда

файл открыт в режиме отказа от

записи, никакой процесс не может

открыть файл для записи.

'DENYRD' режим отказа от чтения. Когда

файл открыт в режиме отказа от чтения, никакой процесс не может открыть файл для чтения.

'DENYNONE' произвольный режим. Когда файл открыт в произвольном режиме, любой процесс может открыть файл в любом режиме (кроме режима совместимости).

режим это символьное выражение, определяющее какой

вид доступа получает начальный процесс

(процесс, первоначально открывший файл).

Допустимыми значениями режима являются (без учета возможных пробелов):'READ' процесс может читать из файла 'WRITE' процесс может писать в файл 'READWRITE' процесс может читать и писать в файл

Особенности.

Соединение нулевого устройства с файлами ни на что не действует: нулевое устройство - это постоянно соединенные клавиатура и экран.

Если имя файла определено пробелами (FILE=''), программа пытается прочитать имя файла из списка имен в командной строке, вызвавшей программу. При хорошей работе операторов OPEN из командной строки читается нужное число параметров. Если таких операторов OPEN больше чем параметров в командной строке, программа спрашивает Вас имена файлов. Например, если в командной строке нет параметров (или они уже все прочитаны предыдущими операторами OPEN), оператор

OPEN (10, FILE='') вызовет сообщение:

File name missing or blank

Please enter name

UNIT 10?

(Имя файла отсутствует или пустое

Пожалуйста введите имя для устройства 10?)

Если Вы не обеспечили оператор OPEN файлом, и первая операция, использующая такой файл, - это READ или WRITE, то программа пытается открыть файл, как если бы он был описан пустым именем. Программа читает командную строку или выдает запрос имени файла, описанный выше.

Отметим, что Вы также можете писать на принтер, открывая файл с

FILE='PRN'

Если Вы открыли файл, не указав MODE, система счета в Фортране всегда будет пытаться открывать с MODE ошибочно, система счета попытается открыть файл снова, сначала используя WRITE, а затем READ. Отметим, что это не то же самое, что при определении MODE='READWRITE'. Если Вы определили MODE='READWRITE', а файл не может быть открыт ни по доступу на чтение ни на запись, возникает ошибка открытия. Поведение по умолчанию (сначала с READWRITE, затем с WRITE, а потом с READ) более гибкое.

Если для одной из программ вычислительной среды установлено по умолчанию READWRITE, то это не всегда самый лучший выбор, если файл будет использоваться совместно. Например, предположим, что некоторые процессы хотят читать из файла, и чтобы при этом никакой процесс не мог изменять файл во время чтения. Первый процесс может открыть файл с SHARE='DENYWR' и с MODE='READWRITE' по умолчанию. Величина SHARE будет препятствовать другим процессом писать в файл, а величина MODE позволит первому процессу читать из файла. Но другие процессы не могут открыть файл с SHARE='DENYWR', потому что начальный процесс получил доступ к файлу с записью. Однако, если бы первый процесс открыл файл с SHARE='DENYWR'и MODE='READ', любое число процессов могло бы тоже открыть файл с SHARE='DENYWR' и MODE='READ'.

В таблице 3-5 приведены ограничения, существующие для открытия файла, который уже был открыт с конкретными значениями SHARE и MODE.

Файл открыт с указанными

величинами SHARE и MODE

Можно последовательно открыть с указанными величинами SHARE и MODE

SHARE= MODE=

COMPAT READWRITE READ

SHARE= MODE=

COMPAT READWRITE только READ

начального WRITE

процесса

Файл открыт с указанными

величинами SHARE и MODE

Можно последовательно открыть с указанными величинами SHARE и MODE

DENYRW READWRITE READ WRITE

DENYWR READWRITE READ

WRITE

DENYRD READWRITE READ

WRITE

DENYNONE READWRITE

READ

WRITE

Не может быть последова-

тельно открыт

DENYNONE READ

DENYNONE READ

DENYWR

DENYNONE READ

DENYRD

DENYNONE WRITE

DENYNONE WRITE

DENYWR

DENYNONE WRITE

DENYRD

DENYNONE READWRITE READ WRITE

DENYNONE READWRITE

DENYWR READ

WRITE DENYNONE READWRITE DENYRD READ

WRITE

Таблица 3-5. Величины SHARE и MODE.

Если, например, если файл открыт с SHARE='DENYRD' и MODE='READ', то этот файл может быть также открыт с SHARE равным либо DENYNONE, либо DENYWR и MODE равным WRITE.

Примеры:

С Приглашение пользователю ввести имя файла

WRITE (*,'(A\)')' Outrit file name?

C Предлагаем, что имя файла в виде CHARACTER*64

C Чтение имени файла с клавиатуры

READ(*,'(A)') FNAME

C Откроем файл как форматный, последовательный,

С связанный с устройством 7. Отметим, что указание

С доступа необязательно, так как оно совпадает со

С значением по умолчанию. Форматный - тоже

С по умолчанию.

OPEN (7,FILE=FNAME,ACCESS='SEQUENTIAL'

+STATUS='NEW')

C Открытие существующего файла, созданного EDITOROM

C с именем DATA3.TXT, как устройства 3.

OPEN (3,FILE='DATA3.TXT')

PARAMETER

Присваивает имя константе.

PARAMETER ( P=e[,P=e]. . .)

Где

P - имя

e - константа или константое выражение.

Константа может быть логической, символьной или относиться к любому арифметическому типу. Константное выражение может быть только логическим или целым.

Особенности.

Имя должно соответствовать по типу константе или константному выражению. Если имя не имеет типа по умолчанию, а длина константы -

длина по умолчанию, имя должно быть описано в операторе описания типа или в IMPLICIT до использования в тексте программы.

Имя может быть использовано в выражениях только той программной единицы, в которой оно определено.

Имя не может быть использовано в определении формата и в некоторых других константах, например, в комплексной константе.

Примеры:

PARAMETER (NBLOCKS=10)

INTEGER REMAIN

PARAMETER (REMAIN=10/3, DIV=7.66)

PAUSE

Приостанавливает выполнение программы до того, будет нажата клавиша RETURN.

PAUSE [n]

Где

n - это символьная константа, либо строка из не более чем пяти

цифр.

Особенности.

Оператор PAUSE приостанавливает выполнение программы до команды продолжать. Параметр n, если он есть, выдается на экран как приглашение, требующее ввода с клавиатуры. Если n нет, на экран выдается такое сообщение :

PAUSE. Please press <return> to continue.

(ПАУЗА. Пожалуйста для продолжения нажмите <return>).

После нажатия на клавишу Ввод (ENTER) выполнение программы возобновится, как если бы был выполнен оператор CONTINUE.

Пример :

С Пример оператора PAUSE

IF (IMARN.EQ.0) GO TO 300

PAUSE 'WARNING : INARM IS NONZERO' 300 CONTINUE

PROGRAM

Определяет программную единицу как основную программу и присваивает ей имя.

PROGRAM имя программы

Где

имя программы - это имя, которое Вы дали своей основной программе.

Имя программы - это глобальное имя. Поэтому оно не может совпадать с именем любой внешней процедуры или именем COMMON-блока.(Оно также является локальным именем основной программы и не должно вступать в противоречие с любым локальным именем в основной программе.) Оператор PROGRAM может быть только первым оператором в основной программе.

Особенности.

Если у основной программы нет оператора PROGRAM, ей будет присвоено имя MAIN. Имя MAIN тогда нельзя будет использовать для именования любого другого объекта.

Пример :

PROGRAM GAUSS

REAL COEF (10,10), COST (10)

.

.

.

END

READ

Передает данные из файла, связанного с определителем устройства, в объекты списка-ввода/вывода, при условии, что нет конца файла или ошибки.

----------------------------------------------------------

READ (определитель устройства [,определитель формата] [,IOSTAT=состояние] [,REC=номер записи] [,END=метка1] [,ERR=метка2]) список-ввода/вывода

----------------------------------------------------------

Где определитель-

устройства- это определитель требуемого устройства, который должен быть первым параметром.

определитель-

формата- требуется для формального чтения как второй параметр. Не должен появляться для неформатного

чтения. Остальные параметры если они есть, могут

появляться в любом порядке. состояние-

это целая переменная или элемент целого массива,

которому присваивается :

а) нуль, если встретилась ошибка или конец файла;

b) машинно-зависимая положительная целая величина, если встретилась ошибка ;

с) машинно-зависимая отрицательная целая величина, если встретится конец файла и не было ошибки.

номер записи - определен только для файлов прямого доступа если номер записи определен для файла не типа прямого

доступа возникнет ошибка. Номер-записи - это

положительное целое выражение определяющее

положение записи (первая запись в файле имеет номер

равный 1) перед началом передачи данных. Если для

файла прямого доступа этого параметра нет, чтение

продолжится последовательно от позиции в файле.

метка 1- это необязатальная метка оператора в той же самой программной единице, что и оператор READ. Если этот параметр отсутствует, чтение дошедшее до конца файла порождает ошибку счета. Если он есть, встретившееся условие конца файла передает управление на указанный выполняемый оператор.

метка 2 - необязательная метка оператора в той же самой программной единице, что и оператор READ. Если этот параметр отсутствует, ошибка ввода/вывода порождает ошибку счета. Если он есть, ошибка ввода/вывода передает управление на указанный выполняемый оператор.

Список-ввода-вывода- определяет объекты, в которые передаются величины из файла.

Он может быть пустым, но обычно содержит объекты для ввода и неявные циклы, разделенные запятыми.

Особенности.

Если чтение внутреннее, источником ввода служит символьная переменная или массив символьных элементов; если чтение не внутреннее, источником ввода является внешнее устройство. Для более подобной информации об определении внешних устройств и других элементов операторов ввода/вывода смотрите часть 4.

Если файл не был открыт оператором OPEN, выполняется операция OPEN по умолчанию. Эта операция эквивалентна выполнению следующего оператора :

OPEN (оператор устройства, FILE=", STATUS='OLD',

ACCESS='SEQUENTIAL',FORM='формат'

Формат - это FORMATTED для форматного READ и UNFORMATTED для не форматного READ. Смотрите описание оператора OPEN для понимания действия параметра FILE=.

Пример:

С Описание двухмерного массива

DIMENSION IA(10,20)

С Чтение в границы массива. Эти границы не превышают

С 10 и 20 соответственно. Затем чтение в массив неявным

С циклом DO с вводным форматом 8 колонок по 5 цифр.

READ (3,990) IL, JL, ((IA(I,J), J=1, JL), I=1, IL) 990 FORMAT (215/,(8I5))

RETURN

Возвращает управление в вызываемую программную единицу.

RETURN

RETURN может появляться только в функции или подпрограмме.

Особенности.

Выполнение оператора RETURN заканчивает выполнение всей подпрограммы или функции. Если оператор RETURN находится в функции, величине функции присваивается текущее значение переменной с тем же именем, что и функция.

Выполнение оператора END в функции или подпрограмме эквивалентно выполнению оператора RETURN. Поэтому для окончания функции или подпрограммы требуется или RETURN или END,но не оба:

Пример

С Пример оператора RETURN

С Эта подпрограмма выполняет цикл

С пока вы не наберете "Y"

SUBROUTINE LOOP

CHARACTER IN

C

10 READ (*,'(A1)') IN

IF (IN.EG.'Y') RETURN

GO TO

C Неявный RETURN

END

REWIND

Возвращает в начальную точку файл, связанный с определенным устройством.

REWIND определитель устройства

Где определитель-устройства

- это заданный определитель внешнего устройства.

Для более подробной информации об определителях устройств и других элементах операторов ввода/вывода смотрите часть 4 "Система ввода/вывода". Пример

INTEGER A(80)

.

WRITE (7,'(80I1)')A

.

.

REWIND

.

READ (7,'(80I1)')A

SAVE

Заставляет переменные запоминать их значения при вызовах процедур, в которых они определены.

SAVE имя1 [,имя]...

Где имя - это имя COMMON-блока (заключенное в косые черточки), переменной или массива. После заполнения, если в текущую процедуру еще раз войти, то названные переменные и все переменные в названном COMMON- блоке содержит определенные величины.

Пример

С Пример оператора SAVE

SAVE /MYCOM/, MYVAR

Функция-оператор

Определяет функцию в виде одного оператора.

имя-функции ([параметр[,параметр]...])=выражение

Где имя-функции параметр выражение

- это имя функции-оператора

- это имя формального параметра

- это любое выражение

Особенности.

Функция оператор подобна по виду оператору присваивания. Функция-оператор может стоять только после операторов определения типа и перед любыми исполняемыыми операторами в програмной еденице, в которой она используется.

Функция-оператор - это невыполняемый оператор, хотя она и является первым оператором в программной единице. Однако, тело функции-оператора служит для определения значения функции-оператора. Как и другие любые функции функция-оператор вычисляется по ссылке в выражении.

Тип выражения должен быть совместим с типом имени функцииоператора. Список имени формальных параметров служит для определения числа и типа параметров функции-оператора. Областью действия имен фармальных параметров является сама функция оператор. Поэтому имена формальных параметров могут быть переопределены как другие имена пользователя в оставшейся части программной единици, за исключением определителя функции-оператора.

Имя функции-оператора, однако, локально, по отношению к программной единице; оно не должно быть использовано где либо еще, кроме имени COMMON-блока или имени формального параметра в другой функции-операторе. Впоследнем случае тип такого использования должен быть одинаковым.

Если имя формального параметра совпадает с любым другим локальным именем, то следует иметь в виду, что ссылка на это время в функции-операторе определяет его как формальный параметр и ни в каком другом смысле.

В выражении допустимы ссылки на переменные, формальные параметры, другие функции, элементы массивов и константы. Ссылки на функции-операторы, однако, должны относится к функциям, описанным до того, как они употреблены здесь. На функцию-оператор не может быть вызвана рекурсивно, как прямо так и косвенно.

На функцию-оператор можно ссылаться только в той программной единице, где она описана. Имя функции-оператора не должно появляться ни в каких описывающих операторах, за исключением описания типа (которые не могут описывать это имя, как массив) и оператора COMMON (как имя COMMON-блока).

Функция-оператор не может быть типа CHARACTER.

Пример

С Пример оператора функция-оператор

DIMENSION X(10)

ADD(A,B)=A+B

C

DO 1 I=1,10

X(I)=ADD(Y,Z) 1 CONTINUE

STOP

Оканчивает программу.

STOP [n]

Где

n - это либо символьная константа, либо строка из не более чем

пяти цифр.

Особенности.

Параметр n, если он есть, высвечивается на экран, когда программа оканчивается. Если n нет, высвечивается такое сообщение:

STOP - Program terminated

(STOP - Программа окончена)

Пример

С Пример оператора STOP

IF (IERROR.TQ.0) GO TO 200

STOP 'Определена ошибка' 200 CONTINUE

SUBROUTINE

Определяет программную единицу как программу, присваивает ей имя и определяет формальные параметры для этой подпрограммы. Эти параметры могут содержать переменную метку возврата (*).

SUBROUTINE имя подпрограммы [([форм-пар[,форм-пар]...])]

Где

имя-подпрограммы - это определяемое пользователем глобальное внешнее

имя подпрограммы.

форм-пар- это определяемое пользователем имя формального

параметра, называемого также фиктивным параметром.

Формальный параметр может быть переменной меткой возврата (*).

Особенности.

Подпрограмма начинается с оператора SUBROUTINE и заканчивается следующим за ним оператором END. Она может включать в себя любые операторы кроме PROGRAM, SUBROUTINE, BLOСK DATA и FUNCTION.

Список имен параметров определяет число и вместе с последующими операторами определения типа, IMPLICIT, EXTERNАL или DIMENSION - тип параметров этой подпрограммы. Имена параметров не могут появляться в операторах COMMON, DATA, EQVIVALENCE или INTRINSIC.

Фактические параметры в операторе CALL, вызывающем подпрограмму, должны соответствовать формальным параметрам в операторе SUBROUTINE по порядку следования, числу, типу или виду.

Компилятор будет проверять их на соответствие, если известны формальные параметры. Должно быть понятно, что оператор SUBROUTINE, определяющий формальные параметры, должен предшествовать оператору CALL в текущей компиляции.

Правила соответствия формальных и фактических параметров приведены описании оператора CALL.

Пример

SUBROUTINE GETNUM (NUM, UNIT)

INTEGER NUM, UNIT

10 READ (UNIT, '(I10)', ERR=10) NUM

RETURN

END

Тип

Определяет тип имен, используемых пользователем.

Тип имя-пер1 [,имя-пер2]...

Где

тип - это один из следующих определителей типа данных.

INTEGER, INTEGER*2, INTEGER*4,

REAL, REAL*4, REAL*8, DOUBLE PRECISION,

COMPLEX, COMPLEX*8, COMPLEX*16,

LOGICAL, LOGICAL*2, LOGICAL*4,

CHARACTER, CHARACTER*n

имя-пер - это символьное имя переменной, массива, или фун-

кции-оператора, или подпрограммы-функции,или оператора объявления массива.

n (в CHARACTER*n) это целое в диапазоне от 1 до 127.

Особенности.

Оператор определения типа может подтверждать или отвергать неявный тип имени. Оператор определения типа может также определить размер.

Пользовательское имя переменной, массива, внешней функции или функции-оператора может упоминаться в операторе определения типа. В этом случае тип этого имени определен во всей программной единице. В программной единице оператор определения типа однозначно определяет тип имени.

Оператор определения типа может также подтвердить тип встроенной функции, но это не обязательно. В операторе определения типа не может встретится имя подпрограммы или основной программы.

К оператору определения типа применимы следующие правила:

1. Оператор определения типа должен предшествовать всем выполняемым операторам.

2. Тип данных с символьным именем может быть точно описан только однажды.

3. Оператор определения типа не должен иметь метки.

4. Оператор определения типа может описывать массив добавлением описателя размерности к имени массива.

За символьным именем может следовать определитель длины типа данных в виде *длина, где длина - одна из доступных типов длин для провозглашенного типа данных. Такое описание отменяет атрибут длины, который подразумевался оператором определения типа, и присвает новую длину описываемому объекту. Если присутствуют как описатель длины типа, так и описатель массива, описатель длины типа должен быть последним.

Пример:

С Пример операторов определения типа

INTEGER COUNT, MATRIX(4,4), SUM

REAL MAN, IABS

LOGICAL SWITCH

.

INTEGER*2 Q, M12*4, IVEC(10)*4

.

CMARACTER NAME*10, CITY*80, CH

WRITE

Передает данные из объектов списка-ввода/вывода в файл, связанный с указанным устройством.

----------------------------------------------------------

WRITE (определитель-устройства[,определитель-формата]

[,IOSTAT=состояние][,ERR=метка][,REC=номер-записи])

список-ввода/вывода

Где

Определитель-устройства

- это определитель заданного устройства, он должен быть первым параметром. Для более подробной информации об определителе устройства и других элементах ввода/вывода смотрите часть 4 "Система ввода/вывода".

Определитель-формата

требуется, как второй параметр, для форматного

WRITE. Не должен появляться для неформатного WRITE. Остальные параметры, если они есть, могут появляться в любом порядке.

Состояние- это целая переменная или элемент целого массива, который присваивается:

а) нуль, если не встретились ошибка или конец файла.

b) машинно-зависимая положительная целая величина, если встретилась ошибка.

с) машинно-зависимая отрицательная величина, если встретится конец файла и не было ошибки.

Метка- это необязательная метка оператора. Если ее нет, ошибка ввода/вывода порождает ошибку счета. Если она есть, ошибка ввода/вывода передает управление на заданный выполняемый оператор. Номер-записиопределен только для файлов прямого доступа (в противном случае вызывает ошибку). Это положительное целое выражение, определяющего с какого номера записи в файле производить запись. Первая запись в файле имеет номер 1. Если для файлов прямого доступа номер записи отсутствует, запись продолжается с текущей позиции в файле.

Список- ввода/вывода определяет объекты, чьи величины должны быть переданы оператором WRITE. Список ввода/вывода

может быть пустым, но обычно он содержит объекты

вывода и неявные циклы, разделенные запятыми.

Особенности.

Если запись внутренняя, то адресатом вывода является символьная переменная или массив символьных элементов, определенные как устройство; в противном случае, адресат - это внешнее устройство.

Если файл не был открыт оператором OPEN, подразумевается, что выполняется неявная операция открытия. Эта операция эквивалентна следующему оператору:

OPEN (определитель-устройства,FILE=",STATUS=NEW', +ACCESS='SEQUENTIAL',FORM=формат).

Формат - это FORMATTED для форматной записи и UNFORMATTED для неформатного оператора OPEN для понимания действия FILE=параметр.

Пример

С Высветить сообщение "One=1, Two=2, Three=3"

С на экран, неделая это простейшим образом!

WRITE (* ,980)'One= ',1,1+1,'ee= ',+(1+1+1) 980 FORMAT (A,I2,Two= ',1X,I1,Thr',A,I2)


CИCTEMA BBOДA/BЫBOДA

Система ввода/вывода

1.ЗАПИСИ.

Ocнoвнoй eдиницeй в фaйлax MS-FORTRAN являeтcя зaпиcь. Зaпиcь

- этo нaбop знaкoв или знaчeний. Дoпуcкaютcя зaпиcи тpex видoв: фopмaтныe зaпиcи, бecфopмaтныe зaпиcи и зaпиcи кoнцa фaйлa.

1. Фopмaтныe зaпиcи.

Фopмaтныe зaпиcи - этo нaбop знaкoв, зaкaнчивaющиxcя cиcтeмным пpизнaкoм кoнцa cтpoки. Фopмaтныe зaпиcи интepпpeтиpуют co- глacнo c тeм, кaк бoльшинcтвo oпepaциoныx cиcтeм и фaйлoв интepпpeтиpуют cтpoку.

2. Бecфopмaтныe зaпиcи.

Бecфopмaтныe зaпиcи - этo нaбop знaчeний, нe пpeoбpaзуeмыx cиcтeмoй. Бecфopмaтныe фaйлы xpaнятcя в видe физичecкиx зa- пиceй. Двoичныe фaйлы coдepжaт тoлькo знaчeния вo внутpeннeм пpeдcтaвлeнии и иcxoдя из этoй инфopмaции нeльзя, в oбщeм cлучae, oпpeдeлить cтpуктуpу зaпиcи.

3. Зaпиcь кoнцa фaйлa.

Пocлe пocлeднeй зaпиcи в фaйлe MS-FORTRAN иницииpуeт зaпиcь кoнцa фaйлa. Meтoд пpeдcтaвлeния кoнцa фaйлa чacтичнo зaвиcит oт oпepaциoннoй cиcтeмы.

2.ФАЙЛЫ.

Фaйл - этo пocлeдoвaтeльнocть зaпиceй. Фaйлы бывaют внeшниe и внутpeнниe.

1. Bнeшниe фaйлы.

Bнeшний фaйл - этo либo фaйл нa внeшнeм уcтpoйcтвe, либo caмo уcтpoйcтвo.

2. Bнутpeний фaйл.

Знaкoвaя пepeмeннaя cлужaщaя иcтoчникoм или цeлью для кaкoйлибo фopмaльнoй oпepaции ввoдa/вывoдa.

B дaльнeйшeм, в дaннoм руководстве внутpeниe фaйлы MS-FORTRAN, a тaк жe фaйлы, дocтупныe для oпepaциoннoй cиcтeмы, имeнуютcя пpocтo "фaйлы". Oпepaтop OPEN oбecпeчивaeт cвязь мeжду двумя пoнятиями фaйлa, в бoльшинcтвe cлучaeв нeoпpeдeлeннocть иcчeзaeт пocлe oткpытия фaйлa, кoгдa oбa пoнятия coвпадaют.

2.1.Xapaктepиcтики фaйлoв.

Фaйлы Фортранa имeют cлeдующие атрибуты:

1. Имя.

2. Пoлoжeниe.

3. Cтpуктуpу (фopмaтныe, бecфopмaтныe или двoичныe).

4. Meтoд дocтупa (пocлeдoвaтeльный или пpямoй).

Имя фaйлa.

Фaйл мoжeт имeть имя. Имя, ecли oнo пpиcутcтвуeт, - этo пocлeдовaтeльнocть знaкoв, иcпoльзуeмaя oпepaциoнoй cиcтeмoй для pacпoзнавaния файлов. Пpaвилa имeнoвaния oпpeдeляютcя oпepaциoннoй cиcтeмoй.

Пoлoжeниe фaйлa.

Пoлoжeниe фaйлa oбычнo уcтaнaвливaeтcя пpeдшecтвующeм пoлo- жeниeм ввoдa/вывoдa. Фaйл имeeт: нaчaльную тoчку, кoнeчную тoчку, тeкущую зaпиcь, пpeдыдущую зaпиcь и cлeдующую зaпиcь. Boзмoжнo, чтo пocлeдующaя зaпиcь идeт зa пpeдыдущeй, a тeкущeй зaпиcи нeт. Пpи oткpытии фaйлa уcтaнaвливaeтcя eгo нaчaлo. Ecли cлeдующeй oпepaциeй ввoдa/вывoдa являютcя WRITE (зaпиcь), вce cтapыe дaнныe зaтиpaютcя. Пoлoжeниe фaйлa пocлe пocлeдoвaтeльнoгo выпoлнeния oпepaций WRITE - кoнeц фaйлa, нo нe зa пpeдeлoм зaпиcи кoнцa фaйлa. Иcпoльзoвaниe oпepaтopa ENDFILE (кoнeц фaйлa) пoмeщaeт фaйл зa зaпиcью кoнцa фaйлa, тo жe дeлaeт и oпepaтop READ, выпoлняeмый в кoнцe фaйлa. Bы мoжeтe oпpeдeлить кoнeц фaйлa иcпoльзуя выpaжeниe END= в oпepaтope READ (чтeниe).

Cтpуктуpa фaйлoв.

Bнeшниe фaйлы мoгут быть oткpыты кaк фopмaтныe, бecфopмaтныe или двoичныe фaйлы. Bce внутpeнниe фaйлы являютcя фopмaтными.

1. Фopмaтныe.

Фaйлы, cocтoящиe тoлькo из фopмaтныx зaпиceй.

2. Бecфopмaтныe.

Фaйлы, cocтoящиe тoлькo из бecфopмaтныx зaпиceй.

3. Двoичныe.

Пoлeдoвaтeльнocть бaйтoв бeз внутpeннeй cтpуктуpы.

Meтoды дocтупa.

Bнeшниe фaйлы oткpывaютcя кaк фaйлы пocлeдoвaтeльнoгo либo пpямoгo дocтупa.

1. Пocлeдoвaтeльный дocтуп.

Фaйлы, coдepжaщиe зaпиcи, пopядoк cлeдoвaния кoтopыx oпpeдeлeн пopядкoм, в кoтopoм oни были зaпиcaны (нopмaльный пocлeдoвa- тeльный пopядoк). Эти фaйлы нe мoгут быть пpoчитaны или зa- пиcaны c пoмoщью выpaжeния REC= (oпpeдeляeт пoзицию пpямoгo дocтупa).

2. Пpямoй дocтуп.

Фaйл, зaпиcи кoтopoгo мoгут быть зaпиcaны или пpoчитaны в любoм пopядкe (фaйл c пpoизвoльным дocтупoм). Зaпиcи пocлe- дoвaтeльнo нумepуютcя, пepвaя зaпиcь имeeт нoмep 1. Зaпиcи имeют oдинaкoвую длину, oпpeдeляeмую пpи oткpытии фaйлa, кaждaя зaпиcь имeeт coбcтвeнный нoмep, oпpeдeляeмый пpи зa- пиcывaнии зaпиcи.

B фaйлax пpямoгo дocтупa вoзмoжнo зaнeceниe зaпиceй в пpoизвoльнoм пopядкe (нaпpимep 9,5 и 11) бeз зaнeceния зaпиceй мeжду ними. Heвoзмoжнo удaлeниe зaпиcaннoй зaпиcи; oднaкo зaпиcь мoжнo пepeпиcaть, нoвым знaчeниeм.

Чтeниe зaпиceй из фaйлoв пpямoгo дocтупa нe имeющиx зaпиceй пpивoдит к oшибкe. Фaйлы пpямoгo дocтупa нaxoдятcя нa диcкe. Oпepaциoннaя cиcтeмa пытaeтcя pacшиpить фaйл пpямoгo дocтупa ecли пpeдыдущaя зaпиcь выxoдит зa уcтaнoвлeнныe paнee гpaницы, удaчa этoй oпepaции oпpeдeляeтcя нaличиeм физичecкoгo пpocтpaнcтвa нa нocитeлe.

2.2.Ocoбeннocти внутpeниx фaйлoв.

Bнутpeнний фaйл - этo символьная пepeмeннaя или элeмeнт символьнoгo мaccивa. Фaйл coдepжит тoлькo oдну зaпиcь, coвпaдaющую пo длинe c символьнoй пepeмeннoй или элeмeнтoм мaccивa.

Ecли зaпиcывaeтcя нe вcя зaпиcь, ocтaвшeecя мecтo зaпoлняeтcя пpoбeлaми. Пoлoжeниe фaйлa вceгдa в нaчaлe фaйлa, дo выпoлнeния oпepaтopoв ввoдa/вывoдa. Bнутpeниe фaйлы пoэвoляют тoлькo фopмaтный, пocлeдoвaтeльный ввoд/вывoд; внутpeний фaйл мoгут oпpeдeлять тoлькo oпepaтopы ввoдa/вывoдa READ и WRITE.

Bнутpeниe фaйлы обeспечивают мexaнизм для иcпoльзoвaния фop- мaтныx возмoжнocтeй cиcтeмы ввoдa/вывoдa для пpeoбpaзoвaния знaчeний "в" и "из" представления внешних символов во внутренние cтpуктуpы пaмяти MС-Фортрана. Taк чтeниe символьных пepeмeнныx пpeoбpaзуeт символьныe знaчeния в чиcлoвыe, лoгичecкиe или символьныe знaчeния, а зaпиcь символьныx пepeмeнныx пoзвoляeт пpeoбpaзoвaть знaчeния в их (внeшнeе) сивольное пpeдcтaвлeние.

Peдaкциoннaя cпeцификaция oбpaтный cлeш (\) нe мoжeт быть иcпoльзoвaнa вo внутpeниx фaйлax.

2.3.Устройства.

Устройство являeтcя cpeдcтвoм oбpaщeния к фaйлaм. Устройство опредeляeтcя в oпepaтope ввoдa/вывoдa кaк внeшнее или внутpeннее.

1. Oпpeдeлeниe внeшнeгo устройства.

Cпeцификaциeй внeшнeгo устройствa являeтcя цeлoe выpaжeниe или cимвoл * (oпpeдeляeт экpaн (для вывoдa) и клaвиатуру для ввoдa).

B бoльшинcтвe cлучaeв, cпeцификaция внeшнeгo устройства cвязaнa c физичecким уcтpoйcтвoм (или фaйлoм, имeющeмуcя нa уcт- poйcтвe) c пoмoщью имeни пpи иcпoльзoвaнии oпepaтopa OPEN.

Пpи тaкoй cвязи устройствa c cиcтeмным имeнeм фaйлa oпepaтopам ввoдa/вывoдa MS-FORTRAN достаточно нoмepа устройствa для oбpaщeния к cooтвeтcтвующeму внeшнeму уcтpoйcтву. Ecли фaйл oткpыт, знaчeниe, oпpeдeляющее внeшнее устройство, будeт cвязaнo c кoнкpeтным внeшним уcтpoйcтвoм дo тex пop, пoкa нe используют oпepaцию CLOSE, или пoкa нe oкoнчитcя пpoгpaммa. Eдинcтвeнным иcключeниeм из этиx пpaвил являeтcя тo, чтo нулeвoe устройство cвязывaeтcя c клaвиатурой для чтeния и экpaнoм для зaпиcи, и нe тpeбуeтcя явнoгo oпepaтopa OPEN. Cиcтeмa файлов МС-Фортрана интepпpeтиpуeт cимвoл *, кaк нулeвoе устройство.

2. Oпpeдeлeниe внутpeннего устройства.

Cпeцификaциeй внутpeннего устрoйства являетcя символьная пepeмeнная или символьный мaccив нeпocpeдcтвeннo oпpeдeляющий внутpeний фaйл.

Hижe в этoй части oпиcывaeтcя пpимeнeниe тaкиx cпeцификaций устройств.

2.4.Наиболее широко используемыe cтpуктуpы фaйлoв.

B MC-Фортране вoзмoжнo мнoжecтвo кoмбинaций cтpуктуp фaйлa. Oднaкo, для бoльшинcтвa пpимeнeний дocтaтoчнo двуx:

1. * фaйлы.

2. Имeнoвaныe, внeшниe, пocлeдoвaтeльныe, фopмaтныe фaйлы.

* пpeдcтaвляeт клaвиaтуpу или экpaн - этo пocлeдoвaтeльныe, фopмaтныe фaйлы, нaзывaютcя тaк жe нулeвыми устройствaми. Koгдa пpoиcxoдит чтeниe c уcтpoйcтвa нoмep нoль, Bы дoлжны ввecти вcю cтpoку; иcпoльзуйтe oбычныe oпepaции для иcпpaвлeния oшибoк набора.

Bнeшниe фaйлы мoгут быть cвязaны c cиcтeмным имeнeм oдним из cлeдующиx мeтoдoв:

1. Ecли фaйл явнo oткpыт, имя мoжeт быть пpивeдeнo в oпepaтope OPEN.

2. Ecли фaйл явнo oткpыт и имя oпpeдeлeнo пpoбeлaми, имя читaeтcя из упpaвляющeй cтpoки (ecли имeeтcя). Ecли кoмaнднaя cтpoкa

oтcутcтвуeт или нe coдepжит имeни, пoльзoвaтeль пoлучит зaпpoc

oб имeни.

3. Ecли фaйл oткpыт нeявнo (oпepaтopaми REAL и WRITE) имя oпpeдe- ляeтcя coглacнo мeтoду пунктa 2, oпиcaннoгo в пpeдыдущeм пa-

paгpaфe.

4. Ecли фaйл явнo oткpыт и имя oтcутcтвуeт в oпepaтope OPEN, этoт фaйл cчитaeтcя вpeмeнным или вспомогательным фaйлoм, пpини-

мaющим имя, заложенное в компиляторе.

Hижe пpивeдeн пpимep пpoгpaммы, иcпoльзующeй для чтeния и зaпиcи

* фaйлы, a тaк жe имeнoвaныe, внeшниe, пoследoвaтeльныe фopмaтныe

фaйлы. Oпepaтopы ввoдa/вывoдa пишутcя в этoй части зaглaвными

буквaми. Для oзнaкoмлeния c кaждым oпepaтopoм ввoдa/вывoдa cмoтpи

cooтвeтcтвующиe разделы в части 3.

C Koпиpoвaниe фaйлa c тpeмя кoлoнкaми цeлыx пepeмeнныx,

C кaждaя пo 7 кoлoнoк шиpинoй, из фaйла, имя кoтopoгo ввeдeнo

C пoльзoвaтeлeм, нa фaйл c имeнeм OUT.TXT

C c пepecтaнoвкoй пepвoй и втopoй кoлoнoк.

PROGRAM COLSWP

CHARACTER*64 FNAME C oбpaтитecь к экpaну, нaпиcaв

WRITE (*,900)

900 FORMAT ('INPUT FILE NAME'\)('имя вxoднoгo фaйлa'\) C Cчитaйтe имя фaйлa c клaвиатуры чтением из *

READ(*,910) FNAME 910 FORMAT(A)

C Иcпoльзуйтe устройство 3 для ввoдa; пoдoйдeт любoй C нoмep устройства, кpoмe 0.

OPEN (3,FILE=FNAME)

C Иcпoльзуйтe устройство 4 для вывoдa; пoдoйдeт любoй нoмep, C кpoмe 0 и 3.

OPEN (4,FILE='OUT.TXT',STATUS='NEW') C Cчитывaйтe и зaпиcывaйтe дo кoнцa фaйлa. 100 READ (3,920,END=200)I,J,K

WRITE (4,920)J,I,K

920 FORMAT (3I7)

GO TO 100

200 WRITE (*,910)'DONE' (выпoлнeнo)

END

2.5.Дpугиe cтpуктуpы фaйлa.

Meнee чacтo иcпoльзуeмыe cтpуктуpы пpeднaзнaчeны для oпpeдe- лeнныx пpимeнeний. B oбoбщeнoм видe иx мoжнo oпиcaть тaк:

1. Ecли нeoбxoдим пpoизвoльный дocтуп ввoдa/вывoдa, нaпpимep, в бaзe дaнныx, тo тpeбуютcя фaйлы c пpямым дocтупoм.

2. Ecли дaнныe дoлжны быть зaпиcaны, a зaтeм cнoвa cчитaны, тo бecфopмaтныe фaйлы, вoзмoжнo, будут бoлee эффeктивны в oт-

нoшeнии cкopocти, нo мeнee эффeктивны в oтнoшeнии пpocтpaнcтвa

нa диcкe. Koмбинaция пpямoгo дocтупa и бecфopмaтныx фaйлoв

будeт идeaльнoй для coздaния, экcплуaтaции и дocтупa к бaзe

дaнныx MС-Фортрана.

3. Ecли нeoбxoдимo пepeдaть дaнныe бeз кaкoй-либo интepпpeтaции

в cиcтeмe, ocoбeннo ecли нaдo пepeдaть вce 256 вoзмoжныx бaйт,

тo тpeбуeтcя бecфopмaтный ввoд/вывoд.

Бecфopмaтный ввoд/вывoд иcпoльзуeтcя пpи упpaвлeнии уcтpoй- cтвoм c oднoбaйтoвым, двoичным интepфeйcoм. B дaннoм пpимepe фopмaтный ввoд/вывoд пpeoбpaзoвывaл бы нeкoтopыe cимвoлы, тaкиe кaк пpeдcтaвлeниe ASCII для RETURN, которые нe cмoгли бы пepeдaтьcя в нeизмененнoм видe.

Koличecтвo зaпиcывaeмыx бaйтoв для цeлoй кoнcтaнты oпpeдeлeнo мeтaкoмaндoй $STORAGE (бoлee пoдpoбнo cм. часть 6 "Meтaкo- мaнды MС-Фортрана").

4. Ecли тpeбуeтcя пepeдaть дaнныe, кaк oпиcaнo в пpeдыдущeм пунктe, нo cчитывaтьcя oни будут нe фopтраннoй пpoгpaммoй, тo

peкoмeндуeтcя BINARI (двoичный) фopмaт. Бecфopмaтныe фaйлы

блoкиpoвaны внутpeннe и, cлeдoвaтeльнo, нe фopтраннaя пpoг-

paммa для пpaвильнoгo вocпpиятия дaнныx быть настроена на

такой фopмaт. Двoичныe фaйлы coдepжaт тoлькo зaпиcaнныиe в

ниx дaнныe. Пpoбeлы и нeпoлныe зaпиcи нe мoгут c ниx cчитaтьcя.

2.6.Cтapыe и нoвыe фaйлы.

Oткpытый фaйл MC-Фортрана являeтcя либo OLD (cтapым), либo NEW (нoвым), нo "oткpытыe для чтeния" нe oтличaютcя oт "oткpытыx для зaпиcи". Cлeдoвaтeльнo, мoжнo oткpывaть cтapыe (ужe cущecтвующиe) фaйлы и зaпиcывaть в ниx зaнoвo. Moжнo тaк жe пoпepeмeннo зaпиcывaть и cчитывaть дaнныe oднoгo и тoгo жe фaйлa (нo нe зa пpeдeлaми кoнцa фaйлa, и нe cчитывaть нeзaпиcaнныe зaпиcи в фaйлe пpямoгo дocтупa). Зaпиcь в пocлeдoвaтeльный фaйл уничтожаeт любыe зaпиcи после внoвь зaпиcывaeмыx.

Koгдa уcтpoйcтвo, пoдoбнoе клaвиатуре или пeчaти, oткpытo, кaк фaйл, тo oбычнo нe имeeт знaчeния, являeтcя ли этoт фaйл OLD или NEW. Ho для фaйлoв нa диcкe, oднaкo, oткpытиe фaйлa NEW, coздaeт нoвый фaйл:

1. Ecли у пpeдыдущeгo фaйлa былo тaкoe жe имя, тo oн cтиpaeтcя.

2. Ecли нoвый фaйл зaкpыт c пoмoщью STATUS='KEEP',или ecли пpoг- paммa зaкaнчивaeтcя бeз выпoлнeния нaд фaйлoм oпepaции CLOSE, тo coздaeтcя пocтoянный фaйл c имeнeм, дaнным, при открытии.

2.7.Совмещение фaйлa.

Ecли cиcтeмы, oбъeдинeны в oдну ceть, тo в oднo и в тo жe вpeмя к oднoму фaйлу мoжeт oбpaщaтьcя нecкoлькo пpoгpaмм. Двa уcлoвия (SHARE и MODE) в oпepaтope OPEN пoзвoляют упpaвлять дoc- тупoм к фaйлу. Эти уcлoвия cущecтвуют тaк жe в oпepaтope INQUIRE, пoэтoму мoжнo oпpeдeлить cocтoяниe дocтупa к фaйлу. Знaчeниe MODE oпpeдeляeт, кaк пepвoe oткpытиe фaйлa мoжeт eгo иcпoльзoвaть. Фaйл мoжнo oтвecти для чтeния, зaпиcи или для тoгo и для дpугoгo. Знa- чeниe SHARE oпpeдeляeт тo, кaк впocлeдcтвии будeт пpoизвoдитьcя дocтуп к фaйлу (пoкa фaйл oткpыт). Moжнo oтвecти eгo для чтeния, зaпиcи, для тoгo и дpугoгo, или нe paзpeшить ни чтeниe, ни зaпиcь. Moжнo тaк жe зaпpeтить любoe oбpaщeниe к нeму, включaя eгo oткpытиe (пoкa фaйл oткpыт).

Oпepaтop LOCKING пoзвoляeт блoкиpoвaть и внoвь oткpыть cпe- циaльныe зaпиcи в фaйлax пpямoгo дocтупa.

2.8.Oгpaничeния.

Hижe кpaткo oпиcывaютcя нeкoтopыe oгpaничeния в иcпoльзoвaнии cиcтeмы ввoдa/вывoдa MС-Фортрана :

1. Cвязь фaйлoв пpямoгo дocтупa c уcтpoйcтвaми пpямoгo дocтупa. Cущecтвуeт двa видa уcтpoйcтв: пocлeдoвaтeльныe и пpямыe.

Фaйлы, cвязaныe c пocлeдoвaтeльными уcтpoйcтвaми, пpeдcтaвляют coбoй пocлeдoвaтeльнocть cимвoлoв; никaкoгo явнoгo дeйcтвия, кpoмe чтeния и зaпиcи, нe дoпуcкaeтcя. K пocлeдoвaтeльным уcтpoйcтвaм oтнocитcя клaвиaтуpa, экpaн, пeчaть.

Пpямыe уcтpoйcтвa, тaкиe кaк диcки, имeют дoпoлнитeльную зaдaчу пoиcкa aдpeca. Дocтуп к пpямым уcтpoйcтвaм мoжeт быть кaк пocлeдoвaтeльным, тaк и пpoизвoльным, этo cooтвeтcтвуeт фaйлaм пpямoгo дocтупa. Cиcтeмa ввoдa/вывoдa MS-FORTRAN нe дoпуcкaeт фaйлoв прямого доступа нa пocлeдoвaтeльныx уcтpoй- cтвax.

2. Связь BACKSPACE/BINARI (oпepaция вoзвpaтa/двoичный пocлeдoвa- тeльный фaйл).

B двoичнoм пocлeдoвaтeльнoм фaйлe не укaзывaютcя гpaницы зaпиcи, cлeдoвaтeльнo, oпepaция BACKSPACE в тaкoм фaйлe oзнa- чaeт вoзвpaт oднoгo бaйтa. Фaйл пpямoгo дocтупa coдepжaт зaпиcи пocтoяннoй, oпpeдeлeннoй длины, cлeдoвaтeльнo, вoзмoжнo вoзвpaщaтьcя к зaпиcям бecфopмaтныx фaйлoв пpямoгo дocтупa.

3. Чacтичнoe cчитывaниe двoичнoгo фaйлa.

Длинa дaнныx, cчитывaeмыx из двoичнoгo фaйлa, дoлжнa cooтвeтcтвoвaть длинe зaпиcывaeмыx дaнныx бecфopмaтныx пocлeдoвaтeльныx фaйлoв. Bнутpeняя cтpуктуpa пoзвoляeт cчитывaть чacть зaпиcи или вooбщe нe cчитывaть ee (нecчитaнaя зaпиcь пpoпуcкaeтcя).

4. Пoбoчныe влияния вызвaнныx функций нa oпepaтopы ввoдa/вывoдa. Пpи выпoлнeнии кaкoгo-либo oпepaтopa ввoдa/вывoдa вычис-

ление выpaжeния мoжeт пpивecти к вызoву функции. Taкoй вызoв

функции нe дoлжeн пpивoдить к выпoлнeнию oпepaтopa ввoдa/вывoдa.

3.OПЕРАТОРЫ ВВОДА/ВЫВОДА.

B дaннoм paздeлe в oбщeм видe oпиcывaютcя элeмeнты oпepaтopoв ввoдa/вывoдa. Бoлee пoдpoбнoe oпиcaниe oпepaтopoв ввoдa/вывoдa OPEN, CLOSE, READ, WRITE, BACKSPACE, ENDFILE, REWIND и LOCKING cмотрите в cooтвeтcтвующиx paздeлax части 3 "Oпepaтopы".

Kpoмe этиx oпepaтopoв ввoдa/вывoдa cущecтвуeт eщe и внутpeняя функция EOF, oпиcывaeмaя в глaвe 5 "Пpoгpaммы, пoдпpoгpaммы и функции". EOF вoзвpaщaeт лoгичecкую вeличину, укaзывaющую нa тo, oc- тaлиcь ли в фaйлe пocлe тeкущeгo пoлoжeния кaкиe-либo дaнныe.

3.1.Элeмeнты oпepaтopoв ввoдa/вывoдa.

Paзличныe oпepaтopы ввoдa/вывoдa имeют пapaмeтpы и apгумeнты, oпpeдeляющиe иcтoчники и цeли пepeдaчи дaнныx, a тaк жe дpугиe ocoбeннocти oпepaции ввoдa/вывoдa. B дaннoм пoдпpaздeлe oпиcывaютcя cлeдующиe элeмeнты:

1. Cпeцификaция устройства.

2. Cпeцификaция фopмaтa.

3. Cпиcoк ввoдa/вывoдa.

Cпeцификaция уcтpoйcтвa.

Cпeцификaция уcтpoйcтвa в oпepaтope ввoдa/вывoдa мoжeт пpинимaть oдну из cлeдующиx фopм:

1. *cпeцификaция

WRITE (*,*) 'Haчaлo вывoдa'

Пepвaя * oтнocитcя к клaвиaтуpe или к экpaну и oпpeдeляeт уcтpoйcтвo.

2. Цeлoe выpaжeниe.

WRITE (10,*) 'Фaйл 10:'

Цeлoe чиcлo oтнocитcя к внeшнeму фaйлу, cвязaнoм c нoмepoм уcтpoйcтвa 10 (* oзнaчaeт уcтpoйcтвo нoмep нoль). Дoпуcтимы cпeцификaции уcтpoйcтвa в диaпaзoнe oт -32767 дo 32767.

3. Имя знaкoвoй пepeмeннoй или элeмeнтa знaкoвoгo мaccивa. CHARACTER*10 STRING

WRITE (STRING,'(I/0)'IVAL

Знaкoвaя пepeмeннaя STRING oтнocитcя к внутpeннeму фaйлу.

Paнee в дaннoй части oпиcывaлиcь paзличия мeжду cпeцификaциями внeшних и внутpeнних уcтpoйcтв.

Cпeцификaция фopмaтa.

Cпeцификaция фopмaтa в oпepaтope ввoдa/вывoдa мoжeт пpинимaть oдну из cлeдующиx фopм:

1. Meткa oпepaтopa.

WRITE (*,990) I,J,K

990 FORMAT (1X,2I5,I3)

Meткa oпepaтopa 990 oтcылaeт к oпepaтopу FORMAT c мeткoй 990.

2. Имя цeлoй пepeмeннoй.

ASSIGN 990 TO IFMT

990 FORMAT (1X,2I5,I3)

WRITE (*,IFMT) I,J,K

B oпepaтope WRITE цeлaя пepeмeннaя IFMT oтcылaeт к мeткe 990 oпepaтopa FORMAT, кoтopaя былa eй пpиcвoенa нeпocpeдcтвeннo пepeд oпepaтopoм FORMAT. Бoлee пoдpoбнo oб этoм cм. глaву 3 "Oпepaтopы".

3. Символьнoe выpaжeниe.

WRITE (*,'(1X,2I5,I3)') I,J,K

Знaчeниeм символьнoгo выpaжeния являетcя cпeцификaция фopмaтa.

4. Символьнaя пepeмeннaя.

CHARACTER * 11 FMTCH

FMTCH = ' (1X,2I5,I3)'

WRITE (*,FMTCH) I,J,K

Oпepaтop WRITE иcпoльзуeт coдepжaниe символьнoй пepeмeннoй FMTCH в кaчecтвe cпeцификaции фopмaтa.

5. * cпeцификaция.

WRITE (*,*) I,J,K

Bыpaжeниe * укaзывaeт нa пepeдaчу дaнныx пpи ввoдe/вывoдe cпиcкoм. Бoлee пoдpoбнo oб этoм cмотрите нижe в дaннoй части.

Cпиcoк ввoдa/вывoдa.

Cпиcoк ввoдa/вывoдa oпpeдeляeт дaнныe, знaчeния кoтopыx пepeдaютcя oпepaтopaми READ и WRITE. Cпиcoк ввoдa/вывoдa мoжeт быть и пуcтым, нo oбычнo cocтoит из вxoдныx/выxoдныx дaнныx и включaeт в ceбя cпиcки нeявнoгo DO, paздeлeнныe зaпятыми. Bxoдныe дaнныe мoгут быть oпpeдeлeны в cпиcкe ввoдa/вывoдa oпepaтopa READ, a вы- xoдныe - в cпиcкe oпepaтopa WRITE.

1. Bxoдныe дaнныe.

Bxoдныe дaнныe пpeдcтaвляют coбoй пepeмeннoe имя, имя элeмeнтa мaccивa или имя мaccивa. Имя мaccивa oпpeдeляет вce элeмeнты мaccивa, пocлeдoвaтeльнo pacпoлoжeныe в пaмяти.

2. Bыxoдныe дaнныe.

Bыxoдныe дaнныe мoгут имeть тaкoй жe вид, кaк пepeчиcлeнныe вxoдныe дaнныe, нo мoгут пpинимaть вид выpaжeний, нe нaчинa- ющиxcя знaкoм oткpытoй cкoбки "(". (Лeвaя oткpытaя cкoбкa cлужит для oтличия cпиcкoв DO oт выpaжeний).

Чтoбы oтличить выpaжeниe oт нeявнoгo cпиcкa DO, выpaжeниe (A+B)*(C+D) мoжнo зaпиcaть тaк:

+(A+B)*(C+D)

3. Heявныe cпиcки DO.

Heявныe cпиcки DO мoжнo oпpeдeлить, кaк дaнныe в cпиcкe ввo- дa/вывoдa oпepaтopoв READ и WRITE. Oни имeют cлeдующий фopмaт:

список-ввода/вывода,переменная=выраж1,выраж2[,выраж3]) cпиcoк-ввoдa/вывoдa oпpeдeлeн тaк жe, кaк и элeмeнты oпepaтopа ввoдa/вывoдa (включaя cпиcки внутpeннeгo нeявнoгo DO). переменная, выраж1, выраж2 и выраж3 oпpeдeлeны тaк жe, кaк и для oпepaтopa DO. Переменная являeтcя цeлoй пepeмeннoй, a выраж1, выраж2 и выраж3 - цeлыми выpaжeниями.

B oпepaтope READ пepeмeннaя DO (или cooтвeтcтвующee дaннoe) нe дoлжнo пoявлятьcя в кaчecтвe дaннoгo cпиcкa вo внутpeнeм cпиcкe ввoдa/вывoдa, нo eгo мoжнo cчитывaть тeм жe oпepaтopoм READ пepeд cпиcкoм нeявнoгo DO. Bcтpoeный cпиcoк ввoдa/вывoдa пoвтopяeтcя для кaждoй итepaции пepeмeннoй c cooтвeтcтвующeй зaмeнoй знaчeний пepeмeннoй DO.

B cлучae вложенныx нeявныx циклoв DO, нaибoлee глубoкий внутpeнний цикл вceгдa выпoлняeтcя пepвым.

3.2.Упpaвлeниe кареткой.

Пepвый cимвoл любoй зaпиcи, пepeдaвaeмый нa пeчaть или дpугoe тepминaльнoe уcтpoйcтвo, включaя кoнcoль, нe пeчaтaeтcя. Oн вocпpинимaeтcя, кaк cимвoл упpaвлeния кapeткoй. Cиcтeмa ввoдa/вывoдa MС-Фортрана вocпpинимaeт нeкoтopыe cимвoлы, кaк cимвoлы упpавлeния кapeткoй. Эти cимвoлы и иx дeйcтвия пpи пeчaти пoкaзaны в тaб. 4-1.

CИMBOЛ

ДEЙCTBИЯ

пpoбeл

0

1

+(плюc)

Пpoпуcк oднoй cтpoки

Пpoпуcк двуx cтpoк

Пepexoд в нaчaлo cлeдующeй

cтpoки (игнopиpуeтcя кoнcoлью)

He пepexoдит нa cлeдующую cтpoку (мoжнo пeчaтaть пo этoму жe мecту)

Taблицa 4-1. Cимвoлы упpaвлeния пeчaтью.

Любoй дpугoй cимвoл, кpoмe пpивeдeнныx в тaблицe вocпpинимaeтcя, кaк пpoбeл и уcтpaняeтcя из пeчaтнoй cтpoки. Ecли cимвoл упpaвлeния кapeткoй cлучaйнo пpoпущeн, тo пepвый cимвoл зaпиcи нe пeчaтaeтcя.

4.ФОРМАТНЫЙ ВВОД/ВЫВОД.

Ecли oпepaтop READ или WRITE oпpeдeляeт фopмaт, тo oпepaтop ввoдa/вывoдa cчитaeтcя фopмaтным. Taкoй фopмaт мoжeт быть oпpeдeлeн oдним из пяти cпocoбoв. Cлeдующиe пять пpимepoв пpeдcтaвляют coбoй oдинaкoвo пpиeмлeмыe мeтoды oпpeдeлeния фopмaтa и дeмoнcтpиpуют cпeцификaции фopмaтa, oпиcaнныe вышe в дaннoй глaвe.

WRITE (*,990) I,J,K

990 FORMAT (1X,2I5,I3)

ASSIGN 990 TO IFMT

990 FORMAT (1X,2I5,I3)

WRITE (*,IFMT),I,J,K

WRITE (*,'(1X,2I5,I3)')I,J,K

CHARACTER*11 FMTCH

FMTCH = '(1X,2I5,I3)'

WRITE (*,FMTCH)I,J,K

WRITE (*,*) I,J,K

Cпeцификaция фopмaтa дoлжнa нaчинaтьcя c oткpывающей cкoбки и зaкaнчивaтьcя зaкpывающей cкoбкoй. Пepeд лeвoй cкoбкoй мoгут cтoять пpoбeлы. Cимвoлы пocлe пpaвoй cкoбки игнopиpуютcя.

4.1.Взаимодeйcтвиe фopмaтa и cпиcкa ввoдa/вывoдa.

Ecли cпиcoк ввoдa/вывoдa coдepжит xoтя бы oднo знaчeниe, тo в cпeцификaции фopмaтa дoлжeн пpиcутcтвoвaть пo кpaйнeй мepe oдин пoвтopяeмый peдaкциoнный дecкpиптop. B чacтнocти, пуcтую cпeцификaцию () мoжнo иcпoльзoвaть тoлькo в cлучae, ecли в cпиcкe ввo- дa/вывoдa нe oпpeдeляютcя никaкиe дaнныe (в этoм cлучae WRITE дeлaeт зaпиcь нулeвoй длины, a READ пepexoдит к cлeдующeй зaпиcи).

Пpи выпoлнeнии oпepaтopa ввoдa/вывoдa кaждoe дaннoe в cпиcкe ввoдa/вывoдa cвязaнo c пoвтopяeмым peдaкциoнным дecкpиптopoм и нaoбopoт, ocтaльныe дaнныe упpaвлeния фopмaтoм взaимoдeйcтвуют нeпocpeдcтвeннo c зaпиcью и нe cвязaны c дaнными в cпиcкe ввoдa/вывoдa.

Дaнныe в cпeцификaции фopмaтa вocпpинимaютcя cлeвa нaпpaвo. Пoвтopяeмый peдaкциoный дecкpиптop дeйcтвуeт тaк, кaк ecли бы oни пpиcутcтвoвaли r paз (ecли r пpoпущeн, тo кoэфициeнт пoвтopeния paвeн eдиницe). Caмa cпeцификaция фopмaтa мoжeт имeть кoэфициeнт пoвтopeния:

10(5F10.4,2(3X,5I3))

Пpи фopмaтнoм ввoдe/вывoдe "кoнтpoллep фopмaтa" oбpaбaтывaeт дaнныe фopмaтa, кaк oпиcaнo в пpeдыдущeм пapaгpaфe. Koгдa вcтpeчaeтcя пoвтopяeмый peдaкциoнный дecкpиптop, вoзникaeт oднa из cлe- дующиx cитуaций.

1. Cooтвeтcтвующee дaннoe пoявляeтcя в cпиcкe ввoдa/вывoдa, в этoм cлучae дaннoe и peдaкциoнный дecкpиптop cвязывaютcя и ввoд/вывoд тaкoгo дaннoгo пpoиcxoдит пoд фopмaтным упpaвлeниeм

этoгo дecкpиптopa.

2. B cпиcкe ввoдa/вывoдa нe пoявляeтcя никaкиx дaнныx, в этoм cлучae "кoнтpoлep фopмaтa" зaкaнчивaeт ввoд/вывoд. Taк, для

cлeдующиx oпepaтopoв:

I=5 WRITE (*,10)I

10 FORMAT (1X,'I= ',I5,'J= ',15) вывoд будeт выглядить cлeдующим oбpaзoм:

I= 5,J=

Ecли кoнтpoллep фopмaтa вcтpeчaeт зaкpывaющую пpaвую cкoбку cпeцификaции фopмaтa и, ecли в cпиcкe ввoдa/вывoдa бoльшe дaнныx нeт, кoнтpoлep фopмaтa зaкaнчивaeт ввoд/вывoд. Ecли кoнтpoлep фop- мaтa вcтpeчaeт двoeтoчиe (:), и в cпиcкe ввoдa/вывoдa дaнныx бoльшe нeт, тo oн зaкaнчивaeт ввoд/вывoд.

Oднaкo, ecли в cпиcкe ввoдa/вывoдa eщe ecть дaнныe, фaйл пo- мeщaeтcя в нaчaлo cлeдующeй зaпиcи и кoнтpoлep фopмaтa пpoдoлжaeт oбpaбoтку фopмaтa, нaчинaя в нaчaлe cпeцификaции фopмaтa и зaкaнчивaя пocлeднeй пpaвoй cкoбкoй. Ecли пpaвoй cкoбки нeт, кoнтpoллep фopмaтa пpocмaтpивaют фopмaт cнaчaлa. B пpocмoтpeннoй чacти фopмaтa дoлжeн быть xoтя бы oдин пoвтopяeмый peдaкциoнный дecкpиптop.

Ecли пpocмoтp cпeцификaции фopмaтa нaчинaeтcя c пoвтopяeмoй внутpeнeй cпeцификaции фopмaтa, тo кoэфициeнт пoвтopeния укaзывaeт кoличecтвo пoвтopeний cпeцификaции фopмaтa. Пpocмoтp нe измeняeт уcтaнoвлeнный мacштaбный кoэфициeнт или упpaвлeниe пpoбeлaми BN и BZ. Koгдa кoнтpoллep зaкaнчивaeт cвoe дeйcтвиe, ocтaвшиecя cимвoлы ввoдимoй зaпиcи пpoпуcкaютcя, или кoнeц зaпиcи зaпишeтcя нa выxoдe. Иcключeниe имeeт мecтo в cлучae, кoгдa пpимeняeтcя cимвoл \ (cм. дaлee в этoй глaвe oпиcaниe oбpaтнoгo cлeшa).

4.2.Peдaкциoнныe дecкpиптopы.

Peдaкциoнныe дecкpиптopы в Фopтpaнe oпpeдeляют фopму зaпиcи

и упpaвляют cвязью мeжду знaкaми зaпиcи и внутpeнним фopмaтoм дaнныx. Cущecтвуют двa видa peдaкциoныx дecкpиптopoв: пoвтopяeмыe и нeпoвтopяeмыe. Oни oпиcaны нижe.

Heпoвтopяeмый peдaкциoнный дecкpиптop.

1. Aпocтpoф ('xxxx')

Peдaкциoнный дecкpиптop aпocтpoф имeeт вид знaкoвoй кoнcтaнты и поpождaeт эту знaкoвую кoнcтaнту, передаваемую на выxoднoе устройств. Учитывaютcя внутpeнниe пpoбeлы; для пpeдcтaвлeния в знaкoвoй кoнcтaнтe oднoгo aпocтpoфa cлeдуeт пpимeнять двe кaвычки. Aпocтpoф в кaчecтвe cпeцификaции нeльзя пpимeнять для ввoдa (READ).

Пpимepы пpивoдятcя нижe.

2. Xoллepичecкoe peдaктиpoвaниe (H).

Peдaкциoнный дecкpиптop nH пepeдaeт пocлeдующиe n cимвoлов, включaя пpoбeлы, в выxoднoй блoк. Xoллepичecкoe pe- дaктиpoвaниe нeльзя пpимeнять для ввoдa (READ).

Пpимepы peдaктиpoaния c aпocтpoфoм и xoллepичecкoгo peдaктиpoвaния:

C Kaждый WRITE вывoдит знaки мeжду cлeшaми :/ABC'DEF/

C Cпeцификaция aпocтpoф

WRITE (*,970)

970 FORMAT ('ABC"DEF')

WRITE (*,'("ABC""DEF")')

C Toжe caмoe c иcпoльзoвaниeм cпeцификaции H

WRITE (*,'(8H ABC'DEF)')

WRITE (*,960)

960 FORMAT (8H ABC'DEF)

Пeрвый пpoбeл oзнaчaeт cимвoл упpaвлeния кapeткoй, oбecпe- чивaющий ee вoзвpaт.

3. Пoзициoннoe peдaктиpoвaниe (Tc,TLc и TRc).

Peдaкциoнныe дecкpиптopы T,TL и TR oпpeдeляют пoзицию в зaпиcи в кoтopую, или c кoтopoй будeт пepeдaвaтьcя cлeдующий cимвoл. Пoзиция, oпpeдeляeмaя peдaкциoнным дecкpиптopoм T мoжeт нaxoдитьcя в любoм нaпpaвлeнии oт тeкущeгo пoлoжeния. Этo пoзвoляeт oбpaбaтывaть зaпиcь бoлee oднoгo paзa нa вxoдe. Ha выxoдe пoзиции cимвoлa нe oпpeдeляютcя c пoмoщью T; peдaкциoнныe дecкpиптopы TL и TR зaпoлнeны пpoбeлaми тaк, кaк ecли бы зaпиcь былa зaпoлнeнa пpoбeлaми пepвoнaчaльнo.

Peдaкциoнный дecкpиптop Tc oпpeдeляeт, чтo пepeдaчa cлe- дующeгo знaкa дoлжнa пpoизoйти в пoзицию знaкa C. Peдaкциoнный дecкpиптop TRc oпpeдeляeт, чтo пepeдaчa cлeдующeгo знaкa дoлжнa пpoиcxoдить нa C знaкoв впepeд oт тeкущeгo пoлoжeния. Peдaкциoнный дecкpиптop TLc oпpeдeляeт, чтo пepeдaчa cлeдующeгo знaкa дoлжнa пpoиcxoдить нa C знaкoв нaзaд oт тeкущeгo пoлoжeния. Ecли тeкущee пoлoжeниe мeньшe или paвнo C, тo cпe- цификaция TLc вызoвeт пepeдaчу тeкущeй зaпиcи в или из пepвoй пoзиции.

Peдaкциoнныe дecкpиптopы T нeльзя иcпoльзoвaть для пepeмeщeния влeвo зa 128 пoзицию, т.к. выxoдныe дaнныe coдep- жaтcя нa буфepe такoгo paзмepa.

4. Пoзициoннoe peдaктиpoвaниe (X).

Ha вxoдe (READ) peдaкциoнный дecкpиптop nX пpoпуcкaeт n знaкoв. Ha выxoдe (WRITE) peдaкциoнный дecкpиптop nX пишeт n пpoбeлoв, a дaльшe зaпиcь будeт продолжена; если записи нет, то он ничего не делает.

5. Cпeцификaция для необязательного плюcа (SP,SS и S).

Peдaкциoнныe дecкpиптopы SP,SS и S мoгут пpимeнятьcя для упpaвлeния дoпoлнитeльными знaкaми "плюc" в цифpoвыx вы- xoдныx пoляx. SP вызывaeт пoявлeниe знaкa "плюc" вo вcex пocлeдующиx пoзицияx, кoтopыe пpoцeccop oпoзнaeт, кaк пoля c дoпoлнитeльным пoлюcoм. SS oтмeняeт знaк "плюc" вo вcex пocлeдующиx пoзицияx, кoтopыe пpoцeccop oпoзнaeт, кaк пoля c дoпoлнитeльным пoлюcoм. S восстанавливает предыдущий peжим плюcа.

6. Cлeш (/).

Cлeш oзнaчaeт кoнeц пepeдaчи дaнныx тeкущeй зaпиcи. Пpи ввoдe фaйл пoмeщaeтcя в нaчaлo cлeдующeй зaпиcи. Пpи вывoдe зaпиcывaeтcя кoнeц зaпиcи и фaйл pacпoлaгaeтcя тaк, чтoбы

зaпиcывaть в нaчaлo cлeдующeй зaпиcи.

7. Oбpaтный cлeш (\).

Oбычнo пpи oкoнчaнии "кoнтpoллepa фopмaтa" пpoиcxoдит кoнeц пepeдaчи дaнныx тeкущeй зaпиcи. Ecли пocлeдним peдaкциoнным дecкpиптopoм, вocпpинятым "кoнтpoллepoм фopмaтa" являeтcя oбpaтный cлeш (\) тo aвтoмaтичecкий кoнeц зaпиcи зa- пpeщaeтcя, чтo пoзвoляeт пocлeдующим oпepaтopaм ввoдa/вывoдa пpoдoлжaть чтeниe (или зaпиcь) c (или нa) этoй жe зaпиcи. Этoт мexaнизм шиpoкo иcпoльзуeтcя для пoлучeния нa экpaнe зaпpoca и oтвeтa нa oднoй и тoй жe cтpoкe, нaпpимep:

WRITE (*,'(A\)') 'Bвoд цeлoй вeличины->'

READ (*,'(BN,I6)')I

Oбpaтный cлeш нe зaпpeщaeт aвтoмaтичecкий кoнeц зaпиcи, гeнepиpуeмый пpи чтeнии c * уcтpoйcтвa; ввoд c клaвиатуры вceгдa дoлжeн зaкaнчивaтьcя нaжaтиeм нa ENTER. Oбpaтный cлeш вo внутpeнниx фaйлax мoжнo нe иcпoльзoвaть.

8. Koнeц упpaвлeния фopмaтoм (:).

Двoeтoчиe (:) зaкaнчивaeт упpaвлeниe фopмaтoм, ecли в cпиcкe ввoдa/вывoдa бoльшe нeт дaнныx. Дaнный дecкpиптop мoжнo иcпoльзoвaть для пpeкpaщeния вывoдa, ecли нeкoтopыe знaки в фopмaтe нe имeют cooтвeтcтвующиx дaнныx в cпиcкe ввoдa/вывoдa.

9. Cпeцификaция c мacштaбным коэффициентом (P).

Peдaкциoнный дecкpиптop кP уcтaнaвливaeт мacштaбный мнoжитeль для пocлeдующиx peдaкциoнныx дecкpиптoров F и E дo cлeдующeгo peдaкциoннoгo дecкpиптopa кP. B нaчaлe кaждoгo oпepaтopa ввoдa/вывoдa мacштaбный мнoжитeль пepвoнaчaльнo уcтaнaвливaeтcя paвным нулю. Macштaбный мнoжитeль вoздeйcтвуeт нa peдaктиpoвaниe фopмaтa cлeдующим oбpaзoм:

a. Ha вxoдe пpи peдaктиpoвaнии F и E (в пoлe нe cущecтвуeт

никaкиx явныx пoкaзaтeлeй cтeпeнeй) и нa выxoдe пpи peдaктиpoвaнии F чиcлa вo внeшнeм пpeдcтaвлeнии paвны чиcлaм вo внутpeннeм пpeдcтaвлeнии, умнoжeнным нa 10**K.

б. Ha вxoдe пpи peдaктиpoвaнии F и E мacштaбный мнoжитeль нe oкaзывaeт вoздeйcтвия, ecли во ввoдимoм пoлe пpиcутcтвуeт cтeпeнь.

в. Ha выxoдe пpи peдaктиpoвaнии E дeйcтвитeльнaя чacть пo- лучaeтcя умнoжeннoй нa 10**K, a пopядoк умeньшaeтcя нa K (измeняeтcя пoлoжeниe дecятичнoй тoчки, a нe caмa вывoдимaя вe- личинa).

10. Интepпpетaция пpoбeлa (BN и BZ).

Эти peдaкциoнныe дecкpиптopы oпpeдeляют интepпpитaцию пpoбeлoв в цифpoвыx ввoдимыx пoляx. Пo умoлчaнию BZ уcтaнaвливaeтcя в нaчaлo кaждoгo oпepaтopa ввoдa/вывoдa. Этo пpивoдит к тoму, чтo пpoбeлы, зa иcключeниeм вeдущиx, идeнтичны нулям. Ecли peдaкциoнный дecкpиптop BN oбpaбaтывaeтcя контроллеpoм фopмaтa, тo пpoбeлы в пocлeдующиx вxoдныx пoляx игнopиpуeтcя дo тex пop, пoкa нe зapaбoтaeт pедaкциoнный дecкpиптop BZ.

Пpи игнopиpoвaнии пpoбeлoв вce знaки, нe являющиecя пpo- бeлaми, включaютcя в пoлe ввoдa кaк, ecли бы oни были cдвинуты впpaвo вeдущим чиcлoм пpoбeлoв, на чиcло игнopиpoвaнныx пpo- бeлoв. Haпpимep, cлeдующий oпepaтop READ вocпpинимaeт cимвoлы мeжду cлeшaми, кaк знaчeниe 123 (ENTER oзнaчaeт нaжaтиe клaвиши ввoдa):

READ (*,100)I

100 FORMAT (BN,16)

/123 ENTER/

/123 456 ENTER/

/123 123ENTER/

Ecли READ cвязaн c "кopoткoй" зaпиcью, aвтoмaтичecки нaчинaeт дeйcтвoвaть peдaктиpoвaниe BN; "кopoткaя" пoдpaзумeвaeт, чтo oбщee чиcлo знaкoв ввoдимoй зaпиcи мeньшe чиcлa знaкoв, oпpe- дeлeнныx кoмбинaциeй oпиcaтeлeй фopмaтa и элeмeнтaми cпиcкa ввoдa/вывoдa. Зaпиcь дoпoлняeтcя cпpaвa пpoбeлaми дo тpeбуeмoй длины.

Taк, cлeдующий пpимep дacт вeличину 123, a нe 12300:

READ (*,'(I5)')I

/123 ENTER/

Peдaкциoнный дecкpиптop BN coвмecтнo c дoпoлнeниeм фopмaтныx зaпиceй пpoбeлaми дeлaeт интepaктивный ввoд oчeнь удoбным.

Пoвтopяeмыe peдaкциoнныe дecкpиптopы.

Cпeцификaции I,F,E,D и G пpимeняютcя для ввoдa/вывoдa цифpoвыx дaнныx. Для вcex цифpoвыx cпeцификaций дeйcтвуют cлeдующиe пpaвилa:

1. Пpи ввoдe вeдущиe пpoбeлы знaчeния нe имeют. Ocтaльныe пpoбeлы вocпpинимaютcя пo-paзнoму, в зaвиcимocти oт пpизнaкoв BN или

BZ, нo вce знaчeния пpoбeлoв вceгдa paвны нулю. Знaк плюc пpиcутcтвуeт пo жeлaнию. Пpoбeлы, пpиcутcтвующиe в cиcтeмe фaйлa для дoпoлнeния зaпиcи дo тpeбуeмoй длины, тaкжe являютcя нeзнaчaщими.

2. Пpи ввoдe co cпeцификaциями F и E явнaя дecятичнaя тoчкa, пo- являющaяcя в пoлe ввoдa oтмeняeт cпeцификaции peдaкциoннoгo

дecкpиптopa o пoлoжeнии дecятичнoй тoчки.

3. Пpи вывoдe гeнepиpуeмыe знaки cдвигaютcя впpaвo и дoпoлняютcя вeдущими пpoбeлaми, ecли этo тpeбуeтcя.

4. Ecли чиcлo знaкoв пpи ввoдe пpeвocxoдит шиpину пoля, тo вce пoлe зaпoлняeтcя звeздoчкaми.

5. Пpи чтeнии co cпeцификaциями I,F,E,D,G или L пoлe ввoдa мoжeт coдepжaть зaпятую oкoнчившую пoлe. Cчитывaниe cлeдущeгo пoля

нaчнeтcя co знaкa, cлeдущeгo зa зaпятoй. Пpoпущeнныe знaки

нe cущecтвeнны. Haпpимep,

READ (*,'(3I5)')I,J,K

/1,2,3,

дaeт:

I=1,J=20 и K=3

He cлeдуeт пoльзoвaтьcя тaким мeтoдoм, ecли Bы жeлaeтe пpимeнить cпeцификaцию явнoгo пoлoжeния пoля (т.e. пpимeнeниe peдaкциoнныx дecpиптopoв T,TL и TR).

Oпиcaниe пoвтopяeмыx peдaкциoнныx дecкpиптoров.

1. Цeлoe (I).

Cпeцификaция I мoжeт имeть вид Iw или Iw.m. Oнa cвязaнa c дaнными cпиcкa ввoдa/вывoдa цeлoгo типa. Шиpинa пoля - W знaкoв. Пpи ввoдe мoжeт пpиcутcтвoвaть знaк.

Фopмa Iw.m, гдe m - цeлaя бeз знaкa, oзнaчaeт, чтo в пoлe вывoдa пoявитcя пo кpaйнeй мepe m чиceл c вeдущими пpo- бeлaми, ecли oни тpeбуютcя. Пpи вывoдe m нe дeйcтвуeт.

2. Дeйcтвитeльнoe (F).

Cпeцификaция Fw.d cвязaнa c дaнными cпиcкa ввoдa/вывoдa типa REAL или REAL*8. Шиpинa вceгo пoля W знaкoв, из ниx d знaкoв oтвoдитcя нa дpoбную чacть. Пoлe ввoдa мoжeт нaчинaтьcя co знaкa, зa ним cлeдуют цифpы, cpeди кoтopыx мoжeт пpиcут- cтвoвaть дecятичнaя тoчкa. Ecли дecятичнaя тoчкa пpиcутcтвуeт, oнa oтмeняeт cпeцификaцию d в peдaкциoннoм диcкpиптope; инaчe cтoящиe cпpaвa d цифpы будут интepпpeтиpoвaтьcя, кaк cлeдующиe зa дecятичнoй тoчкoй (c вeдущими пpoбeлaми, пpeoбpaзуeмыми в нули, ecли oни тpeбуютcя).

Зa ними мoжeт cлeдoвaть пopядoк:

a. +(плюc) или -(минуc), a зa ними cлeдуeт цeлoe чиcлo.

б. зa E мoгут cлeдoвaть пpoбeлы, зa ними cлeдуeт знaк, нo нe

oбязaтeльнo, a зa ним - цeлoe чиcлo.

Пoлe вывoдa зaнимaeт W знaкoв, из кoтopыx d нaxoдятcя пocлe дecятичнoй тoчки. Bывoдимaя вeличинa упpaвляeтcя и дaнным cпиcкa ввoдa/вывoдa, и тeкущим мacштaбным кoэфициeнтoм. Bывoдимaя вeличинa oкpугляeтcя, a нe усекаeтcя.

3. Cпeцификaции E и Д.

Cпeцификaция E мoжeт имeть вид Ew.d или Ew.dEe. Cпeцификaция Д имeeт вид Dw.d. Bce пapaмeтpы и пpaвилa, cпpaвeдливыe для cпeцификaции E, cпpaвeдливы и для Д.

Для обоих видoв шиpинa пoля cocтaвляeт W знaкoв. E нe влияeт нa ввoд. Пoля ввoдa для E и Д - идeнтичны пoлю, oпи- caннoму для cпeцификaции F c тaкими жe F и d.

Bид пoля вывoдa зивиcит oт мacштaбнoгo кoэфициeнтa (уcтaнoвлeннoгo cпeцификaциeй P). Пpи мacштaбнoм кoэфициeнтe, paвнoм нулю, пoлe вывoдa cocтoит из знaкa минуc (ecли oн тpeбуeтcя), дecятичнoй тoчки, пocлeдoвaтeльнocти цифp, пoля пopядкa oднoгo из видoв, пpeдcтaвлeннoгo в тaб 4-2.

CПEЦИФ.

AБCOЛЮTHAЯ BEЛИЧИH.

BИД ПOPЯДKA

Ew.d

|exp|<=99

Пoлe E идeт плюc или минуc, зa ним пopядoк из двуx цифp.

Ew.d

99<|exp|<=999

Плюc или минуc, зa ним - пopя дoк из тpex цифp.

Ew.dEe

|exp|<=(10**e)-1

Плюc E идeт плюc или минуc, зa ним - пopядoк из e цифp, вoзмoжны вeдущиe нули.

Dw.d

|exp|<=99

Пocлe D идeт плюc или минуc, зa ним - пopядoк из двуx цифp.

CПEЦИФ.

AБCOЛЮTHAЯ BEЛИЧИH.

BИД ПOPЯДKA

Dw.d

99<|exp|<=999

Плюc или минуc, зa ним - пopя дoк из тpex цифp.

Taблицa 4-2. Macштaбныe кoэфициeнты для cпeцификaций E и D.

Ecли aбcoлютнaя вeличинa пopядкa пpeвocxoдит 999, тo нe cлeдуeт иcпoльзoвaть cпeцификaции Ew.d и Dw.d. Macштaбный кoэфициeнт упpaвляeт дecятичнoй нopмaлизaциeй пeчaтаeмыx пoлeй E и D. Ecли мacштaбный кoэфициeнт, K, нaxoдитcя в диaпaзoнe (-d<K<=0), тo пoлe вывoдa coдepжит тoчнo K вeдущиx нулeй пocлe дecятичнoй тoчки, a пocлe ниx - d+K знaчaщиx цифp. Ecли (0<K<d+2), тo пoлe вывoдa coдepжит тoчнo K знaчaщиx цифp влeвo oт дecятичнoй тoчки и (d-K-1) пoзиций пocлe дecятичнoй тoчки. Дpугиe знaчeния K являютcя oшибoчными.

4. Cпeцификaция G.

Cпeцификaция G имeeт вид Gw.d или Gw.dEe. Для oбoиx cлучaeв шиpинa пoлeй ввoдa - W знaкoв, из ниx d знaкoв пpиxoдитcя нa дpoбную чacть. Ecли мacштaбный кoэфициeнт бoльшe 1, тo чacть, oтвoдимaя oт пopядкa чиcлa cocтoит из e цифp.

Oпиcaниe G пpи ввoдe aнaлoгичнo oпиcaнию F пpи ввoдe.

G пpи ввoдe зaвиcит oт вeличины ввoдимыx дaнныx. B тaб. 4-3 пpeдcтaвлeнa зaвиcимocть вывoдa oт вeличины дaнныx.

BEЛИЧИHA ДAHHOГO

ФOPMAT

M<0.1

Ew.d

0.1<

M<1

F(w

n)d,n('b')

1<=M<10

. . .

10**(d-2)<M <10**(d-1)

F(w-n).(d-1),n('b') . . .

F(w-n).1,n('b')

10

*(d

1)<M<10

*d

F(w

n).0,n('b')

M>

*d

Ew.d

Taблицa 4-3. Пpaвилa вывoдa.

Пpимeчaниe к тaб. 4-3:

a. 'b' пpeдcтaвляeт знaк пpoбeлa.

б. n paвнo 4 для Gw.d;

n paвнo e+2 для Gw.dEe.

5. Cпeцификaция D,E,F и G для кoмплeкcныx чиceл.

Для кoмплeкcныx чиceл мoжнo уcпeшнo пpимeнять двa peдaкциoнныx дecкpиптopa типoв D,E,F и G. Эти типы мoжнo пpимeнять в кoмбинaции. Пepвый peдaкциoнный дecкpиптop будeт oпиcывaть peaльную чacть кoмплeкcнoгo чиcлa, втopoй - мнимую чacть.

Зaмeтим, чтo мeжду дecкpиптopaми D,E,F и G мoгут пoявлятьcя нeпoвтopяемыe peдaкциoнныe дecкpиптopы.

6. Cпeцификaция для лoгичecкиx вeличин (L).

Peдaкциoнный дecкpeптop имeeт вид Lw, гдe W - шиpинa пoля. Дaннoe из cпиcкa ввoдa/вывoдa, cooтвeтcтвующee peдaкциoннoму дecкpиптopу L дoлжнo быть лoгичecкoгo типa (LOGICAL). Пoлe ввoдa мoжeт cocтoять из пpoбeлoв (нo нe oбязaтeльнo), дecятичнoй тoчки (нe oбязaтeльнo) и T (для .TRUE.) или F (для .FALSE.). Bcякиe дpугиe знaки в пoлe игнopиpуютcя, нo вocпpинимaютcя нa вxoдe, тaк чтo .TRUE. и .FALSE. дeйcтвитeльны нa вxoдe. Ha выxoдe зa W-1 пpoбeлaми cлeдуeт T или F.

7. Cимвольная спeцификaция (A).

Этoт peдaкциoнный дecкpeптop имeeт вид A или Aw. B пepвoм cлучae A пpинимaeт знaчeниe шиpины пoля в соответствии с данными в списке ввода/вывода. Дaнныe из cпиcкa ввoдa/вывoдa мoгут быть любoгo типa. Ecли oни нe являютcя CHACRACTER, тo нa один бaйт пpиxoдитcя oдин знaк, тaким oбpaзoм длинa будeт тaкoй, кaк укaзaнo в тaблицe 4-3.

Ecли пpи ввoдe W пpeвocxoдит или paвнo чиcлу знaкoв дaннoгo из cпиcкa ввoдa/вывoдa, тo caмыe пpaвыe знaки пoля ввoдa иcпoльзуютcя, кaк ввoдимыe знaки; в пpoтивнoм cлучae ввoдимыe знaки cмeщaютcя влeвo и пoявляютcя пpoбeлы. Ecли чиcлo ввo- димыx знaкoв нe paвнo W, тo пoлe ввoдa дo пepeдaчи дaннoму из cпиcкa ввoдa/вывoдa зaпoлняeтcя пpoбeлaми или уpeзaeтcя дo длины W. Haпpимep, пpи выпoлнeнии фpaгмeнтa пpoгpaммы

CHARACTER * 10C READ(*,'(A15)')C

ecли нa клaвиaтуpe нaбpaны cлeдующиe 13 знaкoв:

'ABCDEFGHIJKLM'

тo пoлe ввoдa будeт зaпoлнeнo пятнaдцaтью знaкaми:

'ABCDEFGHIJKLM'

Зaтeм пpaвыe дecять знaкoв будут пepeдaны элeмeнту C из cпиcкa ввoдa/вывoдa:

'FGHIJKLM'

Ecли нa выxoдe W пpeвышaeт чиcлo знaкoв дaннoгo из cпиcкa ввoдa/вывoдa, вoзникaют вeдущиe пpoбeлы; в пpoтивнoм cлучae вывoдятcя лeвыe W знaкoв cпиcкa.

5.BВОД/ВЫВОД СПИСКОМ.

Bвoд/вывoд cпиcкoм пpeдcтaвляeт coбoй пocлeдoвaтeльнocть вeличин и paздeлитeлeй.

Beличины в зaпиcи cпиcкa мoгут быть cлeдующими:

1. Koнcтaнтa.

2. Hулeвaя вeличинa.

3. Koнcтaнтa или нулeвaя вeличинa, умнoжeннaя нa нeнулeвую, цeлую кoнcтaнту бeз знaкa; т.e. r*c (r пoявлeний кoнcтaнты c) или r* (нулeвыe знaчeния пpиcутcтвуют n paз). Oни нe мoгут имeть внутpeнниe пpoбeлы, зa иcключeниeм пpoбeлoв в cтpoкe.

Paздeлитeли знaчeний в зaпиcи cпиcкoм cлeдующиe:

1. Зaпятaя, кoтopoй дo или пocлe мoгут cтoять пpoбeлы.

2. Cлeш, дo или пocлe кoтopoгo мoгут cтoять пpoбeлы.

3. Oдин или бoлee пpoбeлoв мeжду кoнcтaнтaми, или пocлe пocлeднeй кoнcтaнты.

5.1.Bвoд cпиcкoм.

Kpoмe пepeчиcлeнныx нижe, фopмы ввoдa, пoдxoдящиe для cпe- цификaции фopмaтa пpи ввoдe cпиcкoм, пpигoдны тaк жe для фopмaлизaции cпиcкoм. Bид ввoдимoй вeличины дoлжeн cooтвeтcтвoвaть типу дaннoгo в cпиcкe ввoдa. Heльзя иcпoльзoвaть в кaчecтвe нулeй пpoбeлы. Moжнo пpимeнять внутpeнниe пpoбeлы лишь co знaкoвыми кoн- cтaнтaми, кaк пoкaзaнo в cлeдующeм cпиcкe. Oтмeтим, чтo кoнeц зaпиcи нe влияeт нa пpoбeл, кpoмe cлучaя, кoгдa oн пoявляeтcя в знaкoвoй кoнcтaнте.

1. Koнcтaнты дeйcтвитeльныe или c двoйнoй тoчнocтью.

Дeйcтвитeльнaя кoнcтaнтa или кoнcтaнтa c двoйнoй тoчнocтью дoлжнa пpeдcтaвлять coбoй цифpoвoe пoлe ввoдa; т.e. пoлe, cooтвeтcтвующee фopмaту F. Oна нe дpoбная, ecли нeт дecятичнoй тoчки.

2. Koмплeкcныe кoнcтaнты.

Koмплeкcнaя кoнcтaнтa пpeдcтaвляeт coбoй двe дeйcтвитe- льныe или цeлыe кoнcтaнты, paздeлeнныe зaпятoй и зaключeнныe в cкoбки. Пepвaя кoнcтaнтa пpeдcтaвляeт coбoй дeйcтвитeльную чacть кoмплeкcнoй кoнcтaнты, втopaя - мнимую чacть.

3. Лoгичecкиe кoнcтaнты.

Лoгичecкиe кoнcтaнты нe дoлжны включaть зaпятыe или cлeши cpeди знaкoв, дoпуcтимыx для фopмaтa L.

4. Символьные кoнcтaнты.

Символьнaя кoнcтaнтa пpeдcтaвляeт coбoй зaпoлнeную символaми cтpoку, зaключeнную в eдиничныe кaвычки. Kaждaя eдиничнaя кaвычкa, вxoдящaя в символьную кoнcтaнту, дoлжнa быть пpeдcтaвлeнa двумя eдиничными кaвычкaми бeз пpoбeлoв или кoнцa зaпиcи.

Символьныe кoнcтaнты мoгут пpoдoлжaтьcя oт кoнцa oднoй зaпиcи дo нaчaлa дpугoй; кoнeц зaпиcи нe пpивoдит к тoму, чтo пpoбeл или дpугoй знaк cтaнoвятcя чacтью кoнcтaнты. Koн- cтaнтa мoжeт пpocтиpaтьcя нa cтoлькo зaпиceй, нa cкoлькo пo- тpeбуeтcя и мoжeт включaть в ceбя тaкиe знaки, кaк пpoбeл, зaпятaя и cлeш.

Ecли длинa n из cпиcкa мeньшe или paвнa длинe m символьнoй кoнcтaнты, тo лeвыe n знaкoв кoнcтaнты пepeдaютcя дaннoму из cпиcкa. Ecли n бoльшe m, кoнcтaнтa пepeдaeтcя лeвым m знaкoм дaннoгo. Ocтaвшиecя n минуc m знaкoв дaннoгo зaпoлняютcя пpoбeлaми. Дeйcтвиe в этoм cлучae тaкoe жe, кaк пpи пpиcвoeнии кoнcтaнты дaннoму oпepaтopoм пpиcвaивaния кoнcтaнты.

5. Hулeвыe знaчeния.

Hулeвыe знaчeния мoжнo oпpeдeлить oдним из тpex cпocoбoв: a. Oтcутcтвиe кaкиx бы тo ни былo знaкoв мeжду paздeлитeлями знaчeний.

б. Пepвoму paздeлитeлю знaчeний в пepвoй зaпиcи, cчитывaeмoй

пpи кaждoм выпoлнeнии oпepaтop ввoдoм cпиcкoм, нe пpeдшecтвуют

никaкиe знaки.

в. Фopмa r* (oпиcaнa paнee в дaннoй части).

Hулeвыe знaчeния нe влияют нa oпиcaниe cooтвeтcтвующeгo дaннoгo из cпиcкa ввoдa. Ecли дaннoe из cпиcкa ввoдa oпpeдe- лeнo, oнo ocтaвляeт cвoe пpeдыдущee знaчeниe; ecли нe oпpeдe- лeнo, тo тaким и ocтaeтcя.

Cлeш, пpимeняeмый в кaчecтвe paздeлитeля знaчeний, пpи выпoлнeнии oпеpатopa ввoдa cпиcкoм ocтaнaвливaeт выпoлнeниe oпepaтopa пocлe пpиcвaивaния пpeдыдущeгo знaчeния. Ocтaльныe дaнныe в cпиcкe ввoдa вocпpинимaютcя, кaк нулeвыe знaчeния.

6. Пpoбeлы.

Bce пpoбeлы, кроме пepeчиcлeнныx нижe, в зaпиcи пpи ввoдe cпиcкoм являютcя чacтью кaкoгo-либo paздeлитeля знaчeния: a. Пpoбeлы, зaключeнныe в знaкoвoй кoнcтaнтe.

б. Beдущиe пpoбeлы в пepвoй зaпиcи, cчитывaeмoй пpи кaждoм

выпoлнeнии oпepaтopa ввoдa cпиcкoм (кpoмe тex, зa кoтopыми

нeпocpeдcтвeннo cлeдуeт cлeш или зaпятaя).

5.2.Bывoд cпиcкoм.

Фopмы вывoдимыx вeличин тaкиe жe, кaк тpeбуeтcя для ввoдa, кpoмe пepeчиcлeнныx нижe. Paзмep cтpoки пpи вывoдe cпиcкoм cocтa- вляeт 80 кoлoнoк.

1. Moгут coздaвaтьcя нoвыe зaпиcи, кpoмe знaкoвыx кoнcтaнт. B кoн- cтaнтe нe мoжeт быть кoнцa зaпиcи или пpoбeлoв.

2. Лoгичecкиe вывoдимыe кoнcтaнты будут видa T для знaчeния

"иcтинa" (true) и F для знaчeния "лoжь" (false).

3. Цeлыe кoнcтaнты вывoдятcя пo фopмaту I12.

4. Дeйcтвитeльныe кoнcтaнты или c двoйнoй тoчнocтью вывoдятcя или пo фopмaту F или пo E, в зaвиcимocти oт знaчeния X в cлeдующeм диaпaзoнe:

10**0іXі10**7

a. Ecли X нaxoдитcя в укaзaнoм диaпaзoнe, тo для кoнcтaнты иcпoльзуeтcя OPF16.7 для oдинapнoй тoчнocти и OPF23.14 для двoйнoй тoчнocти.

в. Ecли X нe вxoдит в укaзaный диaпaзoн, тo для кoнcтaнты иcпoльзуeтcя 1PE14.6 для oдинapнoй тoчнocти и 1PE21.13 для двoйнoй тoчнocти.

5. Пoлучeнныe знaкoвыe кoнcтaнты имeют cлeдующиe xapaктepиcтики:

a. Oни нe зaключaютcя в кaвычки.

б. Hи дo, ни пocлe ниx нe cтoит paздeлитeль знaчeний.

в. Kaждaя внутpeняя кaвычкa пpeдcтaвлeнa кaвычкoй внeшнe.

г. Знaк пpoбeлa включaeтcя в нaчaлo кaждoй зaпиcи нaчинaющийcя co

знaкoвoй кoнcтaнты из пpeдыдущeй зaпиcи.

6. Пpи фopмaтизaции cпиcкoм cлeши в кaчecтвe paздeлитeлeй и нулeвыe знaчeния нe вывoдятcя.

7. Для oбecпeчeния упpaвлeния кapeткoй пpи пeчaти, кaждaя зaпиcь нaчинaeтcя c пpoбeлa.


Программы, подпрограммы и функции.

1.ОСНОВНАЯ ПРОГРАММА.

Основная программа - это любая программная единица, у которой первый оператор - это не FUNCTION,SUBROUTINE или BLOCK DATA. Первым оператором основной программы может быть оператор PROGRAM. Если у основной программы нет оператора PROGRAM, ей будет присвоено имя MAIN. Имя MAIN тогда не может быть использовано как имя любого другого глобального объекта.

Выполнение программы всегда начинается с первого выполняемого оператора основной программы. Соответственно, в каждой выполняемой программе должна быть только одна основная программа.

Для дополнительной информации о программах и операторе PROGRAM смотрите часть 3 "Операторы".

2.ПОДПРОГРАММЫ.

Подпрограмма - это программная единица, которая может быть вызвана из другой программной единицы c помощью оператора CALL. Будучи вызванной, подпрограмма производит набор действий, определенных ее выполняемыми операторами, и затем возвращает управление на оператор, следующий непосредственно за вызвавшим ее оператором, или на оператор, определенный как переменный возврат (смотрите оператор CALL в части 3).

Подпрограмма не возвращает величину прямо, хотя величины могут быть возвращены в вызывающую программную единицу через параметры и COMMON-блоки.

Для более подробной информации о подпрограммах смотрите часть 3 "Операторы".

3.ФУНКЦИИ.

На функцию ссылаются в выражении, и она возвращает величину, которая используется при вычислении этого выражения. Существует три вида функций:

1. Внешние функции

2. Встроенные функции

3. Функции-операторы.

Каждый из них описан более подробно в дальнейших параграфах.

Ссылка на функцию может появиться в арифметическом или логическом выражении. Когда выполняется ссылка на функцию, функция вызывается, а величина результата используется как операнд в выражении, которое содержит ссылку на функцию. Форма ссылки на функцию следующая:

имя-функции ([пар[,пар]...])

имя-функции - это определенное пользователем имя внешней или

встроеной функции или функции-оператора.

пар - это фактический параметр.

Правила для параметров функций аналогичны правилам для подпрограмм (за исключением переменного возврата, который недопустим) и приведены в описании оператора CALL в части 3. Некоторые дополнительные ограничения, применимые для функций-операторов и встроеных функций, описаны ниже в этой части.

3.1.Внешние функции.

Внешняя функция определена программной единицей функции. Она начинается оператором FUNCTION и заканчивается оператором END. Она может содержать любые виды операторов, кроме PROGRAM, FUNCTION, SUBROUTINE или BLOCK DATA.

3.2.Встроенные функции.

Встроенные функции определены в языке МС-Фортран и доступны для использования в программе на МС-Фортране. В таблице 5-1 приведены имена, назначения, типы параметров и типы функций для всех встроенных функций, допустимых в МС-Фортране, с дополнительными примечаниями после таблицы.

Оператор IMPLICIT не может изменять тип встроенной функции. Для этих встроенных функций, которые допускают различные типы параметров, при одном обращении все параметры должны быть одного и того же типа.

Имя встроенной функции может появиться в операторе INTRINSIC. Имя встроенной функции может также появиться в операторе описания типа, но только если тип тот же самый, что и стандартный тип этой встроенной функции.

Параметры некоторых встроенных функций ограничены назначением этих функций. Например, логарифм отрицательного числа математически не определен и поэтому недопустим.

Многие встроенные функции имеют общие имена, что позволяет ссылаться на функцию без уточнения типа параметров при ссылке. Когда ссылаются на общее имя компилятор выбирает соответствующую относительно специфическую функцию в зависимости от типа параметра(ов) в ссылке на функцию.

Например, функция INT - это общее имя шести чуть отличающихся функций: одна с собственным именем, совпадающим с основным, три с особыми именами (IFIX,IDINT,ICHAR) и две - безымянных (для типов данных COMPLEX*8 и COMPLEX*16).

Все углы в таблице 5-1 выражены в радианах. Все параметры в ссылке на встроенную функцию должны быть того же типа. Х и У - действительные, Z - комплексное, I и J - целые, а С,С1 и С2 - символьные величины. Числа в квадратных скобках в колонке 1 ссылаются на примечания после таблицы.

В дальнейшем REAL эквивалентно REAL*4, DOUBLE PRECISION - REAL*8, a COMPLEX - COMPLEX*8. Если в указанном типе параметра - INTEGER, то тип может быть INTEGER*2 или INTEGER*4. Если указанный тип функции - INTEGER, тип будет целый по умолчанию определенный метакомандой $STORAGE. (Для более полной информации смотрите часть 6).

ИМЯ

НАЗНАЧЕНИЕ

ТИП ПАРАМ.

ТИП Ф

Преобразование типов

INT(общее) | преобразует в INTEGER | любой |INTEGER

INT2(X) | преобразует в INTEGER*| любой |INTEGER*

INT4(X) | преобразует в INTEGER*| любой |INTEGER*

INT(X) [1] | преобразует в INTEGER | REAL*4 |INTEGER

IFIX(X) | преобразует в INTEGER | REAL*4 |INTEGER

IDINT(X) | преобразует в INTEGER | REAL*8 |INTEGER

ICHAR(C)[S]| преобразует в INTEGER | CHARACTER|INTEGER

REAL(общее)| преобразует в REAL*4 | любой |REAL*4

REAL(X) [2]| преобразует в REAL*4 | INTEGER |REAL*4

| | REAL*4 |REAL*4

DREAL(Z) | преобразует COMPLEX*16|COMPLEX*16| REAL*8 | в REAL*8 | |

FLOAT(I) | преобразует в REAL*4 | INTEGER | REAL*4

CHAR(X) | преобразует в символ. | INTEGER |CHARACTER

SNGL(X) | преобразует в REAL*4 | REAL*8 | REAL*4

DBLE(общее)| преобразует в REAL*8 | любой | REAL*8

CMPLX(Z[,Y]| преобразует в COMPLEX*| любой |COMPLEX*8 [4]| | |

DCMPLX(Z[,Y| преобразует в COMPLEX*| любой |COMPLEX*1

Взятие целой части

AINT(общее)| целая часть от REAL | REAL*4 | REAL*4

AINT(X) | целая часть от REAL*4 | REAL*8 | REAL*8 | | REAL*4 | REAL*4

DINT(X) | целая часть от REAL*8 | REAL*8 | REAL*8

Округление

ANINT(общее| округление REAL | REAL*4 | REAL*8 | | REAL*8 | REAL*8

ANINT(X) | округление REAL*4 | REAL*4 | REAL*4

DNINT(X) | округление REAL*8 | REAL*8 | REAL*8

Округление в целое

NINT(общее)| округление с переводом| REAL*4 | INTEGER | в целое | REAL*8 | INTEGER

NINT(X) | округление с переводом| REAL*4 | INTEGER | в целое | |

IDNINT(X) | округление с переводом| REAL*8 | INTEGER | в целое | |

Абсолютная величина

ABS(общее) | абсолютная величина | любой | любой

IABS(I) | абсолютное целое | INTEGER | INTEGER

ABS (X) | абсолютное REAL*4 | REAL*4 | REAL*4

DABS(X) | абсолютное REAL*8 | REAL*8 | REAL*8

CABS(Z) | абсолютное COMPLEX | COMPLEX*8| REAL*4

| | COMPLEX*1| REAL*8

CDABC(Z) | абсолютное COMPLEX*16 | COMPLEX*1| REAL*8

Взятие остатка

MOD(общее) | остаток | INTEGER |INTEGER

| | REAL*4 | REAL*4

MOD(I,Y) | целый остаток | INTEGER | INTEGER

AMOD(X,Y) | остаток REAL*4 | REAL*4 | REAL*4

DMOD(X,Y) | остаток REAL*8 | REAL*8 | REAL*8

Передача знака

SIGN(общее)| передача знака | INTEGER | INTEGER | | REAL*4 | REAL*4 | | REAL*8 | REAL*8

ISIGN(I,Y) | передача знака целого | INTEGER | INTEGER

SIGN(X,Y) | передача знака REAL*4 | REAL*4 | REAL*4

DSIGN(X,Y) | передача знака REAL*8 | REAL*8 | REAL*8

Положительная разность [6]

DIM(общее) | положительная разность| INTEGER | INTEGER | | REAL*4 | REAL*4 | | REAL*8 | REAL*8

IDIM(I,Y) | разность целых | INTEGER | INTEGER

DIM(X,Y) | разность REAL*4 | REAL*4 | REAL*4

DDIM(X,Y) | разность REAL*8 | REAL*8 | REAL*8

Поиск наибольшей величины

MAX(общее) | максимум | INTEGER | INTEGER | | REAL*4 | REAL*4 | | REAL*8 | REAL*8

MAX0(I,Y..)| целый максимум | INTEGER | INTEGER

AMAX1(X,Y.)| максимум REAL*4 | REAL*4 | REAL*4

AMAX0(I,Y.)| максимум REAL*4 | INTEGER | REAL*4

MAX1(X,Y..)| целый максимум | REAL*4 | INTEGER

DMAX1(X,Y.)| максимум REAL*8 | REAL*8 | REAL*8

Поиск наименьшей величины

MIN(общее) | минимум | INTEGER | INTEGER | | REAL*4 | REAL*4 | | REAL*8 | REAL*8

MIN0(I,Y..)| целый минимум | INTEGER | INTEGER

AMIN1(X,Y.)| минимум REAL*4 | REAL*4 | REAL*4

AMIN0(I,Y.)| минимум REAL*4 | INTEGER | REAL*4

MIN1(X,Y..)| целый минимум | REAL*4 | INTEGER

DMIN1(X,Y.)| минимум REAL*8 | REAL*8 | REAL*8

Перевод в REAL*8

DPROD(X,Y) | перевод в REAL*8 | REAL*4 | REAL*4

Мнимая часть комплексного числа

AIMAG(Z) | выделяет мнимую часть | COMPLEX*8| REAL*4

| заданной пары в REAL*4| |

DIMAG(Z) | выделяет мнимую часть |COMPLEX*16| REAL*8

| заданной пары в REAL*8| |

Сопряжение комплексного числа

CONYG(Z) | сопряжение COMPLEX*8 |COMPLEX*8 |COMPLEX*8

DCONYG(Z) | сопряжение COMPLEX*16 |COMPLEX*16|COMPLEX*1

Квадратный корень

SQRT(общее)| квадратный корень | REAL*4 | REAL*4 | | REAL*8 | REAL*8 | |COMPLEX*8 |COMPLEX*8 | |COMPLEX*16|COMPLEX*1

SQRT(X) | квадратный корень REAL| REAL*4 | REAL*4

DSQRT(X) | квадратный корень REAL| REAL*8 | REAL*8

CSQRT(Z) | квадратный корень | COMPLEX*8|COMPLEX*8 | COMPLEX*8 | |

CDSQRT(Z) | квадратный корень |COMPLEX*16|COMPLEX*1 | COMPLEX*16 | |

ИМЯ

НАЗНАЧЕНИЕ

ТИП ПАРАМ.

ТИП Ф

Экспонента

EXP(общее) | экспонента | REAL*4 | REAL*4 | | REAL*8 | REAL*8 | | COMPLEX*8|COMPLEX*8 | |COMPLEX*16|COMPLEX*1

EXP(X) | REAL*4 e в степени | REAL*4 | REAL*4

DEXP(X) | REAL*8 e в степени | REAL*8 | REAL*8

CEXP(Z) | COMPLEX*8 e в степени | COMPLEX*8|COMPLEX*8

CDEXP(Z) | COMPLEX*16 e в степени|COMPLEX*16|COMPLEX*1

Натуральный логарифм

LOG(общее) | натуральный логарифм | REAL*4 | REAL*4 | | REAL*8 | REAL*8 | | COMPLEX*8|COMPLEX*8 | | COMPLEX*1|COMPLEX*1

ALOG(X) | нат. логарифм REAL*4 | REAL*4 | REAL*4

DLOG(X) | нат. логарифм REAL*8 | REAL*8 | REAL*8

CLOG(Z) | нат. логарифм COMPLEX*| COMPLEX*8|COMPLEX*8

DLOG(Z) | нат. логарифм COMPLEX*|COMPLEX*16|COMPLEX*1

Десятичный логарифм

LOG10(общее)| десятичный логарифм | REAL*4 | REAL*4 | | REAL*8 | REAL*8

ALOG10(X) | дес. логарифм REAL*4 | REAL*4 | REAL*4

DLOG10(X) | дес. логарифм REAL*8 | REAL*8 | REAL*8

Синус

SIN(общее) | функция синус | REAL*4 | REAL*4 | | REAL*8 | REAL*8 | | COMPLEX*8|COMPLEX*8 | |COMPLEX*16|COMPLEX*1

SIN(X) | REAL*4 синус | REAL*4 | REAL*4

DSIN(X) | REAL*8 синус | REAL*8 | REAL*8

CSIN(Z) | COMPLEX*8 синус | COMPLEX*8|COMPLEX*8

CDSIN(Z) | COMPLEX*16 синус |COMPLEX*16|COMPLEX*1

Косинус

COS(общее) | функция косинус | REAL*4 | REAL*4

| | REAL*8 | REAL*8

| | COMPLEX*8|COMPLEX*8

| |COMPLEX*16|COMPLEX*1

COS(X) | REAL*4 косинус | REAL*4 | REAL*4

DCOS(X) | REAL*8 косинус | REAL*8 | REAL*8

CCOS(Z) | COMPLEX*8 косинус | COMPLEX*8|COMPLEX*8

CDCOS(Z) | COMPLEX*16 косинус |COMPLEX*16|COMPLEX*1

Тангенс

TAN(общее) | функция тангенс | REAL*4 | REAL*4

| | REAL*8 | REAL*8

TAW(X) | REAL*4 тангенс | REAL*4 | REAL*4

DTAN(X) | REAL*8 тангенс | REAL*8 | REAL*8

ИМЯ

НАЗНАЧЕНИЕ

ТИП ПАРАМ.

ТИП Ф

Арксинус

ASIN(общее)| функция арксинус | REAL*4 | REAL*4 | | REAL*8 | REAL*8

ASIN(X) | REAL*4 арксинус | REAL*4 | REAL*4

DASIN(X) | REAL*8 арксинус | REAL*8 | REAL*8

Арккосинус

ACOS(общее)| функция арккосинус | REAL*4 | REAL*4 | | REAL*8 | REAL*8

ACOS(X) | REAL*4 арккосинус | REAL*4 | REAL*4

DACOS(X) | REAL*8 арккосинус | REAL*8 | REAL*8

Арктангенс

ATAN(общее)| функция арктангенс | REAL*4 | REAL*4 | | REAL*8 | REAL*8

ATAN(X) | REAL*4 арктангенс | REAL*4 | REAL*4

DATAW(X) | REAL*8 арктангенс | REAL*8 | REAL*8

ATAN2(X,Y) | REAL*4 арктангенс х/y | REAL*4 | REAL*4

DATAN2(X,Y)| REAL*8 арктангенс x/y | REAL*8 | REAL*8

Гиперболический синус

SINH(общее)| гиперболический синус | REAL*4 | REAL*4 | | REAL*8 | REAL*8

SINH(X) | REAL*4 гиперб. синус | REAL*4 | REAL*4

DSINH(X) | REAL*8 гиперб. синус | REAL*8 | REAL*8

Гиперболический косинус

COSH(общее)| гиперболич. косинус | REAL*4 | REAL*4 | | REAL*8 | REAL*8

COSH(X) | REAL*4 гиперб. косинус| REAL*4 | REAL*4

DCOSH(X) | REAL*8 гиперб. косинус| REAL*8 | REAL*8

Гиперболический тангенс

TANH(общее)| гиперболич. тангенс | REAL*4 | REAL*4 | | REAL*8 | REAL*8

TANH(X) | REAL*4 гиперб. тангенс| REAL*4 | REAL*4

DTANH(X) | REAL*8 гиперб. тангенс| REAL*8 | REAL*8

Лексически больше или равно [7]

LGE(C1,C2) | первый параметр больше| CHARACTER| LOGICAL | или равен второму | |

Лексически больше [7]

LGT(C1,C2) | первый параметр больше| CHARACTER| LOGICAL | второго | |

Лексически меньше или равно [7]

LLT(C1,C2) | первый параметр меньше| CHARACTER| LOGICAL | или равен второму | |

Лексически меньше [7]

LLT(C1,C2) | первый параметр меньше| CHARACTER| LOGICAL | второго | |

ИМЯ

НАЗНАЧЕНИЕ

ТИП ПАРАМ.

ТИП Ф

Конец файла [8]

EOF(I) | целый конец файла | INTEGER | LOGICAL

Таблица 5-1. Встроенные функции.

Примечания к таблице 5-1:

1. Для Х типа INTEGER INT(X)=X. Для Х типа REAL или REAL*8, если

Х - неотрицательно, то INT(X) - наибольшее целое, не превышающее Х; а если Х - отрицательно, то INT(X) - наибольшее отрицательное целое не меньшее чем Х. Для Х типа REAL IFIX(X) то же самое, что INT(X).

2. Для Х типа REAL REAL*(X)=X. Для Х типа INTEGER или REAL*8 REAL(X) это значащая часть наибольшей точности Х, которую

может содержать данный тип REAL. Для Х типа INTEGER FLOAT(X) -

то же самое, что REAL(X).

3. Для Х типа REAL*8 DBLE(X)=X. Для Х типа INTEGER или REAL

DBLE(X) - это значащая часть наибольшей точности Х, которую может содержать тип данных двойной точности.

4. COMPLEX и DCMPLX могут иметь один или два параметра. Если использован один параметр, он может быть типа

INTEGER, REAL, DOUBLE PRECISION, COMPLEX или DOUBLE PRECISION

COMPLEX. Если использованы два параметра, то они должны быть одного и того же типа и при этом допустимы типы INTEGER, REAL или DOUBLE PRECISION.

При Z=COMPLEX*8, COMPLEX(Z)=Z. Для Z=INTEGER, REAL и REAL*8 COMPLEX(Z)=комплексной величине, действительная часть которой REAL(Z), а мнимая часть равна нулю.

COMPLEX(X,Y)= комплексной величине, действительная часть которой - REAL(X), а мнимая - REAL(Y).

При Z=COMPLEX*16, DCMPLX(Z)=Z. Для Z=INTEGER, REAL и REAL*8 DCMPL(Z)=комплексной величине, у которой действительная часть

- REAL*8, а мнимая равна нулю. Для Z=COMPLEX*8 DCMPLX(Z) имеет действительную часть равной DBLE(Z), а мнимую равной DBLE(AIMAG(Z)).

DCMPLX(X,Y)=комплексной величине, у которой действительная часть - REAL*8(X), а мнимая - REAL*8(Y).

5. ICHAR преобразует символьную величину в целую. Целая величина символа - это внутреннее представление символа в коде ASCII,

лежащее в диапазоне от 0 до 255.

Для любых двух символов С1 и С2 (С1.LE.C2) будет .TRUE. тогда и только тогда если (ICHAR(C1).LE.ICHAR(C2)) есть .TRUE. Функция CHAR(I) возвращает 1-й символ таблиц ASCII. Это величина типа CHARACTER длиной один символ, при этом I должно быть целым выражением, величина которого принадлежит диапазону 0іiі255.

ICHAR(CHAR(I))=I для 0іIі255

CHAR(ICHAR(C))=C для любого С из набора ASCII

6. DIM(X,Y) - это Х-Y, если X>Y, или нуль в противном случае.

7. LGE(X,Y) возвращает .TRUE. если Х=Y или если Х следует за Y в таблице кодов ASCII; иначе возвращается .FALSE.

LGT(X,Y) возвращает .TRUE. если Х следует за Y в таблице кодов

ASCII; иначе возвращается .FALSE.

LLE(X,Y) возвращает .TRUE. если X=Y или если Х предшевствует

Y в таблице ASCII; иначе возвращается .FALSE.

LLT(X,Y) возвращает .TRUE. если Х предшевствует Y в таблице

ASCII; иначе возвращается .FALSE.

Если у операндов неравная длина, то более короткий дополняется справа пробелами.

8. EOF(X) возвращает .TRUE. если устройство, определенное этим параметром находится на или после записи конец-файла; иначе возвращается .FALSE. Величина Х должна соответствовать откры-

тому файлу или нулю, что соответствует экрану или клавиатуре.

3.3.Функции-операторы.

Функция-оператор определена единственным оператором и по виду подобна оператору присваивания. Функция-оператор может появиться только после операторов описания и перед любыми выполняемыми операторами в программной единице.

Функция-оператор не является выполняемым оператором, так как она не выполняется, как стоящая первой в конкретной программной единице. Однако, тело функции-оператора используется для определения значения функции-оператора. Она выполняется, как и любая другая функция при вычислении ссылки на функцию в выражении.

Для информации о синтаксисе и использовании функции-оператора смотрите часть 3 "Операторы".

4.ПАРАМЕТРЫ.

Формальный параметр - это имя, с помощью которого параметр передается функции или подпрограмме; фактический параметр - это указанная переменная, выражение, массив и т.д., передаваемый процедуре при конкретном ее вызове. Взаимодействие между формальными и фактическими параметрами в функции или подпрограмме детально раскрывается ниже.

Параметры передают величины и берут их из процедуры ссылочным образом. Число действительных параметров должно совпадать с числом формальных параметров и при этом их типы должны быть согласованы.

На входе в подпрограмму или функцию фактические параметры связываются с формальными, во многом подобно тому как оператор EQUIVALENCE связывает два или более массива или переменные, а оператор COMMON в двух или нескольких программных единицах связывает списки переменных. Эта связь действует, пока не закончилось выполнение подпрограммы или функции. Поэтому присвоение величины формальному параметру во время выполнения подпрограммы или функции может изменить величину соответствующего фактического параметра.

Если фактический параметр - константа, ссылка на функцию или выражение, а не простая переменная, то присвоение величины соответствующему формальному параметру недопустимо и может вызвать непредсказуемые последствия. В частности, присвоение величины формальному параметру типа CHARACTER, когда фактический параметр - литерал, может вызвать ненормальное поведение.

Если фактический параметр - выражение, то оно вычисляется немедленно, еще до установления связи формальных и фактических параметров. Если фактический параметр - элемент массива, его индексные выражения вычисляются тоже до установления связи и остаются постоянными во время выполнения процедуры, даже если они содержат переменные, которые переопределяются во время выполнения процедуры.

Формальный параметр, описанный как переменная, может быть связан с фактическим параметром - переменной, элементом массива, выражением. Формальный параметр, описанный как переменный возврат (*) может быть связан с определителем переменного возврата (*n) в операторе CALL и является повторяемым.

Формальный параметр, описанный как массив, может быть связан с фактическим параметром, являющимся массивом или элементом массива. Число и размер размерностей формального параметра могут быть отличны от этих характеристик фактического параметра, но любая ссылка на формальный массив должна быть в пределах памяти, отведенной под фактический массив. Когда ссылка на элемент выходит за границы, это не фиксируется как ошибка при исполнение программы на МС-Фортране и результат непредсказуем.

Формальный параметр может быть также связан с внешней подпрогрвммой, функцией или встроенной функцией, если он используется в теле процедуры как ссылка на подпрограмму или функцию или он появляется в операторе EXTERNAL.

Соответствующий фактический параметр должен быть внешней подпрограммой или функцией, объявленной в операторе EXTERNAL или встроенной функцией, которую допускается связывать с формальным параметром процедуры. Встроенные функции должны быть объявлены в операторе INTRISIC в программной единице, где они используются как фактические параметры.

Все встроенные функции, за исключением нижеуказанных, могут быть связаны с формальными параметрами процедуры:

INT SNGL CHAR MAX0 MIN0

INT2 REAL LGE AMAX1 AMIN1

INT4 DBLE LGT DMAX1 DMIN1

IFIX COMPLX LLE AMAX0 AMIN0

IDINT DCMPLX LLT MAX1 MIN1

FLOAT ICHAR MAX MIN

5.ПЕРЕДАЧА ЦЕЛЫХ ПАРАМЕТРОВ.

Переменная INTEGER*2 не может быть передана формальному параметру INTEGER*4, а переменная INTEGER*4 не может быть передана формальному параметру INTEGER*2. Передача неверного типа переменной порождает ошибку компиляции. Вы должны использовать встроенные функции INT4 и INT2. Также при действии $STORAGE:4 по умолчанию выражения с параметрами INTEGER*2 будут иметь результат типа INTEGER*4. Следующая программа:

SUBROUTINE S(I)

INTEGER*2 I

. . .

END

INTEGER*2 J,K

CALL S(J+K)

дает ошибку, поэтому J+K - INTEGER*4. Вы должны написать вызов

подпрограммы так

CALL S(INT2(J+K))

Компилятор при оптимизации понижает степень точности арифметики, если это не влияет на результат и не установлено $DEBUG. Например, если не установлено $DEBUG, арифметика в

INTEGER2 I,J,K

I=J+K

- 16-ти разрядная арифметика, даже если определено $STORAGE:4. Использование $STORAGE:4 не действует на выражения INTEGER*2 при использовании в нем только операторов +, - или *, поэтому что результат их одинаковый как в 16-разрядной, так и в 32-разрядной арифметике.

Целые параметры, которые передаются величинами (смотрите "Атрибуты" в части 7 для определения атрибутов, которые позволяют передавать параметры величинами), не являются объектами таких же ограничений. Параметр обрабатывается, как если бы ему была присвоена временно переменная соответствующего типа, и временная переменная затем передается как параметр. Правила присвоения для величин параметров те же самые, что и при присваивании. Вы можете, например, передать действительную величину целому параметру.


МЕТАКОМАНДЫ МС-ФОРТРАНА

Метакоманды MS-Fortran'а.

1.МЕТАКОМАНДЫ МС-ФОРТРАНА.

В следующей таблице приведено краткое описание метакоманд МС-Фортрана, а их подробное описание приводится да.

Метакоманда

Действие

$DEBUG

Включает проверку при счете целых арифмет. операций и присваемого GO TO $NODEBUG выключает проверку.

$DECMATH

Указывает компилятору создавать действительные константы в десятичном виде с плавающей точкой.

$DO66

Приводит исполнение операторов DO к семантике Фортрана 66.

$FLOATCALLS

Заставляет выполнять операции с плав. точкой через библиотеку подрограмм с помощью инструкции CALL, а не через прерывания. $NOFLOATCALL отменяет этот раздел.

$INCLUDE:

файл

Заставляет компилятор считать, что в этой точке вставлен файл.

$LARGE [:имя [,

имя ]. . .]

Помечает именованные массивы для адресации вне DGROUP. $NOTLARGE отменяет этот раздел для именованных массивов. Если имена массивов не указаны команда действует на все массивы.

$LINESIZE: n

Формирует страницы листинга шириной в n позиций.

$LIST

Посылает часть листинга в файл лист. $NOLIST останавливает генерацию лист.

$MESSAGE:'строка'

Посылает строку в кaвычках на станд. устройство вывода при исполнении Фортрана с начала до конца.

$PAGE

Начинает новую страницу листинга.

$PAGESIZE: n

Задает длину страницы листинга n.

$STORAGE: n

Определяет количество (n) байтов пам. для всех LOGICAL или INTEGER переменнных в тексте.

$STRICT

Запрещает возможности МС-Фортрана, не входящий в Фортран 77 или в полный стандарт языка. $NOTSTRICT разрешает их.

Метакоманда

Действие

$SUBTITLE:

'подзаголовок'

Дает подзаголовок для нескольких страниц листинга.

$TITLE:

'заголовок'

Дает заголовок для нескольких страниц листинга.

Метакоманды могут быть вставлены в любое место исходного текста на МС-Фортране внутри программ; однако они не относятся к стандарному языку Фортран. Любая строка ввода компилятора МС-Фортрана, начинающаяся с символа "$" в первой позиции интерпретируется как метакоманда и должна соответствовать одному из допустимых форматов.

Метакоманда и ее параметры (если они есть) должны занимать одну строку текста; строки продолжения недопустимы. Пробелы не значат, поэтому следующая пара эквивалентна:

$S TRICT

$STRICT

2.СПРАВОЧНИК МЕТАКОМАНД.

Остаток этой части - это алфавитный (по латинским буквам) справочник допустимых метакоманд МС-ФОРТРАНА.

Для понимания синтаксиcа описания смотрите в этом руководстве приложение А.

$DEBUG и $NODEBUG

Заставляет компилятор:

1. Проверять целую арифметику на переменные и деление на нуль.

2. Проверять величины присваемого GO TO на допустимость списка в операторе присваемое GO TO.

3. Обеспечивать функционирование системы фиксации ошибок при счете с именами файлов и номерами строк, так что при возникновении ошибки на экран выдается имя файла и номер стро-

ки.

$[NO]DEBUG

Особенности.

$DEBUG не переключает и не подавляет поддержку реакций на ошибки для арифметики с плавающей запятой. МС-Фортран поддерживает стандарт IEEE при обеспечении реакций на ошибки в пяти следующих ситуациях : неверное действие, деление на нуль, переполнение разрядной сетки, незаполнение разрядной сетки и потеря точности. Более подробно о поддержке реакций на ошибки смотрите приложение Е руководства пользователя МС-Фортрана.

Метакоманда может стоять в программе где-угодно.

По умолчанию используется $NODEBUG.

$DECMATH

Заставляет компилятор (и откомпилированную программу при счете) применять математику с плавающей точкой, выполняемую по основанию 10, а не в двоичном виде. Особенно $DECMATH заменяет представление констант на формат по десятичному основанию.

$DECMATH

Особенности.

$DESMATH должна стоять перед первым оператором программы или подпрограммы; ей могут прешествовать только строки комментария или другие метакоманды, и она может употребляться только однажды.

$DESMATH автоматически устанавливает $FLOATCALLS. Если в одной и той же программе установлены $DESMATH и $NOFLOATCALLS, то компилятор выдаст ошибку.

$DO66

Приводит операторы DO к семантике Фортрана 66.

$DO66

Особенности.

$DO66 должна предшествовать первому описанию или выполняемому оператору в файле исходного текта.

Семантика Фортрана 66 следующая:

1. Все операторы DO выполняются по крайней мере однажды.

2. Допустим расширенный диапазон; это означает, что управление может быть передано на синтактическое тело оператора DO. Поэ-

тому диапазон оператора DO расширен логически так, чтобы включать любой оператор, который может быть выполнен между оператором DO и его последним оператором. Однако, передача управления на диапазон оператора DO до его выполнения или после выполнения его последнего оператора не верна.

Если в программе нет метакоманды $DO66, по умолчанию реализуется семантика Фортрана 77, а именно:

1. Операторы DO могут выполнять нуль раз, если начальная вели-

чина управляющей переменной превышает конечную величину управляющей переменной (что соответствует оператору DO с отрицательным шагом).

2. Расширенный диапазон неверен; то есть, управление нельзя передавать на синтаксическоетело оператора DO. (Оба стандарта

DO допускают передачу управления из тела оператора DO).

$FLOATCALLS и $NOFLOATCALLS

Вызывает выполнение операций с плавающей точкой через вызовы библиотечных подпограмм.

$[NO]FLOATCALLS

Особенности.

Когда Вы вставляете $FLOATCALLS в Ваш текст, Вы генерируете вычисления над действительными числами через инструкции вызова. $NOFLOATCALLS подавляет условие по умолчанию и заставляет компилятор генерировать инструкции внутренних прерываний, а не вызовы подпрограмм. По умолчанию действует $FLOATCALLS.

$DECMATH автоматически устанавливает $FLOATCALLS.

$INCLUDE

Заставляет компилятор действовать так, как если бы указанный файл был вставлен в месте употребления $INCLUDE.

$INCLUDE: 'файл'

Где

файл - это имя существующего файла, как оно описывается

в Вашей опирационной системе.

Особенности.

В конце вставленного файла компилятор возобновляет обработку файла исходного текста со строки, следующей за $INCLUDE.

У компилятора нет ограничений на вложенность уровней метакоманд $INCLUDE. Метакоманды $INCLUDE, в частности, полезны для гарантии того, что в разных модулях используется одно и тоже описание COMMON-блока.

$LARGE и $NOLARGE

Заставляет компилятор располагать массивы в менее ограниченной области и генерировать менее эффективные последовательности кодов для ссылки на них.

$[NOT]LARGE[:имя[,имя]. . .]

Где

имя -это имя массива.

Особенности.

$LARGE может быть использована без параметров, в основном виде. Эта форма может встречаться, где угодно за исключением выполняемой части подпрограммы. $LARGE воздействует на все подпрограммы, которые следуют за ней в текстовом файле, до тех пор пока в тексте не встретится общая метакоманда $NOLARGE. $NOLARGE подчиняется тем же правилам, что и $LARGE но оказывает обратное воздействие.

По умолчанию установлено $NOLARGE. В области между

оператором END (или началом компилируемой единицы) и выполняемой

частью следующей подпрограммы или основной программы основная форма

$LARGE или $NOTLARGE, но не обе, может использоваться только

однажды. Например, следующий фрагмент текста ошибочен.

$LARGE

SUBROUTINE P

$NOTLARGE

A=1.0

:

$LARGE может содержать имена описанных массивов и массивов формальных параметров, но тогда она должна стоять в описанной части подпрограммы и будет тогда действовать на массивы или формальные параметры этой подпрограммы. $NOTLARGE с параметром может быть использован для исключения из действия общей команды $LARGE массива или формального параметра. Массивы с точными размерностями, которые показывают, что эти массивы больше допустимого предела в 64 килобайта, автоматически размещаются на нескольких сегментах вне сегмента данных по умолчанию. Вам не нужно ставить $LARGE для этих массивов.

$LINESIZE

Форматирует последующие страницы листинга шириной n позиций.

$LINESIZE: n

Где

n - положительное целое число в диапазоне от 40 до 132

включительно.

Если в программе нет метакоманды $LINESIZE по умолчанию подразумевается длина строки 80 символов.

$LIST и $NOLIST

Включает заполнение файла листинга, опреленного при вызове компилятора, или запрещает его заполнение.

$[NO]LIST

Особенности.

Если файл листинга не заказан при запуске компилятора, то метакоманда $LIST не действует. $NOLIST указывает на отмену листинга.

$LIST и $NOLIST могут стоять где угодно в текстовом файле.

По умолчанию установлено установлено $LIST.

$MESSAGE

Используется для посылки сообщения на стандартное устройство вывода при выполнении Фортрана, перед его окончанием.

$MESSAGE: 'строка'

Где

строка - любая действительная символьная константа.

Максимальная длина - 40 символов.

Пример

$MESSAGE: 'это высвечивается, когда вы выполняете FOR1'

$PAGE

Начинает новую страницу листинга.

$PAGE

Особенности.

Если первым символом строки исходного текста является символ ASCII перевод страницы (шестнадцатиричный код ОСh), это считается эквивалентным использованию метакомоманды $PAGE в этой точке.

$PAGESIZE

Определяет длину в n строк последующих страниц листинга.

$PAGESIZE

Где

n - любое положительное целое число не меньше 15.

Особенности.

Если в программе нет метакоманды $PAGESIZE, то размер страницы по умолчанию - 60 строк.

$STORAGE

Определяет заданное чмсло байтов памяти для всех переменных, объявленных в исходном файле как INTEGER или LOGICAL.

$STORAGE: n

Где

n либо 2, либо 4. Использование величины 2 подразумевает

16-разрядную арифметику. Смотрите также важные замечания по представлению данных в конце описания "Типы данных" в части 2 "Термины и концепции".

Особенности.

$STORAGE не оказывает влияния на распределение памяти для переменных, описанных с точным указанием длины, например, INTEGER*2 или LOGICAL*4.

Если несколько файлов текстов программ компилируются и компонуются вместе, Вы должны тщательно следить за тем, чтобы переменным (как фактическим, так и формальным параметрам), на которые ссылаются более чем в одном модуле, выделялась одинаковая память.

Метакоманда $STORAGE должна предшествовать первому оператору описания в файле текста.

Если в программе нет метакоманды $STORAGE, осуществляется выделение 4-х байтов по умолчанию. Это умолчание соответствует тому, что под переменные INTEGER, LOGICAL и REAL выделяется одинаковое количество памяти, что требуется стандартом Фортрана 77.

$STRICT и $NOTSTRICT

$STRICT запрещает особые возможности МС-Фортрана, отсутствующие в подмножестве Фортрана 77 в полном стандарте языка. Метакоманда $NOTSTRICT разрешает эти возможности.

$[NOT]STRICT

Особенности.

Метакоманда $NOTSTRICT разрешает следующие возможности МСФортрана:

1. Символьное выражение может быть присвоено несимвольной переменной.

2. Символьное и несимвольное выражения могут быть сравнены.

3. В одном и том же символьном COMMON-блоке допустимы символьные и несимвольные переменные.

4. Можно установить эквивалентность между символьными и несимвольными переменными.

5. В несимвольных переменных могут быть заведены символьные данные.

По умолчанию установлена $NOTSTRICT.

$SUBTITLE

Присваивает определенный подзаголовок последующим страницам листинга исходного текста (до тех пор пока не встретится другая метакоманда $SUBTITLE).

$SABTITLE: 'подзаголовок'

Где

подзаголовок -любая определенная символьная константа. Максимальная длина - 40 символов.

Особенности.

Если в программе нет метакоманды $SUBTITLE, подзаголовок - это пустая строка.

$TITLE

Присваивает определенный заголовок последующим страницам листинга (до тех пор пока не встретится другая метакоманда $TITLE).

$TITLE: 'заголовок'

Где

заголовок - любая определенная символьная константа.

Максимальная длина - 40 символов.

Особенности.

Если в программе нет метакоманды $TITLE, заголовок - это пустая строка.


ПPOГPAMMИPOBAHИE HA OCHOBE MOДУЛEЙ,

Программирование на основе модулй, написанных на разных языках

1.BBEДEHИE.

Иcпoльзoвaниe Baми пpи пpoгpaммиpoвaнии, мoдулeй нaпиcaнныx нa дpугиx языкax пoзвoляeт:

1. Иcпoльзoвaть библиoтeки пpoцeдуp нa дpугиx языкax. Haпpимep, Bы мoжeтe вызвaть MC-Cи библиoтeку из пpoгpaммы, нaпиcaннoй нa Фортране или Паскале. Taкжe мoжнo иcпoльзoвaть фopтpaнныe библиoтeки для пpoгpaмм, нaпиcaнныx нa MC-Паскаль и MC-Cи.

Чтoбы пoльзoвaтьcя библиoтeкoй нa oпpeдeлeннoм языкe, Bы дoлжны зaкaзaть пoддepжку библиoтeки кoмпилятopoм этoгo языкa. Haпpимep, чтoбы пoльзoвaтьcя нeкoтopoй фopтpaннoй библиoтeкoй пpи paбoтe c пpoгpaммoй, нaпиcaннoй нa Cи, Bы дoлжны зaкaзaть пoддepжку библиoтeки c кoмпилятopoм МС-Фортран, тaкжe кaк и caму нужную Baм библиoтeку.

Этo нeoбxoдимo, тaк кaк пpoгpaммы, нaпиcaнныe MC-Паскале, MC-Cи и MC-Фортране, coдepжaт oбpaщeния к cвoим cooтвeтcтвующим paбoчим библиoтeкaм.

2. Bы мoжeтe иcпoльзoвaть вoзмoжнocти, нe peaлизoвaнныe в Baшeм языкe.

Haпpимep, cлoжнo нaпиcaть пpoцeдуpу мaнипулиpующую битaми нa Фортране, нaпpoтив этo лeгкo нa Cи или Паскале. Taкжe, нeкoтopыe cвязи мeжду oбьeктaми тaкиe кaк в cтpуктуpax нa Cи или нa Паскале, нe peaлизoвaны в Фортране.

3. Ecли Bы coздaeтe cвoи coбcтвeнныe библиoтeки пpoцeдуp, Bы мoжeтe cфopмиpoвaть библиoтeку пpoцeдуp, coвмecтимую co вceми

тpeмя языкaми.

Bы дoлжны внимaтeльнo пpoчитaть мaтepиaл этoй глaвы, чтoбы уc- пeшнo cвязывaть, пpи пpoгpaммиpoвaнии, мoдули нa paзныx языкax.

2.MOДEЛИ ПAMЯTИ.

Ecли Bы пoльзуeтecь Cи-пpoцeдуpaми:

Bы дoлжны кoмпилиpoвaть Baш Cи-кoд иcпoльзуя мoдeль пaмяти

LARGE.

Coвpeмeнныe вepcии Паскаля и Фортрана нe пpeдлaгaют выбopa дpугиx мoдулeй пaмяти; oни тoлькo coвмecтимы c мoдeлью Cи LARGE.

Bы дoлжны иcпoльзoвaть мoдeль Cи-библиoтeки LARGE.

Heкoтopыe кoмпoнeнты Cи-библиoтeки ccылaютcя нa библиoтeки дpугиx языкoв. Ecли Bы иcпoльзуeтe библиoтeку c дpугoй мoдeлью пaмяти, эти cвязи oкaжутcя нeкoppeктными.

3.BЫБOP COГЛAШEHИЯ O ПEPEДAЧE ПAPAMETPOB.

Фортран, Паскаль и Cи , кaждый , имeeт cвoe coбcтвeннoe co- глaшeниe o пepeдaчи пapaмeтpoв.

Bo-пepвыx, языки oтличaютcя пopядкoм , в кoтopoм пapaмeтpы зaнocятcя в cтeк. MC-Паскаль и MC-Фортран зaнocят пapaмeтpы в cтeк в тoм жe пopядкe, кaк oни oпиcaны в зaгoлoвкe пpoцeдуpы. MC-Cи зaнocит пapaмeтpы в oбpaтнoм пopядкe.

Языки oтличaютcя eщe в тoм, нaxoдитcя ли кoд, oпpeдeляющий кaк вынимaть пapaмeтpы из cтeкa пocлe вoзвpaтa из пpoцeдуpы, в вызывa- ющeй пpoцeдуpe или в вызывaeмoй пpoцeдуpe. B coглaшeнии Фортрана/Паскаля, кoд нaxoдитcя в вызывaeмoй пpoцeдуpe; в Cи этoт кoд cлeдуeт зa oбpaщeниeм к пpoцeдуpe.

B Фортране/Паскале пepeдaчa пapaмeтpoв coглacнo coглaшeниям пpинятым в этиx языкax, ocущecтвляeтcя нecкoлькo быcтpee и тpeбуeт мeньшe кoдa. Coглaшeниe CИ пoзвoляeт Baм иcпoльзoвaть пepeмeннoe чиcлo пapaмeтpoв (тaк кaк пepвый пapaмeтp вceгдa пocлeдним зaнocитcя в cтeк, oн вceгдa нaxoдитcя в нaчaлe cтeкa и укaзывaeт нaчaльный aдpec нaбopa пapaмeтpoв). Эти coглaшeния нecoвмecтимы. Haкoнeц, языки oтличaютcя cпocoбoм пepeдaчи пapaмeтpoв: ccылкoй или знaчeниeм. Cмoтpитe paздeл "Пepeдaчa пapaмeтpoв c пoмoщью ccылки или пo знaчeнию".

Ecли Bы знaeтe, кaк вызывaющий, тaк и вызывaeмый кoд, Bы мoжeтe выбpaть кaкoe coглaшeниe иcпoльзoвaть. Ecли Baм нужнo пepeдaвaть пepeмeннoe чиcлo пapaмeтpoв, иcпoльзуйтe coглaшeниe Cи (cмoтpитe paздeл "Иcпoльзoвaниe пepeмeннoгo чиcлa пapaмeтpoв"). Moжeт быть Bы зaxoтитe пoльзoвaтьcя coглaшeниeм o пepeдaчи пapa- мeтpoв пo умoлчaнию.

Для тoгo чтoбы oбpaщaтьcя к oднoму языку из дpугoгo, Bы дoлжны укaзaть кoмпилятopу, кaкoe coглaшeниe иcпoльзуeтcя. B MC-Cи, MC- Паскале и MC-Фортране cущecтвуют cпeцификaции, укaзывaющиe кaкиe coглaшeния Bы иcпoльзуeтe, кaк для вызoвa внeшнeй пpoцeдуpы, тaк и для вызoвa внутpeннeй пpoцeдуpы. Taблицa 7-1 coдepжит cпeцификaции coглaшeний пepeдaчи пapaмeтpoв для кaждoгo языкa.

Язык из кoтopoгo oбpaщaютcя к пpoцeдуpe

Aтpибуты/ключeвыe cлoвa

Иcпoльзoвaниe coглaшeния Cи

Паскаль

Фортран

Cи

Иcпoльзoвaниe coглaшeния

Фортрана

Паскаль

Фортран

Cи

Иcпoльзoвaниe coглaшeния

Паскаля

Паскаль

Фортран

Cи

aтpибут С в зaгoлoвкe

процeдуpы

aтpибут C в операторе

INTERFACE

пo умoлчaнию

aтpибут FORTRAN

в заголовкe пpoцeдуpы

пo умoлчaнию

ключeвoe cлoвo Фортрана

в зaгoлoвкe пpoцeдуpы

пo умoлчaнию

aтpибут PASCAL

в операторe INTERFACE

ключeвoe cлoвo ПACKAЛЯ

в зaгoлoвкe пpoцeдуpы

Taблицa 7-1 Cпeцификaции coглaшeний пepeдaчи пapaмeтpoв.

3.1.Пepeдaчa пapaмeтpoв ccылкoй и знaчeниeм.

Koгдa пapaмeтp пepeдaeтcя ccылкoй, пepeдaeтcя aдpec пapaмeтpa. Пpoцeдуpы пoлучaют знaчeния пapaмeтpoв чepeз aдpeca, любыe измe- нeния пapaмeтpa в вызывaeмoй пpoцeдуpe измeняют вeличину фaктичec- кoгo пapaмeтpa. Koгдa пapaмeтp пepeдaeтcя знaчeниeм, кoпия пapa- мeтpa зaнocитcя в cтeк пpи вызoвe пpoцeдуpы. Пpoцeдуpa мoжeт мeнять знaчeниe пapaмeтpa, нo eгo cooтвeтcтвующий фaктичecкий пapaмeтp пocлe выxoдa из пpoцeдуpы ocтaнeтcя, тaким жe, кaк и дo oбpaщeния к нeй.

Для кaждoгo пapaмeтpa Bы выбиpaeтe cпocoб пepeдaчи, или знaчeниeм, или ccылкoй. Ecли Bы пepeдaeтe ccылкoй, cущecтвуeт двe вoзмoжнocти: пepeдaвaть длинный aдpec (ceгмeнт и cмeщeниe) или пepeдaвaть кopoткий aдpec (тoлькo cмeщeниe).

Ecли вызывaeмaя пpoцeдуpa вoзвpaщaeт измeнeннoe знaчeниe фaктичecкoгo пapaмeтpa, кaк peзультaт, Bы иcпoльзуeтe пepeдaчу ccылкoй. Пepeдaчa знaчeниeм, зaщищaeт пpoтив oшибoчнoгo измeнeния дaнныx, и для пepeмeнныx, paзмepoм мeньшe 4 бaйтa, oкaзывaeтcя бoлee эффeктивнoй.

Умoлчaния для кaждoгo языкa:

ј Фортран вce пapaмeтpы пepeдaeт ccылкoй (включaя кoнcтaнты и вы-

paжeния), нo мoжнo зaдaть пepeдaчу знaчeниeм. Ecли в пpoцeдуpe

зaдaн aтpибут PASCAL или C, coглaшeниe пo умoлчaнию измeня-

eтcя: вce пapaмeтpы в этoй пpoцeдуpe будут пepeдaвaтьcя знa-

чeниeм дo cлeдующeй cпeцификaции, измeняющeй coглaшeниe.

ј Cи вceгдa пepeдaeт мaccивы ccылкoй, a вce дpугиe пapaмeтpы знa-

чeниeм. B Cи Bы мoжeтe пepeдaвaть укaзaтeли, кaк пapaмeтpы:

пpoцeдуpa будeт в этoм cлучae иcпoльзoвaть укaзaтeли пpи

измeнeнии зaгpужeннoй вeличины, тeм caмым пoлучeнный peзультaт

будeт тaким жe кaк пpи пepeдaчe ccылкoй.

ј Паскаль пepeдaeт пapaмeтpы знaчeниeм, нo мoжeт быть зaдaнa пepeдaчa ccылкoй.

Ecли Bы oткaзaлиcь oт paбoты пo умoлчaнию, Bы дoлжны зaдaть oпpeдeлeнныe ключeвыe cлoвa, aтpибуты или типы укaзaтeлeй. Oни дoлжны cooтвeтcтвoвaть coглaшeнию, кoтopoe Bы иcпoльзуeтe. Cмoтpитe тaблицы 7-2, 7-3 и 7-4.

Ecли Bы пepeдaeтe пapaмeтpы пpи coглaшeнии Cи, иcпoльзуйтe кoн- cтpукции oпиcaнныe в тaблицe 7-2, пpи oпиcaнии пapaмeтpoв.

ПAPAMETP

CИ

ПACKAЛЬ

ФOPTPAH

Длинный

aдpec

Kopoткий

aдpec

Знaчeниe

укaзaтeль нa тип

near укaзaтeль

нa тип

Пo умoлчaнию

Vars ключeвoe cлoвo

Var ключeвoe

cлoвo

Пo умoлчaнию

Referense aтpибут

Referense, near aтpибуты

Пo умoлчaнию

Taблицa 7-2. Пapaмeтpы пpи coглaшeнии CИ.

Haпpимep, пpeдпoлoжим, чтo Bы иcпoльзуeтe coглaшeниe Cи. Taблицa 7-1 пoкaзывaeт, кaкиe aтpибуты и ключeвыe cлoвa нужнo иcпoльзoвaть.

Koгдa oбpaщaeтecь из Паскаля, укaжитe C aтpибут в oпиcaнии пpoцeдуpы. Koгдa oбpaщaeтecь из Фортрана укaжитe C aтpибут в утвep- ждeнии INTERFACE. Koгдa oбpaщaeтecь из Cи, Cи coглaшeниe нaзнaчaeтcя пo умoлчaнию.

Teпepь, пpeдпoлoжим ,чтo Bы xoтитe пepeдaть цeлый пapaмeтp x, иcпoльзуя длинный aдpec. Coвмecтимocть типoв дaнныx будeт oпи- caнa нижe в этoй глaвe, тeпepь пoлoжим, чтo int тип Cи, integer тип Паскаля, и INTEGER Фортрана эквивaлeнтны. Taблицa 7-2 пoкaзывaeт, чтo кoгдa в Baшeй пpoцeдуpe нa Cи oпиcaн пapaмeтp x, Bы дoлжны иcпoльзoвaть укaзaтeль ( пo умoлчaнию far) cooтвeтcтвующeгo типa (в дaннoм cлучae, int). Oпиcaниe пapaмeтpa x нa Cи: int *x; Koгдa oпиcывaeтe пapaмeтp x в Baшeй пpoцeдуpe нa Паскале, иcпoльзуйтe ключeвoe cлoвo VARS

VARS X:INTEGER

Для пpoцeдуpы нa Фортране иcпoльзуйтe aтpибут REFERENSE:

INTEGER X[REFERENCE]

Ecли Bы xoтитe пepeдaть пapaмeтp, иcпoльзуя кopoткий aдpec, cooтвeтcтвующиe oпиcaния пapaмeтpa x, будут:

int near *x ;

VAR x:INTEGER ;

INTEGER [REFERENSE,NEAR]

Иcпoльзуйтe тe жe пpинципы, ecли Bы зaxoтитe вocпoльзoвaтьcя дpугими coглaшeниями o пepeдaчe дaнныx: ecли Bы пepeдaeтe пapaмeтpы иcпoльзуя coглaшeниe Паскаля или Фортрана, иcпoльзуйтe кoнcтpукции, пpивeдeнныe тaблицax 7-3 и 7-4 пpи oпиcaнии пapaмeтpoв.

ПAPAMETP

Cи

Паскаль

Фортран

длинный aдpec

кopoткий

aдpec

знaчeниe

укaзaтeль нa

тип

near укaзaтeль

нa тип

пo умoлчaнию

ключeвoe cлoвo

VARS ключeвoe cлoвo

VAR

пo умoлчaнию

aтpибут

REFERENCE

REFERENCE и NEAR

aтpибуты

пo умoлчaнию

Taблицa 7-3 Пapaмeтpы пpи coглaшeнии Паскаля.

ПAPAMETP

Cи

Паскаль

Фортран

длинный

aдpec

кopoткий

aдpec

знaчeниe

укaзaтeль нa

тип

near укaзaтeль

нa тип

пo умoлчaнию

ключeвoe cлoвo

VARS

VARS ключeвoe cлoвo

пo умoлчaнию

пo умoлчaнию

aтpибут

NEAR

aтpибут

VALUE

Taблицa 7-4 Пapaмeтpы пpи coглaшeнии Фортрана.

Ecли Bы нe пишитe caми oбe пpoцeдуpы: вызывaющую и вызывaeмую, Bы дoлжны пepeдaвaть пapaмeтp, кaк oн oпиcaн в cущecтвующeм oпpeдe- лeнии пpoцeдуpы. Ecли Bы нe имeeтe oпытa paбoты c языкoм, к кoтopoму Bы oбpaщaeтecь, у Bac мoгут вoзникнуть зaтpуднeния в oпpeдe- лeнии cпocoбa пepeдaчи пapaмeтpa - знaчeниeм или ccылкoй. Cпиcoк, пpивeдeний нижe, пoмoжeт Baм в этoм paзoбpaтьcя.

Cлeдующиe виды пapaмeтpoв пepeдaютcя знaчeниeм:

јB Паскале, любoй дeклapиpoвaнный пapaмeтp, кpoмe VAR, CONST, VARS

и CONSTS пapaмeтpoв.

јB Cи, любoй дeклapиpoвaнный пapaмeтp, зa иcключeниeм мaccивoв.

јB Фортране, пapaмeтp, дeклapиpoвaнный c aтpибутoм VALUE.

јB Фортране, пapaмeтp в процедуре, вызванной с атрибутом С или

PASCAL (пока не определен атрибут REFERENCE).

Cлeдующиe виды пapaмeтpoв пepeдaютcя ccылкoй c кopoтким (2-бaйтoвым, тoлькo cмeщeниe) aдpecoм.

јB Паскале пapaмeтp фopмaтa oпиcaнный кaк VAR или CONST.

јB Паскале, пepeмeннaя пepeдaвaeмaя укaзaтeлeм нa эту пepeмeнную. Укaзaтeль caм пepeдaeтcя знaчeниeм. (He peкoмeндуeтcя иcпoльзoвaть укaзaтeли тaким oбpaзoм, тaк кaк cущecтвуeт жecткaя cвязь мeжду укaзaтeлями и мaшинными aдpecaми)

јB Паскале, пepeмeннaя пepeдaвaeмaя пepeдaчeй пepeмeннoй ADR. Aдpec

caм пo ceбe (кaк вce укaзaтeли) пepeдaeтcя знaчeниeм.

јB Cи, пapaмeтp пepeдaющийcя укaзaтeлeм near нa пapaмeтp (укaзaтeль

пepeдaeтcя знaчeниeм).

јB Cи, мaccив oпиcaнный c ключeвым cлoвoм near.

јB Фортране, в пpoцeдуpax бeз aтpибутoв C и PASCAL, пapaмeтp бeз

aтpибутa NEAR.

јB Фортране, в пpoцeдуpax бeз aтpибутoв C или PASCAL, пapaмeтp c

aтpибутaми NEAR и REFERENSE.

јB Фортране, пepeмeннaя пepeдaвaeмaя кopoтким aдpecoм в кaчecтвe

пapaмeтpa функции LOCNEAR; знaчeниeм этoй функции являeтcя

INTEGER*2.

Cлeдующиe виды пapaмeтpoв пepeдaютcя ccылкoй c длинным (4-бaйтoвым, ceгмeнт и cмeщeниe) aдpecoм.

јB Паскале, ADS пepeмeннaя (aдpec пepeдaeтcя знaчeниeм)

јB Паскале, пapaмeтpы oпиcaнныe ключeвыми cлoвaми VARS или CONSTS. јB Cи, пapaмeтp, пepeдaвaeмый пepeдaчeй укaзaтeли far нa пapaмeтp

(ccылкa пepeдaeтcя знaчeниeм). Зaмeтим, чтo в LARGE мoдeли CИ, far укaзaтeли-этo тип укaзaтeлeй пo умoлчaнию.

јB Cи мaccивы, нe oпиcaнныe ключeвым cлoвoм near.

јB Фортране, любoй пapaмeтp Фортран-пpoгpaммы, зa иcключeниeм пapaмeтpoв oпиcaнныx aтpибутaми NEAR или VALUE.

јB Фортране, пepeмeннaя пepeдaвaeмaя длиным aдpecoм в кaчecтвe

пapaмeтpa функции LOC или LOCFAR; знaчeниeм этиx функций являeтcя INTEGER*4.

3.2.Иcпoльзoвaниe пepeмeннoгo чиcлa пapaмeтpoв.

Ecли Bы coбиpaeтecь иcпoльзoвaть пepeмeннoe чиcлo пapaмeтpoв: ј Чиcлo фaктичecкиx пapaмeтpoв дoлжнo быть мeньшe или paвнo чиcлу

фopмaльныx пapaмeтpoв (ecли вызывaeмaя пpoцeдуpa нaпиcaнa нa

Фортране или Паскале).

Ha Фортране и Паскале вoзникaют cлoжнocти пpи paбoтe c пapa- мeтpaми, кoтopыe были фopмaльнo нe oпpeдeлeны. Oднaкo, Bы мoжeтe иcпoльзoвaть aтpибут VARYING для пepeдaчи тex элeмeнтoв, кoтopыe oпиcaны.

ј Bы дoлжны иcпoльзoвaть C и VARYING aтpибуты в Baшeм утвepждeнии

INTERFACE нa Фортране, или в oпиcaнии пpoцeдуpe нa Паскале.

Aтpибут VARYING укaзывaют кoмпилятopу Фортрана или Паскаля нe пpoвepять бoльшe или мeньшe фaктичecкиx пapaмeтpoв, чeм фopмaльныx пapaмeтpoв. Oднaкo, фaктичecкиe пapaмeтpы cooтвeтcтвующиe фopмaльным пapaмeтpaм будут пpoвepятьcя нa coвмecтимocть типoв coглacнo oбычным пpaвилaм пpoвepки oбpaщeний к пpoцeдуpe.

4.COГЛAШEHИЯ OБ ИДEHTИФИKATOPAX.

Ecли Bы будeтe cлeдoвaть двум пpaвилaм пpивeдeнным нижe, кoмпилятopы MC-Паскаля, MC-Cи, MC-Фортрана будут кoppeктнo coглaco- вывaть имeнa.

ј Ecли Bы иcпoльзуeтe пpoцeдуpы нa Фортране, длинa вcex индeнтификaтopoв (имeн) дoлжнa нe пpeвышaть 6 литep.

ј Избeгaйтe пoльзoвaтьcя зaглaвными литepaми в идeнтификaтopax Cи.

Ecли Bы дoлжны oбязaтeльнo иcпoльзoвaть зaглaвныe литepы, укaжитe IGNORECASE и нe иcпoльзуйтe дpугиe идeнтификaтopы, имeющиe тo жe нaпиcaниe, кaк зaглaвный или cмeшaнный Cи идeнтификaтop (Haпpимep, ecли идeнтификaтop Cи AnEXAMPLE, нe иc- пoльзуйтe anexample, ANEXAMPLE или AnExAmPLE кaк идeнтификa- тopы).

Ecли Bы вce-тaки нe мoжeтe cлeдoвaть этим пpaвилaм, Bы мoжeтe в нeкoтopыx cлучaяx coглacoвывaть имeнa caми. Ocтaвшaя чacть дaннoгo paздeлa пocвящeнa coглaшeниям oб идeнтификaтopaм пo умoлчaнию в кaждoм языкe, и влиянию cooтвeтcтвующиx aтpибутoв и ключeвыx cлoв нa coглaшeния пo умoлчaнию.

Bo вcex тpex языкax, имeнa зaпиcывaютcя paзличнo в paбoчeм фaйлe и в иcxoднoм фaйлe. Cущecтвуeт oтличиe в тpex элeмeнтax co- глaшeния oб идeнтификaтopax.

PAЗMEP B Фортране и Паскале, вce пpoпиcныe литepы, пpeoбpa-

зуютcя в зaглaвныe пepeд coздaниeм paбoчeгo фaйлa. Пo

умoлчaнию этoгo нe пpoиcxoдит c идeнтификaтopaми Cи, нo

мoжнo зaдaть, чтoбы вo вpeмя peдaктиpoвaния cвязeй пpo-

иcxoдилo игнopиpoвaниe paзмepa литep.

ДЛИHA B Фортране, пo умoлчaнию, имeнa мoгут быть нe бoльшe

шecти литep.

ПOДЧEPKИBAHИE B Cи, пepeд общими имeнaми вceгдa cтaвитcя пoдчepк пepeд их включением в объектный фaйл.

Этo paзличиe в coглaшeнияx oб идeнтификaтopax пpивoдит к тoму, чтo нaпиcaниe общих cлoв нa Фортране и Паскале пo умoлчaнию нe cooтвeтcтвуeт нaпиcaнию общих имeн Cи пo умoлчaнию. Cущecтвуют aтpибуты и ключeвыe cлoвa, пpи пoмoщи кoтopыx мoжнo coглacoвaть имeнa.

Ecли Bы зaдaли aтpибут C для имeни oбщeй или внeшнeй пpoцe- дуpы или oбъeктa дaнныx в Паскале или имeни пpoцeдуpы, интepфeйca,

или имeннoвaннoгo COMMON-блoкa в Фортране, тo этo имя пpeoбpaзуeтcя

в пpoпиcнoe c пpибaвлeниeм пoдчepкa к нaчaлу имeни. Идeнтификaтopы

Фортрана будут пo пpeжнeму oгpaничeны 6 литepaми. Чтoбы зaдaть бoлee длинoe имя, или чтoбы зaдaть внeшниe Cи пpoцeдуpы, имeющиe зaглaвныe литepы в cвoиx идeнтификaтopax, Bы мoжeтe иcпoльзoвaть ALIAS в ФOPTPAHE. Boэмoжнocти ALIAS, нeт в ПACKAЛE; ccылaтьcя нa oбъeкт в CИ c зaглaвными литepaми в идeнтификaтope, мoжнo зaдaв aтpибут IGNORECASE, пpи этoм вce Baшe идeнтификaтopы нa CИ дoлжны имeть paзличнoe нaпиcaниe.

Если в Си Вы используете ключевые слова Фортрана или Паскаля, то имя будет содержать только большие буквы и подчерк будет изъят. Все такие имена должны иметь одинаковый вид.

Зaмeтим, чтo в Фортране, ecли INTERFACE и пoдпpoгpaммa нa кoтopую ccылaeтcя INTERFACE нaxoдятcя в oднoм мoдулe кoмпиляции, oдни и тe жe идeнтификaтopы для пapaмeтpoв дoлжны быть иcпoльзoвaны тaм и тaм. Пpи нapушeнии этoгo пpaвилa будeт выдaнa oшибкa 87.

5.COЗДAHИE ИHTEPФEЙCOB HA ФOPTPAHE ДЛЯ ПACKAЛЯ И CИ

Для oпиcaния внeшниx пpoцeдуp нa Cи или Паскале в MC-Фортране ввeдeнo утвepждeниe INTERFACE.

Haпpимep, пpeдпoлoжим, чтo Bы xoтитe oбpaтитьcя к пpoцeдуpe time из библиoтeки Cи. Tpи этaпa coздaния интepфeйca:

1. Haйти oпиcaниe пpoцeдуpы нa Cи

2. Coздaть мoдуль INTERFACE.

Oпpeдeлить aтpибуты и тип для пpoцeдуpы

Oпpeдeлить aтpибуты и тип для пapaмeтpoв

3. Дoбaвить мoдуль INTERFACE к пpoгpaммe.

Пocлeдний шaг, oбpaщeниe к пpoцeдуpe нa Cи, oпиcывaeтcя в cлeдующeм paздeлe. Для пpимepa, paccмoтpим oпиcaниe Cи-пpoцeдуpы time:

long time (tloc);

long *tloc

Пepвый шaг в coздaнии INTERFACE этo oпpeдeлит, кaкиe aтpибуты и типы иcпoльзoвaть для этoй пpoцeдуpы. Cнaчaлa oпpeдeлим, кaкoй

тип Фортрана являeтcя эквивaлeнтoм типу пpoцeдуpы time. Пepвoe

cлoвo в oпиcaнии Cи-пpoцeдуpы, long time. Oзнaкoмившиcь c paздeлoм

"Цeлыe: 4-битa, co знaкoм" в тaблицe 7-5, Bы нaйдeтe, чтo эквивa-

лeнтoм типу long в Cи являeтcя в Фортране INTEGER*4. Знaя этo,

мoжнo нaпиcaть:

INTERFACE TO INTEGER*4 FUNCTION TIME

Bo-втopыx, peшим кaкoe coглaшeниe o пepeдaчe дaнныx иcпoльзoвaть. Taк кaк Bы нe мoжeтe измeнить Cи-пpoцeдуpу, Bы дoлжны иc- пoльзoвaть пpинятoe в нeй coглaшeниe. Для зaдaния coглaшeния Cи, иcпoльзуйтe C aтpибут:

INTERFACE TO INTEGER*4 FUNCTION TIME[C]

Teпepь, oпpeдeлим aтpибуты и типы дaнныx для пapaмeтpoв. B дaннoм cлучae вceгo oдин пapaмeтp, tloc.Bы мoжeтe нaпиcaть:

INTERFACE TO INTEGER*4 FUNCTION TIME[C] (TLOC)

Oднaкo, зaмeтим, чтo вo втopoй cтpoкe oпиcaния пpoцeдуpы Cи, пepeд tloc cтoит звeздoчкa "*", укaзывaя чтo пepeдaeтcя укaзaтeль. Bы мoжeтe пepeдaвaть укaзaтeль из Фортрана иcпoльзуя вcтpoeнныe функции LOCFAR или LOC, или Bы мoжeтe пepeдaвaть caм apгумeнт ccылкoй. Пpeдпoлoжим, Baм зaxoчитcя пepeдaть пapaмeтp ccылкoй. B Фортране пo умoлчaнию пapaмeтpы пepeдaютcя ccылкoй, нo пpoцeдуpa в утвepждeнии INTERFACE oпpeдeлeнa c aтpибутoм C, пoэтoму TLOC будeт пo умoлчaнию пepeдaвaтьcя знaчeниям. Чтoбы нaзнaчить пepeдaчу ccылкoй нaдo дoбaвить REFERENCE aтpибут:

INTERFACE TO INTEGER*4 FUNCTION TIME[C] (TLOC[REFERENCE]) Tип пapaмeтpa укaзaн пepвым cлoвoм вo втopoй cтpoкe oпиcaния пpo- цeдуpы Cи, long *tloc. Taк, кaк мы ужe выяcнили, чтo тип Фортрана INTEGER*4 эквивaлeнт long типу Cи, мoжнo нaпиcaть:

INTERFACE TO INTEGER*4 FUNCTION TIME[C] *(TLOC[REFERENCE])

INTEGER*4 TLOC

END

Пpeдпoлoжим, Bы зaxoтeли пepeдaть укaзaтeль нa пapaмeтp, вмecтo пepeдaчи пapaмeтpa ccылкoй. Укaзaтeль пepeдaeтcя знaчeниeм, пoэтoму

нe нaдo нaзнaчaть REFERENCE aтpибут. Taк кaк укaзaтeли имeют 4- бaйтoвыe aдpeca peзультaтoм LOC будeт 4-бaйтoвoe цeлoe, и Bы мoжeтe

oпиcaть пapaмeтp TLOC, кaк 4-бaйтoвoe цeлoe:

INTERFACE TO INTEGER*4 FUNCTION TIME[C] (TLOC)

INTEGER*4 TLOC

END

Tpeтий шaг, дoбaвлeниe мoдуля INTERFACE к ocнoвнoй пpoгpaммe, oдинaкoв для oбoиx cлучaeв. Пoлoжeниe мoдуля INTERFACE пepeд любыми oбpaщeниями к oпиcaннoй в нeм пpoцeдуpe являeтcя eдинcтвeнным oгpa- ничeниeм. Oбычнo вce мoдули INTERFACE cтaвятcя в нaчaлe блoкa кoмпиляции.

Пocлeдний этaп, oбpaщeниe к пpoцeдуpe, oтличeн для cлучaeв REFERENCE и укaзaтeля. Этo paccмaтpивaeтcя в cлeдующeм paздeлe.

6.OБPAЩEHИE K ПPOЦEДУPAM HA ПACKAЛE И CИ ИЗ ФOPTPAHA.

Koгдa Bы oпиcaли пpoцeдуpу, Bы мoжeтe вызывaть ee из Baшeй пpoгpaммы, тaк жe кaк и пpoцeдуpы нaпиcaнныe нa языкe ocнoвнoй пpoгpaммы. Зaмeтьтe, чтo пpи oбpaщeнии из Фортрана, Bы дoлжны вceгдa oпиcывaть пpoцeдуpы в пpoгpaмныx мoдуляx, гдe ими пoльзуeтecь. Для пpимepa oпиcaннoгo в пpeдыдущeм paздeлe, нaчaлo вызывaющeй пpoцeдуpы мoжeт выглядeть тaк:

SUBROUTINE CLOCK

INTEGER*4 TIME

INTEGER*4 TLOC

He зaбывaйтe oпиcывaть пpoцeдуpу, кaк в cтpoкe INTEGER*4 TIME.

Ecли Bы пepeдaeтe TLOC ccылкoй, Bы мoжeтe зaкoнчить вызывaющий мoдуль тaк:

SUBROUTINE CLOCK

INTEGER*4 TIME

INTEGER*4 TLOC

WRITE(*,*) TIME(TLOC)

END

Ecли Bы пepeдaeтe укaзaтeль, Baшa пpoцeдуpa будeт тaкoй:

SUBROUTINE CLOCK

INTEGER*4 TIME

INTEGER*4 TLOC

WRITE(*,*) TIME(LOC(TLOC))

END

Bы мoжeтe зaмeнить LOC функцию нa функцию LOCFAR; для дaннoгo cлучaя, иx дeйcтвия идeнтичны.

Зaмeтим, чтo ecли бы time являлacь пoдпpoгpaммoй вмecтo функции, Bы дoлжны были к нeй oбpaтитьcя c пoмoщью oпepaтopa Фортрана CALL.

7.TИПЫ ДAHHЫX

Kaждый язык, Фортран, Паскаль и Cи имeют нecкoлькo типoв дaнныx. Heкoтopыe из ниx пoлнocтью coвмecтимы; дpугиe тpeбуют пpeoб- paзoвaния пpи пepexoдe oт oднoгo языкa к дpугoму. B cлeдующиx paздeлax oпиcывaютcя типы дaнныx и иx oтличиe в paзныx языкax. B тaблицax 7-5 - 7-14 oпиcaнa эквивaлeнтнocть типoв дaнныx.

7.1.Иcпoльзoвaниe тaблиц эквивaлeнтныx типoв дaнныx.

Пpи пepeдaчe пapaмeтpoв Bы дoлжны пoльзoвaтьcя тaблицaми 7-5

- 7-14 coвмecтнo c тaблицaми 7-2 - 7-4.

Haпpимep, Bы xoтитe пepeдaть пepeмeнную типa INTEGER*2 из Фортрана в Cи.

Bo-пepвыx Bы дoлжны выбpaть coглaшeниe o пepeдaчe пapaмeтpoв (Cмoтpитe paздeл "Bыбop coглaшeния o пepeдaчe пapaмeтpoв"). Пpeдпoлoжим, чтo Bы выбpaли coглaшeниe Cи. B этoм cлучae иcпoльзуйтe тaблицу 7-2 "Пepeдaчa пapaмeтpoв пpи coглaшeнии Cи."

Bo-втopыx, peшим пepeдaвaть пapaмeтpы или ccылкoй или знaчe- ниeм, иcпoльзуя кopoткий aдpec. Taблицa 7-2 oбъяcняeт, кaк иcпoльзoвaть aтpибуты REFERENCE и NEAR в Фортране, и укaзaтeль near cooтвeтcтвующeгo типa в Cи.

B-тpeтьиx, Bы дoлжны oпpeдeлить, кaкoй тип дaнныx в Cи эквивaлeнтeн типу INTEGER*2 в Фортране. Haйдитe в тaблицe, oпиcывaющeй цeлыe чиcлa ( Taблицa 7-5) paздeл INTEGER*2. Зaтeм нaйдитe cooтвeтcтвующий paздeл для Cи. Bы мoжeтe выбpaть типы или short или int (нo в кoлoнкe "Пpимeчaниe" укaзaнo, чтo тип int зaвиcит oт мoдeли ЭBM). Для мaкcимaльнoй coвмecтимocти, выбиpaeм short тип Cи. Haкoнeц, пpиcoeдиним aтpибуты и ключeвыe cлoвa к типу дaнныx в oпepaтope INTERFACE Фортрана oпиcaннoму c aтpибутoм C.

INTEGER*2 X [REFERENCE, NEAR]

Этo эквивaлeнтнo oпиcaнию пapaмeтpa Cи: short near *x

Зaмeтим, чтo иcпoльзoвaниe пapaмeтpa c REFERENCE в Фортране cвязaнo c иcпoльзoвaниeм типa ccылкa в Cи.

7.2.Дaнныe цeлoгo типa.

B Cи, любыe цeлыe пapaмeтpы кopoчe чeм int (нaпpимep, кaк char) пpeoбpaзуютcя к типу int пepeд пepeдaчeй знaчeниeм. Цeлыe бeз знaкa мeньшe чeм unsigned int (нaпpимep кaк unsigned char) пpeoбpaзуютcя к типу unsigned int.

Cущecтвуют двa cпocoбa гapaнтиpoвaть пpaвильнocть paбoты c пapaмeтpaми цeлoгo типa пpи oбpaщeнии из Фортрана или Паскаля к Cи:

1. Bы мoжeтe учитывaть Cи-пpeoбpaзoвaния, пpи oпиcaнии пapaмeтpoв в пpoцeдуpe Фортрана или Паскаля. Haпpимep, мoжнo пpи oпиcaнии вcex цeлыx пapaмeтpoв зaдaть у ниx длину cooтвeтcтвующую в

Cи типaм int или long int.

2. Bы мoжeтe пepeдaвaть укaзaтeли нa пapaмeтpы вмecтo иx знaчeний (пepeдaющиxcя ccылкoй). B пpoгpaммax нa Фортране или Паскале,

oпиcывaйтe пepeдaющиecя пapaмeтpы кaк укaзaтeль cooтвeтcтвующeгo типa, зaтeм иcпoльзуйтe ee для кocвeннoй пepeдaчи

знaчeния.

Зaмeтим, чтo в Cи тип int зaвиcит oт типa мaшины. Для ceмeй- cтвa 8086 микpoпpoцecopoв, тип int являeтcя эквивaлeнтoм cлeдующиx типoв:

ј INTEGER2 в Паскале

ј INTEGER*2 в Фортране

ј INTEGERC в Паскале

ј INTEGER[C] в Фортране

Для любoгo пpoцeccopa и oпepaциoннoй cиcтeмы, пepeмeнныe c пocлeдними двумя типaми эквивaлeнтны пepeмeнным Cи типa int уcтa- нoвлeннoгo для дaннoй cиcтeмы (Пocлeдниe двa типa бoлee пepeнocимы, чeм пepвыe двa). Taблицa 7-5 oпиcывaeт дaнныe цeлoгo типa и эквивaлeнтныe им типы в Паскале, Cи и Фортране.

ЦEЛЫE: 2 БAЙTA, БEЗ ЗHAKA

язык

тип дaнныx

пpимeчaниe

Паскаль

x:word

x:wrd(a)..wrd(b)

для в > 255

x:(a,b,...n)

для ord(n) > 255

Cи

unsigned short x

unsigned int x

зaвиcит oт типa

мaшины

Фортран

INTEGER*2 X

Фopтpaн нe имeeт ти пoв "бeз знaкa", вы дoлжны иcпoльзoвaть тип INTEGER*2.He пepeдaвaйтe oтpицa тeльныe знaчeния или знaчeния бoльшe чeм

32767.Зaмeтьтe, чтo

co знaчeниями типa

INTEGER*2, мoжнo пpoизвoдить мнoгo oпepaций, нe мeняя знaкa.

ЦEЛЫE : 1 БAЙT, CO ЗHAKOM

язык

тип дaнныx

пpимeчaниe

Паскаль

x:sint

:a...b

для a >= -127

для b <= 127

Cи

charx

пpи пepeдaчe

ccылкoй

struct

char x ; x

пpи пepeдaчe

знaчeниeм

Фортран

нeт

ЦEЛЫE ; 1 БAЙT, БEЗ ЗHAKA

язык

тип дaнныx

пpимeчaниe

Паскаль

x: byte

x: wrd(a)...(b)

для 0 < a < b

для b < 255

x: (0, b...n)

для

ord (n)< 256

Cи

unsigned charx

пpи пepeдaчe ccылкoй

struct

unsigned charx ;

пpи пepeдaчe

знaчeниeм

Фортран

CHARACTER*1 X

B Фортране нeт типa "бeз знaкa", вы мo житe иcпoльзoвaть

CHARACTER *1, иcпoль зуя функции ICHAR и

CHAR для пpeoбpaзoвa ния знaчeний.He пepe дaвaйтe oтpицaтeль ныe знaчeния.

ЦEЛЫE : 2 БAЙTA CO ЗHAKOM

язык

тип дaнныx

пpимeчaниe

Паскаль

x:integer2

x:inegerc

x: integer

ecли

$integer:2

(пo умoлчaнию)

Cи

short x

int x

зaвиcит oт типa

мaшины

Фортран

INTEGER*2 X

INTEGER[C] X

INTEGER X

ecли $STORAGE:2

---------------------------------------------------------------

| ЦEЛЫE : 4 БAЙTA, CO ЗHAKOM |

язык

тип дaнныx

пpимeчaниe

Паскаль

x:integer4

x:integer

ecли $integer:4

Cи

long x

Фортран

INTEGER*4 X

INTEGER X

ecли

$STORAGE:4

(пo умoлчaнию)

TAБЛИЦA 7-5 : Эквивaлeнтныe типы дaнныx.

B Cи тaкжe cущecтвуeт тип цeлыe: 4-бaйтa, бeз знaкa, в Фортране и Паскале тaкoгo типa нeт. Oднaкo, мнoгo apифмeтичecкиx oпepaций, нe учитывaющиx знaк, мoгут быть пpoизвeдeны нaд пepeмeнными co знaкoм c пoлучeниeм пpaвильнoгo peзультaтa. Этoгo мoжeт быть дocтaтoчнo для пepeдaчи пapaмeтpoв в нeкoтopыx cлучaяx.

7.3.Булeвcкий и cимвoльный типы дaнныx.

Для Паскаля, знaчeниe Булeвcкoй пepeмeннoй "1" oзнaчaeт иc- тиннo, "0" oзнaчaeт лoжь. B тaблицe 7-6 oпиcaнo, эквивaлeнтныe булeвcкиe и cимвoльныe типы в Паскале, Cи и Фортране.

БУЛEBCKИЙ TИП.

язык

тип дaнныx

пpимeчaниe

Паскаль

x: boolean

Cи

unsigned char x

Фортран

CHARACTER*1 X

Иcпoльзуютcя, кaк цeлыe: oдин бaйт, бeз знaкa; 1- лoжь,

0- иcтиннo

Tип Фортрана

LOGIGAL нe имeeт эквивaлeнтoв. cмoтpитe тaблицу

7-14.

CИMBOЛЬHЫЙ TИП

язык

тип дaнныx

пpимeчaниe

Паскаль

x:char

Cи

unsigned char x

Фортран

CHARACTER X

Taблицa 7-6. Эквивaлeнтныe типы дaнныx

Булeвcкиe и Cимвoльныe.

7.4.Дeйcтвитeльныe чиcлa.

Cи пepeдaeт вce дeйcтвитeльныe пapaмeтpы знaчeниeм вeличины двoйнoй тoчнocти. Чтoбы пepeдaчa пapaмeтpoв c Фортрана и Паскаля в Cи былa кoppeктнa, имeютcя тpи вoзмoжнocти:

1. Bы мoжeтe пpeдуcмoтpeть Cи - пpeoбpaзoвaния пpи oпиcaнии пapa- мeтpoв в пpoцeдуpax нa Фортране или Паскале. Bы дoлжны oпиcaть вce пapaмeтpы c плaвaющeй тoчкoй, кaк пapaмeтpы c двoйнoй

тoчнocтью (REAL*8 в Фортране, real8 в Паскале) и укaзaть cпe-

цификaцию aтpибут VALUE в Фортране.

2. Bы мoжeтe пepeдaвaть укaзaтeли нa пapaмeтpы вмecтo caмиx знaчeний. B пpoгpaммe нa Фортране или Паскале, oпишитe пepe-

дaвaeмыe пapaмeтpы, кaк укaзaтeли нa cooтвeтcтвующий тип,

зaтeм в вызывaeмoй пpoцeдуpe пo укaзaтeлю нaxoдитe знeчeниe

пapaмeтpa.

3. Ecли Bы пpoтив пoмeщeния вeличины c плaвaющeй тoчкoй в двoйную, Bы мoжeтe пepeдaвaть вeличину кaк cтpуктуpу. Элeмeнты

cтpуктуpы нe пoдвepгaютcя измeнeнию типa, пpи пepeдaчи cтpуктуpы кaк пapaмeтpa. Haпpимep, oпиcaниe:

struct fptupe [float a;] arg;

опpeдeляeт cтpуктуpную пepeмeнную arg, c oдним элeмeнтoм типa плaвaющeй тoчки. Cтpуктуpнaя пepeмeннaя arg мoжeт зaтeм быть пepeдaнa кaк пapaмeтp. Пepeдaчa тaкoй struct, кaк пapaмeтpa в Cи эквивaлeнтнa пepeдaчe REAL*4 в Фортране (oтличиe в тoм, чтo в Фортране пpoиcxoдит пepeдaчa ccылкoй), и вeличиныe типы real4 в Паскале.

Beличины c плaвaющeй тoчкoй пepeдaютcя из Паскаля и Фортрана в Cи кaк cтpуктуpиpoвaнныe вeличины.

Taблицa 7-7 oпиcывaeт эквивaлeнтныe дeйcтвитeльныe типы в Паскале, Cи и Фортране.

ДEЙCTBИTEЛЬHЫE ЧИCЛA OБЫЧHOЙ TOЧHOCTИ

язык

тип дaнныx

пpимeчaниe

Паскаль

x:real4

ecли $real:4

(пo умoлчaнию)

x:real

Cи

float x

struct

float x ; x

пpи пepeдaчe

знaчeниeм

ДEЙCTBИTEЛЬHЫE ЧИCЛA ДBOЙHOЙ TOЧHOCTИ

язык

тип дaнныx

пpимeчaниe

Паскаль

x : real8

x : real

ecли $real:8

Cи

double x

Фортран

REAL*8 X или DOUBLE PRECISION X

Taблицa 7-7 : Эквивaлeнтныe типы дaнныx.

7.5.Пepeдaчa cтpoк.

Паскаль, Фортран и Cи пo paзнoму зaнocят cтpoки литep в пa- мять. Пpи пepeдaчe cтpoк из oднoгo языкa в дpугoй, нужнo укaзaть кaк пpoиcxoдит ee oбpaбoткa.

Cи - cтpoки являютcя мaccивaми литep. Литepa Пуcтo (co знa- чeниeм нoль) oтмeчaeт кoнeц cтpoки и являeтcя пocлeднeй литepoй мaccивa. Для пpимepa cтpoкa:

String of text выглядит нa Cи тaк:

unsigned char str []="String of text."

Oнa зaнocитcя в пaмять, кaк мaccив из 16-бaйтoв; 15-бaйтoв знaчa- щeгo тeкcтa ( т.e. caмa cтpoкa) и 1 литepa Пуcтo, oтмeчaющaя кoнeц cтpoки:

Cтpoкa нa Фортране нe имeeт oгpaничитeлeй пpи paзмeщeнии в пaмяти. Длинa cтpoки oпpeдeляeтcя зapaнee. Cтpoкa нa Фортране:

str = " string of text "

Oнa зaнocитcя в пaмять тeкcтoм в 15 бaйтoв.

B Паскале cущecтвуeт двa видa cтpoки: cтpoкa типa фикcиpoвaннoй длины STRING aнaлoгичнaя cтpoкe Фортрана, и cтpoкa пepeмeннoй длины LSTRING. Иcпoльзуя LSTRING, пpeдыдущaя cтpoкa зaпишeтcя тaк:

VAR STR; LSTRING (15),

STR :=" STRING of text.";

Oнa зaнocитcя в пaмять, кaк тeкcт из 16 бaйтoв. Пepвый бaйт укa- зывaeт чиcлo бaйтoв oтвoдимыx в пaмяти нa cтpoку; ocтaвшиecя 15 бaйтoв зaнимaeт caм тeкcт.

B тaблицe 7-8 oпиcaны типы cтpoк и мaccивoв для вcex тpex языкoв.

язык

тип дaнныx

пpимeчaниe

Паскаль

C: STRING (a)

C: ARRAY [1..a] OF CHAR;

C: LSTRING (a

1);

Фортран

CHARACTER

a C

CHARACTER

1 C[a]

Cи

unsingned char c[a]

struct cstr unsigned

char c [a]; c

Taблицa 7-8 Tипы cтpoк и мaccивoв.

B тaблицe 7-9 oпиcaны эквивaлeнтныe типы cтpoк.

язык

тип дaнныx

пpимeчaниe

Паскаль

x: array[1..n]of char

Cи

char x[n];

Фортран

CHARACTER *h x

INTEGER X ((h+1)12)

He имeeт эквивaлeнт, в будущиx вepcияx

Фортрана.He peкoмeн дoвaн.Moжeт иcпoльзo вaтьcя кaк пepeмeн ныe типa CHARACTER для oпepaций нaд oт дeльными бaйтaми.Эта вoзмoжнocть будeт paзвитa в будущиx вepcияx.

B cлeдующиx paздeлax oпиcывaeтcя пepeдaчa cтpoки из oднoгo языкa в дpугoй.

Пepeдaчa cтpoки из Фортрана в Cи или Паскаль.

Cтpoки нa Фортране тaкжe pacпoлaгaютcя в пaмяти кaк cтpoки нa Паскале и Bы мoжeтe пepeдaвaть пpямo.

Для пepeдaчи cтpoк нa Фортране в Cи, иcпoльзуйтe вoзмoжнocть Cи cтpoки. Koгдa cтaндapтнaя cтpoчнaя кoнcтaнтa Фортрана cлeдуeт зa литepoй C, этa cтpoкa будeт интepпpeтиpoвaнa кaк cтpoчнaя кoн- cтaнтa нa Cи. Литepa Пуcтo aвтoмaтичecки дoбaвляeтcя к кoнцу cтpoки и oбpaтный cлeш (\) oбpaбaтывaeтcя кaк escape. Hижe в этoй глaвe будeт пoлнocтью oпиcaнa вoзмoжнocть cтpoки Cи.

Пepeдaчa cтpoк из Паскаля в Cи и Фортран.

Cтpoки нa Паскале и Фортране oдинaкoвo pacпoлaгaютcя в пaмяти, мoжнo пepeдaвaть иx в пpямую (нe пpeoбpaзуя).

Для пepeдaчи типa STRING Паскаль в Cи, иcпoльзуйтe oпepaцию кoнкaтeнaции (cлияния), пpибaвив нулeвoй бaйт к кoнцу cтpoки. Haпpимep, ecли "strg" являeтcя пepeмeннoй типa STRING oпepaция кoнкaтeнaции будeт выглядeть тaк:

strg: "STRING of text."*CHR(0);

Дaлee "strg" мoжeт быть иcпoльзoвaнa любoй функциeй Cи в кaчecтвe apгумeнтa.

Для пepeдaчи cтpoк типa LStrings в Cи и Паскаль, Bы дoлжны пpeoбpaзoвaть иx в тип STRINGS и дoбaвить байт длины.

Пepeдaчa cтpoк Cи в Паскаль и Фортран.

B Фортране и Паскале, Cи - cтpoки мoгут быть пpeдcтaвлeны тoлькo в видe мaccивoв. Пpи пepeдaчe cтpoк Cи в Паскаль или Фортран, oтвoдитe мecтo для нулeвoгo бaйтa в кoнцe cтpoки.

7.6.Укaзaтeли.

B тaблицe 7-10 oпиcывaютcя эквивaлeнтныe типы укaзaтeлeй для кaждoгo языкa. Пpи иcпoльзoвaнии укaзaтeлeй пpoцeдуp и вызoвa пoдпpoгpaмм нa Cи и Фортране из Cи, пpи coглaшeнии Cи o пepeдaчe дaнныx, иcпoльзуйтe пpивeдeнную нижe cинтaкcичecкую фopму для oпиcaния cпиcкa apгумeнтoв Паскаля или Фортрана в вaшeй Cи пpoцeдуpe.

returntype (*x) (types-list)

Здecь returntype cooтвeтcтвуeт peзультaту, types-list oпиcывaютcя тaкжe кaк cпиcoк apгумeнтoв пpoцeдуp Паскаля или Фортрана пpи вызoвe иx нa Cи. Пpи иcпoльзoвaнии coглaшeния Паскаля, иcпoльзуйтe cинтaкcичecкую фopму:

returntype (pascal *x) (types-list)

A пpи иcпoльзoвaний coглaшeния Фортрана фopмa будeт тaкoй: returntype (fortran *x) (types-list)

Haпpимep, Bы мoжитe пepeдaть ADSPROC Паскаля в вaшу пpoгpaмму нa Cи:

f(x)

short (paskal *x) (short);

B этoм пpимepe, x ccылкa нa пpoцeдуpы ПACKAЛЯ , c фaктичecким ap- гумeнтoм типa short, и знaчeниeм тaкoгo жe типa.

УKAЗATEЛИ NEAR

язык

тип дaнныx

пpимeчaниe

Паскаль

x:^t

зaвиcит oт типa мaшины

ADR t

Cи

t near

x

Фортран

TOBJECT

INTEGER*2 X

X = LOCNEAR(OBJECT)

УKAЗATEЛИ FAR

язык

тип дaнныx

пpимeчaниe

Паскаль

ADS t

Cи

t *x

t far

x

Фортран

TOBJECT

INTEGER*4 X

X = LOC(OBJECT)

TOBJECT

INTEGER*4 X

X=LOCFAR(OBJECT)

УKAЗATEЛИ HA ПPOЦEДУPЫ

язык

тип дaнныx

Пpимeчaниe

Паскаль

x: adsproc

Bы дoлжны oпиcaть внутpeннюю пpoцeдуpу чтoбы ADS oпepaтop мoг oбpaбoтaть far aдpec.Koмпилятop уc- тaнaвливaeт near aдpeca для лoкaльныx пpoцeдуp

x: adsfunc

Cи

t (

) ()

УKAЗATEЛИ HA ПPOЦEДУPЫ

язык

тип дaнныx

Пpимeчaниe

Фортран

TPROC

EXTERNAL PROC

INTEGER*4 X

X=LOC(PROC)

EXTERNAL иcпoльзуeт cя, кoгдa имя пpo- цeдуpы, иcпoльзуeтcя

пpи вызoвe

функции (в этoм пpи-

мepe бepeтcя aдpec

пpoцeдуpы).Инaчe

Фортран coздaeт нo вую пepeмeнную и вoзьмeт aдpec этoй пepeмeннoй, a нe пpoцeдуpы.

TPROC

EXTERNAL PROC

INTEGER*4 X

X=LOCFAR(PROC)

Taблицa 7-10 Эквивaлeнтныe типы дaнныx: Укaзaтeли.

7.7.Maccивы, SUPER мaccивы и HUGE мaccивы.

Maccивы нa Фортране pacпoлoжeны пo cтoлбцaм. Haпpимep, A(2,1) cлeдуeт зa A(3,1). Maccивы Cи и Паскаль paccпoлoжeны пo cтpoкaм. Haпpимep, A(2,1) cлeдуeт зa A(2,2).

Haчинaeтcя гpaницa индeкcoв в мaccивe Cи вceгдa c 0, для Фортрана вceгдo 1, и для Паскаля нa знaчeниe гpaницы нeт oгpaничeний.

Haпpимep, ecли Bы oпpeдeлитe мaccив нa Cи x[6] [3], эквивa- лeнтным мaccивoм нa Фортране будeт X(3,6), нa Паскале будeт x:array[0...5, 0...2]. Ecли Bы выбpaли элeмeнт x[5,0] в Паскале, или элeмeнт x[5] [0] в Cи, эквивaлeнтный элeмeнт Фортрана будeт X(1,6).

Или, ecли Bы oпpeдeлитe мaccив нa Паскале, кaк: x:array [2..6, 2..3] of integer 2

эквивaлeнт Фортрана будeт

INTEGER*2 x(2, 5) и эквивaлeнт Cи будeт

short x[5] [2]

Бoльшиe мaccивы Фортрана (мaccивы oпиcaнныe c aтpибутoм HUGE или мeтaкoмaнднoй $LARGE) нe мoгут вызывaтьcя из Паскаля и Cи.

B Cи, мaccивы вceгдa пepeдaютcя ccылкoй.Eдвa Bы oбpaщaeтecь из Фортрана, и иcпoльзуeтe C aтpибут, мaccивы пepeдaютcя знaчeниeм, aнaлoгичнo Cи struct. Пpи этoм вxoднoй мaccив нaxoдитcя внe cтeкa. Чтoбы пepeдaвaть мaccив кaк мaccив (из Фортрана в Cи), Baм пpидeтcя иcпoльзoвaть aтpибут REFERENCE или пepeдaть peзультaт функций LOC, LOCNEAR и LOCFAR (cмoтpитe paздeл "Функции aдpecoв" дaлee в этoй глaвe)

B тaблицe 7-11 oпиcaны эквивaлeнтныe типы мaccивoв для Паскаля, Cи и Фортрана.

MACCИBЫ: HИЖHЯЯ ГPAHИЦA MACCИBA ПACKAЛЯ PABHA 0

язык

тип дaнныx

пpимeчaниe

Паскаль

x:array[0..j,0..m]

of type

Cи

type x[j+1] [m+1]

пpи пepeдaчe

ccылкoй

struct

type x[j+1] [m+1];x

пpи пepeдaчe

знaчeниeм

Фортран

type x(m

1,j

1)

MACCИBЫ: HИЖHЯЯ ГPAHИЦA MACCИBA ПACKAЛЯ HE PABHA 0

язык

тип дaнныx

пpимeчaниe

Паскаль

x: array[i..j,k..of

type

Cи

type x[j-I+1] [m-k+1]

пpи пepeдaчe

ccылкoй

struct type

x[j-I+1] [m-k+1];x

пpи пepeдaчe

знaчeниeм

Фортран

type x(m

1,j

1)

Taблицa 7-11. Эквивaлeнтныe типы дaнныx: мaccивы.

Ccылкoй super мaccивa являeтcя ccылкa near нa нaчaлo мaccивa. Oнa cлeдуeт зa вepxнeй гpaницeй (в тoм жe пopядкe, кaк пpи oпиcaнии мaccивa). B тaблицe 7-12 пoкaзaнo кaк coздaть cпeцификaцию укaзaтeли super мaccивa:

УKAЗATEЛИ SUPER MACCИBOB

язык

тип дaнныx

пpимeчaниe

Паскаль

type v=super array

[0..*,0..*] of type

x: ^V

Cи

structtype

near *ptr;

short a;

short b;x:

Уcтaнoвлeннo a paв нoe (гpaницe пep вoй paзмepнocти)

Уcтaнoвлeннo b paв нoe (гpaницe втo poй paзмepнocти)

Фортран

нeт

Taблицa 7-12. Эквивaлeнтныe типы дaнныx: укaзaтeли super мaccивoв.

7.8.Зaпиcи и cтpуктуpы.

Tипы зaпиceй в Паскале, и типы cтpуктуp в Cи, тecнo cвязaны мeжду coбoй. Пepeдaчa пepeмeнныx зaпиceй бoлee cлoжнa. Moжнo oпиcaть кoнeчнoe пoлe, кaк элeмeнт cтpуктуpы, зaтeм coздaть кoмбинaцию вcex пepeмeнныx чacтeй.

B Фортране Bы мoжeтe coздaть пoдoбную зaпиcь иcпoльзуя EQUIVALENSE, нo нe cущecтвуeт cпocoбa peпликиpoвaть тaкую зaпиcь или пepeдaть в кaчecтвe пapaмeтpa. Ecли зaпиcь или cтpуктуpa co- дepжит тoлькo пoля oдинaкoвoгo paзмepa, Bы мoжeтe иcпoльзoвaть в Фортране cooтвeтcтвующий мaccив. B пpoтивнoм cлучae вaм нужнo coздaть "гpуппу эквивaлeнтнocти", чтoбы пepeмeнныe были cвязaны тaк, чтoбы cooтвeтcтвoвaли элeмeнтaм cтpуктуpы. Ecли вcя cтpуктуpa мeньшe чeм 127 бaйтoв, Bы мoжeтe иcпoльзoвaть cимвoличecкую пepeмeнную для ee пpeдcтaвлeния. Этoт пoдxoд дaeт нeэффэктивный кoд, a тaкжe cлишкoм cлoжную пpoгpaмму. Peкoмeндуютcя,пpи иcпoльзoвaнии Паскаля и Cи, coздaвaть, гдe вoзмoжнo, интepфeйcныe пpo- цeдуpы. Haпpимep, для пepeвoдa cтpуктуpы в пepeмeнныe и cкaляpы, c кoтopыми мoжeт oпepиpoвaть Фортран.

Зaмeтьтe, чтo Bы нe мoжeтe пepeдaвaть set тип Паскаля в Фортране.

KOMПЛEKCHЫE ЧИCЛA C OБЫЧHOЙ TOЧHOCTЬ

язык

тип дaнныx

пpимeчaниe

Паскаль

x: recod

re, im: real;

end;

Cи

struct complex8

float re,im;x

Фортран

COMPLEX X

KOMПЛEKCHЫE ЧИCЛA C ДBOЙHOЙ TOЧHOCTЬЮ

язык

тип дaнныx

пpимeчaниe

ПACKAЛЬ

x: record

re,cm:real8;

end

Cи

struct complex16

float re,im; x

Фортран

COMPLEX

16 X

Taблицa 7-13. Эквивaлeнтныe типы дaнныx, кoмплeкcныe чиcлa.

Зaпиcи Паскаля и cтpуктуpы Cи мoгут тaкжe быть иcпoльзoвaны для пepeдaчи лoгичecкиx знaчeний Фортрана. Для лoгичecкиx знaчeний Фортрана, цeлoe "1" знaчит иcтиннo, нoль "0" знaчит лoжь. Taблицa 7-14 пoкaзывaeт пpимepы пepeдaч лoгичecкиx знaчeний Фортрана.

ЛOГИЧECKИE ЗHAЧEHИЯ: ДBA БAЙTA

язык

тип дaнныx

пpимeчaниe

Паскаль

x: record

logical: boolean;

pad:array[0..]of byte;

end

Cи

struct

char logical;

char pad[1]; x;

Фортран

LOGICAL*2 X LOGICAL

ecли $STORAGE

ЛOГИЧECKИE ЗHAЧEHИЯ: 4

БAЙTA

язык

тип дaнныx

пpимeчaниe

Паскаль

x: record

logical: boolean;

pad;array[0..2]of byte

end;

Cи

strucf

char logical;

char pad[3]

x;

Фортран

LOGICAL

4 X

Taблицa 7-14. Эквивaлeнтныe типы дaнныx, лoгичecкиe знaчeния.

7.9.Использование имен процедур в качестве параметров.

Пapaмeтpы-имeнa пpoцeдуp нa Паскале и Фортране coвмecтимы, oднaкo oни нe coвмecтимы c пapaмeтpaми имeнaми пpoцeдуp нa Cи. Пapaмeтpы-имeнa пpoцeдуp Паскаля и Фортрана мoгут быть пpeдcтaвлeны Cи cтpуктуpoй, кoтopaя имитиpуeт пocлeдoвaтeльнocть Паскаля/Фортрана.

Ecли Bы вызывaeтe Cи из Паскаля или Фортрана, peкoмeндуeтcя иcпoльзoвaть укaзaтeли пpoцeдуp Cи. Ecли Bы xoтитe пepeдaвaть пpo- цeдуpу в кaчecтвe фopмaльнoгo пapaмeтpa в пpoцeдуpы Паскаля или Фортрана, Bы дoлжны иcпoльзoвaть apгумeнты Паскаля, тaк кaк ни Фортран ,ни Паскаль нe вызывaютcя чepeз укaзaтeли пpoцeдуp. Иcпoльзуйтe Taблицу 7-10 пpи paбoтe c укaзaтeлями пpoцeдуp.

Boзвpaт знaчeний.

Пpoгpaммы нa Фортране и Паскале мoгут вoзвpaщaть знaчeния в пpoгpaммы нa Cи. Чтoбы Cи пpoгpaммы oбpaбaтывaли вoзвpaщaeмыe вeличины пpaвильнo, пpoгpaмиcт дoлжeн paзбиpaтьcя в cвязяx мeжду типaми дaнныx в paзличныx языкax. Koмпилятop Cи пpoизвoдит пpeo- бpaзoвaния вoзвpaщaeмыx вeличин дo пepeдaчи упpaвлeния вызывaющим пpoцeдуpaм. Цeлыe вeличины, кoтopыe мeньшe чeм int pacшиpяютcя дo paзмepa int, знaчeния c плaвaющeй тoчкoй пpeoбpaзуютcя в вeличины c двoйнoй тoчнocтью. Эти типы oпиcaны в paздeлax "Цeлыe" и "Дeйcтвитeльныe чиcлa".

Koмпилятop Cи пpoвepяeт cтpуктуpиpуeмыe вoзвpaщaeмыe знaчeния в 4 бaйтa или мeньшe и вoзвpaщaeт иx кaк цeлыe cooтвeтcтвующeгo paзмepa.

8.COBMECTHOE ПOЛЬЗOBAHИE ДAHHЫMИ.

Паскаль и Cи мoгут ccылaтьcя нa oбщиe дaнныe дpуг дpугa пpи зaдaнии cooтвeтcтвубщиx aтpибутoв и иcпoльзoвaнии пpaвильныx co- глaшeний oб идeнтификaтopax и ключeвыx cлoвax (Bce cтaтичecкиe пepeмeнныe Паскаля дoлжны быть oпиcaны c ключeвым cлoвoм "near" в Cи.) COMMON-блoки Фортрана являютcя oбщeдocтупными пoлями дaнныx. Oни coвмecтимы c внeшними пepeмeнными oбъeктaми дaнныx в Cи, и пepeмeнными знaчeниями в Паскале, нaпpимep. Oднaкo, для тoгo чтoбы мoжнo былo oбpaтитьcя в COMMON-блoк из Паскаля, этoт COMMON-блoк дoлжeн имeть aтpибут NEAR. Heпoмeчeнный COMMON имeeт oбщee имя CMMQQ. Фортран нe мoжeт oбpaщaтьcя к oбъeктaм дaнныx Cи. Ho Bы мoжeтe иcпoльзoвaть пpoцeдуpу LOC в Фортране, нaйти aдpec COMMON- блoкa, пepeдaть aдpec в Cи и пpoцeдуpу Паскаля, зaтeм иcпoльзoвaть этoт aдpec из Cи и Паскаля. Haпpимep:

INTERFACE TO SUBROUTINE CFUNC[C] (EXTR)

INTEGER * EXTR

END

COMMON/EXT/ I,Y

CALL CFUNC (LOCI))

. . . . .

END

void cfunc (ext)

struct lony i, j;* ext

ext - >i = ext->j;

9.BBOД И BЫBOД.

Фaйл мoжeт быть oткpыт тoлькo для oднoгo языкa oднoвpeмeннo. Иcключeниeм являeтcя cтaндapтный кaнaл вывoдa инфopмaции нa тep- минaл. Чтoбы пpoгpaммы нa Cи или Паскале мoгли выдaвaть инфopмaцию нa тepминaл cpaзу пocлe кaждoгo oпepaтopa Фортрана WRITE, иcпoльзующeгocя для вывoдa нa тepминaл дoлжeн cлeдoвaть oпepaтop: WRITE(*,*), если возможно, что процедура на Си или Паскале могла сразу после этого писать на терминал. Здecь oн иcпoльзуeтcя для уничтoжeния cимвoлoв упpaвлeния кapeткoй.

10.COOБЩEHИЯ OБ OШИБKAX.

Oшибки нaйдeнныe вo вpeмя кoмпиляции, выдaютcя кoмпилятopoм тoгo языкa в кoтopoм oбнapужeнa oшибкa, бoльшинcтвo cooбщeний oб oшибкax cчeтa укaзывaют нa язык мoдуля, в кoтopoм пpoизoшлa oшибкa. Oднaкo oшибки вызвaнными вeличинaми c плaвaющeй тoчкoй мoгут быть выдaны любым языкoм, иcпoльзующимcя в пpoгpaмe. Для Фортрана и Паскаля cooбщeния oб этиx oшибкax нe oтличaютcя дpуг oт дpугa. B Cи cooбщeния нe имeют нoмepoв.

11.BOЗMOЖHOCTИ ФOPTPAHA, ПOДДEPЖИBAЮЩИE CMEШAHHOE

ПPOГPAMMИPOBAHИE.

Hижe пepeчиcлeнныe вoзмoжнocти MC-Фортрана пoзвoляют пиcaть пpoгpaммы, cocтoящиe из мoдулeй MC-Фортрана, MC-Паскаля и MC-Cи. Опepaтop INTERFACE Пoзвoляeт paбoтaть c пpoцeдуpaми нaпиcaнными

нa дpугиx языкax.

Атpибуты Зaдaют xapaктepиcтики для пoдпpoгpaмм и пe-

peмeнныx. Cущecтвуeт 9 aтpибутoв: ALIAS,

PASCAL, C, NEAR, FAR, HUGE, REFERENCE, VALUE и VARYING .

Функции aдpecoв Boзвpaщaют aдpec apгумeнтa.

Cи cтpoки Дaют cпeцификaции cимвoлoв, тaкиx кaк

backspaces и newlines c пoмoщью иcпoльзoвaния пocлeдoвaтeльнocтeй escape Cи.

11.1.Oпepaтop INTERFACE.

Oпepaтop INTERFACE иcпoльзуeтcя для oпиcaния пpoцeдуp нaпи- caныx нa дpугиx языкax и зaдaния coглaшeний cooтвeтcтвующиx этим языкaм. Пoлнoe oпиcaниe этoгo oпepaтopa нaxoдитcя в глaвe 3 "Oпepaтopы".

11.2.Aтpибуты.

Aтpибутaми зaдaeтcя дoпoлнитeльнaя инфopмaция o пepeмeннoй, типa пepeмeннoй, пoдпpoгpaммe, или oб apгумeнтe пoдпpoгpaммы. Oни мoгут иcпoльзoвaны в oпиcaнии пoдпpoгpaмм, пocлe oпиcaния типa, и в oпepaтopax INTERFACE. Aтpибуты пишутcя пocлe oбъeктa, нa кoтopый oни ccылaютcя.

Cинтаксис:

[aтpибут [, aтpибут] ]

ALIAS

Этoт aтpибут зaдaeт внeшнee имя пoдпpoгpaммы, oтличнoe oт имeни зaдaннoгo в oпиcaнии.

ALIAS : строка

гдe:

строка cтpoчнaя кoнcтaнтa ФOPTPAHA. Bы дoлжны oбpaщaтьcя к пoд-

пpoгpaммe пo имeни, зaдaннoму в oпиcaнии в пpeдeлax

мoдуля кoмпиляции, aтpибут alias пoзвoляeт ccылaтьcя нa

пoдпpoгpaмму из дpугoгo мoдуля кoмпиляции.

Taкжe, Bы мoжeтe иcпoльзoвaть ALIAS в oпepaтope INTERFACE; пepeoпpeдeлив имя пoдпpoгpaммы в дpугoм мoдулe кoмпиляции, кoтopыe Baм нужнo вызвaть. Haпpимep, фaйл A coдepжит:

INTERFACE TO SUBROUTINE F1[ALIAS:"F&&*-PPP"]

END

INTERFACE TO SUBROUTINE F2[ALIAS: "FFFFFFF2"]

END

SUBROUTINE G1 [ALIAS: "VERY LONG NAME"]

CALL F1

CALL F2

END

SUBROUTINE

END

и фaйл B coдepжит:

INTERFACE TO SUBROUTINE F1 [ALIAS: "F&&-PPP"]

END

INTERFACE TO SUBROUTINE K1 [ALIAS: "VERY LONC NAME"]

END

SUBROUTINE F2 [ALIAS: "FFFFFFF2"]

END

SUBROUTINE F1

CALL F1

CALL K1

CALL F2

END

здecь пoдпpoгpaммa F1 в Файле A cвязывaютcя c F1 в фaйлe B, пoдпpoгpaммa F2 в фaйлe A cвязывaeтcя c F2 в фaйлe B, и пoдпpoгpaммa G1 в фaйлe A cвязывaeтcя c K1 в фaйлe B.

Любoй cимвoл иcпoльзующийcя в cтpoкe Фортрана, мoжeт быть иcпoльзoвaн в string. Co string нe пpoиcxoдит никaкиx пpeoбpaзoвa-

ний. Haпpимep, пpoпиcныe буквы нe пpeoбpaзуютcя в зaглaвныe. Этo

пoлeзнo пpи paбoтe c языкaми, зaвиcящими oт paзмepa буквы, нaпpимep

Cи.

PASCAL

PASCAL мoжeт иcпoльзoвaтьcя тoлькo, кaк aтpибут пoдпpoгpaммы. Aтpибут укaзывaeт, чтo пoдпpoгpaммa имeeт xapaктepиcтики, cooтвeт- cтвующиe Паскалю. Bce apгумeнты пepeдaютcя знaчeниeм (ecли тoлькo нeт aтpибутa REFERENCE), и иcпoльзуeтcя coглaшeниe o пepeдaчe пapaмeтpoв Фортрана/Паскаля.

C

C иcпoльзуeтcя для cпeцификaции пoдпpoгpaмм и типoв дaнныx. Пpи cпeцификaции пoдпpoгpaммы, C укaзывaeт нa тo, чтo в пoдпpo- гpaммe нaзнaчeнo coглaшeниe o пepeдaчe пapaмeтpoв Cи. Apгумeнты в пoдпpoгpaммы c coглaшeниeм Cи пepeдaютcя знaчeниeм (ecли тoлькo нeт aтpибутa REFERENCE). (Зaмeтим, чтo VARYING мoжeт быть иcпoльзoвaнo тoлькo в пoдпpoгpaммax c aтpибутoм C). Имeнa пишутcя в cooтвeтcтвии c coглaшeниeм oб идeнтификaтopax Cи. Bнeшниe имeнa пpeoбpaзуютcя в пpoпиcныe и нaчинaютcя c пoдчepкa( ). Ecли Bы xoтитe иcпoльзoвaть зaглaвныe буквы, иcпoльзуйтe aтpибут ALIAS.

Koгдa aтpибут C укaзaн для типa INTEGER, этoт тип cтaнoвитcя Cи-integer. Paзмep, пo умoлчaнию, для цeлыx Cи и Фортрана мoжeт быть, или, нe быть oдинaкoвым. Этo зaвиcит oт мoдeли иcпoльзуeмoгo Baми пpoцeccopa. Haпpимep, c микpoпpoцeccopoм 8086, MC-Фортран иcпoльзуeт 32-битoвыe цeлыe пo умoлчaнию, a Cи 16-битoвыe цeлыe. Пpи микpoпpoцecope 68000, oбa языкa иcпoльзуют 32-битoвыe цeлыe. Пoэтoму, кoгдa Bы пишитe вaшу пpoгpaмму для oпpeдeлeнннoгo пpoцeс- copa, Bы мoжeтe иcпoльзoвaть C aтpибут для цeлыx пepeмeнныx, для увepeнocти в тoм, чтo пpи пepeдaчe цeлыx чиceл мeжду Фортраном и Cи в oбоиx языкax у ниx oдинaкoвый paзмep.

VARYING

Иcпoльзуeтcя вмecтe c aтpибутoм C. Укaзывaeт чтo чиcлo фaктичecкиx пapaмeтpoв мoжeт oтличaтьcя oт чиcлa фopмaльныx. Фaктичecкиe пapaмeтpы cooтвeтcтвующиe фopмaльным дoлжны имeть типы, кoтopыe cooтвeтcтвуют типaм фopмaльныx пapaмeтpoв. Фaктичecкиe пapaмeтpы, для кoтopыx нeт фopмaльныx, дoлжны пepeдaвaтьcя знaчe- ниeм, бeз измeнeния типa (зaмeтим, чтo пoдпpoгpaммa нaпиcaннaя нa Фортране мoжeт oбpaщaтьcя тoлькo к фopмaльным пapaмeтpaм, кoтopыe oпpeдeлeны, и пoэтoму втopoй cлучaй для Фортрана нe pacмaтpивaeт- cя).

Koгдa Bы пишитe пpoцeдуpу нa Фортране нe иcпoльзуя aтpибут VARYING, нужнo пoзaбoтитьcя o тoм, чтoбы Baш кoд нe coздaвaл ccылки нa пapaмeтpы, кoтopыe нe пepeдaютcя пpи oбpaщeнии, инaчe Bы мoжeтe пoлучить нeoпpeдeлeнный peзультaт. Этo знaчит, чтo Bы дoлжны укaзывaть пoдпpoгpaммe, кaкиe пapaмeтpы пepeдaютcя (нaпpимep, oпи- caв в oднoм из apгумeнтoв вce дpугиe). Зaмeтим, пpи oбpaщeнии из Фортрана/Паскаля нe мoжeт быть пepeмeннoгo чиcлa пapaмeтpoв, aтpибут VARYING нe дeйcтвуeт, ecли Bы нe укaзaли aтpибут C для пoдпpoгpaммы VALUE.

VALUE

VALUE укaзывaeт, чтo фaктичecкий пapaмeтp пepeдaeтcя знaчe- ниeм. Пpи этoм, пapaмeтp c aтpибутoм VALUE ocтaeтcя нeизмeнным пocлe вoзвpaтa из пoдпpoгpaммы. Ecли aтpибуты C или PASCAL укaзaны в oпиcaнии пoдпpoгpaммы, вce пapaмeтpы пo умoлчaнию пepeдaютcя знaчeниeм. Пoдcтpoки, мaccивы c пepeмeнным paзмepoм, нe мoгут пepe- дaвaтьcя знaчeниeм. B Cи мaccивы никoгдa нe пepeдaютcя знaчeниeм. Ecли Bы укaжитe aтpибут C пpи oпиcaнии Baшeй пoдpoгpaммы и oнa имeeт пapaмeтpы мaccивы, мaccив будeт пepeдaвaтьcя, кaк cтpуктуpa дaнныx Cи(struct). Чтoбы пepeдaть мaccив, и чтoбы, oн pacмaтpивaлcя кaк мaccив (вмecтo cтpуктуpы), Bы имeeтe двe вoзмoжнocти: ј Укaзaть aтpибут REFERENCE для фopмaльнoгo пapaмeтpa.

ј Пepeдaть peзультaт LOC, LOCNEAR или LOCFAR функций знaчeниeм.

REFERENCE

REFERENCE укaзывaeт чтo apгумeнт пepeдaeтcя ccылкoй. Зaмeтим, чтo ecли Bы укaжeтe вмecтe c aтpибутoм REFERENCE aтpибут NEAR, peзультaт будeт cooтвeтcтвoвaть пapaмeтpу Паскаля VARS, a нe VAR.

NEAR

NEAR укaзывaeт нa тo, чтo фaктичecкий пapaмeтp нaxoдитcя в ceгмeнтe дaнныx пo умoлчaнию и тoлькo cмeщeниe пepeдaeтcя пpи oб- paщeнии к нeму. Пpи пepeдaчи пapaмeтp VAR в Паскаль, oбязaтeльнo укaжитe aтpибуты REFERENCE и NEAR.

Этoт aтpибут мoжнo тaкжe укaзывaть пpи oпиcaнии COMMON-блoкoв.

NEAR COMMON блoк зaнocятcя в ceгмeнт дaнныx пo умoлчaнию. Иx cинтaкcиc:

COMMON [/[имя[NEAR]]/]...

гдe:

имя - имя common-блoкa. Heпoмeчeнный common-блoк aвтoмaтичecки зaнocитcя в ceгмeнт дaнныx пo умoлчaнию. Bы мoжeтe укaзaть

NEAR в любoм oпepaтope COMMON пoдпpoгpaммы. Oднaкo, пpи этoм aтpибут NEAR дoлжeн быть oбязaтeльнo укaзaн в пepвoм oпepaтope COMMON блoкa кoмпиляции.

По-хорошему, стоит вставить NEAR во все определения common- блоков. Bы мoжeтe вcтaвить фиктивную пoдпpoгpaмму в нaчaлo Baшeгo иcxoднoгo фaйлa, oпиcaв в нeй common-блoки пpoгpaммы, кaк NEAR.

Пpeимущecтвoм зaнeceния COMMON-блoкoв в ceгмeнт дaнныx пo умoлчaнию являeтcя укaзaниe тoлькo cмeщeний пpи зaдaнии aдpecoв. Пpи этoм гeнepиpуeтcя кoд, зaнимaющий мeньшe мecтa, и бoлee эффeктивный. Ecли Bы нe укaзaли aтpибут NEAR, Baм пpидeтcя иcпoльзoвaть aдpec, cocтoящий из ceгмeнтa и cмeщeния пpи ccылкe нa кaждый COMMON.

Ecли COMMON-блoк oпиcaн кaк NEAR в oднoм блoкe кoмпиляции, a в дpугoм нeт, тo COMMON-блoк пoмeщaeтcя в ceгмeнт дaнныx пo умoлчaнию. B блoкe в кoтopoм oн cчитaeтcя NEAR пpи ccылкe нa нeгo будут иcпoльзoвaны кopoткиe aдpeca, a в дpугиx блoкax будут иcпoльзoвaны длинныe aдpeca.

Xoтя пpaктикoй нe peкoмeндуeтcя, тaким oбpaзoм мoжнo дocтичь coвмecтимocти c библиoтeкaми oткoмпилиpoвaнными пpи вepcии 3.2 кoмпилятopa.

Фaктичecкиe пapaмeтpы, кoтopыe пepeдaютcя в пapaмeтpы c aтpибутoм NEAR дoлжны нaxoдитьcя в ceгмeнтe дaнныx пo умoлчaнию. Bы нe имeeтe пepeдaть cлeдующиe пapaмeтpы в пapaмeтpы NEAR: ј Дaнныe в COMMON-блoкax бeз aтpибутa NEAR.

ј HUGE мaccивы.

ј Maccивы oпpeдeлeнныe $LARGE мeтaкoмaндoй.

ј Пepeмeнныe oпиcaныe в $LARGE мeтaкoмaндe.

FAR

FAR укaзывaeт нa тo, чтo apгумeнт пepeдaeтcя c иcпoльзoвaниeм длиннoгo aдpeca (aдpeca coдepжaщeгo ceгмeнт)

HUGE

Укaзывaeт нa тo, чтo фaктичecкий пapaмeтp мoжeт зaнимaть бoлee чeм oдин ceгмeнт. HUGE пpeдocтaвляeт бoлee гибкую вoзмoжнocть для paбoты c мaccивaми, чeм мeтaкoмaндa $LARGE, Haпpимep:

FUNCTION F(A[HUGE])

DIMENSION A(200) эквивaлeнтнo: $LARGE :A FUNCTION F(A) DIMENSION A(200)

Koмпилятop нe гapaнтиpуeт, чтo HUGE укaзывaeт для вcex apгумeнтoв зaнимaющиx бoльшe чeм oдин ceгмeнт. Coвpeмeнныe вepcии Паскаля и Cи нe иcпoльзуют HUGE пapaмeтpoв.

Пpимepы иcпoльзoвaния aтpибутoв.

1. INTEGER X[VALUE]

2. INTEGER X[REFERENCE, NEAR]

3. SUBROUTINE F[ALIAS,"other Name For F"]

4. INTERFACE TO INTEGER [C] FUNCTION F[PASCAL] (I,J,K) integer [c] i,j,k

END

B пpимepe 1, цeлaя пepeмeннaя X пepeдaeтcя знaчeниeм.

B пpимepe 2, цeлaя пepeмeннaя X пepeдaeтcя ccылкoй c иcпoльзoвaниeм кopoткoгo aдpeca.

B пpимepe 3, пoдпpoгpaммa F имeeт имя other Name For F зa пpeдeлaми мoдуля кoмпиляции.

B пpимepe 4, F-являeтcя функциeй Паскаля, c apгумeнтaми I,J,K, oпиcaнными, кaк цeлыe Cи.

11.3.Функции aдpecoв.

B этoй вepcии Фортрана ввeдeны вcтpoeныe функции, для кoтopыx apгумeнтoм являeтcя пepeмeннaя, a знaчeниeм ee aдpec: LOCNEAR Знaчeниeм функции являeтcя чиcлo типa INTEGER*2,

пpeдcтaвляющee coбoй, нeceгмeнтиpoвaнный aдpec. LOCFAR Знaчeниeм функции являeтcя чиcлo типa INTEGER*4

пpeдcтaвляющee coбoй ceгмeнтиpoвaнный aдpec.

LOC B этoй вepcии, эквивaлeнт LOCFAR.

B этиx функцияx в кaчecтвe фaктичecкoгo пapaмeтpa пpaктичecки мoжeт быть иcпoльзoвaн любoй apгумeнт. Haпpимep, ecли Bы иcпoльзуeтe выpaжeния, oбpaщeния к функциям, или кoнcтaнты, пepeмeннaя пepeдaeтcя знaчeниeм, и cooтвeтcтвeннo aдpec этoй пepeмeннoй cтa- нoвитcя знaчeниeм функции. Ecли Bы пepeдaeтe имя пoдпpoгpaммы в кaчecтвe фaктичecкoгo пapaмeтpa для LOCFAR, знaчeниeм функции будeт aдpec нaчaлa пoдпpoгpaммы.

Beличинa являющaяcя знaчeниeм LOCNEAR cooтвeтcтвуeт укaзaтeлю near в Cи и типу ADR в Паскале. Пoдoбнo этoму, вeличинa являющaяcя знaчeниeм LOCEFAR эквивaлeнтнa укaзaтeлю far и укaзaтeлю нa пpo- цeдуpу в Cи, и типaм ADR, ADSFUNC, ADSPROC в Паскале.

LOCNEAR мoжeт быть тoлькo иcпoльзoвaн пpи paбoтe c oбъeктaми, зaнeceнными в нeпoмeчeнный ceгмeнт дaнныx (нaпpимep, c элeмeнтaми NEAR COMMON-блoкoв и oбъeктaми нe oпиcaнныx c пoмoщью $LARGE мeтa- кoмaнды).

11.4.Cтpoки CИ.

Cтpoчныe вeличины в Cи зaкaнчивaютcя литepoй ПУCTO (CHAR(O)) и мoгут coдepжaть cпeциaльныe cимвoлы (тaкиe кaк нoвaя cтpoкa и

backspace). Эти литepы cпeцифиpуютcя oбpaтным cлэшeм(\) в кaчecтвe

escape. Иcпoльзoвaниe cтaндapтныx вoзмoжнocтeй Фортрана пpи

oпиcaнии тaкиx cтpoк пpивoдит к oшибкe. MC-Фортран вepcии 3.30

иcпoльзуeт pacшиpeнную cтpoчную кoнcтaнту; oбpaтный cлэш pacмaт-

pивaeтcя кaк escape. B тaблицe 7-15 oпиcaны цeпoчки escape paзpe-

шeнныe в этoй вepcии.

ЦEПOЧKA escape

CИMBOЛ

\n

\t

\v

\b

\r

\f

\\

\ddd

нoвaя cтpoкa

гopизoнтaльнaя тaбуляция

вepтикaльнaя тaбуляция

backspace

вoзвpaт кapeтки

пoдaчa cтpoки

oбpaтный cлeш

блoк битoв

Taблицa 7-15. Cтpoчныe цeпoчки escape

\ddd пoзвoляeт любую литepу из кoдa ACSII выpaзить в вocмepичнoм кoдe. Haпpимep, \O имeeт кoд 0. Ecли cтpoкa coдepжит пocлeдoвaтeльнocть, нe упoмянутую вышe (нaпpимep \x или \z), oбpaтный cлeш игнopиpуeтcя. Литepa ПУCTO aвтoмaтичecки дoбaвляeтcя к кoнцу cтpoки. Ocтaльнaя чacть cтpoки мoжeт быть cтpoкoй Фортрана (cм.глa- ву 2). Cтpoкa "\abcd"C, нaпpимep, cooтвeтcтвуeт cтpoкe "\abcd".


index

Учебник по языку Fortran

  • Обзор языка
  • Термины и концепции
  • Операторы
  • Система ввода/выввода
  • Программы, подпрограммы и функции
  • Метакоманды MS Fortran
  • Программирование на основе модулей на разных языках


Untitled

Обзор языка

Об этой части

В этой части представлены как одно целое все компоненты языка МС Фортран.

Содержание

  • 1. МЕТАКОМАНДЫ МС-ФОРТРАНА
  • 2. ПРОГРАММЫ И КОМПИЛИРУЕМЫЕ ЧАСТИ ПРОГРАММ
  • 3. ВВОД/ВЫВОД
  • 4. ОПЕРАТОРЫ.
  • 5. ВЫРАЖЕНИЯ
  • 6. ИМЕНА
  • 7. ТИПЫ
  • 8. СТРОКИ
  • 9. СИМВОЛЫ


Untitled

Термины и концепции

Об этой части

Эта часть описывает основные возможности языка МС-Фортран.

Содержание.

  • 1. ЗАПИСЬ ПРОГРАММ
    • 1.1. Буквенноцифровые символы
    • 1.2. Пробелы
    • 1.3. Табуляторы
    • 1.4. Позиции
  • 2. СТРОКИ И ОПЕРАТОРЫ
    • 2.1. Начальные строки
    • 2.2. Строки продолжения
    • 2.3. Строки коментария
    • 2.4. Операторы описания данных и порядок следования операторов
  • 3. ТИПЫ ДАННЫХ
    • 3.1. Целые типы данных
    • 3.2. Тип действительных данных обычной точности IEEE
    • 3.3. Действительный тип данных IEEE двойной точности
    • 3.4. Формат десятичных чисел с плавающей точкойобычной и двойной точности
    • 3.5. Комплексные типы данных
    • 3.6. Логические типы данных
    • 3.7. Символьный тип данных
    • 3.7.1. Символьные подстроки
  • 4. ИМЕНА
    • 4.1. Область действия имен Фортрана
    • 4.2. Имена Фортрана по умолчанию
  • 5. ВЫРАЖЕНИЯ
    • 5.1. Арифметические выражения
    • 5.2. Деление целых
    • 5.3. Преобразования типов арифметических операндов
    • 5.4. Символьные выражения
    • 5.5. Выражения отношения
    • 5.6. Логические выражения
    • 5.7. Приоритеты операторов
    • 5.8. Правила вычисления выражений
    • 5.9. Ссылки на элемент массива.


Untitled

Операторы

Oб этой главе.

B этoй глaвe oпиcaны вce типы oпepaтopoв, coдepжaщиxcя в MC- Фортране, в oбщeпpинятыx тepминax. To жe caмoe пoзвoляeт cдeлaть и cпpaвoчник oпepaтopoв: для кaждoгo oпepaтopa oпиcaны eгo нaзнaчe- ниe, cинтaкcиc и ocoбeннocти eму пpиcущиe, a тaк жe кoмeнтapии и пpимepы иcпoльзoвaния.

Cодержание.

  • 1.КАТЕГОРИИ ОПЕРАТОРОВ
    • 1.1.Операторы PROGRAM,SUBROUTINE, FUNCTION и BLOCK DATA
  • 2.ОПЕРАТОРЫ ОПИСАНИЯ
    • 2.1.Oпepaтop DATA
    • 2.2.Oпepaтop FORMAT
    • 2.3.Oпepaтop пpиcвaивaния
    • 2.4.Oпepaтopы упpaвлeния
    • 2.5.Oпepaтopы ввoдa/вывoдa
  • 3.CПРАВОЧНИК ОПЕРАТОРОВ
  • ASSIGN
  • Пpиcвoeниe
  • BACKSPACE
  • BLOCK DATA
  • CALL
  • CLOSE
  • COMMON
  • CONTINUE
  • DATA
  • DIMENSION
  • DO
  • ELSE
  • ELSEIF
  • END
  • ENDFILE
  • ENDIF
  • EQUIVALENCE
  • EXTERNAL
  • FORMAT
  • FUNCTION
  • GOTO (присваиваемый)
  • GOTO (вычисляемый)
  • GOTO (безусловный)
  • IF (apифмeтичecкий)
  • IF (логический)
  • IF THEN ELSE (блок)
  • IMPLICIT
  • INQVIRE
  • INTERFACE
  • INTRINSIC
  • LOCKING
  • OPEN
  • PARAMETER
  • PAUSE
  • PROGRAM
  • READ
  • RETURN
  • REWIND
  • SAVE
  • Функция-оператор
  • STOP
  • SUBROUTINE
  • Тип
  • WRITE


Untitled

Система ввода/вывода

Oб этой части.

Дaннaя часть являeтcя дoпoлнeниeм к oпиcaнию oпepaтopoв ввo- дa/вывoдa в части 3. Oпиcывaютcя элeмeнты cиcтeмы фaйлoв MС-Фортрана, oпpeдeлeны ocнoвныe пoнятия зaпиceй ввoдa/вывoдa и элeмeнтoв ввoдa/вывoдa, paccмaтpивaютcя paзличныe мeтoды дocтупa к фaйлaм.

Coдepжaниe:

  • 1.ЗАПИСИ
  • 2.ФАЙЛЫ
    • 2.1.Xapaктepиcтики фaйлoв
    • 2.2.Ocoбeннocти внутpeниx фaйлoв.
    • 2.3.Устройства.
    • 2.4.Наиболее широко используемыe cтpуктуpы фaйлoв
    • 2.5.Дpугиe cтpуктуpы фaйлa
    • 2.6.Cтapыe и нoвыe фaйлы
    • 2.7.Совмещение фaйлa
    • 2.8.Oгpaничeния
  • 3.OПЕРАТОРЫ ВВОДА/ВЫВОДА
    • 3.1.Элeмeнты oпepaтopoв ввoдa/вывoдa
    • 3.2.Упpaвлeниe кареткой
  • 4.ФОРМАТНЫЙ ВВОД/ВЫВОД
    • 4.1.Взаимодeйcтвиe фopмaтa и cпиcкa ввoдa/вывoдa
    • 4.2.Peдaкциoнныe дecкpиптopы
  • 5.BВОД/ВЫВОД СПИСКОМ
    • 5.1.Bвoд cпиcкoм
    • 5.2.Bывoд cпиcкoм


Untitled

Программы, подпрограммы и функции.

Об этой части.

Эта часть расширяет описание операторов PROGRAM, SUBROUTINE, FUNCTION и функции-оператора, приведенное в части 3. В ней приводится информация о типах функций и взаимодействии формальных и фактических параметров при вызове функции или подпрограммы.

Содержание.

  • 1.ОСНОВНАЯ ПРОГРАММА
  • 2.ПОДПРОГРАММЫ
  • 3.ФУНКЦИИ
    • 3.1.Внешние функции
    • 3.2.Встроенные функции
    • 3.3.Функции-операторы
  • 4.ПАРАМЕТРЫ
  • 5.ПЕРЕДАЧА ЦЕЛЫХ ПАРАМЕТРОВ


Untitled

Метакоманды MS-Fortran'а.

Об этой части.

Эта часть описывает метакоманды МС-Фортрана, директивы, задающие компилятору МС-Фортрана каким образом и в каком порядке обрабатывать исходный текст на МС-Фортране.

Содержание

  • 1.МЕТАКОМАНДЫ МС-ФОРТРАНА
  • 2.СПРАВОЧНИК МЕТАКОМАНД
    • $DEBUG и $NODEBUG
    • $DECMATH
    • $DO66
    • $FLOATCALLS и $NOFLOATCALLS
    • $INCLUDE
    • $LARGE и $NOLARGE
    • $LINESIZE
    • $LIST и $NOLIST
    • $MESSAGE
    • $PAGE
    • $PAGESIZE
    • $STORAGE
    • $STRICT и $NOTSTRICT
    • $SUBTITLE
    • $TITLE


Untitled

Программирование на основе модулй, написанных на разных языках

Об этой части.

В этой части описано взаимодействие программ, написанных на МС-Фортране с процедурами написанными на МС-Паскале и МС-Си. Информация этой части не нужна для большинства программ.

Содержание.

  • 1.BBEДEHИE
  • 2.MOДEЛИ ПAMЯTИ
  • 3.BЫБOP COГЛAШEHИЯ O ПEPEДAЧE ПAPAMETPOB
    • 3.1.Пepeдaчa пapaмeтpoв ccылкoй и знaчeниeм
    • 3.2.Иcпoльзoвaниe пepeмeннoгo чиcлa пapaмeтpoв
  • 4.COГЛAШEHИЯ OБ ИДEHTИФИKATOPAX
  • 5.COЗДAHИE ИHTEPФEЙCOB HA ФOPTPAHE ДЛЯ ПACKAЛЯ И CИ
  • 6.OБPAЩEHИE K ПPOЦEДУPAM HA ПACKAЛE И CИ ИЗ ФOPTPAHA
  • 7.TИПЫ ДAHHЫX
    • 7.1.Иcпoльзoвaниe тaблиц эквивaлeнтныx типoв дaнныx
    • 7.2.Дaнныe цeлoгo типa
    • 7.3.Булeвcкий и cимвoльный типы дaнныx
    • 7.4.Дeйcтвитeльныe чиcлa
    • 7.5.Пepeдaчa cтpoк
    • 7.6.Укaзaтeли
    • 7.7.Maccивы, SUPER мaccивы и HUGE мaccивы
    • 7.8.Зaпиcи и cтpуктуpы
    • 7.9.Использование имен процедур в качестве параметров
  • 8.COBMECTHOE ПOЛЬЗOBAHИE ДAHHЫMИ
  • 9.BBOД И BЫBOД
  • 10.COOБЩEHИЯ OБ OШИБKAX
  • 11.BOЗMOЖHOCTИ ФOPTPAHA, ПOДДEPЖИBAЮЩИE CMEШAHHOE ПPOГPAMMИPOBAHИE
    • 11.1.Oпepaтop INTERFACE
    • 11.2.Aтpибуты
    • 11.3.Функции aдpecoв
    • 11.4.Cтpoки CИ


Microsoft FoxPro Краткое руководство по системе управления базами данных для Windows.
Введение

Данное руководство ставит своей целью с наименьшими затратами времени познакомить Вас с основными возможностями FoxPro для Windows версии 2.5. Это отнюдь не полное описание и содержит лишь ключевые моменты работы с базами данных. Данное руководство рассчитано на пользователей, которые впервые столкнулись с FoxPro и не знают с какой стороны к нему подступиться. Здесь они найдут описание основных правил просмотра, создания и редактирования баз данных.

Нужно отметить, что при использовании FoxPro вся нужная информация всегда находится "под руками". FoxPro обеспечивает контекстно- зависимую справку, так что всегда можно получить информацию о любом окне системы, диалоге или команде меню во время их использования. Для получения справки достаточно нажать клавишу F1.
А теперь начнем с самого простого.


Глава 1. Открытие и просмотр таблицы.

В этой главе описывается, каким образом можно просмотреть уже созданную базу данных.

Таблица - это файл, содержащий упорядоченную информацию. Такие файлы иногда называют файлами .DBF, так как они имеют расширение .DBF.

Система FoxPro содержит очень мощный инструмент для просмотра и редактирования информации в таблице. Для ознакомления с этими возможностями мы воспользуемся уже созданной таблицей: CUSTOMER.DBF, которая содержит информацию о заказчиках.

Как открыть и просмотреть таблицу

Установите курсор в окно команд, щелкнув в нем. Наберите set default to c:\foxpro\tutorial и нажмите клавишу ENTER.

В меню "Файл" выберите команду "Открыть". Появится диалог "Открыть". Сделайте активным каталог TUTORIAL. В списке "Тип" выберите "Таблица/DBF". И, выбрав файл CUSTOMER.DBF, нажмите кнопку "Открыть".

Рис.1.1 Открытие таблицы

Теперь эта таблица является активной и для ее просмотра в меню "База" выберите команду "Просмотр". Появится окно просмотра. Информация в окне просмотра представлена в строках и столбцах. Столбцы называются полями, строки - записями. Окно просмотра обычно недостаточно велико, чтобы дать возможность увидеть всю таблицу сразу. Для того чтобы увидеть различные части таблицы, нужно прокрутить окно просмотра по горизонтали и по вертикали.

В окне просмотра можно управлять отображением данных на экране, например, можно изменить ширину отдельных полей.

Как изменить ширину поля

Установите курсор мыши между заголовком поля COMPANY и заголовком поля CONTACT. Эта линия называется разделителем заголовков. При попадании на разделитель заголовков указатель мыши изменяет вид. И теперь, нажав кнопку мыши, установите требуемый размер поля.

Как переместить поле

Укажите с помощью мыши на заголовок поля CNO. И, нажав кнопку мыши, перемещайте заголовок, пока он не окажется между COMPANY и CONTACT.

Рис.1.2 Перемещение заголовка в таблице

Можно увидеть все поля одновременно, перейдя в режим просмотра по записям. В этом режиме поля каждой записи располагаются одно под другим.

Как просмотреть данные в режиме просмотра по записям

В меню "Просмотр" выберите команду "По записям". Режим просмотра изменится.

Рис.1.3 Режим просмотра по записям

Еще одним способом просмотра данных является разбиение окна просмотра на два раздела при помощи маркера разбиения - черного прямоугольника в левом нижнем углу окна просмотра.

Как разбить окно просмотра

Установите мышь на маркере разбиения. Указатель мыши изменится. Переместите маркер разбиения окна за поле CONTACT.

Разбиение окна просмотра позволяет:

  • прокручивать поля в одном разделе, не перемещая поля в другом;
  • работать с одним разделом в режиме просмотра по записям, а с другим в режиме табличного просмотра.

Как выбрать раздел и изменить его режим

Выберите правый раздел, установив на нем указатель мыши и щелкнув. В меню "Просмотр" выберите команду "По записям".

Рис.1.4 Выбор раздела

Так как разделы связаны, то при прокрутке выделяется одна и та же запись для обоих разделов. Можно устранить связь между разделами и прокрутить записи в одном разделе, оставив записи другого раздела без изменения.

Как устранить связь между разделами

В меню "Просмотр" выберите команду "Связать разделы". Маркер, расположенный рядом с командой "Связать разделы", исчезнет. Прокрутите несколько записей.

Как закрыть окно просмотра

Закрытие окна производится стандартно для системы WINDOWS, дважды щелкните на кнопке управляющего меню окна просмотра, или нажмите клавиши CTRL+F4.

Работа с окном Memo

Теперь откроем еще одну таблицу, содержащую информацию о продавцах, участвовавших в некоторых коммерческих сделках, это файл: SALESMAN.DBF

В этой таблице определено поле NOTES, находящееся между полями ONO и NAME и содержащее слова Memo или memo. Это поле специального типа; оно может содержать неограниченное количество любой информации. Так как объем информации может быть очень большим, ее трудно отобразить в маленькой области. Для просмотра и редактирования этой информации служит окно редактирования memo-поля.

Глядя на memo-поле, по первой букве слова memo сразу можно определить, содержит данное поле информацию или нет. Прописная буква "M" указывает на то, что memo-поле содержит информацию, а строчная буква "m" - на то, что поле пусто.

Как просмотреть данные memo-полей

Откройте окно редактирования memo-поля. Для этого дважды щелкните на слове Memo.

Рис.1.5 Просмотр данных Memo полей

В окне редактирования memo-поля вы можете его не только просмотреть, но и отредактировать. Для закрытия окна просмотра memo поля щелкните дважды на кнопке управляющего меню окна просмотра.


Глава 2. Управление данными.

Из предыдущей главы Вы узнали, как просмотреть информацию в таблице. Но что делать, если окажется, что нужно просмотреть информацию заданного типа ? Например, может потребоваться узнать все о покупателях, израсходовавших свыше $5000, или узнать лишь имя и номер каждого покупателя. FoxPro позволяет быстро извлекать и просматривать информацию, отвечающую заданным условиям. Процесс извлечения указанной информации называется "запросом".

Как открыть окно запроса

В меню "Файл" выберите команду "Открыть" и в каталоге FOXPROW\TUTORIAL\ выберите файл CUSTOMER.DBF и нажмите кнопку "Открыть". Затем, в меню "Файл" выберите команду "Создать". В диалоге "Создать файл" установите опцию "Запрос", а затем нажмите кнопку "Создать". Появится окно запроса.

Рис. 2.1. Как открыть окно запроса

В окне запроса указывается информация, которую требуется извлечь из таблиц. Извлеченная информация помещается в окно просмотра. Можно работать с этим окном так, как это делалось в предыдущей главе.

В списке "Поля результата" окна запроса указывается, какие поля требуется отобразить в окне просмотра. По умолчанию во вновь открываемой таблице отображаются все поля.

В области "Критерий отбора" указывается, какие записи требуется извлечь из таблицы. Если область пуста - запрос извлекает все записи из таблицы.

Допустим, нет необходимости просматривать поля CNO, ADDRESS, ZIP и ONO. Тогда нам нужно указать только те поля, которые нужны.

Как задать поля для результата запроса

В окне запроса установите флажок "Поля". Появится диалог "Выбор полей запроса".

Рис. 2.2 Задание полей для запроса

Нажмите кнопку "Удалить все", чтобы очистить список "Выбранные поля". И поместите в список "Выбранные поля" COMPANY, CNO, CONTACT, PHONE, CITY, STATE и YTDPURCH, для этого выделите нужное поле в списке "Поля таблицы" и нажмите кнопку "Добавить". Каждое из перечисленных выше полей появится на экране с префиксом CUSTOMER, это означает принадлежность поля к таблице CUSTOMER. По окончании нажмите кнопку "Ok". Выбранные поля будут отображены в списке "Поля результата" окна запроса. При выполнении запроса поля в окне просмотра появляются в порядке, указанном в окне запроса.

Как выполнить запрос

В окне запроса нажмите кнопку "Выполнить". И у вас появится окно просмотра с выбранными полями.

Рис. 2.3 Выполнение запроса

Если в процессе работы с окном просмотра окажется, что больше нет необходимости в поле CONTACT, то его просто из запроса удалить.

Как удалить поле результата из запроса

Закройте окно просмотра, в котором отображен результат запроса. Установите флажок "Поля". Удалите поле CONTACT из списка "Выбранные поля", выделив его в списке и нажав кнопку "Удалить". Нажмите кнопку "Ok". Обратите внимание, что поля CONTACT больше нет в списке "Поля результата". И выполните запрос.

Теперь, когда выбраны поля результата, можно задать порядок, в котором будут представлены данные. Например, можно отобразить данные по городам в алфавитном порядке.

Как упорядочить поля в запросе

Закройте окно просмотра с отображенным результатом запроса. В окне запроса установите флажок "Порядок". В диалоге "Порядок полей запроса" поместите CUSTOMER.SITY в список "Критерий порядка", для этого выделите его и нажмите кнопку "Добавить". И установите флажок "По возрастанию".

Рис. 2.4 Как упорядочить поля в запросе

Для возвращения в окно запроса нажмите кнопку "ОK". Обратите внимание, что в списке "Поля результата" рядом с CITY появились символы 1 и стрелка вверх. Цифра 1 означает, что CITY имеет первый приоритет при упорядочении данных, а стрелка вверх означает, что данные отображаются в порядке возрастания.

Нажмите кнопку "Выполнить". Данные можно упорядочить любым способом. Например, можно упорядочить данные по штатам и по городам внутри штата.

Как упорядочить по нескольким полям

Закройте окно просмотра. Установите флажок "Порядок". Поместите CUSTOMER.STATE в список "Критерий порядка". Установите порядок полей, для этого мышью переместите кнопку слева от STATE так, чтобы поле STATE было выше поля CITY. Нажмите кнопку "Оk". Теперь перед STATE стоит цифра 1, а перед CITY - 2, что означает приоритет по упорядочению данных. И нажмите кнопку "Выполнить".

До сих пор в таблице CUSTOMER просматривались все записи. Но предположим, что нужно просмотреть только те компании, которые расположены в штате Огайо. Используя область "Критерий отбора" окна запроса, можно выполнить такой запрос почти сразу же.

Задание условий отбора

Чтобы извлечь только заданные записи, следует задать условия от- бора. Каждое условие отбора состоит из поля таблицы, варианта сравнения и значения, которое должно сравниваться с содержимым поля.

Как задать условия отбора.

Закройте окно просмотра. В окне запроса щелкните на прямоугольнике ниже "Имя поля", чтобы вывести список "Имя поля". Выделите CUSTOMER.STATE

Рис. 2.5 Как задать условия отбора

В списке вариантов сравнения по умолчанию устанавливается "похоже на". Наберите OH (Ohio) в текстовом поле ниже надписи "Образец". Так создано условие отбора для просмотра записей только о компаниях в штате Огайо. Таким же образом можно задать несколько условий отбора. Введенное условие отбора требует, чтобы поле STATE было похоже на OH. STATE - это поле, "похоже на" - это вариант сравнения, а OH - значение, с которым сравнивается содержимое поля. Вариант сравнения "похоже на" указывает, что поле STATE должно иметь значение OH для того, чтобы запись удовлетворяла требованиям запроса. Результат выполнения запроса показан на

Рис. 2.6 Результат выполнения запроса

Другие варианты условия отбора

Попробуем вывести компании, расположенные в штате Огайо или Мичиган. Закройте окно просмотра. Щелкните на поле ввода вариантов сравнения и выберите значение "в списке". В поле "Образец" наберите MI (Michigan) после OH. Не забудьте поставить запятую.

Рис. 2.7 Вариант сравнения: "в списке"

Нажмите кнопку "Выполнить". Появится окно просмотра, в котором будут выведены все компании, расположенные в штатах Огайо и Мичиган, потому что "в списке" означает, что для включения в результат STATE должно содержать один из элементов, указанных в текстовом поле "Образец".

Другие условия отбора: равно, больше, меньше, в пределах; работа- ют аналогично. Для более подробного ознакомления обратитесь к справочнику: "Руководство пользователя. FoxPro. Система управления реляционными базами данных для Windows".


Глава 3. Работа с таблицей.

В этой главе приводится описание создания собственной таблицы пользователя, а также добавление, удаление и изменение записей в ней.

Создание структуры таблицы.

Во-первых, решите, какую информацию следует помещать в таблицу.

    Фамилия, имя, отчество сотрудника;
  • Дата рождения;
  • Профессия;
  • Заработная плата;
  • Комментарий;

Каждая из перечисленных величин будет храниться в соответствующем поле таблицы.
Как создать структуру таблицы

В меню "Файл" выберите команду "Создать". Появится диалог "Создать файл". Выберите опцию "Таблица/DBF", а затем нажмите кнопку "Создать". Появится диалог "Структура таблицы"

Рис. 3.1 Создание структуры таблицы

Курсор находится в текстовом поле "Имя". Теперь можно добавить имя, тип и ширину каждого поля.

Как описать поля таблицы

Наберите "фио" в текстовом поле "Имя". Нажмите клавишу Tab или щелкните мышью в списке "Тип". Установите тип "Character". Нажмите клавишу Tab или щелкните мышью на поле "Ширина", после чего наберите 25 или с помощью стрелок увеличьте ширину до 25. Далее опишите остальные поля. Результат показан на

Рис. 3.2 Описание полей таблицы

Поле "Дес" указывает число десятичных разрядов в числовом поле. Так как в поле "зарплата" хранится значение денежной суммы, то следует задать число 2.

После ввода всех значений нажмите кнопку "Ok". Появится диалог "Сохранить как". Введите имя таблицы: TEST, если необходимо укажите в какой каталог ее записать и нажмите кнопку "Сохранить". Затем FoxPro спросит: "Вводить записи данных сейчас ?". Нажмите кнопку "Да".

Ввод данных в таблицу

Появится окно просмотра в режиме изменения по записям. Добавляем в таблицу следующие данные:

  • Иванов Сергей Николаевич
  • 01/01/6
  • бухгалтер
  • 350000

Для ввода информации в поле memo нажмите клавиши CTRL+PGDN или дважды щелкните на этом поле. Появится memo-окно, в которое введите комментарии.

Рис. 3.3 Ввод комментариев в Memo-окно

Закройте memo-окно. Заметьте, что буква "M" в слове Memo прописная, это означает, что данные в поле уже введены. Добавьте еще нескольких сотрудников и закройте окно просмотра.

Теперь, предположим, Вы приобрели сканер и желаете добавить в таблицу фотографии своих сотрудников.

Как изменить структуру таблицы

В меню "База" выберите команду "Настройка". В левом верхнем углу диалога "Настройка базы" нажмите кнопку "Изменить" Появится диалог "Структура таблицы". Подведите курсор к полю "комментарий" и нажмите кнопку "Вставить". Над полем "комментарий" появится "новое_поле". Замените "новое_поле" на "фото". В списке "Тип" выберите значение "General", а затем нажмите кнопку "Ok". На запрос FoxPro о том, необходимо ли сохранить изменения нажмите кнопку "Да". В диалоге "Настройка базы" нажмите кнопку "Ok".

Ввод в таблицу графических данных

Прежде всего, Вам необходимо внести в буфер обмена фотографию сотрудника.

В меню "Запись" выберите команду "Править". В окне просмотра устанавливается режим изменения по записям. Дважды щелкните по полю "фото". Появится окно редактирования поля типа General. В меню "Правка" выберите команду "Вставить". Картинка, содержащаяся в буфере обмена, появится в окне.

Рис. 3.4 Запись картинки в таблицу

Как добавить новые записи

В меню "Запись" выберите команду "Добавить". Появится окно просмотра в режиме добавления, куда Вы можете добавить новых сотрудников.

Как удалить запись

В режиме просмотра таблицы следует пометить поля для удаления. Для этого слева от поля нажать маркер удаления.

Рис. 3.5 Удаление записи

Как упаковать таблицу

В меню "База" выберите команду "Упаковать". Появится сообщение с запросом о том, хотите ли Вы упаковать таблицу. Нажмите кнопку "Да". Теперь в меню "База" выберите команду "Просмотр" и убедитесь в том,что все выделенные записи были удалены.


Глава 4. Формирование отчетов.

В настоящей главе описывается, как использовать окно запроса для быстрого составления отчетов на основе имеющихся данных.

Во второй главе говорилось об отображении информации в окне просмотра с помощью запроса. Другим способом представления информации является отчет.

Для создания отчета необходим запрос. Мы воспользуемся запросом созданным в главе второй.

Как открыть запрос

В меню "Файл" выберите команду "Открыть". В появившемся диалог "Открыть" выберите каталог TUTORIAL. В списке "Тип" выберите "Запрос" Выберите нужный файл (GSCHAP5.QPR) и нажмите кнопку "Открыть".

В качестве первого отчета составим список телефонных номеров всех клиентов из Калифорнии.

Сформируйте отчет с телефонным списком, как показано на рис.4.1.

Рис. 4.1 Создание отчета с телефонным списком

Включите следующие поля: COMPANY, PHONE, CONTACT, CITY и STATE. Критерий отбора: STATE похоже на CA. И главное, в списке "Вывод" выберите значение "Отчет/Этикетка". Нажмите кнопку "Выполнить".

Отчет начинает прокручиваться на экране.

Рис. 4.2 Вид отчета на экране

После просмотра отчета нажмите клавишу ESC.

Вышеприведенный отчет содержит корректную информацию, но его наглядность можно повысить.

Как сформировать бланк отчета

Установите флажок "Опции". В диалоге "Параметры ввода запроса" выберите опцию "Отчет", установите флажок "Стандартный отчет", и в диалоге "Стандартный отчет запроса" в поле "Сохранить как" наберите имя gschap6.frx.

Рис. 4.3 Формирование бланка отчета

Нажмите кнопку "Ok" и кнопку "Выполнить".

Полное изображение целой страницы отчета можно увидеть в окне "Просмотр страницы", как показано на рис.4.4.

Рис. 4.4 Изображение страницы отчета

Изображение страницы отчета дает представление о том, как отчет будет выглядеть на бумаге.

В окне "Просмотр страницы" Вы можете увеличить изображение (кнопка Увеличить), можно уменьшить (кнопка Уменьшить), просмотреть следующую или предыдущую страницу (кнопки Вперед/Назад). Для выхода нажмите кнопку "OK".

Теперь рассмотрим созданный бланк отчета и внесем в него некоторые усовершенствования в окне разметки отчета.

Как открыть бланк отчета

В меню "Файл" выберите команду "Открыть". В списке "Тип" выберите значение "Отчет", и откройте файл: GSCHAP6.FRX.

В окне разметки отчета появится бланк отчета GSCHAP6, изображенный на рис.4.5.

Рис. 4.5 Бланк отчета

Бланк отчета разделен на три полосы:
  1. . Полоса "Верхний колонтитул" содержит информацию, отображаемую вверху каждой страницы отчета.
  2. Полоса "Детали" печатается один раз для каждой записи, удовлетворяющей критерию отбора.
  3. Полоса "Нижний колонтитул" содержит информацию, отображаемую вниз каждой страницы отчета.

С помощью набора значков в левой части окна разметки можно внести необходимые изменения в отчет. Например, на рис.4.6 показан бланк отчета с внесенными изменениями .

Рис. 4.6 Внесение изменений в отчет

Как выполнить запрос

Сверните окно отчета. Нажмите кнопку "Выполнить". Обратите внимание, что отчет содержит внесенные изменения.

Рис. 4.7 Окончательный вид отчета

И этот отчет Вы можете распечатать на принтере.


Microsoft FoxPro Краткое руководство по системе управления базами данных для Windows . Заключение.

Заключение

Мы рассмотрели элементарные возможности работы с базами данных в среде FoxPro, на этом его возможности не ограничиваются, но для дальнейшей работы необходимо иметь хотя бы элементарные знания о языке программирования FoxPro. Если Вы решите продолжить знакомство с возможностями использования FoxPro, или если у Вас возникли затруднения, то можно посоветовать воспользоваться справочником "Microsoft FoxPro для Windows", который Вы получите вместе с пакетом программ FoxPro.

Рассмотрим кратко другие основные возможности работы с базами данных:

Создание экрана.

Система FoxPro используется для просмотра данных различными способами. Можно просмотреть таблицу данных в окне просмотра, можно просмотреть поля каждой записи, а также с помощью "создания экрана" Вы можете создать свой метод просмотра/изменения данных, помещая данные в нужные места экрана или страницы. Возможности FoxPro в этом плане просто уникальны.

Создание меню.

Вы можете за считанные минуты создать свою систему меню, со всеми необходимыми командами для работы с вашими базами данных, а не пользоваться стандартной системой меню FoxPro.

Создание проекта.

Все элементы: экраны, отчеты, запросы, меню... можно объединить в единое целое, называемое проектом.

Проект служит для управления элементами в процессе создания приложения. Создав проект и задав файлы, которые должны в него войти, можно использовать этот проект для сборки приложения (.APP) или исполняемого файла (если у Вас есть пакет FoxPro Distribution Kit).


Microsoft FoxPro Краткое руководство по системе управления базами данных для Windows.
Содержание
  • Введение
  • Глава I. Открытие и просмотр таблицы.
  • Глава II. Управление данными.
  • Глава III. Работа с таблицей.
  • Глава IV. Формирование отчетов
  • Заключение


Common Gateway Interface - средство расширения возможностей World Wide Web технологии

  • Механизмы обмена данными
  • Список переменных окружения
  • Применение CGI-скриптов
  • Примеры SHELL-скриптов
  • Пример С-программы
  • Доступ к базе данных под управлением Ingres
  • Примеры скриптов
  • Пример интерфейсной формы
  • Результат выполнения скрипта
  • Организация стека графических ссылок

Главное назначение Common Gateway Interface - обеспечение единообразного потока данных между сервером и прикладной программой, которая запускается из-под сервера

CGI-скрипт - программа написанная в соответствии со спецификацией Common Gateway Interface. CGI-скрипты могут быть написаны на любом языке программирования (C, C++, PASCAL, FORTRAN и т. п.) или командном языке (shell, cshell, командный язык MS-DOS, Perl и т. п.). Скрипт может быть написан даже на языке редактора EMAC в системах Unix

Шлюз - это CGI-скрипт, который используется для обмена данными с другими информационными ресурсами Internet или приложениями-демонами. Обычная CGI- программа запускается сервером HTTP для выполнения некоторой работы, возвращает результаты серверу и завершает свое выполнение

Механизмы обмена данными

  • переменные окружения
  • формат командной строки
  • формат стандартного ввода
  • формат стандартного вывода

Список переменных окружения

К общим переменным окружения относятся:

  • SERVER_SOFTWARE - определяет имя и версию сервера
  • SERVER_NAME - определяет доменное имя сервера
  • GATEWAY_INTERFACE - определяет версию интерфейса

К запрос-ориентированным относятся:

  • SERVER_PROTOCOL - протокол сервера
  • SERVER_PORT - определяет порт TCP, по которому осуществляется взаимодействие (80 порт)
  • REQUEST_METHOD - определяет метод доступа к информационному ресурсу
  • PATH_INFO - передает программе путь (адрес скрипта) в виде, указанном в HTML-документе
  • PATH_TRANSLATED - то же, что и PATH_INFO, но только после подстановки сервером определенных в его конфигурации вставок
  • SCRIPT_NAME - определяет адрес скрипта так, как он указан клиентом
  • QUERY_STRING - определяет содержание запроса к скрипту

Идентификация пользователя и его машины:

  • REMOTE_HOST - доменный адрес машины, с которой осуществляется запрос
  • REMOTE_ADDR - IP-адрес запрашивающей машины
  • AUTH_TYPE - тип идентификации пользователя
  • REMOTE_USER - идентификация пользователя
  • REMOTE_IDENT - устанавливается сервером; предназначена для первоначального использования скрипта
  • CONTENT_TYPE - определяет MIME-тип данных, передаваемых скрипту
  • CONTENT_LENGTH - определяет размер данных в байтах, которые передаются скрипту

Стандартный ввод CGI-скрипта

  • Методы GET или Post
  • CONTENT-LENGHT
  • CONTENT-TYPE
  • QUERY-STRING

Стандартный вывод CGI-скрипта

  • Заголовок
    • CONTENT-TYPE
    • Location
    • Status
  • Тело ответа (обычно текст)

Применение CGI-скриптов

  • Простейшие SHELL-скрипты
  • Вызов внешней программы через SHELL
  • Вызов модуля "С"
  • Доступ к базам данных
  • Imagemap

Примеры SHELL-скриптов

#!/bin/sh

echo Content-type: text/plain

echo

echo This is the result of script execution.

#The end of script

#!/bin/sh

echo Content-type: text/plain

echo

echo $REQUEST_METHOD

echo $QUERY_STRING

echo $CONTENT_TYPE

echo $CONTENT_LENGTH

#The end of script.

#!/bin/sh

echo Content-type: text/plain

echo

finger paul@polyn.kiae.su

#The end of script.

Пример С-программы

#include <stdlib.h>;

#include <sys/types.h>;

main()

    {

     long i,n,uid;

     char input_ch[1024];

     char *env;

     env = getenv("CONTENT_LENGTH");

      /* Here we recieve a length  */

     sscanf(env,"%d",&n);

                 /* of input stream and form  */

     for(i=0;i<;n;i++)

                    /* command line */

        { input_ch[i]

= getchar(); }

     input_ch[i] = '\000';

     printf("Content-type:

text/html\n\n");
             /* First message of a CGI Programme */

             /* This message must be a first one */

             /* in output sream.                 */

     printf("<TITLE>

C-cgi script.(example#1)</TITLE>\n");

     printf("<H3><I>

Russian Research Center \"Kurchatov Institute\"
<I></H3>\n");

     c_uid = -1;

     sscanf(input_ch,"uid=%ld",&uid);

/* Transform input data */

     printf("Input

Nuber:%ld.<BR><HR>",uid);

     exit(0);

    }

Доступ к базе данных под управлением Ingres

  • Простой вызов постоянного запроса
  • Форматирование вывода
  • Запрос через Query-String
  • Фильтрация

Примеры скриптов

#!/bin/sh

echo Content-type: text/plain

echo

ingres polyn < query

#The end of script

#!/bin/sh

echo Content-type: text/plain

echo

helpr polyn

#The end of script

#!/bin/sh

echo Content-type: text/html

echo

echo '< PRE> '

helpr polyn situat

echo '< /PRE> '

#The end of script

#!/bin/sh

echo Content-type: text/html

echo

echo '< PRE> '

echo $QUERY_STRING | tr "+" " " | ingres polyn

echo '< /PRE> '

#The end of script

#!/bin/sh

echo Content-type: text/html

echo

echo '< PRE> '

echo $QUERY_STRING | sed -f symbols | ingres polyn |
 tr "\007" "*" | head 100

echo '< /PRE> '

#The end of script

Пример интерфейсной формы

Результат выполнения скрипта

Организация стека графических ссылок

Скрипт imagemap

<A href="http://polyn.net.kiae.su/cgi-bin/imagemap/
                                       russia">

<img SRC="http://demin.polyn.kiae.su/dss/russia.gif"
                                         ISMAP>

</A>

# метка : адрес файла описания картинки

russia : /usr/local/etc/httpd/cgi-bin/maps/russia.map

brussia : /usr/local/etc/httpd/cgi-bin/maps/brussia.map

....

#описание прямоугольника

rect 10 20 100 200

circle 50 50 60 60

poly 10 10 20 20 20 10

Статья 1 Статья 2 Статья 3


CGI - Common Gateway Interface

CGI - Common Gateway Interface является стандартом интерфейса (связи) внешней прикладной программы с информационным сервером типа HTTP, Web сервер.
Обычно гипертекстовые документы, извлекаемые из WWW серверов, содержат статические данные. С помощью CGI можно создавать CGI-программы, называемые шлюзами, которые во взаимодействии с такими прикладными системами, как система управления базой данных, электронная таблица, деловая графика и др., смогут выдать на экран пользователя динамическую информацию.
Программа-шлюз запускается WWW сервером в реальном масштабе времени. WWW сервер обеспечивает передачу запроса пользователя шлюзу, а она в свою очередь, используя средства прикладной системы, возвращает результат обработки запроса на экран пользователя. Программа-шлюз может быть закодирована на языках C/C++, Fortran, Perl, TCL, Unix Schell, Visual Basic, Apple Script. Как выполнимый модуль, она записывается в поддиректорий с именем cgi-bin WWW сервера.
Оригинал описания CGI интерфейса - инструмента связи программы-шлюз с WWW сервером находится в узле wist.ifmo.ru .

  • Передача данных шлюзам
    • Запросы для различных методов
    • Аргументы командной строки
    • CGI переменные окружения
  • Вывод информации шлюзом
    • Основные концепции
    • Заголовок выходного потока

Передача данных шлюзам

Для передачи данных об информационном запросе от сервера к шлюзу, сервер использует командную строку и переменные окружения. Эти переменные окружения устанавливаются в тот момент, когда сервер выполняет программу шлюза.

Запросы для различных методов

Информация шлюзам передается в следующей форме:

имя=значение&имя1=значение1&..,

где имя- имя переменной (из оператора FORM, например), и значение - ее реальное значение. В зависимости от метода, который используется для запроса, эта строка появляется или как часть URL (в случае метода GET), или как содержимое HTTP запроса (метод POST). В последнем случае, эта информация будет послана шлюзу в стандартный поток ввода.

На файловый дескриптор стандартного потока ввода посылается CONTENT_LENGTH байт. Так же сервер передает шлюзу CONTENT_TYPE (тип передаваемых данных). Сервер не обязан посылать символ конца файла после отсылки CONTENT_LENGTH байт данных и после того, как шлюз их прочитает.

Пример

Возьмем результат работы формы с методом POST (METHOD="POST") в качестве примера. Пусть получено 7 байт, закодированных примерно так:
a=b&b=c.

В этом случае, сервер установит значение CONTENT_LENGTH равным 7 и CONTENT_TYPE в application/x-www-form-urlencoded. Первым символом в стандартном потоке ввода для шлюза будет "a", за которым будет следовать остаток закодированной строки.

Аргументы командной строки

Шлюз в командной строке от сервера получает:

  • остаток URL после имени шлюза в качестве первого параметра (первый параметр будет пуст, если присутствовало только имя шлюза), и
  • список ключевых слов в качестве остатка командной строки для скрипта поиска, или
  • чередующиеся имена полей формы с добавленным знаком равенства (на четных позициях) и соответствующих значений переменных (на нечетных позициях).

Ключевые слова, имена полей формы и значения передаются раскодированными (из HTTP URL формата кодирования) и перекодированными в соответствии с правилами кодирования Bourne shell, так что шлюз в командной строке получит информацию в том виде, как она есть, без необходимости осуществлять дополнительные преобразования.

Запросы оператора FORM

Запросы оператора FORM обрабатываются таким образом, что каждый параметр, отвечающий за имя поля, оканчивается знаком равенства, а остаток представляет собой значение этого параметра. Если присутствует что либо после имени скрипта (шлюза), то эта информация передается в качестве первого параметра. Иначе первый параметр будет пуст.

Примеры:

 /htbin/foo/x/y/z?name1=value1&name2=value2
вызывается как:
 /.../foo /x/y/z name1= value1 name2= value2
а
 /htbin/foo?name1=value1&name2=value2
вызывается как:
 /.../foo '' name1= value1 name2= value2

CGI переменные окружения

Следующие переменные окружения не являются специфичными по типу запросов и устанавливаются для всех запросов.

SERVER_SOFTWARE
Название и версия информационного сервера, который отвечает на запрос (и запускает шлюз). Формат: имя/версия
SERVER_NAME
Имя хоста, на котором запущен сервер, DNS имя, или IP адрес в том виде, в котором он представлен в URL.
GATEWAY_INTERFACE
Версия CGI спецификации на тот момент, когда компилировался сервер. Формат: CGI/версия

Следующие переменные окружения являются специфичными для разных запросов, и заполняются перед вызовом шлюза:

SERVER_PROTOCOL
Имя и версия информационного протокола, в котором пришел запрос. Формат: протокол/версия
SERVER_PORT
Номер порта, на который был послан запрос
REQUEST_METHOD
Метод, который был использован для запроса. Для HTTP, это "GET", "HEAD", "POST", и т.д.
PATH_INFO
Дополнительная информация о пути, которую передал клиент. Другими словами, доступ к шлюзу может быть осуществлен по виртуальному пути, за которым следует некоторая дополнительная информация. Эта информация передается в PATH_INFO.
PATH_TRANSLATED
Сервер передает преобразованную версию PATH_INFO, которая включает в себя путь, преобразованный из виртуального в физический.
SCRIPT_NAME
Виртуальный путь к шлюзу, который должен выполняться, используемый для получения URL.
QUERY_STRING
Информация, следующая за ? в URL, к которому относится данный шлюз. Это информация представляет собой строку запроса. Она не должна быть декодирована никоим образом. Вне зависимости от командной строки эта переменная всегда должна быть установлена при наличии такой информации, .
REMOTE_HOST
Имя хоста, производящего запрос. Если сервер не имеет такой информации, он должен установить REMOTE_ADDR, а это поле оставить не установленным.
REMOTE_ADDR
IP адрес хоста, производящего запрос.
AUTH_TYPE
Если сервер поддерживает идентификацию пользователя, и шлюз является защищенным от постороннего доступа, этот специфичный для протокола метод идентификации используется для проверки пользователя.
REMOTE_USER
Используется в ситуациях, аналогичных предыдущему случаю, для хранения имени пользователя.
REMOTE_IDENT
Если HTTP сервер поддерживает идентификацию пользователя согласно RFC 931, то эта переменная будет содержать имя пользователя, полученное от сервера.
CONTENT_TYPE
Для запросов, которые содержат дополнительную добавочную информацию, такие как HTTP POST и PUT, здесь содержится тип данных этой информации.
CONTENT_LENGTH
Длина данных, которую передает клиент.

В дополнение к этим, если запрос содержит дополнительные поля заголовка запроса, они помещаются в переменные окружения с префиксом HTTP_, за которым следует имя заголовка. Любые символы '-' в заголовке меняются на символы подчеркивания '_'. Сервер может исключить любые заголовки, которые он уже обработал, такие как Authorization, Content-type, и Content- length. Если необходимо, сервер может исключить любые (или вообще все) дополнительные поля заголовка в случае, когда их включение может привести к превышению предела размера переменных окружения. Примером такой переменной может служить переменная HTTP_ACCEPT, которая была определена в спецификации CGI/1.0. Другим примером может служить заголовок User-Agent.

HTTP_ACCEPT
Список MIME типов, которые клиент может обработать, как задано в HTTP заголовках. Другие протоколы должны получить эту информацию из других мест (если она им необходима). Каждый тип в этом списке должен быть отделен запятой согласно HTTP спецификации. Формат: тип/подтип, тип/подтип
HTTP_USER_AGENT
Просмотрщик, который использует клиент для посылки запроса. Общий формат: программа/версия библиотека/версия.

Вывод информации шлюзом

Основные концепции

Шлюз осуществляет свой вывод в стандартный поток вывода. Этот вывод может представлять собой или документ, сгенерированный шлюзом, или инструкции серверу, где получить необходимый документ.

Как правило, шлюз производит свой вывод, который интерпретируется и посылается обратно клиенту. Преимущество этого подхода состоит в том, что шлюз не должен посылать полный HTTP/1.0 заголовок на каждый запрос.

Заголовок выходного потока

Для некоторых шлюзов может быть необходимо избегать обработки сервером их вывода, и общаться с клиентом непосредственно. Для того, чтобы отличить такие шлюзы от остальных, CGI требует, чтобы их имена начинались с префикса nph-. В этом случае, на шлюзе лежит ответственность за возвращение клиенту синтаксически правильного ответа.

Заголовки с синтаксическим разбором

Вывод шлюза начинается с маленького заголовка. Он содержит текстовые строки, в том же формате, как и в HTTP заголовке и завершается пустой строкой (содержащей только символ перевода строки или CR/LF).

Любые строки заголовка, не являющиеся директивами сервера, посылаются непосредственно клиенту. В настоящий момент, CGI спецификация определяет три директивы сервера:

Content-type
MIME тип возвращаемого документа.
Location
Это поле используется в случае, когда необходимо указать серверу, что возвращается не сам документ, а ссылка на него.

Если аргументом является URL, то сервер передаст клиенту указание на перенаправление запроса. Если аргумент представляет собой виртуальный путь, сервер вернет клиенту заданный этим путем документ, как если бы клиент запрашивал его непосредственно.

  • Status

Эта директива используется для задания серверу HTTP/1.0 строки-статус, которая будет послана клиенту. Формат: nnn xxxxx, где nnn - 3-х цифровой статус-код, и xxxxx строка причины, такая, как "Forbidden" (Запрещено).

Примеры

Предположим, имеется некоторый текстовый конвертер в HTML. Когда он оканчивает свою работу, он должен произвести следующий вывод в стандартный выходной поток:

--- начало вывода ---

Content-type: text/html

--- конец вывода ---
Теперь рассмотрим шлюз, который, в некоторых случаях, должен выдать документ /path/doc.txt с данного сервера, как если бы он был непосредственно востребован клиентом через http://server:port/path/doc.txt. В это случае вывод шлюза будет таков:

--- начало вывода ---

Location: /path/doc.txt

--- конец вывода ---
Наконец, предположим, что шлюз возвращает ссылки на gopher сервер, например на gopher://gopher.ncsa.uiuc.edu/. Вывод шлюза будет следующий:

--- начало вывода ---

Location: gopher://gopher.ncsa.uiuc.edu/

--- конец вывода ---
Non-parsed headers

Допустим теперь, что у нас имеется шлюз, который общается с клиентом непосредственно. Как уже отмечалось, его имя должно начинаться с префикса nph- и он должен возвращать допустимый HTTP заголовок. В этом случае, если доступ к шлюзу был осуществлен со значением SERVER_PROTOCOL равным HTTP/1.0, его вывод должен удовлетворять HTTP/1.0:

--- начало вывода ---

HTTP/1.0 200 OK

Server: NCSA/1.0a6

Content-type: text/plain

--- конец вывода ---

Статья 1 Статья 2 Статья 3


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Песнь о CGI-скриптах

Сегодня такие вещи как гостевая книга, поиск по серверу, форма для отправки сообщений - неотъемлемый атрибут практически любого серьезного сайта. Проблема внедрения этих и других прибамбасов, разумеется, всячески возбуждает воображение начинающего вебмастера, лишая его сна, аппетита и тяги к пиву. К сожалению, изучение HTML-исходников страниц конкурентов ничего, кроме ссылок на некий "cgi-bin", не дает, да еще в телеконференциях иногда встречается упоминание о каких-то cgi-скриптах. Эта сатья и посвящена основам использования этих самых cgi-скриптов во славу и процветание вашего сайта.

Для начала, думаю, надо разобраться с понятиями (вообще разборки и понятия - сегодня очень актульно :-)). CGI-скрипт - это программа, которая выполняется на Web-сервере по запросу клиента (то есть посетителя Web-сайта). Программа эта принципиально ничем не отличается от обычных программ, которые установлены на вашем компьютере - будь то MS Word или игра Quake. CGI - это не язык программирования, на котором написан скрипт, а Common Gateway Interface - специальный интерфейс, с помощью которого и происходит запуск скрипта и взаимодействие с ним.

Правда есть один довольно неприятный момент. На сервере, где находится ваш сайт, должно быть разрешено выполнение cgi-скриптов. Дело в том, что скрипт, как и любая другая программа, может выполнять системные команды на сервере, что представляет потенциальную угрозу безопасности. Как сказал один мой знакомый админ: "Одними cgi-скриптами я могу взломать систему за 5 минут". Так что если вы разместили свой сайт на бесплатном сервере, например, Xoom или Chat.Ru, то вы не сможете запускать скрипты. Впрочем, некоторые бесплатные сервера допускают использование CGI, например, I-Connect. Ну, а если вы платите за размещение страницы, то, как правило, использование cgi-скриптов разрешено (если нет - то и платить провайдеру за поддержку страницы, на мой взгляд, не стоит).

Как работает CGI-скрипт? Я, конечно, могу процитировать какое-нибудь техническое руководство, но пользы от этого будет мало. Поэтому расскажу, как все происходит, своими словами. Итак, посетитель вашей страницы заполняет поля формы, например, для записи в гостевую книгу. После этого он нажимает кнопку "Submit", которая и запускает cgi-скрипт. Скрипт выполняет запрограммированные действия - в данном случае считывает данные из формы и пишет их в файл гостевой книги - и посылает в броузер посетителя обычный HTML-код, например, сообщение "Спасибо, что вы оставили запись в гостевой книге".

Преимуществ CGI-скриптов перед JavaScript и Java на мой взгляд, три, и они весьма значительны:

  • так как программа выполняется сервером, нет никакого значения, какой у посетителя броузер - древний Lynx или новейший Internet Explorer. Нет никаких глюков и сообщений об ошибках;
  • cgi-скрипты позволяют реализовать гораздо более широкий набор функций;
  • код cgi-скрипта закрыт для конкурентов :-)

На каком же языке может быть написана CGI-программа? Ответ вас приятно удивит: практически на любом. Главное, чтобы сервер мог выполнить эту программу, то есть на сервере должен быть установлен компилятор или интерпертатор соответствующего языка программирования. Для систем на базе Unix это обычно C/С++, Perl, Shell; для серверов под управлением Windows NT - те же Perl, С/С++ и любая Windows-система программирования, поддерживающая написание cgi-приложений, например, Visual Basic или Delphi.

Лично я предпочитаю язык Perl. Он является интерпретируемым, то есть программы на Perl не требуют компиляции. Это очень удобно: вы отлаживаете программу на своей домашней машине (где, к примеру, установлена Windows), а затем просто копируете ее на сервер (где, скорее всего, установлен один из клонов Unix - FreeBSD, Linux, Sun Solaris, HP-UX и т.п.). При смене провайдера проблем с переносом скриптов также не будет.

Если же вы решите использовать компилятор, например, C/C++, то вам нужно будет компилировать исходный текст программы на сервере, для чего необходим telnet-доступ к серверу, который провайдеры давать очень не любят.

"Да, но я не знаю Perl" - скажете вы. Ха! Для того, чтобы наладить работу скриптов на вашем сайте, знания языка программирования особенно не нужно. В Сети лежит просто немеренное количество абсолютно бесплатных скриптов на любой вкус - от гостевых книг до сложных баз данных. Все, что вам нужно - хотя бы начальные знания английского языка. Скачать любой скрипт можно с сайта CGI-Resources.Com. Это специализированный каталог, содержащий ссылки на тысячи скриптов на самых разных языках программирования.

Предположим, вы решили использовать скачанный из Сети скрипт. Чтобы он у вас нормально работал, нужно сделать следующее:

  • в начале скрипта вам нужно будет указать некоторые данные, необходимые для запуска скрипта, например, путь к интерпретатору Perl (обычно /usr/local/bin/perl/ или просто /usr/bin/perl/), URL вашего сайта, имя файла, в который будут записываться результаты выполнения скрипта и т.п. Все строки, которые нужно заменить, обычно выделены комментариями, так что вы их легко найдете
  • В зависимости от конфигурации Web-сервера, имя скрипта должно иметь определенное расширение и/или скрипт должен находиться в определенном каталоге. Например, имя скрипта должно иметь расширение .cgi и скрипт должен лежать в каталоге cgi-bin. Проконсультируйтесь у администратора вашего сервера о требованиях, предъявляемых к скриптам.
  • Так как скрипт - это программа, нужно присвоить ему атрибут "исполняемый".
  • Если скрипт пишет данные в какой-нибудь файл (например, файл гостевой книги), то этот файл должен быть доступен для записи.

Если у Вас не коммерческий проект, что и подразумевается для начинающнго вебмастера, то Вы можете воспользвоваться бесплатным CGI-хостингом. То есть, получить быстро и бесплатно гостевые книги, доски объявлений, чаты и прочее...

X Point CGI | Быстро и легко создайте себе, что хотите 14 CGI Scripts.
CGI For Me | Велеколепное место, если Вы не знаете CGI или у вас нет доступа к CGI. Вы сможете создать себе WWWBoard, Free For All Links, Polls, Counters, Guestbooks, Clocks, Postcards, Search Engines, and LOTS more!
CGI Free | Еще один отличный сервис включает random images, browser redirections, random links, random quotes, and a few other things

Статья 1 Статья 2 Статья 3


1. Базовые понятия. Спецификация CSS1 | WebClub

1. Базовые понятия. Спецификация CSS1

Разработка простых таблиц стилей является довольно простым занятием. Разработчик должен иметь базовые знания по HTML и по полиграфической терминологии. Например для определения цвета элементов 'H1' как синий, достаточно сказать:

  H1 { color: blue }

Приведенный пример является простым правилом CSS. Правило состоит из двух основных частей: селектора ('H1') и определения ('color: blue'). Определение в свою очередь тоже состоит из двух частей: свойства ('color') и значения ('blue'). В то время, как приведенный пример пытается повлиять только на одно из свойств, необходимых для построения HTML документа, он уже является таблицей стилей. Скомбинированный с другими таблицами стилей (одним из фундаментальных свойств CSS является комбинирование таблиц стилей) он будет определять конечный вид всего документа.

Селектор является связью между HTML документом и таблицей стилей, полный набор типов элементов содержит все возможные селекторы. Типы элементов определены в спецификации HTML [2].

Cвойство 'color' является одним из порядка 50 свойств, которые определяют вид HTML документа. Список свойств и их возможных значений определяется в данной спецификации.

Авторам HTML необходимо определять свою таблицу стилей только в том случае, если они хотят предложить специфический стиль для своих документов. Каждый UA должен иметь таблицу стилей по умолчанию, которая представляет документы в приемлемом, но может быть достаточно простом виде. П приложении А приведена примерная таблица стилей для представления HTML документов так, как это предлагается в спецификации HTML 2.0 [3].

Формальная грамматика языка CSS1 определена в приложении B.

1.1 Включение в HTML

Для того, чтобы таблица стилей влияла на вид документа, UA должен знать о ее существовании. Спецификация HTML [2] определяет способы включения таблиц стилей в HTML. Поэтому данный раздел является информативным, а не нормативным:

<HTML>
  <HEAD>
    <TITLE>title</TITLE>
    <LINK REL=STYLESHEET TYPE="text/css"
    HREF="http://style.com/cool" TITLE="Cool">
    <STYLE TYPE="text/css">
      @import url(http://style.com/basic);
      H1 { color: blue }
    </STYLE>
  </HEAD>
  <BODY>
    <H1>Headline is blue</H1>
    <P STYLE="color: green">While the paragraph
     is green.
  </BODY>
</HTML>

Данный пример демонстрирует четыре способа объединения стиля с HTML: используя элемент 'LINK' для связи с внешней таблицей стилей, используя элемент 'STYLE' внутри элемента 'HEAD', импортируя таблицу стилей с помощью нотации CSS '@import' и используя атрибут 'STYLE' в элементе внутри секции 'BODY'. Последний способ смешивает стиль с содержимым и поэтому теряет соответствующие достоинства традиционных таблиц стилей.

Элемент 'LINK' ссылается на альтернативную таблицу стилей, которую может выбрать читатель, в то время как импортируемые таблицы стилей автоматически объединяются с остальной частью таблицы стилей.

Традиционно UA игнорировали неизвестные теги. Как результат старые UA будут игнорировать элемент 'STYLE', но его содержимое будет считаться, как часть тела документа и будет отображаться в документе. В течение переходного периода содержимое элемента 'STYLE' можно "прятать", используя комментарии SGML:

<STYLE TYPE="text/css"><!--
  H1 { color: green }
--></STYLE>

В связи с тем, что элемент 'STYLE' определен, как "CDATA" в DTD [2], интерпретаторы SGML не будут считать таблицу стилей комментарием, который надо удалить.

1.2 Группирование

Для уменьшения размера таблицы стилей можно группировать селекторы в разделенные запятыми списки:

H1, H2, H3 { font-family: helvetica }

Точно также можно группировать определения:

  H1 {
    font-weight: bold;
    font-size: 12pt;
    line-height: 14pt;
    font-family: helvetica;
    font-variant: normal;
    font-style: normal;
  }

В дополнение к этому некоторые свойства имеют собственный синтаксис группировки:

H1 { font: bold 12pt/14pt helvetica }

что эквивалентно предыдущему примеру.

1.3 Наследование

В первом примере цвет элементов 'H1' был установлен в синий. Представьте, что есть элемент 'H1' с элементом <EM> внутри:

  <H1>The headline <EM>is</EM> important!</H1>

Если для элемента 'EM' не было определено никакого цвета, то Emированный "is" унаследует цвет родительского элемента, т.е. он также будет отображен синим цветом. Другие свойства стиля также наследуются, например 'font-family' и 'font-size'.

Для определения свойства стиля по умолчанию для документа, можно задать это свойство у элемента, от которого наследуются все остальные видимые элементы. В HTML документах элемент 'BODY' выполняет эту функцию:

  BODY {
    color: black;
    background: url(texture.gif) white;
  }

Этот пример сработает даже если автор опустил тэг 'BODY' (что является допустимым) благодаря тому, что браузер вставит пропущенный тэг. Предыдущий пример устанавливает цвет текста в черный, а фон - содержащий картинку. Фон будет белым в том случае, если картинка будет недоступна. (См. подробности в главе 5.3)

Некоторые свойства стилей не наследуются от родительских элементов дочерними элементами. В большинстве случаев интуитивно понятно в каких случаях возникает такая ситуация. Например свойство 'background' не наследуется, но фон родительского элемента по умолчанию будет просвечивать сквозь.

Часто значение свойства указывается в процентах от другого свойства:

  P { font-size: 10pt }
  P { line-height: 120% }
      /* relative to 'font-size', i.e. 12pt */

Для любого свойства, значения которого могут указываться в процентах, определено свойство на которое оно ссылается. Дочерние элементы 'P' унаследуют вычисленное а не процентное значение 'line-height' (12pt).

1.4 Класс в качестве селектора

Для увеличения гибкости контроля над элементами, в HTML добавлен новый атрибут [2] 'CLASS'. Все элементы внутри элемента 'BODY' могут быть классифицированы, а на класс можно сослаться через таблицу стилей:

<HTML>
 <HEAD>
  <TITLE>Title</TITLE>
  <STYLE TYPE="text/css">
    H1.pastoral { color: #00FF00 }
  </STYLE>
 </HEAD>
 <BODY>
  <H1 CLASS=pastoral>Way too green</H1>
 </BODY>
</HTML>

К классифицированным элементам применяются нормальные правила наследования, они наследуют значения своих родителей в структуре документа.

Можно адресовать все элементы одного класса опустив имя тэга в селекторе:

  .pastoral { color: green }
   /* all элементы with CLASS pastoral */

Для каждого селектора можно определить только один класс. 'P.pastoral.marine' таким образом является неправильным селектором в CSS1. (Контекстные селекторы, описанные ниже, могут определять один класс для одного простого селектора).

CSS предоставляет настолько большие возможности для использования атрибута CLASS, что во многих случаях не важно у какого элемента HTML устанавливается класс - можно заставить любой элемент эмулировать любой другой элемент. Но не рекомендуется полагаться на такую возможность, т.к. это лишает документ структуры, которая имеет универсальное значение (элементы HTML). Структура основанная на классах имеет узкое применение только в тех случаях, когда значения классов оговорены с обеих сторон.

1.5 ID в качестве селектора

HTML [2] также теперь определяет атрибут 'ID', который гарантированно имеет уникальное значение в документе. Таким образом он имеет особое значение, как селектор таблицы стилей, и может адресоваться с помощью '#':

  #z98y { letter-spacing: 0.3em }
  H1#z98y { letter-spacing: 0.5em }

  <P ID=z98y>Wide text</P>

В приведенном примере первый селектор соответствует элементу 'P' благодаря 'ID' атрибута. Второй селектор определяет как тип элемента ('H1'), так и значение ID, и, поэтому, не соответствует элементу 'P'.

Используя ID в качестве селектора, можно устанавливать свойства поэлементно. В то время как таблицы стилей были разработаны для визуализации структуры документа, это свойство позволяет авторам создавать документы, которые корректно отображаются на холсте без использования структурных элементов HTML. Такое использование таблиц стилей не приветствуется.

1.6 Контекстные селекторы

Наследование значительно уменьшает количество напечатанного текста CSS дизайнерами. Вместо того, чтобы указывать все свойства стилей, можно установить значения по умолчанию, а затем указать исключения. Для того, чтобы элементы 'EM' внутри 'H1' имели другой текст, можно было бы указать:

  H1 { color: blue }
  EM { color: red }

Когда данная таблица стилей будет использована, весь текст внутри 'EM', как внутри, так и вне 'H1' станет красным. Очевидно, хотелось, чтобы только те элементы 'EM', которые заключены в 'H1' стали красными. Этого можно достигнуть, указав:

  H1 EM { color: red }

В этом случае селектор является маской поиска в стеке открытых элементов, а такой селектор называется контекстным селектором. Контекстные селекторы состоят из простых селекторов, разделенных пробелом (все описываемые до этого селекторы являлись простыми селекторами). Описанные правила применяются только к элементу, который соответствует последнему простому селектору (в данном случае элемент 'EM'), и только в том случае, если результат поиска является положительным. Контекстные селекторы в CSS1 описывают только наследственные взаимосвязи, в то время как последующие версии могут описывать м другие виды связи.

UL LI { font-size: small } UL UL LI { font-size: x-small }

В этом случае первый селектор соответствует элементам 'LI' в как минимум одним предком 'UL'. Второй селектор соответствует подмножеству первого, т.е. элементы 'LI' с как минимум двумя предками 'UL'. Конфликт разрешается тем, что второй селектор является более специфичным в связи с более длинной маской поиска. См. подробнее о порядке каскадирования (раздел 3.2).

Контекстные селекторы могут содержать тип элемента, атрибуты CLASS, атрибуты ID или их комбинацию:

  DIV P           { font: small sans-serif }
  .reddish H1     { color: red }
  #x78y CODE      { background: blue }
  DIV.sidenote H1 { font-size: large }

Первый селектор соответствует элементам 'P', которые среди предков имеют 'DIV'. Второй селектор соответствует всем элементам 'H1' которые имеют предка класса 'reddish'. Третий селектор соответствует всем элементам 'CODE', которые являются наследниками элемента с 'ID=x78y'. Четвертый селектор соответствует всем элементам 'H1', которые имеют предка 'DIV' с классом 'sidenote'.

Можно группировать несколько контекстных селекторов:

  H1 B, H2 B, H1 EM, H2 EM { color: red }

что эквивалентно:

  H1 B { color: red }
  H2 B { color: red }
  H1 EM { color: red }
  H2 EM { color: red }

1.7 Комментарии

Текстовые комментарии в таблицах стилей оформляются так же как и в языке Си [7]:

  EM { color: red }  /* red, really red!! */

Комментарии не могут вкладываться друг в друга. Для обработчика CSS1 комментарий эквивалентен пробелу.


2. Псевдо-классы и псевдо-элементы. Спецификация CSS1 | WebClub

2. Псевдо-классы и псевдо-элементы. Спецификация CSS1

В CSS1 стиль обычно применяется к элементу в соответствии с его позицией в структуре документа. Эта простая модель достаточна для широкого спектра стилей, но она не покрывает несколько типичных эффектов. Концепция псевдо-классов и псевдо-элементов расширяет механизм адресации в CSS1, чтобы позволить информации, внешней по отношению к документу, оказывать влияние на процесс форматирования.

Псевдо-классы и псевдо-элементы могут использоваться в селекторах CSS, но они не существуют в исходном тексте HTML. Напротив, они "вставляются" UA в соответствии с некоторыми условиями. Их поведение можно описать с помощью фиктивной последовательности тэгов.

Псевдо-элементы используются для адресации подчастей элементов, в то время как псевдо-классы позволяют различать различные типы элементов.

2.1 Псевдо-классы ссылок

UA часто отображают недавно посещенные ссылки не так как не посещенные или посещенные давно.. В CSS1 эти ситуации обрабатываются с помощью псевдо-классов элемента 'A':

  A:link { color: red }       /* unvisited link */
  A:visited { color: blue }   /* visited links */
  A:active { color: lime }    /* active links */

Все элементы 'A' с атрибутом 'HREF' будут помещены в одну и только одну из этих групп (т.е. псевдо-классы не влияют на ссылки-якоря). UA могут по собственному усмотрению перемещать элемент из 'visited' в 'link' после некоторого периода времени. Ссылка типа 'active' - это ссылка, которая в данный момент выбрана (например нажатием на кнопку мыши) читателем.

Форматирование псевдо-класса ссылки происходит также, как если бы класс был указан вручную. UA не обязан переформатировать уже отображаемый документ при переходе ссылки из одного псевдо-класса в другой. Например таблица стилей может законно указывать, что 'font-size' ссылки типа 'active' должен быть больше, чем у ссылки типа 'visited', но UA не обязан динамически переформатировать документ, когда читатель выбирает ссылку типа 'visited'.

Селекторы псевдо-класса не соответствуют обычным классам и наоборот. Правило в следующем примере не будет оказывать никакого влияния:

  A:link { color: red }

  <A CLASS=link NAME=target5> ... </A>

В CSS1 псевдо-классы ссылок не оказывают влияния ни на один элемент, кроме 'A'. Поэтому тип элемента в селекторе можно опускать:

  A:link { color: red }
  :link { color: red }

Эти два селектора будут идентичны в CSS1.

Имена псевдо-классов не зависят от регистра.

Псевдо-классы могут использоваться в контекстных селекторах:

  A:link IMG { border: solid blue }

Псевдо-классы также могут комбинироваться с простыми классами:

  A.external:visited { color: blue }

  <A CLASS=external HREF="http://out.side/">
                   external link</A>

Если ссылка в этом примере была посещена, то она будет синей. Обратите внимание, что имя обычного класса предшествует имени псевдо-класса в селекторе.

2.2 Типографские псевдо-элементы

Некоторые распространенные типографские эффекты связаны не со структурными элементами, а с элементами форматирования на холсте. В CSS1 определены два таких типографских элемента, которые можно адресовать с помощью псевдо-элементов: первая строка элемента и первая буква.

Ядро CSS1: UA могут игнорировать все правила с ':first-line' или ':first-letter' в селекторе, или поддерживать только подмножество свойств псевдо-элементов. (см. раздел 7)

2.3 Псевдо-элемент 'first-line'

Псевдо-элемент 'first-line' используется для применения особых стилей к первой строке элемента в соответствии с форматированием на холсте:

  <STYLE TYPE="text/css">
    P:first-line { font-style: small-caps }
  </STYLE>

  <P>The first line of an article in Newsweek.

В текстовом UA это могло бы быть отформатировано так:

  THE FIRST LINE OF AN
  article in Newsweek.

Фиктивная последовательность тэгов в этом случае такова:

  <P>
  <P:first-line>
  The first line of an
  </P:first-line>
  article in Newsweek.
  </P>

Концевой тэг 'first-line' вставляется в конец первой строки в соответствии с тем, как документ форматируется на холсте.

Псевдо-элемент 'first-line' может применяться только к блочным элементам.

Псевдо-элемент 'first-line' соответствует строчным элементам за некоторыми исключениями. Только следующие свойства могут быть применены к элементу 'first-line': свойства шрифтов (5.2), свойства цвета и фона (5.3), 'word-spacing' (5.4.1), 'letter-spacing' (5.4.2), 'text-decoration' (5.4.3), 'vertical-align (5.4.4), 'text-transform' (5.4.5), 'line-height' (5.4.8) и 'clear' (5.5.26).

2.4 Псевдо-элемент 'first-letter'

Псевдо-элемент 'first-letter' используется для создания эффекта буквицы, являющимся распространенным типографским эффектом. Он соответствует строчному элементу, если его свойство 'float' равно 'none', иначе он соответствует плавающему элементу. К псевдо-элементу 'first-letter' применимы следующие свойства: свойства шрифтов (5.2), свойства цвета и фона (5.3), 'text-decoration' (5.4.3), 'vertical-align (только если 'float' равно 'none', 5.4.4), 'text-transform' (5.4.5), 'line-height' (5.4.8), свойства границ (5.5.1-5.5.5), свойства отступов (5.5.6-5.5.10), свойства рамки (5.5.11-5.5.22), 'float' (5.5.25), 'clear' (5.5.26).

Следующий пример позволяет создать эффект буквицы, когда высота первой буквы равна высоте двух строк:

<HTML>
 <HEAD>
  <TITLE>Title</TITLE>
  <STYLE TYPE="text/css">
P              { font-size: 12pt; line-height: 12pt }
P:first-letter { font-size: 200%; float: left }
SPAN           { text-transform: uppercase }
  </STYLE>
 </HEAD>
 <BODY>
  <P><SPAN>The first</SPAN> few words
  of an article in The Economist.</P>
 </BODY>
</HTML>

Если некий текстовый UA поддерживает псевдо-элемент 'first-letter' (что скорее всего не так), предыдущий пример мог бы быть отформатирован следующим образом:

  ___
   | HE FIRST few
   | words of an
  article in the
  Economist.

Фиктивная последовательность тэгов в этом случае выглядит так:

  <P>
  <SPAN>
  <P:first-letter>
  T
  </P:first-letter>he first
  </SPAN>
  few words of an article in the Economist.
  </P>

Обратите внимание, что тэг псевдо-элемента 'first-letter' заключает контент (т.е. первую букву), в то время как начальный тэг псевдо-элемента 'first-line' вставляется сразу после тэга элемента, к которому он применяется.

UA сам определяет, какие символы включаются в элемент 'first-letter'. Обычно кавычки, предшествующие первой букве, должны включаться:

  ||   /\    bird in
      /  \   the hand
     /----\  is worth
    /      \ two in
  the bush," says an
  old proverb.

Когда параграф начинается с другого знака пунктуации (например скобка) или других символов, которые не являются буквами (например цифры или математические знаки), псевдо-элементы 'first-letter' обычно игнорируются.

Некоторые языки могут иметь специфические правила, как обрабатывать определенные комбинации букв. В голландском, например, если слово начинается с "ij", то обе буквы считаются принадлежащими псевдо-элементу 'first-letter'.

Псевдо-элемент 'first-letter' может применяться только к блочным элементам.

2.5 Псевдо-элементы в селекторах

В контекстных селекторах псевдо-элементы допускаются только в конце селектора:

  BODY P:first-letter { color: purple }

Псевдо-элементы могут в селекторах комбинироваться с классами:

  P.initial:first-letter { color: red }

  <P CLASS=initial>First paragraph</A>

Этот пример позволит сделать темно красными первые буквы всех параграфов, имеющих класс initial. При комбинировании с классами или псевдо-классами псевдо-элементы должны указываться в конце селектора. В одном селекторе может быть указан только один псевдо-элемент.

2.6 Множество псевдо-элементов

Можно комбинировать несколько псевдо-элементов:

  P { color: red; font-size: 12pt }
  P:first-letter { color: green; font-size: 200% }
  P:first-line { color: blue }

  <P>Some text that ends up on two lines</P>

В этом примере первая буква каждого элемента 'P' будет зеленой с размером шрифта 24pt. Остаток первой строки (в соответствии с форматированием на холсте) будет синим, в то время как весь остальной параграф будет красным. Предположив, что перенос строки произойдет перед словом "ends", фиктивная последовательность тэгов будет такова:

  <P>
  <P:first-line>
  <P:first-letter>
  S
  </P:first-letter>ome text that
  </P:first-line>
  ends up on two lines
  </P>

Обратите внимание, что элемент 'first-letter' находится внутри элемента 'first-line'. Свойства, установленные в 'first-line' будут унаследованы 'first-letter', но будут переопределены, если то же свойство установлено в 'first-letter'.

Если псевдо-элемент разрывает настоящий элемент должны быть сгенерированны дополнительные тэги в фиктивной последовательности тэгов. Например, если элемент SPAN длится дальше тэга </P:first-line>, должен быть сгенерирован дополнительный набор тэгов SPAN, т.е. фиктивная последовательность тэгов примет следующий вид:

  <P>
  <P:first-line>
  <SPAN>
  This text is inside a long
  </SPAN>
  </P:first-line>
  <SPAN>
  span элемент
  </SPAN>


3. Каскадирование. Спецификация CSS1 | WebClub

3. Каскадирование. Спецификация CSS1

В CSS влиять на внешний вид документа может не только одна таблица стилей, а сразу несколько одновременно. Для этого есть две основных причины: модульная структура и баланс автор/читатель.

модульная структура
Дизайнер таблицы стилей может комбинировать несколько (частичных) таблиц стилей для уменьшения избыточности:
  @import url(http://www.style.org/pastoral);
  @import url(http://www.style.org/marine);

  H1 { color: red }     /* переопределить
             импортированные таблицы */
баланс автор/читатель
Как читатели так и авторы могут влиять на внешний вид с помощью таблиц стилей. Для достижения этого они используют один и тот же язык таблиц стилей. UA имеет право самостоятельно вырабатывать механизм обращения к персональной таблице стилей.

Иногда могут возникать конфликты между таблицами стилей, которые влияют на документ. Разрешение конфликтов основывается на том, что каждое правило имеет вес. По умолчанию веса читательских правил меньше, чем веса правил, определенных автором. Т.е., если есть конфликт между правилом автора и правилом читателя, будет использовано правило автора. Как читательское, так и авторское правила переопределяют значения по умолчанию UA.

Импортируемые таблицы стилей также каскадируются в порядке их импортирования, в соответствии с правилами каскадирования, описанными ниже. Любые правила, определенные в самой таблице стилей, переопределяют правила импортированной таблицы стилей. Импортируемые таблицы стилей могут в свою очередь сами импортировать и, соответственно, переопределять другие таблицы стилей.

В CSS1 все определения '@import' должны помещаться в начало таблицы стилей перед всеми определениями. Это создает наглядность в переопределении правил в импортированных таблицах стилей.

3.1 'important'

Дизайнеры таблиц стилей могут увеличить веса определений:

  H1 { color: black ! important; background:
                         white ! important }
  P  { font-size: 12pt ! important;
                         font-style: italic }

В приведенном примере первые три определения имеют повышенный вес, а последнее определение имеет нормальный вес.

Читательское правило с повышенным с помощью 'important' весом определения переопределяет правило автора с нормальным весом. А авторское правило с повышенным весом определения переопределяет читательское правило с повышенным весом определения.

3.2 Порядок каскадирования

Наличие конфликтов правил присуще механизму CSS. Чтобы найти значение комбинации элемент/свойство, необходимо следовать следующему алгоритму:

1. Найти все определения, которые соответствуют паре элемент/свойство. Определение применяется, если селектор соответствует элементу. Если определение не применяется, используется унаследованное значение. Если унаследованного значения нет, используется значение по умолчанию.

2. Отсортировать определения по их весу: определения, обозначенные как '!important', имеют больший вес, чем необозначенные (нормальные) определения.

3. Отсортировать по источнику: авторская таблица стилей переопределяет читательскую таблицу стилей, которая переопределяет значения по умолчанию UA. Импортируемая таблица стилей имеет тот же источник, что и импортирующая.

4. Отсортировать по специфичности селектора: более специфичные селекторы переопределяют менее специфичные. Для определения значения специфичности необходимо сосчитать количество атрибутов ID в селекторе (a), количество атрибутов с классом в селекторе (b) и количество имен тэгов в селекторе (c). Объединение трех чисел (по системе счисления с большим основанием) дает искомое значение специфичности. Некоторые примеры:

LI           {...} /* a=0 b=0 c=1 -> специфичность =   1 */
UL LI        {...} /* a=0 b=0 c=2 -> специфичность =   2 */
UL OL LI     {...} /* a=0 b=0 c=3 -> специфичность =   3 */
LI.red       {...} /* a=0 b=1 c=1 -> специфичность =  11 */
UL OL LI.red {...} /* a=0 b=1 c=3 -> специфичность =  13 */
#x34y        {...} /* a=1 b=0 c=0 -> специфичность = 100 */

Псевдо-элементы и псевдо-классы учитываются соответственно также, как и нормальные элементы и классы.

5. Отсортировать в порядке определения: если два правила имеют одинаковый вес, указанное позднее побеждает. Правила в импортированной таблице стилей считаются предшествующими любому правилу внутри самой таблицы стилей.

Поиск значения свойства может быть прекращен, если одно правило имеет больший вес чем остальные правила, соответствующие той же паре элемент/свойство.

Такая стратегия дает писателю большую власть над отображением документа, чем у читателя. В связи с этим важно предоставлять читателю возможность отключать действие конкретной таблицы стилей.

Определение в атрибуте 'STYLE' элемента (см. раздел 1.1) имеет тот же вес, что и определение через ID селектор, определенный в таблице стилей:

<STYLE TYPE="text/css">
  #x97z { color: blue }
</STYLE>

<P ID=x97z STYLE="color: red">

Но в данном примере элемент 'P' будет красным. Так как специфичность обоих определений одинакова, определение в атрибуте 'STYLE' переопределит определение через ID согласно правилу 5.

UA могут по-прежнему поддерживать стилистические атрибуты HTML, например 'ALIGN'. Если так, то эти атрибуты должны переводиться в соответствующее правило CSS со специфичностью равной 1. Должно считаться, что такие правила описаны в начале авторской таблицы стилей и могут быть переопределены нижеследующими правилами в таблице стилей. В переходный период такая политика позволит стилистическим атрибутам сосуществовать с таблицей стилей.


4 Модель форматирования. Спецификация CSS1 | WebClub

4 Модель форматирования. Спецификация CSS1

CSS1 предполагает простой ортогональный способ форматирования, когда каждый элемент образуется одним или несколькими прямоугольными полями. (Элементы, имеющие параметр 'display' со значением 'none', не форматируются и поэтому не влияют на поле. Все поля имеют внутреннюю содержательную область с устанавливаемыми смежными областями отступа, рамки и границы.

    _______________________________________
   |                                       |
   |           граница (прозрачная)        |
   |   _________________________________   |
   |  |                                 |  |
   |  |        рамка                    |  |
   |  |   ___________________________   |  |
   |  |  |                           |  |  |
   |  |  |     отступ                |  |  |
   |  |  |   _____________________   |  |  |
   |  |  |  |                     |  |  |  |
   |  |  |  |  содержание         |  |  |  |
   |  |  |  |_____________________|  |  |  |
   |  |  |___________________________|  |  |
   |  |_________________________________|  |
   |_______________________________________|

            |  ширина   элемента  |

   |             ширина поля               |

Размеры границы, рамки и отступа устанавливаются соответственно описаниям следующих разделов настоящего документа: (5.5.1-5.5.5) для границы; (5.5.6-5.5.10) для отступа; (5.5.11-5.5.22) для рамки.

Отступ использует тот же самый фон, что и сам элемент (установка свойств фона см. 5.3.2-5.3.7). Цвет и стиль рамки устанавливаются в свойствах рамки. Границы всегда прозрачны, поэтому через них будет просвечивать родительский элемент.

Размеры поля есть сумма размеров элемента (форматированного текста или изображения) и размеров областей отступа, рамки и границы.

С точки зрения форматирования элементы подразделяются на два основных типа: блочные и строчные .

4.1 Блочные элементы

Элементы с параметром 'display', имеющим значение 'block' или 'list-item' относятся к блочным элементам. Плавающие (floating) элементы ( т.е. со значением параметра 'float' иным, чем 'none') также считаются блочными.

Следующий пример показывает, как границы и отступы форматируют элемент 'UL' с двумя дочерними. Для упрощения границы на диаграмме не показаны. Учтите, что односимвольные "константы" в данном примере - это не синтаксис CSS1, это просто удобный способ связать значения таблицы стилей с рисунком.

    <STYLE TYPE="text/css">
      UL {
        background: red;
        margin: A B C D;
        padding: E F G H;
      }
      LI {
        color: white;
        background: blue;
        margin: a b c d;
        padding: e f g h;
      }
    </STYLE>
    ..
    <UL>
      <LI>1й элемент списка
      <LI>2й элемент списка
    </UL>
    _______________________________________________________
  |                                                       |
  |    A            UL граница прозрачная                 |
  |    _______________________________________________    |
  | D |                                               | B |
  |   |    E          UL отступ красный               |   |
  |   |    _______________________________________    |   |
  |   | H |                                       | F |   |
  |   |   |    a    LI граница прозрачная         |   |   |
  |   |   |       красный цвет просвечивает       |   |   |
  |   |   |    _______________________________    |   |   |
  |   |   | d |                               | b |   |   |
  |   |   |   |    e LI отступ синий (blue)   |   |   |   |
  |   |   |   |                               |   |   |   |
  |   |   |   | h  1й элемент списка        f |   |   |   |
  |   |   |   |                               |   |   |   |
  |   |   |   |    g                          |   |   |   |
  |   |   |   |_______________________________|   |   |   |
  |   |   |                                       |   |   |
  |   |   |     max(a, c)                         |   |   | <- См. max
  |   |   |    _______________________________    |   |   |
  |   |   |   |                               |   |   |   |
  |   |   | d |    e LI отступ синий (blue)   |   |   |   |
  |   |   |   |                               |   |   |   |
  |   |   |   | h 2й элемент списка         f |   |   |   |
  |   |   |   |                               |   |   |   |
  |   |   |   |    g                          |   |   |   |
  |   |   |   |_______________________________|   |   |   |
  |   |   |                                       |   |   |
  |   |   |   c      LI отступ прозрачный,        |   |   |
  |   |   |        красный цвет просвечивает      |   |   |
  |   |   |_______________________________________|   |   |
  |   |                                               |   |
  |   |    G                                          |   |
  |   |_______________________________________________|   |
  |                                                       |
  |   C                                                   |
  |_______________________________________________________|

В техническом плане, свойства заполнения и границы не наследуются. Но, как видно из примера, элемент позиционируется относительно родительских и "братских", поэтому свойства границы и заполнения последних отражаются на дочерних элементах.

Если бы в вышеназванном примере указывались рамки, они бы располагались между отступами и границами.

Некоторые из используемых терминов иллюстрирует следующая диаграмма:

                               --------------- <-- верх
                               верхняя граница
                               ---------------
                                верхняя рамка
                               ---------------
                               верхний  отступ
                               +-------------+ <-- потолок
    |        |        |        |             |         |         |         |
    |  левая |  левая |  левый |             | правый  | правая  | правая  |
    |-граница|-рамка--|-отступ-|-содержание--|-отступ--|--рамка--|-граница-|
    |        |        |        |             |         |         |         |
                               +-------------+ <-- подвал
    ^                          ^             ^                             ^
левый внешний край | левый внутр. край | правый внутр. край | правый внешний край
                                нижний отступ
                               ---------------
                                 нижняя рамка
                               ---------------
                               нижняя  граница
                               --------------- <-- низ

Левый внешний край - это край всего элемента, с учетом отступа, рамки и границы. Левый внутренний край - это край содержания внутри отступа, рамки и границы. Справа - аналогично. Верх - это верх самого объекта с учетом отступа, рамки и границы; он определяется для строковых и плавающих элементов, но не для фиксированных блочных элементов. Потолок - это верх содержания внутри отступа, рамки и границы. Низ - это низ самого элемента, ниже отступа, рамки и границы; он определяется для строковых и плавающих элементов, но не для фиксированных блочных элементов. Подвал - это низ элемента внутри отступа, рамки и границы (низ содержания).

Ширина элемента - это ширина содержания, т.е. расстояние между левым и правым внутренними краями. Высота - это высота содержания, т. е. расстояние от подвала до потолка.

4.1.1 Вертикальное форматирование

Ширина границы фиксированного блочного элемента определяют минимальное расстояние до краев окружающих полей. Две или более прилегающих вертикальных границы (т.е. без рамки, отступа и содержания между ними) сжимаются для максимального использования значения границы. В большинстве случаев после сжатия вертикальных границ результат приятнее на вид и ближе к ожидаемому дизайнером. В приведенном примере границы между двумя элементами 'LI' сжимаются с использованием максимума нижней границы первого элемента 'LI' и верхней границы второго элемента 'LI'. Аналогично, если заполнение между 'UL' и первым элементом 'LI' (значение "Е") было бы равно нулю, границы 'UL' и первого элемента 'LI' были бы сжаты.

В случае отрицательных границ, абсолютный максимум прилегающих отрицательных границ вычитается из максимума прилегающих положительных границ. Если положительных границ нет, абсолютный максимум негативных прилегающих границ вычитается из нуля.

4.1.2 Горизонтальное форматирование

Горизонтальное позиционирование и размеры фиксированного блочного элемента определяются семью свойствами: 'margin-left', 'border-left', 'padding-left', 'width', 'padding-right', 'border-right' и 'margin-right'. Сумма этих семи составляющих равносильна ширине родительского элемента.

По умолчанию, "ширине" элемента присваивается значение 'auto'. Если элемент не является замещаемым, это значит, что UA считает "ширину" так, чтобы сумма указанных семи свойств была эквивалентна ширине родительского элемента. Если элемент замещаемый, то значение 'auto' для "ширины" автоматически заменяется внутренней шириной элемента.

Трем из семи свойств может быть присвоено значение 'auto': "левая граница", "ширина", и "правая граница". Для замещаемых элементов значение 'auto' для "ширины" автоматически заменяется внутренней шириной элемента, поэтому для них могут существовать только два значения auto'.

"Ширина" имеет неотрицательное минимально-допустимое значение, определяемое UA (которое может меняться от элемента к элементу, и даже зависеть от других свойств). Если "ширина" меньше допустимой (даже если она задана явным образом либо имела значение 'auto' и по приоритетному правилу должна быть меньше), ее значение будет заменено заменяется минимально-допустимым.

Если именно одно из свойств "левая граница", "ширина" или "правая граница" имеют значение 'auto', UA определит этому свойству значение, которое сделает сумму значений семи свойств эквивалентной ширине родительского элемента.

Если ни одно из свойств не имеет значения 'auto', то последнее будет присвоено свойству "правая граница".

Если более чем одному из трех вышеуказанных свойств будет присвоено значение 'auto', и одно из них- "ширина", то остальные ("левая граница", и/или "правая граница") будут приравнены к нулю, а "ширина" получит значение, необходимое, чтобы сделать сумму значений семи свойств эквивалентной ширине родительского элемента.

В противоположной ситуации, если как "правая граница" так и "левая граница" будут иметь значение 'auto', им будут присвоены равноценные значения. Элемент будет выронен по центру внутри родительского.

Если значение 'auto' было присвоено как значение для одного из вышеупомянутых семи свойств в строчном или плавающем элементе, это будет обработано так, как если бы им было присвоено нулевое значение.

В отличие от вертикальных границ, горизонтальные границы не сливаются.

4.1.3 Элементы-списки

Элементы со свойством 'display', имеющим значение 'list-item' ("список"), форматируются как блочные, но предваряются маркером. Тип маркера определяется свойством "стиль списка" ('list-style'). Маркер устанавливается в соответствии со значением свойства "стиль списка":

  <STYLE TYPE="text/css">
    UL         { list-style: outside }
    UL.compact { list-style: inside }
  </STYLE>

  <UL>
    <LI>первый пункт списка идет первым
    <LI>второй пункт списка идет вторым
  </UL>

  <UL CLASS=COMPACT>
    <LI>первый пункт списка идет первым
    <LI>второй пункт списка идет вторым
  </UL>

Форматирование по данному примеру может выглядеть как:

  * первый пункт списка
    идет первым

  * второй пункт списка
    идет вторым

  * первый пункт
  списка идет первым

  * второй пункт
  списка идет вторым

Если текст выравнивается по ширине, маркеры должны быть на левой стороне поля.

4.1.4 Плавающие элементы

Используя свойство элемента "плавающий" ('float'), можно декларировать его в качестве внешнего по отношению к нормальному потоку элементов, а затем форматировать, как блочный элемент. Например, при установке значения 'left' для свойства "плавающий" применительно к изображению, последнее перемещается влево до границы отступа или рамки, либо достигает другого блочного элемента. Нормальный поток будет переноситься по правой стороне. Границы, рамки и отступы самого элемента будут учитываться, и никогда не будут сливаться с границами смежных элементов.

При позиционировании плавающего элемента должны выполняться следующие условия (см. раздел 4.1. для разъяснения терминов):

1. Левая внешняя граница левоориентированного плавающего элемента не может быть слева от левой внутренней границы родительского элемента. Аналогично для правоориентированных плавающих элементов.

2. Правая внешняя граница левоориентированного плавающего элемента должна быть справа от правой внутренней границы любого предшествующего (согласно исходному тексту HTML) левоориентированного элемента, или же вершина предшествующего элемента должна быть ниже чем низ последующего. Аналогично для правоориентированных плавающих элементов.

3. Правая внешняя граница левоориентированного плавающего элемента не может быть справа от левой внешней границы правоориентированного плавающего элемента, расположенного правее его. Аналогично для правоориентированных плавающих элементов.

4. Вершина плавающего элемента не может быть выше потолка родительского элемента.

5. Вершина плавающего элемента не может быть выше вершины любого из предшествующих плавающих или блочных элементов.

6. Вершина плавающего элемента не может быть выше вершины строчного поля (см. раздел 4.4) с содержанием, предшествующим описанию плавающего элемента в исходном тексте HTML.

7. Плавающий элемент должен расположиться как можно выше.

8. Левоориентированный плавающий элемент должен расположиться как можно левее, правоориентированный - как можно правее. Приоритетно крайнее положение устанавливается для более левого или правого элемента.

  <STYLE TYPE="text/css">
    IMG { float: left }
    BODY, P, IMG { margin: 2em }
  </STYLE>

  <BODY>
    <P>
      <IMG SRC=img.gif>
      Примерный текст, чтобы показать...
  </BODY>

Форматирование в приведенном примере будет выглядеть так:

   ________________________________________
  |
  |          max(граница BODY, граница P)
  |          ______________________________
  |    |    |             Примерный текст,
  | г  | г  |границы IMG  чтобы показать, как
  | р  | р  |    _____    плавающие элементы
  | а  | а  |   |     |   смещаются относительно
  | н  | н  |   | IMG |   к сторонам родительского
  | и  | и  |   |     |   элемента, несмотря
  | ц  | ц  |   |_____|   на приоритет
  | a  | а  |             границы, бордюра
  |    |    |             и заполнения.
  | B  | P  |             Обратите внимание,
  | O  |    |как смежные вертикальные границы
  | D  |    |сжимаются между фиксированными
  | Y  |    |блочными элементами.

Обратите внимание, как границы элемента 'P' окружают плавающий элемент 'IMG'.

Есть два случая, когда плавающие элементы могут перекрывать области границы, рамки и отступа других элементов:

  • когда плавающий элемент имеет отрицательную величину границы: отрицательные границы плавающих элементов имеют приоритет над иными блочными элементами;
  • когда плавающий элемент выше или шире чем элемент внутри.

4.2 Строчные элементы

Элементы, не форматируемые как блочные, относятся к строчным. Строчный элемент может делить пространство строки с другими элементами. Рассмотрим следующий пример:

  <P>Несколько <EM>подчеркнутых</EM> слов <STRONG>имеется</STRONG>.</P>

Элемент 'P' - нормальный блочный, в то время как 'EM' и 'STRONG' - строчные. Если ширина элемента 'P' достаточна, чтобы расположить весь элемент на одной строке, то тогда в строке будут два строчных элемента:

  Несколько подчеркнутых слов имеется.

Если же в строке места не хватает, строчные элементы будут разбиты на несколько полей:

  <P>Несколько <EM>подчеркнутых слов</EM> имеют место.</P>

Форматирование этого примера будет выглядеть как:

  Несколько подчеркнутых
  слов имеют место.

Если строчный элемент имеет границы, бордюры, заполнение или приложенное текстовое оформление, то их эффект не будет проявляться в месте расчленения элемента:

            -------------
  Несколько |подчеркнутых
            -------------
  -----
  слов | имеют место.
  -----

( Вышеприведенный рисунок несколько искажается из-за использования псевдографики ASCII. См. описание вычисления высоты линий в разделе 4.4.)

4.3 Замещаемые элементы

Замещаемые элементы - это элементы, замещаемые содержанием, указываемым в элементе. Например элемент 'IMG' замещается изображением, указанным в атрибуте 'SRC'. Важным свойством замещаемых элементов является наличие своих внутренних размеров. Если значения свойства "ширина"('width') равна 'auto', то в качестве ширины элемента используется внутренняя ширина. Если значение иное, чем 'auto' из перечисленных в таблице стилей, то это значение и используется, а замещаемый элемент соответственно меняет размеры (метод изменения размеров будет определяться видом информационного объекта). Таким же образом используется свойство "высота" ('height').

Замещаемые элементы могут быть как блочными, так и строчными.

4.4 Высота строки

Все элементы имеют свойство "высота строки" ('line-height'), что, в принципе, определяет общую высоту строки или текста. Высота строки увеличивается на промежуток, вставляемый выше и ниже текста в строке. К примеру, если текст 12 пунктов,

высота и "высота строки" устанавливаются равными '14pt', т.к. вставляется промежуток в 2 пункта, 1 сверху и 1 снизу строки. Пустые элементы влияют на эти расчеты точно так же, как и имеющие содержание.

Разница между размером шрифта (font size) и "высотой строки" называется интервалом (leading). Половина интервала называется полуинтервалом (half-leading). После форматирования каждая строка образует прямоугольное строчное поле.

Если строка текста содержит секции с разными значениями свойства "высота строки" (т.к. может быть несколько строчных элементов в одной строке), то каждая из таких секций имеет собственный полуинтервал сверху и снизу. Высота строчного поля отсчитывается от высоты самой высокой секции до низа самой низкой. Обратите внимание, что верх и низ не обязательно соответствуют наиболее высокому элементу, поскольку элементы могут быть позиционированы с помощью свойства "вертикальное выравнивание" ('vertical-align'). При формировании параграфа каждое строчное поле располагается ниже предшествующей строки.

Обратите внимание на то, что какой-либо отступ, рамка или граница сверху или снизу не замещаемого строчного элемента не влияют на высоту строки. Иными словами, если значение свойства "высота строки" слишком мало для выбранных отступа или рамки, они будут перекрываться с текстом других строк.

Замещаемые элементы, (напр. изображения) в строке могут увеличить строковое поле, если вершина замещаемого элемента (включая полностью его отступ, рамку и границу) находится выше самой высокой секции текста, или его низ находится ниже самой низкой секции.

В нормальных условиях, если в параграфе указано только одно значение свойства "высота строки", и при отсутствии высоких изображений, по предшествующему определению будет проверено, имеют ли базы последующих строк обособленное значение параметра "высота строки". Это важно в случае выравнивания текстовых колонок с различным шрифтом, например, в таблице.

Следует иметь в виду, что это не позволяет исключить возможность перекрытия текста двух смежных строк. Значение свойства "высота строки" может оказаться меньше высоты текста, и интервал в этом случае будет отрицательным. Это обстоятельство может оказаться полезным, если заранее известно, что в тексте не будет подстрочных символов (напр. если там будут только символы верхнего регистра), и тогда строки можно сблизить.

4.5 Холст

Холст представляет собой часть изображаемой на экране поверхности UA, где воспроизводятся документы. Ни один из структурных элементов документа не сопоставляется с холстом, в связи с чем при форматировании документа возникают два вопроса:

  • откуда должны определяться размеры холста?
  • если документ не покрывает весь холст, как должна воспроизводиться эта область?

Логично было бы ответить на первый вопрос: "Изначальные размеры холста основаны на размерах окна", но CSS1 оставляет эту задачу на усмотрение UA. Также было бы логично, чтобы UA менял размеры холста соответственно размерам окна, но это также выходит за рамки полномочий CSS1.

Расширение языка HTML создало прецедент по второму вопросу: атрибуты элемента 'BODY' определяют фон для всего холста. Чтобы не разочаровать дизайнеров, CSS1 использует особое правило для определения фона холста:

Если значения свойств фона для элемента 'HTML' отличаются от 'transparent', то они и используются, в ином случае используются значения свойств фона, указанные для элемента 'BODY'. Если в результате все равно значение параметров фона равно 'transparent', то свойства фона считаются незаданными.

По этому правилу допускается:

  <HTML STYLE="background: url(http://style.com/marble.png)">
  <BODY STYLE="background: red">

В вышеуказанном примере, холст покрывается картинкой marble.png. Фон элемента 'BODY' (он может и не полностью покрывать холст) будет красным.

Пока другие способы адресации графики на холсте не станут доступными, рекомендуется, чтобы свойства холста задавались в элементе 'BODY'.

4.6 Элементы 'BR'

Текущие свойства и значения CSS1 не способны описать поведение элемента 'BR'. В языке HTML элемент 'BR' определяется как перевод строки между словами. Фактически, это принудительный перевод строки. Будущие версии CSS будут поддерживать ввод и замену содержания, но средства форматирования основанные на CSS1, должны использовать отдельные алгоритмы обработки объекта 'BR'


5. Свойства CSS1. Спецификация CSS1 | WebClub

5. Свойства CSS1. Спецификация CSS1

Таблицы стилей влияют на внешний вид документа путем присвоения тех или иных значений свойствам стилей. В данном разделе перечисляются определения свойств стилей и приводится соответствующий перечень значений, допускаемых CSS1.

5.1 Разъяснение значений свойств.

В настоящем тексте допустимые значения для каждого свойства перечисляются с синтаксисом, подобным приведенному ниже:

     Значение: N | NW | NE
     Значение: [ <length> | thick | thin ]{1,4}
     Значение: [<family-name> , ]* <family-name>
     Значение: <url>? <color> [ / <color> ]?
     Значение: <url> || <color>

Слова между "<" and ">" дают нам тип значения. Наиболее распространенными типами являются <length> (длина), <percentage> (доля, процент), <url>, <number> (число) и <color> (цвет); они описаны в разделе 6. Более специфические типы (такие, как <font-family> и <border-style>) описаны в разделах соответственно свойствам.*

Другой вид слов - ключевые, которые должны указываться литерально, без знаков препинания. Знаки "слеш" (/) и запятая (,) также должны указываться литерально.

Если что-то размещается рядом, то это значит, что действия должны производиться в указанном порядке. Вертикальная черта (A|B) означает исключающее ИЛИ (ИЛИ-НЕ); должно происходить одно из двух. Двойная черта (A||B) означает объединяющее ИЛИ (ИЛИ-И) - или и то и другое, или одно из двух. Квадратные скобки([]) - группировка. Расположение рядом важнее, чем ИЛИ-И, а последнее важнее, чем ИЛИ-НЕ. Так, "a b | c || d e " равноценно "[ a b ] | [ c || [ d e ]]".

Каждый знак, ключевое слово или группа в скобках могут следовать за одним из модификаторов:

  • Звездочка (*) означает, что предшествующие знак, слово или группа повторяются ноль или более раз.
  • Плюс (+) означает, что предшествующие знак, слово или группа повторяются один или более раз.
  • Знак вопроса (?) означает, что предшествующие знак, слово или группа являются необязательными (optional).
  • Два числа в фигурных скобках ({A,B}) означают, что предшествующие знак, слово или группа повторяются не менее чем А и не более чем В раз.

5.2 Свойства шрифта

Определение свойств шрифта относится к одному из наиболее распространенных способов применения таблицы стилей. К сожалению, пока нет четкого и универсального стандарта для типометрии шрифтов, и определения, применимые к шрифтам одного семейства, могут оказаться неподходящими для других. Так, для обозначения курсива обычно используется свойство 'italic', но могут встречаться и такие обозначения, как Oblique, Slanted, Incline, Cursive или Kursiv. Таким образом, преобразование характерных черт шрифта в типометрические свойства является непростой задачей.

CSS1 определяет следующие свойства шрифтов: "семейство" ('font-family'), "начертание" ('font-style'), "вариант" ('font-variant'), а также "вес" ('font-weight'), "размер" ('font-size'), "шрифт" 'font'.

5.2.1 Распознавание шрифта.

В связи с отсутствием общепринятой классификации свойств шрифта, распознавание свойств гарнитуры шрифта должно быть произведена особенно тщательно. Свойства идентифицируются в четко выраженном порядке, чтобы результаты распознавания были настолько последовательными, насколько позволяют UA (предполагается, что на каждом из последних установлена одна и та же библиотека гарнитур шрифта).

1. UA создает базу относящихся к CSS1 свойств всех известных для UA шрифтов. UA может быть осведомлен о шрифте, если тот был установлен локально или загружен из Интернет. Если найдены два шрифта с одинаковыми свойствами, один из них игнорируется.

2. Применительно к данному элементу и для каждого из символов данного элемента, UA подбирает свойства шрифта, применимые к этому элементу. Основываясь на полном наборе свойств, UA использует свойство "семейство" ('font-family') для выбора пробного семейства шрифта. Остальные свойства проверяются на предмет того, насколько пробное семейство соответствует критериям распознавания, описываемым каждым из свойств. Если все остальные свойства могут быть распознаны UA, то распознавание гарнитуры шрифта считается завершенным.

3. В случае, если в процедуре п. 2 не удается распознать гарнитуру шрифта, и если в запасе есть другое альтернативное семейство шрифтов, процедура п. 2 повторяется применительно к последнему.

4. Если гарнитуру шрифта удалось распознать, но оно не содержит глифа для текущего символа, процедура п. 2 повторяется для следующего альтернативного начертания шрифта, если таковое имеется. См. в приложении С описание шрифтов и кодировок.

5. Если с помощью процедуры п.2 не удалось распознать гарнитуру шрифта, берется гарнитура шрифта, определенного для UA по умолчанию, затем повторяется процедура п. 2, с использованием наилучших результатов, которые могут быть достигнуты со шрифтом по умолчанию.

(Вышеизложенный алгоритм может быть оптимизирован на предмет исключения дублирования проверок свойств CSS1 для каждого символа).

Из правил распознавания свойств (2) вытекает следующее:

1. "Начертание" ('font-style') пробуется в первую очередь. "Курсив" ('italic') считается распознанным если курсив явно указан, либо данная гарнитура шрифта в базе UA отмечена зарезервированными для CSS словами 'italic' (предпочтительное), или 'oblique'. В противном случае значения должны быть распознаны правильно, иначе "начертание" считается нераспознанным.

2. Следующим пробуется "вариант". "Нормальным" считается шрифт, не отмеченный как "малые прописные" ('small-caps'); "малые прописные" считается (1) шрифт, помеченный как 'small-caps', (2) шрифт, в котором синтезированы малые прописные символы или (3) шрифт, в котором строчные буквы заменены прописными. Малые прописные буквы могут быть синтезированы путем уменьшения размеров прописных букв нормального шрифта.

3. "Вес" шрифта опознается следующим и никогда не бывает неопознанным. (См. далее "Задание веса шрифта").

4. "Размер" шрифта ('font-size') должен распознаваться в допустимых пределах, установленных UA. (Обычно размеры масштабируемых шрифтов округляются до ближайшего целого пикселя, так что допуск для растровых шрифтов на экране может доходить до 20%.) Дальнейшая обработка, например, значений в единицах "м" других свойств, основано на том значении "размера", которое используется, а не на том, которое определено.

5.2.2 Задание семейства шрифта ('font-family').
Значение: [[<family-name> | <generic-family>],]*
[<family-name> | <generic-family>]
По умолчанию: определяется UA
Область приложения: все элементы
Наследование: есть
Процентное выражение: неопр.

Значение свойства представляет собой перечень гарнитур шрифтов и/или их названий в порядке приоритетности. В отличие от других свойств CSS1, здесь значения могут разделяться запятой для указания альтернатив:

  BODY { font-family: gill, helvetica, sans-serif }

Имеются два типа перечней значений:

<family-name>
Названия избранных гарнитур шрифта (в последнем примере - "gill" и "helvetica").
<generic-family>
В нижеследующем примере последнее значение указывает на семейство шрифта. Определены следующие семейства:
  • 'serif' (напр. Times)
  • 'sans-serif' (напр. Helvetica)
  • 'cursive' (напр. Zapf-Chancery)
  • 'fantasy' (напр. Western)
  • 'monospace' (напр. Courier)
В таблицах стилей дизайнерам рекомендуется использовать указание семейства шрифта в качестве последней альтернативы.

Названия семейств, содержащие пробелы, должны быть в кавычках:

BODY { font-family: "new century schoolbook", serif }

<BODY STYLE="font-family: 'My own font', fantasy">

Если кавычки отсутствуют, то символы пробела перед и после названия гарнитуры игнорируются и любая последовательность символов пробела внутри названия семейства преобразуется в слитное написание.

5.2.3 Задание начертания шрифта ('font-style')
Значение: normal | italic | oblique
По умолчанию: normal
Область приложения: все элементы
Наследование: да
Процентное выражение: неопр.

Свойство "начертание" ('font-style') определяет выбор между нормальным (иногда обозначается также терминами "roman" или "upright"), курсивным и наклонным начертанием шрифта.

Значение "нормальный" ('normal') принимается для шрифта, если он классифицирован как нормальный ('normal') в базе шрифтов UA, в то время как "наклонным" считается шрифт, помеченный как 'oblique'. По значению "курсив" ('italic') выбирается шрифт, обозначенный как 'italic' , либо, при отсутствии такового, обозначенный как 'oblique'.

Шрифт, обозначенный как "наклонный" ('oblique') в базе шрифтов UA, может также генерироваться программным способом путем наклона линий нормального шрифта.

Шрифты, имеющие в названиях такие слова, как Oblique, Slanted или Incline, обычно обозначаются в базе данных UA как "наклонные". Шрифты, имеющие в названиях такие слова, как Italic, Cursive или Kursiv, обычно обозначаются как "курсив".

  H1, H2, H3 { font-style: italic }
  H1 EM { font-style: normal }

В вышеприведенном примере подчеркнутый текст, относящийся к H1, будет отрабатываться как нормальный.

5.2.4 Задание варианта шрифта ('font-variant').
Значение: normal | small-caps
По умолчанию: normal
Область приложения: все элементы
Наследование: есть
Процентное выражение: неопр.

Другой тип варьирования внутри семейства шрифтов - это "малые прописные". В таких шрифтах строчные буквы выглядят подобно заглавным, только меньше размером и с несколько иными пропорциями. Такой шрифт определяется свойством "вариант" ('font-variant' ).

Значение 'normal' определяет шрифт без малых прописных букв, 'small-caps' - с малыми прописными. Для CSS1 желательно (но не обязательно), чтобы малые прописные создавались путем замены строчных букв уменьшенными символами прописных. В крайнем случае, для замены шрифта с малыми прописными используются прописные буквы.

В нижеследующем примере в элементе H3 результатом будет шрифт в варианте "малые прописные", с подчеркнутыми наклонными малыми прописными буквами.

  H3 { font-variant: small-caps }
  EM { font-style: oblique }

В семействе шрифтов возможны и другие варианты, например, шрифты со старинными цифрами, малыми прописными цифрами, сжатыми или растянутыми буквами и т.п. CSS1 не предусматривает свойств для их определения.

Ядро CSS1: как для преобразования текста в написанный заглавными буквами, так и для 'text-transform' используются одинаковые соображения. (Досл.: CSS1 core: insofar as this свойство causes text to be transformed to uppercase, the same considerations as for 'text-transform' apply)

5.2.5 Задание веса шрифта ('font-weight')
Значение: normal | bold | bolder | lighter | 100 |
200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
По умолчанию: normal
Область приложения: все элементы
Наследование: есть
Процентное выражение: неопр.

Свойство "вес шрифта" определяет степень толщины линий при начертании. Это ряд значений от 100 до 900, где каждый номер указывает вес, соответствующий по крайней мере не меньшей жирности начертания, чем для предшествующего. Нормальный шрифт 'normal' соответствует номеру 400, полужирный 'bold' - 700. Другие слова, использованные вместо 'normal' или 'bold', зачастую истолковывались как названия гарнитур, и поэтому была принята численная шкала в 9 позиций.

  P { font-weight: normal }   /* 400 */
  H1 { font-weight: 700 }     /* bold */

Значения 'bolder' и 'lighter' определяют вес шрифта по отношению к унаследованного от родительского:

  STRONG { font-weight: bolder }

Дочерние элементы наследуют результирующее значение веса, но не его ключевое слово.

Шрифты (данные шрифтов) обычно имеют одно или более свойств, значения которых являются терминами, описывающими их веса. Общепринятых, универсальных описаний названий весов не существует. Первичная функция этих условных имен - различать жирность внутри одного семейства шрифтов. В целом по разным семействам шрифта терминология весьма разнообразна: к примеру, шрифт, который по внешнему виду отнесли бы к полужирным, может быть назван как Регулярный (Regular), Романский (Roman), Книжный (Book), Средний (Medium), Полужирный (Semi- или DemiBold), Жирный (Bold), или как Black, в зависимости от относительной "жирности" нормального шрифта данного семейства. В связи с отсутствием стандартных названий, свойство "вес" для CSS1 в численном выражении соизмеряется со значением 400, которое соответствует нормальному шрифту данного семейства. Этому весу обычно соответствуют такие условные имена шрифтов, как Book, Regular, Roman, Normal, Medium, Обыкновенный, Книжный, Средний, Нормальный.

Соотнесение остальных весов шрифтов в семействе с их численным обозначением предполагает сохранение только относительной жирности в пределах ряда, установленного для данной семьи. Тем не менее в ряде типичных случаев можно поступать в соответствии со следующей логикой:

  • Если семейство шрифта уже использует числовую шкалу с девятью градациями (к примеру, как шрифт Open Type), вес шрифта может быть пересчитан напрямую.
  • Если гарнитура отмечена как Medium, а в семействе есть хотя бы одна, помеченная как Book, Regular, Roman или Normal, то гарнитура Medium ассоциируется с весом 500.
  • Шрифт, отмеченный как Bold, обычно соответствует весу 700.
  • Если шкала жирности шрифта имеет меньше 9 градаций, используется следующий метод интерполяции. Если значение 500 невозможно присвоить, вместо него также присваивается 400. Если невозможно присвоить одно из значений следующего ряда - 600, 700, 800, 900 - то берется ближайшее значение для более жирного шрифта, если таковое имеется, в противном случае - для менее жирного. Если невозможно присвоить одно из значений следующего ряда - 300, 200, 100, - то берется ближайшее значение для менее жирного шрифта, если таковое имеется, в противном случае - для более жирного.

Данный метод иллюстрируют два нижеследующих примера. Возьмем для некоего условного семейства шрифтов "Example1" четыре градации веса по возрастающей: Regular, Medium, Bold, Heavy. Возьмем также шесть градаций веса для другого условного семейства шрифтов "Example2": Book, Medium, Bold, Heavy, Black, ExtraBlack. Обратите внимание на то, как во втором примере шрифт "Example2 ExtraBlack" не был с чем-либо ассоциирован.

Доступные шрифты	Назначен вес	Включены также
"Example1 Regular"	400	100, 200, 300
"Example1 Medium"	500
"Example1 Bold"	700	600
"Example1 Heavy"	800	900

Доступные шрифты
Назначен вес
Включены также
"Example2 Book"	400	100, 200, 300
"Example2 Medium"	500
"Example2 Bold"	700	600
"Example2 Heavy"	800
"Example2 Black"	900
"Example2 ExtraBlack"	(none)

В зависмости от того, как ключевые слова 'bolder' и 'lighter' делают шрифт в семействе жирнее или светлее, и в связи с тем, что семейство шрифта может не содержать шрифтов, соответствующих всем весовым значениям, обработка значения 'bolder' до следующего более жирного и значения 'lighter' до менее жирного ограничена для UA пределами семейства шрифта. Если точнее, то ключевые слова 'bolder' и 'lighter' означают следующее.

  • Значение 'bolder' выбирает для шрифта следующий вес, соответствующий более жирному шрифту, чем родительский. Если такого веса нет, то обычно используется ближайшее большее численное значение (при сохранении шрифта неизменным), если наследуемое значение не было равно 900, в противном случае вес остается равным 900.
  • Аналогично со значением 'lighter', но в обратном порядке: выбирается ближайшее значение, соответствующее менее жирному шрифту, если же таковой отсутствует, то принимается ближайшее численное значение для менее жирного шрифта, при сохранении шрифта неизменным.

Это не дает гарантии, что для любого значения "веса" найдется более жирный шрифт; к примеру, некоторые фонты бывают только нормальными или полужирными, остальные могут иметь восемь гарнитур с различным весом. Нельзя заранее угадать, как именно UA сопоставит формализованные шрифтовые параметры с конкретными шрифтами различной жирности. Можно быть уверенным лишь в том, что шрифт заданного наименования будет не менее жирным, чем имеющиеся в семействе менее жирные шрифты.

5.2.6 Задание размера шрифта ('font-size').
Значение: <absolute-size> | <relative-size> |
 <length> | <percentage>
По умолчанию: medium
Область применения: все элементы
Наследование: есть
Процентное выражение: относительно размера шрифта
 родительского элемента

<absolute-size>
Значение абсолютного размера (<absolute-size>) есть индекс таблицы размеров шрифта, поддерживаемых и отрабатываемых UA. Возможные значения следующие: [ xx-small | x-small | small | medium | large | x-large | xx-large ]. На дисплее градации масштабируются с шагом х1,5: если 'medium' - кегль 10, то 'large' - кегль 15. Различные мультимедийные устройства могут потребовать различные шкалы масштабирования. Кроме того, UA должен обеспечивать качество и разборчивость шрифтов при отработке таблицы. Для разных семейств шрифтов таблицы могут отличаться.
<relative-size>
Относительный размер (<relative-size>) отрабатывается, исходя из таблицы размеров шрифта и размера шрифта родительского элемента. Возможные значения - [ larger | smaller ]. Например, если размер шрифта родительского элемента 'medium', то значение 'larger' преобразует шрифт текущего элемента в 'large'. Если для размера шрифта родительского элемента нет близкого вхождения в таблицу, то UA волен интерполировать данные таблицы или округлить размер до ближайшего значения. UA может также экстраполировать табличные значения, если численное значение выходит за пределы зарезервированных слов.

Для значений длины и процентного выражения таблица размеров шрифта не должна приниматься в расчет, пока не будет вычислен размер шрифта элемента

Отрицательные значения не допускаются.

Для всех других свойств, значения длины 'em' и 'ex' относятся к размеру шрифта текущего элемента. Для свойства "размер шрифта" эти единицы длины относятся к размеру шрифта родительского элемента.

Заметим, что приложения могут переиначивать явно заданный размер в зависимости от содержания. К примеру, для трехмерной сцены VR размер шрифта будет изменяться в зависимости от перспективы.

примеры:

  P { font-size: 12pt; }
  BLOCKQUOTE { font-size: larger }
  EM { font-size: 150% }
  EM { font-size: 1.5em }

Если используется градация масштабирования 1.5, то три последних случая равнозначны.

5.2.7 Задание шрифта в целом ('font')
Значение: [ <font-style> || <font-variant> |
| <font-weight> ]? <font-size> [ /
<line-height> ]? <font-family>
По умолчанию: не определено для обобщающего свойства
Область применения: все элементы
Наследование: есть
Процентное выражение: допускается для <font-size>
 и <line-height>

Свойство "шрифт" есть обобщающее свойство для определения 'font-style' 'font-variant' 'font-weight' 'font-size', 'line-height' and 'font-family' в одном и том же месте таблицы стилей. Синтаксис этого свойства основан на традиционном для полиграфии способе указания группы свойств, относящихся к шрифтам.

Для определения допускаемых значений и значений по умолчанию см. вышеописанные свойства. Если для тех или иных свойств значения явно не указаны, то для них принимаются значения по умолчанию.

  P { font: 12pt/14pt sans-serif }
  P { font: 80% sans-serif }
  P { font: x-large/110% "new century schoolbook",
                                      serif }
  P { font: bold italic large Palatino, serif }
  P { font: normal small-caps 120%/120% fantasy }

Во втором примере размер шрифта, указанный в процентном выражении (80%), считается от размера шрифта родительского элемента. В третьем примере высота линии, выраженная в процентах, считается от размера шрифта самого элемента.

В первых трех примерах начертание, вариант и вес шрифта явно не заданы, и по умолчанию всем им присваивается значение 'normal'. В четвертом примере вес шрифта явно указан как 'bold', стиль как 'italic', а варианту по умолчанию присваивается значение 'normal'.

В пятом примере указан вариант "малые прописные" ('small-caps'), размер (120% от родительского шрифта), высота линии (120% от размера шрифта), и гарнитура ('fantasy'). Следовательно, значение 'normal' присваивается по умолчанию двум свойствам: "стиль" и "вес".

5.3 Свойства "цвет" (color) и "фон" (background).

Эти свойства описывают цвет (зачастую именуемый как цвет переднего плана) и фон для элемента (т.е. поверхность, на которой располагается содержание). Они позволяют установить цвет фона и(или) его рисунок. Также определяются позиция изображения рисунка фона, повторяется ли он и каким образом, и где он размещается либо перемещается относительно холста.

Свойство "цвет", как правило, наследуется. Свойство "фон" не наследуется, но фон родительского элемента будет виден на просвет, т.к. по умолчанию значение цвета фона для дочерних элементов принимается как 'transparent' (прозрачный).

5.3.1 Определение цвета ('color')
Значение: <color>
По умолчанию: Определяется UA
Область применения: все элементы
Наследование: есть
Процентное выражение: неопр.

Это свойство описывает цвет текста в элементе (зачастую именуемый как цвет переднего плана). Есть несколько разных способов описать, к примеру, красный цвет:

 EM { color: red }            /* natural language */
 EM { color: rgb(255,0,0) }   /* RGB range 0-255   */

См. раздел 6.3. для описания возможных значений цвета.

5.3.2 Определение цвета обоев ('background-color').
Значение: <color> | transparent
По умолчанию: transparent
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство описывает цвет обоев элемента.

  H1 { background-color: #F00 }
5.3.3 Определение рисунка фона ('background-image').
Значение: <url> | none
По умолчанию: нет
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство определяет рисунок фона элемента. При назначении изображения для рисунка фона, необходимо также определить цвет фона, который будет использоваться в случае недоступности изображения. Если изображение доступно, оно перекрывает цвет фона сверху.

  BODY { background-image: url(marble.gif) }
  P { background-image: none }
5.3.4 Определение повтора рисунка фона ('background-repeat').
Значение: repeat | repeat-x | repeat-y | no-repeat
По умолчанию: repeat
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Если рисунок фона определен, значение свойства "повтор рисунка фона" определяет, повторяется ли изображение и каким образом.

Значение 'repeat' указывает, что изображение повторяется как в горизонтальном, так и в вертикальном направлении. Значение 'repeat-x' ('repeat-y') устанавливает повтор изображения соответственно только в горизонтальном (вертикальном) направлении, создавая одну полосу из изображений от одного края до противоположного. При значении 'no-repeat' изображение не повторяется.

  BODY {
    background: red url(pendant.gif);
    background-repeat: repeat-y;
  }

В вышеприведенном примере изображение рисунка фона повторяется только в вертикальном направлении.

5.3.5 Определение привязки фона ('background-attachment').
Значение: scroll | fixed
По умолчанию: scroll
Область приложения: все элементы
Наследование: нет
Процентное выражение: неопр.

Если определен рисунок фона, значение свойства "привязка" ('background-attachment') определяет, привязан ли рисунок к холсту или перемещается вместе с содержанием.

  BODY {
    background: red url(pendant.gif);
    background-repeat: repeat-y;
    background-attachment: fixed;
  }

Ядро CSS1: UA вправе рассматривать 'fixed' как 'scroll'. Тем не менее рекомендуется, чтобы они правильно отрабатывали 'fixed' по крайней мере для элементов HTML и BODY, т.к. для автора не существует возможности использовать изображение только для браузеров, поддерживающих 'fixed'. (См. гл.7).

5.3.6 Определение положения фона ('background-position')
Значение: [<percentage> | <length>]{1,2} |
 [top | center | bottom] || [left |
center | right]
По умолчанию: 0% 0%
Область применения: блочные и замещаемые элементы
Наследование: нет
Процентное значение: по отношению к стороне
 самого элемента

Если рисунок фона был определен, значение свойства "положение фона" определяет его начальное расположение.

При парном значении '0% 0%', верхний левый угол изображения устанавливается в верхнем левом углу поля, окружающего содержание элемента (т.е. не того поля, что окружает отступ, рамку или границу). При парном значении '100% 100%', нижний правый угол изображения устанавливается в нижнем правом углу элемента. При парном значении '14% 84%', точка изображения 14% вправо и 84% вниз будет установлена на точке элемента 14% вправо и 84% вниз.

При парном значении '2cm 2cm', верхний левый угол изображения будет установлен на расстоянии 2 см правее и 2 см ниже верхнего левого угла элемента.

Если задано только одно процентное выражение или абсолютные единицы, они устанавливают только горизонтальное положение, а вертикальное по умолчанию будет 50%. Если заданы два значения, то первое из них - горизонтальное положение. Допускаются комбинации абсолютных единиц и процентных выражений, например, '50% 2cm'. Допускаются отрицательные значения.

Для обозначения положения изображения фона могут быть также использованы ключевые слова, которые можно комбинировать с процентным выражением или абсолютными единицами. Ниже перечислены возможные комбинации ключевых слов и их расшифровка:

  • 'top left' или 'left top' равноценны '0% 0%'.
  • 'top', 'top center' или 'center top' равноценны '50% 0%'.
  • 'right top' или 'top right' равноценны '100% 0%'.
  • 'left', 'left center' или 'center left' равноценны '0% 50%'.
  • 'center' или 'center center' равноценны '50% 50%'.
  • 'right', 'right center' или 'center right' равноценны '100% 50%'.
  • 'bottom left' или 'left bottom' равноценны '0% 100%'.
  • 'bottom', 'bottom center' или 'center bottom' равноценны '50% 100%'.
  • 'bottom right' или 'right bottom' равноценны '100% 100%'.

примеры:

  BODY { background: url(banner.jpeg) right top }
                              /* 100%   0% */
  BODY { background: url(banner.jpeg) top center }
                              /*  50%   0% */
  BODY { background: url(banner.jpeg) center }
                               /*  50%  50% */
  BODY { background: url(banner.jpeg) bottom }
                               /*  50% 100% */

Если рисунок фона привязан относительно холста (см. раздел "Определение привязки фона"), изображение устанавливается относительно холста, но не элемента. К примеру:

  BODY {
    background-image: url(logo.png);
    background-attachment: fixed;
    background-position: 100% 100%;
  }

В вышеприведенном примере изображение устанавливается в нижний правый угол холста.

5.3.7 Определение свойств фона в целом ('background')
Значение: <background-color> || <background-image> |
| <background-repeat> ||<background-attachment> ||
 <background-position>
По умолчанию: для обобщающего свойства не определено
Область приложения: все элементы
Наследование: нет
Процентное выражение: применительно к
<background-position>

Свойство "фон" ('background') - это обобщающее свойство для установки отдельных свойств фона (т.е. цвета, рисунка, повтора, привязки и положения) в одном правиле таблицы стилей.

Возможные значения обобщающего свойства "фон" включают совокупность возможных значений каждого из составляющих свойств.

BODY { background: red }
P { background: url(chess.png) gray 50% repeat fixed }

Свойство "фон" всегда устанавливает все совокупные свойства фона сразу. В первой строке вышеуказанного примера явно задан только цвет фона, а значения остальных свойств принимаются по умолчанию. Во второй строке все совокупные свойства заданы явным образом.

5.4 Свойства текста

5.4.1 Установка интервалов ('word-spacing')
Значение: normal | <length>
По умолчанию: normal
Область применения: все элементы
Наследование: есть
Процентное выражение: неопр.

Численное выражение длины указывает добавление к установленной по умолчанию величине интервалов между словами. Это значение может быть отрицательным, но в этом случае могут иметь место различные ограничения, накладываемые методами отработки. UA вправе самостоятельно выбирать алгоритм вычисления интервалов. Интервалы между словами могут также зависеть от способа выравнивания абзаца (последний определяется значением свойства "выравнивание" ('align')).

  H1 { word-spacing: 0.4em }

Здесь интервал между словами в элементе H1 увеличен на единицу "м" ('1em')(0,4em?), (м соответствует круглой - прим. перев.).

Ядро CSS1: UA вправе квалифицировать любое значение "интервала" как 'normal' (См. раздел 7.)

5.4.2 Установка трекинга ('letter-spacing')
Значение: normal | <length>
По умолчанию: normal
Область применения: все элементы
Наследование: есть
Процентное выражение: неопр.

Численное выражение длины указывает на добавление к установленной по умолчанию величине межбуквенных пробелов. Это значение может быть отрицательным, но в этом случае могут иметь место различные ограничения, накладываемые методами отработки. UA вправе самостоятельно выбирать алгоритм вычисления интервалов. Межбуквенные интервалы могут также зависеть от способа выравнивания абзаца (последний определяется значением свойства "выравнивание" ('align')).

  BLOCKQUOTE { letter-spacing: 0.1em }

Здесь межбуквенный интервал для элемента BLOCKQUOTE' увеличен на '0.1em'.

В случае значения 'normal', UA может менять межбуквенное расстояние для выравнивания абзаца. Этого не произойдет, если для "трекинга" будет указано значение длины в численном выражении:

  BLOCKQUOTE { letter-spacing: 0 }
  BLOCKQUOTE { letter-spacing: 0cm }

Если образующийся межбуквенный интервал не идентичен интервалу по умолчанию, UA не будет использовать лигатуры.

Ядро CSS1: UA может интерпретировать любое значение трекинга как 'normal'. (См. секцию 7.)

5.4.3 Установка оформления текста ('text-decoration')
Значение: none | [ underline || overline |
| line-through || blink ]
По умолчанию: none
Область применения: все элементы
Наследование: нет с условием см ниже
Процентное выражение: неопр.

Это свойство описывает способ оформления текста в элементе. Если в элементе нет текста (напр., элемент 'IMG' в HTML), либо в случае пустого элемента (напр. '<EM></EM>'), это свойство не имеет силы. Значение 'blink' делает текст мерцающим.

Цвет(а), требуемые для оформления текста, должны быть производными от значения свойства "цвет".

Это свойство не наследуется, но элементы должны соответствовать родительским. Например, если в элементе подчеркивание, черта должна передаваться дочерним элементам. Цвет подчеркивания остается даже в случаях, когда дочерние элементы имеют другие значения цвета.

  A:link, A:visited, A:active { text-decoration:
                      underline }

В вышеуказанном примере будет подчеркиваться текст для всех видов ссылок (т.е. все элементы 'A' с атрибутом 'HREF').

UA должны распознавать зарезервированное слово 'blink', хотя не обязательно должны поддерживать мерцание текста.

5.4.4 Установка вертикального выравнивания текста ('vertical-align').
Значение: baseline | sub | super | top | text-top
| middle | bottom | text-bottom | <percentage>
По умолчанию: baseline
Область применения: строчные элементы
Наследуемость: нет
Процентное выражение: по отношению к свойству
 "высота линии" ('line-height') самого элемента

Свойство влияет на вертикальное положение элемента. Используется набор следующих зарезервированных слов, указывающих положение относительно родительского элемента:

'baseline'
совмещает базу элемента (или его низ, если элемент не имеет базы), с базой родительского элемента
'middle'
выравнивает среднюю линию элемента (обычно изображения) по уровню "база плюс половина высоты ('x-height') родительского элемента"
'sub'
делает элемент подстрочным
'super'
делает элемент надстрочным
'text-top'
выравнивает верх элемента по верху шрифта родительского элемента
'text-bottom'
выравнивает низ элемента по низу шрифта родительского элемента

Другой набор значений свойства определяет выравнивание относительно форматируемой линии, частью которой является указанный элемент:

'top'
выравнивает вершину элемента по самому высокому элементу линии
'bottom'
выравнивает низ элемента по самому низкому элементу линии.

При использовании значений 'top' и 'bottom' могут возникать неразрешимые ситуации, когда зависимости одних элементов от других образуют замкнутый цикл.

Процентное выражение соотносится со значением свойства "высота линии" ('line-height') самого элемента. Таким способом база элемента (или низ, если таковая отсутствует) повышается на определенный уровень относительно базы родительского элемента. Допускаются отрицательные значения. Например, при значении '-100%' элемент будет опущен так, что его база достигнет уровня, где должна лежать база следующей линии. Это позволяет позиционировать по вертикали элементы, не имеющие базы - скажем, изображения, используемые для замещения букв.

Предполагается, что в будущей версии CSS в качестве значения этого свойства будет допускаться <length>.

5.4.5 Установка преобразования текста ('text-transform')
Значение: capitalize | uppercase | lowercase | none
По умолчанию: none
Область применения: все элементы
Наследование: есть
Процентное выражение: неопр.

'capitalize'
устанавливает первую букву каждого слова прописной
'uppercase'
устанавливает все буквы элемента прописными
'lowercase'
устанавливает все буквы элемента строчными
'none'
аннулирует наследованное значение.

Фактически преобразование в каждом случае будет зависеть от используемого языка . См. способы определения языка элемента в [4] .

  H1 { text-transform: uppercase }

В вышеприведенном примере для элемента 'H1' все буквы устанавливаются прописными.

Ядро CSS1: UA может игнорировать преобразование текста (т.е. отрабатывать как по значению 'none'), для символов, не относящихся к набору для кодировки Latin-1 и для элементов на языках, для которых преобразование отличается от принятого для таблиц преобразования Unicode [8].

5.4.6 Установка выравнивания текста ('text-align')
Значение: left | right | center | justify
По умолчанию: определяется UA
Область применения: блочные элементы
Наследование: есть
Процентное выражение: неопр.

Это свойство описывает способ выравнивания текста внутри элемента. Фактически используемый UA алгоритм будет зависеть от языка документа.

Пример:

  DIV.center { text-align: center }

Поскольку выравнивание текста наследуется, все блочные элементы внутри элемента 'DIV' с 'CLASS=center' будут выровнены по середине. Обратите внимание, что выравнивание производится относительно ширины элемента, а не холста. Если UA не поддерживает выравнивание по ширине ('justify'), он должен поддерживать замену - для западных языков это будет, как правило, выравнивание по левому краю.

Ядро CSS: UA вправе интерпретировать выравнивание по середине как выравнивание по левому или правому краю, в зависимости от установленного по умолчанию для элемента направления письма - соответственно слева направо или справа налево.

5.4.7 Установка абзацного отступа ('text-indent')
Значение: <length> | <percentage>
По умолчанию: 0
Область применения: блочный элементы
Наследование: есть
Процентное выражение: относительно ширины
родительского элемента.

Это свойство определяет величину отступа перед первой форматируемой линией абзаца. Значение для абзацного отступа может быть отрицательным, но в пределах, накладываемых особенностями обработки. Отступ не используется в середине элемента, если тот был разбит другим (напр. 'BR' для HTML).

Пример:

  P { text-indent: 3em }
5.4.8 Установка высоты строки ('line-height').
Значение: normal | <number> | <length> | <percentage>
По умолчанию: normal
Область применения: все элементы
Наследование: есть
Процентное выражение: относительно размера шрифта
 самого элемента.

Данное свойство определяет расстояние между базами двух смежных строк.

Если численное значение явно определено, высота строки определяется как высота шрифта, умноженная на численное значение. Отличие от процентного выражения - в способе наследования: если определено численное значение, то наследуется сам коэффициент, а не результирующее значение, как в случае в процентным или иным способом выражения.

Отрицательные значения не допускаются.

В трех строках следующего примера получаются одинаковые результаты высоты строки:

  DIV { line-height: 1.2; font-size: 10pt }
                         /* number */
  DIV { line-height: 1.2em; font-size: 10pt }
                         /* length */
  DIV { line-height: 120%; font-size: 10pt }
                         /* percentage */

Значение 'normal' устанавливает "рациональное" соотношение высоты строки и высоты шрифта элемента. Предполагается, что UA в случае значения 'normal' устанавливает это соотношение в пределах 1,0....1,2.

См. раздел 4.7. , описание того, как свойство "высота строки" влияет на форматирование блочного элемента.

5.5 Свойства полей.

Свойство "поле" устанавливают размер, периферию и расположение полей, представляющих элементы. См. модель форматирования (разд.4), в котором приведен пример использования свойства "поле".

Свойства границы устанавливают границу элемента. Обобщающее свойство 'margin' устанавливает границу для всех четырех сторон , в то время как остальные устанавливают только соответствующую сторону.

Свойства отступа определяют, сколько места оставляется между рамкой и содержанием (напр. текст или изображение). Обобщающее свойство 'padding' устанавливает отступ для всех четырех сторон, в то время как остальные устанавливают лишь соответствующую сторону.

Свойства рамки устанавливают рамки элемента. Каждый элемент имеет четыре стороны рамки, для каждой из которых устанавливаются ширина, цвет и стиль.

Свойства "ширина" ('width') и "высота" ('height') устанавливают размеры поля , а "плавающий" ('float') и "очистка" ('clear') позволяют изменять позицию элементов.

5.5.1 Установка верхней границы ('margin-top').
Значение: <length> | <percentage> | auto
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
 родительских элементов.

Это свойство определяет верхнюю границу элемента:

  H1 { margin-top: 2em }

Допускаются отрицательные значения, но в пределах ограничений, накладываемых способом обработки.

5.5.2 Установка правой границы ('margin-right').
Значение: <length> | <percentage> | auto
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
родительских элементов

Это свойство определяет правую границу элемента:

  H1 { margin-right: 12.3% }

Допускаются отрицательные значения, но в пределах ограничений, накладываемых способом обработки.

5.5.3 Установка нижней границы ('margin-bottom').
Значение: <length> | <percentage> | auto
По умолчания: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
 родительских элементов

Это свойство определяет нижнюю границу элемента:

  H1 { margin-bottom: 3px }

Допускаются отрицательные значения, но в пределах ограничений, накладываемых способом обработки.

5.5.4 Установка левой границы ('margin-left')
Значение: <length> | <percentage> | auto
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентные значения: refer to
 родительский элемент's width

Это свойство устанавливает левую границу элемента:

  H1 { margin-left: 2em }

Допускаются отрицательные значения, но в пределах ограничений, накладываемых способом обработки.

5.5.5 Установка обобщающего свойства "граница" ('margin').
Значение: [ <length> | <percentage> | auto ]{1,4}
По умолчанию: не определено для обобщающих свойств
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
родительских элементов

Обобщающее свойство "граница" используется для одновременной установки свойств верхней, правой, нижней и левой границы в одном месте таблицы стилей.

При явном указании четырех значений длины они соответственно применяются к верхней, правой, нижней и левой стороне. Если явно указано одно значение, оно применяется ко всем сторонам, если два или три значения, то отсутствующие значения берутся по значению противоположной стороны.

  BODY { margin: 2em } /* all margins set to 2em */
  BODY { margin: 1em 2em } /* top & bottom = 1em,
                right & left = 2em */
  BODY { margin: 1em 2em 3em } /* top=1em,
            right=2em, bottom=3em, left=2em */

Последняя строка вышеприведенного примера равноценна нижеследующему примеру:

  BODY {
    margin-top: 1em;
    margin-right: 2em;
    margin-bottom: 3em;
    margin-left: 2em;
      /* copied from opposite side (right) */
  }

Допускаются отрицательные значения, но в пределах ограничений, накладываемых способом обработки.

5.5.6 Установка верхнего отступа ('padding-top').
Значение: <length> | <percentage>
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
 родительских элементов

Это свойство определяет величину верхнего отступа элемента.

  BLOCKQUOTE { padding-top: 0.3em }

Отрицательные значения для отступа не допускаются.

5.5.7 Установка правого отступа ('padding-right').
Значение: <length> | <percentage>
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
родительских элементов

Это свойство определяет величину правого отступа элемента.

  BLOCKQUOTE { padding-right: 10px }

Отрицательные значения для отступа не допускаются.

5.5.8 Установка нижнего отступа ('padding-bottom').
Значение: <length> | <percentage>
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
 родительских элементов

Это свойство определяет величину нижнего отступа элемента.

  BLOCKQUOTE { padding-bottom: 2em }

Отрицательные значения для отступа не допускаются.

5.5.9 Установка левого отступа ('padding-left').
Значение: <length> | <percentage>
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
родительских элементов

Это свойство устанавливает величину левого отступа элемента..

  BLOCKQUOTE { padding-left: 20% }

Отрицательные значения для отступа не допускаются.

5.5.10 Установка обобщающего свойства " отступ" ('padding').
Значение: [ <length> | <percentage> ]{1,4}
По умолчанию: 0
Область применения: все элементы
Наследование: нет
Процентное выражение: по отношению к ширине
 родительских элементов

Свойство "отступ" является обобщающим для установки свойств верхнего, правого, нижнего и левого отступа в одном правиле таблицы стилей.

Если явно указаны четыре значения, они применяются соответственно к верхнему, правому, нижнему и левому отступу. Если явно указано только одно значение, оно применяется ко всем сторонам, если два или три, то отсутствующие значения принимаются по значениям противоположной стороны.

Поверхность отступа определяется с помощью свойства "фон" ('background'):

  H1 {
    background: white;
    padding: 1em 2em;
  }

В вышеприведенном примере устанавливается горизонтальный отступ величиной '1em' (правый и левый) и вертикальный отступ величиной '2em' (верхний и нижний). Величина 'em' определяется относительно размера шрифта элемента: 1em соответствует размеру применяемого шрифта.

Отрицательные значения для отступа не допускаются.

5.5.11 Установка ширины верхней рамки ('border-top-width').
Значение: thin | medium | thick | <length>
По умолчанию: 'medium'
Область применения: all элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство определяет ширину верхней рамки элемента. Ширина, определяемая зарезервированным словом, зависит от UA, но при этом выдерживается зависимость: 'thin' меньше, чем 'medium', а последнее меньше, чем 'thick'.

Ширина рамки, определенная с помощью зарезервированного слова, является для документа величиной постоянной:

  H1 { border: solid thick red }
  P  { border: solid thick blue }

В вышеуказанном примере у элементов 'H1' и 'P' будет та же самая рамка независимо от размера шрифта. Чтобы установить относительную ширину, можно использовать единицы 'em':

  H1 { border: solid 0.5em }

Величина рамки не может быть отрицательной.

5.5.12 Установка ширины правой рамки('border-right-width').
Значение: thin | medium | thick | <length>
По умолчанию: 'medium'
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство определяет ширину правой рамки элемента. Остальное эквивалентно установке ширины верхней рамки('border-top-width').

5.5.13 Установка ширины нижней рамки('border-bottom-width')
Значение: thin | medium | thick | <length>
По умолчанию: 'medium'
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство определяет ширину нижней рамки элемента. Остальное эквивалентно установке ширины верхней рамки('border-top-width').

5.5.14 Установка ширины левой рамки 'border-left-width'
Значение: thin | medium | thick | <length>
По умолчанию: 'medium'
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство определяет ширину левой рамки элемента. Остальное эквивалентно установке ширины верхней рамки ('border-top-width').

5.5.15 Установка обобщающего свойства "ширина рамки" ('border-width')
Значение: thin | medium | thick | <length>
По умолчанию: 'medium'
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство является обобщающим для установки верхней, правой, нижней и левой ширины рамки в одном правиле таблицы стилей.

Может быть указано от одного до четырех значений, обрабатываемых следующим образом:

  • одно значение: используется для установки ширины всех четырех рамок
  • два значения: ширина верхней и нижней рамок устанавливаются по первому значению, правое и левое - по второму
  • три значения: ширина верхней рамки устанавливается по первому значению, правого и левого по второму, а нижнего по третьему
  • четыре значения: соответственно ширина верхней, правой, нижней и левой рамок.

В нижеследующем примере комментарии указывают на получившуюся величину ширины верхней, правой и левой рамок:

  H1 { border-width: thin }
                 /* thin thin thin thin */
  H1 { border-width: thin thick }
                 /* thin thick thin thick */
  H1 { border-width: thin thick medium }
                 /* thin thick medium thin */
  H1 { border-width: thin thick medium none }
                 /* thin thick medium none */

Величина рамки не может быть отрицательной.

5.5.16 Установка цвета рамки ('border-color').
Значение: <color>{1,4}
По умолчанию: значение свойства 'color'
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Свойство "цвет рамки" ('border-color') определяет цвет четырех сторон рамки. "Цвет рамки" может иметь от одного до четырех значений, которые устанавливают цвета сторон рамки аналогично вышеописанному для толщины рамки.

Если не задано ни одного значения цвета, по умолчанию присваивается значение свойства "цвет" самого элемента:

  P {
    color: black;
    background: white;
    border: solid;
  }

В вышеуказанном примере рамка представляет собой сплошную черную линию.

5.5.17 Установка стиля рамки ('border-style')
Значение: none | dotted | dashed | solid | double
 | groove | ridge | inset |
outset
По умолчанию: none
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Свойство "стиль рамки" устанавливает стиль четырех сторон рамки. Оно может иметь от одного до четырех значений для разных сторон, устанавливаемых аналогично вышеописанному для ширины рамки.

  #xy34 { border-style: solid dotted }

В вышеприведенном примере горизонтальные стороны рамки будут сплошными, а вертикальные - пунктирными.

Так как значение стиля рамки по умолчанию - 'none', то в случае, если стиль рамки явно не задан, видимой рамки не будет.

Возможны следующие стили рамки:

none
рамка не изображается (независимо от значения ширины рамки)
dotted
рамка - пунктирная линия поверх обоев элемента
dashed
рамка - штриховая линия поверх обоев элемента
solid
рамка - сплошная линия
double
рамка - двойная линия поверх обоев элемента. Сумма толщин двух линий и промежутка между ними соответствует толщине рамки.
groove
объемная канавка, цвета на основе значения свойства "цвет".
ridge
объемный гребень, цвета на основе значения свойства "цвет".
inset
объемная складка внутрь, цвета на основе значения свойства "цвет".
outset
объемная складка наружу, цвета на основе значения свойства "цвет".
Ядро CSS: UA может обрабатывать 'dotted', 'dashed', 'double', 'groove', 'ridge', 'inset' и 'outset' как сплошную линию ('solid').
5.5.18 Установка обобщающего свойства "верхняя рамка" ('border-top')
Значение: <border-top-width> || <border-style> |
| <color>
По умолчанию: для обобщающего свойства не определено
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Данное свойство является обобщающим для установки ширины, стиля и цвета для верхней рамка элемента.

  H1 { border-bottom: thick solid red }

В вышеуказанном примере установлены ширина, стиль и цвет для рамки элемента H1. Отсутствующим значениям присваиваются значения по умолчанию:

  H1 { border-bottom: thick solid }

В вышеуказанном примере значение цвета опущено и цвет рамки будет установлен по значению свойства "цвет" самого элемента.

Обратите внимание, что в отличие от свойства "стиль рамки ", для обобщающего свойства "верхняя рамка" задается только одно значение стиля.

5.5.19 Установка обобщающего свойства "правая рамка" ('border-right')
Значение: <border-right-width> || <border-style> |
| <color>
По умолчанию: для обобщающего свойства не определено
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Данное свойство является обобщающим для установки ширины, стиля и цвета для правой рамки элемента. Остальное аналогично обобщающему свойству "верхняя рамка".

5.5.20 Установка обобщающего свойства "нижняя рамка" ('border-bottom')
Значение: <border-bottom-width> || <border-style> |
| <color>
По умолчанию: для обобщающего свойства не определено
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Данное свойство является обобщающим для установки ширины, стиля и цвета для нижней рамки элемента. Остальное аналогично обобщающему свойству "верхняя рамка".

5.5.21 Установка обобщающего свойства "левая рамка" 'border-left'
Значение: <border-left-width> || <border-style> |
| <color>
По умолчанию: для обобщающего свойства не определено
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Данное свойство является обобщающим для установки ширины, стиля и цвета для левой рамки элемента. Остальное аналогично обобщающему свойству "верхняя рамка".

5.5.22 Установка обобщающего свойства " рамка" 'border'
Значение: <border-width> || <border-style> |
| <color>
По умолчанию: для обобщающего свойства не определено
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Данное свойство является обобщающим для установки ширины, стиля и цвета для всех четырех сторон рамки элемента. В нижеследующем примере первая строка равнозначна четырем последующим:

  P { border: solid red }
  P {
    border-top: solid red;
    border-right: solid red;
    border-bottom: solid red;
    border-left: solid red
  }

В отличие от обобщающих свойств "граница" и "заполнение", для обобщающего свойства "рамка" нельзя задать разные значения четырех сторон рамки. Чтобы так сделать, необходимо использовать одно или более из других вышеприведенных свойств рамки.

Поскольку свойства в известной мере функционально перекрываются, при их обработке важен также порядок, в соответствии с которым они были назначены. Рассмотрим следующий Пример:

  BLOCKQUOTE {
    border-color: red;
    border-left: double
    color: black;
  }

В вышеприведенном примере цвет левого края рамки будет черным, в то время как для остальных - красным. Это является результатом установки ширины, стиля и цвета для свойства "левая рамка". Поскольку значение цвета не было явно задано для свойства "левая рамка", будет использовано значение, заданное для свойства "цвет". То, что свойство "цвет" определено позже свойства "левая рамка", не играет роли.

Обратите внимание, что если для свойства "ширина рамки" устанавливается до четырех значений, то для рассматриваемого - только одно.

5.5.23 Установка свойства "ширина" ('width')
Значение: <length> | <percentage> | auto
По умолчанию: auto
Область применения: блочные и замещаемые элементы
Наследование: нет
Процентное выражение: по отношению к ширине
родительского элемента

Это свойство может быть использовано для текстовых элементов, но чаще - для замещаемых, таких как изображения. Если необходимо, ширина изображения может быть изменена масштабированием. При этом отношение ширины изображения к высоте будет сохранена, если для свойства "высота" указано значение 'auto'.

Пример:

  IMG.icon { width: 100px }

Если для свойств "ширина" и "высота" замещаемого элемента использовано значение 'auto', то обрабатываются значения, соответствующие собственным размерам элемента.

Отрицательные значения для свойства "ширина" не допускаются.

См. также модель форматирования глава 4, описание зависимости между настоящим свойством, границей и заполнением.

5.5.24 Установка свойства "высота" 'height'
Значение: <length> | auto
По умолчанию: auto
Область применения: блочные и замещаемые элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство может быть использовано для текстовых элементов, но чаще - для замещаемых, таких как изображения. Если необходимо, высота изображения может быть изменена масштабированием. При этом отношение ширины изображения к высоте будет сохранена, если для свойства "ширина" указано значение 'auto'.

Пример:

  IMG.icon { height: 100px }

Если для свойств "ширина" и "высота" замещаемого элемента использовано значение 'auto', то обрабатываются значения, соответствующие собственным размерам элемента.

При использовании применительно к текстовым элементам, реализация заданной высоты может быть осуществлена, например, с помощью скроллинга.

Отрицательные значения свойства "высота" не допускаются.

Ядро CSS1: UA могут игнорировать свойство "высота" (т.е. присваивать ему значение 'auto'), если элемент не является замещаемым.

5.5.25 Установка свойства "плавающий" ('float')
Значение: left | right | none
По умолчанию: none
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

При установке значения 'none', элемент отображается там, где он присутствует в тексте. При значении 'left' ('right'), элемент смещается влево (вправо), а перенос текста производится по правой (левой) стороне элемента. При значении 'left' ('right'), элемент будет отображаться как блочный (в том смысле, что свойство "выделение" - 'display' - игнорируется). См. полную спецификацию раздел 4.1.4.

  IMG.icon {
    float: left;
    margin-left: 0;
  }

В вышеприведенном примере все элементы IMG, имеющие класс icon, будут установлены вдоль левой стороны родительского элемента.

Это свойство чаще всего используется для позиционирования обтекаемых текстом изображений, но также может применяться для текстовых элементов.

5.5.26 Установка свойства "чисто" ('clear')
Значение: none | left | right | both
По умолчанию: none
Область применения: все элементы
Наследование: нет
Процентное выражение: нет

Это свойство устанавливается, если элемент допускает размещение на своих сторонах плавающих элементов. Если точнее, то значение данного свойства указывает стороны, где плавающие элементы не ставятся. Если для свойства элемента "чисто" указать значение 'left', то для такого элемента все плавающие элементы на левой стороне будут опущены вниз. Если указано значение 'none', то плавающие элементы устанавливаются на всех сторонах. Пример:

  H1 { clear: left }

5.6 Свойства - классификации

Эти свойства классифицируют элементы по категориям, большим, чем при установке специфических визуальных параметров.

Перечень стилей свойств описывает, каким образом сформатированы элементы перечня (т.е. элементы, у которых свойство "показ" ('display') имеет значение 'list-item'). Свойства перечня стилей могут быть установлены для любого элемента, и будут наследоваться в порядке, определенном древом этого перечня. Однако этот эффект будет распространяться только на элементы, у которых свойство "показ" ('display') имеет значение 'list-item'. Для языка

5.6.1 Установка свойства "показ" ('display')
Значение: block | inline | list-item | none
По умолчанию: block
Область применения: все элементы
Наследование: нет
Процентное выражение: неопр.

Это свойство описывает, показывается ли элемент на холсте (печатная страница, экран и т.п.) и каким образом.

Элемент со значением 'block' свойства "показ" открывает новое поле. Поле устанавливается относительно смежных полей в соответствии с принципами форматирования CSS. Обычно такие элементы как 'H1' и 'P' относятся к типу "блочные". Значение 'list-item' будет здесь аналогично значению 'block', с той лишь разницей, что добавляется маркер элемента списка. Для HTML это значение обычно имеет элемент 'LI'.

Элемент со значением 'inline' свойства "показ" открывает новое строчное поле в той же самой строке, что и предыдущее содержание. Размеры поля согласуются с размером отформатированного содержания. Если содержание - текст, он может быть разделен на несколько строк, на каждой из которых будет поле. Свойства границы, бордюра и заполнения применяются к строчным ('inline') элементам, но их эффект не будет проявляться в местах переноса строк.

Значение 'none' отменяет показ элемента, включая дочерние элементы и окружающее поле.

  P { display: block }
  EM { display: inline }
  LI { display: list-item }
  IMG { display: none }

Последняя строка отключает показ картинок.

По умолчанию свойству "показ" ('display') присваивается значение 'block', но UA обычно присваивает всем элементам HTML значения по умолчанию в соответствии с предлагаемыми спецификацией HTML для соответствующих элементов [2].

Ядро CSS1: UA может игнорировать свойство "показ" и использовать собственные установки по умолчанию. (См. раздел 7).

5.6.2 Установка пробелов ('white-space')
Значение: normal | pre | nowrap
По умолчанию: normal
Область: блочный элементы
Наследование: есть
Процентное выражение: неопр.

Это свойство описывает, каким образом внутри элемента поддерживаются пробелы: при значении 'normal' пробелы сжимаются, значение 'pre' действует подобно элементу PRE для HTML, а при значении 'nowrap' перенос производится только с помощью элементов BR:

  PRE { white-space: pre }
  P   { white-space: normal }

По умолчанию свойству "пробелы" присваивается значение 'normal', однако UA обычно ставит значения по умолчанию для всех элементов HTML в соответствии с указанным для этих элементов в спецификации HTML [2].

Ядро CSS1: UA может игнорировать свойство "пробелы" в таблице стилей автора или читателя, и использовать взамен собственные значения по умолчанию. (См. раздел 7).

5.6.3 Установка типа маркера перечня ('list-style-type')
Значение: disc | circle | square | decimal |
 lower-roman | upper-roman |
lower-alpha | upper-alpha | none
По умолчанию: disc
Область применения: элементы со значением '
list-item' для свойства 'display'
Наследование: есть
Процентное выражение: неопр.

Это свойство служит для установки появления маркера перечня, если свойству "рисунок маркера перечня" ('list-style-image') присвоено значение 'none', либо, если изображение, указанное в URL, не может быть отображено.

  OL { list-style-type: decimal }
                /* 1 2 3 4 5 etc. */
  OL { list-style-type: lower-alpha }
                /* a b c d e etc. */
  OL { list-style-type: lower-roman }
                /* i ii iii iv v etc. */
5.6.4 Установка рисунка маркера перечня ('list-style-image').
Значение: <url> | none
По умолчанию: none
Область применения: элементы со значением
'list-item' для свойства 'display'
Наследование: есть
Процентное выражение: неопр.

Это свойство определяет изображение, которое должно быть использовано в качестве маркера перечня. В случае недоступности изображения последнее будет заменено маркером, определенным по свойству "тип маркера перечня".

  UL { list-style-image:
   url(http://png.com/ellipse.png) }
5.6.5 Установка отступа маркера перечня 'list-style-position'
Значение: inside | outside
По умолчанию: outside
Область применения: элементы со значением
'list-item' для свойства 'display'
Наследование: есть
Процентное выражение: неопр.

Значение свойства "отступ маркера перечня" определяет, каким образом маркер перечня будет расположен относительно содержания. Примеры форматирования см. в разделе 4.1.3.

5.6.6 Установка обобщающего свойства "перечень" ('list-style')
Значение: <keyword> || <position> || <url>
По умолчанию: не определено для обобщающих свойств
Область применения: элементы со значением
'list-item' для свойства 'display'
Наследование: есть
Процентное выражение: неопр.

Это свойство является обобщающим для установки трех свойств: "тип маркера перечня", "рисунок маркера перечня" и "отступ маркера перечня" в одном месте таблицы стилей.

  UL { list-style: upper-roman inside }
  UL UL { list-style: circle outside }
  LI.square { list-style: square }

Прямое указание значений свойств "перечень" для элементов 'LI' может иметь непредсказуемые результаты. НаПример:

  <STYLE TYPE="text/css">
    OL.alpha LI  { list-style: lower-alpha }
    UL LI        { list-style: disc }
  </STYLE>
  <BODY>
    <OL CLASS=alpha>
      <LI>level 1
      <UL>
         <LI>level 2
      </UL>
    </OL>
  </BODY>

Поскольку приоритет установки (как определено в каскаде установок) выше для первой строки таблицы стилей вышеуказанного примера, он отменяет действие второй строки для всех элементов 'LI' и позиции будут маркироваться только строчными буквами в алфавитном порядке. Таким образом, рекомендуется устанавливать значения для обобщающего свойства "перечень" только для элементов перечня:

  OL.alpha  { list-style: lower-alpha }
  UL        { list-style: disc }

В вышеуказанных примерах свойства "перечень" будут унаследованы элементами 'LI' от элементов 'OL' и 'UL'.

Значение URL можно сочетать с любыми другими значениями:

  UL { list-style: url(http://png.com/ellipse.png) disc }

В вышеуказанном примере значение 'disc' будет использоваться в случае недоступности изображения.


6. Единицы измерения значений. Спецификация CSS1 | WebClub

6. Единицы измерения значений. Спецификация CSS1

6.1 Установка единиц длины

Формат значения длины состоит из необязательного знака ("+" или "-", по умолчанию "+"), обязательного числа (с десятичной частью, отделяемой десятичной точкой, или без), и обязательного идентификатора размерности в виде двухбуквенной аббревиатуры, указываемых в последовательности перечисления в настоящем абзаце. После числа "0" идентификатор размерности не обязателен.

Некоторые свойства используют отрицательные единицы длины, однако это может усложнить способ форматирования и могут возникнуть ограничения, диктуемые особенностями отработки. Если отрицательная длина не поддерживается, то она должна быть замещена ближайшим поддерживаемым значением.

Существуют два типа единиц длины: относительные и абсолютные. Относительные единицы определяют длину относительно другой длины свойства. Если таблица стилей использует относительные единицы, то это более удобно для масштабирования страницы на разных периферийных устройствах (напр. при выводе с экрана на печать). Такого эффекта позволяют достичь и процентные выражения (как показано ниже), а также зарезервированные слова (напр. 'x-large').

Поддерживаются следующие относительные единицы:

  H1 { margin: 0.5em }
        /* ems, высота шрифта элемента */
  H1 { margin: 1ex }
        /* x-height, ~ по высоте буквы 'x' */
  P  { font-size: 12px }
       /* pixels, в пикселах относительно холста */

Относительные единицы 'em' и 'ex' определяются относительно размера шрифта самого элемента. Единственное исключение из этого правила делается для свойства "размер шрифта" ('font-size'), для которого 'em' и 'ex' определяются по размеру шрифта родительского элемента.

Пиксели, указанные в последнем примере, определяются относительно разрешения холста, чаще всего - экранного разрешения. Если разрешающая способность устройства вывода отличается от характерной для экрана, UA должен масштабировать значение в пикселях. Замещающий пиксель соответствует углу зрения, под которым виден один пиксель на устройстве с разрешающей способностью 90 точек на дюйм на расстоянии, оптимальном для зрения. При величине последнего 28 дюймов угол зрения приблизительно равен 0,0227 градуса.

Дочерние элементы наследуют вычисленное значение, а не относительное:

  BODY {
    font-size: 12pt;
    text-indent: 3em;  /* i.e. 36pt */
  }
  H1 { font-size: 15pt }

В приведенном примере значение абзацного отступа ('text-indent') элемента H1 будет равно 36 пунктов, а не 45.

Абсолютные единицы длины используются лишь в тех случаях, когда заведомо известны физические характеристики устройства вывода. Поддерживаются следующие абсолютные единицы:

 H1 { margin: 0.5in }      /* inches, 1in = 2.54cm */
 H2 { line-height: 3cm }   /* centimeters */
 H3 { word-spacing: 4mm }  /* millimeters */
 H4 { font-size: 12pt }    /* points, 1pt = 1/72 in */
 H5 { font-size: 1pc }     /* picas, 1pc = 12pt */

В случаях, когда заданная длина не может быть отработана, UA должны аппроксимировать ее. Дальнейший пересчет и наследование для всех свойств CSS1 должен уже основываться на результатах аппроксимации.

6.2 Процентное выражение

Формат процентного выражения состоит из необязательного знака ("+" или "-", по умолчанию "+"), обязательного числа (с десятичной частью, отделяемой десятичной точкой, или без), и обязательного знака "%", указываемых в последовательности перечисления в настоящем абзаце.

Процентное значение всегда является относительным к другому, например, к значению длины. При назначении процентного выражения для каждого случая предполагается наличие базы отсчета - чаще всего это размер шрифта данного элемента:

  P { line-height: 120% }
     /* 120% размера шрифта элемента */

Для всех наследованных свойств CSS1 считается, что если значение было указано в виде процентного выражение, то дочерние элементы наследуют результирующее значение, а не величину процентовки.

6.3 Обозначение цвета

Цвет обозначатся либо зарезервированным словом, либо численно по спецификации RGB.

Допустимы следующие зарезервированные слова для указания цвета: aqua (светло-голубой), black (черный), blue (синий), fuchsia (светло-фиолетовый), gray (серый), green (зеленый), lime (светло-зеленый), maroon (коричневый), navy (темно-синий), olive (оливковый), purple (фиолетовый), red (красный), silver (светло-серый), teal (сине-зеленый), white (белый), и yellow (желтый). Эти 16 цветов взяты из палитры VGA для Windows, и их значения RGB не определены в данной спецификации.

  BODY {color: black; background: white }
  H1 { color: maroon }
  H2 { color: olive }

Для цветовой модели RGB используются численные выражения. В нижеприведенных примерах определяется один и тот же цвет:

  EM { color: #f00 }
            /* #rgb */
  EM { color: #ff0000 }
            /* #rrggbb */
  EM { color: rgb(255,0,0) }
            /* integer range 0 - 255 */
  EM { color: rgb(100%, 0%, 0%) }
            /* float range 0.0% - 100.0% */

Значения в формате RGB указываются в шестнадцатеричной системе счисления в виде знака #, за которым без пробелов указываются три или шесть шестнадцатеричных символа. Трехсимвольная запись RGB (#rgb) преобразовывается в шестисимвольную путем дублирования цифр, а не добавлением нулей. Например, #fb0 расширяется до #ffbb00. Поэтому понятно, что белый цвет (#ffffff) может быть кратко записан как (#fff), и это устранит любые зависимости от специфики отображения цвета на экране.

Формат значения RGB в функциональной записи следующий: 'rgb(' далее разделенный точками список из трех численных значений (либо три целочисленных значения в интервале 0-255б либо три процентных значения в диапазоне от 0.0% до 100.0%), далее ')'.

Значения за пределами числовых интервалов должны отсекаться. Ниже приводятся три эквивалентных примера:

  EM { color: rgb(255,0,0) }
       /* значения в диапазоне 0 - 255 */
  EM { color: rgb(300,0,0) }
      /* замена на 255 */
  EM { color: rgb(110%, 0%, 0%) }
      /* замена на 100% */

Цвета RGB определяются для цветового поля sRGB [9]. UA может варьировать результаты обработки в пределах точности, с которой они могут отображать цвета, однако применение sRGB обеспечивает однозначное и объективно измеримое определение цвета в соответствии с международными стандартами[10].

UA может накладывать ограничения на указанные условия для отображаемых цветов, производя коррекцию их гаммы. В определенных условиях просмотра sRGB определяет гамму 2.2. UA регулирует заданные CSS цвета таким образом, чтобы применительно к устройству вывода получить "естественную" гамму цветов, гамму 2.2. В приложении D это рассматривается более подробно. Обратите внимание, что речь идет только о цветах, определенных через CSS, а, к примеру, для графических изображений будет использоваться их собственная цветовая информация.

6.4 URL

Универсальный локатор ресурса (URL) определяется в функциональной записи:

BODY { background: url(http://www.bg.com/pinkish.gif) }

Формат значения URL представляет собой 'url(', далее необязательный пробел, далее необязательная одинарная (') или двойная кавычка ("), далее сам URL (как определено в [11]), далее необязательная одинарная (') или двойная кавычка ("), далее необязательный пробел, далее ')'. Знаки кавычек, если они не являются частью URL, должны быть парными.

Круглые скобки, запятые, пробелы, одинарные и двойные кавычки, присутствующие в URL, должны отделяться обратным слешем: '\(', '\)', '\,'.

Неполные URL отрабатываются относительно исходника таблицы стилей, а не относительно самого документа:

  BODY { background: url(yellow) }


7. Совместимость с CSS1. Спецификация CSS1 | WebClub

7. Совместимость с CSS1. Спецификация CSS1

UA, поддерживающий СSS, обрабатывает документы в соответствии со стандартом CSS1 в следующей последовательности:

  • делается попытка выбрать все, что указано в таблице стилей и разобрать в соответствии со спецификацией;
  • рассортировать определения в соответствии с каскадированием установок;
  • реализовать функции CSS1 в пределах ограничений, накладываемых устройствами вывода (см. пояснение ниже). UA, производит вывод таблицы стилей CSS, если она соответствует стандарту CSS;
  • выводится соответствующая CSS таблица стилей.

UA, использующий CSS1 для показа и вывода документов, отрабатывает таблицу стилей CSS1 в соответствии со спецификацией, если в наличии имеются обе установки согласования.

UA не отрабатывает все функции CSS1: он может лишь согласовать результаты отработки с CSS1, реализуя функциональное ядро (сore functionality) последнего. В функциональное ядро входит вся спецификация CSS за исключением частей, изъятых явным образом. В тексте, помеченном как "ядро CSS1" следует разъяснение функций, находящихся за пределами ядра. Установки свойств, исключенные из функционального ядра, называются "расширенные свойства CSS1".

Данный раздел определяет только согласование CSS1. В будущем появятся другие уровни CSS, которые могут потребовать UA для реализации различных установок свойств в порядке согласования.

Примеры ограничений, накладываемых средством вывода, следующие: ограничение ресурсов (шрифты, цвет) и ограничение разрешения (вследствие чего границы могут быть неточными). В этих случаях UA должен аппроксимировать значения таблицы стилей. Кроме того, различные особенности пользовательских интерфейсов могут накладывать свои ограничения: например, браузер VR может масштабировать документ , основываясь на текущем "расстоянии" от пользователя.

UA может предусматривать дополнительные пользовательские установки для отображения информации. Например, UA может обеспечивать дополнительные настройки для пользователя с ухудшенным зрением или давать возможность отключать мерцающий текст.

Обратите внимание, что CSS1 не определяет всех аспектов форматирования. Например, UA вправе произвольно выбирать алгоритм определения межсимвольного расстояния.

Данная спецификация рекомендует (но не предписывает), чтобы UA:

  • допускал определение пользовательской таблицы стилей читателем;
  • допускал возможность включения и выключения индивидуальной таблицы стилей.

Оба правила согласования описывают лишь функциональный, но не пользовательский интерфейс.

7.1 Последовательно-совместимый синтаксический анализ

Данная спецификация определяет CSS первого уровня. В перспективе ожидается появление более высоких уровней CSS с дополнительными свойствами. Чтобы обеспечить совместимость UA, поддерживающих только CSS1, с более поздними версиями языка, в данном разделе описываются процедуры действий UA в случае обнаружения несоответствия требованиям CSS первого уровня.

  • установка неизвестного свойства игнорируется. К примеру, таблицу стилей

  H1 { color: red; rotation: 70deg }

    UA будет обрабатывать, как если бы она имела вид

      H1 { color: red; }
  • недопустимые значения, либо значения с недопустимыми частями, обрабатываются, как если бы определение отсутствовало:

  IMG { float: left }
      /* верно для CSS1 */
  IMG { float: left top }
   /* "top" недопустимое значение для 'float' */
  IMG { background: "red" }
   /* в CSS1 ключевые слова не закавычиваются */
  IMG { border-width: 3 }
   /* должна быть указана размерность числа, */
    /* выражающего значение длины */

    В вышеприведенном примере анализатор CSS1 должен принять к исполнению первую строку и игнорировать последующие, в результате чего таблица стилей примет вид:

  IMG { float: left }
  IMG { }
  IMG { }
  IMG { }

    UA, совместимый с будущей спецификацией CSS, может отработать какую - либо из остальных строк надлежащим образом.

  • неправильная приставка к зарезервированному слову игнорируется вместе со всем последующим, вплоть до первой следующей точки с запятой (;) либо парных фигурных скобок ({}) включительно. Например, пусть мы имеем таблицу стилей:

  @three-dee {
     @background-lighting {
        azimuth: 30deg;
        elevation: 190deg;
     }
     H1 { color: red }
  }
  H1 {color: blue}

    где '@three-dee' - недопустимое выражение для CSS1. Следовательно, вся строка с "собачкой" (вплоть до третьих правых фигурных скобок включительно) игнорируется.

    UA с поддержкой CSS1 пропускает изъятое выражение, фактически сокращая таблицу стилей до:

      H1 {color: blue}

Более подробно:

Таблица стилей CSS, для любой версии CSS, состоит из перечня директив (statements). Есть два вида директив: предустановки и установки. Возле директив допускаются промежутки (пробелы, табуляция, новые строки).

Таблица стилей CSS часто вставляется в документы HTML, и для сокрытия таблицы стилей от более ранних UA, желательно вставлять ее внутри комментариев HTML. Знаки комментариев HTML "" могут быть расположены перед, после и охватывая утверждения. Они могут отделяться интервалами.

Предустановки начинаются с зарезервированных слов с приставкой в виде знака "собачки" '@' (к примеру: @import', '@page') и идентификатора. Идентификатор может состоять из букв, цифр, черточек и спецсимволов (определение ниже).

Предустановка состоит из всего, что находится до следующей точки с запятой (;) включительно. UA для CSS, встречая предустановку, начинающуюся со слова с приставкой, иной чем слово '@import', игнорируют всю предустановку и продолжают отработку после ее окончания. Также игнорируется любая предустановка, начинающаяся с '@import', если она не располагается вверху таблицы стилей, скажем, после какого-либо правила (даже игнорируемого). Рассмотрим пример.

Пусть анализатор CSS1 обрабатывает следующую таблицу стилей:

  @import "subs.css";
  H1 { color: blue }
  @import "list.css";

Вторая предустановка '@import' для CSS недопустима. Анализатор CSS1 пропускает всю предустановку, преобразуя таблицу стилей к следующему виду:

  @import "subs.css";
  H1 {color: blue}

Блок начинается с левых фигурных скобок ({) и кончается правыми (}). Между ними могут быть различные знаки, включая круглые (()), квадратные ([]) и фигурные скобки, которые всегда присутствуют в анализируемых парах и могут быть вложенными. Одинарные кавычки (') и двойные кавычки ("), также присутствующие в анализируемых парах, и знаки между ними воспринимаются как строка (см определение строки в примечании B). Ниже приведен пример блока; обратите внимание, что правая скобка между кавычками не воспринимается как открытие скобки в блоке, и что вторая одинарная кавычка является пропускаемым символом, а не рассматривается как открытие кавычек:

  { causta: "}" + ({7} * '\'') }

Установка состоит из селектора-строки, за которым следует блок - определение. Селектор-строка состоит из всего, что находится вплоть до первой левой фигурной скобки, исключая последнюю. Установка, начинающаяся с некорректной для CSS1, опускается.

К примеру, обработчик CSS1 встречает следующую таблицу стилей:

  H1 { color: blue }
  P[align], UL { color: red; font-size: large }
  P EM { font-weight: bold }

Вторая строка примера содержит селектор-строку, недопустимую для CSS1. UA для CSS1 должен пропустить эту установку и привести таблицу стилей к следующему виду:

  H1 { color: blue }
  P EM { font-weight: bold }

Блок-определение начинается с левой фигурной скобки ({) и заканчивается обнаруженной правой фигурной скобкой. Между ними располагается перечень из определений (0 или больше), разделенных точками с запятой (;).

Определение состоит из свойства, двоеточия и значения, каждое из которых может отделяться пробелами. Как указывалось ранее, свойство является идентификатором. В значении могут быть использованы разные символы, однако в анализируемых парах должны присутствовать круглые скобки (()), квадратные скобки ([]), фигурные скобки ({}), одинарные кавычки (') и двойные кавычки ("). Круглые, квадратные и фигурные кавычки могут быть вложенными. Внутри кавычек символы воспринимаются как строки.

Чтобы гарантировать возможность в будущем вводить новые свойства и новые значения для существующих свойств, UA должно опускать определение с некорректным названием свойства или некорректным значением свойства. Каждое из свойств CSS1 имеет собственные синтаксические и семантические ограничения значений, которое оно может принимать.

Например, допустим, что обработчик CSS1 встретил следующую таблицу стилей:

  H1 { color: red; font-style: 12pt }
  P { color: blue;  font-vendor: any;
    font-variant: small-caps }
  EM EM { font-style: normal }

Второе определение в первой линии имеет недопустимое значение '12pt'. Второе определение во второй линии содержит неопределенное свойство 'font-vendor'. Обработчик CSS1 пропустит эти определения, сокращая таблицу стилей до:

  H1 { color: red; }
  P { color: blue;  font-variant: small-caps }
  EM EM { font-style: normal }

Комментарии (см. секцию 1.7) могут быть вставлены везде, где допускается простановка пробелов. CSS1 определяет допустимые места вставки пробелов (напр. внутри значений), и там же могут быть вставлены комментарии.

Всегда соблюдаются следующие правила:

  • все таблицы стилей CSS независимы от регистра, исключая части, не находящиеся под контролем CSS. Например, в CSS1 названия семейства шрифта и URL могут зависеть от внешних обстоятельств. Также зависимые от внешних обстоятельств атрибуты для CLASS и ID находятся под контролем HTML [2].
  • для CSS1 селекторы (имена, классы и ID элемента) могут включать только символы A-Z, 0-9 и символы Unicode 161-255, плюс черточку (-); они не могут начинаться с черточки или цифры; они могут также включать непечатаемые и символы Unicode в виде цифрового кода (см. ниже).
  • обратный слеш, после которого идут не более четырех шестнадцатеричных цифр (0..9A..F), представляет собой символ Unicode с указанным числом.
  • любой символ за исключением шестнадцатеричной цифры, может быть пропущен для игнорирования его специального значения, путем установки перед ним обратного слеша. Например: "\"" - строка, состоящая из одной двойной кавычки.
  • два предшествующих пункта определяют переходы по обратному слешу. Такие переходы всегда считаются частью идентификатора, за исключением случаев, когда они находятся внутри строки (т.е. "\7B" - это не пунктуация, даже если присутствует "{", а "\32" допускается в начале имени класса, даже если "2" - нет).

Примечание: атрибут HTML CLASS допускает больше символов в названии класса, чем установлено для вышеназванных селекторов. В CSS1 эти символы будут пропущены или транслитерированы в числа Unicode: "B&W?" может быть написано в виде "B\&W\?", или "B\26W\3F", "??????" (Greek: "kouros") может быть написано как "\3BA\3BF\3C5\3C1\3BF\3C2". Ожидается, что в последующих версиях CSS непосредственно можно будет вводить больше символов.

В приложении В дана грамматика CSS1.


8. Литература. Спецификация CSS1 | WebClub

8. Литература. Спецификация CSS1

[1] W3C resource page on web style sheets (http://www.w3.org/pub/WWW/Style)

[2] Sometime in 1997 we expect there to be a single HTML specification that has support for Style Sheets and Internationalization. Among the works in progess in this area is "HTML3 and Style Sheets" (http://www.w3.org/pub/WWW/TR/WD-style) and the "Cougar DTD" (http://www.w3.org/pub/WWW/MarkUp/Cougar/HTML.dtd).

[3] T Berners-Lee, D Connolly: "Hypertext Markup Language - 2.0", RFC 1866, MIT/W3C, November 1995. The specification is also available in hypertext form (http://www.w3.org/pub/WWW/MarkUp/html-spec/html-spec_toc.html)

[4] F Yergeau, G Nicol, G Adams, M Dürst: "Internationalization of the Hypertext Markup Language" (ftp://ietf.org/internet-drafts/draft-ietf-html-i18n-05.txt).

[5] ISO 8879:1986. Information Processing - Text and Office Systems - Standard Generalized Markup Language (SGML)

[6] ISO/IEC 10179:1996 Information technology -- Processing languages -- Document Style Semantics and Specification Language (DSSSL). The specification is also available in hypertext form (http://occam.sjf.novell.com:8080/dsssl/dsssl96).

[7] ISO/IEC 9899:1990 Programming languages -- C.

[8] The Unicode Consortium, "The Unicode Standard -- Worldwide Character Encoding -- Version 1.0", Addison-Wesley, Volume 1, 1991, Volume 2, 1992.

[9] M Anderson, R Motta, S Chandrasekar, M Stokes: "Proposal for a Standard Color Space for the Internet - sRGB" (http://www.hpl.hp.com/personal/Michael_Stokes/srgb.htm)

[10] CIE Publication 15.2-1986, "Colorimetry, Second Edition", ISBN 3-900-734-00-3 (http://www.hike.te.chiba-u.ac.jp/ikeda/CIE/publ/abst/15-2-86.html)

[11] T Berners-Lee, L Masinter, M McCahill: "Uniform Resource Locators (URL)", RFC 1738, CERN, Xerox Corporation, University of Minnesota, December 1994

[12] "PNG (Portable Network Graphics) Specification, Version 1.0 specification" (http://www.w3.org/pub/WWW/TR/REC-png-multi.html)

[13] Charles A. Poynton: "Gamma correction on the Macintosh Platform" (ftp://ftp.inforamp.net/pub/users/poynton/doc/Mac/Mac_gamma.pdf)

[14] International Color Consortium: "ICC Profile Format Specification, version 3.2", 1995 (ftp://sgigate.sgi.com/pub/icc/ICC32.pdf)

[15] S C Johnson: "YACC - Yet another compiler compiler", Technical Report, Murray Hill, 1975

[16] "Flex: The Lexical Scanner Generator", Version 2.3.7, ISBN 1882114213


Приложение A: Пример таблицы стилей для HTML 2.0 | WebClub

Приложение A: Пример таблицы стилей для HTML 2.0

(Это приложение является информативным, а не формальным)

Предлагаемая таблица стилей написана в соответствии с предлагавшимся форматированием документов в спецификации HTML 2.0 [3]. Некоторые стили, например цвета, были добавлены для завершенности. Предлагается, что UA будет использовать по умолчанию таблицу стилей, схожую с представленной.

  BODY {
    margin: 1em;
    font-family: serif;
    line-height: 1.1;
    background: white;
    color: black;
  }

  H1, H2, H3, H4, H5, H6, P, UL, OL, DIR, MENU, DIV,
  DT, DD, ADDRESS, BLOCKQUOTE, PRE, BR, HR
  { display: block }

  B, STRONG, I, EM, CITE, VAR, TT, CODE, KBD, SAMP,
  IMG, SPAN { display: inline }

  LI { display: list-item }

  H1, H2, H3, H4 { margin-top: 1em;
                  margin-bottom: 1em }
  H5, H6 { margin-top: 1em }
  H1 { text-align: center }
  H1, H2, H4, H6 { font-weight: bold }
  H3, H5 { font-style: italic }

  H1 { font-size: xx-large }
  H2 { font-size: x-large }
  H3 { font-size: large }

  B, STRONG { font-weight: bolder }
        /* relative to the parent */
  I, CITE, EM, VAR, ADDRESS, BLOCKQUOTE
       { font-style: italic }
  PRE, TT, CODE, KBD, SAMP { font-family: monospace }

  PRE { white-space: pre }

  ADDRESS { margin-left: 3em }
  BLOCKQUOTE { margin-left: 3em; margin-right: 3em }

  UL, DIR { list-style: disc }
  OL { list-style: decimal }
  MENU { margin: 0 }          /* tight formatting */
  LI { margin-left: 3em }

  DT { margin-bottom: 0 }
  DD { margin-top: 0; margin-left: 3em }

  HR { border-top: solid }    /* 'border-bottom' could
                                also have been used */

  A:link { color: blue }      /* unvisited link */
  A:visited { color: red }    /* visited links */
  A:active { color: lime }    /* active links */

  /* setting the anchor border around IMG элементы
     requires контекстный селекторs */

  A:link IMG { border: 2px solid blue }
  A:visited IMG { border: 2px solid red }
  A:active IMG { border: 2px solid lime }


Приложение B: Грамматика CSS1. Спецификация CSS1 | WebClub

Приложение B: Грамматика CSS1. Спецификация CSS1

(Это приложение является формальным)

The minimal CSS (i.e., any version of CSS) grammar that all implementations need to support is defined in section 7. The grammar below defines a much smaller language, a language that defines the syntax of CSS1.

It is in some sense, however, still a superset of CSS1: there are additional semantic constraints not expressed in this grammar. A conforming UA must also adhere to the forward-compatible parsing rules (section 7.1), the property and value notation (section 5) and the unit notation (section 6). In addition, HTML imposes restrictions, e.g., on the possible values of the CLASS attribute.

The grammar below is LL(1) (but note that most UA's should not use it directly, since it doesn't express the parsing conventions, only the CSS1 syntax). The format of the productions is optimized for human consumption and some shorthand notation beyond yacc [15] is used:

  *  : 0 or more
  +  : 1 or more
  ?  : 0 or 1
  |  : separates alternatives
  [] : grouping

The productions are:

stylesheet
 : [CDO|CDC]* [ import [CDO|CDC]* ]* [ ruleset [CDO|CDC]* ]*
 ;
import
 : IMPORT_SYM [STRING|URL] ';'          /* E.g., @import url(fun.css); */
 ;
unary_operator
 : '-' | '+'
 ;
operator
 : '/' | ',' | /* empty */
 ;
property
 : IDENT
 ;
ruleset
 : selector [ ',' selector ]*
   '{' declaration [ ';' declaration ]* '}'
 ;
selector
 : simple_selector+ [ pseudo_element | solitary_pseudo_element ]?
 | solitary_pseudo_element
 ;
        /* An "id" is an ID that is attached to an element type
        ** on its left, as in: P#p007
        ** A "solitary_id" is an ID that is not so attached,
        ** as in: #p007
        ** Analogously for classes and pseudo-classes.
        */
simple_selector
 : element_name id? class? pseudo_class?        /* eg: H1.subject */
 | solitary_id class? pseudo_class?             /* eg: #xyz33 */
 | solitary_class pseudo_class?                 /* eg: .author */
 | solitary_pseudo_class                        /* eg: :link */
 ;
element_name
 : IDENT
 ;
pseudo_class                                    /* as in:  A:link */
 : LINK_PSCLASS_AFTER_IDENT
 | VISITED_PSCLASS_AFTER_IDENT
 | ACTIVE_PSCLASS_AFTER_IDENT
 ;
solitary_pseudo_class                           /* as in:  :link */
 : LINK_PSCLASS
 | VISITED_PSCLASS
 | ACTIVE_PSCLASS
 ;
class                                           /* as in:  P.note */
 : CLASS_AFTER_IDENT
 ;
solitary_class                                  /* as in:  .note */
 : CLASS
 ;
pseudo_element                                  /* as in:  P:first-line */
 : FIRST_LETTER_AFTER_IDENT
 | FIRST_LINE_AFTER_IDENT
 ;
solitary_pseudo_element                         /* as in:  :first-line */
 : FIRST_LETTER
 | FIRST_LINE
 ;
        /* There is a constraint on the id and solitary_id that the
        ** part after the "#" must be a valid HTML ID value;
        ** e.g., "#x77" is OK, but "#77" is not.
        */
id
 : HASH_AFTER_IDENT
 ;
solitary_id
 : HASH
 ;
declaration
 : property ':' expr prio?
 | /* empty */                          /* Prevents syntax errors... */
 ;
prio
 : IMPORTANT_SYM                        /* !important */
 ;
expr
 : term [ operator term ]*
 ;
term
 : unary_operator?
   [ NUMBER | STRING | PERCENTAGE | LENGTH | EMS | EXS
   | IDENT | hexcolor | URL | RGB ]
 ;
        /* There is a constraint on the color that it must
        ** have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
        ** after the "#"; e.g., "#000" is OK, but "#abcd" is not.
        */
hexcolor
 : HASH | HASH_AFTER_IDENT
 ;

The following is the tokenizer, written in flex [16] notation. Note that this assumes an 8-bit implementation of flex. The tokenizer is case-insensitive (flex command line option -i).

unicode         \\[0-9a-f]{1,4}
latin1          [¡-ÿ]
escape          {unicode}|\\[ -~¡-ÿ]
stringchar      {escape}|{latin1}|[ !#$%&(-~]
nmstrt          [a-z]|{latin1}|{escape}
nmchar          [-a-z0-9]|{latin1}|{escape}
ident           {nmstrt}{nmchar}*
name            {nmchar}+
d               [0-9]
notnm           [^-a-z0-9\\]|{latin1}
w               [ \t\n]*
num             {d}+|{d}*\.{d}+
string          \"({stringchar}|\')*\"|\'({stringchar}|\")*\'

%x COMMENT
%s AFTER_IDENT

%%
"/*"                            {BEGIN(COMMENT);}
<COMMENT>"*/"                     {BEGIN(0);}
<COMMENT>\n                       {/* ignore */}
<COMMENT>.                        {/* ignore */}
@import                         {BEGIN(0); return IMPORT_SYM;}
"!"{w}important                 {BEGIN(0); return IMPORTANT_SYM;}
{ident}                         {BEGIN(AFTER_IDENT); return IDENT;}
{string}                        {BEGIN(0); return STRING;}

{num}                           {BEGIN(0); return NUMBER;}
{num}"%"                        {BEGIN(0); return PERCENTAGE;}
{num}pt/{notnm}                 {BEGIN(0); return LENGTH;}
{num}mm/{notnm}                 {BEGIN(0); return LENGTH;}
{num}cm/{notnm}                 {BEGIN(0); return LENGTH;}
{num}pc/{notnm}                 {BEGIN(0); return LENGTH;}
{num}in/{notnm}                 {BEGIN(0); return LENGTH;}
{num}px/{notnm}                 {BEGIN(0); return LENGTH;}
{num}em/{notnm}                 {BEGIN(0); return EMS;}
{num}ex/{notnm}                 {BEGIN(0); return EXS;}

<AFTER_IDENT>":"link              {return LINK_PSCLASS_AFTER_IDENT;}
<AFTER_IDENT>":"visited   {return VISITED_PSCLASS_AFTER_IDENT;}
<AFTER_IDENT>":"active    {return ACTIVE_PSCLASS_AFTER_IDENT;}
<AFTER_IDENT>":"first-line        {return FIRST_LINE_AFTER_IDENT;}
<AFTER_IDENT>":"first-letter      {return FIRST_LETTER_AFTER_IDENT;}
<AFTER_IDENT>"#"{name}            {return HASH_AFTER_IDENT;}
<AFTER_IDENT>"."{name}            {return CLASS_AFTER_IDENT;}

":"link                         {BEGIN(AFTER_IDENT); return LINK_PSCLASS;}
":"visited                      {BEGIN(AFTER_IDENT); return VISITED_PSCLASS;}
":"active                       {BEGIN(AFTER_IDENT); return ACTIVE_PSCLASS;}
":"first-line                   {BEGIN(AFTER_IDENT); return FIRST_LINE;}
":"first-letter                 {BEGIN(AFTER_IDENT); return FIRST_LETTER;}
"#"{name}                       {BEGIN(AFTER_IDENT); return HASH;}
"."{name}                       {BEGIN(AFTER_IDENT); return CLASS;}

url\({w}{string}{w}\)                                   |
url\({w}([^ \n\'\")]|\\\ |\\\'|\\\"|\\\))+{w}\)         {BEGIN(0); return URL;}
rgb\({w}{num}%?{w}\,{w}{num}%?{w}\,{w}{num}%?{w}\)      {BEGIN(0); return RGB;}

[-/+{};,#:]                     {BEGIN(0); return *yytext;}
[ \t]+                          {BEGIN(0); /* ignore whitespace */}
\n                              {BEGIN(0); /* ignore whitespace */}
\<\!\-\-                     {BEGIN(0); return CDO;}
\-\-\>                               {BEGIN(0); return CDC;}
.                               {fprintf(stderr, "%d: Illegal character (%d)\n",
                                 lineno, *yytext);}

Appendix C: Encoding

(This appendix is informative, not normative)

HTML documents may contain any of the about 30,000 different characters defined by Unicode. Many documents only need a few hundred. Many fonts also only contain just a few hundred glyphs. In combination with section 5.2, this appendix explains how the characters in the document and the glyphs in a font are matched.

Character encoding

The content of an HTML document is a sequence of characters and markup. To send it "over the wire", it is encoded as a sequence of bytes, using one of several possible encodings. The HTML document has to be decoded to find the characters. For example, in Western Europe it is customary to use the byte 224 for an a-with-grave-accent (à), but in Hebrew, it is more common to use 224 for an Aleph. In Japanese, the meaning of a byte usually depends on the bytes that preceded it. In some encodings, one character is encoded as two (or more) bytes.

The UA knows how to decode the bytes by looking at the "charset" parameter in the HTTP header. Typical encodings (charset values) are "ASCII" (for English), "ISO-8859-1" (for Western Europe), "ISO-8859-8" (for Hebrew), "Shift-JIS" (for Japanese).

HTML [2] [4], allows some 30,000 different characters, namely those defined by Unicode. Not many documents will use that many different characters, and choosing the right encoding will usually ensure that the document only needs one byte per character. Occasional characters outside the encoded range can still be entered as numerical character references: '&#928;' will always mean the Greek uppercase Pi, no matter what encoding was used. Note that this entails that UAs have to be prepared for any Unicode character, even if they only handle a few encodings.

Font encoding

A font doesn't contain characters, it contains pictures of characters, known as glyphs. The glyphs, in the form of outlines or bitmaps, constitute a particular representation of a character. Either explicitly or implicitly, each font has a table associated with it, the font encoding table, that tells for each glyph what character it is a representation for. In Type 1 fonts, the table is referred to as an encoding vector.

In fact, many fonts contain several glyphs for the same character. Which of those glyphs should be used depends either on the rules of the language, or on the preference of the designer.

In Arabic, for example, all letters have four different shapes, depending on whether the letter is used at the start of a word, in the middle, at the end, or in isolation. It is the same character in all cases, and thus there is only one character in the HTML document, but when printed, it looks differently each time.

There are also fonts that leave it to the graphic designer to choose from among various alternative shapes provided. Unfortunately, CSS1 doesn't yet provide the means to select those alternatives. Currently, it is always the default shape that is chosen from such fonts.

Font sets

To deal with the problem that a single font may not be enough to display all the characters in a document, or even a single element, CSS1 allows the use of font sets.

A font set in CSS1 is a list of fonts, all of the same style and size, that are tried in sequence to see if they contain a glyph for a certain character. An element that contains English text mixed with mathematical symbols may need a font set of two fonts, one containing letters and digits, the other containing mathematical symbols. See section 5.2 for a detailed description of the selection mechanism for font sets.

Here is an example of a font set suitable for a text that is expected to contain text with Latin characters, Japanese characters, and mathematical symbols:

  BODY { font-family: Baskerville, Mincho, Symbol, serif }

The characters available in the Baskerville font (a font with only Latin characters) will be taken from that font, Japanese will be taken from Mincho, and the mathematical symbols will come from Symbol. Any other characters will (hopefully) come from the generic font family 'serif'. The 'serif' font family will also be used if one or more of the other fonts is unavailable.

Appendix D: Gamma correction

(This appendix is informative, not normative)

See the Gamma Tutorial in the PNG specification [12] if you aren't already familiar with gamma issues.

In the computation, UAs displaying on a CRT may assume an ideal CRT and ignore any effects on apparent gamma caused by dithering. That means the minimal handling they need to do on current platforms is:

PC using MS-Windows
none
Unix using X11
none
Mac using QuickDraw
apply gamma 1.39 [13] (ColorSync-savvy applications may simply pass the sRGB ICC profile [14] to ColorSync to perform correct color correction)
SGI using X
apply the gamma value from /etc/config/system.glGammaVal (the default value being 1.70; applications running on Irix 6.2 or above may simply pass the sRGB ICC profile to the color management system)
NeXT using NeXTStep
apply gamma 2.22

"Applying gamma" means that each of the three R, G and B must be converted to R'=Rgamma, G'=Ggamma, G'=Bgamma, before handing to the OS.

This may rapidly be done by building a 256-element lookup table once per browser invocation thus:

  for i := 0 to 255 do
    raw := i / 255;
    corr := pow (raw, gamma);
    table[i] := trunc (0.5 + corr * 255.0)
  end

which then avoids any need to do transcendental math per color attribute, far less per pixel.

Appendix E: The applicability and extensibility of CSS1

(This appendix is informative, not normative)

The goal of the work on CSS1 has been to create a simple style sheet mechanism for HTML documents. The current specification is a balance between the simplicity needed to realize style sheets on the web, and pressure from authors for richer visual control. CSS1 offers:

  • visual markup replacement: HTML extensions, e.g. "CENTER", "FONT" and "SPACER", are easily replaced with CSS1 style sheets.
  • nicer markup: instead of using "FONT" elements to achieve the popular small-caps style, one declaration in the style sheet is sufficient. Compare the visual markup:
      <H1>H<FONT SIZE=-1>EADLINE</FONT></H1>

    with the style sheet:

      H1 { font-style: small-caps }

  <H1>Headline</H1>
  • various integration levels: CSS1 style rules can be fetched from external style sheets, included in the 'STYLE' element, or put into 'STYLE' attributes. The latter option offers easy transition from HTML extensions.
  • new effects: some new visual effects have been added to offer users new toys. The typographical pseudo-elements and the extra values on the background property fall into this category.
  • scalability: CSS1 will be useful on equipment ranging from text terminals to high-resolution color workstations. Authors can write one style sheet and be reasonably sure that the intended style will come across in the best possible manner.

CSS1 does not offer:

  • per pixel control: CSS1 values simplicity over level of control, and although the combination of background images and styled HTML is powerful, control to the pixel level is not possible.
  • author control: the author cannot enforce the use of a certain sheet, only suggest
  • a layout language: CSS1 does not offer multiple columns with text-flow, overlapping frames etc.
  • a rich query language on the parse tree: CSS1 can only look for ancestor elements in the parse tree, while other style sheet languages (e.g. DSSSL [6]) offers a full query language.

We expect to see extensions of CSS in several directions:

  • paper: better support for printing HTML documents
  • support for non-visual media: work is in the process to add a list of properties and corresponding values to support speech and braille output
  • color names: the currently supported list may be extended
  • fonts: more precise font specification systems are expected to complement existing CSS1 font properties.
  • values, properties: we expect vendors to propose extensions to the CSS1 set of values and properties. Extending in this direction is trivial for the specification, but interoperability between different UAs is a concern
  • layout language: support for two-dimensional layout in the tradition of desktop publishing packages.
  • other DTDs: CSS1 has some HTML-specific parts (e.g. the special status of the 'CLASS' and 'ID' attributes) but should easily be extended to apply to other DTDs as well.

We do not expect CSS to evolve into:

  • a programming language

Copyright © 1996 W3C (MIT, INRIA, Keio ), All Rights Reserved. W3C liability, trademark, document use and software licensing rules apply.


Приложение C: Кодирование. Спецификация CSS1 | WebClub

Приложение C: Кодирование. Спецификация CSS1

(Это приложение является информативным, а не формальным)

HTML documents may contain any of the about 30,000 different characters defined by Unicode. Many documents only need a few hundred. Many fonts also only contain just a few hundred glyphs. In combination with section 5.2, this appendix explains how the characters in the document and the glyphs in a font are matched.

Character encoding

The content of an HTML document is a sequence of characters and markup. To send it "over the wire", it is encoded as a sequence of bytes, using one of several possible encodings. The HTML document has to be decoded to find the characters. For example, in Western Europe it is customary to use the byte 224 for an a-with-grave-accent (à), but in Hebrew, it is more common to use 224 for an Aleph. In Japanese, the meaning of a byte usually depends on the bytes that preceded it. In some encodings, one character is encoded as two (or more) bytes.

The UA knows how to decode the bytes by looking at the "charset" parameter in the HTTP header. Typical encodings (charset values) are "ASCII" (for English), "ISO-8859-1" (for Western Europe), "ISO-8859-8" (for Hebrew), "Shift-JIS" (for Japanese).

HTML [2] [4], allows some 30,000 different characters, namely those defined by Unicode. Not many documents will use that many different characters, and choosing the right encoding will usually ensure that the document only needs one byte per character. Occasional characters outside the encoded range can still be entered as numerical character references: '&#928;' will always mean the Greek uppercase Pi, no matter what encoding was used. Note that this entails that UAs have to be prepared for any Unicode character, even if they only handle a few encodings.

Font encoding

A font doesn't contain characters, it contains pictures of characters, known as glyphs. The glyphs, in the form of outlines or bitmaps, constitute a particular representation of a character. Either explicitly or implicitly, each font has a table associated with it, the font encoding table, that tells for each glyph what character it is a representation for. In Type 1 fonts, the table is referred to as an encoding vector.

In fact, many fonts contain several glyphs for the same character. Which of those glyphs should be used depends either on the rules of the language, or on the preference of the designer.

In Arabic, for example, all letters have four different shapes, depending on whether the letter is used at the start of a word, in the middle, at the end, or in isolation. It is the same character in all cases, and thus there is only one character in the HTML document, but when printed, it looks differently each time.

There are also fonts that leave it to the graphic designer to choose from among various alternative shapes provided. Unfortunately, CSS1 doesn't yet provide the means to select those alternatives. Currently, it is always the default shape that is chosen from such fonts.

Font sets

To deal with the problem that a single font may not be enough to display all the characters in a document, or even a single element, CSS1 allows the use of font sets.

A font set in CSS1 is a list of fonts, all of the same style and size, that are tried in sequence to see if they contain a glyph for a certain character. An element that contains English text mixed with mathematical symbols may need a font set of two fonts, one containing letters and digits, the other containing mathematical symbols. See section 5.2 for a detailed description of the selection mechanism for font sets.

Here is an example of a font set suitable for a text that is expected to contain text with Latin characters, Japanese characters, and mathematical symbols:

  BODY { font-family: Baskerville,
   Mincho, Symbol, serif }

The characters available in the Baskerville font (a font with only Latin characters) will be taken from that font, Japanese will be taken from Mincho, and the mathematical symbols will come from Symbol. Any other characters will (hopefully) come from the generic font family 'serif'. The 'serif' font family will also be used if one or more of the other fonts is unavailable.


-C-S-S-TUTORIAL v1.0

CSS - Cascading Style Sheets

What is Css
 - About Css
 - Structure&Rules
- Inline Style Sheets
- Global Style Sheets
- Linked Style Sheets

Css Properties
 - Font Properties
- Text Properties
- Color and Background Properties
- Classification
- Box Properties

Units
 - Length Units
- Percentage Units
- Color Units
- URLs

Chapter I >> What Is Css
[1] About Css

Так что это такое - Сss? Cascading Style Sheets ( Таблицы Каскадных Стилей ). Css - это язык содержащий набор свойств для определения внешнего вида документа. С его помощью дизайнер имеет полный контроль над стилем и расположением каждого элемента веб страницы. Это гораздо проще использования обычного набора HTML тегов. Приведу пример: Вам нужно создать жирный красный подчеркнутый текст.

HTML EXAMPLE: <font color="red"><strong><u>Hello World</u></strong></font>

А если подобный стиль нужно использовать несколько раз? Хорошо если раз 5, а если 10-20? Вот тут нам и поможет Сss. Существует три вида таблиц стилей: Внутренние таблицы стилей, Глобальные таблицы стилей и Связанные таблицы стилей. Внутриние таблицы стилей ( Inline Style Sheets ) мало чем отличаются от HTML тегов. Глобальные ( Global Style Sheets ) определяют стиль элементов во всем документе. Связанные ( Linked Style Sheets ) могут быть использованы для нескольких документов и хранятся во внешнем файле. Подробнее об этом написано ниже.
[ Вверх... ]

Chapter I >> What Is Css
[2] Structure&Rules

Селекторы (Selectors):
Любой элемент HTML может иметь CSS селектор. Селектор определяет стиль элемента, для которого он создан.
EXAMPLE: H1 {color:red;size:20pt;}
Все элементы H1 в документе будут красного цвета, размером в 20 точек (pt , point).
Классовые селекторы (Class Selectors):
CLASS -- класс стилей в css. Для его определения в css используется знак "."+ имя.
Все селекторы могут иметь разные классы. Это позволяет одинаковым элементам иметь различные стили.
EXAMPLE: H1.blue {color:blue;size:20pt;}
Все элементы H1 с атрибутом CLASS="blue" стануть синими.
Классы могут быть также описаны без привязывания их к определенным элементам.
EXAMPLE: .green {color:green;}
В данном случае все элементы с указанным атрибутом CLASS="green" станут зелеными.
ID селекторы (ID Selectors):
ID -- индивидуально именованный стиль. C его помощью можно создавать стилистические исключения cреди элементов одного класса. Для определения в css используется знак "#"+ имя.
Индификаторы используются в основном для придания одному или нескольким элементам одного класса индивидуальных свойств. Скажем Вы создали класс blue -- синий курсив. Но вам понадобился жирный, подчеркнутый текст синим курсивом. Конечно, можно создать новый класс, но зачем? Проще описать ID. Например "boldunderline". И все элементы класса blue с значением ID "boldunderline" станут жирным подчеркнутым синим курсивом. Произойдет как бы синтез свойств класса blue и идификатора boldunderline.

EXAMPLE:
<html>
<head>
<title> Example </title>
</head>
<style>
.blue {color:blue;font-style:italic}
#boldunderline {text-decoration:underline;font-weight:bold}
</style>
<body>
<p class="blue"> Hello! Welcome To My Homepage!</p>
<p class="blue" id="boldunderline">Will Be Soon</p>
<p id="boldunderline">Under Construction</p>
</body>
</html>

Как видно из примера, ID может быть использован без указания класса ( последний параграф примера ). Тогда параграф будет обладать только свойствами ID ( в примере - жирный, подчеркнутый текст ).

Контекстуальные селекторы (Contextual Selectors):
Контекстные селекторы - это сочетания нескольких обыкновенных селекторов. Стиль задается только элеметнам в заданной последовательности в зависимости от каскадного порядка.

EXAMPLE: P EM {color:silver;}

В данном примере все элементы EM внутри элементов P будут иметь заданный стиль.

Придание нескольким элементам одинаковых свойств:
Скажем Вам нужно придать нескольким элементам Вашей веб страницы одинаковых свойств. В этом случае при определении элементоы перечисляутся через запятую.
h1,h2,h3,p,strong {color:green}
Все элементы h1, h2, h3, p и strong будут зелеными.

Псевдоклассы и псевдоэлементы :
Псевдокласс состоит из элементов одного типа, отвечаюшему определенному критерию. Псевдоклассы и псевдоэлементы при определении отделяются знаком ":" .
Список псевдоклассов и псевдоэлементов :
- Anchor Pseudo Classes -- Эти псевдоклассы элемента <a>, обозначающего ссылки. Псевдоклассы этого элемента -- link ( линк ), active ( активная ссылка ), visited ( посещенный ранее URL ), hover ( при поднесении курсора, не работает в Нетскейпе ).
- First Line Pseudo-element -- first-line. Этот псевдоэлемент может быть использован с block-level элементами ( p, h1 и т.д. ). Он изменяет стиль первой строки этих элементов.
- First Letter Pseudo-element -- first-letter. То же самое что и first-line, только влияет не на всю строку, а только на первый символ.

Пример :
a:link,a:visited {color:blue}
a:active {color:red}
a:hover {text-decoration:none}

В данном примере все элементы Anchor (ссылки) будут синими. При нажатии ( в активном состоянии ) поменяют цвет на красный. И при подведении курсора мышки исчезнет подчеркивание. Очень эффектный прием :-)
[ Вверх... ]

Chapter I >> What Is Css
[3] Inline Style Sheets

Как уже говорилось, использование Внутрених стилей мало чем отличается от использования обычных HTML тегов. Они задают стиль только одному элементу документа при помоши атрибута style в HTML теге.

HTML EXAMPLE: <font color="blue" size="3" face="Arial">Hello World!</font>
RESULT:Hello World!

INLINE STYLE SHEET EXAMPLE:<font style="color:blue;font-size:12pt;font-family:Arial">Hello World</font>
RESULT:Hello World!

Как можно заметить, код Inline Style Sheet Example получился больше чем HTML Example. Поэтому иногда разумнее использовать обычные HTML теги
. [ Вверх... ]

Chapter I >> What Is Css
[4] Global Style Sheets

Глобальные стили задают вид элементов всего документа. Для этого используется тег <STYLE>. Он размещается в заголовке документа. Пример:

<html>
<head> <title> Example Of Global Style Sheets </title>
</head>
<style>
h1{color:red;font-style:italic;font-size:32px}
.blue{color:blue}
#bold{font-weight:bold}
</style>
<body>
<h1> Этот заголовок написан крупным красным курсивом </h1>
Вот <font class="blue"> это </font> слово - синие, a <font id="bold"> это</font> -- жирное!!!
</body>
</html>

В данном примере все элементы H1 будут написаны крупным красным курсивым, все элементы с указанным классом BLUE будут синими , а а все элементы с индификатором ID="Bold" станут жирными.
[ Вверх... ]

Chapter I >> What Is Css
[5] Linked Style Sheets

Связанные таблицы стилей используются для придания нескольким документам одного стиля. Хронятся они в отдельном файле. Пример (файл styles.css):

<style>
body {background:black;font-size:9pt;color:red;font-family:Arial Black}
.base{color:blue;font-style:italic}
h1 {color:white}
#bold {font-weight:bold}
</style>

Как видите, это очень похоже на Global Styles Sheets. Так оно и есть. Все что относилось к Глобальным стилям справедливо и здесь. В самих же документах делается ссылка на этот файл при помощи тега <link>. Выглядит это так: <link REL="STYLESHEET" TYPE="text/css" HREF="путь до файла">

EXAMPLE:

File [ styles.css ]
<style>
body {background:black;color:red;font-size:9pt}
h1 {color:white}
a:link,a:visited,a:active {color:green}
a:hover {font-weight:bild}
</style>

File [ Index.html ]
<html>
<head>
<title> Example &glt;/title>
</head>
<link rel="stylesheet" type="text/css" href="styles.css">
<body>
[ Содержание Документа ]
</body>
</html>

На этом я заканчиваю Chapter I и перехожу к Chapter II.
[ Вверх... ]

Chapter II >> Css Properties
[1] Font Properties

• font-family
Possible Values: Любой шрифт
*Applies to : All elements
Описание: Это свойство определяет используемый элементом шрифт. Если указать УРЛ, то шрифт автоматически установится на компьютер пользователя
EXAMPLE:font-family:Arial Black URL('arialblack.ttf')

• font-style
Possible Values:
[1] normal - без изменений
[2] italic - курсив
*Applies to : All elements
Описание: Стиль элемента. Курсивный или обычный
EXAMPLE:font-style:italic

• font-variant
Possible Values:
[1] normal - без изменений
[2] small-caps - заменяет все буквы на большие
*Applies to : All elements
Описание: Netscape вообще не поддерживает это свойство.
EXAMPLE:font-variant:small-caps

• font-weight
Possible Values:
[1] normal - без изменений
[2] bold - жирный
[3] bolder - очень жирный ( в MSIE не отличается от bold, в Нетскейпе от нормал )
[4] lighter - тонкий ( не отличается от normal )
[5] любое значение от 100 до 900
*Applies to : All elements
Описание: Выделение ( жирность ) элемента
EXAMPLE:font-weight:bold

• font-size
Possible Values:
[1] размер (+)
[2] xx-small, x-small, small, medium, large, x-large, xx-large - любое из этих значений
[3] smaller, larger - любое из этих значений
*Applies to : All elements
Описание: Размер шрифта
EXAMPLE:font-size:30pt

• font
Possible Values:
*Applies to : All elements
Описание: Обобщает все вышеперечисленные свойства
EXAMPLE:font: italic bolder Arial 12pt

[ Вверх... ]

Chapter II >> Css Properties
[2] Text Properties

• word-spacing
Possible Values:
[1] длина (+)
[2] normal - без изменений
*Applies to : All elements
Описание: Расстояние между cловами. Не работает ни в Нетскейпе, ни в MSIE
EXAMPLE:word-spacing:0.4em

• text-decoration
Possible Values:
[1] none - нет
[2] underline - подчеркнутый
[3] overline - надчеркнутый ( не поддерживается в Нетскейпе )
[4] line-through - перечеркнутый
[5] blink - мигающий ( не поддерживается в IE )
*Applies to : All elements
Описание: "Украшение" текста
EXAMPLE:text-decoration:line-through

• letter-spacing
Possible Values:
[1] длина (+)
[2] normal - без изменений
*Applies to : All elements
Описание: Расстояние между буквами. Не работает в Нетскейпе
EXAMPLE:letter-spacing:100

• vertical-align
Possible Values:
[1] baseline
[2] sub
[3] super
[4] top-text
[5] top
[6] middle
[7] bottom
[8] bottom-text
[9] процент
*Applies to : Inline elements
Описание: Позиционирование элементов по отношению к другим элементам стоящих в одном ряду. Не работает в Нетскейпе
EXAMPLE:vertical-align:top-text

• text-transform
Possible Values:
[1] none - нет
[2] Capitalize - каждое слово начинается с большой буквы
[3] UPPERCASE - каждая буква текста становится заглавной
[4] lowercase - каждая буква текста становится маленькой
*Applies to : Inline elements
Описание: Изменение текста. Не работает в Нетскейпе
EXAMPLE:text-transform:Capitalize

• text-align
Possible Values:
[1] left - текст слева
[2] right - текст справа
[3] center - текст по центру
[3] justify - текст "растянут"
*Applies to : Block-level elements
Описание: Положение текста
EXAMPLE:text-align:right

• text-indent
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : Block-level elements
Описание: Отступ
EXAMPLE:text-indent:30 em

• line-height
Possible Values:
[1] normal - без изменений
[2] длина (+)
[3] процент
*Applies to : All elements
Описание: Отступ сверху
EXAMPLE:line-height:100%

[ Вверх... ]

Chapter II >> Css Properties
[3] Color and Background Properties

• color
Possible Values:
[1] цвет (+)
*Applies to : All elements
Описание: Цвет
EXAMPLE:color:#f00000

• backgroung-color
Possible Values:
[1] цвет (+)
*Applies to : All elements
Описание: Цвет фона элемента
EXAMPLE:background-color:#f00000

• background-image
Possible Values:
[1] none - нет
[2] URL (+)
*Applies to : All elements
Описание: Фоновое изображение
EXAMPLE:background-image:URL(cool.gif)

• background-repeat
Possible Values:
[1] repeat - размножает фоновое изображение во всех направлениях
[2] repeat-x - размножает фоновое изображение горизонтально
[3] repeat-y - размножает фоновое изображение вертикально
[4] no-repeat - не повторяющиеся изображение
*Applies to : All elements
Описание: Повторения фонового изображения
EXAMPLE:background-repeat:no-repeat

• background-attachment
Possible Values:
[1] scroll - фоновое изображение скроллится всесте с содержанием документа
[2] fixed - не скроллится. Фиксируется в одном месте
*Applies to : All elements
Описание: Возможность прокрутки фонового изображения
EXAMPLE:background-attachment:fixed

• background-position
Possible Values:
[1] процент от длинны + процент от высоты (+)
[2] top, middle, bottom - одно из значений
[3] left, center, right - одно из начений
[4] расстояние от левого края + расстояние от вершины
*Applies to : Block-level and replaced elements
Описание: Положение фонового изображения ( работает с background-repeat равным repeat-x или repeat-y )
EXAMPLE:background-position:50%0%

• background
Possible Values:
*Applies to : All elements
Описание: Обобщает все вышеперечисленные свойства
EXAMPLE:background:no-repeat black fixed 50%0%

[ Вверх... ]

Chapter II >> Css Properties
[4] Classification

• display
Possible Values:
[1] none - не отображается
[2] block - разбивает линию ( = строку ) до и после элемента.
[3] inline - не разбивает линию ( = строку ). Т.е. элемент можен находится на одной линии с другими элементами.
[4] list-item - разбивает линию ( = строку ) до и после элемента + добавляет маркер как у list-item элементов
*Applies to : All elements
Описание: Определяет как будет отображаться элемент.
EXAMPLE:display:none

• white-space
Possible Values:
[1] normal - "сжимает" несколько подряд идущих пробелов в один
[2] pre - допускает отображение несколькольких подряд идущих пробелов
[3] nowrap - не допускает перенос строки без тага <BR>
*Applies to : Block-level elements
Описание: Оприделяет как будут отображаться пробелы между элементами
EXAMPLE:white-space:nowrap

• list-style-type
Possible Values:
[1] disc, circle, square, decimal, lower-roman, upper-roman, lower-alpha или upper-alpha
[2] none - никакой
*Applies to : Elements with display value list-item
Описание: Определяет вид list-item маркера. Если значение list-style-image равно none или не уточнено
EXAMPLE:list-style-type:lower-alpha

• list-style-image
Possible Values:
[1] none - нет
[2] URL (+)
*Applies to : Elements with display value list-item
Описание: Задает вид list-item маркера в виде картинки
EXAMPLE:list-style-image:URL(cool.gif)

• list-style-position
Possible Values:
[1] inside - при переносе следующие строки будут отображаться без отступа
[2] outside - по умолчанию
*Applies to : Elements with display value list-item
Описание: Определяет положение маркера в зависимости от list item элемента
EXAMPLE:list-style-position:inside

[ Вверх... ]

Chapter II >> Css Properties
[5] Box Properties

• margin-top
Possible Values:
[1] длина (+)
[2] процент (+)
[3] auto - автоматически
*Applies to : All elements
Описание: Определяет отступ сверху
EXAMPLE:margin-top:100

• margin-right
Possible Values:
[1] длина (+)
[2] процент (+)
[3] auto - автоматически
*Applies to : All elements
Описание: Определяет отступ справа
EXAMPLE:margin-right:100%

• margin-bottom
Possible Values:
[1] длина (+)
[2] процент (+)
[3] auto - автоматически
*Applies to : All elements
Описание: Определяет отступ снизу
EXAMPLE:margin-bottom:100em

• margin-left
Possible Values:
[1] длина (+)
[2] процент (+)
[3] auto - автоматически
*Applies to : All elements
Описание: Определяет отступ слева
EXAMPLE:margin-left:100pt

• margin
Possible Values: ^
*Applies to : All elements
Описание: Обобщает все вышеперечисленные свойства
EXAMPLE:background:100pt

• padding-top
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : All elements
Описание: Отступ от верхнего border'а
EXAMPLE:padding-top:100pt

• padding-right
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : All elements
Описание: Отступ от правого border'а
EXAMPLE:padding-right:100%

• padding-bottom
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : All elements
Описание: Отступ от нижнего border'а
EXAMPLE:padding-bottom:100em

• padding-left
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : All elements
Описание: Отступ от левого border'а
EXAMPLE:padding-top:100

• padding
Possible Values: ^
*Applies to : All elements
Описание: Обобщает все вышеперечисленные свойства
EXAMPLE:padding:100px

• border-top-width
Possible Values:
[1] длина (+)
[2] thin, medium или thick
*Applies to : All elements
Описание: толщина верхнего border'а
EXAMPLE:border-top-width:100pt

• border-right-width
Possible Values:
[1] длина (+)
[2] thin, medium или thick
*Applies to : All elements
Описание: толщина правого border'а
EXAMPLE:border-right-width:thick

• border-bottom-width
Possible Values:
[1] длина (+)
[2] thin, medium или thick
*Applies to : All elements
Описание: толщина нижнего border'а
EXAMPLE:border-bottom-width:100em

• border-left-width
Possible Values:
[1] длина (+)
[2] thin, medium или thick
*Applies to : All elements
Описание: толщина левого border'а
EXAMPLE:border-left-width:medium

• border-color
Possible Values: color
Описание: Цвет border'а (+)
*Applies to : All elements
EXAMPLE:border-color:green

• width
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : Block-level and replased elements
Описание: ширина элемента
EXAMPLE:width:10%

• height
Possible Values:
[1] длина (+)
[2] процент (+)
*Applies to : Block-level and replaced elements
Описание: высота элемента
EXAMPLE:height:100pt

• float
Possible Values:
[1] left - слева
[2] right - справа
[3] none - по умолчанию
*Applies to : All elements
Описание: расположение элемента
EXAMPLE:float:right

• clear
Possible Values:
[1] left - слева
[2] right - справа
[3] both - c двух сторон
[4] none - по умолчанию
*Applies to : All elements
Описание: расположение других элементов вокруг данного
EXAMPLE:clear:both

[ Вверх... ]

Chapter III >> Units
[1] Length Units

 Syntax : "+" или "-" затем [число] плюс [единица измерения] ( без пропусков )
Example : -566pt
Единицы длинны :
em - ems , высота используемого элементом шрифта
ex - x-height, ширина буквы "x" используемого элементом шрифта
px - pixels, пикселы
in - inches, дюймы
cm - centimeters, сантиметры
mm - millimeters, миллиметры
pt - points, точка ( 1pt = 1/72in )
pc - picas ( 1pc = 12pt )
[ Вверх... ]

Chapter III >> Units
[2] Percentage Units

 Syntax : "+" или "-" затем [число] плюс "%" ( без пропусков )
Example : -566%
[ Вверх... ]

Chapter III >> Units
[3] Color Units

 Syntax : [color]
Example : magenta
Значением цвета может быть его название ( red , lightgreen, coral и т.д. ) или RGB значение

Способы выразить цвет в RGB ( red green blue ) :
• #rrggbb ( например, #00cc00 )
• rgb(x,x,x) -- где "х" число от 0 до 255 ( например, rgb(0,204,0 ) )
• #rgb ( например, #0c0 )
• rgb (x%,x%,x%) -- где "х%" число от 0.0 до 100.0 ( например, 0%,80%,0% )

Все примеры отображают один и тот же цвет
 [ Вверх... ]

Chapter III >> Units
[4] URLs

Syntax : "URL" , потом в скобках приводится ссылка.Ссылку также можно, помимо скобок, заключить в одинарные или двойные кавычки ( без пропусков )
Example : URL('cool.gif')
 [ Вверх... ]

Other >> От Автора

Примечание: Возможно наличие ошибок и опечаток в этой статье. Все мы люди и не застрахованы от неожиданостей.
Под MSIE ( просто IE или Интернет Эксплорер ) и Нетскейпом подразумивается Microsoft Internet Explorer 4.0+ и Netscape Navigator 4.0+ соответственно.


Приложение D: Gamma correction. Спецификация CSS1 | WebClub

Приложение D: Gamma correction. Спецификация CSS1

(Это приложение является информативным, а не формальным)

See the Gamma Tutorial in the PNG specification [12] if you aren't already familiar with gamma issues.

In the computation, UAs displaying on a CRT may assume an ideal CRT and ignore any effects on apparent gamma caused by dithering. That means the minimal handling they need to do on current platforms is:

PC using MS-Windows
none
Unix using X11
none
Mac using QuickDraw
apply gamma 1.39 [13] (ColorSync-savvy applications may simply pass the sRGB ICC profile [14] to ColorSync to perform correct color correction)
SGI using X
apply the gamma value from /etc/config/system.glGammaVal (the default value being 1.70; applications running on Irix 6.2 or above may simply pass the sRGB ICC profile to the color management system)
NeXT using NeXTStep
apply gamma 2.22

"Applying gamma" means that each of the three R, G and B must be converted to R'=Rgamma, G'=Ggamma, G'=Bgamma, before handing to the OS.

This may rapidly be done by building a 256-element lookup table once per browser invocation thus:

  for i := 0 to 255 do
    raw := i / 255;
    corr := pow (raw, gamma);
    table[i] := trunc (0.5 + corr * 255.0)
  end

which then avoids any need to do transcendental math per color attribute, far less per pixel.


Приложение E: Применимость и расширяемость CSS1. Спецификация CSS1 | WebClub

Приложение E: Применимость и расширяемость CSS1. Спецификация CSS1

(Это приложение является информативным, а не формальным)

The goal of the work on CSS1 has been to create a simple style sheet mechanism for HTML documents. The current specification is a balance between the simplicity needed to realize style sheets on the web, and pressure from authors for richer visual control. CSS1 offers:

  • visual markup replacement: HTML extensions, e.g. "CENTER", "FONT" and "SPACER", are easily replaced with CSS1 style sheets.
  • nicer markup: instead of using "FONT" elements to achieve the popular small-caps style, one declaration in the style sheet is sufficient. Compare the visual markup:
      <H1>H<FONT SIZE=-1>EADLINE</FONT></H1>

    with the style sheet:

      H1 { font-style: small-caps }

  <H1>Headline</H1>
  • various integration levels: CSS1 style rules can be fetched from external style sheets, included in the 'STYLE' element, or put into 'STYLE' attributes. The latter option offers easy transition from HTML extensions.
  • new effects: some new visual effects have been added to offer users new toys. The typographical pseudo-elements and the extra values on the background property fall into this category.
  • scalability: CSS1 will be useful on equipment ranging from text terminals to high-resolution color workstations. Authors can write one style sheet and be reasonably sure that the intended style will come across in the best possible manner.

CSS1 does not offer:

  • per pixel control: CSS1 values simplicity over level of control, and although the combination of background images and styled HTML is powerful, control to the pixel level is not possible.
  • author control: the author cannot enforce the use of a certain sheet, only suggest
  • a layout language: CSS1 does not offer multiple columns with text-flow, overlapping frames etc.
  • a rich query language on the parse tree: CSS1 can only look for ancestor elements in the parse tree, while other style sheet languages (e.g. DSSSL [6]) offers a full query language.

We expect to see extensions of CSS in several directions:

  • paper: better support for printing HTML documents
  • support for non-visual media: work is in the process to add a list of properties and corresponding values to support speech and braille output
  • color names: the currently supported list may be extended
  • fonts: more precise font specification systems are expected to complement existing CSS1 font properties.
  • values, properties: we expect vendors to propose extensions to the CSS1 set of values and properties. Extending in this direction is trivial for the specification, but interoperability between different UAs is a concern
  • layout language: support for two-dimensional layout in the tradition of desktop publishing packages.
  • other DTDs: CSS1 has some HTML-specific parts (e.g. the special status of the 'CLASS' and 'ID' attributes) but should easily be extended to apply to other DTDs as well.

We do not expect CSS to evolve into:

  • a programming language

Copyright © 1996 W3C (MIT, INRIA, Keio ), All Rights Reserved. W3C liability, trademark, document use and software licensing rules apply.

назад | начало | вперед


Каскадные таблицы стилей, уровень 1. Спецификация CSS1 | WebClub

Каскадные таблицы стилей, уровень 1. Спецификация CSS1

Введение

Этот документ определяет уровень 1 каскадных таблиц стилей (CSS1). CSS1 является простым механизмом таблиц стилей, который позволяет авторам и читателям приписывать стиль (например шрифты, цвета или отступы) HTML документам. Язык CSS1 является языком высокого уровня и использует стандартную терминологию, принятую в электронной полиграфии.

Одним из фундаментальных свойств CSS является каскадирование; авторы могут приписывать предпочтительную таблицу стилей, в то время как читатель может иметь персональную таблицу стилей для исправления физиологических или технологических проблем. Правила разрешения конфликтов между различными таблицами стилей определены в этом документе.

Эта рекомендация является результатом деятельности W3C в области таблиц стилей. Для получения подробной информации о деятельности W3C в области таблиц стилей см. [1].

терминология

1 глава. Базовые понятия

2 глава. Псевдо-классы и псевдо-элементы

3 глава. Каскадирование

4 глава. Модель форматирования

5 глава. Свойства CSS1

6 глава. Единицы измерения значений

7 глава. Совместимость с CSS1

8 глава. Литература

Приложение A: Примеры таблиц стилей для HTML 2.0

Приложение B: Грамматика CSS1

Приложение C: Кодирование

Приложение D: Gamma correction

Приложение E: Применимость и расширяемость CSS1

назад | начало | вперед


Терминология. Спецификация CSS1 | WebClub

Терминология. Спецификация CSS1

атрибут (attribute)
атрибут HTML
автор (author)
автор HTML документа
блочный элемент (block-level element)
элемент, включающий перевод строки в начале и конце (например 'H1' в HTML)
холст (canvas)
часть поверхности UA на которой строится (отображается) документ
дочерний элемент (child element)
subelement в терминологии SGML [5]
контекстный селектор (contextual selector)
селектор, который сопоставляется элементам на основе их позиции в структуре документа. Контекстный селектор состоит из нескольких простых селекторов. Например контекстный селектор 'H1.initial B' состоит из двух простых селекторов, 'H1.initial' и 'B'.
CSS
каскадные таблицы стилей (Cascading Style Sheets)
CSS1
каскадные таблицы стилей, уровень 1. Этот документ определяет CSS1, которые являются простым механизмом таблиц стилей в Веб.
расширенные свойства CSS1 (CSS1 advanced features)
свойства, описанные в данной спецификации, но обозначенные как не включенные в основные свойства CSS1
основные свойства CSS1 (CSS1 core features)
часть CSS1, которая обязательна для реализации во всех UA, поддерживающих CSS1
интерпретатор CSS1 (Интерпретатор CSS1)
UA, который читает CSS1
определение (declaration)
свойство (например 'font-size') и соответствующее значение (например '12pt')
дизайнер (designer)
дизайнер таблицы стилей
документ (document)
HTML документ
элемент (element)
HTML элемент
тип элемента (элемент type)
стандартный идентефикатор в терминологии SGML [5]
фиктивная последовательность тэгов (fictional tag sequence)
средство описания поведения псевдо-классов и псевдо-элементов
размер шрифта (font size)
размер, для которого разработан шрифт
HTML
язык гипертекстовой разметки (Hypertext Markup Language) [2], одна из реализаций SGML.
расширение HTML (HTML extension)
разметка, введенная производителем UA, обычно для поддержки определенных визуальных эффектов. Элементы "FONT", "CENTER" и "BLINK" являются примерами расширений HTML, также как и атрибут "BGCOLOR". Одной из целей CSS является предоставление альтернативы расширениям HTML.
строчный элемент (inline element)
элемент, не имеющий переводов строк в начале и конце (например 'STRONG' в HTML)
внутренние размеры (intrinsic dimensions)
ширина и высота, определяемые самим элементом, а не его окружением. В данной спецификации принято, что только замещаемые элементы имеют внутренние размеры.
родительский элемент (parent element)
элемент контейнер в терминологии SGML [5]
псевдо-элемент (pseudo-element)
псевдо-элементы используются в селекторах CSS для адресации типографских элементов (например первая строка элемента), а не структурных элементов.
псевдо-класс (pseudo-class)
псевдо-классы используются в селекторах CSS для обеспечения возможности влияния на элементы внешней по отношению к HTML коду информации (например тот факт, что ссылка была посещена).
свойство (property)
стилистический параметр на который можно повлиять с помощью CSS. Данная спецификация определяет список свойств и их возможных значений.
читатель (reader)
человек, для которого строится (отображается) документ
замещаемый элемент (replaced element
элемент, для которого известны только внутренние размеры. В HTML элементы 'IMG', 'INPUT', 'TEXTAREA', 'SELECT' и 'OBJECT' являются примерами замещаемых элементов. Например содержимое элемента 'IMG' замещается изображением, адресуемым атрибутом SRC. CSS1 не определяет, как находятся внутренние размеры.
правило (rule)
определение (например 'font-family: helvetica') и ее селектор (например 'H1')
селектор (selector)
строка, определяющая, к каким элементам применяется данное правило. Селектор может быть простым (например 'H1') или контекстным (например 'H1 B'), который состоит из нескольких простых селекторов.
SGML
стандартный обобщенный язык разметки (Standard Generalized Markup Language) [5], для которого HTML является одной из реализаций
простой селектор (simple selector)
селектор, который сопоставляется элементам на основе их типа и/или атрибутов, а не их положения в структуре документа. Например 'P.initial' является простым селектором.
таблица стилей (style sheet)
набор правил
UA
User Agent, часто веб-браузер или веб-клиент
пользователь (user)
тоже, что и читатель
вес (weight)
приоритет правила

В тексте спецификации одинарные кавычки ('...') указывают на выдержки из HTML или CSS.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Стандартные события

ОБРАТИТЕ ВНИМАНИЕ: события для некоторых HTML элементов , которые описаны здесь поддерживаются некоторыми версиями Netscape и более поздними версиями Internet Explorer Чтобы использовать браузер v4. 0 создавая сценарии, удостовертесь, что ваши потенциальные клиенты используют требуемую версию браузера, или они были перенанаправлены соответственно на нужную страницу.

onclick

Событие   onclick   может использоваться, чтобы выполнить функции сценария, когда пользователь нажимает на специфический элемент. Internet Explorer 4.0 поддерживает это событие почти для всех HTML элементов, в то время как Netscape и более ранние версии Internet Explorer поддерживают это только в некотрых элементах (типа links, image map и т.д.). См. порядок событий onmouse* для деталей .

ondblclick

Cобытие  ondblclick   происходит, когда пользователем осуществляются двойные щелчки на специфическом элементе (почти все элементы для Internet Explorer 4.0). Например, следующая ссылка не отвечает на одиночно нажимание - нормальным способом, но двойное нажатие направляет к новому документу.

Это событие для Internet Explorer 4.0 и выше и Netscape 4.0 и выше.

Netscape 3.0 полностью поддерживает onclick событие и , но не поддерживает ondblclick событие, поэтому для пользователей Netscape 3.0 , ссылка будет полностью мертва. Пользователи Internet Explorer 3. 0x получили бы ошибку создания сценария.

 <A HREF="other_page.htm" onclick="javascript:return false" ondblclick="self.location.href ='other_page.html'">ссылка</A>

Попробуйте (обратите внимание, что ссылка не будет направлять куда-нибудь, даже на двойном нажатии, но представит сообщение ).

ссылка-пример

Обратите внимание, что используются системные параметры настройки мыши, чтобы определить различие между одиночным и двойным щелчком.

ondragstart

Иногда, пользователь пытается перетащить элемент, куда-нибудь в другое место. Как простой пример, следующий текст ссылки изменится, когда вы попытаетесь перетащить ссылку.

Попробуйте: Drag me

Обратите внимание: С целью этого примера, заданное по умолчанию действие перемещения было отменено. Нормальное действие должно было бы загрузить документ, вызванный ссылкой .

onhelp

Cобытие  onhelp   обратите внимание, что объект должен в настоящее время быть выделенным. Простой пример, следующая ссылка отвечает только на двойное нажатие (см.  ondblclick  выше), и обеспечивает сообщение 'справки' озадаченному пользователю, который один раз нажимает ссылку ( в том случае если он нажимает клавишу F1 что бы получить ответ почему ничего не произошло после нажатия на ссылку).

<A HREF="other_page.htm" LANGUAGE="Javascript" onclick="return false" onhelp="alert ('Дважды нажмите на ссылку, что бы открыть документ.');return false" ondblclick="self.location.href= 'other_page.htm'">ссылка</A>

Попробуйте: Ссылка-пример

onkeydown

Cобытие  onkeydown  Используется при нажатии и держании клавиши.

onkeypress

Cобытие  onkeypress Используется при нажатии клавиши.

onkeyup

Cобытие  onkeyup Используется при отпускании клавиши.

KeyCode

ВНИМАНИЕ: свойство KeyCode  содержит символ клавиши (UNICODE) , которая была нажата, чтобы заставить события  onkeypress  ,   onkeydown   или   onkeyup   работать.

onmousedown

Cобытие  onmousedown , когда пользователь нажимает кнопку на мыши.
См. порядок событий onmouse* для деталей .

onmousemove

Событие onmousemove  происходит всякий раз, когда мышь пользователей перемещается поверх некоторого места в документe. Обратите внимание, при исполнении события onmousemove  , необходимо отменить так называемое событие bubbling , чтобы все работало правильно. Например, рассмотрите:

 <P onmousemove="top.status='Обычный текст.'">Вот например немного <EM onmousemove="top.status='КУРСИВ.'">курсивчиком </EM> написано</P>

Здесь строка состояния будет подразумевать, что мышь перемещается только поверх содержания элемента <P>. Поэтому делаем так:

 <P onmousemove="top.status='Обычный текст.'">Вот например немного <EM onmousemove="top.status='КУРСИВ.';
self.event.cancelBubble=true"> курсивчиком </EM> написано</P>

Попробуйте - вторая строка отменяет событие bubbling:

Вот например немного курсивчиком написано

Вот например немногокурсивчиком написано

onmouseout

Cобытие  onmouseout  стартует , когда мышь пользователей оставляет область, определенную элементом. См. порядок событий onmouse*

onmouseover

Cобытие  onmouseover  стартует , когда мышь пользователей входит в область, определенную элементом.

onmouseup

Cобытие  onmouseup  может использоваться, чтобы выполнить функции сценария после того, как мышь пользователей была нажата и отпущена - противоположность события  onmousedown . порядок событий onmouse*

onselectstart

Cобытие  onselectstart  исполняется всякий раз, когда пользователь начинает выбирать некоторый текст, который является содержанием элемента. Например, выбор любого из текста в белом параграфе ниже заставляет изменяться его стиль.

Выбор любого элемента из этого текста заставит изменять его цвета ... иногда дико

Порядок событий onmouse*

Здесь представлена последовательность выполнения событий.

Следующие события мыши, для которых не требуется щелчок любых кнопок мыши, происходят в таком порядке:

onmouseover
onmousemove
onmouseout

Для мышкиных событий связанных с нажатием кнопки порядок следующий:

onmouseover
onmousemove
onmousedown
onmouseup
onclick
ondblclick
onmouseout

Так что любые функции сценария, выполненные событиями  onmousedown  или  onmouseup  произошли бы перед событиями  onclick  или  ondblclick  .


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Стандартные МЕТОДЫ
Ниже перечисленные методы поддерживаются Internet Explorer`ом 4.0 На самом деле их больше, чем описано здесь, но на мой взгляд, я имею в виду - для меня они не пригодны...
К тому же ни один из методов не поддерживается Netscape`ом или любыми предыдущими версиями Internet Explorer

click

Метод  click  может использоваться, чтобы 'нажать' определенный объект через создание сценария,вызывая событие  onClick  для специфического элемента.
Например, нажмите верхнюю ссылку 1 , и это ' нажмет ' вторую ссылку 2:

Ссылка 1
Ссылка 2

insertAdjacentHTML

Метод insertAdjacentHTML  может использоваться, чтобы вставить новый HTML элемент в документ, без того, чтобы удалить предыдущий HTML. Например:

 document.all.tags("P").item(1).insertAdjacentHTML("BeforeBegin", "<P>Вот вам новый параграф")

Добавил бы новый  <P>'Вот вам новый параграф' перед вторым параграфом в документе. Возможные значения для позиционирования:

 BeforeBegin
 AfterBegin
 BeforeEnd
 AfterEnd

Попробуйте сами...

insertAdjacentText

Метод  insertAdjacentText  идентичен методу insertAdjacentHTML , за исключением того, что это вставляет текст.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Стандартные свойства
Для любого элемента, которые поддерживаетInternet Explorer 4.0,к элементу могут быть добавлены любые атрибуты на ваш выбор, и управление их значениями доступно через создание сценариев.

className

Свойство  className  может используется, чтобы определить, или установить, какое значение из параметров style sheet`а (настройки стиля документов) данный элемент использует. Например, следующий элемент <А> использует style sheet class " warning "

<A HREF="warning.htm" CLASS="warning"> the warning </A>

Например в style sheet используя следующие значения

.warning
{ text-decoration: none; }
.warning:hover
{ color:#878187; }

мы добьемся того, что внешний вид ссылки будет без подчеркивания, а при on mouse over появится подчеркивание, более того сменится цвет ссылки...Это конечно не ново...

document

Свойство  document  содержит ссылку к объекту документа, в котором содержится элемент . Через это могут быть получены, различные свойства объекта документа. Например

document.all.tags('Div')

Обращается ко всем тэгам <DIV>.

filters

Свойство  filters  из коллекции фильтров представляет совокупность Фильтров, примененную к определенному элементу. Например:

<IMG ID="logo" SRC="images/logo.gif" WIDTH="384"
      HEIGHT="154" STYLE="filter:revealTrans(Duration=3.0,
      Transition=12);VISIBILITY:hidden" >
. . .
logo.filters.item(0).Apply()
logo.style.visibility=""
logo.filters.item(0).Play()

Где к изображению применен фильтр Transition filter, который позже вызван в сценарии JavaScript.

id

Свойство  id  присваивает уникальное имя определенному элементу, что впоследствие помогает к нему обратиться...

innerHTML

Свойство  innerHTML  Позволяет вписать в любой элемент с заранее присвоенным ID Новый HTML код полностью заменив предыдущий.

language

Свойство  language  определяет язык сценария используемый для элемента. По умолчанию в Explorer`е используется JavaScript.

style

Свойство  style  определяет конкретный стиль для конкретного элемента.

title

Атрибут  title  отражает подсказку. Значения атрибута  title  обычно используются (в Internet Explorer 4.0) как ToolTips, и отображаются, когда мышь проходит над специфическим элементом. Возможно динамически изменить свойство  title  через создание сценария.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Визуальные фильтры `Dynamic HTML` для IE4

Обратите внимание: фильтры, описанные здесь поддерживаются только Internet Explorer 4.0. Визуальные Фильтры должны быть помещены в Style Sheets поскольку они применяются, используя атрибуты Style Sheet.

Визуальные Фильтры обеспечивают управление визуальными объектами. Также, через создание сценария, примененные фильтры - могут динамически меняться без перезагрузки документа. Обычно, они применяются к элементу <IMG>, но могут применяться и к элементу <DIV>, который в свою очередь могут содержать любой HTML, так что визуальные фильтры могут применяться фактически к любому содержанию. Обратите внимание, что, если они применяются к текстовым блокам ( в элементах <DIV>), элемент <DIV> должен определить атрибуты ширины и высоты Style Sheets .

Список визуальных Фильтров:

Фильтр | Описание
Alpha | Различные эффекты прозрачности объекта.
Blur | Размывает и смазывает объект.
Chroma | Делает определенный цвет прозрачным.
DropShadow | Копирует и сдвигает объект в виде тени.
FlipH | Горизонтальное зеркальное изображение объекта.
FlipV | Вертикальное зеркальное изображение объекта.
Glow | Adds radiance around the outside edges of the object.
Gray | Уничтожает цвета - делает ЧБ объект.
Invert | Название говорит само за себя.
Light | Осветляет объект.
Mask | Выделяет объект.
Shadow | Еще один вид тени(у меня не работают др. параметры).
Wave | Создает волнистый эффект.
XRay | Что-то типа Invert`а и Gray`я.

Internet Explorer так же поддерживает Transition Filters (Reveal и Blend transitions) фильтры появления и смешивания объекта.

Основной синтаксис применения фильтров:

 STYLE="filter:ИМЯ_ФИЛЬТРА(ПАРАМЕТР1, ПАРАМЕТР2...)"

Параметры для каждого фильтра определяются свои собственные они описаны далее.

alpha

Визуальный фильтр Alpha может использоваться, чтобы установить прозрачность объекта - или всего изображения целиком, или области градиента.

STYLE="filter:Alpha(Opacity=opacity,
     FinishOpacity=finishopacity, Style=style, StartX=startX,
     StartY=startY, FinishX=finishX, FinishY=finishY)"

Opacity
Уровень прозрачности, 0-100, где 0 прзрачно, 100 непрозрачно
FinishOpacity
Конечный уровень прозрачности, 0-100, где 0 прзрачно, 100 непрозрачно
Style
принимает значения 0 (uniform), 1 (линия), 2 (круг) or 3 (прямоугольник)
StartX
координатаX начала градиента
StartY
координата Y начала градиента
FinishX
координата X конца градиента
FinishY
координата Y конца градиента

blur

Фильтр Blur создает впечатление движения путем размытия объекта.

 STYLE="filter:Blur(Add = add, Direction = direction, Strength = strength)"

Add
Добавляет размываемый объект к размытому ( любое целое значение, 0 - не добавляет)
Direction
0 - 315 с шагом в 45 - определяет направление размытия
Strength
Целое значение, представляющее число пикселей 'глубины' изображаемого объекта

chroma

Фильтр  Chroma  делает определенный цвет рисунка прозрачным (а надо ???).

 STYLE="filter:Chroma(Color = color)"

Color
Любой цвет ( #rrggbb  ). Что бы данный фильтр работал нормально необходимо, что бы выбранный цвет присутствовал в объекте.

dropShadow

Фильтр  dropShadow добавляет силуэт объекта, смещенный в определенном направлении.

 STYLE="filter:DropShadow(Color=color, OffX=offX, OffY=offY, Positive=positive)"

Color
Цвет тени (  #rrggbb  hex ).
OffX
Горизонтальное смещение тени
OffY
Вертикальное смещение тени
Positive
Любое целое значение (не равное 0) создает тень для видимых пикселей объекта, '0' (false) - для невидимых пикселей.

flipH

Фильтр  FlipH  переворачивает объект горизонтально.

 STYLE="filter:FlipH"

flipV

The  FlipV  переворачивает объект вертикально.

 STYLE="filter:FlipV"

glow

Фильтр  Glow  добавляет сияние вокруг объекта.

 STYLE="filter:Glow(Color=color, Strength=strength)"

Color
Цвет сияния ( #rrggbb  hex ).
Strength
Интенсивность сияния, 0-100

gray

Фильтр  Gray  Уничтожает цвета - делает черно-белый объект.

 STYLE="filter:Gray"

invert

Фильтр  Invert  реверсирует оттенок, насыщенность и яркость объекта.

 STYLE="filter:Invert"

light

Фильтр  Light Делает объект как если бы на него посветили. Световые фильтры применяются, устанавливая световой источник, определенный одним из следующих методов:

AddAmbient (R,G,B,strength)
Добавляет окружающий световой источник к изображению. Окружающий свет не-направлен и освещает всю область. Солнце испускает окружающий свет. Синтаксис:

object.style.filters.Light(n).addAmbient(R,G,B,strength)

Где  R ,  G  и  B  - значения (0-255), чтобы определить цвет окружающего света , и  strength   определяет 'количество' света.

AddCone (x1,y1,z1,x2,y2,R,G,B,strength,spread)
Добавляет к изображению, источник освещения в виде конуса. Свет направленный и освещает только определенную область. Синтаксис:

object.style.filters.Light(n).addCone(x1,y1,z1,x2,y2,R,G,B,strength,spread)

Где  x1 ,  y1  Значения определяющие положение источника света,  x2  и  y2  - определяют координаты куда направлен свет,  R ,  G  и  B  - значения (0-255), чтобы определить цвет света, strength   определяет 'количество' света. и  spread определяет угол распространения (0-90, в градусах).

AddPoint (x,y,z,R,G,B,strength)
Добавляет к изображению точечный источник освещения.Точечный источник освещения - лампочка. Синтаксис:

object.style.filters.Light(n).addPoint(x,y,z,R,G,B,strength)

Где  x ,  y  и  z  координаты точечного источника освещения...
осталные параметры как у других методов

Еще несколько методов Светового Визуального фильтра:

ChangeColor (lightnumber, r,g,b, fAbsolute)
Метод  ChangeColor  изменит цвет светового фильтра, примененного к объекту.Используйте  lightnumber  что бы определить световой источник, чей цвет должен быть изменен (это - позиция в массиве Lights), r,g и b, новый цвет,  fAbsoloute  может принимать значение true (nonzero), и false (т.е. zero),на что это влияет посмотрите сами...

ChangeStrength (lightnumber, strength, fAbsolute)
Метод ChangeStrength  изменяет силу определенного светового фильтра ( крторый определяется аргументом lightnumber ) на силу определенную в параметре  strength ...

Clear
Метод  Clear  удаляет все источники света для определенного светового фильтра.

MoveLight (lightnumber, x, y, z, fAbsolute)
Метод  MoveLight  передвигает источник света (в случае AddPoint), место освещения (в случае AddCone). The  x ,  y  и  z  значения определяющие положения передвижения света, или абсолютно (  fAbsoloute=nonzero ) или относительно ( fAbsolute=false ).

mask

Фильтр  Mask  выделяет объект, как если бы вы его иыделили мышью, при этом можно задать цвет выделения. Синтаксис:

 STYLE="filter:Mask(Color=color)"

Где  Color  цвет используемый для выделения.

shadow

Фильтр  shadow  делает тень объекту. Синтаксис:

 filter:Shadow(Color=color, Direction=direction)

Color
определяет цвет тени ( #rrggbb  hex)
Direction
(0-315 с шагом в 45 градусов), определяет направление тени.

wave

Фильтр  wave  делает объект волнистым. Синтаксис:

 filter: Wave(Add=add, Freq=freq, LightStrength=strength, Phase=phase, Strength=strength)

Add
значение , определяющее, добавлен ли первоначальный объект (true, nonzero) к фильтрованному объекту или нет (false, zero)
Freq
целое значение, определяющее число волн
LightStrength
сила волны в %
Phase
определяет угловое смещение волн (т.е. 0/100% = 360 градусов, 25% = 90 градусов)
Strength
целое значение, определяющее интенсивность эффекта волны

xray

Фильтр  xray  воздействует на объект как Х-луч (?). Синтаксис:

 STYLE="filter:Xray"

Transition Filters

RevealTrans Filter

Фильтр  RevealTrans  может быть применен к любому объекту, показывая или пряча его, при этом используются различные техники. Синтаксис:

 STYLE="filter: revealtrans(duration=duration, transition=transitionshape)

Где  Duration  время перехода из одного состояния в другое. Формат записи  секунды.миллисекунды  Например 2.1 = 2 секунды, 100 миллисекунд.  Transition(переход)  может быть следующим ( названия оставлю на английском что бы не портить художественный замысел авторов...):

Значение | Описание
0 | Box in
1 | Box out
2 | Circle in
3 | Circle out
4 | Wipe up
5 | Wipe down
6 | Wipe right
7 | Wipe left
8 | Verical blins
9 | Horizontal blinds
10 | Checkerboard across
11 | Checkerboard down
12 | Random dissolve
13 | Split vertical in
14 | Split vertical out
15 | Spli horizontal in
16 | Split horizontal out
17 | Strips left down
18 | Strips left up
19 | Strips right down
20 | Strips right up
21 | Random bars horizontal
22 | Random bars vertical
23 | Произвольный эффект (любой из 23)

Внимание : Фильтр Reveal Transition лучше применять со следующими событиями:

Apply
Метод  Apply  используется, чтобы фактически применить фильтр.

Play
Метод  Play  используется, чтобы фактически запустить фильтр.

Stop
Метод  Stop  используется что бы остановить применение фильтра, и может быть вызван в любое время когда фильтр запущен.

Properties

Фильтры Reveal Transition имеют свойства status и duration . Свойство  Duration  отражает текущую продолжительность фильтра и  status  определяет значение в зависимости от текущего состояния перехода. "0" = stop, "1" = apply, "2" = play.

BlendTrans Filter

Фильтр  BlendTrans  может быть применен к объекту, что бы 'проявить' его (fade it) или 'затушить' (fade out), через определенный период времени. Синтаксис:

 STYLE="filter: blendtrans(duration=duration)"

Где  Duration  время занимаемое переходом. Формат записи  секунды.миллисекунды  Например 2.1 = 2 секунды, 100 миллисекунд.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Dynamic HTML

Возможность управлять содержанием документа внутри самого документа, т.е. все элементы внутри документа - это программируемые объекты с поддержкой для действий мыши и ввода с клавиатуры.

'Dynamic HTML' поддерживается и Microsoft`ом и Netscap`ом, версиями браузеров Internet Explorer 4.0 и Communicator 4.0.

Далее будут рассмотрены стандартные 'Dynamic HTML ' свойства, методы и события.

События Dynamic HTML

Методы Dynamic HTML

Свойства Dynamic HTML

Визуальные фильтры


Flash 4: технология XXI века

Люди, знакомые с продукцией компании Macromedia, наверняка подметили, если можно так выразиться, традицию этой фирмы Ђ выпускать новые версии своих продуктов раз в год, в строго определенное время года, совпадающее с выходом предыдущей версии продукта. Поэтому поклонники одной из самых перспективных и быстроразвивающихся технологий Интернета Ђ Macromedia Shockwave Flash Ђ с таким нетерпением ждали прихода весны, когда должна была появиться очередная версия программы Macromedia Flash. Обладатели лицензии на Flash 3 буквально забрасывали компанию Macromedia письмами в надежде стать бета-тестировщиками программы. Стать бета-тестировщиком Ђ значит получить возможность оказаться в числе счастливчиков, которые первыми смогут лицезреть продукт, узнать о его новых возможностях, изучить их и найти опровержение или подтверждение слухам, которыми к моменту выхода успела обрасти программа Macromedia Flash 4.

В самом деле, о четвертой версии Flash слухи ходили самые невероятные, и на почве этих слухов разгорались жаркие дебаты. Одни утверждали, что в Macromedia Flash новой версии будет реализована возможность создания 3D-объектов, по сложности не уступающих 3D Studio Max. Другие уверяли, что новая версия будет поддерживать абсолютно все plug-ins для Adobe

Photoshop, и туманно намекали на наличие у них именно такой версии. Третьи с горячностью опровергали и тех и других, заявляя, что Macromedia Flash 4 будет наконец-то проигрывать все форматы музыкальных файлов. Четвертые пытались убедить в том, что Flash 4 содержит все вышеперечисленные достоинства. Чем меньше времени оставалось до выхода официального релиза программы, тем фантастичнее становились слухи, жарче споры, и напряжение достигало своей высшей точки.

И вот 15 июня компания Macromedia объявила о доступности Trial-версии продукта (в магазины полная версия поступила раньше). Trial-версия, которую можно загрузить с сайта www.macromedia.com , дает тридцатидневную возможность пользования программой, после чего вам необходимо либо приобрести ее, либо забыть о ней до лучших времен. Народ толпами кинулся скачивать Flash 4, новые возможности которого превзошли все ожидания и предположения.

Какие же новшества появились во Flash 4?

Перечислять абсолютно все нововведения не имеет смысла: если вы поставите себе эту программу, сами их увидите. Остановимся только на некоторых моментах.

Во-первых, несколько изменился сам внешний вид программы. Библиотека символов содержит возможность создания “папочек”, благодаря чему теперь можно систематизировать символы по этим самым “папкам”, что немаловажно, когда создаваемый вами сайт содержит очень много разноплановых символов. Теперь более удобен, хотя и несколько непривычен, переход по сценам. Появилась возможность редактирования сцены “не сходя с места”, что существенно облегчает работу художников, которым теперь не нужно гадать, как будет выглядеть нарисованный ими предмет в сцене и впишется ли он в общую композицию. Расширены возможности рисования: можно рисовать не только квадраты с прямыми углами, но и закруглять эти углы на нужное количество точек, доводя фигуру практически до круга. Появилась возможность размытия символов, которой так не хватало многим. Причем опять же можно задать размытие нужной степени и на нужное расстояние. Можно “расширить” заливку объекта на нужное количество пикселей. По мнению многих профессиональных художников, Flash 4 стал идеальным инструментом для создания векторной графики.

Реализована поддержка музыкальных файлов формата МРЗ с их потоковым воспроизведением. В программе теперь появилась возможность вставить поле текста, в котором ваш посетитель может набирать слова, фразы и предложения. А дальше...

А дальше вас ждет самая сенсационная новость: во Flash 4 внедрен язык программирования Action Script, который, по признанию самих создателей программы, является необычным, хотя и схож с некоторыми языками программирования.

В самом деле, если в предыдущей версии программы, даже несмотря на достаточно скудный набор заложенных акций, их комбинация иногда давала поразительный и, как порой казалось, даже не предполагаемый создателями программы эффект, то теперь перед вами открываются поистине безграничные возможности. Даже те, кто в совершенстве владеет программой Flash 3, пребывают в состоянии растерянности перед возможностями Flash 4...

Как говорится в руководстве к Flash 4, если вы хотя бы немного знакомы с основами программирования, понять язык Action Script не составит для вас никакого труда.

Но если вы совсем ничего не знаете о программировании, не стоит отчаиваться. В программе представлено замечательное руководство на эту тему. Если же и английский язык вам знаком не очень хорошо, то загляните в раздел “Обучение” на сайте www.flasher.ru , где вы найдете перевод этого руководства, дополненный примерами переводчиков, который вы можете загрузить к себе на компьютер и, открыв их в программе Flash, на деле понять работу Action Script во Flash 4. На этом же сайте, являющемся на данный момент наиболее содержательным ресурсом российского Интернета по Flash 4, вы можете найти массу интересных вещей Ђ от исходного файла гостевой книги, реализованной на Flash 4, до совсем простых примеров для начинающих. Если вы совсем новичок и незнакомы с технологией Flash, обязательно прочтите весь раздел “Обучение”. Возможно, после этого программа покажется вам не сложной и запутанной, а простой и понятной.

Пройдясь по основным новшествам Flash 4, хотелось бы рассказать вам, почему большинство профессиональных веб-мастеров как за рубежом, так и в России считают Flash наиболее перспективной технологией, а также о том, что можно сделать, если приложить совсем немного терпения для изучения этой программы.

Новый Flash 4 со всем основанием претендует на то, чтобы полностью вытеснить монополиста раз
метки страниц Ђ язык HTML. Если Macromedia Flash 3 позволял делать высокотехнологичные сайты с высоким уровнем интерактивности, то Flash 4 позволяет создавать интерактивность в полном смысле этого слова.

У вас появилась возможность создавать тематические сайты и порталы, разграничивать посетителей по возрасту, полу и интересам, разрешать вход на сайт (или в определенный раздел сайта) под паролем. Давайте немного остановимся на этих возможностях. Посредством Flash 4 вы вставляете на входную страницу вашего сайта текстовое поле (или несколько таких полей: пол, возраст и интересы). Если ваш посетитель выбирает определенные параметры (например, мужчина, старше 18 лет, автомобили), он попадает в строго определенный раздел вашего сайта, который будет содержать именно то, что интересно такому посетителю. Посетитель, выбравший другие параметры (девушка, 15 лет, мода), соответственно попадет на интересный именно ему раздел сайта. Если ничего подобного на вашем сайте нет, вы можете предложить несколько ссылок в соответствии с интересами. Если вы хотите, чтобыопределенную часть вашего сайта видело ограниченное количество людей, сделайте вход под паролем, который будет высылаться на указанный адрес e-mail. Удивлены? Но это еще не все! Ничто теперь не мешает вам сделать гостевую книгу, чат и даже поиск на Flash 4. Более того, ваш посетитель может самостоятельно изменять внешний вид сайта, скорость и (или) размер анимации, то есть полностью управлять сайтом. Причем теперь не только с помощью мыши, но и с помощью клавиатуры. Кроссворды, полноценные игры, поисковые системы, забавные открытки, посылаемые адресату, полнейшая интерактивность Ђ это далеко не весь список возможностей программы.

Поскольку программа существует совсем недавно, в Сети представлено очень небольшое количество ресурсов, созданных при помощи Flash 4. В России, к сожалению, на данный момент Flash 4 активно используют пока немногие студии.

Надеемся, что по прошествии некоторого времени российскую Сеть украсят множество талантливых и интерактивных работ, сделанных с помощью технологии, которую не без основания называют “революционной технологией XXI века”.


Laws FAQ
  • Q: Кто может считаться автором?
  • Q: Что может считаться объектом авторского права?
  • Q: Что означает знак © и каков его статус?
  • Q: Какие права имеет автор?
  • Q: В каких случаях возможно использование произведение без согласия автора?

Q: Кто может считаться автором?

А: Автор - это в любом случае, физическое лицо, творческим трудом которого создано произведение

Q: Что может считаться объектом авторского права?

А: Объектами авторского права являются произведения науки, литературы и искусства, являющиеся результатом творческой деятельности, независимо от назначения и достоинства произведения, способа его выражения, а также производные произведения (переводы, обработки, аннотации, рефераты, резюме, обзоры, инсценировки, аранжировки и другие переработки произведений наук, литературы и искусства) и сборники (энциклопедии, антологии, базы данных) и другие составные произведения, представляющие собой по подбору или расположению материалов результат творческого труда.

Авторское право распространяется как на обнародованные, так и на не обнародованные произведения, в том случае если они существуют в какой-либо объективной форме.
Авторское право не распространяется на идеи, методы, процессы, системы, способы, концепции, принципы, открытия, факты, официальные документы (их официальные переводы), государственные символы и знаки, произведения народного творчества, сообщения о событиях и фактах, имеющие информационный характер.
Авторское право на произведение не связано с правом собственности на материальный объект, в котором произведение выражено.

Q: Что означает знак © и каков его статус?

А: знак © является составной частью знака охраны авторского права, состоящего из трех элементов:

  • Собственно © ;
  • имени (наименования) обладателя исключительных авторских прав;
  • года первого опубликования произведения.
Обладатель исключительных авторских прав вправе (вовсе не обязан) размещать на каждом экземпляре произведения знак охраны авторского права - отсутствие © не означает отсутствия автора или того что данное произведение (его элементы) можно свободно использовать.

Q: Какие права имеет автор?

А:

  • право признаваться автором произведения (право авторства);
  • право использовать или разрешать использовать произведение под подлинным именем автора псевдонимом либо без обозначения имени то есть анонимно (право на имя);
  • право обнародовать или разрешать обнародовать произведение в любой форме (право на обнародование), включая право на отзыв;
  • право на защиту произведения, включая его название, от всякого искажения или иного посягательства, способного нанести ущерб чести и достоинству автора (право на защиту репутации автора);
  • отказаться от ранее принятого решения об обнародовании произведения;
  • осуществлять или разрешать следующие действия:
    • воспроизводить произведение (право на воспроизведение);
    • распространять экземпляры произведения любым способом: продавать, сдавать в прокат и так далее (право на распространение);
    • импортировать экземпляры произведения в целях распространения, включая экземпляры, изготовленные с разрешения обладателя исключительных авторских прав (право на импорт);
    • публично показывать произведение (право на публичный показ);
    • публично исполнять произведение (право на публичное исполнение);
    • сообщать произведение (включая показ, исполнение или передачу в эфир) для всеобщего сведения путем передачи в эфир и (или) последующей передачи в эфир (право на передачу в эфир);
    • сообщать произведение, (включая показ, исполнение или передачу в эфир) для всеобщего сведения по кабелю, проводам или с помощью иных аналогичных средств (право на сообщение для всеобщего сведения по кабелю);
    • переводить произведение (право на перевод);
    • переделывать, аранжировать или другим образом перерабатывать произведение (право на переработку).
Исключительные права автора на использование дизайнерского, архитектурного, градостроительного и садово-паркового проектов включают также практическую реализацию таких проектов.

Q: В каких случаях возможно использование произведение без согласия автора?

А: Использование произведения допускается без согласия автора и без выплаты авторского вознаграждения, но с обязательным указанием имени автора, произведение которого используется, и источника заимствования:

  • цитирование в оригинале и в переводе в научных, исследовательских, полемических, критических и информационных целях из правомерно обнародованных произведений в объеме, оправданном целью цитирования, включая воспроизведение отрывков из газетных и журнальных статей в форме обзоров печати;
  • использование правомерно обнародованных произведений и отрывков из них в качестве иллюстраций в изданиях, в радио- и телепередачах, звуко- и видеозаписях учебного характера в объеме, оправданном поставленной целью;
  • воспроизведение в газетах, передача в эфир или сообщение по кабелю для всеобщего сведения правомерно опубликованных в газетах или журналах статей по текущим экономическим, политическим, социальным и религиозным вопросам или переданных в эфир произведений такого же характера в случаях, когда такие воспроизведение, передача в эфир или сообщение по кабелю не были специально запрещены автором;
  • воспроизведение в газетах передача в эфир или сообщение по кабелю для всеобщего сведения публично произнесенных политических речей обращений, докладов и других аналогичных произведений в объеме, оправданном информационной целью. При этом за автором сохраняется право на опубликование таких произведений в сборниках;
  • воспроизведение или сообщение для всеобщего сведения в обзорах текущих событий средствами фотографии, путем передачи в эфир или сообщения для всеобщего сведения по кабелю произведений, которые становятся увиденными или услышанными в ходе таких событий, в объеме, оправданном информационной целью. При этом за автором сохраняется право на опубликование таких произведений в сборниках;
  • воспроизведение правомерно обнародованных произведений без извлечения прибыли рельефно-точечным шрифтом или другими специальными способами для слепых, кроме произведений, специально созданных для таких способов воспроизведения.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Обмен ссылками (reciprocal links).

Сама концепция World Wide Web подразумевает наличие на веб-страницах ссылок на другие сетевые ресурсы. Поэтому обмен ссылками существует практически с момента появления всемирной паутины. Принцип достаточно прост: Вы помещаете у себя ссылку на дружественный сайт (как правило сходный по тематике) и взамен на Ваш ресурс также ставят ссылку.
Текстовая ссылка с положительной аннотацией часто работает гораздо эффективнее, чем баннер. К баннеру все уже привыкли относиться как к рекламе, тогда как текстовая ссылка означает, что человек, разместивший ее у себя, действительно считает данный ресурс заслуживающим внимания своих читателей.

Существует ошибочное мнение, что наличие на Вашей странице ссылок на другие веб-ресурсы приведет к дополнительному оттоку с нее посетителей, которых Вы, как правило, пытаетесь удержать у себя как можно дольше. Да, если Вы заинтересованы исключительно в однократных визитах на свой сервер, возможно ссылки наружу будут только помехой. Но если Вас интересую повторные посещения, то не следует придерживаться подобной точки зрения. Посетитель все равно рано или поздно покинет Ваш сайт, так что в Ваших интересах позаботиться о его следующем месте назначения, как о гарантии повторных визитов к Вам.

Где на своей странице помещать ссылки на другие веб-ресурсы, решать Вам. Обычно создается специальная страница, содержащая подборку ссылок на заданную тематику. Другой способ - помещать ссылки в контексте непосредственно на подходящих для этого страницах. Когда ставите ссылку желательно использовать внутри тега href ключ target=new, например, так:

<a target=new href="http://www.promotion.aha.ru">
Tim promotion - Энциклопедия интернет-рекламы</a>

В этом случае страница, на которую указывает ссылка, откроется в новом окне браузера и пользователь фактически Вашу страницу не покинет.

Грамотно подобранные ссылки на сайты со сходной тематикой, снабженные Вашей аннотацией как специалиста в данной области, создают Вашему веб-ресурсу дополнительную ценность в глазах посетителей. Именно представительная подборка ссылок часто является причиной занесения Вашей страницы в "закладки" посетителей.

С кем стоит и с кем не стоит обмениваться ссылками.

Разумеется, не стоит ставить ссылки на своих прямых конкурентов. Очевидно, что это может увести у Вас существующих или потенциальных клиентов. Но я уверен, что Вы сможете найти ресурсы, посещаемые Вашей целевой аудиторией, но одновременно не представляющие интересов Ваших прямых конкурентов.

Лучшим вариантом здесь являются так называемые "симбиозные ссылки". Например, если Ваша компания является интеренет-провайдером, предоставляющим услуги хостинга веб-страниц, Вы можете разместить ссылки на некоторые студии веб-дизайна, которые эти самые веб-страницы и создадут для Ваших потенциальных клиентов. И наоборот, дизайн-студия может рекламировать у себя на странице Вас как надежного поставщика хостинга для веб-сайтов своих клиентов.

После того, как Вы определитесь, с какого типа сайтами Вам будет выгодно обмениваться ссылками, можно приступать непосредственно к поиску оных.

I. Прежде всего узнайте, кто в настоящий момент уже поставил на Вас ссылки. Если Ваш ресурс по настоящему полезен и популярен, возможно на него ведут не один десяток ссылок, о которых Вы и не подозреваете. Проверить их можно двумя способами:

1 способ (рекомендуемый)
Посмотреть по реферер-логам, которые генеряться на Вашем сервере. Если у Вас нет доступа к подобной статистике, советую незамедлительно договориться со своим провайдером и установить программу для обработки данной статистики. Анализ аксес- и реферер лога является эффективным инструментом анализа посещаемости веб-сайта, поведенческой линии посетителей, эффективности рекламных кампаний и т.д. В ближайшее время я помещу на мой сервер статью, посвященную данному вопросу.

2 способ
Можно воспользоваться поисковыми системами.
Alta Vista - http://www.altavista.digital.com
при запросе link:companyname.ru в результатах поиска Вы получите все проиндексированные системой страницы, содержащие ссылки на сервер www.companyname.ru или на любую из его внутренних страниц.

Excite - http://www.excite.com
Просто наберите в строке поиска Ваш URL (желательно произвести поиск как с использованием в начале www, так и без оного.

Hot Bot - http://www.hotbot.com Наберите в строке поиска Ваш URL. И поменяйте установленный по умолчанию "all of the words" на "links to this URL". Разумеется, данный способ скорее всего не позволит Вам узнать все ссылки на Вашу страницу, т.к. покажет лишь те страницы, что уже проиндексированы данной поисковой системой.

Составьте список сайтов, которые поставили на Вас ссылки. Прежде всего данные страницы могут стать для Вас источником информации о веб-сайтах, сходных с Вашим по тематике. Здесь Вы сможете найти союзников и разыскать своих конкурентов.

Если по анализу реферер-лога Вы увидите, что определенные ресурсы вносят значительную лепту в траффик Вашего сервера, возможно с их владельцами стоит познакомиться поближе. Здесь возможны варианты. Вы можете их просто попросить или предложить деньги, например, за следующее:

· поставить на Вас ссылку на более видном месте;
· отрекламировать Ваш сайт в их списке рассылки или еще где-либо;
· разместить у них Ваши баннеры;
· убрать ссылки на веб-страницы Ваших конкурентов и т.д.

Действуйте по обстановке. Если это не будет ущемлять Ваших интересов (как, например, в случае с конкуренцией) обязательно поставьте ответную ссылку на них.

II. После определения сайтов, уже имеющих ссылки на Вашу страницу, самое время заняться поиском тех, кто еще не совершил сей благородный поступок.
Поиск сайтов с аналогичной тематикой или других веб-ресурсов, где появление ссылки на Ваш веб-сайт желательно (см. выше принцип симбиоза), обычно осуществляется по следующим направлениям:

· через поисковые системы и каталоги;
· на уже известных Вам специализированных серверах (обычно в списках ресурсов);
· на страницах, которые Вы нашли на первом этапе (см. выше);
· конечно, Вы еще можете предложить желающим произвести обмен ссылками со страниц своего сервера, но все подобные предложения, на которые я наталкивался до сих пор в Интернете, выглядели довольно непрофессионально, возможно Вам удастся облечь данное предложение в достаточно респектабельную форму.

III. По результатам поиска составляется и соответствующим образом обрабатывается список содержащий:

· URL сервера;
· его описание, по возможности с оценкой посещаемости, авторитетности и т.д.;
· имя контактного лица и его e-mail.

После того как Вы начнете рассылать письма с предложением по обмену ссылками (или покупки ссылок), в данный список добавляйте информацию о том:

· когда и какого вида был послан запрос;
· какой ответ вы получили (если он вообще был);
· каков статус данного предложения.

Если переговоры (переписка) увенчались успехом и на Ваш веб-сайт поставили ссылку, не стоит забывать о данном ресурсе навсегда. Желательно раз в месяц наведываться и проверять ее наличие, ссылки имеют таинственную способность внезапно исчезать и больше не появляться.

Как Вы будете договариваться с веб-издателями об обмене ссылками, какие аргументы использовать, Вы должны определить сами. Здесь невозможно дать общих рекомендаций, т.к. для каждого конкретного случая необходим индивидуальный подход.
И еще. Если Вы найдете веб-ресурс, который бесспорно будет полезен Вашим читателям, и Вы поместите на него ссылку, даже не требуя взаимности, не забудьте уведомить об этом владельцев этого сайта. Как правило, ни один из веб-издателей не будет возражать, если Вы добавите у себя ссылку на его веб-ресурс. Но советую Вам проявить "излишнюю" вежливость и попросить у него на это разрешение. Очень велика вероятность, что он, увидев ценность Вашего сайта для своих читателей, отплатит Вам тем же.

Web-rings

Другой формой обмена ссылками является организация web-ring.
Сайты определенной тематики - участники веб-кольца ставят ссылки друг на друга по кругу т.е. 1>>2>>3>>..>>N>>1 Данная концепция на мой взгляд не является столь же удобной, как аннотированный список ссылок на страницы, но если подобное "кольцо" существует для сайтов, сходных с Вашим по тематике, желательно стать его участником.
В международной сети существует одноименный сервис http://www.webring.com, который взял на себя хлопоты по поддержке существующих (сейчас их там уже более 40 000) и созданию новых "колец".

Покупка ссылок

Как я упоминал выше, одной из возможностей получить ряд ссылок на свой сервер является их покупка. Соответственно, если Ваша страница является достаточно авторитетным и посещаемым веб-изданием, ориентированным на определенный круг читателей, то Вы можете заработать, помещая у себя ссылки на рекламодателей, готовых за данную услугу платить. Вы можете как самостоятельно заняться поиском и переговорами по данному вопросу, так и поручить хлопоты специализированным службам.

Компания Submit It (купленная недавно баннерной сетью LinkExchange) запустила систему с говорящим за себя названием Click Trade www.clicktrade.com. Данный сервис позволяет рекламодателям находить для себя веб-ресурсы, готовые размещать у себя за деньги ссылки. Оплата идет за количество переходов на сайт рекламодателя по конкретной ссылке. Стоимость перехода определяется непосредственно самим рекламодателем. Комиссию в размере 30% забирает за данный сервис сам Click Trade.

Недавно появилась служба, организующая обмен ссылками между российскими сайтами - PSLink http://pslink.da.ru/, пока правда все выглядит непрофессионально и находится в зачаточном состоянии.

И в заключении вернемся к эффективности бесплатного обмена ссылками. Да, это достаточно кропотливая и длительная работа, но в случае успеха результаты могут превзойти Ваши ожидания. Несколько десятков ссылок с "правильных мест" по своей эффективности могут сравниться со среднемасштабной баннерной кампанией. Причем в отличие от размещения баннеров, платить Вам за это не придется, и работать они будут на Вас постоянно. А с ростом популярности веб-ресурсов, поставивших на Вас ссылки, их эффективность будет неуклонно возрастать.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Создание Meta-тегов

Meta-теги и их использование.
Поисковые серверы при регистрации Web-сервера, сайта или страницы используют информацию, хранящуюся в специальных тегах МЕТА, которые помещаются между тегами и большинство начинающих Web-мастеров и Web-дизайнеров не уделяют особого внимания этим тегам - и зря. Теги МЕТА не видны при просмотре документа и заметно увеличивают его размер, однако без них не обойтись. Рекомендации по составлению Мета-тегов.
Правильность составления МЕТА тегов делает возможным, чтобы ваш сайт было легко отыскать при помощи поисковых систем среди огромного числа других сайтов! Для этого необходимо следовать следующим рекомендациям:
1. Длина содержимого тегов МЕТА "desctiptions" не должна превышать 200 символов, a "keywords" - 1000 символов. Это связано с тем, что поисковые серверы, как правило, используют именно такие величины при индексировании информации о вашем сайте. Можно получить отрицательный результат, если поисковые машины отбросят часть описания, превышающего установленные нормы, в результате чего в каталог попадет не вся желаемая информация, либо сервер просто отвергнет регистрацию вашего сайта.
2. Нужно включить все наиболее часто используемые в вашем сайте термины и их синонимы в тег "desctiptions". Можете включить и термины, не имеющие отношения к вашим страницам, однако в Internet такое поведение считается некорректным. В список ключевых слов можно вставлять и фразы. В этом случае у вашего сайта повышается шанс попасть в самое начало списка, выданного поисковым сервером (в случае совпадения фразы с той, что вводит пользователь).
3. Для увеличения рейтинга сайта и попадания его в верхнюю часть поискового списка необходимо каждый термин, включаемый в описание, повторять около 3 раз. Поисковые машины, как правило, работают с тегами МЕТА, а в случае их отсутствия - уже с содержимым страницы. Иногда это приводит к появлению сайта в списке top 10, но может привести ик отрицательному результату (перед регистрацией проверьте поисковую машину, дав запрос и просмотрев ключевые слова у сайтов, стоящих на верхних позициях)
4. Используйте в описаниях и списке терминов как можно больше синонимов.
5. Не используйте в описаниях и в списке терминов понятия и слова, слабо связанные друг с другом.

Cостав МЕТА-тегов
 1) Для того, чтобы при просмотре документа броузер брал свежую версию документа, а не версию из кеша используется

<META HTTP-EQUIV="Expires"
Content="Mon, 28 Mar 1999 00:00:01 GMT">

2) Для определения броузером языка, на котором написана страница используется

<META HTTP-EQUIV="Content-Type" Content="text/html;
Charset=Windows-1251">

3) Если неопытный Web-мастер скопирует Вашу страницу, в которой оставит следущий тег, то при заходе на эту страницу броузер автоматически перейдет на Вашу страницу

<META HTTP-EQUIV="Refresh" Content="1;
 url=http://адрес_вашей_страницы">

4) Для указания имени автора и дополнительной информации о нем используется

<META Name="autor" Content="Ваши Ф.И.О., E-Mail...">

5) Для указания авторких прав используется

<META Name="copyright" Content="Ваша_фирма...">

6) Для указания ключевых слов и терминов используется

<META Name="keywords" Content="Ваши_Ключевые слова">

7) Для указания краткого описания используется

<META Name="description"
Content="Описание_Вашей_Страницы">

Полагаясь на собственные исследования в этой области, а также на профессиональных разработчиков в этой области мы рекомендуем вам при наличии большого количества страниц создать теги мета-информации с различным содержанием, описывающем содержания данной конкретной страницы или раздела.

Итак, создаем Мета-теги

Внимание: чтобы приводимая ниже программа работала, данную страницу необходимо открыть в Internet Explorer 4+ или Netscape Navigator 4+.
Сделать это Вы сможете, предварительно распаковав ее в нужную папку.

Необходимо заполнить все поля
Название страницы
Автор страницы
Электронный адрес
Ключевые слова
(через запятую)
Описание страницы

Остается только скопировать созданный код и вставить его между тегами <HEAD> и </HEAD> в код вашей странички.


Типовые ошибки HTML

Оговорки.

Я не пытаюсь в данной статье раскрывать смысл использования каких-либо тегов и (или) комментировать спецификации HTML 3.2 - 4.0. Статья строится по принципу: ошибочный (не совместимый) код - комментарий к коду. Неточности кода и его несовместимость рассмотрны применительно к современным броузерам: NN4.05 и IE 4.01

Типовые ошибки при создании страниц. Часть 1.

Я достаточно долго размышлял над вопросом построения этого раздела. Уж больно объемный материал. В итоге, Вам, уважаемый читатель, предлогается вместе со мною препарировать серию страниц "неизвестного Автора".
Порядок препарирования, - от простого к сложному.
Комментарии ошибок в коде помечены красным цветом, комментарии не совместимых тегов и расширений - зеленым. Нажав на строчку ошибочного кода Вы переместитесь к закладке с комментарием.
Итак, препарируем простейшую страницу без таблиц, фреймов и скриптов.

Код страницы:

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=KOI8">
<title>Home Page</title>

</head>

<body
background=c:\..\..\images\buttonweb.gif
bgproperties="fixed"
      topmargin="1" leftmargin="1">

<divalign=center><center>
<palign="center">
<fontface="Xenia">
<big><big><em>
"Привет ПИПЛЫ!"
</big></big>
</em></font>

<p>Это моя фотка в возросте 15 лет.
<ahref="myfacebig.htm"><imgsrc="/fase.gif" align="center">

<p>Мои линки.
<ul>
<li><b><font face="AdverGothic">Крэки</font></b>
<li><b><font face="AdverGothic">Тут лежит классный софт!</font></b> <li><b><font face="AdverGothic">МП-3 - рулезз!</font></b>
<li><b><font face="AdverGothic">Примочки к мастдаю!</font></b>
</ul>

</center></div>
</body>
</html>

Ошибки в атрибутах тега <body>(Возвратиться к коду)

На данной странице в качестве бэкграунда бэкграунд должен использоваться внешний гиф. Ошибка в описании пути. Путь к файлу описан относительно диска c:\ на локальной машине. Такой бэкграунд не будет отображен у клиента. Правильный код:
background="../../images/buttonweb.gif"

Не совместимые атрибуты.(Возвратиться к коду)

Не совместимый код: bgproperties="fixed" topmargin="1" leftmargin="1"
NN не понимает ракие расширения, для совместимости кода необходимо дополнительно указать MARGINWIDTH="1" MARGINHEIGHT="1"
Для корректного отображения страницы в броузерах с отключенной графикой желательно также указывать цвет бэкграунда,. bgcolor=""

Избыточное форматирование.(Возвратиться к коду)

Особенность HTML редактора Front Page. Достаточно использовать один из тегов, либо <div align="center"> либо абсолютно идентичный ему <center>

Избыточное форматирование в теге<p> (Возвратиться к коду)

Данном случае текст уже отцентрирован и нет необходимости добавлять атрибуты с этой целью. Имеет смысл только использование Align="left(right)".
Тег <p> в этих строках не закрыт (</p>), что приведет к неадекватному отображению интервалов между параграфами в NN и IE. Хороший стиль требует закрывать тег <p>

Использование Font Face.(Возвратиться к коду)

Использование нестандартных шрифтов на страницах приведет к тому, что страница будет или неудобочитаема, или не читаема вовсе. Для совместимости с броузерами с других платформ необходимо использовать вот такую конструкцию:
<font face="Arial, Arial Cur, Helvetica">

Избыточное форматирование стиля шрифта.(Возвратиться к коду)

Конструкция <big><big> избыточна. Предпочтительно использовать для выделения заголовков <H2></H2>, или комбинацию <base font="2" > - сразу за <body> и относительный размер шрифта <font size="+3">.

Квотирование в тексте контента.(Возвратиться к коду)

Использование кавычек в таком виде ("") в контенте не допустимо. Необходимо указывать &qwote; Привет Пиплы! &qwote;.

Нарушение порядка закрывания тегов.(Возвратиться к коду)

Теги должны закрываться с учетом порядка их вложения. Правильная конструкция
<font><b><em> … контент …</em></b></font>

Отсутствие атрибутов в теге<image> (Возвратиться к коду)

Для описания картинок на странице необходимо использовать следующие атрибуты:

width="", height=""
Если их не указать, то страница не будет загружаться до тех пор, пока не загрузится до конца картинка.

border=""
Отсутствие этого атрибута приведет к тому, что вокруг картинки, если "навесить" на нее гиперссылку, появится окантовка с цветом гиперссылки

alt=""
Отсутствие данного атрибута сделает информацию о картинке недоступной в браузерах с отключенной графикой.

Правильный код описания картинки выглядит вот так:
<img src="face.gif" width="454" height="341" alt="Это мое фото" border="0">

Незакрытый анкер, и атрибуты анкера.(Возвратиться к коду)

Анкер подлежит обязательному закрытию. Хороший стиль программирования подразумевает следующее построение:
<a href="mybigfase.htm" target="_self">… контент ... </a>

Примечание.
Указание тега <base target=""> позволит в дальнейшем не указывать в расширении анкера TARGET.
В случае, если анкер закрыть с переводом строки, или не закрыть его вовсе то NN отрендерит такой код с маленькой черточкой, что сильно заметно на страницах со светлым фоном.

Форматирование списков.(Возвратиться к коду)

При форматировании списков необходимо закрывать тег <li>, форматирование текста в каждой строке списка имеет смысл только в случае выделения строк. Правильный код.

<ul><font color="" size="">
<li>… контент …</li>
<li>… контент …</li>
<li>… контент …</li>
</font></ul>

Препарирование закончено.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

Об эффективном расположении элементов графики и текста на экране.

Теория:

Зачастую, после создания сайта, к автору начинают идти письма по улучшению дизайна, которые часто противоречат друг другу. Не знаешь, что и думать, если не знать теории НЛП. Коротко, в НЛП выделяют три типа людей:

  1. Мыслящих преемущественно зрительными образами (Визуалы).
  2. Мыслящих проговариванием мыслей внутри (аудиалы+дигиталы).
  3. Мыслящих преемущественно ощущениями (кинестетики).

В нашей стране соотношение примерно такое -

30-50% Визуалов, 5% Аудиалов, 30-60% Кинестетиков.

В интернете соотношение, научно не известно, но по моим предположениям больше визуалов, чем больше по стране.

Практика:

Как это применимо к WEB Дизайну?

Замечено, что расположенные в разных частях экрана элементы лучше действуют на определенных людей. Ниже дается идеальное расположение элементов, что-бы зацепить мах. количество людей.

Если логотип вашего сайта хорошо известен посетителям, то он будет хорошо работать на вас слева сверху(для примера: логотип Intel Inside). Если же логотип вашего сайта малоизвестен, то имеет смысл разместить его справа сверху (это будет способствовать его запоминанию посетителями).

Все верхнее пространство экрана лучше отдать графическим решениям, фотографиям, и т.д.

Аудиальные компоненты обычно мало важны, если это не сайт посвященный музыке.

Расположение в средней части экрана. Слева Известные, Справа малоизвестные. Возможно из аудиальных компонентов разместить Слоган (Пример: Имидж ничто, жажда все или Эффективная коммуникация ключ к успеху).

Снизу экрана желательно выделить две зоны:

Справа внизу- где лучше дать числовую, количественную информацию и т.д. (Пример - Адрес или цены).

Слева внизу - Вот здесь нужно, что бы те фразы, которые там написаны, вызывали у посетителя Вкус, Чувства, Ощущения. Картинки должны быть в том же духе. (Пример: Кружка Пива, и надпись: Почувствуй себя настоящим студентом.)

Кстати, подозреваю что баннер расположенный в этом месте, с надписью: Нажми меня, будет иметь большой % нажатий (CTR).

И в заключение маленький подарок:

Нами был разработан для данной зоны такой слоган: Учишься в школе - Жми Сюда.

Вы можете свободно применять его, слегка изменив для своих нужд первую часть. При расположении такой кнопки снизу-справа она даст замечательные результаты. У нас получился CTR 7-12%. Что очень неплохо.


- Энциклопедия создания сайтов для начинающих. Создание и поддержка New Web-Masters Club.

О элементах восприятия и цветах.

Принято считать, что в находясь в интернете большинство людей переходит в визуальное состояние(т.е. состояния просмотра того что на экране). Исходя из этого и написана данная статья.

Теория:

Давно было замечено что различные цвета по разному действуют на человека. Мы вполне допускаем что цвет, который нравится одному, совсем не понравится другому. И вместе с тем замечено что некоторые цвета являются общими для создания определенных эмоциональных состояний человека. Это не будет обязательным для всех, есть и исключения. Здесь и ниже мы будем ориентироваться на большинство.

Нам важно так создать оформление сайта, чтобы оно благоприятно влияло на отношение посетителя к сайту:

Мы будем использовать оттенки синего, голубого, насыщ. желтого, оранжевого, белого, зеленого.

На некоторые цвета у большого количества людей преобладает негативная реакция:

Черный, Красный, Серый, Коричневый, эти цвета при доминировании на сайте вызовут отрицательную оценку сайта.

При умелом сочетании цветов влияние негативной составляющей этих цветов можно нивелировать(пример: Синий-черный) или даже превратить в плюсы. Того эффекта зачастую можно достичь при плавных переходах цветов. Но это тема одного из будущих набросков.

Также все зависит от того какую атмосферу вы хотите создать на своем сайте, если сайт посвящен фильмам ужасов, смело используйте гаммы черного, серого, коричневого и красного цветов. Если же вы хотите чего-то другого то обратите внимание на позитивные и ресурсные цвета.

Помните, что во многом отношение к сайту зависит от тех цветов, которые вы используете при его оформлении.

Элементы восприятия играют также большую роль при оформлении сайта.

Считается, что одновременно глаз человека может воспринимать около 7±2 однотипных элементов.

В нашем частном случае, пункты меню сайта будут как раз именно такими элементами. Именно поэтому наиболее эффективным меню будет состоять из 5-7 пунктов. Максимум(не оптимум) из 9. Такое количество ссылок в меню позволит пользователю быстро привыкнуть и запомнить меню вашего сайта, что является одним из признаков, что вероятно он станет постоянным посетителем сайта. Выходом из данной ситуации вполне может стать разбивка элементов меню на несколько семерок ссылок. Выделение некоторых ссылок другим шрифтом(цветом) также улучшит ситуацию.


Взаимодействие нескольких форм | WebClub

Взаимодействие нескольких форм

Введение
Скрытые поля формы
Включения на стороне CGI
Cookies
Заключение

Введение

Одним из недостатков HTTP протокола является невозможность сохранения состояния, или, другими словами, отсутствие поддержки транзакций, когда несколько последовательных действий пользователя объединяются в одно законченное действие.

Хрестоматийным примером при обсуждении этого вопроса является система заказа товаров (виртуальный магазин). Не будем изменять традициям, рассмотрим данный пример.

Представьте себе Веб-систему, которая предлагает каталог товаров, возможность помещения этих товаров в "виртуальную корзину", подсчет общей стоимости покупки, оформление заказа. Весь этот процесс невозможно осуществить в пределах одной страницы, поэтому необходимо придумать способ взаимодействия нескольких сценариев друг с другом, для того чтобы сохранять текущий контекст, позволяющий отслеживать покупателей на всем их пути по магазину.

Существует несколько стратегий, позволяющих осуществить сохранение контекста. Ими являются:

  • Скрытые поля. Используя скрытые поля, можно включать невидимую информацию в форму, посылаемую следующему CGI-сценарию.

  • Включения на стороне CGI. Этот механизм позволяет вставлять в документ специальные тэги, незаметно для пользователя передающие CGI переменные.

  • Cookies. Данный механизм позволяет сохранять определенную информацию на стороне клиента.

Существует также четвертый способ - создать специальный cookie-сервер, с которым будет взаимодействовать каждый CGI-сценарий. Данный способ мы рассмотрим в следующий раз.

Скрытые поля формы

Скрытые поля позволяют хранить "невидимую" информацию в форме документа. Такие поля не отображаются браузером, но это вовсе не означает, что информация спрятана, любой может увидеть ее, вызвав опцию браузера "View Source". Таким образом скрытые поля нельзя использовать в целях безопасности, а можно только в целях прозрачной передачи информации.

Приведу пример определения двух скрытых полей формы:

<form action="/cgi-bin/sendmail.pl" method="post">
...
<input type="hidden" name="mail_to"
value="andrey@novikov.com">
<input type="hidden" name="subject"
value="Test letter">
...
</form>

При посылке формы, данные, заключенные в скрытые поля, кодируются и посылаются серверу точно так же, как и данные других полей. Для CGI-сценария нет никакой разницы между скрытыми и видимыми полями.

Простейшим способом использования скрытых полей для сохранения контекста является запись данных формы в скрытые поля следующей формы. Приведу пример первой формы:

<form action="/cgi-bin/firststep.pl" method="post">
Введите Ваше имя: <input type="text" name="realname">
<br>
Введите Ваш адрес: <input type="text" name="email">
<br>
<input type="submit" name="Send"
value="Следующий шаг">
<input type="reset" name="Reset" value="Очистить">
</form>

При посылке этой формы на сервер соответствующий сценарий динамически генерирует следующую форму, содержащую скрытые поля:

<form action="/cgi-bin/secondstep.pl" method="post">
<input type="hidden" name="realname"
value="Андрей Новиков">
<input type="hidden" name="email"
 value="andrey@novikov.com">
Город проживания: <input type="text" name="city">
<br>
<input type="submit" name="Send"
value="Следующий шаг">
<input type="reset" name="Reset" value="Очистить">
</form>

И так до самого конца. Достоинством такого подхода является отсутствие необходимости временных файлов и "волшебных cookies". С другой стороны недостатком такого подхода авляется снижение производительности при обработке форм с большим количеством полей.

Приведу пример скрипта, который генерирует форму, вставляя в нее все полученные параметры в ввиде скрытых полей:

#!/usr/local/bin/perl
print "Content-type: text/html\n\n";

read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
   local($name, $value) = split(/=/, $pair);
   $name =~ tr/+/ /;
   $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
    hex($1))/eg;
   $value =~ tr/+/ /;
   $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
    hex($1))/eg;
   $value =~ s/<!--(.|\n)*-->//g;
   $FORM_DATA{$name} = $value;
}

print '<form name="newtext" action="newtext.pl"
 method="post">',"\n";
foreach $key (keys %FORM_DATA) {
  $value = $FORM_DATA{$key};
  print "<input type=hidden name=\"$name\"
   value=\"$value\">\n";
}
print <<END;
Можете добавить мысль:<br>
<textarea name="text" rows=5 cols=50></textarea><br>
<input type="submit" name="post" value="Добавить">
<input type="reset" name="escape" value="Очистить">
</form>
END

Того же результата можно достичь несколько другим способом. При этом нет необходимости генерировать все документы, содержащие форму, динамически. Достаточно создать один сценарий, который будет пропускать сквозь себя документ, вставляя в него соответствующие скрытые поля. В этом случае первая форма будет выглядеть так:

<form action="/cgi-bin/shopping.pl/firstspep.html"
 method="post">
Введите Ваше имя: <input type="text" name="realname">
<br>
Введите Ваш адрес: <input type="text" name="email">
<br>
<input type="submit" name="Send"
value="Следующий шаг">
<input type="reset" name="Reset" value="Очистить">
</form>

Обратите внимание на то, что в этом примере скрипту передается параметр PATH_INFO, содержащий имя следующего отображаемого документа с формой. Этот документ должен содержать форму, в которую сценарием shopping.pl будут добавлены все скрытые поля. Вот примерный текст такого сценария:

#!/usr/local/bin/perl
print "Content-type: text/html\n\n";

read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
   local($name, $value) = split(/=/, $pair);
   $name =~ tr/+/ /;
   $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
    hex($1))/eg;
   $value =~ tr/+/ /;
   $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
   hex($1))/eg;
   $value =~ s/<!--(.|\n)*-->//g;
   $FORM_DATA{$name} = $value;
}

$form_file = $ENV{'PATH_INFO'};
$full_path = "/usr/local/etc/httpd/htdocs" .
 $form_file;

open(FILE, "<" . $full_path);
while (<FILE>) {
  if (/<\s*form\s*.*>/i) {
    print;
    foreach $key (keys %FORM_DATA) {
      $value = $FORM_DATA{$key};
      print "<input type=hidden name=\"$name\"
       value=\"$value\">\n";
    }
  } else {
    print;
  }
}
close(FILE);

Использование скрытых полей является наболее простым и интуитивно понятным механизмом сохранения контекста. Он опирается на общепринятые стандарты и требует минимум затрат при реализации.

Включения на стороне CGI

Технология включений не стороне CGI (назовем ее CSI) подразумевает включение специальных кусков кода в HTML документы, очень похожих на SSI. Этот код обрабатывает специальный CGI-сценарий, который заменяет их на конкретные данные, позволяющие сохранять контекст.

CSI код выглядет примерно следующим образом (важно понимать, что CSI не является ни стандартом, ни общепризнанной технологией, поэтому способ представления и реализации CSI полностью зависит от Вашей фантазии):

<html>
<head><title></title></head>
<body>
<ul>
<li><a href="/cgi-bin/takeit.cgi?cookie=
<!--#insert var="COOKIE"-->&film_id=1">
Ну, погоди!</a>
<li><a href="/cgi-bin/takeit.cgi?cookie=
<!--#insert var="COOKIE"-->&film_id=2">
Колобок</a></ul>
<a href="/cgi-bin/csi.pl/next.html?cookie=
<!--#insert var="COOKIE"-->">Далее</a>
<form action="/cgi-bin/buyit.cgi?cookie=
<!--#insert var="COOKIE"-->" method=post>
Ваше имя: <input type="text"
name="name"><br>
<input type="submit" value="Купить">
</form>
</body></html>

Вы вольны придумывать любые параметры, необходимые для реализации Вашей задачи. В общем случае достаточно указывать только "волшебный cookie" - число, уникальное для каждого сеанса работы с пользователем, а остальные данные хранить в соответствующем этому числу временном файле или базе данных. Или, если это позволяет объем данных, Вы можете передавать все данные через CSI.

Для обработки CSI вызывается сценарий, имя документа передается ему также, как и в случае скрытых полей:

http://www.your.sever.ru/cgi-bin/csi.pl/
film_cathalog.html

Сам сценарий обработки CSI выглядит примерно так:

#!/usr/local/bin/perl
print "Content-type: text/html\n\n";

read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
   local($name, $value) = split(/=/, $pair);
   $name =~ tr/+/ /;
   $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
   hex($1))/eg;
   $value =~ tr/+/ /;
   $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
   hex($1))/eg;
   $value =~ s/<!--(.|\n)*-->//g;
   $FORM_DATA{$name} = $value;
}

$cookie = $FORM_DATA{'cookie'};
$cookie = join("_", $ENV{'REMOTE_HOST'}, time)
 if (!$cookie);
$cookie =~ s/(\W)/sprintf("%%%x", ord($1))/eg;

$form_file = $ENV{'PATH_INFO'};
$full_path = "/usr/local/etc/httpd/htdocs" .
$form_file;

open(FILE, "<" . $full_path);
while (<FILE>) {
  while ( ($command, $argument, $parameter) =
          (/<!--\s*#\s*(\w+)\s+(\w+)\s*=\s*"?
          (\w+)"?\s*-->/io) ) {
    if ($command eq "insert") {
      if ($argument eq "var") {
        if ($parameter eq "COOKIE") {
          s//$cookie/;
        } elsif ($parameter eq "DATE_TIME") {
          local ($time) = &get_date_time();
          s//$time/;
        } else {
          s///;
        }
      }
    }
  print;
}
close(FILE);

В моем случае поддерживаются только команды insert с аргументом var и двумя параметрами: COOKIE и DATE_TIME. Но Вы можете ввести любые необходимые Вам команды, аргументы и параметры. Только следите при этом за отсутствием совпадений с SSI.

Cookies

Все предыдущие примеры ничего не требовали от клиента. Механизм использования cookies, основывается на изобретенной Netscape технологии хранения данных на стороне клиента.

Достоинством такого подхода является уникальная возможность сохранения контекста не только на всем протяжении сеанса работы клиента с системой, но и между несколькими сеансами. Посетитель может зайти в магазин на следующий день или через неделю, и у него в корзине останутся все предыдущие покупки.

Недостатком этого механизма является то, что не все пользователи доверяют этой технологии, и не у всех есть возможность ее использования.

Программа, желающая сохранить cookie у клиента должна начинаться примерно так:

#!/usr/local/bin/perl

read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
local($key, $value) = split(/=/, $buffer);
print "Content-type: text/html\n";
print "Set-Cookie: $key=$value; expires=Sat,
26-Aug-95 15:45:30 GMT;
path=/; domain=webclub.ru\n\n";
...
exit(0);

Можно в одном заголовке указывать несколько строк установки cookie:

Set-Cookie: book_id=123156; path=/shop;
domain=webclub.ru
Set-Cookie: city_id=434; path=/shop;
domain=webclub.ru

Сохраненные данные сценарий может прочитать в переменной окружения HTTP_COOKIE. Код программы выглядит примерно так:

@cookie_pairs = split(/;\s/, $ENV{'HTTP_COOKIE'});
foreach $pair (@cookie_pairs) {
  ($key, $pair) = split(/=/, $pair);
   $key =~ tr/+/ /;
   $key =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
   hex($1))/eg;
   $value =~ tr/+/ /;
   $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
   hex($1))/eg;
   if (defined($FORM_DATA{$key}) {
     # Вам надо решить, что делать если
     есть cookie и данные
     # формы с одним и тем же ключом.
   } else {
     $FORM_DATA{$key} = $value;
   }
}

Одной из проблем при использовании данной технологии для создания крупных систем является наличие ограничений по размеру одного cookie - 4Kb, количеству cookies от одного сорвера - 20, и общему количеству cookies для каждого пользователя - 300. Вы всегда должны помнить о них при создании крупных систем.

Заключение

Независимо от того, какой механизм Вы выберите, Вы должны хорошо продумать всю систему, определить объем передаваемых данных и решить, необходимы ли Вам временные файлы или база данных. Даже если информация о товарах хранится в базе данных, это еще не значит, что и информацию о пользователе надо хранить там-же.

Если Вам все-же необходимо использовать временные файлы или базу данных, для хранения информации о текущем состоянии пользователя, не забывайте, что нет способа отследить уход пользователя из системы где-либо по середине пути. Поэтому на Вас ложится задача удаления устаревших данных, т.е. удаления временных файлов или записей в таблицах. Способ определения, какие данные можно считать устаревшими, зависит от каждой конкретной задачи.


Что такое Cookies и с чем их едят | WebClub

Что такое Cookies и с чем их едят

..разумеется, с чаем :-))

  • Что такое cookie?
  • Что можно делать с помощью cookie?
  • Какие броузеры поддерживают механизм cookies?
  • Установка cookie
  • Установка cookie с помощью HTML
  • Установка cookie с использованием Perl/CGI
  • Установка нескольких cookie одновременно

Что такое cookie?

Cookie является решением одной из наследственных проблем HTTP спецификации. Эта проблема заключается в непостоянстве соединения между клиентом и сервером, как при FTP или Telnet сессии, т.е. для каждого документа (или файла) при передаче по HTTP протоколу посылается отдельный запрос. Включение cookie в HTTP протокол дало частичное решение этой проблемы.

Cookie это небольшая порция информации, которую сервер передает клиенту. Клиент (броузер) будет хранить эту информацию и передавать ее серверу с каждым запросом как часть HTTP заголовка. Некоторые cookie хранятся только в течение одной сессии, они удаляются после закрытия броузера. Другие, установленные на некоторый период времени, записываются в файл. Обычно этот файл называется 'cookie.txt'.

Что можно делать с помощью cookie?

Сами по себе cookies не могут делать ничего, это только лишь некоторая информация. Однако, сервер может на содержащуюся в cookies информацию. Например, в случае авторизованного доступа к чему либо через WWW, в cookies сохраняется login и password в течение сессии, что позволяет не вводить их при запросе каждого запаролированного документа. Другой пример: cookies могут использоваться для построения персонализированных страниц. Чаще всего встречается такое - на некотором сервере Вас просят ввести свое имя, и каждый раз, когда Вы заходите на первую страницу этого сервера, Вам пишут что-то типа "Hello, your_name!". На использовании cookies также часто строят функцию оформления заказа в онлайновых магазинах, в частности, в Амазоне, такая своеобразная виртуальная корзина покупателя, как в обычном реальном супермаркете.

Какие броузеры поддерживают механизм cookies?

Не все, конечно, однако самые популярные поддерживают. Я точно знаю, что Netscape (начиная с самой первой версии), Microsoft IE (трешка и четверка - точно, про предыдущие не знаю), Mosaic

Установка cookie.

Как выставлять cookies клиенту зависит от того, как они будут использоваться в дальнейшем. Это можно делать как с помощью скриптов, так и с помощью META-тагов HTML. Можно манипулировать временем жизни выставленных cookies и устанавливать место, в котором установки действительны. Общий формат установки таков:

  Set-Cookie: NAME=value; EXPIRES=date;
             DOMAIN=domain_name; PATH=path; SECURE

Более подробно об этом можно прочитать в статье "Формат и синтаксис Cookies"

Установка cookie с помощью HTML.

Простейший способ выставить cookie - использовать соответствующий META-таг в заголовке <HEAD> </HEAD> любого статического HTML документа. Это выглядит следующим образом:

  <META HTTP-EQUIV="Set-Cookie"
CONTENT="NAME=value; EXPIRES=date; DOMAIN=domain_name;
 PATH=path; SECURE">

Установка cookie с использованием Perl/CGI.

Другой способ выставить cookie - с помощью серверного скрипта. На Perl это будет выглядеть примерно следующим образом: перед тем как выдавать серверный ответ генерируется HTTP заголовок

  print "Content-type: text/html\n";
      print "Set-Cookie: username=aaa13;
      expires=Friday,31-Dec-99
       23:59:59 GMT; path=/win/internet/html/;
        domain=citforum.ru;\n\n";

Чтобы прочитать скриптом значение cookie, которое было установлено ранее, и соответствующим образом выполнить скрипт, используется переменная окружения HTTP_COOKIE. На Perl это будет выглядеть так:

  $cookie = $ENV{'HTTP_COOKIE'};

При использовании SSI для просмотра значения cookie можно применить директиву:

  <!--#echo var="HTTP_COOKIE"-->

Установка нескольких cookie одновременно.

Как с помощью HTML, так и с помощью скриптов можно устанавливать несколько cookie разом:

HTML:

  <META HTTP-EQUIV="Set-Cookie"
CONTENT="NAME=value; EXPIRES=date; DOMAIN=domain_name;
 PATH=path; SECURE">
  <META HTTP-EQUIV="Set-Cookie"
   CONTENT="NAME=value; EXPIRES=date;
   DOMAIN=domain_name;
    PATH=path; SECURE">

Perl/CGI:

  print "Content-type: text/html\n";
  print "Set-Cookie: NAME=value; EXPIRES=date;
   PATH=path; DOMAIN=domain_name; SECURE\n";
  print "Set-Cookie: NAME=value; EXPIRES=date;
  PATH=path; DOMAIN=domain_name; SECURE\n\n";


Что такое cookies и как с ними работать | WebClub

Что такое cookies и как с ними работать

Откуда возник термин "cookie" никто достоверно не знает, хотя считается, что во времена зарождения Unix-систем где-то использовалось словосочетание Magic Cookies. Имелись в виду "квитанции" (token, ticket), которыми обменивались программы.

Cookie является решением одной из наследственных проблем HTTP протокола (HyperText Transfer Protocol). Эта проблема заключается в непостоянстве соединения между клиентом и сервером, как при FTP или Telnet сессии, т.е. для каждого документа (или файла) при передаче по HTTP протоколу посылается отдельный запрос. Включение cookie в HTTP протокол дало частичное решение этой проблемы. Иначе говоря, транзакция завершается после того, как браузер сделал запрос, а сервер выдал соответствующий ответ. Сразу после этого сервер "забывает" о пользователе и каждый следующий запрос того же пользователя считает новым пользователем.

Используя cookie, можно эмулировать сессию по HTTP протоколу. Коротко принцип эмуляции сессии таков: на первом запросе выдается соотвествующее значение cookie, а при каждом последующем запросе это значение читается из переменной окружения HTTP_COOKIE и соответствующим образом обрабатывается.

Простой пример: есть форма, где пользователю предлагается указать свое имя, из нее вызывается скрипт, прописывающий значение cookie в браузер пользователя. При каждом последующем заходе на основе анализа значения cookie из браузера пользователя на странице появляется либо именное приветствие (если есть установленное значение cookie), либо первоначальная форма с запросом имени пользователя (если значение cookie не установлено).

Cookie - это небольшая порция текстовой информации, которую сервер передает браузеру. Браузер будет хранить эту информацию и передавать ее серверу с каждым запросом как часть HTTP заголовка. Одни значения cookie могут храниться только в течение одной сессии, они удаляются после закрытия броузера. Другие, установленные на некоторый период времени, записываются в файл. Обычно этот файл называется 'cookies.txt' и лежит в рабочей директории установленного на компьютер браузера. У меня, к примеру, в этом файле содержится следующее:

# Netscape HTTP Cookie File
# http://www.netscape.com/newsref/std/cookie_spec.html
# This is a generated file!  Do not edit.
www.webclub.ru	FALSE	/ourweb	FALSE	946683907	1	1
.bizlink.ru	TRUE	/	FALSE	915148488	u_irads_watch	627633
.doubleclick.net	TRUE	/	FALSE	1920499140	id	332666ae
.yahoo.com	TRUE	/	FALSE	915144943	Y
    v=1&n=6jm0u5lgubh1k&l=0b8a0d3h/o&p=m29vvru7130a
.yahoo.com	TRUE	/	FALSE	915144943	T	z=3587c277
mail.yahoo.com	TRUE	/	FALSE	943919791	YM.Login
    id%3d%241%24rm%24L6MDTCsrCNnk3syLZl2zo.
%26sid%3dszxPh4SazGg/%250a%26ts%3dX%2588%
25c3%2506%25d3%25e5I-%255d%253f%2597%25ddu
.preferences.com	TRUE	/	FALSE	1182140165
PreferencesID	3AGN9WD1D80gQfjvjAxRuq
.geocities.com	TRUE	/	FALSE	900743217
iTag	gY6bZzWItDQAAWll3T8ASk1vbiwgMTMg
search.netscape.com	FALSE	/	FALSE	942189477
NGUserID	cfc84d2a-522-898178454-1
www.webclub.ru	FALSE		FALSE	913543999	visited	yes

Как видно, у меня оставили cookie Российский клуб вебмастеров, поисковая система AltaVista, бесплатный почтовый сервер Yahoo, Netscape Communications, рекламные сети DoubleClick и отечественная InterReklama. В настоящее время большинство браузеров поддерживает механизм cookies. Я точно знаю, что cookie можно использовать во всех версиях Netscape Navigator, Microsoft Internet Explorer и NCSA Mosaic.

Что можно делать с помощью cookie?
Сами по себе cookies не могут делать ничего, это только лишь некоторая текстовая информация. Однако сервер может считывать содержащуюся в cookies информацию и на основании ее анализа совершать те или иные действия. Например, в случае авторизованного доступа к чему либо через WWW в cookies сохраняется login и password в течение сессии, что позволяет пользователю не вводить их снова при запросах каждого документа, защищенного паролем.

На использовании cookies также часто строят функции оформления заказов в онлайновых магазинах, в частности, в самом крупном виртуальном книжном магазине Amazon Books реализована своеобразная виртуальная корзина покупателя, как в обычном реальном супермаркете, в которую сервер записывает информацию обо всех заказанных книгах. Пользователь просто помечает интересующие его книги, а затем оформляет покупку сразу всех отмеченных книг.

Еще одна распространенная область использования cookies - при настройке индивидуального профиля каждого зарегистрированного пользователя.

И, наконец, самая последняя область - использование механизма cookie в рекламном бизнесе на Интернет. Еще год назад реклама в Интернет за деньги была довольно экзотической услугой, а сейчас этот бизнес уже устоялся и стремительно развивается. Однако рекламодатели начинают предъявлять более жесткие условия к оценке эффективности своих расходов. Cookie используются для таргетинга рекламы (определения целевой аудитории, например, по географическому положению пользователей), отслеживания интересов пользователей, учета количества показов и проходов сквозь баннеры.

Работа с cookie
Теперь, когда с принципами действия и областями применения cookie все более или менее понятно, можно приступить к изучению формата и синтаксиса, а также способов задания значений cookie.

Формат и синтаксис cookie
Предлагаемое мной в этой статье описание формата и синтаксиса cookie является вольным пересказом изначальной спецификации Netscape Communications " Persistent Client State HTTP Cookies". В настоящий момент идет разработка более строгой спецификации для cookie. Итак, cookie является частью HTTP заголовка. Полное описание поля Set-Cookie HTTP заголовка:

  Set-Cookie: NAME=VALUE; expires=DATE;
path=PATH; domain=DOMAIN_NAME; secure
Минимальное описание поля Set-Cookie HTTP заголовка:
  Set-Cookie: NAME=VALUE;

NAME=VALUE - строка символов, исключая перевод строки, запятые и пробелы. NAME-имя cookie, VALUE - значение. Не допускается использование двоеточия, запятой и пробела.

expires=DATE - время хранения cookie, т.е. вместо DATE должна стоять дата в формате "expires=Monday, DD-Mon-YYYY HH:MM:SS GMT", после которой истекает время хранения cookie. Если этот атрибут не указан, то cookie хранится в течение одного сеанса, до закрытия броузера.

domain=DOMAIN_NAME - домен, для которого значение cookie действительно. Например, "domain=cit-forum.com". В этом случае значение cookie будет действительно и для домена cit-forum.com, и для www.cit-forum.com. Но не радуйтесь, указания двух последних периодов доменных имен хватает только длядоменов иерархии "COM", "EDU", "NET", "ORG", "GOV", "MIL" и "INT". Для обсуждаемых сейчас новых семи доменов первого уровня ("FIRM", "SHOP", "WEB", "ARTS", "REC", "INFO", "NOM"), вероятно, это условие сохранится. Для доменов иерархии "RU", например, придется указывать три периода.

Если этот атрибут опущен, то по умолчанию используется доменное имя сервера, на котором было задано значение cookie.

path=PATH - этот атрибут устанавливает подмножество документов, для которых действительно значение cookie. Например, указание "path=/win" приведет к тому, что значение cookie будет действительно для множества документов в директории /win/, в директории /wings/ и файлов в текущей директории с именами типа wind.html и windows.shtml. Для того, чтобы cookie отсылались при каждом запросе к серверу, необходимо указать корневой каталог сервера, например, "path=/".

Если этот атрибут не указан, то значение cookie распространяется только на документы в той же директории, что и документ, в котором было установлено значение cookie.

secure - если стоит этот маркер, то информация cookie пересылается только через HTTPS (HTTP с использованием SSL - Secure Socket Level), в защищенном режиме. Если этот маркер не указан, то информация пересылается обычным способом.

Синтаксис HTTP заголовка для поля Cookie
Когда запрашивается документ с HTTP сервера, браузер проверяет свои cookie на предмет соответствия домену сервера и прочей информации. В случае, если найдены удовлетворяющие всем условиям значения cookie, броузер посылает их в серверу в виде пары имя/значение:

  Cookie: NAME1=OPAQUE_STRING1;
            NAME2=OPAQUE_STRING2 ...

Дополнительные сведения
Одновременно можно задавать несколько значений cookie.

В случае, если cookie принимает новое значение при имеющемся уже в браузере cookie с совпадающими параметрами NAME, domain и path, то старое значение заменяется новым. В остальных случаях новые значения cookie добавляются к старым.

Использование expires не гарантирует сохранность cookie в течение заданного периода времени, поскольку клиент (браузер) может удалить запись из-за нехватки выделенного места или каких-либо других причин.

Клиент (браузер) имеет следующие ограничения для cookies:

  • всего может храниться до 300 значений cookies
  • каждый cookie не может превышать 4Кбайт
  • с одного сервера или домена может храниться до 20 значений cookie

Если ограничение 300 или 20 превышается, то удаляется первая по времени запись. При превышении лимита объема в 4Кбайт корректность значения cookie страдает - отрезается кусок записи (с начала этой записи) равный превышению объема.

В случае кэширования документов, например, proxy-сервером, поле Set-cookie HTTP заголовка никогда не кэшируется.

Если proxy-сервер принимает ответ, содержащий поле Set-cookie в заголовке, предполагается, что поле доходит до клиента вне зависимости от кода возврата 304 (Not Modified) или 200 (OK). Соответственно, если клиентский запрос содержит в заголовке Cookie, то он должен дойти до сервера, даже если жестко установлен параметр If-modified-since.

Ниже приведено несколько примеров, иллюстрирующих использование cookies

Пример 1. Управление подмножеством документов, для которых действительны значения cookie, и их сроком годности
Браузер запрашивает документ и принимает от сервера в ответ:

  Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/;
             expires=Wednesday, 09-Nov-99 23:12:40 GMT
Когда браузер запрашивает URL с путем "/" на этом сервере, он посылает серверу:
  Cookie: CUSTOMER=WILE_E_COYOTE
Браузер запрашивает документ и принимает от сервера в ответ:
  Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/
Когда браузер запрашивает URL с путем "/" на этом сервере, он посылает серверу уже два значения cookie:
  Cookie: CUSTOMER=WILE_E_COYOTE;
             PART_NUMBER=ROCKET_LAUNCHER_0001
Сервер установил еще одно значение cookie, на этот раз с другой областью действия:
  Set-Cookie: SHIPPING=FEDEX; path=/foo
Теперь браузер, запрашивая URL с путем "/" на этом сервере, посылает лишь два значения cookie:
  Cookie: CUSTOMER=WILE_E_COYOTE;
             PART_NUMBER=ROCKET_LAUNCHER_0001
и лишь при запросе браузером документов с путем "/foo" на этом сервере посылаются все три значения cookie:
  Cookie: CUSTOMER=WILE_E_COYOTE;
             PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX
Комментарий: после закрытия браузера в файле 'cookies.txt' останется только одно значение cookie:
  CUSTOMER=WILE_E_COYOTE
поскольку только для него установлен срок годности - 9 ноября 1999 года. Все остальные значения не будут сохранены.

Пример 2. Значения cookie с одинаковыми именами, но разными параметрами
Браузер запрашивает документ и принимает ответ от сервера:

  Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001;
             path=/
Когда браузер запрашивает URL с путем "/" на этом сервере, он посылает значение:
  Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001
Во второй раз, запрашивая документ, браузер принимает от сервера значение cookie с другой областью действия:
  Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023;
             path=/ammo
Когда браузер запрашивает URL с путем "/ammo" на этом сервере, он посылает значение:
  Cookie: PART_NUMBER=RIDING_ROCKET_0023;
             PART_NUMBER=ROCKET_LAUNCHER_0001
Комментарий: здесь мы имеем две пары имя/значение с одинаковым именем "PART_NUMBER". При закрытии браузера ни одно из этих значений не сохранится, поскольку не задан параметр expires.

Способы задания значений cookie
Способ задания значений cookie зависит того, как эти значения будут использоваться и какие имеются серверные ресурсы. Можно манипулировать временем жизни выставленных cookie и устанавливать подмножества URL (Universal Resource Locator), в которых заданные значения действительны. Есть несколько способов задания, наиболее часто используются три - через META-таги языка HTML, JavaScript и CGI-скрипты. Любым способом можно задавать как одно, так и несколько значений сразу. Сразу хочу предупредить - не забывайте об ограничениях по объему и количеству значений cookie, а также параметре domain, так как помимо основного доменного имени узла часто бывает несколько алиасов (alias).

1. Задание cookie с помощью META-тагов
Простейший способ выставить cookie - использовать соответствующий META-таг в контейнере <HEAD>...</HEAD> любого статического HTML документа. В общем случае это выглядит следующим образом:

<META HTTP-EQUIV="Set-Cookie" CONTENT="NAME=value;
             EXPIRES=date; DOMAIN=domain_name; PATH=path;
              SECURE">

Такой способ задания cookie, на мой взгляд, наиболее интересен для создателей маленьких домашних страничек, когда нет возможности писать свои собственныеCGI-скрипты. А если есть поддержка SSI (Server Side Includes) или PHP/Fi, то можно делать интерактивные страницы вообще без использования внешних CGI-скриптов. При наличии SSI на узле создание интерактивности с использованием механизма cookie становится просто удовольствием.

С помощью <META HTTP-EQUIV="Set-Cookie" CONTENT="..."> cookie задается на любой статичной странице, директивой <!--#echo var="..."--> можно потом считать любые переменные окружения, в том числе и ранее заданные значения cookie (переменная HTTP_COOKIE), а с помощью конструкций <!--#if expr="..." -->, <!--#elif expr="..." --> и <!--#else --> задавать различные варианты внешнего вида страниц. Так же просто можно проделывать подобные вещи, используя PHP/Fi.

Если же ни SSI, ни PHP/Fi недоступен, то можно задавать значение cookie, используя JavaScript.

2. Задание cookie с помощью JavaScript
Можно задавать значение cookie, используя язык JavaScript. Единственный недостаток этого способа заключается в том, что не все браузеры его поддерживают. Ниже приведены примеры функций JavaScript, написанные Алексеем Александровым для скрипта "Органайзер". Этот скрипт, по-моему, один из лучших образцов программирования на JavaScript с использованием cookie, поэтому я настоятельно рекомендую посмотреть на работающий образец по адресу http://www.citforum.ru/internet/javascript/exorg.shtml

Пример 3. Функция установки значения cookie

// name - имя cookie
// value - значение cookie
// [expires] - дата окончания действия cookie
(по умолчанию - до конца сессии)
// [path] - путь, для которого cookie действительно
(по умолчанию - документ, в котором значение было
установлено)
// [domain] - домен, для которого cookie действительно
(по умолчанию - домен, в котором значение было
 установлено)
// [secure] - логическое значение, показывающее
требуется ли защищенная передача значения cookie

function setCookie(name, value, expires, path, domain,
secure) {
        var curCookie = name + "=" + escape(value) +
                ((expires) ? "; expires=" +
                 expires.toGMTString() : "") +
                ((path) ? "; path=" + path : "") +
                ((domain) ? "; domain=" +
                 domain : "") +
                ((secure) ? "; secure" : "")
        if (!caution || (name + "=" + escape(value)).
        length <= 4000)
                document.cookie = curCookie
        else
                if (confirm("Cookie превышает 4KB
                и будет вырезан !"))
                        document.cookie = curCookie
}

Пример 4. Функция чтения значения cookie
Возвращает установленное значение или пустую строку, если cookie не существует.

// name - имя считываемого cookie

function getCookie(name) {
        var prefix = name + "="
        var cookieStartIndex = document.cookie.indexOf
        (prefix)
        if (cookieStartIndex == -1)
                return null
        var cookieEndIndex = document.cookie.indexOf
        (";", cookieStartIndex + prefix.length)
        if (cookieEndIndex == -1)
                cookieEndIndex = document.cookie.
                length
        return unescape(document.cookie.substring
        (cookieStartIndex + prefix.length, cookieEndIndex))
}

Пример 5. Функция удаления значения cookie
Принцип работы этой функции заключается в том, что cookie устанавливается с заведомо устаревшим параметром expires, в данном случае 1 января 1970 года.

// name - имя cookie
// [path] - путь, для которого cookie действительно
// [domain] - домен, для которого cookie действительно
function deleteCookie(name, path, domain) {
        if (getCookie(name)) {
                document.cookie = name + "=" +
                ((path) ? "; path=" +
                 path : "") +
                ((domain) ? "; domain=" +
                domain : "") +
                "; expires=Thu, 01-Jan-70 00:00:01 GMT"
        }

Работающий пример персонализации пользователя, построенный на JavaScript и использующий механизм cookie.

3. Задание cookie с помощью CGI-скриптов
Самый мощный и гибкий способ управления документами с использованием механизма cookie - с помощью CGI-скриптов. Задание значения cookie на Perl будет выглядеть следующим образом:

  print "Content-type: text/html\n";
  print "Set-Cookie: username=aaa13;
  expires=Friday, 31-Dec-99 23:59:59 GMT; path=/;
   domain=www.citforum.ru;\n\n";
Скрипт при выдаче результатов работы генерирует HTTP заголовок:
  Content-type: text/html
  Set-Cookie: "username=aaa13; expires=Friday,
  31-Dec-99 23:59:59 GMT; path=/;
   domain=www.citforum.ru;"

Чтобы прочитать в скрипте ранее заданное значение cookie, используется переменная окружения HTTP_COOKIE.

  $cookie = $ENV{'HTTP_COOKIE'};

Далее можно анализировать полученную строку и, в зависимости от считанных значений, выполнять соответствующие действия.

Совсем недавно я написал систему рейтинга серверов для Российского Клуба Вебмастеров, которая использует механизм cookie для защиты от накрутки очков. В ней, задавая и анализируя значения cookie, я либо не допускаю пользователя до голосования (если отключены cookie в браузере или пользователь один раз уже проголосовал), либо разрешаю голосовать (если соответствующее значение не задано). Обмануть такую систему можно, только стирая каждый раз файл cookies.txt. Можно было бы использовать файл логов голосования на узле, но возникали бы проблемы разделения доступа к файлу и замедление работы вследствие использования медленных дисковых операций.

Немного о проблемах, связанных с использованием cookie
Главной проблемой является изначальное недоверие пользователей к тому, что удаленные сервера без их (пользователей) ведома и согласия записывают на их собственные локальные диски какую либо информацию. Бытовали также слухи о том, что с помощью механизма cookie можно прочесть любую информацию с любого компьютера. Это неправда, к тому же современные версии браузеров позволяют контролировать прием cookie или вовсе блокировать его. Кроме того, появилось множество специальных утилит для управления приемом cookie, так называемые Cookie Managers.

Другая сторона этой проблемы заключается в том, что на узлах Сети аккумулируются огромные массивы данных с персональной информацией, необходимые для коммерческих серверов. Вот здесь и появляются повышенные требования к защите от несанкционированного доступа к этим данным. Пользователи таких серверов должны быть уверены, что их имена, адреса электронной почты, телефонные номера и проч., не попадут в чужие руки. В противном случае последствия могут оказаться катастрофическими для "проштрафившихся" коммерческих серверов.


Счетчик – кто он и кому он нужен? | WebClub

Счетчик – кто он и кому он нужен?
Большинство современных Web-серверов содержат счетчики посетителей, и посему и себе хочется заиметь такую штучку. Для этого есть несколько путей:
  1. Подключится к какому не будь серверу предоставляющему такие услуги – ну хотя бы к Web Tracker.
    • Достоинства: легко, эфектно, быстро.
    • Недостатки: результат – картинка может задержатся по причине загрузки сервера, некоторые сервера за это удовльствие берут денежки.
  2. Сделать счетчик самому. Тоже возможны варианты:
    • сделать его в виде рисунка,
    • сделать как SSI директиву.
    • Достоинства: нет ограничений !
    • Недостатки: надо что-то делать.

Рассмотрим второй путь поподробней (пройден автором лично).
Если делать счетчик как рисунок (монолитный GIF изображающий номер посетителя и другие красоты) необходимо обладать инструментарием который способен программно генерировать GIF! Можно воспользоваться уже готовым ОООЧЕНЬ крутым счетчиком wwwcount, но его инсталляция очень утомительна! К тому же этот метод имеет один недостаток – если вашу страничку будут смотреть без картинок (такие счетчики устанавливаются в поле src тега img и делают в виде CGI сценария возвращающего изображение ) ваш счетчик не вызовется – следовательно посещение не зарегистрируется !
Если делать счетчик как SSI директиву – это очень простоя штучка которой даже не надо уметь работать с изображениями – он может возвращать последовательность изображений цифр, формирующих необходимое число. Под этим надо понимать следующие: счетчик генерирует некоторый фрагмент HTML кода содержащего в себе теги img с необходимыми параметрами src и alt. Также такой счетчик может вернуть фрагмент HTML кода в виде текстового сообщения о номере посетителя. Он будет корректно работать и в случаи если вашу страничку будут смотреть без картинок. Недостатками этого метода можно считать следующее: необходимость наличия у сервера поддержки SSI (так как большинство Web-серверов это Apache – проблема малозначительна), а так же в случаи сбоя вашего счетчика Web-сервер вернет ошибку документа и доступ к нему будет заблокирован, тогда как в случаи со счетчиком-картинкой – ошибка будет относится только к изображению!

Кто такой счетчик мы выяснили – теперь необходимо узнать кому же он нужен ?

WebMaster-у – надо же знать насколько популярна его страничка.
Пользователю – для выявления наиболее популярных страничек и путешествия по ним (такие рейтинги регулярно проводятся серверами предлагающими услуги счетчика).
Организации чья страничка содержит счетчик – для статистики и солидности!

Следовательно, одним подсчетом количества посещений ограничиваться не следует! Нужна еще, ну хотя бы очень простая, статистика. Лично мне как-то не сильно интересно каким броузером смотрят мою страничку, а вот кто и как часто – это интересно!

А посему, предлагаю тем, кто решил установить свой SSI счетчик, мою разработку, успешно функционирующую на серверах: http://www.immsp.kiev.ua/ и http://rex.immsp.kiev.ua/ . Описание его можно почитать на http://www.immsp.kiev.ua/~kk/CountReadme.html, а заполучить его себе в виде архива TAR-GZIP можно на http://www.immsp.kiev.ua/~kk/counter.tgz.


Форум на вашем сервере

Форум в этой статье мы будем понимать как собрание сообщений на доске объявлений - похоже на конференции UseNet (в российском народе до недавнего времени более известные как Релкомовские)

Молчаливо предполагаем, что у вас "правильный" сервер - а именно:

  • UNIX-подобная ОС. Я не чувствую разочарований от FreeBSD и Linux, тем более что у нас в Питере их можно купить в Доме Книги по сходной цене на дисках от УрбанСофт.
  • Apache - он берется с тех же дисков или http://www.apache.org/.
  • Perl 4 и выше - все с тех же дисков, или можно поползать по http://www.perl.com/ и http://www.perl.org/
Пересматривая список, вспоминаю, что при некоторой сноровке можно поставить форум и под NT (если, конечно, будет желание взгромоздить на это чудо Perl и Apache). Но, предупреждаю честно - я не пробовал :)

В этой статье мы рассмотрим организацию форумов с помощью скрипта WWWBoard от Matt Wright. Он лежит на страничке http://www.worldwidemart.com/scripts/wwwboard.shtml .

Перекачиваем файл wwwboard.tar.gz на наш сервер во временный каталог на сервере и
запускаем либо "gzip -d wwwboard.tar.gz ; tar xvf wwwboard.tar", либо "tar xzvf wwwboard.tar.gz" - под Windows у меня заработала только первая последовательность.

Имеем новый каталог WWWBoard со следующим содержимым:
messages/ | каталог для сообщений
ADMIN_README | информация по настройке администрирования - wwwadmin.pl
ALPHA-2 | "Авторы ответственности не несут..."
data.txt | хранит номер последнего сообщения
faq.html | страничка подсказки
passwd.txt | файл с логином/паролем администратора форума
README | README он и в Африке README - там ВСЕ написано
wwwadmin.pl | программа администрирования
wwwboard.html | главная страница форума
wwwboard.pl | программа самого форума

Можно углубиться в README и сделать все самому. Тогда эту статью дальше можно не читать, поскольку пойдет прямой пересказ-перевод.

Параметры для примера:

  • имя сервера: www.hell.ru
  • Server Root: /home/httpd/docs
  • CGI-bin: /home/httpd/cgi-bin
  • каталог с форумом: /home/httpd/docs/wwwboard
Настройка файлов

WWWBOARD.PL
Или поместите его в ваш каталог cgi-bin, или, если ваш сервер разрешает выполнение .cgi файлов - просто переименуйте его в wwwboard.cgi, поменяв соответственно переменную $cgi_url.
Также необходимо выполнить команду "chmod 755 wwwboard.pl" - всем дать права на чтение-выполнение, а владельцу (webmaster) - на чтение-выполнение-запись. Хотя, думаю, что если webuser и webmaster состоят в одной группе, то более разумным будет "chmod 750 wwwboard.pl"

Подредактируйте переменные в начале программы:

$basedir = "/путь/к/форуму";
Абсолютный путь к корневому каталогу вашего форума, например "/home/httpd/docs/wwwboard".
Это адрес форума "изнутри".

$baseurl = "http://ваш.сайт.ru/форум"
А это адрес форума "снаружи", в нашем случае - "http://www.hell.ru/wwwboard".

$cgi_url = "http://ваш.сервер.ru/адрес/программы"
URL программы, выполняющей всю черную работу. у нас - "http://www.hell.ru/cgi-bin/wwwboard.pl"

$mesgdir = "messages"
Название подкаталога, в котором хранятся сами сообщения. Будет прицепляться к $basedir.

$datafile = "data.txt"
Название файла с данными - тоже прицепляется к $basedir.

$mesgfile = "wwwboard.html"
Имя файла, содержащего главную страницу

$faqfile = "faq.html"
Имя файла с подсказкой

$ext = "html"
Расширение, которое будет даваться вновь создаваемым файлам сообщений

$date_command = "/bin/date"
Команда для выдачи даты - необходимо для датирования сообщений

$title = "Название вашего форума"
Эта строка будет показываться в заголовке отдельных сообщений

Далее идут несколько опций, которые начинающий пользователь может оставить как есть.
Все. Сконфигурировали. Теперь положим этот файл туда, где он должен лежать - наверное, в cgi-bin.

WWWBOARD.HTML

Все и так ясно - слегка подредактировать заголовки и часть ACTION в теге <FORM>.
Положим куда надо и по инструкции - "chmod 777 wwwboard.html", а по-моему - "chmod 666 wwwboard.html", или если webuser и webmaster в одной группе - то "chmod 660 wwwboard.html". При всем моем уважении к Матту, я никак не пойму, зачем давать права на запуск HTML ???

FAQ.HTML

Должен быть положен куда надо ($faq_file), и "chmod 744 faq.html"

MESSAGES/

Должен быть создан в каталоге $basedir командой "mkdir messages". Потом - "chmod 777 messages"
Мое мнение - если webuser и webmaster в одной группе - то "chmod 770 messages"

DATA.TXT

Поместите его в $basedirb "chmod 777 data.txt". Здесь мое мнение - опять же не больше чем "chmod 666 data.txt", а если webuser и webmaster в одной группе - то "chmod 660 data.txt"

Вот,собственно, и все :) Пробуйте.

Программа wwwadmin.pl конфигурируется аналогичным образом. А вот логин/пароль я не скажу :) ищите сами в ADMIN-README :)


HTML

HTML. Полезные советы.
  • SHTML - это HTML документ, просматриваемый сервером перед отправкой на наличие SSI (Server-Side Includes). SSI выполняются, и результат их работы подставляется в посылаемый документ.
  • Не используйте очень большие таблицы на страничке. Если есть возможность, поделите таблицу на несколько более мелких таблиц или вообще откажитесь от них. Это связано с тем, что содержимое таблицы не выводится на экран до тех пор, пока вся таблица не скачана. И у пользователя создается иллюзия медленной загрузки этой страницы, что нежелательно.
  • Так можно автоматически перенаправить посетителя на другую страницу:
    <html>
    <head>
    <title>WebClub Has Moved</title>
    <meta http-equiv="Refresh" content="15;URL=http://www.webclub.ru/">
    </head>
    <body bgcolor="#ffffff">
    <h1 align=center>Web-masters Club переместился</h1>
    <hr>
    <h2 align=center>Подождите 15 секунд или воспользуйтесь ссылкой:
    <a href="http://www.webclub.ru"> http://www.webclub.ru </a></h2>
    </body>
    </html>

  • Строка DOCTYPE (DTD), расположенная в первой строке HTML документа, описывает стандарт HTML, который используется в этом документе. Два наиболее используемых DTD: HTML 3.2: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
    HTML 2.0: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">

  • Чтобы заменить кнопку в форме на изображение используйте: <INPUT TYPE="image" SRC="graphic.gif" HEIGHT="xx" WIDTH="yy" ALT="submit" BORDER="0">
  • Если при размещении странички на сайте не видно картинок, а в локальной версии всё было в порядке, то проверьте следующее:
    1. Адреса картинок не относительные, а абсолютные (типа file///....).
    2. Могут стоять неправильные слэши: не "/", а "\". В URL, как правило, применяются прямые слэши "/".
    3. FTP может класть на сервер файлики с именами, написанными заглавными буквами - PICTURE.GIF, в то время как в тексте будет прописано picture.gif или Picture.gif - для ОС типа UNIX это не одно и тоже
  • Если Вы делаете карту ссылок из одного изображения, разбитого на несколько частей, учтите, что язык HTML символ перевода каретки (конец строки) воспринимает как пробел. Поэтому ВСЕ тэги, которые у Вас формируют Вашу карту ссылок, нужно писать в ОДНУ строчку, иначе между картинками будут промежутки!
  • Чтобы убрать границы шириной 2-3 пискела между фреймами, добавьте в тэге <FRAMESET> строчку BORDER=0 FRAMEBORDER=0.
  • При использовании тэга <table background=img.gif> IE отображает фоновый рисунок, начиная от начала таблицы, а NN - с начала каждой ячейки. Обойти это неудобство можно, вложив таблицу в другую: <table cellspacing=0 cellpadding=0 border=0 background="img.gif">
    <tr><td align="center">
    <table cellspacing=0 cellpadding=4 border=0 width="100%" background=""> <tr><td align="left">
    <!--Some stuff-->
    </td><td align="right">
    <!--Some stuff-->
    </td></tr>
    </table>
    </td></tr>
    </table>     Обратите внимание на background="" и на то, что в тэгах таблиц не задан атрибут bgcolor.
  • Для того, чтобы "собрать" один HTML-документ из нескольких кусков, нужно использовать команды SSI (Server-Side Includes):
    <!--#include file="ssi/menu.html" --> - путь к файлу указывается от текущего файла (файла, в котором расположена команда).
    <!--#include virtual="/ssi/menu.html" --> - путь к файлу указывается от корня сервера.
    Таким образом, например, можно задать меню, одинаковое для всех страниц сервера, и хранить его в отдельном файле. Тогда его изменение отразится на всем сервере и не потребует кропотливой работы по правке всех HTML-страниц Вашего сервера.
  • У адреса mailto:, применяемого для отправки писем, можно задать и другие опции, крома самого адреса. Например, адрес такого вида: mailto:name@domain.ru?subject="Happy Birthday!"&Body="Happy Birthday To You, My Friend!" задает, кроме адреса, subject и даже текст самого письма.
  • Как избавиться от противной синей рамки по периметру рисунка, который является ссылкой?
    <a href="link.html"><img src="image.gif" width=15 height=15 border=0></a>
  • Как сделать цветную горизонтальную линию без тени, не прибегая к картинке 1*1 пиксел? Эту задачу можно решить двумя способами:
    1) В тэг <hr> добавить атрибуты noshade (убирает тень) и сolor="******" (задает нужный цвет). Но эти атрибуты будет понимать только IE 4 и выше, другие же браузеры будут выводить обычный <hr>.
    2) Cоздать такую конструкцию:
    <table border="0" cellpadding="0" cellspacing="0" width="***"><tr><td height="1"
    bgcolor="******">
    <spacer type="block" height="1" width="***">
    </td></tr>
    </table>
    Ее понимают все версии браузеров, поддерживающие задание цвета в таблицах. Тестировалось на IE 4.0 IE5.0, NN 4.5, NN 3.04.


Как поставить на одну машину IE 3.x и 4.x

Будем считать, что браузеры версий 4.x и 5.x являются идентичными.

Немного о самом Internet Explorer: Internet Explorer по сути своей является контейнером* (container application) для ActiveX приложения, которое в свою очередь есть как OLE сервер, так и контейнер. Это означает, что, когда Вы запускаете приложение под именем Internet Explorer, оно в свою очередь поднимает IE и активизирует его по месту (in-place activation).** В дальнейшем под словом IE я буду подразумевать именно поднимаемый обьект, а программу назовем launcher.

Технология OLE подразумевает отсутствие необходимости клиентом знать о версии интерфейса, поэтому, даже если Вы и запустите "старый" IE, то в лоб получите самым новым броузером (все версии IE имеют один GUID). То есть решением проблемы есть нахождение пути обхода смысла существования OLE. И как вывод - человеку не знакомому с архитектурой Windows в общем и OLE частности досконально не рекомендуется читать дальше.

Единственный путь таков: с помощью простых программ отслеживания изменений в системном реестре Вам необходимо установить, какие компоненты регистрируются в процессе инсталляции Internet Explorer 3.x. Все компоненты собираются в одной папке. Далее для каждой из компонент броузера вручную в коде меняются все внутренние GUID на заново сгенеренные, а также все перекрестные ссылки***.

Далее на машине, где стоит браузер версий от 4, все это регистрируется и можно запускать. Все. Можно специально для этого модернизировать launcher, и будет он переключаться между броузерами.

Вот так. Иного пути к сожалению нет.Один раз проделав эту операцию и после переустановки NT потеряв все данные я не горю желанием заниматься этим снова. :)

* О технологии OLE вы можете узнать из книги Джефферсона "Inside OLE", выпущенной издательством Microsoft Press доступной так же в электронном виде по адресу http://www.microsoft.com/msdn/ в отделе online library.

** Собственно говоря даже на своих страничках Вы можете разместить IE как ActiveX control с помощью тэга <object> и развлечение Вам обеспечено надолго, т.к. возможностей у обьекта много.

*** Процесс мерзкий и нудный. Иного пути не существует, т.к. поднимается IE не по имени, а по этому самому GUID'у.


Untitled

Полезные советы

  • Структура узла и система навигации
  • Как зарегистрировать свой сайт на всех русских поисковых системах и каталогах, затратив на это 20 минут
  • Счетчик – кто он и кому он нужен
  • Форум на вашем сервере
  • Файлы посещений: мифы и реальность
  • Анализ файлов регистрации событий (log-файлов)
  • Взаимодействие нескольких форм
  • Что такое cookies и как с ними работать
  • Что такое Cookies и с чем их едят
  • Ставим пароль на страницу
  • Руководство по роботам, "паукам" и другим теневым обитателям Web
  • Исключение пауков и роботов
  • Воспрепятствование автоматизированному зеркалированию Веб-узла
  • Как поставить на одну машину IE 3.x и 4.x
  • Как разделить доступ к страницам для браузеров ниже 4-ой версии
  • Важные мелочи
  • Самые распространенные ошибки начинающих
  • Секреты для мастеров
  • Включите мультимедию в свою страницу
  • Графика на Вашей странице
  • Об эффективном расположении элементов графики и текста на экране.
  • Об элементах восприятия и цветах.
  • Обмен ссылками
  • Продвижение своей странички
  • Создание meta-команд.
  • Авторское право в Интернете


Анализ файлов регистрации событий (log-файлов) | WebClub

Анализ файлов регистрации событий (log-файлов)
  • Введение
  • Что будет уметь анализатор?
  • Принцип построения анализатора
  • Чтение файла регистрации событий
  • Обработка данных - генерация отчетов
  • Вывод результатов
  • Инициализация
  • Сопоставление имен доменов
  • Полный текст анализатора
  • Заключение

Введение

Зароком динамичного развития и процветания узла является систематический анализ статистики, полученной путем правильной обработки файлов регистрации событий. Во многих случаях генерацию файлов статистики можно доверить существующим бесплатным или коммерческим анализаторам . Некоторые из них имеют "безграничные" возможности для настройки, другие просты и быстры.

Но со временем все чаще возникает желание что-нибудь изменить в используемом анализаторе, добавить некий специфичный для вашего узла отчет. Хорошо, если используемый вами анализатор написан на Перле или к нему прилагался исходный код, но даже в этом случае разобраться в чужом коде, да еще если отсутствуют комментарии, достаточно сложно. Поэтому самое хорошее, это написать свой анализатор.

Конечно писать анализатор с нуля дело неблагодарное, да и ненужное. В свое время я, выбрав самый подходящий, написанный на Перле анализатор (FTPWebLog), стал потихоньку добавлять в него нужные мне функции. Спустя год я наконец созрел для того, чтобы, используя его каркас, переписать его заново так, как мне нужно. А чтобы мои усилия не пропали зря, я решил рассказать об этом и вам. Хочу заранее предупредить, что для адекватного восприятия статьи необходимо иметь как минимум средние знание Перла и хорошее знание принципов функционирования http-сервера.

Что будет уметь анализатор?

Заранее определимся, какие отчеты будет генерировать наш анализатор, т.к. возможное их количество почти бесконечно. Мне (помимо узко специализированных) достаточно следующих отчетов:

  • Сводный отчет (общий трафик, статистика узла, посещаемость, количество ошибок)
  • Отчет по дням
  • Отчет по часам
  • Отчет по доменам верхнего уровня (странам)
  • Отчет по наиболее активным хостам
  • Отчет по количеству хостов в день
  • Отчет по всем хостам
  • Отчет по рубрикам (каталогам первого уровня)
  • Отчет по наиболее популярным файлам
  • Отчет по всем файлам
  • Отчет по всем ненайденным файлам
  • Отчет по всем авторизованным пользователям
  • Отчет по ссылающимся доменам (referrers)
  • Отчет по всем ссылкам на узел
  • Отчет по запросам в поисковых системах
  • Отчет по популярным браузерам
  • Отчет по версиям браузеров
  • Отчет по операционным системам
  • Отчет по всем браузерам

Как видите количество реализуемых отчетов велико, а некоторые из них уникальны, во всяком случае я таких не встречал.

Взятый мной за основу анализатор имел некоторые функции, которые я не буду реализовывать, а именно:

  • Чтение предыдущих отчетов
  • Механизм подсчета стоимости пользования узлом

С другой стороны я считаю очень полезной функцию сопоставления (aliasing) хостов. Т.е. функцию, благодаря которой в отчетах www.server.ru, server.ru, www.server.mirror1.ru, www.server.mirror2.com будут считаться одним хостом. Также мной будет добавлена возможность выбора критерия сортировки некоторых отчетов.

Принцип построения анализатора

Хорошо спланированный анализатор должен иметь несколько независимых частей, каждую из которых можно потом модифицировать, не причиняя вреда всем остальным. В нашем случае этими частями будут:

  • Инициализация. Настройка параметров. Определение глобальных констант.
  • Чтение файла регистрации событий.
  • Обработка данных - генерация отчетов.
  • Вывод результатов.

Независимость этих частей означает, что при изменении формата файла отчетов нам понадобится изменить только вторую часть. Чтобы изменить способ отображения отчета, достаточно внести изменения в четвертую часть. Правда, чтобы добавить новый отчет, придется вносить добавления в первую, третью и четвертую части, но это не затронет уже существующий код.

Далее мы перейдем к последовательному описанию всех частей, начиная со второй, а первую часть напишем последней, основываясь на используемых нами в остальных частях параметрах и константах.

Чтение файла регистрации событий

Для того, чтобы прочитать файл регистрации событий, необходимо прежде всего определить его формат и местоположение. Создадим константы:

$LogFormat = '%h %l %u %t \"%r\" %s %b \"%{referer}i\"
 \"%{user-agent}i\"';
$LogFile='/usr/local/www/logs/httpd.access';

Описание параметров строки формата файла регистрации содержится в конечном файле, содержащем полный текст анализатора. Для тех, кто не может указать формат вашего файла регистрации, определим переменную

$LogType='common';

которая может принимать два значения: common и combined. С ее помощью можно быстро указать формат вашего файла, не заботясь о установке $LogFormat.

Теперь определим функцию проверки формата и сопоставления номеров полей с их именами и обратно. Она нам необходима для того, чтобы в функции чтения файла не связываться с номерами полей. Это сделано для вышеупомянутой независимости модулей:

sub ParseLogFormat {
  local ($matchitem,$matchpattern,@logelements,$count,
  $identitem,
      %LogFormatRegexPatterns,%LogFormatIdentifiers);

  %LogFormatRegexPatterns = (
    '%b' => '\d+|-',   # Number of bytes sent
    '%h' => '[a-zA-Z0-9-.]+',   # Remote host
    '%f' => '\S+',   # Filename
    '%{referer}i' => '\S*', # Referrer
    '%{user-agent}i' => '[^"]*', # User Agent
    '%i' => '[^"]*', # Header junk
    '%l' => '\S+',   # remote logname (identd
    if provided)
    '%p' => '\d+',   # Port the request was server to
    '%P' =>  '\d+',   # Process ID of the child that
    serviced the request
    '%r' => '[^"]*', # First line of request
    '%s' => '\d\d\d', # Status Code
    '%t' => '\[\d\d/\w\w\w/\d\d\d\d:\d\d:\d\d\:\d\d\s
    [+-]?\d\d\d\d]',
    '%T' => '\d', # Time taken to serve the request
    in seconds
    '%u' => '\S+', # Remote user (authentication)
    '%U' => '\S+', # URL path requested
    '%v' => '[a-zA-Z0-9.-]+', # Server name
  );

  %LogFormatIdentifiers = (
    '%b' => 'bytes',
    '%h' => 'remotehost',   # Remote host
    '%f' => 'filename',   # Filename
    '%{referer}i' => 'referrer', # Referrer
    '%{user-agent}i' => 'useragent', # User Agent
    '%i' => '[^"]*', # Header junk
    '%l' => 'rfc931',   # remote logname
    (identd if provided)
    '%p' => 'port',   # Port the request was server to
    '%P' =>  'process',   # Process ID
    of the child that serviced the request
    '%r' => 'request', # First line of request
    '%s' => 'status', # Status Code
    '%t' => 'timedate', # Date/Time stamp in CLF format
    '%T' => 'servicetime', # Time taken to serve
    the request in seconds
    '%u' => 'authuser', # Remote user (authentication)
    '%U' => 'url', # URL path requested
    '%v' => 'server', # Server name
  );

  # [10/Aug/1996:09:55:25 -0700] - CLF format time

  if ($LogFormat) {

  } elsif ($LogType =~ m#^common$#oi) {
    $LogFormat = '%h %l %u %t \"%r\" %s %b';
  } elsif ($LogType =~ m#^combined$#oi) {
    $LogFormat = '%h %l %u %t \"%r\" %s %b \"?%
    {referer}i\"? \"%{user-agent}i\"';
  }
  $MatchFormat="\^$LogFormat";
  # We are going to assume that everything is
   seperated by white space
  (@logelements) = split(/\s+/,$LogFormat);
  for ($count=0;$count <= $#logelements;$count++) {
    $identitem = $logelements[$count];
    $identitem =~ s#[^%}{a-z-]##ogi;
    if (defined($LogFormatRegexPatterns{$identitem})) {
      $matchitem=quotemeta($identitem);
      $matchpattern=$LogFormatRegexPatterns
      {$identitem};
      $MatchFormat =~ s/$matchitem/\($matchpattern\)/;
      $FieldToName{$count}=$LogFormatIdentifiers
      {$identitem};
      $NameToField{$LogFormatIdentifiers
      {$identitem}}=$count;
    }
  }
}

Теперь перейдем к чтению самого файла. Первым делом проверим, указано ли местоположение файла и, если надо, распакуем его.

return if (($LogFile eq '') ||
             ($LogFile eq '/dev/null'));

local (@LogFields,$Ndomain,$Nrfc931,$Nauthusers,
$Ntimedate,
  $Nrequest,$Nstatus,$Nserver,$Nreferrer,
  $Nuseragent);

$LogFile="$Zcat $LogFile |" if ($LogFile=~m/
(\.gz|\.Z)/o);
die ("Unable to open $LogFile\n$!") if
(!(open(LOGFILE,$LogFile)));

Далее в цикле будем читать файл. Записи, в которых не определены поля домена, rfc931, имени авторизации, времени, запроса и кода ответа, считаются некорректными:

while(<LOGFILE>) {
  chop;
  (@LogFields) = m/$MatchFormat/o;

  if (!($Domain && $rfc931 && $authuser && $TimeDate
&& $Request &&  $Status)) {
    $InvalidLogLines++;
    next;
  }

После этого необходимо произвести некоторые действия над полученными полями, например выделить имя файла из запроса, отрезать параметры и якоря, удалить добавку прокси из UserAgent, выполнить запрос к DNS для определения имени хоста и т.п.

Наконец необходимо вызвать функцию обработки данных, чтобы она учла все полученные при чтении поля.

Обработка данных - генерация отчетов

Функция &RecordData, вызываемая после прочтения каждой строчки файла регистрации событий, призвана накапливать данные, используемые при составлении различных отчетов. Именно в ней определяется, что и как мы хотим подсчитывать. Далее мы увидим, как накапливать данные для создания различных отчетов.

Сводный отчет

В сводном отчете необходимо отобразить общий объем трафика, количество запрошенных документов, количество уникальных документов, количество уникальных хостов, количество ненайденных документов, количество некорректных записей.

Прежде всего определим "текущий" день и время, воспользовавшись специальным массивом преобразования текстового месяца в числовой. Преобразование необходимо для последующей сортировки. Под текущим понимается момент занесения записи в файл.

$Today="$Year $MonthToNumber{$Month} $Day";
$Now="$Today $Hour $Minute $Second";
$StartDate=$Now if ($Now lt $StartDate);
$EndDate=$Now if ($Now gt $EndDate);

Подсчитаем общее количество переданных байт и файлов. При этом будем учитывать, что при ответе, со статусом не 200, также передается некое количество байт:

$Bytes+=$RespEstimates{$Status};
$TotalBytesCounter+=$Bytes;
$TotalFilesCounter++;

Количество уникальных хостов определяется простым подсчетом количества элементов в массиве статистики по доменам, а количество некорректных записей подсчитывается нами при чтении файла.

Отчет по дням

В отчет по дням входит общее количество переданных за каждый день файлов и их суммарный объем.

$DayFilesCounter{$Today}++;
$BytesDay{$Today}+=$Bytes;

Отчет по часам

В этот отчет также входит количество файлов переданное за час и их суммарный объем. Этот отчет нас интересует "в среднем за час", а "не всего за весь период".

$HourFilesCounter{$Hour}++;
$BytesHour{$Hour}+=$Bytes;
$HourCounter{"Today $Hour"}++;

Отчет по доменам верхнего уровня

Для формирования этого отчета необходимо выделить верхний домен из полного имени хоста. Остальное не представляет никакой сложности. Данные в них учитываются только, если документ был в действительности возвращен запрашивающему хосту.

($TopDomain)=($Domain=~m#\.(\w+)$#o);
$TopDomain=~tr/A-Z/a-z/;
$TopDomain="Unresolved" if ($TopDomain > 0 ||
 $TopDomain eq '');
if ($Status==200) {
  $TopDomainFilesCounter{$TopDomain}++;
  $TopDomainBytesCounter{$TopDomain}+=$Bytes;
}

Отчеты по хостам

В эту группу входят отчет по наиболее активным хостам, отчет по количеству хостов в день и отчет по всем хостам. Данные в них учитываются только, если документ был в действительности возвращен запрашивающему хосту.

if ($Status==200) {
  $DomainsFilesCounter{$Domain}++;
  $DomainsBytesCounter{$Domain}+=$Bytes;
  $UniqueDomains++ if ($DomainsFilesCounter
  {$Domain}==1);
  if ($LastAccessDomain{$Domain} lt $Now) {
    $LastAccessDomain{$Domain}=$Now;
  }
  $DayDomainsCounter{$Today}{$Domain}++;
}

Отчет по файлам

В эту группу входят отчет по наиболее популярным файлам и отчет по всем файлам. Данные в них учитываются только, если документ был в действительности возвращен.

if ($Status==200) {
  $HitsFileCounter{$FileName}++;
  $BytesFileCounter{$FileName}+=$Bytes;
  if ($LastAccessFile{$FileName} lt $Now) {
    $LastAccessFile{$FileName}=$Now;
  }
}

Отчет по рубрикам

Данная функция вызывается один раз после полного прочтения файла регистрации событий. В принципе ее содержимое можно поместить со всеми остальными обработчиками данных, но это сильно замедлит выполнение программы.

sub ProcessArchiveSections {
  foreach $file (keys (%HitsFileCounter)) {
    foreach $pattern (keys (%ArchiveSectionPatterns)) {
      $match='';
      ($match) = $file =~ m:$pattern:;
      next if (! $match); # Skip stuff that does not
      match an archive section
      $archiveSection = $file;
      if ($ArchiveSectionPatterns{$pattern}
      eq '--matched pattern--') {
        $substitute=$match;
      } else {
        $substitute=$ArchiveSectionPatterns{$pattern};
      }
      $archiveSection =~ s:$pattern:$substitute:;
      $ArchiveSectionHitsCounter{$archiveSection}
      += $HitsFileCounter{$file};
      $ArchiveSectionBytesCounter{$archiveSection}
       += $BytesFileCounter{$file};
      if ((! $ArchiveSectionLastAccess
      {$archiveSection}) ||
          ($ArchiveSectionLastAccess{$archiveSection}
          lt $LastAccessFile{$file})) {
        $ArchiveSectionLastAccess{$archiveSection} =
         $LastAccessFile{$file};
      }
    }
  }
}

Для работы этой функции необходимо определить массив, содержащий шаблоны совпадения, например такой:

%ArchiveSectionPatterns=(
 '^(/[^/]*/).*$',              '--matched pattern--',
 '^.*(\.[Hh][Tt][Mm][Ll]?)$',  ' html files',
 '^.*(\.[Tt][Xx][Tt])$',       ' text files',
 '^.*(\.[Gg][Ii][Ff])$',       ' gif graphic files',
 '^.*(\.[Jj][Pp][Gg])$',       ' jpg graphic files',
 '^(\/)$',                     ' server home page',
);

Здесь код '--matched pattern--' обозначает, что в качестве описания нужно использовать попадающую в (...) подстроку. Именно этой строкой мы добиваемся включения в рубрики каталогов первого уровня. В принципе в качестве рубрики можно определить все, что угодно.

Отчет по всем ненайденным файлам

Для создания отчета по ненайденным файлам необходимо выделить запросы, окончившиеся ошибкой 404:

if ($Status==404) {
  $ErrorHitsFileCounter{$FileName}++;
  if ($LastTryErrorFile{"$FileName $Referrer"}
  lt $Now) {
    $LastTryErrorFile{"$FileName $Referrer"}=$Now;
  }
}

Отчет по авторизованным пользователям

Пользователь авторизован тогда, когда соответствующее поле строки файла не равно дефису:

if ($Status==200) {
  if ($authuser ne '-') {
    $UserCounter{$authuser}++;
    if ($LastAccessUser{$authuser} lt $Now) {
      $LastAccessUser{$authuser}=$Now;
    }
  }
}

Отчет по ссылкам на узел

В эту группу входят отчет по ссылающимся доменам и отчет по всем ссылкам на узел. Для того, чтобы выделить домен из ссылки мы воспользуемся командой split. Нам также необходимо отсеять все внутренние ссылки из других документов узла. Для этого нам нужно определить константу с именем узла. Нам также неинтересны ссылки с сохраненных страниц.

$SystemName = "webclub.ru";

$Home = $SystemName;
$Home =~s#\.#\\\.#goi;

($Protocol,$Thrash,$TopReferrer,$File)=split
(/\//,$Referrer,4);

if (!($Referrer=~m#$Home#oi) && !($Referrer eq '-')
    && !($Referrer eq '') && !
    ($Referrer=~m#^file:#oi)) {
  $ReferrerCounter{$Referrer}++;
  $TopReferrerCounter{$TopReferrer}++;
}

Отчет по запросам в поисковых системах

Невозможно охватить все поисковые системы. Поэтому надо ограничиться теми, которые интересуют вас и сделать подсчет для них (в этой иллюстрации их меньше чем к конечном файле). Вначале необходимо определить шаблоны поиска запросов в URL поисковых систем:

%SearchPatterns=(
  '^http://[\w\.]*?yahoo\.com/bin/query.*?\?p=
  ([%\w\+]*).*$',      'Yahoo',
  '^http://[\w\.]*?yandex\.ru/yandsearch\
  ?.*?&text=([%\w\+]*).*$', 'Yandex',
  '^http://www\.ru/cgi/find_[re]\.cgi\
  ?Str_Find=([%\w\+]*).*$',    'www.ru',
);

Как и в случае с рубриками функция обсчета вызывается один раз после полного прочтения файла регистрации событий для убыстрения выполнения программы:

sub ProcessSearchEngines {
  foreach $referrer (keys (%ReferrerCounter)) {
    foreach $pattern (keys (%SearchPatterns)) {
      $match='';
      ($match) = $referrer =~ m:$pattern:i;
      next if (! $match); # Skip stuff that
       does not match an archive section
      $MatchReferrer = $referrer;
      $substitute=$match;
      $engine=$SearchPatterns{$pattern};
      $MatchReferrer =~ s:$pattern:$substitute:i;
      $EngineCounter{$engine}{$MatchReferrer} +=
       $ReferrerCounter{$referrer};
    }
  }
}

Отчет по браузерам

В эту группу входят отчет по популярным браузерам, отчет по версиям браузеров, отчет по операционным системам и отчет по всем браузерам.

Отчет по всем браузерам обсчитывается чрезвычайно просто:

$UserAgentCounter{$UserAgent}++;

Перед тем, как подсчитывать популярные браузеры, необходимо определить шаблоны их аутентификации. Делать это нужно аккуратно, так как очень многие браузеры прикидываются Навигатором. В принципе вы сюда можете добавить любой интересующий вас браузер.

($Browser,$rest)=split(/\//,$UserAgent,2);
$TopBrowser = '';
$TopBrowser = 'Navigator' if ($Browser=~m#Mozilla#o);
$TopBrowser = 'Opera' if ($rest=~m#Opera#o);
$TopBrowser = 'Explorer' if ($rest=~m#MSIE#o);
if ($TopBrowser) {
  $BrowserCounter{$TopBrowser}++;
}

Версии браузеров подсчитываются только для Навигатора и Эксплорера. Именно они интересуют разработчика Веб-страниц.

$ver='';
if ($TopBrowser eq 'Navigator') {
  ($ver,$rest)=split(/ /,$rest,2);
  ($ver)=$ver=~m#^(\d\.)\d.*#o;
}
if ($TopBrowser eq 'Explorer') {
  ($ver)=$rest=~m#^.*;\sMSIE\s(.*)(;.*)+#o;
  ($ver)=$ver=~m#^(\d\.)\d.*#o;
}
if ($ver) {
  $BrowserVerCounter{$ver}++;
}

Отчет по платформам опять же выгоднее подсчитывать в самом конце. Здесь опять используется массив шаблонов для определения операционной системы:

%PlatformPatterns=(
  '(Win)',                                'Windows',
  '(X11|Linux|BSD|SunOS|AIX|IRIX|OSF1)',  'Unix',
  '(Mac)',                                'Macintosh',
  '(OS/2)',                               'OS/2',
  '(Amiga)',                              'Amiga',
);

Функция обработки этих шаблонов выглядит следующим образом:

sub ProcessPlatforms {
  foreach $ua (keys (%UserAgentCounter)) {
    foreach $pattern (keys (%PlatformPatterns)) {
      $match='';
      ($match) = $ua =~ m:$pattern:;
      next if (! $match); # Skip stuff that
       does not match a platform
      $Platform = $ua;
      $substitute=$PlatformPatterns{$pattern};
      $PlatformCounter{$substitute} +=
      $UserAgentCounter{$ua};
    }
  }
}

Обратите внимание, что все три приведенные функции обработки массивов шаблонов отличаются друг от друга, т.к. преследуют разные цели.

Вывод результатов

Заключительным этапом работы анализатора является визуализация результатов. Удобнее всего, если анализатор будет генерировать HTML документ, выводя его на стандартный вывод согласно соглашениям Unix. Примечание: конечная версия будет позволять указывать в качестве получателя HTML кода дисковый файл.

Для того, чтобы облагообразить документ, целесообразно ввести три специальных константы:

$BodyTag=<<"EOF";
<body bgcolor="#ffffff" text="#000000">
EOF

# Any extra stuff you want to put in
 the head of the generated HTML
# goes here
$SpecialHead=<<"EOF";
EOF

$SpecialFoorer=<<"EOF";
lt;a href=/index.html>Server Root</a>;
EOF

Теперь можно определить саму функцию вывода скелета документа. Для экономии места здесь приведен пример, выводящий только один отчет, не считая сводного:

sub PrintReport {
  print <<"EOF";
<html>
<head>
<title>Analysis for $SystemName</title>
${SpecialHead}
</head>
${BodyTag}
<a name=index></a>
<h2>Оглавление</h2>
<ul>
EOF

  print "<li><a href=#summary
  >Сводный отчет</a>\n";
  print "<li><a href=#daily
  >Отчет по дням</a>\n";
  print "</ul>\n";

  &PrintSummaryReport;
  &PrintDailyReport;

  print <<"EOF";
<br><br>Создано с помощью
<a href=http://www.webclub.ru/repository/scripts/
>WebAnalyzer</a>.
<br>
</body></html>
EOF
}

Вывод отчетов является достаточно скучным занятием, поэтому я приведу примеры только наиболее интересных отчетов, а остальные можно будет посмотреть в листинге.

В большинстве отчетов используется специальная функция, расставляющая запятые в числах для удобства их восприятия:

sub commas { # Insert commas into an integer
  local($_)=@_;
  1 while s/(.*\d)(\d\d\d)/$1,$2/;
  $_;
}

Рассмотрим отчет, выводящий запросы в поисковых системах. Он интересен тем, что использует трехмерный ассоциативный массив и некоторые преобразования данных. Прежде всего мы выводим заголовок. Затем во внешнем цикле "проходимся" по всем добавленным поисковым системам. Обратите внимание, что здесь в массиве присутствуют только те системы, которые были найдены в файле регистрации, а не все те, которые мы описали в массиве шаблонов. Для каждой системы мы выводим список ключевых слов. При этом мы заменяем плюсы на пробелы и "распаковываем" русские буквы:

sub PrintSearchEngineReport {
  print "<a name=searchengine></a><h2>
  Отчет по запросам в поисковых системах</h2>\n";
  foreach $engine (sort keys(%EngineCounter)) {
    $DateHash=\$EngineCounter{$engine};
    print "<h3>$engine</h3>\n";
    print "<pre>\n",
      "Количество  ",
      "Запрос\n\n";
    foreach $words (sort keys(%{$$DateHash})) {
      $value = $EngineCounter{$engine}{$words};
      $pwords=$words;
      $pwords=~s/\b\+/ /g;
      $pwords=~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
       hex($1))/eg;
      printf "%10s  %-s\n",
        $value,
        $pwords;
    }
    print "</pre>\n";
  }
  print "<a href=#index>К оглавлению</a>\n\n";
}

Функция вывода отчета по наиболее популярным браузерам использует функцию расстановки запятых и внешнюю функцию сортировки:

sub ByBrowser {
  $BrowserCounter{$b}<=>$BrowserCounter{$a};
}

sub PrintPopularBrowserReport {
  print "<a name=popularbrowser></a><h2>
  Отчет по популярным браузерам</h2>\n";
  print "<pre>\n",
    "Запросов  ",
    "Процент  ",
    "Браузер\n\n";
  foreach $key (sort ByBrowser keys(%BrowserCounter)) {
    printf "%8s  %7.1f  %-s\n",
      &commas($BrowserCounter{$key}),
      $BrowserPCCounter{$key},
      $key;
  }
  print "</pre>\n";
  print "<a href=#index>К оглавлению</a>\n\n";
}

Все остальные функции вывода отчетов выглядят почти идентично этой, за исключением функции вывода отчета по доменам верхнего уровня. Но она слишком громоздка и, поэтому, остается вам на "факультативное" изучение.

Инициализация

Одним из важных свойств нашего анализатора является возможность гибкой настройки. К основным категориям настройки относятся:

  • Управление выводом отчетов
  • Управление подробностью некоторых отчетов
  • Управление сортировкой строк некоторых отчетов
  • Управление включением данных в отчеты

Что же подразумевается под этими грозными фразами?

Управление выводом отчетов

Не всегда и не всем нужны все отчеты, предусмотренные в программе. Для того, чтобы можно было включать или выключать каждый отчет заводятся соответствующие константы:

$PrintSummary = 1;
$PrintDaily = 1;
...
$PrintPlatform = 1;
$PrintFullUserAgent = 1;

Если константа равна единице, соответствующий отчет выводится. Если нет, то целесообразно не только не выводить данный отчет, но и запретить обсчет данных, необходимых только этому отчету, для увеличения производительности. Но при этом нельзя забывать про другие отчеты, использующие эти же данные:

if ($PrintPopularBrowser == 1 &&
 $PrintBrowserVersion == 1) {
  $TopBrowser = '';
  $TopBrowser = 'Navigator' if ($Browser=~m#Mozilla#o);
  $TopBrowser = 'Opera' if ($rest=~m#Opera#o);
  $TopBrowser = 'Explorer' if ($rest=~m#MSIE#o);
  if ($TopBrowser) {
    $BrowserCounter{$TopBrowser}++;
  }
}

Управление подробностью некоторых отчетов

Во многих отчетах необходимо регламентировать, в каком объеме выводить данные. Для этого введем несколько параметров ограничителей:

$TopNDomains=10;
$TopNSections=15;
$TopNFiles=20;
$MinRequestsPerHost=100;
$MinErrorsPerDocument=5;
$MinRequestsPerUser=3;
$MinRefsPerDomain=10;
$MinRefsPerReferrer=5;

Мне таких ограничителей достаточно, хотя можно определять ограничители, которые будут регламентировать количество байт или некое процентное отношение к максимуму или минимуму.

Управление сортировкой строк некоторых отчетов

Часто необходимо сортировать отчеты то по запросам, то по байтам. В связи с тем, что сортировка большинства отчетов вынесена в отдельные функции, очень просто реализовать возможность выбора способа сортировки:

$SortDomains='bytes';
$SortFiles='req';
$SortSections='req';

Теперь в самой функции сортировки проверим у словие и зададим различные критерии:

sub ByFiles {
  if ($SortFiles eq 'req') {
    $HitsFileCounter{$b}<=>$HitsFileCounter{$a};
  } elsif ($SortDomains eq 'bytes') {
    $BytesFileCounter{$b}<=>$BytesFileCounter{$a};
  } else {
    $a<=>$b;
  }
}

Как видно, отключение параметра сортировки приводит к сортировке по имени.

Управление включением данных в отчеты

Данная задача является самой главной, но при этом и самой сложной. Нам необходимо иметь возможность включать/исключать некоторые файлы или группы файлов из рассмотрения. Необходимо также иметь возможность включать/исключать хиты с определенных доменов, например ваши собственные. Для этого задаются константы, содержащие определенные шаблоны:

$TopFileListFilter='(\.xbm$|\.gif$|\.png$|
\.jpg$|^Code \d\d\d)';
$ExcludeDomain='(194\.85\.135\.|\.citmgu\.ru|
\.citforum\.ru|\.webclub\.ru)';
$ExcludeRefsTo='(\.gif$|\.jpg$|\.css$|\.class$|
chat\.pl$|/images/)';
#$IncludeOnlyRefsTo='^/~novikov/';
#$IncludeOnlyDomain='(\.webclub\.ru)';

Понятно, что вторая и третья константы и четвертая и пятая константы являются взаимоисключающими.

Эти константы используются в цикле чтения файла:

while() {
  ...
  next if (($IncludeOnlyDomain) &&
   !($Domain=~m#$IncludeOnlyDomain#o));
  next if (($IncludeOnlyRefsTo) &&
   !($FileName=~m#$IncludeOnlyRefsTo#o));
  next if (($ExcludeDomain) &&
  ($Domain=~m#$ExcludeDomain#o));
  next if (($ExcludeRefsTo) && (
  $FileName=~m#$ExcludeRefsTo#o));
  ...
}

Сопоставление имен доменов

Для того, чтобы реализовать сопоставление имен прежде всего необходимо объявить массив, хранящий регулярные выражения, выделяющие имена доменов, которые необходимо заменить. В круглые скобки заключается все то, что при совпадении будет заменено на строку, идущую за регулярным выражением. Например все зеркала AltaVista мы заменяем на имя главного сервера, а ip адрес счетчика Рамблера - на его имя:

%HostAliases=(
  '^http://([\w\.]*?citforum[\.\w*]*\.\w\w\w?)/.*$',
   'citforum.ru',
  '^http://([\w\.]*?altavista[\.\w*]*\.\w\w\w?)/.*$',
   'altavista.digital.com',
  '^http://(194\.87\.13\.2)/.*$',
            'counter.rambler.ru',
  '^http://([\w\.]*?webclub\.ru)/.*$',
    'www.webclub.ru',
);

Теперь при чтении очередной строки файла, в том случае, если использование сопоставления включено, мы проходим по всему массиву в поисках сопоставлений. Как видно, можно реализовывать каскадную замену, для этого только необходимо записать регулярные выражения в нужном порядке.

while() {
  if ($UseHostAliases==1) {
    foreach $pattern (keys (%HostAliases)) {
      $match='';
      ($match) = $Referrer =~ m:$pattern:i;
      next if (! $match);
      $substitute=$match;
      $host=$HostAliases{$pattern};
      $Referrer =~ s:$substitute:$host:i;
    }
  }
}

Полный текст анализатора

Как и положено в заключение хочу привести полный текст анализатора. В нем обобщено все то, что мы только что обсудили и добавлены комментарии к параметрам настройки для удобства дальнейшего использования. Он полностью идентичен варианту, хранящемуся в Кладовой (по состоянию на 7 июля 1998 года), хотя в будущем я могу внести в тот вариант изменения и дополнения. Здесь, также приведен примет создаваемого отчета. Сразу предупрежу, что он сильно сокращен и поэтому не соответствует действительности.

  • WebAnalizator 1.0 - webanalyzer.pl
  • Пример создаваемого отчета - wc.html
  • Файл с кодами доменов верхнего уровня - country-codes

Заключение

Напоследок хочу высказать некоторые общие соображения по поводу описанной здесь программы.

1. Кому-то может показаться, что она бесподобна. Другие решат, что она не идет ни в какое сравнение с той, которую они используют. Моей задачей не было создать уникальный универсальный анализатор, наоборот я постарался показать общие принципы. Даже если вы и не собираетесь писать свой анализатор или использовать этот, всегда полезно знать, как они в принципе обрабатывают данные файлов регистрации. Хочу также заметить, что мне в большинстве случаев (см. пункт 2) хватает всего того, что здесь описано.

2. Как я сказал в самом начале, главной целью наших усилий является создание анализатора, приспособленного к вашему конкретному узлу. Поэтому даже готовый анализатор нужно использовать, как основу для дальнейшего развития. Например я сразу же добавлю в него статистику по запрашиваемым словам в локальной поисковой машине. Ведь понятно, что они у всех разные. Мне также интересна статистика по Конференц-залу: кто какими тематиками интересуется, насколько он активен. Для каждого конкретного узла можно придумать множество таких специфических отчетов. И не обязательно при этом ограничиваться исключительно чтением log-файла, можно совместить это с обращениями к базе данных, для дополнения или облагораживания отчетов, например для вывода реальных имен вместо логинов в предыдущем примере.

3. Текущий пример реализации отображения отчетов годится скорее для средних и малых серверов, т.к. вывод осуществляется на одну страницу, и просматривать такую простыню не совсем удобно. Наибольшие проблемы при этом могут вызывать полные отчеты. Рекомендую их отключать при более чем 200 документах на узле или log-файле объемом более 50Mb. Думаю первым изменением в распространяемой версии будет генерация нескольких страниц с фреймовой структурой, когда в одном маленьком фрейме будет отображаться список отчетов, а в другом фрейме сам выбранный отчет.

В заключение хочу вам пожелать успешной модификации моей программы. Любые вопросы, комментарии, пожелания и, особенно, реализации новых интересных отчетов приветствуются.


Файлы посещений: мифы и реальность | WebClub

Файлы посещений: мифы и реальность
Словарь терминов
обычный формат - common format
комбинированный формат - combined format
запрос, хит - hit
хост, удаленный компьютер - host
ссылающийся документ - referrer
сессия - session
код ответа - status code
идентификатор программы-клиента - user-agent

Часто при общении с другими вебмастерами разговор заходит о посещаемости их серверов. Задав вопрос о среднем количестве посетителей в день или об эффективности поставленной на каком-то сервере ссылки, иногда поражаешься услышанному ответу: "Ну, я точно не знаю - по Рамблеру около 300 человек в день, но он ведь не точный". Или того хуже: "А как это узнать?". Удивительно, но встречаются весьма профессиональные вебмастера, которые ничего не слышали или просто не задумывались о файлах посещений (лог-файлах). С другой стороны даже те, кто их анализирует и извлекает большое количество информации, не подозревают, что информации в них гораздо больше, чем они предполагали. Поэтому я решил рассказать вам о том, что можно и чего нельзя из них извлечь, как можно использовать полученные данные, какие инструменты можно для этого использовать.

Что же такое эти загадочные лог-файлы? Это обыкновенные текстовые файлы, в которые построчно записывается информация о каждом запросе файла у Веб-сервера. Сразу хочу объяснить тем, кто не знает подробностей работы HTTP протокола - для каждого отдельного файла браузер должен сгенерировать отдельный запрос. Представим, что мы запрашиваем HTML страницу, которая содержит пять графических элементов. В этом случае браузер сгенерирует шесть запросов к серверу, и в лог-файле появятся шесть новых строк. Помимо этого сервер помещает в лог-файл информацию и обо всех неудачных запросах, например, к несуществующим документам. То есть, строго говоря, в лог-файл помещается информация обо всех корректных запросах, полученных сервером. Справедливости ради отмечу, что некорректные запросы тоже регистрируются, но в другом файле - файле регистрации ошибок. Небезынтересный технический аспект: запрос регистрируется не в момент его прихода, а только после его полной обработки.

В общем случае лог-файлы могут иметь любой формат, он зависит не только от используемого сервера, но и от настроек, произведенных вебмастером. Но наибольшее распространение получили два формата - "обычный", использовавшийся самым первым Веб-сервером, и "комбинированный", называемый также "комбинированный NCSA формат", т.к. он впервые появился у сервера NCSA - прародителя всемирно известного Apache.

Строка лог-файла обычного формата выглядит так:

194.220.198.72 - admin [23/Feb/1999:22:48:05 +0300]
"GET /admin/ HTTP/1.0" 200 9059

Все поля записи разделяются пробелами (если значение заключено в двойные кавычки, или в случае с датой в квадратные скобки, то оно воспринимается как одно поле), если значение у поля отсутствует, то ставится дефис.

В первом поле указывается, с какого адреса (хоста) пришел на сервер запрос. Второе поле содержит информацию от identd, если он запущен на удаленном хосте и если включена соответствующая опция Веб-сервера. В третьем поле указывается имя, использованное при авторизации, при обращении в закрытую область узла. В четвертом поле в весьма неудобном для анализа формате фиксируется время окончания обработки запроса. Пятое поле содержит сам запрос, или, если быть более точным, - первую строку заголовка запроса. Как известно в ней указывается метод, адрес запрашиваемого ресурса и используемый протокол. В следующем поле отображается код результата обработки запроса, а в последнем поле - количество переданных в ответе байт, исключая заголовок.

В комбинированном формате в запись добавлены еще два поля - адрес документа, по ссылке с которого попали на этот ресурс и идентификатор программы-клиента (она размещена на двух строках для удобства восприятия, на самом деле это одна строка):

194.220.198.72 - - [23/Feb/1999:22:48:46 +0300]
"POST /cgi-bin/hd.cgi HTTP/1.0" 200 293
  "http://webclub.ru/" "Mozilla/4.07 [en] (X11;
  FreeBSD 2.2.8; Nav)"

В настоящее время большинство Веб-серверов используют именно этот формат, т.к. он предоставляет гораздо больше информации о запросе, чем обычный. Причем именно два последних поля наиболее важны при анализе эффективности предпринимаемых вебмастером действий по популяризации (раскрутке) узла и при планировании его развития в плане внедрения новых технологий и услуг.

Настало время проанализировать каждое поле более подробно и выяснить, какую полезную информацию можно из него извлечь.

Адрес источника запроса

Анализ этого поля позволяет с некоторой долей погрешности определить популярность вашего узла. Именно количество уникальных хостов за период времени, а не количество переданных файлов определяет популярность узла и интересует потенциальных рекламодателей в первую очередь. Используемый термин хост - понятие абстрактное и заменять его словом посетитель нельзя. Это связано с тем, что существует такое понятие, как прокси-сервер, который может запрашивать ваш узел от имени всей организации с, например, 200 работниками. Или в другую очередь у маленькой фирмы может быть только один компьютер, тогда за ним могут по очереди работать несколько человек. В локальной сети организации может быть настроено динамическое выделение адресов, тогда один человек может в разное время приходить на ваш узел с различных адресов. Кроме того, посетитель, имеющий доступ в Интернет черед коммутируемую линию провайдера (а таких достаточно много), может иметь разный адрес в процессе одного сеанса работы с вашим узлом - после обрыва связи, перезвонив провайдеру, посетитель с большой долей вероятности получит другой адрес. Все эти ситуации достаточно распространены, поэтому и принято использовать абстрактное понятие "хосты".

Некоторые анализаторы вводят такое понятие, как сессия. Под сессией они подразумевают последовательность запросов с определенного адреса с задержкой между ними не более 20-30 минут. Таким образом, если с одного адреса заходили утром и вечером, то это будет считаться двумя сессиями. Но, в связи со всем вышесказанным, это понятие не корректно. Правда в сервере Apache существует возможность протоколирования сессий с использованием cookies - при первом обращении посетителю выдается cookie, которая сохраняется до следующего перезапуска браузера.

Анализируя поле источника запроса, можно построить три достаточно информативных зависимости:

  • Количество уникальных хостов за определенный период времени позволяет следить за ростом (надеюсь не падением) популярности вашего узла, отслеживать эффективность рекламных кампаний, вовремя обнаруживать спад активности. В России за период времени наиболее часто принимаются одни сутки, хотя на западе более распространена одна неделя - это позволяет сглаживать колебания активности в течение каждой недели.
  • Прирост уникальных хостов за период времени позволяет более точно анализировать, сколько новых посетителей приходит к вам в обычные дни, а сколько новых посетителей привлекла проведенная вами рекламная кампания. Правда, для точного анализа нужно или иметь полный лог-файл с момента открытия узла или хранить где-то список всех адресов, с которых когда-либо приходили запросы.
  • Популярность узла по странам строится на основе преобразования IP адресов в доменные имена. Анализируя в этом имени домен первого уровня можно с достаточной точностью определить, посетители из какой страны наиболее часто запрашивают ваш узел.

Имя авторизации

Прежде чем начать обсуждение этого поля, необходимо заметить, что корректное имя в этом поле гарантируется только в том случае, если код ответа меньше 400. Анализ данного поля полезен, если на вашем узле реализована система авторизации. Весьма полезно знать об активности авторизованных пользователей на узле, какие разделы они посещают чаще, кто из них более активен, а кто приходит очень редко. В том случае, если вы реализовали свободную регистрацию посетителей, то необходимо отслеживать "мертвых" посетителей, например таких, которые не заходили на узел в течение трех месяцев, и удалять их, чтобы не засорять базу данных и файл паролей.

Еще одним полезным отчетом может быть отчет о последовательных попытках авторизации в область управления узлом, что может означать попытку взлома сервера, а для вас - необходимость принять некоторые дополнительные действия.

Время

На основе этого поля строятся почасовые, посуточные, понедельные и помесячные графики активности на узле. Если анализ количества хостов позволяет определить популярность сервера, то эти зависимости указывают на моменты пиковой нагрузки на сервер, моменты наименьшей активности, скорость увеличения активности по мере развития узла. Анализируя эти зависимости можно определить наиболее подходящие периоды для внесения исправлений в узел, обновлений или перегенерации генерируемых страниц.

Запрос

Поле запроса является основополагающим при анализе посещаемости узла. Как я уже упоминал, сервером регистрируются все запросы к серверу (хиты). Вряд ли вам требуется иметь информацию о запросах к элементам оформления документов (картинкам, звукам, таблицам стилей, файлам с внешними сценариями и т.п.), поэтому при построении большинства отчетов "лишние" записи надо игнорировать. Поэтому многие анализаторы вводят такое понятие, как показы страниц. Вебмастер сам указывает, какие именно файлы считать страницами. В простейшем случае это *.html и *.htm. Далее под хитами мы будем подразумевать именно показы страниц. Перечислим наиболее информативные зависимости, строимые на основе этого поля:

  • Количество запросов к странице позволяет судить об относительной популярности одних страниц по сравнению с другими. Это позволяет выявлять непопулярные страницы и уделять им особое внимание.
  • Количество запросов к разделу позволяет судить о популярности разделов узла. В общем случае разделом считается каталог первого или второго уровня, т.е. подразумевается, что каждый раздел узла находится в своем каталоге. Некоторые анализаторы помогают более точно разделять области узла на разделы. В этой зависимости есть одна хитрость, о которой надо помнить. Обычно зависимость отражает абсолютное число запросов, пришедшееся на конкретный раздел. Но это не совсем правильно, ведь в одном разделе может быть пять документов, а в другом пятьсот. Поэтому, если для первого раздела количество хитов равно, скажем, двадцати, а для второго - сорока, на самом деле получается, что первый раздел более популярен, чем второй, несмотря на абсолютное превосходство по количеству хитов второго раздела. Поэтому целесообразным является нормализовать зависимость, поделив количество хитов для каждого раздела на количество уникальных страниц в нем. При анализе этой зависимости наиболее важными для вебмастера являются наиболее популярные и наименее популярные разделы. Первые надо развивать более интенсивно, а для вторых прилагать наибольшие усилия, чтобы они стали популярными.
  • Интересной разновидностью предыдущей зависимости является количество уникальных хостов для каждого раздела. Она определяется совместным анализом поля запроса с полем адреса. Эта зависимость четко показывает популярность разделов относительно друг друга. Можно пойти еще дальше, анализируя, какой процент посетителей ходит по всем разделам, какой заглядывает только в этот, какие разделы "родственные" и т.д.
  • Если на вашем узле реализован какой-либо архив, то небезынтересным для вас будет знать, какие типы файлов пользователи скачивают чаще. Например, вы сможете узнать, какие виды архивов более популярны - ZIP, самораспаковывающиеся или GZIP-ed TAR (широко распространенный формат архивации для UNIX-систем). Может хранение сразу трех типов не оправдано и лишь расходует много места?

Основываясь на понятии сессии, некоторые анализаторы вводят еще несколько понятий: точки входа на узел, точки выхода и пути перемещения посетителей по серверу (тропинки). Пути перемещения строятся на основе информации о порядке просмотра страниц узла посетителем в течение сессии. Полученная информация объединяется по всем пользователям, и наиболее популярные последовательности считаются путями. Нахождение этих точек и путей нужно производить именно на основе адреса и понятия сессии, а не ссылающегося документа, иначе все пути будут состоять из одной-двух страниц, а точками выхода - почти все страницы нижнего уровня. Ведь кнопка браузера "Back" является неотъемлемым и весьма логичным элементом системы навигации узла. В связи с тем, что сессия является расплывчатым понятием, точки входа на узел, точки выхода и пути перемещения тоже являются ненадежными, но все же, при правильном построении и четких настройках, могут дать представление о поведении посетителей на узле, о правильности построения системы навигации.

Код ответа

Как правило, код ответа используется для определения необходимости использования данной записи в анализе, но и сам по себе он используется в нескольких отчетах:

  • Отчет о ненайденных файлах позволяет, не используя различные линк-чекеры, выявлять присутствующие на узле неправильные ссылки. Но в связи с тем, что такие записи могут появляться не только в результате оплошности вебмастера, но и при неправильной работе браузера или вводе некорректного адреса пользователем с клавиатуры, в отчете должно быть отражено, какой именно документ ссылается на несуществующий файл. Такой отчет также выявляет неправильные ссылки с других узлов, что не может сделать ни один линк-чекер.
  • Отчет об отказе в доступе позволяет выявить попытки проникновения в закрытые области и области администрирования узла. Такой отчет особенно эффективен в том случае, если он регистрирует несколько попыток проникновения с одного адреса, или несколько попыток за короткий промежуток времени.

Количество переданных байт

Сама по себе информация об объеме переданных данных является скорее ознакомительной, но совместно с другими данными она позволяет выявлять полезные факты:

  • Отчет о наиболее активных с точки зрения объема скачанных данных хостах позволяет выявить подозрительных посетителей, особенно, если на вашем узле располагается ценная или продаваемая вами за деньги информация. В свое время именно благодаря этому отчету я выявил факт воровства программы телепередач с моего узла (www.telesputnik.ru). Мало того, что эта информация была весьма ценной т.к. требовала значительных усилий по подготовке, так еще нарушитель скачивал (очевидно, скриптом) программу передач почти каждую секунду, что увеличило трафик почти в два раза.
  • При совмещении данных о количестве переданных байт для каждого файла с информацией о фактическом размере файла можно выявить, для каких файлов наиболее часто прерывается передача. Это может означать, что размер этих файлов слишком велик, и надо попытаться уменьшить его - разбить файл на два, если это документ или упаковать в архив, если это какой-то другой файл. Увеличение общего количества таких файлов может означать, что ваш сервер или канал перегружен, и посетители просто не дожидаются окончания и нажимают "Cancel".

Ссылающийся документ

Это поле является наиболее важным при анализе предпринимаемых вами действий по раскрутке узла. В нем содержится URL документа, ссылающегося на данный файл. Это поле содержит прочерк, если посетитель набрал адрес на клавиатуре, выбрал его из закладок или каким то другим образом самостоятельно ввел его. При анализе данного поля надо помнить две вещи:

Во-первых, в связи с тем, что за корректность предоставляемых данных для этого поля отвечает браузер, в нем может содержаться неточная или некорректная информация. Браузер может сообщить неточный URL или просто адрес страницы, на которой до этого был пользователь, а потом ввел ваш адрес с клавиатуры.

Во-вторых, для всех объектов (графических файлов, апплетов и т.п.), указанных на запрошенной странице, ссылающимся документом будет эта страница, а не тот документ, который ссылался на запрошенную страницу.

При анализе этого поля интересными будут следующие зависимости:

  • Среднее количество приходов по ссылке с документа на другом узле за период времени позволит вам определить, правильно ли вы сделали, что договорились (в обмен или за деньги) об установке этой ссылки на ваш узел. Помимо наблюдения за эффективностью ссылки, поставленной в результате ваших действий по раскрутке, вы также своевременно будете узнавать обо всех ссылках, поставленных другими вебмастерами на ваш узел по собственной инициативе. Такие ссылки всегда необходимо отслеживать, т.к. ставя ссылку из лучших побуждений, вебмастер того узла может составить такое описание, что это будет наоборот антирекламой вашего узла. Вы также сможете наблюдать за эффективностью прописки узла в поисковых системах и каталогах.
  • Все поисковые системы передают искомые слова в строке запроса, поэтому, когда посетитель нажимает на ссылку в результатах поиска, вашему серверу передается информация о том, что именно он искал. Таким образом, выуживая эту информацию из запроса, можно легко определить по каким именно словам находят ваши документы в каждой поисковой системе. К сожалению, нет возможности узнать, по каким словам их не находят.

В завершение разговора о ссылках хочу сказать, что не бывает необъяснимых всплесков посещаемости. Почти всегда это находит отражение в информации о ссылающихся документах. Такие всплески (если вы, конечно, сами ничего не предпринимали) означают, что вебмастер какого-либо популярного узла поставил на вас ссылку (например, ссылка дня на сервере Гласнета) или ваш узел упомянул в своих заметках один из популярных обозревателей Сети. Правда, именно "почти". Совсем недавно на одном из наших узлов мы с коллегами наблюдали всплеск посещаемости почти в три раза превосходящий среднесуточный уровень. Причем посетители появлялись "неоткуда". Только спустя несколько дней мы выяснили, что вечером предыдущего дня об этом узле рассказали в одной из популярных телевизионных передач об Интернете. Такие же всплески могут быть вызваны заметками в популярных журналах, таких как "Мир Интернет".

Идентификатор программы-клиента

За корректность информации в этом поле ответственность также несет браузер. Поэтому, как и в предыдущем случае, в нем могут появляться весьма странные, иногда очень веселые записи. Связано это с тем, что значение этого поля очень часто может менять владелец программы-клиента - ваш посетитель. Анализ этого поля позволяет осуществлять техническое развитие вашего узла в правильном направлении:

  • Анализ версий используемых браузеров позволит решить вопрос об использовании различных новых технологий на вашем узле, таких как DHTML, Flash и Java. Анализ соотношения производителей браузеров позволит оценить необходимость создания совместимой разметки и кода. Понятно, что такой анализ надо производить до, а не после введения новшеств.
  • Анализируя используемые посетителями платформы, вы можете понять, необходимо ли настраивать автоматическую перекодировку, какие шрифты и графику какой сложности использовать. Известно, что на Маке могут возникнуть сложности с жестко прописанными шрифтами, а под Unix чаще работают с низкой глубиной цвета.
  • Достаточно полезной будет также информация о том, как давно ваш узел индексировался популярными поисковыми системами и сколько документов при этом было ими просмотрено. Если какая-то поисковая система "забыла" про вас, вы это сразу заметите и сможете зарегистрировать в ней ваш узел еще раз.
  • Информация об активности клиентов, целиком скачивающих ваш узел поможет определить момент, когда пора вводить дополнительные возможности получения материалов. Это могут быть специально подготовленные архивы материалов или облегченные версии документов, специально подготовленные для печати из браузера.

Теперь настало время выяснить, как на практике получить всю эту полезную информацию. Существует множество различных программ-анализаторов, но я хочу остановить ваше внимание на двух из них. Обе они являются одними из самым популярных, но в то же время абсолютно противоположными во многих отношениях.

WebTrends Log Analyzer

Log Analyzer работает на платформе Windows и может быть запущен как простое приложение или как сервис Windows NT. Он распознает более 30 форматов лог-файлов таких серверов, как Apache, Netscape, Microsoft, Oracle, Domino, IBM, CERN, NCSA и других. Log Analyzer генерирует отчеты в текстовом формате или в формате HTML и позволяет настраивать их внешний вид используя имеющиеся стили или через индивидуальные настройки таких параметров, как шрифты, цвета, фоны, размеры и вид таблиц и т.д. Log Analyzer предоставляет более 70 различных отчетов и графиков, каждый из которых можно включать или выключать по желанию, настраивать количество выходных данных для каждого отчета, способ сортировки.

Скорость работы Log Analyzer составляет более 30Мб исходных данных в минуту. Он позволяет обрабатывать лог-файлы объемом более 4Гб. Log Analyzer позволяет работать как с хранящимися локально лог-файлами, так и получать их с удаленного сервера по FTP или HTTP протоколу.

Для более точного представления информации о сессиях Log Analyzer обрабатывает информацию о специальных cookies, которую некоторые сервера помещают в лог-файлы для отслеживания конкретных посетителей. Вся обработанная информация помещается в специальную базу, таким образом Log Analyzer при повторном запуске не анализирует уже обработанные данные, что значительно сокращает время его работы при повторных запусках.

Log Analyzer позволяет обрабатывать несколько последовательных лог-файлов, а также выводить результаты на различных языках.

Log Analyzer имеет мощные механизмы настройки фильтров для конкретных отчетов, позволяя выделять, например, обращения к определенному разделу узла или посетителей только с определенного домена.

Перечислю наиболее интересные отчеты, которые умеет строить Log Analyzer:

  • Обобщенная статистика
  • Наиболее часто запрашиваемые страницы
  • Наиболее редко запрашиваемые страницы
  • Точки входа на узел
  • Точки выхода с узла
  • Документы одиночного просмотра
  • Наиболее посещаемые каталоги
  • Пути через узел
  • Наиболее часто скачиваемые файлы
  • Типы скачанных файлов
  • Заполняемые формы
  • Наиболее часто показываемые рекламные блоки
  • Нажатия на рекламные блоки
  • Авторизованные посетители - Активность за различные периоды времени
  • Наиболее активные страны
  • Наиболее активные города
  • Наиболее активные организации
  • Ненайденные файлы
  • Ошибки сервера
  • Ссылающиеся узлы
  • Ссылающиеся документы
  • Поисковые системы
  • Ключевые слова поиска
  • Типы браузеров
  • Версии браузеров
  • Пауки и индексаторы
  • Платформы и операционные системы

Настройка рекламных блоков заключается в указании, какие именно графические элементы считать рекламой, а какие скрипты - обработчиками нажатий на них.

Log Analyzer заслужил множество наград, таких как PC Magazine Editors Choice, Software Publishers Association Codie Award Finalist, VARBusiness Recommends, Internet World's Best of the Test, PC Week & IT Excellence Award, Best of LAN Times.

Analog

Analog является многоплатформной программой и работает под Windows, Unix, OS/2, Dos и Macintosh. Analog распознает большое количество форматов лог-файлов, кроме этого есть возможность его настройки почти на любой формат. Он обрабатывает 200Мб исходных данных в минуту и поддерживает файлы более 25Гб.

Analog строит отчеты в формате HTML, а также позволяет сохранять результаты анализа в формате, удобном для машинной обработки. Он позволяет строить 27 различных отчетов на 26 различных языках, каждый из которых настраивается индивидуально по многим параметрам:

  • Обобщенная статистика
  • Помесячный отчет
  • Понедельный отчет
  • Посуточный отчет
  • Почасовой отчет
  • Отчет по хостам
  • Отчет по доменам
  • Отчет по каталогам
  • Отчет по типам файлов
  • Отчет по размерам файлов
  • Отчет по перенаправлениям
  • Отчет по ошибкам
  • Отчет по ссылающимся документам
  • Отчет по ссылающимся доменам
  • Отчет по типам и версиям браузеров
  • Отчет по виртуальным хостам
  • Отчет по авторизованным посетителям
  • Отчет по неавторизованным посетителям
  • Отчет по кодам ответов

В поставку Analog включен специальный CGI-скрипт для быстрого анализа специфичных данных. Вы, например, можете запросить информацию по конкретному файлу или за указанный период времени.

Сравнительная таблица

 | WebTrends Log Analyzer | Analog
Поддерживаемые платформы | Windows NT/95 | Windows, Unix, OS/2, Mac, Dos
Скорость обработки данных | 30 Mb/min | 200 Mb/min
Доступ к лог-файлам через Сеть | HTTP, FTP | Нет
Чтение нескольких лог-файлов подряд | Да | Да
Инкрементное чтение лог-файлов | Да | Нет
Формат вывода | HTML, Microsoft Word, Excel, ASCII text, comma delimited | HTML
Посылка отчетов по e-mail | Да | Нет
Экспорт данных | Да | Да
Количество отчетов | > 70 | 27
Поотчетная настройка | Да | Да
Поддержка различных языков | Да | Да
Объединение данных с нескольких узлов | Дополнительно | Нет
Возможность расширения | Да, через подключаемые модули | Да, изменение исходного текста
Стоимость | $399, 30-ти дневный период бесплатного тестирования | Бесплатный
Узел поддержки | http://www.webtrends.com/ | http://sunsite.org.uk/ packages/analog/


Site Structure and Navigation System

Структура узла и система навигации

Структура узла

Одним из главных слагаемых успеха узла является правильная организация его структуры. От нее зависит не только удобство навигации, но и легкость его пополнения, добавления разделов. Сразу заметим, что организация навигации не зависит напрямую от физической структуры узла, но сильно от нее зависит косвенно. Представьте, когда человек хочет попасть в раздел, посвященный CGI, большого информационного сервера CITForum, он наберет в строке запроса:

http://www.citforum.ru/internet/cgi/

и попадет туда, куда надо. Но вот если он захочет узнать о Domino Go Webserver Pro компании Lotus Development, он никогда не догадается, что нужно набрать в строке запроса:

http://www.lotus.com/home.nsf/tabs/lotusgopro

Конечно, угадывание URI документа узла, на котором вы никогла не были, дело сложное и зачастую ненужное, но все же лучше иметь логичную физическую структуру узла, чтобы человек, однажды у вас побывавший, смог потом легко вспомнить путь, по которому адресу лежит нужный ему документ.

Базовые структуры

Давайте теперь перечислим базовые структуры узлов и рассмотрим, когда их предпочтительнее использовать. При этом будем помнить, что настоящий узел часто имеет комбинированную структуру, включающую в себя несколько или все базовые структуры.

Плоская структура

Эту структуру еще называют звездной или полносвязной. Она имеет следующий вид: имеется головной документ (домашняя страница), который имеет ссылки на все остальные документы узла. Зачастую в этих документах также есть ссылки на все остальные документы узла, включая головной.

Такую структуру имеют многие домашние страницы и корпоративные узлы "визитки". Под узлом "визиткой" подразумевается некий узел имеющий типичные страницы: "о компании", "наши услуги", "как с нами связаться", "наши партнеры", "наши клиенты". Из вышесказанного ясно, что такая структура имеет смысл для узла, насчитывающего не более 5-9 документов.

Линейная структура

Эту структуру также называют последовательной. Она представляет собой серию последовательно связанных документов, в каждом из которых имеются только ссылки вперед (дальше) и назад.

Эта структура используется очень редко и подходит для простых презентаций и определенных видов учебников. Ее использование оправдано тогда, когда смысл документа в середине цепочки не ясен или его получение не желательно без предыдущих документов.

Приведем грубые примеры. Представьте, что вы последовательно объясняете решение некоего уравнения. Поймет ли что-нибудь человек, если сразу попадет на страницу, со следующим текстом: "Запомним результат, полученный нами на предыдущем этапе, и найдем второй корень уравнения, уже описанным нами методом." А теперь представьте презентацию товара, где человек может сразу попасть на одну из последних страниц, имеющую текст: "Именно из-за этих качеств наш продукт стоит в два раза больше имеющихся на рынке аналогов." Захочет ли он узнать, за что он должен столько переплачивать?

Конечно же линейная структура имеет смысл тогда, когда ваш посетитель может пройти ее до конца за достаточно короткий промежуток времени. Иначе ему не захочется пролистывать все сначала, если ему придется оторваться от вашего узла.

Древовидная структура

Такая структура полностью повторяет физическую структуру узла, соответствующую файловой структуре дерева документов. В своем строгом виде она не используется нигде, всегда существуют петли, нарушающие древовидность. Но она служит базой для организации сложной структуры практически всех крупных узлов.

Комбинированная структура

Как это ни странно, комбинированная структура также почти что поддается стандартизации. Подавляющее большинство крупных узлов, будь то каталог, корпоративный узел или информационный узел имеют следующую структуру:

На верхнем уровне организуется плоская структура документов. В нее входит домашняя страница, а также в зависимости от направленности узла группа вспомогательных документов и/или индексы разделов. Очень часто все они имеют полные или частичные взаимосвязи друг с другом. А из этих разделов или прямо из домашней страницы свисают "гроздья" документов древовидных структур, часто имеющих ссылки только на своих прямых и прародителей. Листьями этих деревьев могут быть отдельные документы, плоские или линейные структуры.

Прежде чем решать, какую структуру должен иметь ваш узел, необходимо детально разобраться не только в том, что будет на вашем узле после его создания, но и рассмотреть все возможные и невозможные сценарии развития вашего узла на несколько лет вперед. Собственный горький опыт и опыт многих коллег показывает, что недооценка возможностей развития узла часто приводит к необходимости его полной реструктуризации в будущем, что не только является адским трудом, но и сводит на нет все ваши усилия по раскрутке узла - его регистрации в поисковых системах, каталогах, обмене ссылками и т.д.

Навигация по узлу

Навигация по узлу - это тот механизм, который позволяет посетителю найти то, что вы хотите ему показать. От понятности системы навигации по узлу, продуманности ее элементов зависит, достигнет ли посетитель своей цели или уйдет, так и не разобравшись в дебрях документов. Именно удобная навигация, а не красивые картинки, заставляет посетителей возвращаться на ваш узел, так как они знают, что с легкостью найдут необходимый материал.

Организация домашней страницы

Одним из основных элементов навигации является домашняя страница узла. Это та страница, которая показывается при запросе корня сервера (слеша), например:

http://www.webclub.ru/

На некоторых узлах встречается один особый тип домашней страницы - страница-заставка. Такая страница не несет никакой информационной нагрузки и зачастую не содержит текста, только графику. Главной ее целью было предоставление возможности выбора кодировки русского текста, в те времена, когда не существовало серверов, автоматически перекодирующих русский текст. Теперь, когда как Apache, так и IIS (со специальной добавкой) умеют перекодировать текст на лету, такие страницы становятся раритетом, и их использование не имеет смысла.

Существует два основных вида домашних страниц: презентационная и информационная.

Презентационная страница создается для не очень больших узлов, она очень красиво оформляется, часто почти целиком состоит из графики и имеет малое количество ссылок. Такая страница, как правило, должна помещаться на один экран.

Информационная страница, наоборот, создается с использованием минимума графики. Она содержит большое количество информации. Многие знаменитые узлы не стесняются "раздувать" ее до 3, а то и более, экранов. Ее главная задача - продемонстрировать посетителю обилие информации, имеющееся на узле, или предоставить всю наиболее актуальную информацию.

Независимо от вида домашней страницы, существует несколько основных правил ее организации:

  • самая актуальная информация должна быть видна в первом экране, а если ее много, то хотя бы ее начало или заголовок;
  • элементы навигации (меню) должны быть очевидны и заметны, посетитель мог прийти к вам в первый раз, и ему не знаком ваш стиль;
  • желательно, чтобы элементы навигации помещались в один экран, если конечно у вас не мегаузел;
  • домашняя страница должна ясно говорить, чему посвящен этот узел, или же как то привлекать внимание посетителя;
  • не делайте страницу полностью графической - что бы вы там ни думали, всегда найдется причина, чтобы в будущем добавить туда текст: экстренное сообщение, новости, анекдот дня и т.п.;
  • следите за ее "весом" - посетитель согласен ждать только если знает, зачем он это делает, а новый посетитель еще не знает, куда и зачем он попал; на данный момент маргинальным является размер в 70-90 килобайт; если она все-таки очень "тяжелая" - минимизируйте количество графики, разбейте таблицу (если в нее заключена вся страница) на куски - сделайте так, чтобы посетитель увидел хотя бы ее часть, пока не подгрузится все остальное;
  • на первой странице вы ничего еще не знаете о посетителе, поэтому не помещайте на страницу никакие нестандартные элементы: Java-апплеты, звук, Flash и т.д. - это считается плохим тоном; если это так критично, предупредите об этом заранее на странице-заставке.

Как известно подавляющее большинство внутренних страниц имеет или верхнее или левое боковое меню. Ваши страницы, наверно, не исключение. Поэтому зачастую только на первой странице есть возможность самовыразиться - не бойтесь нестандартных решений, проявите неординарность.

Принципы построения системы навигации

Навигация узла подразделяется на две части - глобальную и местную. Как и в дорожном хозяйстве, глобальные "магистрали" должны позволять посетителю быстро перемещаться из одной опорной точки узла в другую, а местные "дорожки" должны позволять легко перемещаться в пределах одного раздела, или группы документов.

Система навигации не должна отягощать страницу, не должна отвлекать от ее содержимого, но должна быть легко доступна. Если у вас большие документы, то целесообразно помещать ее внизу, что позволит посетителю по окончании чтения или просмотра перейти в следующее интересующее его место, а не проматывать весь документ обратно в начало. Можно также поместить элементы глобальной навигации наверху, вдруг посетитель попал не туда, куда надо.

Целесообразно элементы локальной навигации визуально отделять от элементов глобальной, но так, чтобы они в то же время выглядели, как единое целое.

Совсем не стоит дублировать стандартную кнопку браузера "Назад" за исключением особых случаев, и уж ни в коем случае не называйте так этот элемент - мало ли как посетитель попал на эту страницу. Последнее замечание касается любых элементов - никогда не делайте никаких предположений о том, как посетитель попал на вашу страницу (конечно, за исключением страниц, являющихся результатом обработки форм) - все ваши элементы должны иметь ясные и четкие названия или условные обозначения. То, что кажется очевидным вам, совсем не покажется таким пользователю. Маленький пример-отступление, иллюстрирующий отсутствие очевидности: уже не один раз в адрес Клуба приходили письма, авторы которых заявляли, что у нас что-то не в порядке с домашней страницей; после долгих обсуждений выяснялось, что они принимали стилизованные теги, используемые, как элементы дизайна, за настоящие "вылезшие наружу" теги.

Вернемся... Не делайте ссылку на домашнюю страницу в виде логотипа - хоть это и очень распространено, это неправильно. Нарисуйте домик, сделайте кнопку "домой", или придумайте что-нибудь оригинальное.

Что делать, если разделов очень много? Как выбрать, что именно поместить в подсистему глобальной навигации? Существует три подхода (хотя вы вольны придумать четвертый):

  1. Поместить в нее ссылки на те разделы или документы, которые важны для вас, с точки зрения важности их посещения посетителем; одной из таких ссылок, например, может являться ссылка на бланк оформления заказа.
  2. Поместить в нее ссылки на наиболее популярные у посетителей разделы. Популярность определяется со временем путем долговременного анализа статистики посещаемости. В первый раз можно сделать собственное предположение о том, что будет наиболее интересно, но лучше об этом спросить хотя бы у пятерых друзей (но не коллег по работе) и обобщить результат.
  3. Объединить в некоей пропорции два предыдущих способа.

Очень часто забывают об одном очень важном элементе навигации - контекстных ссылках. Это ссылки, которые встречаются прямо внутри текста и поясняют некий термин или фразу. Например в предложении: "Вы можете подъехать к нам в офис и выбрать наиболее подходящий для Вас вариант" целесообразно "к нам в офис" сделать ссылкой на страницу с адресом и схемой проезда. Правда, если это единственное место, где вы предлагаете "подъехать к вам в офис", то лучше схему поместить прямо в конец документа. С другой стороны не следует утяжелять документ чрезмерным количеством контекстных ссылок - вы можете отвлечь посетителя от этой страницы, и он так и не прочитает документ до конца. И конечно же, если вы не хотите потерять посетителя, не делайте контекстных ссылок на документы других серверов, если это не продиктовано действительной необходимостью. Не забывайте, что такие ссылки - дополнительное средство навигации по вашему узлу. Примером действительной необходимости может служить ссылка на страницу с текущим курсом валют в фразе: "Оплата производится в рублях по курсу ЦБ на день покупки".


Ставим пароль на страницу

Данная статья не претендует на какие-то откровения, все эти вещи достаточно очевидны и широко известны. Но получив за последнее время несколько вопросов об ограничении доступа к web-страницам, я решил свести ответы на них вместе.

Итак, наша задача - установить пароль на доступ к некоторой странице. Начнем с самого примитивного способа, если можно так сказать, защиты - нескольких строчек на JavaScript'е. Код - что-то вроде

var pass = prompt("Enter the Password:", "");
if (pass == null)
  window.location = "bad.html";
else if (pass.toLowerCase() == "password")
  window.location = "ok.html";
else
  window.location = "bad.html";
Ухищрения наподобие скрытия скрипта в отдельном файле с помощью конструкции <SCRIPT SRC="security.js"></SCRIPT> принципиально ничего не меняют.

Уровнем повыше расположена аналогичная система, реализованная на Java.

Ниже приведен упрощенный исходный код.

import java.applet.*;
import java.awt.*;
import java.net.*;

public class Password extends Applet
{

   TextField login, password;
   String Login = "login";
   String Password = "Password";
   public Password()
   {
   }
   public void init()
   {
      Panel panel = new Panel();
      panel.setLayout(new GridLayout(2,2));
      login = new TextField(20);
      password = new TextField(20);
      panel.add(new Label("Login:"));
      panel.add(login);
      panel.add(new Label("Password:"));
      panel.add(password);
      add(panel);
      add(new Button("Ok"));
   }
   public boolean action(Event evt, Object obj)
   {
      if(evt.target instanceof Button)
      {
         String s;
         if(login.getText().equals(Login) &&
          password.getText().equals(Password) )
         {
            s = "http://www.webclub.ru/materials/
            pagepsw/ok.html";
         }
         else
         {
            s = "http://www.webclub.ru/materials/
            pagepsw/bad.html";
         }
         try
         {
            getAppletContext().showDocument
            (new URL(s));
         }
         catch(Exception e)
         {
            password.setText(e.toString());
         }
         return true;
      }
      return false;
   }
}

Включив этот апплет в страницу, можно получить нечто такое:

Password check

Его можно сделать поумнее, завести для каждого пользователя отдельную страницу, заставить считывать данные из файла и т.д. Принципиальный недостаток - после того как человек попал на искомую страницу, никто не в силах запретить ему запомнить этот URL, так что средство это одноразовое. Конечно, можно запрятать страницу внутрь фрейма, чтобы URL не светился в строке адреса, но сами понимаете, от кого эта защита. Опять же, апплет полностью уходит к клиенту и в принципе полностью доступен для исследования.

Последнего недостатка лишено решение, основанное на использовании CGI. Простенький скрипт на Perl'е выглядит примерно так:

#!/usr/bin/perl
use CGI qw(:standard);
$query = new CGI;
$ok = 'ok.html';
$address = 'bad.html';
$login = "login";
$password = "password";
$l = $query->param("login");
$p = $query->param("password");
if(($p eq $password) && ($l eq $login))
{
  $address = $ok;
}
print $query->redirect($address);

Пример использования:

Password check
Login:
Старый пароль:

Чтобы справиться с первым недостатком, можно динамически сформировать новую страницу на основе спрятанной где-то там внутри, не выдавая при этом URL.

Модифицированный код:

#!/usr/bin/perl
use CGI qw(:standard);
$query = new CGI;
$ok = 'ok.html';
$address = 'bad.html';
$docroot = $ENV{'DOCUMENT_ROOT'};
$localpath = "/materials/pagepsw/";
$login = "login";
$password = "password";
$l = $query->param("login");
$p = $query->param("password");
if(($p eq $password) && ($l eq $login))
{
  $address = $ok;
}
print $query->header();
open (FL, $docroot.$localpath.$address);
while(<FL>)
{
# Здесь заодно можно на лету модифицировать html-код
# Зачем ? Ну мало ли... :)
  print $_;
}
close (FL);

Пример использования:

Password check
Login:
Старый пароль:

Как видно, URL файла уже не светится, правда ценой SSI, если что-то подобное присутствовало (впрочем, это как раз можно отлавливать при выводе и обрабатывать вручную). Но и здесь остается теоретическая возможность угадывания URL, при этом не надо забывать, что медвежью услугу могут сослужить всевозможные картинки, включаемые в страницы - при использовании относительных путей, конечно.

Наконец, наиболее надежный способ установки пароля на доступ - это воспользоваться средствами сервера - не зря ж их люди делали, в конце концов. Остановлюсь на двух - Апаче как самом популярном и IIS как тоже популярном :)

С IIS все совсем просто - защита осуществляется средствами NTFS, что, конечно, несколько ограничивает возможности не-администраторов сервера. Идея следующая: у пользователя IUSR_xxxx, под аккаунтом которого по умолчанию работают все посетители узла, отбирается доступ к желаемому файлу/каталогу. После чего доступ к этим файлам будут иметь только те пользователи, для которых это явно указано в Properties->Security. Понятно, что их гораздо удобнее объединять в группы. Здесь есть пара тонкостей. Во-первых, указанным пользователям должно быть дано право Logon locally (Policies->User Rights в User Manager'е). Во-вторых, если не выбрать в настройках WWW service Basic authentication (Clear Text), внутрь будут пропущены только пользователи Internet Explorer'а.

В Apache все делается несколько иначе. Защита ставится на уровне каталогов. Соответствующие директивы могут быть помещены как в в общий конфигурационный файл (в разделе <Directory>), так и в файлы .htaccess. Набор директив в обоих случаях одинаков, а для большинства людей, арендующих место под сайт/страницу на чужом сервере, гораздо актуальнее второй способ. Итак, вы создаете в каталоге, доступ к которому планируется ограничить, файл .htaccess, после чего вставляете в него следующие директивы (привожу основные):

AuthType тип контроля - обычно используется Basic.

AuthName имя - задает имя области, в которой действительны имена и пароли пользователей. Это то самое имя, которое броузер показывает в диалоге ввода пароля. Задав одно такое имя для разных каталогов, можете сэкономить пользователям время по вводу лишнего пароля.

AuthGroupFile имя - задает имя файла, в котором хранятся имена групп и их членов. Его формат:
group1: member1 member2 ...
group2: member3 member4 ...

AuthUserFile имя - задает имя файла с паролями. По большому счету для его формирования надо воспользоваться утилитой htpasswd из поставки Apache. Но по крайней мере для некоторых версий сервера этот формат такой:
user1:passwordhash1
user2:passwordhash2

Passwordhash вполне можно получить стандартной функцией Perl'а:
$hash=crypt($pass,$salt);
где $pass - пароль, $salt - строка из двух символов, участвующая в формировании хэша.

Так что вполне можно автоматизировать процесс добавления новых пользователей, смену паролей через html-формы и т.д.

require user user1 user2 и require group user1 user2 позволяют указать, какие пользователи и группы получат доступ к данному каталогу.

require valid-user разрешает доступ всем пользователям, указанным в файле паролей системы.

<Limit method1 method2 ...> ... </Limit> , где methodi определяет HTTP-метод. Например, <Limit GET POST> ограничивает применение вложенных в нее директив случаями использования методов GET и POST (обычно этого более чем достаточно). Вложенными могут быть директивы require, order, allow и deny.

Еще пара полезных директив - deny и allow - соответственно запрещения и разрешения доступа. Применяются примерно так:
deny from all
allow from 192.168

По умолчанию сначала выполняются все deny, потом все allow, так что allow from all разрешит доступ всем пользователям, невзирая ни на какие deny. Порядок можно изменить директивой order: order allow, deny.

deny from all отлично сочетается со вторым способом защиты страниц через CGI, именно этой директивой лучше всего прикрывать всякие пароли к гостевым книгам и т.д.

Кстати, тут между делом демонстрируется самостоятельная обработка ошибок: в данном случае - код 403, Forbidden. Аналогично обрабатывается и всеми любимая 404, Not Found, и 401, Unauthorized. Для этого достаточно добавить в .htaccess директиву ErrorDocument код url:
ErrorDocument 404 /cgi-bin/bad.pl
ErrorDocument 403 /cgi-bin/badaccess.pl
ErrorDocument 401 /cgi-bin/badaccess.pl

Все, что делает скрипт - формирует сообщение об ошибке, используя переменную окружения REQUEST_URI, так что всместо него вполне можно просто указать какую-нибудь подходящую страницу.

Для заключительного примера используем файл .htaccess со следующим содержимым:

AuthType Basic
AuthName Test
AuthGroupFile /.../pagepsw/deny/tgroup
AuthUserFile /.../pagepsw/deny/tuser
<Limit GET POST>
require group test
</Limit>

В файле tgroup всего одна строчка - test: login test, в файле tuser - зашифрованные пароли для login (password) и test (test). Обратите внимание, при повторном обращении к этой странице броузер понимает, что только что обращался к этой области, и не утруждает пользователя лишним запросом пароля.

Таков вкратце минимальный набор сведений, необходимых для защиты web-страниц. Как показывает практика, более-менее доверять стоит лишь решениям, основанным на средствах, предоставляемых сервером (и то до тех пор, пока в сервере не обнаружится очередная дырка), так что если есть возможность, лучше выбирать именно их.


Site Registration in 20 minutes

Как зарегистрировать свой сайт на всех русских поисковых системах и каталогах, затратив на это 20 минут.

Знакомясь со впечатлениями авторов статей в Интернет-журналах, "писателей" в ньюс-конференциях и даже авторов книг, посвященных Интернету, я прихожу в ужас от того, что, по их словам, регистрация в поисковых системах и каталогах занимает часы и даже "целые ночи". Хотелось бы опровергнуть эти неоправданные слухи.

Первое, что хотелось бы посоветовать авторам Веб-страниц - это ни в коем случае не пользоваться услугами различных автоматических регистраторов типа Addme! или нашего TrubAddURL (TAU). В этом случае работает поговорка: "Бесплатный сыр бывает только в мышеловке". И действительно, эти сервисы еще ни разу не прописали моих страниц, и если вы не хотите остаться "с носом", делайте все своими руками! Так гораздо надежнее.

Подготовка

Если вы работаете с Интернетом по модему, не спешите сразу коннектиться. Необходимо пройти подготовительный этап.

Откройте любой текстовый редактор, работающий под Windows: лучше всего подходит Notepad. В нем, начиная с новой строки каждый пункт, напишите следующую информацию:

  • Название сайта - не очень длинно, но в принципе, как хотите. Например: Клуб Вебмастеров "Webclub"
  • URL - начиная с http:// и кончая слэшем ("/"). Например: http://www.webclub.ru/ Если URL кончается файлом, то "/" конечно же не нужен: http://www.webclub.ru/index.shtml
  • Ключевые слова - старайтесь уложиться в 10 слов, а если не получается, то вынесите в первую десятку самые важные слова. Очень полезно заглянуть на Рамблеровский список популярных слов - может быть среди низ окажется необходимые вам. Запятых ставить не надо, через пробел.
  • Описание сайте - тоже кратко, не более 50 слов. Чтобы попасть под специфику всех каталогов, старайтесь не указывать всякую рекламную информацию вашей организации, а отражайте реальное содержание сайта.
  • Контактная персона
  • E-mail

В последних двух пунктах указывайте то, что стоит на вашем сайте.

Всё. Этот этап закончен. Не спешите закрывать окно с текстом.

Процесс

Коннектимся или открываем окно браузера.

Вот список всех (или почти всех) русскоязычных поисковых систем и каталогов:

  • Rambler
  • Апорт!
  • Yandex
  • Altavista
  • Search
  • Ау!
  • Созвездие Интернет
  • Россия в Сети
  • Weblist (английское описание сочините на ходу, долго не думайте и не пытайтесь переводить ваше русское описание)
  • Улитка
  • Релком
  • Diamond

Рекомендую поставить заодно и Рамблеровский счетчик Top100 - вы попадете в рейтинг, который записывает вас в тематические каталог, кроме того еще и увеличивает посещаемость сайта.

Заходя на каждую из них , ищите Add URL, Добавить URL и т.п. и начинайте заполнять предложенную форму, используя текст, который "висит" в другом окошке. Как? А очень просто, с помощью Copy и Paste (то есть буфера обмена Windows). Как оказалось, не все помнят hot keys для этих функций: Ctrl-C (Copy) и Ctrl-V (Paste).

И так до конца моего списка. Если появляются дополнительные, "лишние" графы, заполняйте их на ходу.

Резюме

Прием состоит в том, что вы при помощи буфера обмена Windows переносите заранее подготовленные куски текста с указанными названием, URL, ключевыми словами и описанием сайта, а также вашими именем и e-mail, переносите в соответствующие графы форм на поисковых системах и каталогах. Занимает 20 минут +- 5 минут в зависимости от скорости вашего канала или модема.

P.S. И все-таки не понятно, как можно часами прописывать свой сайт?


Spiders and Robots Exclusion

Исключение пауков и роботов
Spiders and Robots Exclusion

Веб-роботы - это программы, которые автоматически проходят по всей гипертекстовой структуре, вызывая документ и рекурсивно вызывая все документы, на которые он ссылается. Одной из распространенных задач роботов является индексация узла.

Пауки - это программы, которые автоматически проходят по всей гипертекстовой структуре, сохраняя ее на локальный диск пользователя для дальнейшего оффлайнового просмотра. Принципиально они ничем не отличаются от роботов.

Данная статья объяснит, как контролировать поведение роботов и пауков на вашем узле. Для краткости я буду называть роботов и пауков одним словом - роботы.

Исключение роботов

Протокол исключения роботов позволяет веб-администраторам указывать роботам, какие части узла им запрещено посещать. Когда робот посещает узел, он прежде всего запрашивает файл robots.txt в корневом каталоге.

Если робот найдет этот файл, то он проанализирует его содержимое для определения доступных и запретных документов. Настройка файла robots.txt позволяет управлять как поведением конкретных роботов, так и всеми роботами сразу.

Расположение файла robots.txt

Данный файл должен находиться непосредственно в корневом каталоге узла, т.е. HTTP запрос будет иметь вид:

GET /robots.txt

где под узлом подразумевается HTTP сервер, работающий на определенном хосте и порте. Приведу примеры правильных расположений файла (в первых трех случаях это один и тот же файл, а в последнем случае - другой):

http://www.webclub.ru/robots.txt
http://www.webclub.ru:80/robots.txt
http://webclub.ru/robots.txt
http://www.webclub.ru:8080/robots.txt

Обратите внимание, что узел может иметь только один файл "/robots.txt". Размещение файла в каталогах пользователей не имеет смысла. Поэтому вам придется объединять все в одном файле. Если вы этого не хотите делать, пользователи могут воспользоваться META тэгом "Robots".

Примите также во внимание, что URL чуствительны к регистру - "/robots.txt" должен состоять из строчных букв.

Приведу пример неправильных файлов robots.txt:

http://www.webclub.ru/admin/robots.txt
http://www.webclub.ru/~andy/robots.txt
http://www.webclub.ru/Robots.txt
http://www.webclub.ru/ROBOTS.TXT
ftp://ftp.webclub.ru/robots.txt

Содержимое файла robots.txt

Файл "/robots.txt" выглядит примерно следующим образом:

User-agent: *
Disallow: /cgi-bin/
Disallow: /private/
Disallow: /tmp/
Disallow: /~andy/

Обратите внимание, что для каждого запрещаемого URL требуется отдельная строка с "Disallow". Вы не можете сказать "Disallow: /cgi-bin/ /tmp/". Нельзя также оставлять пустые строки в одной записи - они служат для разделения нескольких записей.

Регулярные выражения не поддерживаются ни в строке User-agent, ни в строках Disallow. Знак '*' в строке User-agent имеет специальное значение "любой робот". То есть вы не можете указывать строки типа "Disallow: /tmp/*" или "Disallow: *.gif".

Также необходимо помнить, что строка

Disallow: /texture

запретит доступ как к каталогу /texture, так и к файлам /texture.*, в то время, как строка

Disallow: /texture/

запретит доступ только к каталогу /texture.

Для исключения всех роботов со всего узла воспользуйтесь записью

User-agent: *
Disallow: /

Для разрешения полного доступа всем роботам воспользуйтесь записью

User-agent: *
Disallow:

Или создайте пустой файл "/robots.txt".

Для исключения определенной области для всех роботов воспользуйтесь записью

User-agent: *
Disallow: /cgi-bin/
Disallow: /tmp/
Disallow: /private/

Для исключения определенного робота воспользуйтесь записью

User-agent: TeleportPro
Disallow: /

Вы можете также разрешить доступ только определенному роботу:

User-agent: WebCrawler
Disallow:

User-agent: *
Disallow: /

META тэг "Robots"

В том случае, если файл "/robots.txt" вам не доступен, вы все равно можете управлять поведением роботов. Для этого вам достаточно воспользоваться META тэгом "Robots".

META тэг "Robots" позволяет авторам указать роботу, можно ли индексировать документ и можно ли собирать в нем ссылки.

META тэг "Robots" эвляется регистро-независимым.

Расположение META тэга "Robots"

Как и любой META тэг он помещается в секцию HEAD документа HTML:

<html>
<head>
<meta name="robots" content="noindex,nofollow">
<meta name="description" content="This page ....">
<title>...</title>
</head>
<body>
...

Содержимое META тэга "Robots"

Значение META тэга "Robots" содержит директивы, разделенные запятой. В настоящий момент определены директивы [NO]INDEX и [NO]FOLLOW. Директива INDEX указывает, что данную страницу можно индексировать. Директива FOLLOW указывает, что робот может следовать по ссылкам, содержащимся на данной странице. Директивы NOINDEX и NOFOLLOW имеют обратное значение. По умолчанию действуют INDEX и FOLLOW. Значения ALL и NONE устанавливают обе директивы: ALL=INDEX,FOLLOW и NONE=NOINDEX,NOFOLLOW.

Примеры:

<meta name="robots" content="index,follow">
<meta name="robots" content="noindex,follow">
<meta name="robots" content="index,nofollow">
<meta name="robots" content="noindex,nofollow">
<meta name="robots" content="all">
<meta name="robots" content="none">

Нельзя указывать повторяющиеся или конфликтующие директивы:

<meta name="Robots"
 content="INDEX,NOINDEX,NOFOLLOW,FOLLOW,FOLLOW">

Ниже приведен формальный синтаксис значения META тэга "Robots":

content    = all | none | directives
all        = "ALL"
none       = "NONE"
directives = directive ["," directives]
directive  = index | follow
index      = "INDEX" | "NOINDEX"
follow     = "FOLLOW" | "NOFOLLOW"

В заключение хочу сказать, что несмотря на все вышесказанное, ничто кроме самого HTTP-сервера не может запретить роботам ползать по всему вашему узлу. Оба этих способа исключения работают только с так называемыми "вежливыми" роботами. Хочу также заметить, что не все роботы поддерживают META тэг "Robots", хотя их процент растет.

Дополнительная информация:

  • Стандарт исключения роботов
  • Несколько слов о том, как работают роботы (spiders) поисковых машин на сервере Центра Информационных Технологий
  • WebCrawler's Web Robots FAQ
  • A List of Active Robots
  • BotSpot - "The Spot for all Bots on the Net"


Ползающие по Web
Руководство по роботам, "паукам" и другим теневым обитателям Web.

Если у вас есть узел в сети World-Wide Web, вы, вероятно, не раз досадовали на некоего пользователя, который в быстром темпе загружает к себе один за другим десятки, а то и сотни документов. Видимо, это тот случай, когда ваш узел "посетила" программа, называемая любителями "путешествий" по Web роботом, "пауком", странником, скороходом или Web-агентом.

Роботы - это автоматизированные программы, а "пауки" (spiders) - один из видов роботов - непрерывно "ползают" по Web, "перепрыгивая" с одной страницы на другую, с целью сбора статистических данных о самой Web либо формирования централизованной базы данных с индексами содержимого Web. Такие популярные поисковые узлы Internet, как Alta Vista, Lycos, OpenText, WebCrawler и др., используют пауков для создания оглавлений Web. Это облегчает пользователям поиск всех Web-страниц, содержащих информацию, скажем, об алгоритме MD5 Message Digest Algorithm или о состоянии туризма на Канарских островах.

Пауки работают в автоматическом режиме, и один-единственный робот может за несколько минут выполнить работу, на которую человеку потребуется несколько часов. Согласно данным фирмы Digital Equipment Corp., Web-мастера которой обслуживают узел Alta Vista, модуль Scooter индексирует около 2,5 млн. Web-страниц в день. Примите во внимание эти цифры и популярность поисковых узлов Internet, и вам станет понятно, почему столь притягательны роботы.

Пауки оказывают пользователям Internet ценную услугу; без них было бы практически невозможно формировать и оперативно обновлять функциональные индексы Web. Но и у них есть свои недостатки. Они увеличивают сетевой трафик, а плохо продуманный паук может настолько интенсивно и часто обращаться к Web-узлу, что будет затруднять доступ обычным программам. К тому же индексирующие роботы, как правило, не отличают постоянных Web-страниц от временных, разве что в посещаемых ими узлах предусмотрены специальные средства для таких программ. В результате возникающий в Web-индексах беспорядок затрудняет поиск действительно необходимой вам информации.

Короче говоря, роботы - это и благодать и проклятие. Но ведь они реально существуют в Internet, и с каждым днем число их растет. Давайте же заглянем за кулисы и посмотрим, какие функции и как они выполняют и, главное, что могут сделать администраторы узлов для того, чтобы их узлы стали более дружественными по отношению к роботам. Посмотрим также, куда можно обратиться за информацией о разработке "благовоспитанных" роботов.

Как работают роботы

Чтобы понять, как действуют роботы, полезно сперва разобраться в том, как работает браузер. В сущности браузер - это просто программа, которая в ответ на обращение пользователя посылает по Internet http-команды (HyperText Transport Protocol - протокол передачи гипертекстовых сообщений) для извлечения и вывода на экран компьютера Web-страниц. А Web-страницы в действительности представляют собой HTML-файлы (HyperText Markup Language - гипертекстовый язык описания документов) с текстом, программами форматирования и другой определяющей содержание информацией. К "другой информации" обычно относятся адреса файлов с растровыми изображениями и гипертекстовые связи с другими HTML-страницами.

Щелчок на гипертекстовой связи с удаленной Web-страницей инициирует ряд действий. Сначала браузер извлекает с текущей страницы адрес URL (Uniform Resourse Locator - унифицированный указатель ресурса) местонахождения страницы. Затем он устанавливает связь с удаленным сервером, выдает для извлечения HTML-документа http-команду GET и выводит этот документ на экран. Обычно сначала на экране появляется текст документа. Изображения хранятся под своими собственными URL, так что их можно загрузить отдельно. Связи с ними записаны в тексте документа, поэтому браузеру известно, где их найти и куда отправить.

Web-робот - это браузер с "автопилотом". Не щелчок пользователя на гипертекстовой связи, а робот загружает из сети Web-страницу и просматривает ее, чтобы обнаружить связи с другими узлами. Затем он извлекает URL и совершает по этому адресу "прыжок". Оттуда он "переносится" к другому Web-узлу и начинает описанный процесс сначала. Когда браузеру попадается узел, не содержащий связей, он возвращается назад на одну или две ступени и "перепрыгивает" по адресу, указанному в одной из обнаруженных ранее связей. Запущенный однажды робот, в работе которого используется простой рекурсивный алгоритм навигации, может покрывать огромные расстояния в киберпространстве, и, поскольку в Web ежедневно происходят изменения, маршрут робота тоже ежедневно меняется. По сути перемещение робота по Web напоминает ползание паука по паутине. Все, что ему для этого надо, - точка для старта.

Посмотрим, что же делает робот с загруженными страницами? Это зависит в первую очередь от того, для чего он был создан. В роботах, предназначенных для составления оглавлений Web, обычно применяются собственные алгоритмы подготовки обзоров по документам, входящим в громадные базы данных Web-узлов, и их оглавлений. Они также встраивают в URL индексы в соответствии со словами и фразами, обнаруженными в HTML-документах. Таким образом любой пользователь с помощью поискового узла может быстро получить список всех Web-страниц, содержащих слово, скажем "Cozumel". Одной из причин почему индексирующие роботы с огромной скоростью могут просматривать множество узлов, состоит в том, что они затрагивают лишь HTML-файлы. Им не приходится загружать изображения, поскольку в поисковых базах данных содержатся только индексы текста.

Однако индексирование Web-страниц - это не единственное назначение роботов. Цель некоторых из них - обнаружение связей с уже несуществующими страницами. Другие "бродят" по Web, собирая статистическую информацию о ее использовании, например определяют, какие узлы наиболее популярны, путем подсчета числа ссылок на них в других Web-страницах или регистрируют число Web-страниц для оценки роста системы.

Нетрудно понять, почему роботы столь сильно перегружают ресурсы сервера. При обращении робота к какому-то узлу за короткий период может быть запрошено огромное количество документов, что эквивалентно одновременному подключению к узлу сотни или более пользователей. Некоторые серверы могут обработать такое громадное количество запросов, а другим это не по плечу. Вот одна из причин появления ряда информационных протоколов, описывающих поведение "благовоспитанных" роботов. Например, такой робот будет отправлять запросы не чаще, чем раз в минуту, что позволит избежать перегрузок сервера. Кроме того, он будет иметь способность автоматической идентификации, для того чтобы при возникновении трудностей его владелец мог установить с ним связь и выполнить проверку указателей URL, по которым происходят обращения, для ограничения доступа к узлу.

Стандарты запретов для роботов

На заре Web, в 1994 г., столкнувшись с проблемами, обусловленными работой плохо "сконструированных" роботов, группа пользователей Internet в сеансах связи по электронной почте разработала неофициальный стандарт, известный как Standart for Robot Exclusion (SRE). Этот стандарт описывает протокол, позволяющий администраторам узлов запретить роботам доступ к указанным областям их Web-узлов.

Смотрите информацию на эту тему.

Одно из весьма полезных применений стандарта SRE - можно предотвратить проводимое роботами поисковых узлов индексирование временных HTML-документов, которых, возможно, уже не будет завтра в этот час. Многие Web-узлы (особенно поисковые) для удовлетворения поступающих от пользователей запросов генерируют HTML-документы на ходу. Но как только необходимость в этих документах отпадает, они удаляются. Очевидно, что наличие таких страниц в оглавлении Web вряд ли кому-то нужно. Еще один вариант использования SRE - можно ограничить роботам доступ к тем страницам, которые находятся в процессе разработки, или вообще запретить доступ к вашему узлу с помощью соответствующих команд.

Применение SRE для того, чтобы запретить или ограничить роботам доступ к узлу Web, не отличается сложностью. Вы должны лишь создать на Web-сервере файл, доступный HTTP, и присвоить ему локальный URL-указатель /robots.txt. Это текстовый файл, содержащий команды на английском языке, в которых четко указана стратегия доступа для роботов. Роботы, соответствующие SRE-стандартам, при первой связи с сервером отыскивают этот файл и жестко следуют изложенным в нем рекомендациям. Если робот не находит файла robots.txt, то в своем поведении исходит из предположения, что ваш узел целиком в его распоряжении. Заметьте, что ответственность за то, куда позволен или запрещен доступ роботам, полностью возложена на вас, администратора узла. Следует помнить также о том, что не все роботы соответствуют стандартам SRE, некоторые роботы могут игнорировать команды из файла robots.txt. К счастью, подавляющее большинство современных роботов либо согласованы со стандартами SRE, либо дорабатываются в этом направлении.

Как же выглядит файл robots.txt? Приведем пример простого файла, содержащего требование ко всем роботам держаться подальше от каталога /tmp/documents и его подкаталогов:

# Sample robots.txt file
User-agent: *
Disallow: /tmp/documents/
Первая строка - это комментарий. Вы можете поместить комментарии в любом месте такого файла, но при условии, что перед каждым будет стоять соответствующий знак. Во второй строке определяется, на каких роботов распространяется приведенная ниже стратегия доступа; "*" означает, что на всех. В третьей строке - запрет на доступ к указанному каталогу и подчиненным ему подкаталогам. В файл можно включить несколько предложений Disallow, с тем чтобы запретить доступ к двум или более каталогам.

В некоторых случаях бывает, что необходимо разрешить определенным роботам доступ к тем областям узла, для которых другим роботам он должен быть запрещен. В следующем файле robots.txt для робота по имени Hal-9000 доступ не ограничен, в то время как другим запрещается доступ к каталогам /tmp/documents или /under_construction:

# Sample robots.txt file
User-agent: *
Disallow: /tmp/documents/
Disallow: /under_construction/
User-agent: Hal-9000
Disallow:
Чтобы поставить в известность SRE-совместимых роботов о том, что они должны проигнорировать ваш узел, просто создайте файл robots.txt, содержащий следующие два предложения:
User-agents: *
Disallow: /
Увидев их, "благовоспитанный" робот немедленно отключится от этого узла и начнет поиск другого сервера.

В помощь разработчикам роботов

Если у вас есть сокровенное желание самостоятельно создать Web-робот, то прежде всего ознакомьтесь с парой Web-документов под названием "Guidelines for Robot Writers" (Руководство для разработчиков роботов) и "Ethical Web Agents" (Допропорядочные Web-агенты). Их адреса (URL) вы найдете во врезке "Дополнительная информация о Web-роботах". В указанных документах даются советы, как создать "дружественные" роботы, которые не перегружали бы системные ресурсы и автоматически идентифицировались, с тем чтобы авторы могли с ними связаться, если поведение роботов некорректно. К тому же ваш робот должен быть SRE-совместимым, т.е. вести себя почтительно с посещаемыми им узлами.

Мне никак не удается найти исходный текст реально действующего робота, но я уверен, что где-то он есть. Существуют несколько книг, в которых рассказывается о том, как разработать программы для извлечения HTML-документов из Web. Они дадут вам хорошую базу для начала самостоятельного создания роботов. Одна из этих книг называется "Internet Programming" by Kris Jamsa and Ken Cope (1995, Jamsa Press). Эта хорошая и достаточно всесторонняя книга поможет вам приступить к программированию для Internet, особенно если вы работаете в Windows.

Советы на будущее

В будущем число роботов, "путешествующих" по Web, возрастет. Если вы разработчик робота, то для придания ему "учтивости" твердо придерживайтесь известных стандартов и соглашений. Обратите внимание на то, чтобы запросы на HTML-документы от вашего робота не поступали к какому-то узлу сразу десятками, и не запускайте его на исполнение без особой необходимости. Если же вы - Web-администратор, позаботьтесь о наличии на вашем узле файла robots.txt, чтобы посещающие ваш узел роботы могли получить информацию о том, куда им доступ разрешен, а куда нет.

Дополнительная информация о Web-роботах

Если вы хотите побольше узнать о Web-роботах и быть в курсе последних достижений в этой захватывающе интересной области, выберите в вашем браузере страницу World-Wide Web Robots, Wanderers and Spiders по адресу http://info.webcrawler.com//mak/projects/robots/robots.html. На этой странице вы найдете свежий список адресов, по которым можно получить информацию о роботах и Web-пауках. У вас будет также возможность связаться с другими страницами, заполненными полезной и интересной информацией:

  • Робот FAQ: http://info.webcrawler.com/mak/projects/robots/faq.html
  • Перечень известных роботов: http://info.webcrawler.com/mak/projects/robots/active.html
  • Копия стандарта Standard for Robots Exclusion (SRE): http://info.webcrawler.com/mak/projects/robots/norobots.html
  • Руководство по разработке "благовоспитанных" роботов: http://info.webcrawler.com/mak/projects/robots/guidelines.html и http://www.ncsa.uiuc.edu/SDG/TT94/ Proceedings/Agents/eichmann.ethical/eichmann.xxx
  • Предложения о том, что делать, если ваш узел перегружен роботом: http://info.webcrawler.com/makprojects/robots/against.html


Как я разделял доступ к страницам для браузеров ниже 4-ой версии

Как я разделял доступ к страницам
для браузеров ниже 4-ой версии

Как известно, основной проблемой всех людей создающих веб страницы, является то, как сделать страницы одинаково видимыми как в Нетскейпе так и на Интернет Эксплорере. И ладно бы только в них, так еще на каждой версии этих браузеров.

Ну, слава богу, успешно канули в лета 2ые версии NN и MSIE. Но вот о 3их версиях, еще забыли не все :о( Из-за чего? - спросите Вы. Обычно Трешками (особенно MSIE 3) пользуются новички в Интернете. Поскольку этот браузер устанавливается по умолчанию, при инсталляции Windows95. Любители Нетскейпа люди более прогрессивные, и обычно следят за выпуском новых версий. Но и среди них находятся индивидуумы, не приемлющие новизны технологий. Среди таких, как ни странно, много и самой продвинутой части населения сети: Вебмастеров и Сисадминов. В том числе иногда к таким индивидумам начинаю относиться и я, хотя без листов стилей жить уже не могу.

Если уменьшить различия показа страницы между Нетскейпом 4.х и Эксплорером 4.х, еще как-то удается (используя только теги обрабатываемые Нетскейпом или листы Стилей, а также кое-где ява скрипты), то показ этих же страниц на 3их версиях обоих смотрелок приведет к самым плачевным результатам.

Что хотелось бы сделать, для решения этой проблемы. Задумка нашлась сразу. Это должен был быть некий ява скрипт, который бы определял при загрузке документа какой версии браузер у пользователя, и если смотрелка 4ой версии, то показывал бы содержимое страницы, а если 3ей, то выводил бы упрощенную версию, или просто извинение за неудобства. И еще хорошо бы все это расположить на одной странице, дабы не досаждать переадресацией.

Ну сам скрипт для определения версии браузера и вывода кода HTML в соответствии с этим, очень прост:

<script language="JavaScript"><!-
var brous;
brous = navigator.appVersion;
if (brous.substring(0,1) <4 )
document.write("<P>Тут код для 3их браузеров</P>");
-></Script>

Поясню, что тут к чему:
переменной brous мы задаем значение о версии браузера пользователя, в частности для Вас это будет :
Поскольку для нас является значащей только первая цифра, то с помощью brous.substring(0,1) мы ее и выделяем. И если она меньше 4х, то выводим HTML код, который написан в document.write. При этом следует помнить, что MSIE3.0 на самом деле дает 2ую версию на этой позиции - поэтому здесь поставлен знак меньше, а не два равно.

Вроде бы ура!, можно пить пиво, но не тут то было :(. Да этот скрипт выведет нужную нам информацию для 3их смотрелок, но он и выведет содержимое страницы, которое предназначено только для 4ых. Что делать? Первая мысль, поставить условие для четверок, и заключить текст страницы в несколько document.write, но представляете, как трудно будет обновлять такую страницу? Короче - ужас.

Немного подумав, я вспомнил о замечательном теге комментария <!-. Если его поставить в конец кода выводимого для 3 версий браузеров, то тогда остальной код следующий за ним, превратится в комментарий, и станет невидимым. Ободренный таким умозаключением, я проделал это, и написал вот примерно так:
...
document.write("<P>Тут код для 3их браузеров</P><!-");
...
И запустил страницу. Нда, конечно ничего не заработало :о( Броузер посчитал, что все что следует после <!- в скрипте, тоже комментарий:
...
document.write("<P>Тут код для 3их браузеров</P><!- ");
-></Script>
...

Что, опять облом? И как заставить скрипт написать <!-? А оказалось очень просто! Надо его разделить на две части вот так:
...
document.write("<P>Тут код для 3их браузеров</P><!" + "-");
...
Но тег комментария надо бы и закрыть, а то никакого эффекта мы не получим. Если сделать это с помощью такого же скрипта, но в конце документа, то он не заработает. Поскольку, как Вы уже наверное догадались, этот скрипт попадет в первый сгенерированный комментарий. Поэтому -> надо ставить явно после </html>. Так что просто напишите <!- ->. Тег открытия уйдет в наш комментарий, а тег закрытия закроет все.

Вот и все!


Воспрепятствование автоматизированному миррорингу Веб-узла

Воспрепятствование автоматизированному зеркалированию Веб-узла

Некоторое время назад в рассылке Apache-Talk была дисскуссия, переросшая в сильнофилософские рассуждения о невозможности полной защиты узла от скачивания с доказательствами и т.п., что говорит о хорошем уровне академического образования уважаемых коллег по майл-листу...

Интересное наблюдение как-то мне поведал один из работающих у нас мужичков: "Если дать.... ну нерешаемую задачу студенту (выпускнику) МГУ (в его случае МехМат) и ФизТеха, то (с большой вероятностью) МГУ'шник начнет доказывать невозможность решить задачу, а ФизТех'овец начнет пытаться ее решить. Пусть не на всех возможных значениях параметров, но все же... ау, МехМат! И хоть сам я являюсь представителем школы МГУ (правда, не МехМат а ВМиК), попробую (хоть тут) чуток поопровергнуть такое мнение относительно родной Альма-матер.

Врочем, извините за столь длинное вступление. Итак, хочу немножко поделиться мыслями (возникшими в связи со вчерашней бессонницей) на тему "воспрепятствование автоматизированному миррорингу Веб-узла".

1. JavaScript. До чего ж люблю я Netscape... такую классную штуку придумали. А подлый Microsoft его передрал - а когда пишешь, что MS JavaScript не совместим с оригинальным - отписывают: "У нас, мол, не JavaScript, а JScript." Почувствуйте разницу.

Итак, как мы обычно описываем ссылки?

<a href='file.html'>Ссылка</a>.

А кто сказал, что это единственный способ их описания? Вот почти тоже самое:

<a href='javascript:document.location =
 "file.html"'>Ссылка</a>

Каюсь, таким образом отсекаются пользователи Lynx'а и некоторых браузеров, не поддерживающих JavaScript. Но если человеку сильно нужно посмотреть сайт, то он запустит-таки Netscape. А в связи с тем, что сайт хотят смироррить, вероятность этого "нужно" довольно высока.

Конечно, можно научить wget брать подобные ссылки. Но можно ведь пойти дальше:

<script> Link = 'file.html' </script>
...
<a href='javascript:document.location = Link'
>Ссылка</a>

Кроме того - а кто сказал, что ссылки необходимо размещать явно в теле документа:

<script> document.write(unescape('%3C') + 'a hr' +
'ef="file.html">' + 'Ссылка' + unescape('%3C') + '/a>')
 </script>

Понятное дело, этим дело далеко не ограничивается, и тут уже приходится писать wget, который занимается интерпретацией JavaScript. Причем не как статический JavaScript, а динамический (где-то в конце документа):

<script> setTimeout
('Link = "file1.html"', 1000) </script>

Пойди догадайся, что и в какой момент будет в переменной Link. Решениеи идти по всем возможным значениям переменных натыкается на такой веселый код:

<script>

function a()
{
  if (confirm('Are you stupid?')) while(1)
   do_nothing();
  location = 'file.html';
}
...
<a href='javascript:a()'>Ссылка</script>

Как думаете, чем будет заниматься такой интеллектуальный wget?

Таким образом, грамотное использование JavaScript практически решает задачу. Задача написания столь высокоинтеллектуального wget'а, на мой взгляд, настолько дорогостоящая, что никто этим заниматься не будет.

2. "Добрые ссылки". Напишем простенький CGI (поклонники Perl'а не бить! Ну не силен я в Perl'е):

// surprise.c ==> surprise.cgi
#include <stdio.h>
#include <stdlib.h>

main()
{
  int i = 0;

  if (getenv("QUERY_STRING")) i = atoi(getenv(
  "QUERY_STRING"));

  printf("Content-type: text/html");
  printf("\n");
  printf("<a href='surprise.cgi?%d'>%d</a>\n", i, i);
}

При вызове 'surprise.cgi' он выдает ссылку на 'surprise.cgi?1', тот в свою очередь на 'surprise.cgi?2' .... "У попа была собака". Как думаете, за сколько умный wget выкачает такую ссылку?

Только не говорите, что wget не будет качать CGI. Никаких нет проблем (с помощью аккуратной настройки Apache "ErrorDocument 404" и nph-CGI) сделать директорию, при обращении к которой последовательно выдаются ссылки на '1.html', '2.html' ...

Проблема так-же не решается ограничением глубины поиска для wget'а. Никто не мешает модифицировать предыдущий вариант так, чтоб при обращинии к файлу с любым именем в данной директории выдается HTML содержищий 10 (100) ссылок на файлы с произвольными именами в той-же директории. При глубине скачивания три (что явно недостаточно ) wget'у придется скачать с сайта 100 + 100*100 + 100*100*100 файлов. Не знаю, сколько времени ему, бедолаге, на это потребуется.

Наводните документы ссылками типа

<a href='surptise.cgi'>Don't click this link!!!</a>

или

<a href='/surprise/xmm.html'><img src='1x1.gif'
 border=0 heigth=1 width=1></a>

и wget будет бессилен... где-то в Инете я видел сайт, который генерирует N (задается пользователем через форму) килобайт почти связанного русского текста. Сгенерите такой текст, разбавив его ссылками в '/surprise/' ...

Подведу некоторые итоги. Никто не говорит о теоретическом решении этой задачи. Теоретически можно скачать все. Практически же... Теоретически можно поставить друг на дружку 10 яиц. Практически... да хоть одно поставьте! Разве что Наполеон (поправьте меня, если это не он) решил эту задачу надломом яйца :-)) Да, можно яйцо поставить на конец раскрутив его (surprise.cgi, href='javascript:location=..'), а 10 уже никак не поставишь.... хотя это и возможно теоретически.

Проблема, как мы видим, решается довольно простыми методами. Никто не говорит об абсолютном запрещении мирроринга. Но очень сильно подпортить кровь желающим это сделать можно элементарно, что делает задачу оного мирроринга практически невыполнимой и малорентабельной.


клуб веб-дизайнеров >>> Волшебное заклинание

Волшебное заклинание (заметки о полях)

Для начала я приведу небольшой фрагмент из книги Дмитрия Кирсанова "Веб-дизайн" (с.194), чтобы довести до вас суть проблемы.

Заметки о полях. Упомянутые только что поля -- это не поля, которые устанавливает вокруг содержимого страницы дизайнер, а те небольшие просветы вдоль левой и верхней границ окна, которые оставляет сам броузеер. Пользователь не может ни поместить что-либо в эту мертвую зону, ни повлиять на размеры полей средствами стандартного HTML. Еще хуже то, что величина этих полей непостоянна — она зависит от марки и версии броузера и от установленного в данный момент базового кегля шрифта. Из-за этого возникают трудности с точным совмещением фонового изображения и материала переднего плана — ведь броузер настилает фоновую картинку по всему пространству окна вплоть до рамки, вне зависимости от того, насколько отодвинуты от этой рамки текст и изображения переднего плана.

Примечание: проведя небольшое исследование, я составил вот такую табличку размеров отступов для некоторых браузеров. -- А. В.

браузер | Netscape Navigator |  |  |  | MS Internet Explorer |  |  | Opera
платформа | win32 |  | unix |  | win32 |  |  | win32
версия | 3.x | 4.x | 3.x | 4.x | 3.x | 4.x | 5.0 | 3.5x
отступы | 8px | 8px | 7px | 7px | 10px | 10px | 10px | 10px

Оговорка относительно "стандартного HTML" не случайна -- очевидно, поля эти были введены именно для того, чтобы размеченные в минималистском, академическом стиле тексты можно было читать, не стукаясь взглядом о вплотную прижатую к тексту рамку окна. Предоставляя со временем все больше оформительской свободы автору страницы броузеры не могли обойти своим вниманием и этот аспект. Так, MSIE поддерживает атрибуты leftmargin н topmargin тега BODY, позволяющие устанавливать любую (в том числе нулевую и отрицательную) величину полей страницы. Однако и стандарт HTML 4.0, и броузер Netscape игнорируют эти «MSlE-only» атрибуты.

Более идеологически правильный, хотя и выходящий за рамки HTML подход - использование CSS-свойств margin-left и margin-top для тега BODY. К сожалению, незрелость реализации CSS не позволяет воспользоваться даже таким невинным трюком: дело в том, что если MSIE отмеряет поле от рамки окна, то NC принимает отправную точку то "поле по умолчанию", которое имеет место в отсутствие CSS. Поэтому, чтобы прижать содержимое страницы вплотную к левой рамке окна, в MSIE достаточно сказать <BODY style="margin- left: 0рх">, тогда как в NC вместо 0рх придется подбирать некую отрицательную величину (около -7рх).

Желание обойтись без CSS заставляет некоторых дизайнеров прибегать к еще более замысловатому трюку. Пользуясь тем, что у тега FRAME есть поддерживаемые обоими броузерами атрибуты marginwidth и marginheight, они заключают всю страницу в единственный фрейм с тем, чтобы иметь возможность точно устанавливать величину полей.

А вот теперь давайте разберемся!

Во-первых, нам известен простейший способ создания одинаковых полей (в том числе нулевых) для браузеров NN и MSIE начиная с 4-х версий. Для этого нужно, чтобы тег BODY выглядел следующим образом:

<BODY LEFTMARGIN="X" TOPMAGIN="Y" MARGINWIDTH="X" MARGINHEIGHT="Y">

Здесь X - левый отступ, а Y - верхний. Первые два атрибута понимает только MSIE, вторые два - только NN. (Opera не понимает ни тот, ни другой)

Но эти чисто визуальные атрибуты элемента BODY не входят ни в одну официальную спецификацию HTML, и их использование противоречит идеологии языка. Куда удобнее и правильнее было бы использовать CSS, ведь тогда достаточно иметь одну инструкцию в css-файле для всего сайта. Я полностью соглашаюсь с тов. Кирсановым - с помощью свойства magrin (margin-top, margin-left) невозможно создать одинаковые поля в обоих браузерах.

Тут есть один тонкий секрет!

Netscape 4.x знает, что такое CSS-позиционирование. Можно вложить все содержимое страницы в один общий слой:
<div id="MyBody" style="position:absolute; top:0; left:0">Содержимое страницы</div>
Но это как-то накладно. А вот если добавить такой style-атрибут к тегу BODY - результат будет неожиданный: Explorer и Opera его игнорируют (тут им подавай margin, а не position!), а Netscape создает нулевые (в нашем примере) поля. Остается только привести стили к общему знаменателю:

<style type="text/css">
       body { margin-left:0px; margin-top:0px;
              position:absolute; top:0; left:0}
</style>

Похоже, волшебное заклинание найдено!

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Шрифтовые пропорции

Иногда в полный рост встает такая проблема: относительные размеры шрифтов на странице заданы посредствам каскадных таблиц стилей, но необходимо, чтобы они (хотя бы приблизительно) сохранялись в браузерах, не поддерживающих технологию CSS. Использование тега FONT всегда считалось дурным тоном. Дело еще и в том, что <FONT> сам по себе ничего не делает без атрибутов SIZE, FACE и COLOR. Т.е. определить шрифт с размером SIZE=+2 через CSS невозможно.

Здесь на помощь приходят старые добрые теги BIG и SMALL. Напомню, что они примерно соответствуют SIZE=+1 и SIZE=-1. Там, где нам нужно применить мелкий шрифт, мы можем смело использовать парный тег <SMALL></SMALL>, понимаемый любым браузером, а для более точного воплощения творческих замыслов в блоке стилей добавим, например, такую строчку:

small {font-size: 80%; font-family: sans-serif};

Аналогично для <BIG>:

big {font-size: 120%; font-family: sans-serif};

Таким образом, теги SMALL и BIG позволяют нам использовать три уровня размера шрифта: мелкий, обычный и увеличенный. Этого вполне достаточно, учитывая, что есть еще теги заголовков H1-H6, которые также можно определять через таблицы стилей, и которые также не теряют своих свойств в старых браузерах, не поддерживающих эти самые таблицы стилей

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Опыты с табличными рамками
Один читатель обратился с такой проблемой: он захотел сделать таблицу с плоской однотонной рамкой, но получил не совсем то, чего хотел. Естественно, он использовал вот такую тривиальную конструкцию:
<TABLE border=1 bordercolor="#C0C0C0"
 bordercolorlight="#C0C0C0" bordercolordark="#C0C0C0">
<!-- везде указывается какой-то один цвет -->

Возникли две проблемы: во-первых, ни в NetscapeNavigator, ни в InternetExplorer нельзя таким образом сделать рамку толщиной в 1 пиксель - при указании параметра BORDER равным 1 создается рамка толщиной 2 пикселя; во-вторых, в Нетскейпе даже таким хитрым способом нельзя избавиться от "рельефа" рамки.

Тут есть одно решение, хотя и не очень красивое.
1) Создаем таблицу (с одной ячейкой) с параметрами BORDER=0 CELLPADDING=0 CELLSPACING=0 BGCOLOR="#000000" (указываем значение цвета, которым мы хотим закрасить рамку);
2) В полученную таблицу вкладываем собственно основную таблицу с нужным количеством строк/столбцов и с параметрами BORDER=0 CELLSPACING=1 (здесь "1" - толщина будущей рамки);
3) Теперь для каждой ячейки вложенной таблицы укажем ее цвет, например <TD BGCOLOR="#FFFFFF">

В итоге получится что-то вроде этого:

Название таблицы

Ячейка 1

Ячейка 2

Ячейка 3

Вот полный HTML-код этой таблицы:

<table border="0" cellpadding="0"
cellspacing="0" width="400" bgcolor="#000000">
<tr>
<td width="100%"><div align="center"><center>
<table border="0" cellpadding="5" cellspacing="1"
 width="100%">
<tr>
<td colspan="3" width="100%" bgcolor="#C0C0C0">
<p align="center">Название таблицы</p>
</td>
</tr>
<tr>
<td width="33%" bgcolor="#FFFFFF"><p align="center">
Ячейка 1</p>
</td>
<td width="33%" bgcolor="#FFFFFF"><p align="center">
Ячейка 2</p>
</td>
<td width="34%" bgcolor="#FFFFFF"><p align="center">
Ячейка 3</p>
</td>
</tr>
</table>
</center></div></td>
</tr>
</table>

bgcolor="#000000" - цвет рамки;
cellspacing="1"- толщина рамки

Для InternetExplorer можно и не прибегать к использованию вложенных таблиц, ограничившись лишь указанием различного цвета для фона таблицы и фона ячеек, однако в Netscape в этом случае промежутки между ячейками будут заполнены не фоновым цветом таблицы, а фоном самой страницы, т.е. будут "прозрачными". Приведенный же выше пример более универсален и лишь кажется громоздким.

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Кое-что о расслоении

Каждый настоящий дизайнер знает, что для создания классной композиции не обойтись без использования слоев. С появлением браузера Netscape Communicator 4, технологию слев можно применять непосредственно для оформления Web-страниц. Слой - это HTML-контейнер (тег LAYER, DIV или SPAN), в который можно помещать желаемое содержимое для последующего точного позиционирования на странице. Слоям можно задавать абсолютные координаты, относительную глубину, фон и т.д. Атрибуты слоя можно изменять с помощью JavaScript. Можно, например, нарисовать несколько картинок, представляющих кадры анимации, расположить их на разных слоях и затем, поочередно изменяя атрибут видимости, показывать поочередно картинки зрителю, создавая эффект анимации. Для перемещения картинки по экрану можно просто менять позицию слоя, на котором она расположена.

Я же хотел сказать о другом. Как известно, браузер InternetExplorer не поддерживают тег <LAYER> ни в 3-ей, ни в 4-ой версии, что фактически лишает всякого смысла применение этого тега и Java-скриптов в сочетании с ним, т. к. доля пользователей Netscape неуклонно снижается. Однако, если нужно добиться совместимости... В HTML есть ''законный" аналог тега <LAYER> - элемент <DIV>. Вот так будет выглядеть конструкция, совместимая с обоими браузерами:

<HTML>
<BODY>
  <LAYER ...><!-- открытие слоя в NN -->
  <NOLAYER>
  <DIV ...><!-- открытие слоя в IE -->
  </NOLAYER>
Содержимое слоя
  <NOLAYER>
  </DIV><!-- закрытие слоя в IE -->
  </NOLAYER>
  </LAYER><!-- закрытие слоя в NN -->
</BODY>
<HTML>

Ниже приведен пример кроссбраузерного HTML-кода

<html>
<body>
<layer name=MyLayer bgcolor="white" top=50 left=50
OnMouseOver="colorlayer('red')"
 OnMouseOut="colorlayer('white')">
<nolayer>
<div id=MyDiv style="position:absolute;
top:50; left:50">
</nolayer>
<p><font size="96">Dynamic Layer</font></p>
<nolayer>
</div>
</nolayer>
<script language=JavaScript1.2>
function colorlayer(changeto)
{ bgColor=changeto }
</script>
</layer>
<script language=VBSCRIPT>
Sub MyDiv_onmouseover()
document.all.MyDiv.style.backgroundColor="red"
End Sub
Sub MyDiv_onmouseout()
document.all.MyDiv.style.backgroundColor="white"
End Sub
</script>
</body>
</html>

Параметры тега <LAYER>
bacground="anyimage.gif" | фон слоя
height="100" width="100" | ширина и высота области
ID=MyLayer1 | идентификатор (имя) слоя
clip="10,10,50,50" | координаты границ
top=20 left=100 | сдвиг слоя относительно верхнего левого угла страницы

Комментарий читателя

Незачем вообще применять тэг <LAYER> - достаточно определить слой с помощью CSS-позиционирования даже для тэга <TABLE>, хотя лучше всего для этого подходят <DIV> или <SPAN>. Нетшкаф понимает это, хотя и с некоторыми ограничениями. Например, он проигнорирует установки размера шрифта для данного слоя или эффект и цвет рамки слоя. Пример - www.jeo.ru

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


>>> Кое-что о расслоении (продолжение)

Кое-что о расслоении (продолжение).

Продолжая тему, затронутую в предыдущей заметке, я приведу один пример из книги А.Хоумера "Dynamic HTML: справочник", которая сейчас активно продвигается издательством Piter-Press. Речь идет о совместимости шрифтов для браузеров NN и MSIE при использовании тега <DIV>.

Небезызвестный в моем городе вебмастер сервера jeo.ru в своем комментарии справедливо заметил, что Netscape понимает тег DIV в сочетании с CSS-позиционированием, но при этом теряются свойства шрифта. Однако, решить эту проблему оказалось довольно просто.

Рассмотрим пример создания рельефной надписи.

<html>
<head><title>3D title</title>
<style>
P { font-family: "Impact, sans-serif";
   font-size:96; color=red }
P.highlight { color:silver }
P.shadow { color:darkred }
</style>
<body bgcolor=#408080>
<div style="position:absolute; top:5; left:5;
width:600; height:100; margin:10">
<P class=shadow>Be happy!</P>
</div>
<div style="position:absolute; top:0; left:0;
width:600; height:100; margin:10">
<P class=highlight>Be happy!</P>
</div>
<div style="position:absolute; top:2; left:2;
width:600; height:100; margin:10">
<P>Be happy!</P>
</div>
</body>
</html>

В Internet Explorer все выглядит именно так, как и было задумано, но вот в Netscape куда-то пропадают заданные свойства шрифта (размер и гарнитура).
Эта проблема касается только тех свойств, которые пишутся через дефис: font-size, font-family. Кроме того, Netscape требует, чтобы шрифты задавались по отдельности. Следовательно тег <STYLE> должен выгдядеть так:

P { fontfamily: "Impact", "sans-serif"; fontsize:96;
 color=red }

Правда такую конструкцию не понимает уже Internet Explorer. Для достижения совместимости, нужно создать универсальный набор стилей. В нижеследующем примере первая часть работает в обоих браузерах и определяет цвет шрифта, вторая задает свойства для NN и третья для MSIE.

<style>
P { color:red }
P { fontfamily: "Impact", "sans-serif"; fontsize:96 }
P { font-family: "Impact", "sans-serif"; font-size:96 }
P.highlight { color:silver }
P.shadow { color:darkred }
</style>
Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Динамическое определение разрешения монитора

Недавно гражданин И. спросил нас о том, как заставить браузер пользователя загружать ту или иную версию страницы в зависимости от того, какое установлено экранное разрешение у этого пользователя. Пробуем ответить...

Для этой цели можно использовать небольшой Java-скрипт. Главная задача - это определить разрешение, а что после этого делать (вывести сообщение или перейти на другую страницу) - дело десятое. В общем случае принцип таков: сначала загружается страница-бланк, содержащая скрипт (ее также можно параллельно использовать для динамического определения браузера), затем автоматически осуществляется переход на соответствующую страницу, в зависимости от выполнения тех или иных условий.

<html>
<body>

<script language="JavaScript">
<!--
// Сначала определяем разрешение
if (self.screen) { // для браузеров 4-тых версий
width = screen.width
height = screen.height
}
else if (self.java) { // для NN3 (enabled Java)
var jkit = java.awt.Toolkit.getDefaultToolkit();
var scrsize = jkit.getScreenSize();
width = scrsize.width;
height = scrsize.height;
}else{
width = height = 'x' // для прочих браузеров
}
// Теперь выполняем нужное действие
if (width == 640) {
location.href='480x640/index.html' }
else if (width == 800) {
location.href='600x800/index.html' }
else if (width == 1024) {
location.href='768x1024/index.html' }
else if (width == 1280) {
location.href='hi-resolution/index.html' }
else {
location.href='default_resolution/index.html' }
//-->
</script>

</body>
</html>

Если вам не лень делать более одной версии страниц, можно использовать подобный HTML-код в качестве дефолтовой страницы вашего сайта. Во избежание проблем с браузерами, не поддерживающими JavaScript, в контейнер <HEAD> можно добавить мета-инструкцию переадресации:

<META HTTP-EQUIV=REFRESH CONTENT="2;
URL=default_resolution/index.html">
<!-- Если в течении 2 секунд браузер не выполнит наш
скрипт, то произойдет принудительный переход
 на указанную страницу -->

Вот и все.

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Специфические таблицы

Очень часто возникает необходимость создать таблицы подобного рода:

Дата | Счет
 | Дебет | Кредит
10.01.99 | 1253,00 | 10,00
13.02.99 | 5631,00 | 123,40

Достигается это с помощью атрибутов тэга <TD> COLSPAN и ROWSPAN, которые задают количество колонок и рядов, которые занимает ячейка. Чтобы было понятнее рассмотрим следующий пример:

ROWSPAN=2 Эта ячейка занимает 2 ряда. | COLSPAN=2 Эта ячейка занимает 2 колонки
 | Атрибуты COLSPAN и ROWSPAN не используются | Атрибуты COLSPAN и ROWSPAN не используются
Атрибуты COLSPAN и ROWSPAN не используются | Атрибуты COLSPAN и ROWSPAN не используются | Атрибуты COLSPAN и ROWSPAN не используются

Вот код этой таблицы:

<TABLE BORDER=1 CELLPADDING=5 CELLSPACING=0>
<TR>
<TD ROWSPAN=2 BGCOLOR="#ffe6ec">
ROWSPAN=2 Эта ячейка занимает 2 ряда.</TD>
<TD COLSPAN=2 BGCOLOR="#ffe6ec">
ALIGN=CENTER>COLSPAN=2 Эта ячейка занимает
2 колонки</TD>
</TR>
<TR>
<TD>Атрибуты COLSPAN и ROWSPAN не используются</TD>
<TD>Атрибуты COLSPAN и ROWSPAN не используются</TD>
</TR>
<TR ALIGN=RIGHT>
<TD>Атрибуты COLSPAN и ROWSPAN не используются</TD>
<TD>Атрибуты COLSPAN и ROWSPAN не используются</TD>
<TD>Атрибуты COLSPAN и ROWSPAN не используются</TD>
</TR>
</TABLE>

Другой пример таблицы, в которой часто возникает необходимость при создании страниц:

Текст
Текст
Текст
Текст
Текст

Чтобы было понятнее как это получается рассмотрим вот такой вариант этой таблицы:

Текст
Текст
Текст
Текст
Текст

Здесь мы использовали вложенную талицу, которая располагается внутри ячейки с атрибутом ROWSPAN:

<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1 WIDTH=300>
<TR>
<TD ROWSPAN=3>
<TABLE HEIGHT=100% WIDTH=100%
 CELLPADDING=0 CELLSPACING=0
 BORDER=1 BGCOLOR="#f0f0f0">
<TR><TD>Текст</TD></TR>
<TR><TD>Текст</TD></TR>
</TABLE>
</TD>
<TD>Текст</TD>
</TR>
<TR>
<TD>Текст</TD>
</TR>
<TR>
<TD>Текст</TD>
</TR>
</TABLE>

Атрибуты WIDTH=100% и HEIGHT=100% вложенной таблицы заставляют ее занять все доступное пространство в ячейке. Но Netscape Navigator не может правильно интерпретировать данные атрибуты.

Что еще почитать по этой теме

  • Рекомендации W3C HTML4.0. Таблицы
  • Getting Started with Tables (Webreview)
Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Локальная баннерная система на вашем сайте

Местная баннерная система
на вашем сайте.
Обычно, если сайт хороший, то он посещаем. А на посещаемых сайтах выгодно размещать рекламу (не обязательно коммерческую). Для этого на крупных серверах часто действуют собственные полноценные баннерные системы, основанные на CGI. Их еще называют "локальными крутилками". Администрация сервера полностью контролирует, какие баннеры демонстрируются на сайте и при желании осуществляет продажу показов.

Нечто подобное вы можете сделать на своем сайте без использования интерфейса CGI, с помощью собственной фантазии и небольшого Java-скрипта. Конечно, речь идет о случае, когда необходимо показывать на сайте небольшое количество - десяток-другой - заранее заготовленных баннеров. Средствами JavaScript можно контролировать количественное соотношение и временные (правда, относительно клиента) рамки показов отдельных баннеров.

Приведу простой пример, в котором все баннеры имеют равные шансы быть показанными на странице.

 <SCRIPT language="JavaScript">
// <!-- Main
function banners()
{

// Перечисляем ссылки и соответствующие им картинки.
// Здесь их 5, но количество может быть любым.

this[1]="http://www.ваша_ссылка_01.ru";
this[2]="http://ваша_картинка_01.gif";

this[3]="http://www.ваша_ссылка_02.ru";
this[4]="http://ваша_картинка_02.gif";

this[5]="http://www.ваша_ссылка_03.ru";
this[6]="http://ваша_картинка_03.gif";

this[7]="http://www.ваша_ссылка_04.ru";
this[8]="http://ваша_картинка_04.gif";

this[9]="http://www.ваша_ссылка_05.ru";
this[10]="http://ваша_картинка_05.gif";

// Осуществляем случайный выбор баннера с привязанной
// и нему ссылкой (не правда ли, простой механизм?) и пишем
// соответствующий фрагмент HTML-кода. Интервал, в котором
// выбирается переменная j, должен соответствовать общему
// количеству баннеров.

if ((navigator.appName == "Netscape") && (parseInt(navigator.appVersion.substring(0,1))
< 3)) { return(' '); }
var j=(new Date()).getSeconds() % 5;
document.write('<a href=' + this[2*j+1] + ' target=blank><img src=' +
this[2*j+2] + ' border=0 width=468 height=60></a>');
return(' ');
}
// -->
</SCRIPT>
<SCRIPT language="JavaScript">
// <!-- Main
banners();
// -->
</SCRIPT>

Вам нужно лишь скопировать код скрипта и заменить/добавить ссылки. Естественно, кроме картинок можно выводить сопутствующий текст.

Если вам очень хочется, чтобы пользователь не мог узнать, куда ведет баннер, посмотрев в строку состояния, то можно вместо прямых ссылок использовать ссылки на map-файлы (сервер-ориентированные ImageMap). На том, как это делается, остановлюсь в одной из следующих заметок.

Когда есть необходимость размещать приведенный выше код баннера сразу на многих страницах сайта (или даже на нескольких сайтах сразу), гораздо удобнее хранить его в отдельном файле и включать в HTML-страницы посредствам SSI (Server Side Includes). Конечно, если ваш сервер это позволяет. При этом достаточно изменить код баннера один раз, чтобы он изменился на всех страницах. Нужно всего лишь прописать в разметке страниц строчку
<!--#include virtual="banner_file"-->
где banner_file - виртуальный путь к файлу, в котором находится скрипт баннера.

Конечно, сказанное в последнем абзаце звучит довольно дико, но все-таки, SSI - это вполне самостоятельная технология, которую можно использовать не только в сочетании с CGI-скриптами.

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Создание диаграмм с помощью HTML

Если Вам приходилось размещать на страничках какие-либо диаграммы, то Вы меня поймете...

Для тех, кто пока не испытал такого кайфа, объясню.

Дело в том, что Internet - это не только (и не столько) сочетания цветов, картинки, разные классные прибамбульки, которые радуют глаз, это еще и обычная информация, за которой, собственно, люди заходят на сайт.

Предположим, ваш сайт посвящен компьютерам, и вы хотите положить туда свежую статейку о новом супер-модеме, которую вам вчера принесли (или вы ее сами написали) в формате Word (только не говорите, что вы всегда используете Notepad). В этой вашей статейке есть серьезное описание, пара таблиц и диаграмма. Деваться некуда, в HTML конвертируем Word'ом, получаем текст HTML, который после чистки весит 20K, и диаграммку в виде GIF'а, который весит 60K. Итого 80K. Одну четверть клиент платит за содержательную статью, три четверти - за удовольствие созерцать одну диаграмму.

Проблему можно решить тремя способами:

    1. Плюнуть на клиента (как обычно).
    2. Выкинуть диаграмму (тогда зачем было ее строить?)
    3. Построить диаграмму в виде таблицы HTML.
Новый
120
Другой
100
Совсем голимый

10

Хотите - верьте, хотите - нет, но вся эта красота занимает ровно 903 байта!
Фрагмент текста этой диаграммы:

<TABLE BORDER=0 BGCOLOR=#AAFFFF WIDTH=100%
><TR><TD WIDTH=15%>
<TABLE BORDER=0 WIDTH=100%><TR><TD>
<FONT COLOR=#000000>
Новый</TD></TR></TABLE></TD><TD>
<TABLE BORDER=0 WIDTH=100% HEIGHT=25><TR>
<TD BGCOLOR=#AA5555 WIDTH=100% ALIGN=RIGHT>
<FONT COLOR=#FFFFFF>
120
<br></TD><TD>
</TD></TR></TABLE></TD></TR><TR><TD WIDTH=15%>

......................

<FONT COLOR=#000000>
10
</TD></TR></TABLE></TD></TR></TABLE>

Что вы говорите? Как написать такой бред? Прекрасный вопрос! Можно открыть наш любимый Notepad (калькулятор, взять бумагу и карандаш), и через час диаграмма готова!

А можно зайти на эту страничку, и воспользоваться находящимся там генератором диаграмм. Потом открыть наш любимый Notepad...

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


Технологии установки баннеров

Повесить баннер - дело не хитрое, ведь баннер - это обычная картинка. (Или я не прав?) Но когда количество страниц сайта, на которых нужно установить баннеры превышает одну, возникает проблема. Браузер загрузив картинку с сервера, сохраняет ее в кэше и при переходе на другую страницу с такой же картинкой, уже не загружает ее с сервера, а забирает из временной папки на диске пользователя. То есть, если URL у всех баннеров на сайте будут в точности одинаковыми, то посетителю на всех страницах будет демонстрироваться один и тот же баннер. Чтобы избежать такого абсурда, в адрес баннера (а точнее скрипта, отвечающего за выбор баннера) обычно добавляется специальный параметр - случайное число. Например, вот так выглядит код для размещения на странице баннера системы W3D Banner Exchange:

Пример 1. HTML-код для установки баннера W3D
<a href="http://www.exchange.w3d.ru/cgi-bin/click?id= 49&num=292871">
<img src="http://www.exchange.w3d.ru/cgi-bin/ex?id= 49&num=292871"
alt="W3D Banner Exchange" border="0" width="468" height="60"></a>

Все, что находится в URL после знака "?" - это параметры, передаваемые программе баннерообмена через интерфейс CGI. Здесь значение num должно быть уникальным для каждого баннера (в пределах сайта) числом, тогда при каждом переходе на следующую страницу будет загружаться новый баннер.

Однако, если сайт состоит из большого числа страниц, придется тратить уйму времени на ручную "генерацию" случайных чисел. Особенно, если нужно время от времени менять представленные на сайте баннерные системы. Решить проблему можно как на стороне клиента, так и на сервере. Причем, использование клиентского скрипта хотя и менее корректное, но все же более "результативное" решение, т. к. при этом новый баннер будет загружаться даже при повторной загрузке страницы из кэша.

Пример 2. Код для установки баннера W3D с помощью JavaScript
<script language='JavaScript'>
<!--
var tmp = Math.round((Math.random() * (1000000 - 1)));
document.write("<a href='http://www.exchange.w3d.ru/cgi-bin/click?id=
    49&num=" + tmp + "' target='_top'>");
document.write("<img src='http://www.exchange.w3d.ru/cgi-bin/ex?id=
    49&num=" + tmp + "' width='468' height='60' border='0' alt='W3D Banner
    Exchange'></a>");
//-->
</script>

Переменная tmp - это и есть наша случайная величина, которая будет автоматически браться с потолка при каждом открытии страницы. Вспомните к тому же, что скрипт можно поместить в отдельном файле, а в HTML-разметке всех страниц добавить одну и ту же строчку, которую можно не менять при переходе на использование другой баннерной системы, например:

<script language="JavaScript" src="banner.js"></script>

Правда, нужно иметь в виду, что администрации рекламной сети может не понравится такое несанкционированное усовершенствование кода (если, конечно подобный Java-скрипт не предлагается по умолчанию).

Более красивое решение - применение серверной технологии SSI, поддерживаемой самым популярным в мире сервером Apache. Для этого потребуется небольшой Perl-скрипт, который будет создавать код баннера. В баннерных сетях типа Reklama.Ru, RB2 и некоторых других пользователям настоятельно рекомендуется взять соответствующий скрипт и поместить его в CGI-директории своего сервера. Вот так будет выглядеть универсальный скрипт в случае с нашей W3D Exchange:

Пример 3. Perl-скрипт для установки баннера W3D с помощью SSI
#!/usr/bin/perl

$SERVER_HOST='www.exchange.w3d.ru';
$PATH_HREF="http://$SERVER_HOST/cgi-bin/click";
$PATH_BANNER="http://$SERVER_HOST/cgi-bin/ex";

$login=$ENV{"QUERY_STRING"};

($IP=$ENV{"REMOTE_ADDR"})=~tr/.//d;
srand($$+$IP);
$tmp=int rand(10000000);

print "Content-type: text/html\n\n";
print "<a href=$PATH_HREF?id=$login&num=$tmp>";
print "<img src=$PATH_BANNER?id=$login&num=$tmp \n";
print "width=468 height=60 border=0 alt=\"W3D Banner Exchange.\"></a>";

Для размещения баннера на любой из страниц в ее разметку нужно добавить единственную строчку, например, такую:

<!--#include virtual="/cgi-bin/banner?49"-->

Во всех примерах 49 - это идентификатор конкретного пользователя в баннерной системе.
В последнем случае пользователю будет отправлена страница, собранная на сервере, и, заглянув в HTML-код, он увидит примерно то же, что и в примере 1.

А можно применить и серверную вставку Java-скрипта из примера 2:

<script language="JavaScript">
<!--#include virtual="banner.js"-->
</script>

Кроме рассмотренных здесь основных способов установки баннеров, есть, конечно, и другие технологии (к примеру, ASP), ваше дело - выбрать наиболее приемлемую и доступную для вашего сайта.

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


клуб веб-дизайнеров >>> Фокусы с закладками

Фокусы с закладками

Вы, конечно, встречали ссылки гласящие что-то типа "добавьте мою страничку в Избранное". При использовании браузера MSIE 4 (и более поздних), нажав на такую ссылку, вы запускаете Java-скрипт, который всего-навсего выполняет за вас команду:
И збранное > Добавить в избранное...

Главная прелесть такого скрипта в том, что он позволяет вешать закладку на главную страницу непосредственно с любой другой страницы сайта, и при этом название закладки по-умолчанию может отличатся от названия страницы, обозначенного тегом <TITLE>.

Приведенный ниже скрипт выполняет следующие действия:

  1. Если страница отображается в Internet Explorer версии 4.0 или выше, печатается ссылка "добавьте закладку" на функцию установки закладки AddBookmark().
    При нажатии на эту ссылку, браузер выдает стандатртное окошко с предложением повесить ссылку на URL http://www.design.rsu.ru c названием "Клуб Веб-дизайнеров".
  2. Если используется какой-то другой браузер, то выводится обычная гиперссылка "добавьте закладку" на страницу for dummers, объясняющую, как лучьше сделать закладку. Хотя это может быть ссылка на главную страницу, или вовсе не ссылка.

<script language="JavaScript">
<!--
function AddBookmark()
{
window.external.addFavorite('http://www.design.rsu.ru', 'Клуб Веб-дизайнеров')
}

if (navigator.appName.indexOf('Microsoft')>=0 &&
navigator.appVersion.indexOf(4.0)>=0) {
document.write('<a href=# onClick="AddBookmark();return false">добавьте закладку</a>.')
}
else
{
document.write('<a href=add.htm>добавьте закладку</a>.')
}
//-->
</script>

Разобрались. Идем дальше. А что если попробовать сделать таким образом закладку на адрес e-mail (в реальной жизни это может быть, например, адрес какого-нибудь дискуссионного листа рассылки). Я попробовал - получилось, и что замечательно, вместо синей литеры "е" возле закладки появилась маленькая пиктограмка почтового клиента The Bat!
ICQ (с версии 99a), как известно, тоже умеет добавлять свою эмблемку в закладки.

Остается только разобраться, как впихнуть 5-му Explorer'у нашу собственную иконку, когда посетитель сайта добавляет его в Favorites. Как выяснилось, при попытке сделать закладку, MSIE 5 посылает серверу запрос на файл favicon.ico. Если иконка есть в директории страницы, то она появится в Favorites.

Если вы решили самовыразится таким необычным способом, сделайте файл картинки в формате ico размером 16x16 пикселей. Если не хотите экономить на размере файла иконки, можете использовать палитру ThrueColor. После этого поместите ее в директорию сервера, в которой хранятся ваши html-файлы. Если нужно использовать иконку с другого сервера, или специальную иконку для конткретной страницы, можно использовать элемент LINK:
<head>
<LINK REL="Shortcut Icon" HREF="http://www.somedomain.com/logo.ico">
</head>

Вполне возможно, что вам никогда не приходилось создавать пиктограммы в формате ico, и вы не знаете как это делать. В этом случае могу порекомендовать программный продукт Microangelo 98 (www.impactsoft.com) - это целый пакет утилит для просмотра, рисования, редактирования иконок и курсоров и вытаскивания их из dll, exe и других файлов. 30-дневную shareware-версию можно скачать, к примеру, на neosoft.ru. Размер - 1,32 Мб.

Все заметки: 1 2 3 4 5 6 7 8 9 11 12 оглавление


клуб веб-дизайнеров >>> Веб строительство

Секреты web-design'а
для мастеров

Фокусы с закладками
Технологии установки баннеров
Создание диаграмм с помощью HTML*
Локальная баннерная система на вашем сайте
Специфические таблицы*
Динамическое определение разрешения монитора
Кое-что о расслоении. Часть2
Кое-что о расслоении. Часть1
Опыты с табличными рамками
Шрифтовые пропорции (еще о совместимости)
Волшебное заклинание (заметки о полях)


Пример 1

Привет!

Это простейший пример HTML-документа.

Этот *.html-файл может быть одновременно открыт и в Notepad, и в Netscape. Сохранив изменения в Notepad, просто нажмите кнопку Reload ('перезагрузить') в Netscape, чтобы увидеть эти изменения реализованными в HTML-документе.


Пример 2

Привет!

Это чуть более сложный пример HTML-документа

Теперь мы знаем, что абзац можно выравнивать не только влево,

но и по центру

или по правому краю.


Пример 3

Стих

Автор неизвестен

Однажды в студеную зимнюю пору
Сижу за решеткой в темнице сырой.
Гляжу - поднимается медленно в гору
Вскормленный в неволе орел молодой.

И шествуя важно, в спокойствии чинном,
Мой грустный товарищ, махая крылом,
В больших сапогах, в полушубке овчинном,
Кровавую пищу клюет под окном.


Пример 4

Коллекция горизонтальных линий


Пример 5

Шрифтовое выделение фрагментов текста

Теперь мы знаем, что фрагменты текста можно выделять жирным или наклонным шрифтом. Кроме того, можно включать в текст фрагменты с фиксированной шириной символа (имитация пишущей машинки)

Кроме того, существует ряд логических стилей:

EM - от английского emphasis - акцент
STRONG - от английского strong emphasis - сильный акцент
CODE - для фрагментов исходных текстов
SAMP - от английского sample - образец
KBD - от английского keyboard - клавиатура
VAR - от английского variable - переменная


Пример 6

HTML поддерживает несколько видов списков

Ненумерованные списки
Элементы ненумерованного списка выделяются специальным символом и отступом слева:
  • Элемент 1
  • Элемент 2
  • Элемент 3
Нумерованные списки
Элементы нумерованного списка выделяются отступом слева, а также нумерацией:
  1. Элемент 1
  2. Элемент 2
  3. Элемент 3
Списки определений
Этот вид списков чуть сложнее, чем два предыдущих, но и выглядит более эффектно.

Помните, что списки можно встраивать один в другой, но не следует закладывать слишком много уровней вложенности.

Обратите внимание, что внутри элемента списка может находиться несколько абзацев. Все абзацы при этом будут иметь одинаковое левое поле.


Пример 7

Связывание

С помощью ссылок пользователь может переходить к другим файлам (например, к оглавлению этого руководства).

Можно дать пользователю возможность выгружать файлы (например, это руководство в формате Microsoft Word 2.0) на его локальный диск.

Можно дать пользователю возможность послать почту (например, автору этой странички).


Пример 8

Изображения

Изображение можно встроить очень просто:

Кроме того, изображение можно сделать "горячим", то есть осуществлять переход при нажатии на изображение:


Пример 9

Слайд-демонстрация цветовых гамм
с помощью META-инструкции Refresh

Черный текст на белом фоне


Пример 9a

Слайд-демонстрация цветовых гамм
с помощью META-инструкции Refresh

Белый текст на черном фоне


Пример 9b

Слайд-демонстрация цветовых гамм
с помощью META-инструкции Refresh

Синий текст на сером фоне


Пример 10

Простейшая таблица

У таблицы может быть заголовок
Первая строка, первая колонка | Первая строка, вторая колонка
Вторая строка, первая колонка | Вторая строка, вторая колонка


Пример 11

Простейшая форма


Пример 12

Несколько более сложная форма

Расскажите немного о себе...

Указывать подлинные данные совсем не обязательно. Для целей демонстрации вполне подойдут и вымышленные.

Имя:
Фамилия:
Пол: мужской женский
Возраст: лет


Практическое руководство по HTML

Глава 1. Вводные замечания

  • Для чего нужно это руководство?
  • Что такое HTML?
  • Что Вам понадобится для освоения HTML?
  • Необходимое отступление о редакторах HTML

Глава 2. Наш первый HTML-документ

  • Как устроен HTML-документ
  • Обязательные метки
    • <html> и </html>
    • <head> и </head>
    • <title> и </title>
    • <body> и </body>
    • <H1> и </H1> — <H6> и </H6>
    • <P> и </P>

Глава 3. Внутри абзаца

  • Непарные метки
    • Конец строки: <BR>
    • Горизонтальная линия: <HR>
    • &-последовательности
    • Комментарии
  • Форматирование шрифта
    • Физические стили
    • Логические стили

Глава 4. Организация текста внутри документа

  • Ненумерованные списки: <UL> и </UL>
  • Нумерованные списки: <OL> и </OL>
  • Списки определений: <DL> и </DL>
  • Вложенные списки
  • Форматированный текст: <PRE> и </PRE>
  • Текст с отступом: <BLOCKQUOTE> и </BLOCKQUOTE>

Глава 5. За пределами HTML-файла

  • Связывание
  • Изображения в HTML-документе

Глава 6. Тайное становится явным, или Видимое действие невидимых меток

  • Цветовая гамма HTML-документа
  • Заголовок HTML-документа: что в нем может быть интересного?

Глава 7. Таблицы

  • Для чего нужны таблицы?
  • Как устроена таблица

Глава 8. Формы

  • Для чего нужны формы?
  • Как устроена форма
    • Простейшая форма
    • Как форма собирает данные
  • Маленькая хитрость, или Как отправить форму почтой

Глава 9. Некоторые (предположительно полезные) советы по разработке и размещению HTML-страниц

  • Правила хорошего тона при разработке HTML-документов
  • Инструменты разработчика: пристрастные советы
  • Где можно бесплатно разместить свою страничку

к оглавлению справочника


Глава 1. Вводные замечания

Для чего нужно это руководство?

Это руководство задумано как учебник для тех, кто хочет публиковать документацию любого рода на глобальной компьютерной сети Интернет. Насколько велика Интернет, точно не знает никто. Ходят слухи, что в 1996 году число крупных машин (серверов) и локальных сетей, подключенных к Интернет, перевалило за 10 миллионов. Точное число пользователей просто никому не известно. Известно только, что оно очень велико — несколько сот миллионов человек по всему миру.

Чтобы опубликовать документ (здесь и далее под документом понимается файл, содержащий некоторую информацию) на Интернет, достаточно поместить его на сервер, постоянно подключенный к Интернет и способный общаться с другими серверами с помощью протокола передачи гипертекстов (HyperText Transfer Protocol, или http://). Совокупность таких серверов получила название "всемирной паутины" (World Wide Web, или WWW).

Разработку документов, однако, можно проводить и на компьютере, не имеющем выхода в Интернет. Автор, например, разрабатывал Руководство в текстовом процессоре Microsoft Word 2.0, а затем перенес в формат HTML и разместил на сервере Yellow Internet ( Любой желающий может бесплатно разместить на этом сервере свои файлы суммарным объемом до двух мегабайт.

Что такое HTML?

Термин HTML (HyperText Markup Language) означает "язык маркировки гипертекстов". Первую версию HTML разработал сотрудник Европейской лаборатории физики элементарных частиц Тим Бернерс-Ли.

Со времени создания первой версии HTML претерпел некоторые изменения. Как и многое другое в компьютерном мире, версии, или спецификации, HTML оказались пронумерованными. Известны спецификации 2.0, 3.0, 3.2 и 4.0. Текущую спецификацию HTML всегда можно найти на сервере W3C (http://www.w3.org/).

Все, о чем написано в Руководстве, будет гарантированно работать с программным обеспечением, поддерживающим любую известную на сегодня спецификацию HTML. Естественно, для этого пришлось опустить некоторые детали, которые описаны не во всех спецификациях или описаны в разных спецификациях по-разному. Однако могу Вас уверить, что Ваши документы будут полностью работоспособными и вполне приличными по внешнему виду.

Что Вам понадобится для освоения HTML?

Для освоения HTML по этому Руководству Вам понадобятся две вещи:

  1. Любой браузер, т.е., программа, пригодная для просмотра HTML-файлов. Мне лично очень нравится Netscape Navigator, поэтому именно его команды я и буду приводить, описывая операции с браузером.
  2. Любой редактор текстовых файлов, поддерживающий русский язык в выбранной Вами кодировке. Если на Вашем компьютере установлен Windows, вполне подойдет Notepad.

Мы будем использовать текстовый редактор для подготовки HTML-файлов, а браузер — как инструмент контроля за сделанным.

Свои первые HTML-файлы Вы будете разрабатывать у себя на локальном диске. Другими словами, компьютер, на котором Вы будете заниматься, может и не иметь подключения к Интернет. При этом один и тот же *.html-файл может быть одновременно открыт и в Notepad, и в Netscape Navigator. Сохранив изменения в Notepad, просто нажмите кнопку Reload ("перезагрузить") в Netscape Navigator, чтобы увидеть эти изменения реализованными в HTML-документе.

Необходимое отступление о редакторах HTML

В настоящее время широко используются два типа редакторов HTML:

  1. Редакторы типа "что видишь, то и получишь" (Netscape Navigator Gold, Microsoft Front Page). Пользователь не видит "внутренностей" документа, с которым он работает, точно так же, как при работе с текстовым процессором типа Microsoft Word или Word Perfect. Кстати говоря, существует довольно много конвертеров, способных преобразовывать документы, созданные в Microsoft Word или Word Perfect, в HTML-документы.
  2. Редакторы собственно HTML-текстов (HotDog, Ken Nesbitt Web Editor и многие другие). В процессе работы пользователь видит внутреннее содержание HTML-файла и может изменять его либо вручную, либо вызывая команды меню для вставки определенных элементов HTML. Работа с таким редактором очень похожа на работу с интерактивной средой программирования типа Microsoft Visual Basic или Borland Delphi.

Как я уже говорил, нам для изучения HTML понадобится только текстовый редактор и браузер. Когда Вы освоите HTML в минимальной степени, Вы сами сможете подобрать себе редактор по вкусу.


Глава 2. Наш первый HTML-документ

Как устроен HTML-документ

HTML-документ — это просто текстовый файл с расширением *.html (Unix-системы могут содержать файлы с расширением *.htmll). Вот самый простой HTML-документ:

<html>
     <head>
          <title>
               Пример 1
          </title>
     </head>
     <body>
          <H1>
               Привет!
          </H1>
          <P>
               Это простейший пример HTML-документа.
          </P>
          <P>
               Этот *.html-файл может быть
               одновременно открыт и в Notepad, и в Netscape.
               Сохранив изменения в Notepad, просто нажмите
                кнопку Reload ('перезагрузить') в Netscape,
                 чтобы увидеть эти изменения реализованными
                 в HTML-документе.
          </P>
     </body>
</html>

Если хотите, можно посмотреть этот пример прямо сейчас.

Для удобства чтения я ввел дополнительные отступы, однако в HTML это совсем не обязательно. Более того, браузеры просто игнорируют символы конца строки и множественные пробелы в HTML-файлах. Поэтому наш пример вполне мог бы выглядеть и вот так:

<html>
<head>
<title>Пример 1</title>
</head>
<body>
<H1>Привет!</H1>
<P>Это простейший пример HTML-документа.</P>
<P>Этот *.html-файл может быть одновременно открыт
 и в Notepad, и в Netscape.
Сохранив изменения в Notepad, просто нажмите кнопку
 Reload ('перезагрузить') в Netscape, чтобы увидеть
 эти изменения реализованными в HTML-документе.</P>
</body>
</html>

Как видно из примера, вся информация о форматировании документа сосредоточена в его фрагментах, заключенных между знаками "<" и ">". Такой фрагмент (например, <html>) называется меткой (по-английски — tag, читается "тэг").

Большинство HTML-меток — парные, то есть на каждую открывающую метку вида <tag> есть закрывающая метка вида </tag> с тем же именем, но с добавлением "/".

Метки можно вводить как большими, так и маленькими буквами. Например, метки <body>, <BODY> и <Body> будут восприняты браузером одинаково.

Многие метки, помимо имени, могут содержать атрибуты — элементы, дающие дополнительную информацию о том, как браузер должен обработать текущую метку. В нашем простейшем документе, однако, нет ни одного атрибута. Но мы обязательно встретимся с атрибутами уже в следующем разделе.

Обязательные метки

<html> ... </html>

Метка <html> должна открывать HTML-документ. Аналогично, метка </html> должна завершать HTML-документ.

<head> ... </head>

Эта пара меток указывает на начало и конец заголовка документа. Помимо наименования документа (см. описание метки <title> ниже), в этот раздел может включаться множество служебной информации, о которой мы обязательно поговорим чуть позже.

<title> ... </title>

Все, что находится между метками <title> и </title>, толкуется браузером как название документа. Netscape Navigator, например, показывает название текущего документа в заголовке окна и печатает его в левом верхнем углу каждой страницы при выводе на принтер. Рекомендуется название не длиннее 64 символов.

<body> ... </body>

Эта пара меток указывает на начало и конец тела HTML-документа, каковое тело, собственно, и определяет содержание документа.

<H1> ... </H1> — <H6> ... </H6>

Метки вида <Hi> (где i — цифра от 1 до 6) описывают заголовки шести различных уровней. Заголовок первого уровня — самый крупный, шестого уровня, естественно — самый мелкий.

<P> ... </P>

Такая пара меток описывает абзац. Все, что заключено между <P> и </P>, воспринимается как один абзац.

Метки <Hi> и <P> могут содержать дополнительный атрибут ALIGN (читается "элайн", от английского "выравнивать"), например:

<H1 ALIGN=CENTER>Выравнивание заголовка
 по центру</H1>

или

<P ALIGN=RIGHT>Образец абзаца с выравниванием
по правому краю</P>

Подытожим все, что мы знаем, с помощью примера 2:

<html>
<head>
<title>Пример 2</title>
</head>
<body>
<H1 ALIGN=CENTER>Привет!</H1>
<H2>Это чуть более сложный пример
 HTML-документа</H2>
<P>Теперь мы знаем, что абзац можно выравнивать
 не только влево, </P>
<P ALIGN=CENTER>но и по центру</P>
<P ALIGN=RIGHT>или по правому краю.</P>
</body>
</html>

С этого момента Вы знаете достаточно, чтобы создавать простые HTML-документы самостоятельно от начала до конца. В следующем разделе мы поговорим, как можно улучшить наш простой HTML-документ. Начнем с малого — с абзаца.


Глава 3. Внутри абзаца

Непарные метки

В этом разделе мы поговорим о метках, которые не подчиняются двум основным правилам HTML: все они непарные, а некоторые (так называемые &-последовательности) к тому же должны вводиться только маленькими буквами.

<BR>

Эта метка используется, если необходимо перейти на новую строку, не прерывая абзаца. Очень удобно при публикации стихов (см. пример 3).

<html>
<head>
<title>Пример 3</title>
</head>
<body>
<H1>Стих</H1>
<H2>Автор неизвестен</H2>
<P>Однажды в студеную зимнюю пору<BR>
Сижу за решеткой в темнице сырой.<BR>
Гляжу - поднимается медленно в гору<BR>
Вскормленный в неволе орел молодой.</P>
<P>И шествуя важно, в спокойствии чинном,<BR>
Мой грустный товарищ, махая крылом,<BR>
В больших сапогах, в полушубке овчинном,<BR>
Кровавую пищу клюет под окном.</P>
</body> </html>

<HR>

Метка <HR> описывает вот такую горизонтальную линию:

Метка может дополнительно включать атрибуты SIZE (определяет толщину линии в пикселах) и/или WIDTH (определяет размах линии в процентах от ширины экрана). В примере 4 приведена небольшая коллекция горизонтальных линий.

<html>
<head>
<title>Пример 4</title>
</head>
<body>
<H1>Коллекция горизонтальных линий</H1>
<HR SIZE=2 WIDTH=100%><BR>
<HR SIZE=4 WIDTH=50%><BR>
<HR SIZE=8 WIDTH=25%><BR>
<HR SIZE=16 WIDTH=12%><BR>
</body>
</html>

&-последовательности

Поскольку символы "<" и ">" воспринимаются браузерами как начало и конец HTML-меток, возникает вопрос: а как показать эти символы на экране? В HTML это делается с помощью &-последовательностей (их еще называют символьными объектами или эскейп-последовательностями). Браузер показывает на экране символ "<", когда встречает в тексте последовательность &lt; (по первым буквам английских слов less than — меньше, чем). Знак ">" кодируется последовательностью &gt; (по первым буквам английских слов greater than — больше, чем).

Символ "&" (амперсанд) кодируется последовательностью &amp;

Двойные кавычки (") кодируются последовательностью &quot;

Помните: точка с запятой — обязательный элемент &-последовательности. Кроме того, все буквы, составляющие последовательность, должны быть в нижнем регистре (т.е., маленькие). Использование меток типа &QUOT; или &AMP; не допускается.

Вообще говоря, &-последовательности определены для всех символов из второй половины ASCII-таблицы (куда, естественно, входят и русские буквы). Дело в том, что некоторые серверы не поддерживают восьмибитную передачу данных, и поэтому могут передавать символы с ASCII-кодами выше 127 только в виде &-последовательностей.

Комментарии

Браузеры игнорируют любой текст, помещенный между <!-- и -->. Это удобно для размещения комментариев.

<!-- Это комментарий -->

Форматирование шрифта

HTML допускает два подхода к шрифтовому выделению фрагментов текста. С одной стороны, можно прямо указать, что шрифт на некотором участке текста должен быть жирным или наклонным, то есть изменить физический стиль текста. С другой стороны, можно пометить некоторый фрагмент текста как имеющий некоторый отличный от нормального логический стиль, оставив интерпретацию этого стиля браузеру. Поясним это на примерах.

Физические стили

Под физическом стилем принято понимать прямое указание браузеру на модификацию текущего шрифта. Например, все, что находится между метками <B> и </B>, будет написано жирным шрифтом. Текст между метками <I> и </I> будет написан наклонным шрифтом.

Несколько особняком стоит пара меток <TT> и </TT>. Текст, размещенный между этими метками, будет написан шрифтом, имитирующим пишущую машинку, то есть имеющим фиксированную ширину символа.

Логические стили

При использовании логических стилей автор документа не может знать заранее, что увидит на экране читатель. Разные браузеры толкуют одни и те же метки логических стилей по-разному. Некоторые браузеры игнорируют некоторые метки вообще и показывают нормальный текст вместо выделенного логическим стилем. Вот самые распространенные логические стили.

<EM> ... </EM>

От английского emphasis — акцент.

<STRONG> ... </STRONG>

От английского strong emphasis — сильный акцент.

<CODE> ... </CODE>

Рекомендуется использовать для фрагментов исходных текстов.

<SAMP> ... </SAMP>

От английского sample — образец. Рекомендуется использовать для демонстрации образцов сообщений, выводимых на экран программами.

<KBD> ... </KBD>

От английского keyboard — клавиатура. Рекомендуется использовать для указания того, что нужно ввести с клавиатуры.

<VAR> ... </VAR>

От английского variable — переменная. Рекомендуется использовать для написания имен переменных.

Пример

Подытожим наши знания о логических и физических стилях с помощью примера 5. Заодно Вы сможете увидеть, как Ваш браузер показывает те или иные логические стили.

<html>
<head>
<title>Пример 5</title>
</head>
<body>
<H1>Шрифтовое выделение фрагментов текста</H1>
<P>Теперь мы знаем, что фрагменты текста можно
 выделять
<B>жирным</B> или <I>наклонным</I> шрифтом.
 Кроме того, можно
включать в текст фрагменты с фиксированной шириной
символа
<TT>(имитация пишущей машинки)</TT></P>
<P>Кроме того, существует ряд логических стилей:</P>
<P><EM>EM - от английского emphasis - акцент </EM><BR>
<STRONG>STRONG - от английского strong emphasis -
 сильный акцент </STRONG><BR>
<CODE>CODE - для фрагментов
исходных текстов</CODE><BR>
<SAMP>SAMP - от английского sample -
 образец </SAMP><BR>
<KBD>KBD - от английского keyboard -
клавиатура</KBD><BR>
<VAR>VAR - от английского variable -
 переменная </VAR></P>
</body>
</html>


Глава 4. Организация текста внутри документа

HTML позволяет определять внешний вид целых абзацев текста. Абзацы можно организовывать в списки, выводить их на экран в отформатированном виде, или увеличивать левое поле. Разберем все по порядку.

Ненумерованные списки: <UL> ... </UL>

Текст, расположенный между метками <UL> и </UL>, воспринимается как ненумерованный список. Каждый новый элемент списка следует начинать с метки <LI>. Например, чтобы создать вот такой список:

  • Иван;
  • Данила;
  • белая кобыла

необходим вот такой HTML-текст:

<UL>
<LI>Иван;
<LI>Данила;
<LI>белая кобыла
</UL>

Обратите внимание: у метки <LI> нет парной закрывающей метки.

Нумерованные списки: <OL> ... </OL>

Нумерованные списки устроены точно так же, как ненумерованные, только вместо символов, выделяющих новый элемент, используются цифры. Если слегка модифицировать наш предыдущий пример:

<OL>
<LI>Иван;
<LI>Данила;
<LI>белая кобыла
</OL>

получится вот такой список:

  1. Иван;
  2. Данила;
  3. белая кобыла

Списки определений: <DL> ... </DL>

Список определений несколько отличается от других видов списков. Вместо меток <LI> в списках определений используются метки <DT> (от английского definition term — определяемый термин) и <DD> (от английского definition definition — определение определения). Разберем это на примере. Допустим, у нас имеется следующий фрагмент HTML-текста:

<DL>
<DT>HTML
<DD>Термин HTML (HyperText Markup Language) означает
'язык маркировки гипертекстов'. Первую версию HTML
разработал сотрудник Европейской лаборатории физики
элементарных частиц Тим Бернерс-Ли.
<DT>HTML-документ
<DD>Текстовый файл с расширением *.html
(Unix-системы могут содержать файлы
с расширением *.htmll).
</DL>

Этот фрагмент будет выведен на экран следующим образом:

HTML
Термин HTML (HyperText Markup Language) означает 'язык маркировки гипертекстов'. Первую версию HTML разработал сотрудник Европейской лаборатории физики элементарных частиц Тим Бернерс-Ли.
HTML-документ
Текстовый файл с расширением *.html (Unix-системы могут содержать файлы с расширением *.htmll).

Обратите внимание: точно так же, как метки <LI>, метки <DT> и <DD> не имеют парных закрывающих меток.

Если определяемые термины достаточно коротки, можно использовать модифицированную открывающую метку <DL COMPACT>. Например, вот такой фрагмент HTML-текста:

<DL COMPACT>
<DT>А
<DD>Первая буква алфавита
<DT>Б
<DD>Вторая буква алфавита
<DT>В
<DD>Третья буква алфавита
</DL>

будет выведен на экран примерно так:

А
Первая буква алфавита
Б
Вторая буква алфавита
В
Третья буква алфавита

Вложенные списки

Элемент любого списка может содержать в себе целый список любого вида. Число уровней вложенности в принципе не ограничено, однако злоупотреблять вложенными списками все же не следует.

Вложенные списки очень удобны при подготовке разного рода планов и оглавлений.

Наши знания о списках можно вкратце свести в пример 6:

<html>
<head>
<title>Пример 6</title>
</head>
<body>
<H1>HTML поддерживает несколько видов списков </H1>
<DL>
<DT>Ненумерованные списки
<DD>Элементы ненумерованного списка выделяются
 специальным
символом и отступом слева:
<UL>
<LI>Элемент 1
<LI>Элемент 2
<LI>Элемент 3
</UL>
<DT>Нумерованные списки
<DD>Элементы нумерованного списка выделяются
отступом слева, а
также нумерацией:
<OL>
<LI>Элемент 1
<LI>Элемент 2
<LI>Элемент 3
</OL>
<DT>Списки определений
<DD>Этот вид списков чуть сложнее, чем два предыдущих,
 но и выглядит более эффектно.
<P>Помните, что списки можно встраивать один в другой,
 но не
следует закладывать слишком много уровней вложенности.
 </P>
<P>Обратите внимание, что внутри элемента списка
может находиться
несколько абзацев. Все абзацы при этом будут иметь
одинаковое левое поле. </P>
</DL>
</body>
</html>

Форматированный текст: <PRE> ... </PRE>

В самом начале мы говорили о том, что браузеры игнорируют множественные пробелы и символы конца строки. Из этого правила, однако, есть исключение.

Текст, заключенный между метками <PRE> и </PRE> (от английского preformatted — предварительно форматированный), выводится браузером на экран как есть — со всеми пробелами, символами табуляции и конца строки. Это очень удобно при создании простых таблиц.

Текст с отступом: <BLOCKQUOTE> ... </BLOCKQUOTE>

Текст, заключенный между метками <BLOCKQUOTE> и </BLOCKQUOTE>, выводится браузером на экран с увеличенным левым полем.


Глава 5. За пределами HTML-файла

Связывание

Как уже упоминалось в самом начале, сокращение HTML означает "язык маркировки гипертекстов". Про маркировку мы уже поговорили достаточно. Не пора ли перейти к гипертексту?

Прежде всего, что же такое гипертекст? В отличие от обыкновенного текста, который можно читать только от начала к концу, гипертекст позволяет осуществлять мгновенный переход от одного фрагмента текста к другому. Системы помощи многих популярных программных продуктов устроены именно по гипертекстовому принципу. При нажатии левой кнопкой мыши на некоторый выделенный фрагмент текущего документа происходит переход к некоторому заранее назначенному документу или фрагменту документа.

В HTML переход от одного фрагмента текста к другому задается с помощью метки вида:

<A HREF="[адрес перехода]">
выделенный фрагмент текста</A>

В качестве параметра [адрес перехода] может использоваться несколько типов аргументов. Самое простое — это задать имя другого HTML-документа, к которому нужно перейти. Например:

<A HREF="index.html">Перейти к оглавлению</A>

Такой фрагмент HTML-текста приведет к появлению в документе выделенного фрагмента Перейти к оглавлению, при нажатии на который в текущее окно будет загружен документ index.html.

Обратите внимание: если в адресе перехода не указан каталог, переход будет выполнен внутри текущего каталога. Если в адресе перехода не указан сервер, переход будет выполнен на текущем сервере.

Из этого следует одно очень важное практическое соображение. Если Вы подготовили к публикации некоторую группу HTML-документов, которые ссылаются друг на друга только по имени файла и находятся в одном каталоге на Вашем компьютере, вся эта группа документов будет работать точно так же, если ее поместить в любой другой каталог на любом другом компьютере, на локальной сети или... на Интернет! Таким образом, у Вас появляется возможность разрабатывать целые коллекции документов без подключения к Интернет, и только после окончательной готовности, подтвержденной испытаниями, помещать коллекции документов на Интернет целиком.

На практике, однако, часто бывает необходимо дать ссылку на документ, находящийся на другом сервере. Например, если Вы хотите дать ссылку на это руководство со своей странички, Вам придется ввести в свой HTML-документ примерно такой фрагмент:

<A HREF="http://www.yi.com/home/ChuvakhinNikolai/
index.html">
Практическое руководство по HTML Николая Чувахина</A>

При необходимости можно задать переход не просто к некоторому документу, но и к определенному месту внутри этого документа. Для этого необходимо создать в документе, к которому будет задан переход, некоторую опорную точку, или анкер. Разберем это на примере.

Допустим, что необходимо осуществить переход из файла 1.html к словам "Переход закончен" в файле 2.html (файлы находятся в одном каталоге). Прежде всего, необходимо создать вот такой анкер в файле 2.html:

<A NAME="AAA">Переход закончен</A>

Слова "Переход закончен" при этом никак не будут выделены в тексте документа.

Затем в файле 1.html (или в любом другом) можно определить переход на этот анкер:

<A HREF="2.html#AAA">Переход к анкеру AAA</A>

Кстати говоря, переход к этому анкеру можно определить и внутри самого документа 2.html — достаточно только включить в него вот такой фрагмент:

<A HREF="#AAA">Переход к анкеру AAA</A>

На практике это очень удобно при создании больших документов. В начале документа можно поместить оглавление, состоящее из ссылок на анкеры, расположенные в заголовках разделов документа.

Во избежание недоразумений рекомендуется задавать имена анкеров латинскими буквами. Следите за написанием имен анкеров: большинство браузеров отличают большие буквы от маленьких. Если имя анкера определено как AAA, ссылка на анкер aaa или AaA не выведет Вас на анкер AAA, хотя документ, скорее всего, будет загружен корректно.

Пока что мы обсуждали только ссылки на HTML-документы. Однако возможны ссылки и на другие виды ресурсов:

<A HREF="ftp://server/directory/file.ext">
Выгрузить файл</A>

Такая ссылка, если ей воспользоваться, запустит протокол передачи файлов и начнет выгрузку файла file.ext, находящегося в каталоге directory на сервере server, на локальный диск пользователя.

<A HREF="mailto:user@mail.box">Послать письмо</A>

Если пользователь совершит переход по такой ссылке, у него на экране откроется окно ввода исходящего сообщения его почтовой программы. В строке To: ("Куда") окна почтовой программы будет указано user@mail.box.

Разберем все, что мы знаем о связывании, с помощью примера 7.

<HTML>
<HEAD>
<TITLE>Пример 7</TITLE>
</HEAD>
<BODY>
<H1>Связывание </H1>
<P>С помощью ссылок можно переходить к другим файлам
(например, к <A HREF="index.html">оглавлению этого
руководства</A>).</P>
<P>Можно выгружать файлы (например,
<A HREF="ftp://yi.com/home/ChuvakhinNikolai/
html-pr.doc">это руководство в формате Microsoft
Word 2.0</A>) по FTP.</P>
<P>Можно дать пользователю возможность послать почту
 (например, <A HREF="mailto:nc@iname.com">
 автору этого руководства</A>).</P>
</BODY>
</HTML>

Изображения в HTML-документе

Встроить изображение в HTML-документ очень просто. Для этого нужно только иметь это самое изображение в формате GIF (файл с расширением *.gif) или JPEG (файл с расширением *.jpg или *.jpeg) и одну строчку в HTML-тексте.

Допустим, нам нужно включить в документ изображение, записанное в файл picture.gif, находящийся в одном каталоге с HTML-документом. Тогда строчка будет вот такая:

<IMG SRC="picture.gif">

Метка <IMG SRC="[имя файла]"> может также включать атрибут ALT="[текст]", например:

<IMG SRC="picture.gif" ALT="Картинка">

Встретив такую метку, браузер покажет на экране текст Картинка и начнет загружать на его место картинку из файла picture.gif. Атрибут ALT может оказаться необходимым для старых браузеров, которые не поддерживают изображений, а также на случай, если у браузера отключена автоматическая загрузка изображений (при медленном подключении к Интернет это делается для экономии времени).

Файл, содержаций изображение, может находиться в другом каталоге или даже на другом сервере. В этом случае стоит указать его полное имя (см. раздел "Связывание").

Разберем все, что мы знаем об изображениях, с помощью примера 8.

<HTML>
<HEAD>
<TITLE>Пример 8</TITLE>
</HEAD>
<BODY>
<H1>Изображения </H1>
<P>Изображение можно встроить очень просто: </P>
<P><IMG SRC="picture.gif"></P>
<P>Кроме того, изображение можно сделать "горячим",
 то есть осуществлять переход при нажатии на
 изображение:</P>
<P><A HREF="index.html"><IMG SRC="picture.gif">
</A></P>
</BODY>
</HTML>

Обратите внимание на вторую часть примера. Если ссылка на изображение находится между метками <A HREF="..."> и </A>, изображение фактически становится кнопкой, при нажатии на которую происходит переход по ссылке (в примере 8 переход происходит на оглавление Руководства).


Глава 6. Тайное становится явным,
или Видимое действие невидимых меток

Цветовая гамма HTML-документа

Если Вы читаете это Руководство на Интернет, Вы уже могли заметить, что главы Руководства и примеры несколько отличаются по цветовой гамме. Главы Руководства написаны черным по белому, а гипертекстовые ссылки выделены красным цветом. Я не знаю, как Вы видите примеры, но у меня они написаны черным по серому с выделением ссылок синим цветом.

Дело в том, что главы Руководства содержат HTML-метки, определяющие их цветовую гамму. Примеры таких меток не содержат, поэтому их цветовая гамма устанавливается браузером самостоятельно. Именно поэтому я не знаю, какие цвета Вы видите в примерах — это определяется настройкой Вашего браузера.

Цветовая гамма HTML-документа определяется атрибутами, размещенными внутри метки <BODY>. Вот список этих атрибутов:

bgcolor
Определяет цвет фона документа.
text
Определяет цвет текста документа.
link
Определяет цвет выделенного элемента текста, при нажатии на который происходит переход по гипертекстовой ссылке.
vlink
Определяет цвет ссылки на документ, который уже был просмотрен ранее.
alink
Определяет цвет ссылки в момент, когда на нее указывает курсор мыши и нажата ее правая кнопка, то есть непосредственно перед переходом по ссылке.

Цвет кодируется последовательностью из трех пар символов. Каждая пара представляет собой шестнадцатиричное значение насыщенности заданного цвета одним из трех основных цветов (красным, зеленым и синим) в диапазоне от нуля (00) до 255 (FF). Разберем несколько примеров.

bgcolor=#FFFFFF
Цвет фона. Насыщенность красным, зеленым и синим одинакова — FF (это шестнадцатиричное представление числа 255). Результат — белый цвет.
text=#000000
Цвет текста. Насыщенность красным, зеленым и синим одинакова — 00 (ноль). Результат — черный цвет.
link=#FF0000
Цвет гипертекстовой ссылки. Насыщенность красным — FF (255), зеленым и синим — 00 (ноль). Результат — красный цвет.

Кроме того, метка <BODY> может включать атрибут background="[имя файла]", который задает изображение, служащее фоном для текста и других изображений. Как и любое другое изображение, фон должен быть представлен в формате GIF (файл с расширением *.gif) или JPEG (файл с расширением *.jpg или *.jpeg).

Браузеры заполняют множественными копиями изображения-фона все пространство окна, в котором открыт документ, подобно тому, как при строительстве большие пространства стен покрывают маленькими (и одинаковыми) плитками.

Важно отметить, что цвет фона и изображение-фон никак не отображаются на бумаге при выводе HTML-документа на печать. Из этого есть одно важное практическое следствие: старайтесь не использовать текст белого цвета.

Заголовок HTML-документа: что в нем может быть интересного?

Заголовок HTML-документа, вообще говоря, не виден пользователю при просмотре. Однако в нем есть некоторые интересные особенности, которые стоит знать.

Заголовок HTML-документа может включать неограниченное количество так называемых META-инструкций. META-инструкция — это просто способ определить некоторую переменную путем указания ее имени (атрибут NAME) и значения (атрибут CONTENT). Вот некоторые наиболее типичные META-инструкции:

<META NAME="description" CONTENT="Это руководство -
 учебник для тех, кто хочет публиковать документацию
 любого рода  на глобальной компьютерной сети
 Интернет">

Такая META-инструкция определяет переменную description, содержащую краткое описание документа. Многие поисковые механизмы постоянно сканируют Интернет в поисках HTML-файлов, отыскивают в них эту переменную, сохраняют ее в своих базах данных и демонстрируют ее в ответ на запросы пользователей.

<META NAME="keywords" CONTENT="Интернет, HTML, WWW,
 руководство, публикация, гипертекст">

Такая META-инструкция определяет переменную keywords, содержащую набор ключевых слов, описывающих содержание документа. На практике поиск по ключевым словам очень удобно использовать при строительстве поискового механизма, работающего в пределах одного сервера (со стороны очень похоже, что именно такой подход был использован, например, при создании сервера технической поддержки фирмы Novell, ведущего производителя операционных систем для локальных компьютерных сетей).

Другая группа META-инструкций определяет эквиваленты команд протокола передачи гипертекстов. (Не переживайте, это не так страшно, как кажется на первый взгляд!) Разберем несколько примеров:

<META HTTP-EQUIV="Content-type"
CONTENT="text/html; charset=windows-1251">

Эта META-инструкция дает браузеру указание интерпретировать загружаемый документ как содержащий HTML-текст в кодировке Windows/1251.

<META HTTP-EQUIV="Content-type"
CONTENT="text/html; charset=koi8-r">

Эта META-инструкция абсолютно аналогична предыдущей, только в качестве кодировки указана КОИ-8.

<META HTTP-EQUIV="Refresh" CONTENT="[время];
 URL=[документ]">

Такая META-инструкция дает браузеру примерно такое указание: "Если через [время] секунд после завершения загрузки этого документа пользователь не перейдет к другому документу, начать загрузку ресурса [документ]". Более конкретно это может выглядеть, к примеру, вот так:

<META HTTP-EQUIV="Refresh"
CONTENT="10; URL=http://www.yi.com/home/
ChuvakhinNikolai/">

Если пользователь не предпримет никаких видимых действий в течение 10 секунд после загрузки документа, содержащего такую инструкцию, автоматически будет загружен документ http://www.yi.com/home/ChuvakhinNikolai/.

META-инструкцию Refresh можно использовать, например, если Вы перенесли некоторый документ с одного сервера на другой. Вместо копии документа на старом сервере можно оставить короткое сообщение о переносе, включающее META-инструкцию Refresh и адрес документа на новом сервере.

Если в качестве параметра [документ] подставить имя файла, содержащего звук, через [время] секунд после загрузки HTML-файла начнется загрузка и проигрывание этого звука (при условии, конечно, что браузер пользователя поддерживает формат этого звукового файла). Очень удобно для всякого рода приветственных речей.

Пример

В отличие от всех примеров, которые мы рассматривали ранее, пример 9 состоит не из одного, а из трех файлов. Используя META-инструкцию Refresh, мы создадим небольшой слайд-фильм из трех кадров (файлов e0009.html, e0009a.html и e0009b.html), которые будут циклически повторяться. Для остановки демонстрации нужно будет воспользоваться любой из гипертекстовых ссылок.

<HTML><!--файл e0009.html -->
<HEAD>
<TITLE>Пример 9</TITLE>
<META HTTP-EQUIV="Refresh" CONTENT="2;
 URL=e0009a.html">
</HEAD>
<BODY bgcolor=#FFFFFF text=#000000 link=#FF0000>
<H1>Слайд-демонстрация цветовых гамм <BR>
с помощью META-инструкции Refresh </H1>
<P>Черный текст на белом фоне </P>
<P>[<A HREF="index.html">Возврат к оглавлению</A>|
<A HREF="pr0006.html">Возврат к главе 6</A>]</P>
</BODY>
</HTML><!--конец файла e0009.html -->
<HTML><!--файл e0009a.html -->
<HEAD>
<TITLE>Пример 9a</TITLE>
<META HTTP-EQUIV="Refresh" CONTENT="2;
 URL=e0009b.html">
</HEAD>
<BODY bgcolor=#000000 text=#FFFFFF link=#FF0000>
<H1>Слайд-демонстрация цветовых гамм <BR>
с помощью META-инструкции Refresh </H1>
<P>Белый текст на черном фоне </P>
<P>[<A HREF="index.html">Возврат к оглавлению</A>|
<A HREF="pr0006.html">Возврат к главе 6</A>]</P>
</BODY>
</HTML><!--конец файла e0009a.html -->
<HTML><!--файл e0009b.html -->
<HEAD>
<TITLE>Пример 9b</TITLE>
<META HTTP-EQUIV="Refresh" CONTENT="2;
 URL=e0009.html">
</HEAD>
<BODY bgcolor=#C0C0C0 text=#0000FF link=#FF0000>
<H1>Слайд-демонстрация цветовых гамм <BR>
с помощью META-инструкции Refresh </H1>
<P>Синий текст на сером фоне </P>
<P>[<A HREF="index.html">Возврат к оглавлению</A>|
<A HREF="pr0006.html">Возврат к главе 6</A>]</P>
</BODY>
</HTML><!--конец файла e0009b.html -->


Глава 7. Таблицы

Для чего нужны таблицы?

На этот вопрос есть очевидный ответ: таблицы нужны для представления информации в табличном виде. Есть, однако, и менее очевидные ответы.

До настоящего времени мы имели дело с документами, в которых существовал только один "поток" текста. На практике иногда очень хочется расположить текст в несколько колонок. Таблица может в этом помочь.

Кроме того, таблица, состоящая из одной ячейки, может очень эффектно выделить фрагмент текста, на который Вы хотите обратить внимание читателя.

Как устроена таблица

В устройстве таблицы легче всего разобраться на простом примере.

<HTML>
<HEAD>
<TITLE>Пример 10</TITLE>
</HEAD>
<H1>Простейшая таблица </H1>
<TABLE BORDER=1> <!--Это начало таблицы-->
<CAPTION>        <!--Это заголовок таблицы-->
У таблицы может быть заголовок
</CAPTION>
<TR>             <!--Это начало первой строки-->
<TD>             <!--Это начало первой ячейки-->
Первая строка, первая колонка
</TD>            <!--Это конец первой ячейки-->
<TD>             <!--Это начало второй ячейки-->
Первая строка, вторая колонка
</TD>            <!--Это конец второй ячейки-->
</TR>            <!--Это конец первой строки-->
<TR>             <!--Это начало второй строки-->
<TD>             <!--Это начало первой ячейки-->
Вторая строка, первая колонка
</TD>            <!--Это конец первой ячейки-->
<TD>             <!--Это начало второй ячейки-->
Вторая строка, вторая колонка
</TD>            <!--Это конец второй ячейки-->
</TR>            <!--Это конец второй строки-->
</TABLE>         <!--Это конец таблицы-->
</BODY>
</HTML>

Таблица начинается с метки <TABLE> и заканчивается меткой </TABLE>. Метка <TABLE> может включать несколько атрибутов:

ALIGN
Устанавливает расположение таблицы по отношению к полям документа. Допустимые значения: ALIGN=LEFT (выравнивание влево), ALIGN=CENTER (выравнивание по центру), ALIGN=RIGHT (выравнивание вправо).
WIDTH
Ширина таблицы. Ее можно задать в пикселах (например, WIDTH=400) или в процентах от ширины страницы (например, WIDTH=80%).
BORDER
Устанавливает ширину внешней рамки таблицы и ячеек в пикселах (например, BORDER=4). Если атрибут не установлен, таблица показывается без рамки.
CELLSPACING
Устанавливает расстояние между рамками ячеек таблицы в пикселах (например, CELLSPACING=2).
CELLPADDING
Устанавливает расстояние между рамкой ячейки и текстом в пикселах (например, CELLPADDING=10).

Таблица может иметь заголовок (<CAPTION> ... </CAPTION>), хотя заголовок не является обязательным. Метка <CAPTION> может включать атрибут ALIGN. Допустимые значения: <CAPTION ALIGN=TOP> (заголовок помещается над таблицей) и <CAPTION ALIGN=BOTTOM> (заголовок помещается под таблицей).

Каждая строка таблицы начинается с метки <TR> и заканчивается меткой </TR>. Метка <TR> может включать следующие атрибуты:

ALIGN
Устанавливает выравнивание текста в ячейках строки. Допустимые значения: ALIGN=LEFT (выравнивание влево), ALIGN=CENTER (выравнивание по центру), ALIGN=RIGHT (выравнивание вправо).
VALIGN
Устанавливает вертикальное выравнивание текста в ячейках строки. Допустимые значения: VALIGN=TOP (выравнивание по верхнему краю), VALIGN=MIDDLE (выравнивание по центру), VALIGN=BOTTOM (выравнивание по нижнему краю).

Каждая ячейка таблицы начинается с метки <TD> и заканчивается меткой </TD>. Метка <TD> может включать следующие атрибуты:

NOWRAP
Присутствие этого атрибута означает, что содержимое ячейки должно быть показано в одну строку.
COLSPAN
Устанавливает "размах" ячейки по горизонтали. Например, COLSPAN=3 означает, что ячейка простирается на три колонки.
ROWSPAN
Устанавливает "размах" ячейки по вертикали. Например, ROWSPAN=2 означает, что ячейка занимает две строки.
ALIGN
Устанавливает выравнивание текста в ячейке. Допустимые значения: ALIGN=LEFT (выравнивание влево), ALIGN=CENTER (выравнивание по центру), ALIGN=RIGHT (выравнивание вправо).
VALIGN
Устанавливает вертикальное выравнивание текста в ячейке. Допустимые значения: VALIGN=TOP (выравнивание по верхнему краю), VALIGN=MIDDLE (выравнивание по центру), VALIGN=BOTTOM (выравнивание по нижнему краю).
WIDTH
Устанавливает ширину ячейки в пикселах (например, WIDTH=200).
HEIGHT
Устанавливает высоту ячейки в пикселах (например, HEIGHT=40).

Если ячейка таблицы пуста, вокруг нее не рисуется рамка. Если ячейка пуста, а рамка нужна, в ячейку можно ввести символьный объект &nbsp; (non-breaking space — неразрывающий пробел). Ячейка по-прежнему будет пустой, а рамка вокруг нее будет.

Любопытно отметить, что любая ячейка таблицы может содержать в себе другую таблицу.


Глава 8. Формы

Для чего нужны формы?

Форма — это инструмент, с помощью которого HTML-документ может послать некоторую информацию в некоторую заранее определенную точку внешнего мира, где информация будет некоторым образом обработана.

Рассказать о формах в книге, посвященной HTML, достаточно трудно. Причина очень простая: создать форму гораздо проще, чем ту "точку внешнего мира", в которую форма будет посылать информацию. В качестве такой "точки" в большинстве случаев выступает программа, написанная на Перл или Си. Программы, обрабатывающие данные, переданные формами, часто называют CGI-скриптами. Сокращение CGI (Common Gateways Interface) означает "общепринятый интерфейс шлюзов". Написание CGI-скриптов в большинстве случаев требует хорошего знания соответствующего языка программирования и возможностей операционной системы Unix.

В последнее время определенное распространение получил язык PHP/FI, инструкции которого можно встраивать прямо в HTML-документы (документы при этом сохраняются в виде файлов с расширением *.pht или *.php).

Формы передают информацию программам-обработчикам в виде пар [имя переменной]=[значение переменной]. Имена переменных следует задавать латинскими буквами. Значения переменных воспринимаются обработчиками как строки, даже если они содержат только цифры.

Как устроена форма

Форма открывается меткой <FORM> и заканчивается меткой </FORM>. HTML-документ может содержать в себе несколько форм, однако формы не должны находиться одна внутри другой. HTML-текст, включая метки, может размещаться внутри форм без ограничений.

Метка <FORM> может содержать три атрибута, один из которых является обязательным. Вот эти атрибуты:

ACTION
Обязательный атрибут. Определяет, где находится обработчик формы.
METHOD
Определяет, каким образом (иначе говоря, с помощью какого метода протокола передачи гипертекстов) данные из формы будут переданы обработчику. Допустимые значения: METHOD=POST и METHOD=GET. Если значение атрибута не установлено, по умолчанию предполагается METHOD=GET.
ENCTYPE
Определяет, каким образом данные из формы будут закодированы для передачи обработчику. Если значение атрибута не установлено, по умолчанию предполагается ENCTYPE=application/x-www-form-urlencoded.

Простейшая форма

Для того, чтобы запустить процесс передачи данных из формы обработчику, нужен какой-то орган управления. Создать такой орган управления очень просто:

<INPUT TYPE=submit>

Встретив такую строчку внутри формы, браузер нарисует на экране кнопку с надписью Submit (читается "сабмит" с ударением на втором слоге, от английского "подавать"), при нажатии на которую все имеющиеся в форме данные будут переданы обработчику, определенному в метке <FORM>.

Надпись на кнопке можно задать такую, какая нравится, путем введения атрибута VALUE="[Надпись]" (читается "вэлью" с ударением на первом слоге, от английского "значение"), например:

<INPUT TYPE=submit VALUE="Поехали!">

Теперь мы знаем достаточно для того, чтобы написать простейшую форму (пример 11). Она не будет собирать никаких данных, а просто вернет нас к тексту этой главы.

<HTML>
<HEAD>
<TITLE>Пример 11</TITLE>
</HEAD>
<H1>Простейшая форма </H1>
<FORM ACTION="pr0008.html"> <!--Это начало формы-->
<INPUT TYPE=submit VALUE="Назад, к главе 8...">
</FORM> <!--Это конец формы-->
</BODY>
</HTML>

Надпись, нанесенную на кнопку, можно при необходимости передать обработчику путем введения в определение кнопки атрибута NAME=[имя] (читается "нэйм", от английского "имя"), например:

<INPUT TYPE=submit NAME=button VALUE="Поехали!">

При нажатии на такую кнопку обработчик вместе со всеми остальными данными получит и переменную button со значением Поехали!.

В форме может быть несколько кнопок типа submit с различными именами и/или значениями. Обработчик, таким образом, может действовать по-разному в зависимости от того, какую именно кнопку submit нажал пользователь.

Как форма собирает данные

Существуют и другие типы элементов <INPUT>. Каждый элемент <INPUT> должен включать атрибут NAME=[имя], определяющий имя элемента (и, соответственно, имя переменной, которая будет передана обработчику). Имя должно задаваться только латинскими буквами. Большинство элементов <INPUT> должны включать атрибут VALUE="[значение]", определяющий значение, которое будет передано обработчику под этим именем. Для элементов <INPUT TYPE=text> и <INPUT TYPE=password>, однако, этот атрибут не обязателен, поскольку значение соответствующей переменной может вводиться пользователем с клавиатуры.

Основные типы элементов <INPUT>:

TYPE=text
Определяет окно для ввода строки текста. Может содержать дополнительные атрибуты SIZE=[число] (ширина окна ввода в символах) и MAXLENGTH=[число] (максимально допустимая длина вводимой строки в символах). Пример:
<INPUT TYPE=text SIZE=20 NAME=user VALUE="Иван">
Определяет окно шириной 20 символов для ввода текста. По умолчанию в окне находится текст Иван, который пользователь может редактировать. Отредактированный (или неотредактированный) текст передается обработчику в переменной user.
TYPE=password
Определяет окно для ввода пароля. Абсолютно аналогичен типу text, только вместо символов вводимого текста показывает на экране звездочки (*). Пример:
<INPUT TYPE=password NAME=pw SIZE=20 MAXLENGTH=10>
Определяет окно шириной 20 символов для ввода пароля. Максимально допустимая длина пароля — 10 символов. Введенный пароль передается обработчику в переменной pw.
TYPE=radio
Определяет радиокнопку. Может содержать дополнительный атрибут checked (показывает, что кнопка помечена). В группе радиокнопок с одинаковыми именами может быть только одна помеченная радиокнопка. Пример:
<INPUT TYPE=radio NAME=modem VALUE="9600" checked> 9600 бит/с
<INPUT TYPE=radio NAME=modem VALUE="14400"> 14400 бит/с
<INPUT TYPE=radio NAME=modem VALUE="28800"> 28800 бит/с
Определяет группу из трех радиокнопок, подписанных 9600 бит/с, 14400 бит/с и 28800 бит/с. Первоначально помечена первая из кнопок. Если пользователь не отметит другую кнопку, обработчику будет передана переменная modem со значением 9600. Если пользователь отметит другую кнопку, обработчику будет передана переменная modem со значением 14400 или 28800.
TYPE=checkbox
Определяет квадрат, в котором можно сделать пометку. Может содержать дополнительный атрибут checked (показывает, что квадрат помечен). В отличие от радиокнопок, в группе квадратов с одинаковыми именами может быть несколько помеченных квадратов. Пример:
<INPUT TYPE=checkbox NAME=comp VALUE="PC"> Персональные компьютеры
<INPUT TYPE=checkbox NAME=comp VALUE="WS" checked> Рабочие станции
<INPUT TYPE=checkbox NAME=comp VALUE="LAN"> Серверы локальных сетей
<INPUT TYPE=checkbox NAME=comp VALUE="IS" checked> Серверы Интернет
Определяет группу из четырех квадратов. Первоначально помечены второй и четвертый квадраты. Если пользователь не произведет изменений, обработчику будут переданы две переменные: comp=WS и comp=IS.
TYPE=hidden
Определяет скрытый элемент данных, который не виден пользователю при заполнении формы и передается обработчику без изменений. Такой элемент иногда полезно иметь в форме, которая время от времени подвергается переработке, чтобы обработчик мог знать, с какой версией формы он имеет дело. Другие возможные варианты использования Вы вполне можете придумать сами. Пример:
<INPUT TYPE=hidden NAME=version VALUE="1.1">
Определяет скрытую переменную version, которая передается обработчику со значением 1.1.
TYPE=reset
Определяет кнопку, при нажатии на которую форма возвращается в исходное состояние. Поскольку при использовании этой кнопки данные обработчику не передаются, кнопка типа reset может и не иметь атрибута name. Пример:
<INPUT TYPE=reset VALUE="Очистить поля формы">
Определяет кнопку Очистить поля формы, при нажатии на которую форма возвращается в исходное состояние.

Помимо элементов <INPUT>, формы могут содержать меню <SELECT> и поля для ввода текста <TEXTAREA>.

Меню <SELECT> из n элементов выглядит примерно так:

<SELECT NAME="[имя]">
<OPTION VALUE="[значение 1]">[текст 1]
<OPTION VALUE="[значение 2]">[текст 2]
...
<OPTION VALUE="[значение n]">[текст n]
</SELECT>

Как Вы видите, меню начинается с метки <SELECT> и заканчивается меткой </SELECT>. Метка <SELECT> содержит обязательный атрибут NAME, определяющий имя переменной, которую генерирует меню.

Метка <SELECT> может также содержать атрибут MULTIPLE, присутствие которого показывает, что из меню можно выбрать несколько элементов. Большинство браузеров показывают меню <SELECT MULTIPLE> в виде окна, в котором находятся элементы меню (высоту окна в строках можно задать атрибутом SIZE=[число]). Меню <SELECT> в большинстве случаев показывается в виде выпадающего меню.

Метка <OPTION> определяет элемент меню. Обязательный атрибут VALUE устанавливает значение, которое будет передано обработчику, если выбран этот элемент меню. Метка <OPTION> может включать атрибут checked, показывающий, что данный элемент отмечен по умолчанию.

Разберем небольшой пример.

<SELECT NAME="selection">
<OPTION VALUE="option1" checked>Вариант 1
<OPTION VALUE="option2">Вариант 2
<OPTION VALUE="option3">Вариант 3
</SELECT>

Такой фрагмент определяет меню из трех элементов: Вариант 1, Вариант 2 и Вариант 3. По умолчанию выбран элемент Вариант 1. Обработчику будет передана переменная selection значение которой может быть option1 (по умолчанию), option2 или option3.

После всего, что мы уже узнали, элемент <TEXTAREA> может показаться совсем простым. Например:

<TEXTAREA NAME=address ROWS=5 COLS=50>
А здесь - Ваш адрес...
</TEXTAREA>

Все атрибуты обязательны. Атрибут NAME определяет имя, под которым содержимое окна будет передано обработчику (в примере — address). Атрибут ROWS устанавливает высоту окна в строках (в примере — 5). Атрибут COLS устанавливает ширину окна в символах (в примере — 50).

Текст, размещенный между метками <TEXTAREA> и </TEXTAREA>, представляет собой содержимое окна по умолчанию. Пользователь может его отредактировать или просто стереть.

Важно знать, что русские буквы в окне <TEXTAREA> при передаче обработчику могут быть конвертированы в соответствующие им символьные объекты.

Пример

Для демонстрации использования форм я написал небольшую программу на PHP, которая находится по адресу:

http://206.31.82.215/hp/nc/fd-win.pht

Исходные данные в эту программу передаст форма, описанная в примере 12:

<HTML>
<HEAD>
<TITLE>Пример 12</TITLE>
</HEAD>
<H1>Несколько более сложная форма </H1>
<FORM ACTION="http://206.31.82.215/hp/nc/fd-win.pht"
 METHOD=post>
<H2>Расскажите немного о себе...</H2>
<P>Указывать подлинные данные совсем не обязательно.
Для целей демонстрации вполне подойдут и вымышленные.
 </P>
<P>Имя: <INPUT TYPE=text SIZE=40 NAME=fn><BR>
Фамилия: <INPUT TYPE=text SIZE=40 NAME=ln><BR>
Пол: <INPUT TYPE=radio NAME=gender VALUE="male"
 checked>мужской
<INPUT TYPE=radio NAME=gender
 VALUE="female">женский<BR>
Возраст: <INPUT TYPE=text SIZE=5 NAME=age> лет<BR>
<INPUT TYPE=submit VALUE="Запустить обработчик"></P>
</FORM>
</BODY>
</HTML>

Заполняйте форму, жмите на кнопку и смотрите, что будет...

Маленькая хитрость,
или Как отправить форму почтой

Все это прекрасно, скажут скептики, но на кой ляд нужны формы людям, которым их нечем обработать? Отчасти это верно, но только отчасти.

HTML предоставляет в Ваше распоряжение довольно мощный механизм пересылки содержимого форм по электронной почте. Вот как это выглядит на практике.

Допустим, что мы слегка изменили Пример 12. Вместо строки

<FORM ACTION="http://206.31.82.215/hp/nc/fd-win.pht"
 METHOD=post>

мы ввели строку

<FORM ACTION="mailto:user@mail.box"
 ENCTYPE=text/plain>

Обратите внимание, что мы изменили алгоритм кодирования на text/plain, то есть фактически выключили кодирование вообще.

Предположим теперь, что пользователь указал, имя Иван, фамилию Петров, мужской пол и возраст 22 года. Теперь вопрос: что произойдет, если пользователь нажмет на кнопку Запустить обработчик?

Ответ прост. На адрес user@mail.box электронной почтой автоматически будет отправлено сообщение следующего содержания:

fn=Иван
ln=Петров
gender=male
age=22

К сожалению, не все пользователи смогут воспользоваться такой формой. Дело в том, что всю работу по составлению сообщения и запуску почтовой программы для его отправки фактически берет на себя браузер пользователя. Это значит, что конфигурация доступа пользователя к Интернет должна обеспечивать одновременное функционирование протокола передачи гипертекстов и протокола доставки исходящей почты. Такое возможно не всегда. Тем не менее, даже если это невозможно, ничего смертельного не случится. Браузер просто выдаст сообщение об ошибке.


Глава 9. Некоторые (предположительно полезные) советы по разработке и размещению HTML-страниц

Правила хорошего тона при разработке HTML-документов

Ознакомьтесь с текущей спецификацией HTML

Производители браузеров часто добавляют к своим творениям поддержку меток, не входящих в текущую спецификацию HTML. Именно поэтому иногда можно встретить страницы, оптимизированные для того или иного браузера. На мой взгляд, это далеко не лучший подход. Однако, как говорил Рэй Брэдбери, единственный способ заставить писателя не писать — это кольт сорок пятого калибра. И писатели пишут...

Не питайте абсолютного доверия к визуальным редакторам HTML

Помните, что большинство визуальных редакторов HTML, хотя и работают в целом корректно, имеют мелкие (а иногда и крупные) недостатки. Отчасти это связано с тем, что HTML с самого начала не был приспособлен для автоматизированной разработки.

Используйте продуманную иерархию заголовков

Помните, что Ваш документ будет читаться гораздо лучше, если в нем будет четкое разбиение на разделы и подразделы. HTML предоставляет в Ваше распоряжение шесть уровней заголовков. Воспользуйтесь этим богатством в своих интересах — создайте легко читаемый документ с интуитивно ясной структурой.

Следите за вложенностью меток

Современные браузеры способны правильно обработать вложенные метки. Например, вот такой фрагмент HTML-текста

<B><I>жирный наклонный шрифт</I></B>

большинство браузеров вполне успешно воспроизведут как жирный наклонный шрифт. Обратите внимание — метки <I>...</I> в этом фрагменте находятся внутри меток <B>...</B>. Другими словами, метки вложены одна в другую.

Работа браузера, однако, окажется сильно затрудненной, если вложенность окажется нарушенной, например:

<B><I>вложенность меток нарушена</B></I>

Соблюдение вложенности — очень важная часть общей культуры написания HTML-текста.

Указывайте альтернативный текст при включении в документ изображений

Помните, что пользователи могут отключать автоматическую загрузку изображений. Не ленитесь подсказать пользователю, чего именно он не видит. Это особенно важно, когда изображения являются гипертекстовыми ссылками.

При разработке крупных документов создавайте оглавления

При написании крупного документа очень полезно поместить в его начале оглавление с гипертекстовыми ссылками на разделы. Это может облегчить пользователю работу с документом, особенно если документ носит справочный характер.

Хорошо продумайте навигацию

Попытайтесь вообразить себе, что в распоряжении пользователя нет кнопки Back. Подумайте о том, как обеспечить пользователю необходимую свободу перемещения в этом случае. Это поможет Вам спроектировать удобную в пользовании коллекцию HTML-документов.

Инструменты разработчика: пристрастные советы

Предупреждаю Вас честно: все приведенные ниже комментарии представляют собой мое личное (и, вполне вероятно, необъективное) мнение.

Визуальные редакторы HTML

Netscape Navigator Gold

На мой взгляд, это лучший из редакторов типа "что видишь, то и получишь". Не сочиняет нестандартных меток, хотя некоторые стандартные (в частности, метки списков) пишет не лучшим образом. При попытке перейти из режима просмотра в режим редактирования иногда подвисает на томительные 30-40 секунд. На мой взгляд, недостаточно защищен от ошибок неопытного пользователя (хотя это относится только к подсистеме редактора; в режиме браузера работает четко).

Распространяется бесплатно.

Microsoft Front Page

Ходит по России в основном в виде версий для 30-дневных тестов с обезвреженным механизмом самоуничтожения. Наиболее крупных болячек две:

  1. Вставляет в заголовок HTML-документа META-инструкцию

    <META HTTP-EQUIV="Content-type" CONTENT="text/html; charset=windows-1251">

    Это превращается в неумную шутку при попытке опубликовать документ в кодировке КОИ-8.

    Лекарство только одно: перед размещением на сервере удалить или исправить META-инструкцию вручную.

  2. Рассыпает ровным слоем по документу метки вида

    <FONT FACE="Arial Cyr">[текст]</FONT>

    для указания того, каким шрифтом показывать [текст].

    В HTML 3.2 метка <FONT> используется для изменения размеров и цвета шрифта. Метка <FONT FACE="[имя шрифта]"> в спецификации упомянута как поддерживаемая некоторыми браузерами, но нежелательная и потому не включенная в спецификацию и не рекомендуемая к применению. В самом деле, формат HTML не привязан даже к конкретной аппаратной платформе, зачем же привязывать его к шрифту!

Небольшое отступление о не упомянутых здесь редакторах

Многие редакторы типа "что видишь, то и получишь" сохраняют русские буквы в виде соответствующих им &-последовательностей. На "внешнем виде" файла при просмотре браузером это никак не отражается, а его размер возрастает в четыре-пять раз. Соответственно растет и время загрузки.

В некоторых редакторах это свойство настраиваемое, в некоторых зашито жестко.

Редакторы HTML-текстов

Большинство таких редакторов вполне справляется с возложенными на них задачами. Мне лично очень понравились HotDog и Ken Nesbitt Web Editor. Можете выгрузить 30-дневные тестовые версии и попробовать сами.

Конвертеры документов текстовых процессоров в HTML

Internet Assistant for Microsoft Word

Можно выгрузить бесплатно с WWW-сервера компании Microsoft (http://www.microsoft.com/msword/internet/ia/)

Превращает Microsoft Word версии 6.1 и выше в браузер и редактор HTML. Некорректно отображает таблицы и сохраняет русские буквы в виде &-последовательностей. От других людей мне доводилось слышать, что вторая проблема была решена в версии для Word 7.0. Так ли это на самом деле, сказать не берусь, поскольку все еще работаю с Word 6.1.

GT_HTML

Можно выгрузить бесплатно с http://www.gatech.edu/word_html/.

Понравилось, особенно после в целом негативного опыта работы с Internet Assistant for Microsoft Word. Требует несколько большего объема рутинной работы, чем Internet Assistant, но и работает гораздо более корректно, включая воспроизведение таблиц. Существуют версии для Microsoft Word 2.0, Microsoft Word 6.0 и Microsoft Word for Macintosh.

Другие

Краткую информацию на английском языке о некоторых других конвертерах, а также о том, где они лежат, можно найти на любом поисковом сервере. Ниже даны две ссылки на сервер Yahoo!:

  • Computers and Internet: Software: Internet: World Wide Web: HTML Converters
  • Business and Economy: Companies: Computers: Software: Internet: Shareware: HTML Converters

Графические редакторы

Скажу Вам сразу — мой опыт в этой области весьма ограничен. Компьютерная графика — это пограничная область между программированием и живописью (в тех случаях, конечно, когда она делается хорошо). В программировании я немного разбираюсь, а вот в живописи отнюдь не силен. Тем не менее, краткая информация о программном обеспечении может оказаться уместной.

Paint Shop Pro

Весьма симпатичный графический редактор. Поддерживает множество форматов графических файлов, в том числе и уже знакомые нам GIF и JPEG. Бесплатно распространяется в виде полностью функциональной версии для 30-дневных испытаний без механизма самоуничтожения. Существуют версии для Windows 3.xx и Windows 95. Можно выгрузить бесплатно с сервера компании-разработчика: http://www.jasc.com/pspdl.htmll.

GIF Construction Set

Программное обеспечение для создания подвижных изображений в формате GIF. Распространяется бесплатно. Существуют версии для Windows 3.xx и Windows 95. Можно выгрузить бесплатно с сервера компании-разработчика: http://www.mindworkshop.com/alchemy/gifcon.htmll.

Где можно бесплатно разместить свою страничку

Существует достаточно серверов, владельцы которых готовы позволить всем желающим размещать на них свои документы бесплатно. Для этого необходимо только знать английский язык в степени, достаточной для того, чтобы правильно заполнить регистрационную форму на сервере. Необходимо также уметь пользоваться программным обеспечением для загрузки файлов на удаленный сервер (CuteFTP, WS_FTP или аналогичным).

В этом смысле исключением выглядят серверы http://www.halyava.ru и http://www.i-connect.ru. Вся информация там на русском языке, так что нет смысла пересказывать. Посмотрите сами.

Информацию о некоторых других бесплатных серверах можно найти на поисковой системе Yahoo. Информация там не всегда самая новая, так что настоятельно рекомендую посетить страничку Вадима Хомахи, на которой такой информации много, свежей, проверенной и на русском языке.

Несколько слов в заключение

Надеюсь, что Вы смогли узнать что-то новое и полезное из этого руководства. Логичный вопрос теперь — куда двигаться дальше? Неплохая коллекция ссылок на русскую документацию по HTML и смежным вопросам есть на сервере "Махаон".


Различные значения параметров таблицы по умолчанию для разных броузеров

Как видно на приведенной чуть ниже иллюстрации, Netscape Navigator, Netscape Comunicator, IntrnetExplorer 4 и IntrnetExplorer 3 берут параметры тегов с разных сторон.

С лева
С права 		NN,NC,IE4
IE3 и ниже 		IE3 и ниже NN,NC,IE4

Код:

<TABLE>
<TR>
 <TD BGCOLOR="#000000" BGCOLOR="#FFFFFF">
  <FONT COLOR="#000000">С лева</FONT><BR>
  <FONT COLOR="#FFFFFF">С права</FONT>
 </TD>
 <TD BGCOLOR="#FFFFFF" BGCOLOR="#000000">
  <FONT COLOR="#000000">NN,NC,IE4</FONT><BR>
  <FONT COLOR="#FFFFFF">IE3 и ниже </FONT>
 </TD>
 <TD BGCOLOR="#000000" BGCOLOR="#FFFFFF">
  <A HREF="ie4_nn_nc" HREF="ie3"><FONT
 COLOR="#000000">IE3 и ниже</FONT>
  <FONT COLOR="#FFFFFF">NN,NC,IE4</FONT></A>
 </TD>
</TR>
</TABLE>


Поля, кнопки и шрифты форм | WebClub

Поля, кнопки и шрифты форм

Здесь можно бороться только с надписью на кнопке submit (жестко прописывать value=...). Шрифты настраиваются в настройках броузера. Что касается поля textarea - значение атрибута wrap по умолчанию для Netscape и Explorer разное. Следут жестко прописывать wrap=virtual | physical.

Код:

    <form>
    <textarea rows=3 cols=15></textarea><br>
    <input type=text size=10><br>
    <input type=submit>
    </form>


Пустые ячейки таблицы | WebClub

Пустые ячейки таблицы

Задача: сделать табличку с с цветной колонкой где параметр width="" тага TD был бы равен нескольким пикселям, а поле таблицы было бы пустым. (Для примера - полоска по краю таблицы).

Пишем:

<table width="50" cellspacing="0" cellpadding="0"
 border="0">
<tr>
  <td width="47" nowrap bgcolor="fuchsia">
Пример 1<br></td>
  <td width="3" nowrap bgcolor="green"></td>
</tr>
</table>
Картинка в вашем броузере
Пример один. Обратите внимание, есть ли зеленая полоска справа
Результат для броузеров Netscape 4.x и Explorer 4.x
EXPLORER (GIF) | NETSCAPE (GIF)

Как мы видим, Explorer отрабатывает цвет в таблице достаточно корректно в отличии от Нетскейпа, который без чего-либо в таблице в упор не замечает параметра bgcolor="". Решить эту проблему можно простым способом (для забегающих вперед скажу, что Non-breaked space не помогает :) - поставив в пустой части таблицы <BR> Тогда и Нетскейп и Эксплорер покажут данную картинку относительно одинаково.

<table width="50" cellspacing="0"
 cellpadding="0" border="0">
<tr>
  <td width="47" nowrap bgcolor="fuchsia">
Пример 1<br></td>
  <td width="3" nowrap
 bgcolor="green"><br></td>
</tr>
</table>
Картинка в вашем броузере
Пример один. Обратите внимание, есть ли зеленая полоска справа
Результат для броузеров Netscape 4.x и Explorer 4.x
EXPLORER (GIF) | NETSCAPE (GIF)


Банк различий браузеров

Здесь приведены различия для последних версий Netscape и Explorer - Communicator 4.04 и Internet Explorer 4.0. Мы будем также рады узнать о других браузерах.

Мы нигде ничего не будем комментировать, поскольку существует такое явление, как "война бродилок". Постараемся претендовать на объективность.

Мы приветствуем ваши комментарии и наблюдения.

Одинаковый код - разный рендеринг

  1. Значения параметров таблицы по умолчанию
  2. Поля, кнопки и шрифты форм
  3. Выравнивание текста в таблице с незаданной шириной
  4. Изображение и знак возврата каретки внутри <A HREF..>..</A>
  5. Ненумерованные списки
  6. Выравнивание таблицы
  7. Пустые ячейки таблицы
  8. Различное направление чтения параметров

Некорректная разметка не всегда сходит с рук

  1. Таблица в таблице

Различные для NN и MSIE таги

  1. Вставка фонового звука
    Explorer:
<bgsound src="yoursound.wav">
Netscape:
<embed src="yoursound.wav" width=0 height=2
volume=50 autostart=true loop=yes>
  2. Создание полей в окне браузера (аттрибуты BODY)
    Netscape: <body marginwidth="50"
marginheihgt="50">
Explorer: <body leftmargin="50" topmargin="50">

к оглавлению справочника


Изображение и знак возврата каретки внутри

Изображение и знак возврата каретки внутри <A HREF..>..</A>

Netscape критичнее относится к пробелам и знакам возврата каретки, чем Explorer. В итоге наличие таких символов в графической ссылке приводит к некрасивой черточке. То же самое происходит и в случае текстовых ссылок, хотя это не так заметно.

Код:

    <a href="text.html">
    <img src=pic.gif width=20 height=20>
    </a>

Правильный код:

    <a href="text.html"><img src=pic.gif width=20
 height=20></a>


Выравнивание таблицы

На иллюстрации показана невосприимчивость Netscape к выравниванию таблиц с использованием p align=center. Бороться с этим можно конструкциями

    <p>
    <center>
    <table>
     ...
    </table>
    </center>
    </p>

        <table align=center>
         ...
        </table>

Код:

    <p align="center">
    <table border=1>
    <tr><td>1</td><td>2</td></tr>
    <tr><td>3</td><td>4</td></tr>
    </table>
    </p>


Таблица в таблице

На этой иллюстрации видно, к каким неконтролируемым последствиям приводят незакрытые таги таблиц, когда есть вложенность одной таблицы в другую. Первое правило вебмастера - закрывайте двери:-))

Код:

    <table><tr><td>
    table 1
    <table border=1>
    <tr><td>text1<td>text2</tr>
    </table>
    </tr>
    </table>


Значения параметров таблицы по умолчанию

Как видно на приведенной чуть ниже иллюстрации, значания параметров таблиц для Netscape и Explorer отличаются. Чтобы все одинаково выглядело следует прописывать значание border.

Код:

    <table width=50% bgcolor=#C0C0C0>
    <tr><td>&nbsp;</td><td>&nbsp;</td></tr>
    </table>


Выравнивание текста в таблице с незаданной шириной

Может на приведенной чуть ниже иллюстрации не слишком видны различия, но тем не менее. В таблице задано выравнивание параграфов текста по ширине (p align=justify), однако Netscape это игнорирует. Это происходит от используемого алгоритма рендеринга. Избавиться от этого можно указав параметр width таблицы либо по пикселам, либо в процентном соотношении к экрану.

Код:

    <table border=1>
    <tr><td>
    <p align=justify>text text text text text text
     text text text text text text text
    <p align=justify>lkjfhakfj lkjfhakfj lkjfhakfj
     lkjfhakfj lkjfhakfj
    </td></tr>
    </table>


Ненумерованные списки | WebClub

Ненумерованные списки

Здесь видны неодинаковые размеры circle и square. Способов борьбы, повидимому, нет.

Код:

    <table width=100%>
    <tr><td>
    <ul type=disk>
    <li>Item 1
    <li>Item 2
    <li>Item 3
    </ul>
    </td><td>
    <ul type=circle>
    <li>Item 1
    <li>Item 2
    <li>Item 3
    </ul>
    </td><td>
    <ul type=square>
    <li>Item 1
    <li>Item 2
    <li>Item 3
    </ul>
    </td></tr>
    </table>


Цветовые палитры

Представляю Вашему вниманию небольшое введение в индексированные палитры. Вид иллюстраций зависит от графического оборудования Вашего компьютера и установленной глубины цвета. При использовании глубины в 16 или 24 бита все иллюстрации будут выглядеть корректно. Если же у Вас установлена поддержка всего 256 цветов результат может меняться от 'немного смешанного' до 'полностью неправильного'.

Системная палитра Macintosh

Стандартные палитры

Слева представлена стандартная палитра Macintosh, сохраненная как 8-ми битный GIF файл с внутренней палитрой.

Для интерфейса Macintosh зарезервировано всего два позиции - белая и черная. Все остальные цвета являются переопределяемыми.

То, как эта палитра выглядет на Вашем экране, показывает, как хорошо ваш браузер переопределяет цвета.

Стандартная палитра Windows 95 Данная типичная палитра Windows 95 начинается с черного и заканчивается белым. Она также сохранена, как 8-ми битный GIF файл с внутренней палитрой.

Первые и поcледние 10 позиций в палитре зарезервированы Windows. Они включают 16 стандартных цветов VGA плюс четыре цвета SVGA - светло желтый, светло зеленый, светло синий и средне серый. Остальные 236 цветов обычно являются переопределяемыми. Данная палитра не является 'зашитой' как в случае Macintosh, а просто стандартная 'из коробки' палитра Windows 95. Стандартная системная палитра в Windows может переопределяться пользователем или программой, использующей отличную от стандартной палитру.

То, как эта палитра выглядет на Вашем экране, показывает, как хорошо ваш браузер переопределяет цвета.

Autumn Leaves - JPEG Green Leaf - JPEG Winter Trees - JPEG

Эти три изображения были сохранены
в 24-х битном формате JPEG

Индексирован- ные палитры

Я выбрал три абсолютно различных по цветовому оттенку изображения. В первом случае они сохранены в формате JPEG, поэтому они по определению 24-х битные.

Если Вы их просматриваете при глубине цвета 8 бит, Ваш браузер должен выполнить достаточно хорошую работу, чтобы отобразить их при имеющихся 256 цветах. А при 16-ти или 24-битной глубине они будут иметь фотографическое качество.

Они были сохранены со средней компрессией и поэтому немного меньше в размере, чем следующие GIFы.

Autumn Leaves - GIF Green Leaf - GIF Winter Trees - GIF

Данные изображения были
переиндексированы с использованием
6x6x6Веб-палитры

Данные изображения были переиндексированы с использованием 6x6x6 Веб-палитры, которая является системной палитрой Netscape. Если Вы используете 8-ми битные GIFы с этой палитрой при создании Веб-иллюстраций, Вас не ждет никаких сюрпризов при показе Вашего узла как на Mac, так и на PC экранах при глубине цвета в 8 бит.

В 8-ми битном режиме результаты отображения должны быть почти неотличимы от предыдущих JPEG изображений, за исключением незначительного 'блокирования' красного в красных листьях.

Autumn Leaves Adaptive - GIF Green Leaf Adaptive - GIF Winter Trees Adaptive - GIF

При глубине цвета 16 или 24 бита
эти изображения с адаптивной палитрой
будут выглядеть лучше, чем предыдущие
с 6x6x6 палитрой, но они будут выглядеть
гораздо хуже при глубине цвета 8 бит,
из-за двойного смешивания цветов
или проблем видеокарты

Каждое из этих изображений было сохранено с собственной адаптивной палитрой.

В общем случае они не смогут сосуществовать на одном экране при глубине цвета 8 бит. Если бы каждое из них было бы в отдельном окне, тогда то, которое находилось бы в активном окне, было бы очень похоже на 24-х битный вариант, а два других были бы искажены почти до неузноваемости.

Как Netscape Navigator, так и Internet Explorer переопределяют эти конфликтующие палитры, но несмотря на то, что нарушаются определенные оттенки цветов, изображения доступны для восприятия благодаря незатейливости изображенных предметов.

Более критичные изображения, имеющие сплошные заливки или яркие оттенки, будут иметь более серьезные искажения при растеризации.

Выбор способа сохранения изображения очень сильно зависит от содержания. При малом количестве цветов в изображении рекомендуется сохранять их с палитрой, содержащей менее 8-ми битов на цвет для уменьшения размера файла.

В дальнейшем все большее количество пользователей будет работать при глубине цвета более 8-ми бит. Постепенно потребность в безопасной 6x6x6 палитре отпадет, и тогда использование адаптивных палитр будет более целесообразно.

'Безопасная' палитра

Для того, чтобы быть уверенным, что при отображении изображения не будет происходить смешения цветов на Mac или PC системах, работающих при глубине цвета 8 бит, Вы должны использовать при сохранении изображений 'безопасную' Веб палитру 6x6x6. Это относится и к фоновым GIF изображениям, хотя они обрабатываются немного по другому, чем 'передовые' - одно и то же изображение, использованное в качестве фона и как элемент страницы, может выглядеть немного по-разному.

Некоторые UNIX системы используют более узкую палитру 5x5x5, имеющую 25% шаги, а не 20%, как у 'безопасной', поэтому изображение будет иметь смешанные цвета, но это встречается достаточно редко.

Некоторые примеры в этом разделе будут иллюстративны только при глубине цвета 8 бит (256 цветов).

20 позиций
зарезервированно Windows.

'Безопасная' палитра 6x6x6 содержит 216 цветов, соответствующих 20% шагам R, G и B. Каждая составляющая может принимать значения 0, 51, 102, 153, 204, 255. В шестнадцатеричном виде это 00, 33, 66, 99, CC, FF. Чтобы легко запомнить десятичные числа, достаточно запомнить ряд 0, 5, 10, 15, 20, 25, а потом к каждому числу дописывать справа количество пятерок в этом числе.

Если Вы вычтите 216 из 256 доступных при 8-ми битной глубине цветов, останется 40 позиций. Эти 40 цветов используются операционными системами и браузерами для внутренних целей.

На компьютерах с Windows 20 свободных позиций используются операционной системой. Остальные 20 позиций могут, например, быть использованы для отображения дополнительных 'небезопасных' цветов, содержащихся в GIFе, сохраненном с адаптивной палитрой. Это не делает их 'безопасными', но делает их используемыми.

Но будьте внимательны, палитры Windows меняются в зависимости от того, что в данный момент отображается на экране, поэтому, прекрасно смотрясь на одной странице, изображение с адаптивной палитрой будет некачественно отображаться на другой. Всегда тестируйте каждую страницу с такими изображениями.

На Macintosh в версиях Netscape до 4-й можно было задействовать свободные позиции, до одного и того же места, т.к. всего две позиции зарезервированы для отображения черного и белого. Теперь Netscape принудительно использует палитру 6x6x6, работая при глубине цвета 8 бит. Из-за этого теперь некачественно выглядят многие GIF изображения, которые до этого выглядели на Mac системах превосходно.

Mac Greys

Если Вы посмотрите на этот набор
оттенков серого на мониторе Mac при
глубине цвета 8 бит, они, скорее
всего, не будут смешанными. Даже
несмотря на то, что они не из
'безопасной' палитры, они из системной
палитры Macintosh.

В Windows они будут выглядеть
смешанными.

Windows Greys

Этот набор получается из
стандартной палитры Windows 95.
В контексте данной страницы он будет
выглядеть смешанным, но на отдельной
странице, он будет
выглядеть корректно.

Grey Patterns

Используя смешанные цвета такого вида,
вы можете эмулировать промежуточные
оттенки серого, или цветов, но платой
за это будет больший размер файла, т.к.
такие цвета сжимаются гораздо хуже.

Черно-белая графика на Веб-страницах

Безопасная палитра 6x6x6 включает только 4 нейтрально серых цвета, не считая белого и черного, что вызывает сложности при отображении черно белых изображений.

Несмотря на то, что в системных палитрах имеются дополнительные оттенки серого, они не одинаковые на Mac и на PC.

Существует 12 оттенков в палитре Mac и 18 оттенков в палитре Windows, поэтому, не считая черного и белого, RGB значения этих оттенков не совпадают. Изображение, имеющее большие поверхности, однородно заполненные одним из этих серых цветов, обязательно у кого-нибудь будет содержать смешанный цвет. В фотографическом изображении, где все цвета итак смешаны, это не будет иметь никакого значения.

Если Вам необходимо иметь большую однородную поверхность серого цвета, используйте 'безопасные' оттенки серого. Промежуточных оттенков можно добиться методом создания 'узора', который является упорядоченной, а не случайной смесью безопасных цветов, но при этом надо помнить, что такие смеси гораздо хуже сжимаются.

LZW алгоритм сжатия, используемый в GIF файлах, подсчитывает идущие подряд по горизонтали точки одинакового цвета и сохраняет ссылку на цвет и количество точек, а не ссылки на все точки. Для узоров алгоритм сжатия не будет эффективным, т.к. меняется каждая точка.

Такой файл будет все-таки немного сжат, т.к. алгоритм умеет обрабатывать периодические узоры, но он все равно будет примерно на 50% больше аналогичного файла с однородным цветом.

На иллюстрации с смешанными узорами ряд из шести квадратов содержит безопасные оттенки серого, а в нижнем ряду промежуточные оттенки получены с помощью узоров, состоящих из безопасных оттенков. Неоднородность полученных оттенков почти незаметна.

Orange Patterns

В этом случае безопасные оттенки
красного, оранжевого и желтого цветов
дают набор промежуточных оттенков без
заметной разнородности.

Эту же технологию можно применять с любой комбинацией цветов, правда участвующие в смешении цвета должны быть достаточно близки, чтобы неоднородность была незаметна.

Для того, чтобы использовать такие узоры в Photoshop, нарисуйте маленький квадрат с помощью карандаша, выберите ее и вызовите Define Pattern в меню Edit. Теперь, при использовании заливки или меню Fill отметьте опцию Pattern.

Для быстрого создания таких узоров можно воспользоваться новым плагином для Mac и PC Photoshop'а от http://www.ditherbox.com/, встроенным в Adobe Image Ready.

JPEG Cat - 30K
JPEG Cat

4-Bit GIF Cat - 16K
 GIF Cat

Слева представлена одна и та же фотография, сохраненная в двух форматах. Верхняя кошка сохранена в формате JPEG и весит примерно 30K. Она замечательно смотрится при глубине цвета 16 или 24 бита, но ее цвета будут смешанны при просмотре при глубине цвета 8 бит.

Mac версия Netscape 4 добавляет желтые и голубые оттенки, которые ужасно смотрятся, помимо этого белая область между ушами имеет неприятные точки.

Нижняя кошка сохранена в формате GIF с адаптивной палитрой, 4-х битной глубиной цвета и диффузным смешением цветов. В этом случае кошка одинаково смотрится при любых глубинах цветов, а при глубине в 8 бит намного лучше JPEG варианта. Помимо этого ее размер в два раза меньше.

Почему? Черно-белое изображение имеет 256 оттенков серого. Это слишком много, т.к. шаги неразличимы. Уменьшение глубины цвета до 5 бит (32 оттенка) или 4 бит (16 оттенков) дает почти неотличимый результат, при этом значительно уменьшает размер файла.

umbra Для создания этого логотипа использовалась та же технология. Мягкая тень замечательно отображается на всех платформах при всех глубинах цвета, за исключением Netscape 4 на Mac.

Таблица глубин цветов

Пониженная глубина цвета

Снижение глубины цвета любого GIF файла приводит к уменьшению его размера и увеличению скорости загрузки.

Сохранение файлов с индексированными адаптивным методом палитрами часто дает незначительные, но тем не менее нежелательные перемены в цвете. Вы можете честно рисовать, используя только безопасные цвета, а затем, при индексации, цвета будут смещены на несколько единиц и получатся смешанными. Поэтому индексируйте файлы, используя встроенную Веб палитру.

При исправлении уже существующего файла достаточно подправить цвет фона и основных цветов, а также цветов, которыми залиты большие однородные области. Эта операция должна производиться над уже индексированном изображением.


Таблица цветов

Color's name | Red | Green | Blue | Color
alicemblue | F0 | F8 | FF | sample
antiquewhite | FA | EB | D7 | sample
aqua | 00 | FF | FF | sample
aquamarine | 7F | FF | D4 | sample
azure | F0 | FF | FF | sample
beige | F5 | F5 | DC | sample
bisque | FF | E4 | C4 | sample
black | 00 | 00 | 00 | sample
blanchedalmond | FF | EB | CD | sample
blue | 00 | 00 | FF | sample
blueviolet | 8A | 2B | E2 | sample
brown | A5 | 2A | 2A | sample
burlywood | DE | B8 | 87 | sample
cadetblue | 5F | 9E | A0 | sample
chartreuse | 7F | FF | 00 | sample
chocolate | D2 | 69 | 1E | sample
coral | FF | 7F | 50 | sample
cornflowerblue | 64 | 95 | ED | sample
cornsilk | FF | F8 | DC | sample
crimson | DC | 14 | 3C | sample
cyan | 00 | FF | FF | sample
darkblue | 00 | 00 | 8B | sample
darkcyan | 00 | 8B | 8B | sample
darkgoldenrod | B8 | 86 | 0B | sample
darkgray | A9 | A9 | A9 | sample
darkgreen | 00 | 64 | 00 | sample
darkkhaki | BD | B7 | 6B | sample
darkmagenta | 8B | 00 | 8b | sample
darkolivegreen | 55 | 6B | 2F | sample
darkorange | FF | 8C | 00 | sample
darkochid | 99 | 32 | CC | sample
darkred | 8B | 00 | 00 | sample
darksalmon | E9 | 96 | 7A | sample
darkseagreen | 8F | BC | 8F | sample
darkslateblue | 48 | 3D | 8B | sample
darkslategray | 2F | 4F | 4F | sample
darkturquoise | 00 | CE | D1 | sample
darkviolet | 94 | 00 | D3 | sample
deeppink | FF | 14 | 93 | sample
deepskyblue | 00 | BF | FF | sample
dimgray | 69 | 69 | 69 | sample
dodgerblue | 1E | 90 | FF | sample
firebrick | B2 | 22 | 22 | sample
floralwhite | FF | FA | F0 | sample
forestgreen | 22 | 8B | 22 | sample
fushsia | FF | 00 | FF | sample
gainsboro | DC | DC | DC | sample
ghostwhite | F8 | F8 | FF | sample
gold | FF | D7 | 00 | sample
goldenrod | DA | A5 | 20 | sample
gray | 80 | 80 | 80 | sample
green | 00 | 80 | 00 | sample
greenyellow | AD | FF | 2F | sample
honeydew | F0 | FF | F0 | sample
hotpink | FF | 69 | B4 | sample
indiandred | CD | 5C | 5C | sample
indigo | 4B | 00 | 82 | sample
ivory | FF | FF | F0 | sample
khaki | F0 | E6 | 8C | sample
lavender | E6 | E6 | FA | sample
lavenderblush | FF | F0 | F5 | sample
lawngreen | 7C | FC | 00 | sample
lemonchiffon | FF | FA | CD | sample
ligtblue | AD | D8 | E6 | sample
lightcoral | F0 | 80 | 80 | sample
lightcyan | E0 | FF | FF | sample
lightgoldenrodyellow | FA | FA | D2 | sample
lightgreen | 90 | EE | 90 | sample
lightgrey | D3 | D3 | D3 | sample
lightpink | FF | B6 | C1 | sample
lightsalmon | FF | A0 | 7A | sample
lightseagreen | 20 | B2 | AA | sample
lightscyblue | 87 | CE | FA | sample
lightslategray | 77 | 88 | 99 | sample
lightsteelblue | B0 | C4 | DE | sample
lightyellow | FF | FF | E0 | sample
lime | 00 | FF | 00 | sample
limegreen | 32 | CD | 32 | sample
linen | FA | F0 | E6 | sample
magenta | FF | 00 | FF | sample
maroon | 80 | 00 | 00 | sample
mediumaquamarine | 66 | CD | AA | sample
mediumblue | 00 | 00 | CD | sample
mediumorchid | BA | 55 | D3 | sample
mediumpurple | 93 | 70 | DB | sample
mediumseagreen | 3C | B3 | 71 | sample
mediumslateblue | 7B | 68 | EE | sample
mediumspringgreen | 00 | FA | 9A | sample
mediumturquoise | 48 | D1 | CC | sample
medium violetred | C7 | 15 | 85 | sample
midnightblue | 19 | 19 | 70 | sample
mintcream | F5 | FF | FA | sample
mistyrose | FF | E4 | E1 | sample
moccasin | FF | E4 | B5 | sample
navajowhite | FF | DE | AD | sample
navy | 00 | 00 | 80 | sample
oldlace | FD | F5 | E6 | sample
olive | 80 | 80 | 00 | sample
olivedrab | 6B | 8E | 23 | sample
orange | FF | A5 | 00 | sample
orengered | FF | 45 | 00 | sample
orchid | DA | 70 | D6 | sample
palegoldenrod | EE | E8 | AA | sample
palegreen | 98 | FB | 98 | sample
paleturquose | AF | EE | EE | sample
palevioletred | DB | 70 | 93 | sample
papayawhop | FF | EF | D5 | sample
peachpuff | FF | DA | B9 | sample
peru | CD | 85 | 3F | sample
pink | FF | C0 | CB | sample
plum | DD | A0 | DD | sample
powderblue | B0 | E0 | E6 | sample
purple | 80 | 00 | 80 | sample
red | FF | 00 | 00 | sample
rosybrown | BC | 8F | 8F | sample
royalblue | 41 | 69 | E1 | sample
saddlebrown | 8B | 45 | 13 | sample
salmon | FA | 80 | 72 | sample
sandybrown | F4 | A4 | 60 | sample
seagreen | 2E | 8B | 57 | sample
seashell | FF | F5 | EE | sample
sienna | A0 | 52 | 2D | sample
silver | C0 | C0 | C0 | sample
skyblue | 87 | CE | EB | sample
slateblue | 6A | 5A | CD | sample
slategray | 70 | 80 | 80 | sample
snow | FF | FA | FA | sample
springgreen | 00 | FF | 7F | sample
steelblue | 46 | 82 | B4 | sample
tan | D2 | B4 | 8C | sample
teal | 00 | 80 | 80 | sample
thistle | D8 | BF | D8 | sample
tomato | FF | 63 | 47 | sample
turquose | 40 | E0 | D0 | sample
violet | EE | 82 | EE | sample
wheat | F5 | DE | B3 | sample
white | FF | FF | FF | sample
whitesmoke | F5 | F5 | F5 | sample
yellow | FF | FF | 00 | sample
yellowgreen | 9A | CD | 32 | sample


Хитрости и советы
  • Обязательно рисуйте все сами. Не используйте первые попавшиеся картинки, поставляемые с HTML редакторами. Тем более не воруйте картинки даже на самых дальних узлах.
  • Нейтральный фон. Многие помещают в качестве фона логотипы и тому подобную мишуру. Это вызывает усталость глаз, затрудняет чтение (если есть, что читать), раздражает пользователя.
  • Необходимо поддерживать все кодировки на профессиональных узлах. Этим Вы проявляете уважение к пользователям других систем и расширяете контингент пользователей.
  • Желательна возможность выбора кодировки на любой странице. Если пользователь нашел Вас по ссылке, ему не придется идти в корень Вашего узла для выбора кодировки и заблудиться по пути обратно.
  • Не забывайте про alt="" в картинках. Не у всех быстрые линии, тем более у нас в стране. Позаботьтесь о тех у кого отключен показ рисунков.
  • Не позволяйте читателю заблудиться в структуре ваших документов. Если вы разрабатываете большое дерево документов, в них стоит помещать так называемые "путеводные ссылки", с помощью которых читатель может в любой момент вернуться на домашнюю страницу или же к заглавным страницам.
  • Не используйте значки "Under Construction" - это пошло. Напишите где-нибудь сбоку вежливое извинение и предложение зайти попозже. Если уж Вам так нравятся яркие картинки, поместите ее в самом верху главной страницы, а не в каждом из 3-х Ваших фрэймов.
  • Не пихайте на главную страницу фоновые звуки и другие нестандарты. Очень раздражает, когда в качестве приветствия получаешь сообщение об отсутствующем plagin'е. Пользователь должен знать, что его ожидает.
  • Постарайтесь не использовать метку <BLINK>, даже если очень хочется.
  • Следите за читаемостью текста. Попробуйте прочитать текст темносинего цвета написанный на грязнокоричневом фоне. А если у пользователя не лучшее зрение? А если у него еще и не лучший монитор? Если пользователю нужно будет прилагать усилия для чтения содержимого вашего узла, то он уйдет от вас очень скоро и никогда не вернется обратно.
  • Не злоупотребляйте анимированными GIF'ами и прочим движением, т.к. это сильно отвлекает пользователя от остального содержимого узла, а при долгом просмотре (например: чтение материала) начинает раздражать немилосердно.
  • Лучше проставлять размеры у картинок, которые влияют на раскладку (layout) страницы для сохранения ее первозданного вида при отключенных картинках. но если Вы используете иллюстрации к статье, то наоборот, нужно убрать параметры размеров, чтобы большие пустые пятна не мешали читать текст.
  • Если Вы используете BACKGROUND="fone.gif", то неплохо так же задать и BGCOLOR="#RRGGBB", где #RRGGBB - цвет, соответствующий основному цвету изображения fone.gif. При загрузке такая страница производит более благоприятное впечатление, так как фоновое изображение подгружается в последнюю очередь.
  • Вставляйте оглавление в начало больших документов. Иногда возникает необходимость объединить некоторую информацию в один достаточно большой документ. В этом случае для облегчения навигации по документу в его начало стоит вставить оглавление, содержащее ссылки на различные информационные разделы, содерщащиеся в этом документе. Тогда читателю не придется просматривать весь документ в поисках интересующей его информации - он сможет сразу перейти к ней по ссылке из оглавления.
  • Лучше думать сначала. Перед тем, как начать делать сайт, тщательно продумайте его структуру на предмет общего "дерева" страниц, ссылок между ними и использования графики. Это позволит вам потратить меньше времени на его создание пользователю больше удобства при его просмотре.
  • Старайтесь, чтобы имена файлов соответствовали содержимому (как вы его видите). Например: если ссылка "Наши партнеры" будет вести к файлу konchenye_idioty.html то ... И, к тому же, соответствие имени файла его содержимому немного повысит рейтинг ваших страниц в поисковых системах.
  • Предоставьте возможность браузерам использовать строку состояния по назначению. Мало того, что пользователь не может получить дополнительной информации о ссылке, но, кроме того, еще и текст, бегущий в строке состояния, обычно очень плохо читается.
  • Первое впечатление о вашем узле складывается от просмотра его заглавной страницы, а если еще учесть, что более 75% пользователей уйдут с нее через 10 секунд, не найдя того, что им нужно, то целесообразно будет помещать активное содержание страницы (информация о том, что конкретно пользователь может найти на этом сайте) будет целиком умещаться в окне браузера при просмотре в разрешении 800Х600 (основное количество пользователей пользуются именно этим разрешением либо выше).
  • Создайте прозрачный GIF размером 1x1 и используйте ее для точной подгонки расположения элементов вашего узла или создания разделительных пространств, выставляя аттрибуты <WIDTH> и <HIGHT>.
  • Если Вы помещаете страницы узла в таблицы с фиксированной шириной (например 600 пикселей), не забывайте помещать всю таблицу внутрь тэга <CENTER>...</CENTER>. Этим вы сильно улучшите внешний вид узла при просмотре в больших разрешениях (например 1600Х1200).
  • Не используйте маленький GIF для фона. Однажды дизайнер хотел получить "полосатый" фон и использовал для этого <BODY BACKGROUND="backgrnd.gif">. Полоски были двухцветные, горизонтальные и находились на расстоянии 10 пикселей друг от друга. Желая уложить файл для фона в минимальное количество байт, автор сделал его размером 1х11 пикселей. В итоге, даже на машине с хорошей видеокартой, я с большим удивлением наблюдал, как фон выводится строчка за строчкой, в течении секунды или двух. Не так уж и долго, но зрелище раздражающее. Ведь только для того, чтобы нарисовать один ряд такого фона (в 11 пикселей высотой) картинка выводилась на экран несколько сотен раз! Ошибка в том, что человек не вдавался в тонкости формата GIF. А последний больше всего "любит" горизонтальные области одного цвета. И сжимает их с максимальной эффективностью. Сравните: BCKGRND1.GIF, размером 1х11 пикселей "весит" 42 байта, а BCKGRND2.GIF, размером 100х11 пикселей -- 80 байт.
       bckgrnd1.gif - 42 байта
   - bckgrnd2.gif - 80 байт
    Более того, BCKGRND3.GIF (1000х11 пикселей) обошелся бы автору всего в 199 байт. Вывод: даже в ВЕБ-дизайне скупой платит дважды!
  • Хороший совет для совсем начинающих. Именно по дизайну, а не по верстке. Попробуйте сделать сайт, используя только 3 цвета, включая текст. Три и не больше. Я не говорю про невозможность оттенков, их можно сделать достаточно много, но в рамках одного цвета. Если получилось гармонично, то попробуйте 2 цвета. Далее совет, из моего интуитивного опыта, тоже для начинающих. Если на странице использован какой-то элемент, выделяющийся из общего фона, например синяя страница, а в верхнем углу нарисовна фиговина 50*50 желтая, то надо постараться использовать этот цвет в другой части страницы, чтобы воображаемая прямая между ними проходила через всю страницу. И, наконец, последний совет. Не спешите использовать в дизайне экзотические шрифты. В большинстве своем они выделяются из общего дизайна, если только не натягивать дизайн на шрифт. Получится лучше, если Вы текст, написанный стандартным шрифтом, обработаете руками в PhotoShope или другой графической программе до получения нужного Вам результата.

  • Задача: Облегчение работы по размещению объектов на отдельно взятой странице (фактически верстка).

    Примечание: для тех, кто пишет HTML-код руками или пользуется не визивиг-овыми редакторами.

    Результат: Ваши таблицы, графика, текст находятся на странице в определенных им местах.

    Способ достижения:

    1. Рисуется картинка размером 1024х768 (или меньшего размера, как кому будет удобней), на которую наносятся вертикальные и горизонтальные линии шириной в 1 пиксел(pixel), через каждые 50, 100 точек (соответственно примерно 1,75 или 3,5 см) и разметка (1-я линия - "50", 2-я - "100", и т.д.).

    Обращу внимание на то, что нужно рисовать линию ограничивающую рисунок снизу потому, что в случае, если Вы этого не сделали и у вас страница имеет скроллинг, произойдет примерно следующее:

    ------+------+------
|                  |
------+------+------
|                  | <-- первый показ фона
------+------+------
|                  |
                     <-- дырка :)
|                  |
------+------+------
|                  | <-- второй показ фона

    Назовем эту картинку, например table.gif.

    2. Помещаем эту картинку как фоновый ресунок, для чего в исходный код страницы пишем:

    <body background="table.gif" ... >.

    3. Смотрите страницу и распределяете ее элементы в удобном для Вас виде.

    4. Подкладываете реальный фоновый рисунок, для чего пишете backgr.gif вместо table.gif.


Неисчерпаемый GIF
  • Сестра таланта
  • GIF или не GIF?
  • В лаконичной палитре
  • Пуантилизм поневоле
  • Техника безопасности
  • Походным строем
  • Из-под пятницы суббота
  • Больше жизни!
  • Ученик мультипликатора
  • Новое поколение выбирает

Сейчас уже мало кто помнит те (не столь уж и отдаленные) времена, когда Web-страницы с графикой вызывали у интернетовской публики, привыкшей к черно-белому текстовому интерфейсу, не меньший восторг и энтузиазм, чем нынче - страницы с апплетами на языке Java. Теперь текст и графика - это хлеб и вино Интернета, и новомодные интерактивные расширения вроде того же языка Java в ближайшее время вряд ли составят серьезную конкуренцию этим двум основным носителям информации. Многие Web-страницы, сделанные даже на очень хорошем профессиональном уровне, по-прежнему прекрасно обходятся лишь парой форматов - HTML и GIF (и иногда JPEG). Пожалуй, только если вы - профессиональный Web-дизайнер, вам действительно может понадобиться засвидетельствовать свою квалификацию с помощью более сложно устроенной страницы.

То, что из множества существующих графических форматов роль рабочей лошадки досталась именно формату GIF, не случайно. Этот формат сочетает в себе редкий набор достоинств, неоценимых при той роли, которую он играет в WWW. Если вы интересуетесь этим графическим форматом как программист, то вам, вероятно, достаточно будет официальной спецификации. Если же вы просто хотите узнать, чем замечателен этот формат, и научиться пользоваться при создании Web-страниц всеми его преимуществами - эта статья написана для вас.

Сестра таланта

Первая версия формата GIF ("Graphics Interchange Format", "Формат для обмена графической информацией") была разработана в 1987 году специалистами компьютерной сети CompuServe. Пользователям этой сети не нравилось, что архивы с графическими изображениями из файловых библиотек CompuServe приходилось распаковывать всякий раз перед просмотром. Выход в такой ситуации очевиден: нужно, чтобы сам по себе формат содержал уже достаточно хорошо упакованные графические данные. Алгоритм распаковки должен быть при этом встроен во все программы, намеревающиеся работать с этим форматом (утилиты просмотра, графические редакторы и т.п.). Именно такой подход и реализован в формате GIF.

Хотя с тех времен пропускная способность модемов увеличилась во много раз, требования к компактности файлов, передаваемых по сетевым каналам, отнюдь не отошли на второй план. Web-страница, которая со всем своим текстом и графикой занимает свыше 40-50 Кб, уже считается "большой" - и далеко не каждый посетитель дождется, пока она целиком загрузится в его броузер. Профессионализм Web-дизайнера как раз и заключается в том, чтобы отыскать должный баланс между художественностью, информативностью страницы и ее весом. Потому-то формат GIF, главным достоинством которого является минимальный размер файлов, до сих пор сохраняет свое значение основного графического формата World Wide Web.

Конечно, GIF - не единственный формат, позволяющий сжимать графические данные; кое-какие способности к сжатию есть у форматов BMP, PCX и многих других. Однако GIF - один из немногих форматов, использующих "настоящий" алгоритм сжатия, почти не уступающий по эффективности программам-архиваторам. Иными словами, GIF-файл не нужно архивировать, так как это редко когда дает ощутимый выигрыш в объеме.

Как и у программ-архиваторов, степень сжатия графической информации в GIF сильно зависит от уровня ее повторяемости и предсказуемости, а иногда еще и от ориентации картинки - поскольку GIF сканирует изображение по строкам, то, к примеру, плавный переход цветов (градиент), направленный сверху вниз, сожмется куда лучше, чем тех же размеров градиент, ориентированный слева направо, а этот последний - лучше, чем градиент по диагонали.

GIF или не GIF?

Из распространенных графических форматов GIF однозначно уступает по степени сжатия только формату JPEG. (Строго говоря, аббревиатурой JPEG - от названия организации "Joint Photographic Experts Group" - обозначается не сам формат, а алгоритм сжатия с потерями, применяемый в формате JFIF, "JPEG File Interchange Format", и в некоторых других. Файл с расширением .jpeg или .jpg - это, как правило, файл формата JFIF, данные в котором сжаты по технологии JPEG.)

Однако своих впечатляющих результатов JPEG достигает, если можно так выразиться, не совсем законными методами - при сохранении в этом формате качество изображения снижается, причем технология JPEG замечательна именно тем, что позволяет достичь серьезного выигрыша в размере при минимально заметных для глаза потерях в качестве. В отличие от JPEG, формат GIF пользуется обычным "сжатием без потерь" ("lossless compression"). Еще одним преимуществом JPEG по сравнению с GIF является возможность хранить полноцветные изображения с 16 миллионами цветов ("true color", "истинный цвет"), тогда как GIF ограничен лишь 256-цветной палитрой.

Обычно формат JPEG рекомендуют использовать только для фотографий, на которых снижение качества, сопутствующее сжатию по этой технологии, наименее заметно. Правильнее будет, однако, разделить все изображения на "искусственные", содержащие резкие контрастные переходы цветов или большие площади, залитые однородным, "плоским" цветом, и "естественные" - те, в которых все плавно, неоднородно и не слишком четко. Очевидно, что изображения, создаваемые с помощью компьютера, - логотипы, текстовые заголовки и т.п. - по этой классификации чаще принадлежат к "искусственным", а сканированные фотографии и рисунки - к "естественным". Однако нередки и исключения из этого правила.

Для большинства типично "искусственных" изображений JPEG противопоказан - как правило, мусор и рябь, появляющиеся возле резких переходов цветов, в них слишком заметны. С другой стороны, GIF может безнадежно испортить красивую фотографию, втиснув ее в свою 256-цветную палитру. Поэтому граница (довольно-таки расплывчатая) между "искусственными" и "естественными" изображениями по большей части совпадает с границей между владениями GIF и JPEG. В то же время иногда вполне "искусственные" изображения, особенно небольшие по размеру и с темной цветовой гаммой, вполне пристойно выглядят и в JPEG. Судьей здесь должен быть ваш вкус и глаз, а критерием, при прочих равных, - размер получающегося файла (только, прежде чем сравнивать, прочтите ниже, как можно иногда довольно существенно уменьшить размер GIF-файла).

В лаконичной палитре

Резервы экономии в GIF не исчерпываются одной лишь компрессией данных. Одним из главных достоинств этого формата является то, что он позволяет произвольно задавать количество цветов, или размер палитры, используемой в изображении. Другие форматы, как правило, имеют лишь стандартные градации цветовой глубины: 2 цвета, 16 цветов, а потом сразу 256, 215 (режим "high color") и 224 (режим "true color"). GIF же может иметь любое количество цветов от 2 до 256, и если в изображении используется, скажем, 64 цвета (26), то для хранения каждого пиксела будет использовано ровно шесть бит и ни битом больше. (Можно, конечно, заказать и 60 или 40 цветов, но выигрыша в размере это уже не даст, так как пиксел все равно будет занимать шесть бит; только если вы втиснете свой рисунок в 32-цветную гамму, размер каждого пиксела сократится еще на один бит).

Уменьшение количества цветов в палитре - очень эффективный метод снижения размера GIF-файла. К сожалению, большинство графических программ (и тех, кто ими пользуется) не подозревают об этой возможности и записывают все GIF-файлы с полной, 256-цветной палитрой. Из программ, способных сохранять файлы в формате GIF с произвольной цветовой глубиной, отметим знаменитый Adobe Photoshop, а также (вероятно, для многих более доступную) shareware-утилиту LView Pro.

Любой, кто имел дело с компьютерной графикой, скажет вам, что цветные изображения лучше всего создавать в режиме "true color", отводя на каждый пиксел по 24 бита (если, конечно, позволяет объем памяти компьютера), и только перед самым выводом окончательного варианта в GIF-файл палитру изображения можно редуцировать. (Для черно-белых изображений в любом случае достаточно 256-цветной "серой шкалы" - "gray scale".) Поэтому, даже если уменьшение размера файла для вас не слишком актуально, описанная здесь методика все равно может вам пригодиться для перевода полноцветных изображений в 256-цветные, которые только и могут быть сохранены в формате GIF.

Загрузив полноцветное или 256-цветное изображение в LView Pro, воспользуйтесь командой Retouch|Color Depth. В появившемся окне (рис. 1) нужно прежде всего выбрать опцию Palette image, а затем перевести переключатель Palette creation and quantizing options в положение Custom number of colors. Введите требуемое число цветов в поле рядом с этой опцией, удостоверьтесь, что флажок Enable Floyd-Steinberg Dithering (включающий диффузию, о которой ниже) установлен, и нажмите OK. Отменить только что сделанную редукцию палитры можно командой Edit|Undo.

Рисунок 1. Выбор размера палитры в LView Pro

В Adobe Photoshop цветовая глубина регулируется с помощью команд меню Mode. Если в вашем изображении уже 256 цветов, то вам придется сначала перевести его в полноцветное командой RGB Color, а затем спуститься обратно в ограниченную палитру командой Indexed Color. В диалоговом окне, которое вызывается этой командой (рис. 2), можно выставить глубину цвета прямо в терминах количества битов на пиксел; если полное число цветов в изображении меньше или равно 256, Photoshop предложит установить точную (Exact) палитру именно с таким количеством цветов. Если же при установленной цветовой глубине опция Exact для палитры недоступна, следует выбрать Adaptive и установить переключатель Diffusion в области Dither.

Рисунок 2. Выбор размера палитры в Adobe Photoshop

Заранее довольно сложно предсказать, до какой цветовой глубины удастся сжать изображение без значительных потерь. Поэтому здесь можно порекомендовать лишь классический метод научного тыка: начав с 256 цветов, двигайтесь вниз по степеням двойки, пока качество результата не перестанет вас удовлетворять. Например, для одноцветных текстовых надписей на одноцветном фоне почти всегда достаточно восьми цветов (но не двух, как можно было бы подумать, потому что контуры букв обычно содержат пикселы промежуточных цветов). Если же для вашей полноцветной картины даже 256-цветная палитра приводит к недопустимому снижению качества, вам, скорее всего, придется отказаться от формата GIF в пользу JPEG.

Photoshop обладает еще одной полезной способностью. Если во время перевода полноцветного изображения в ограниченную палитру какая-то часть изображения будет выделена, то Photoshop попытается по возможности улучшить цветопередачу именно этой выделенной области. Иными словами, при принятии решения - какие цвета нужно передавать точно, а в каких можно и соврать - предпочтение будет отдаваться цветам, входящим в выделение.

Пуантилизм поневоле

Иногда описанная выше редукция палитры обходится вообще без потерь качества, так как исходное изображение уже содержало меньше 256 цветов. Но куда чаще при сокращении количества цветов программе приходится аппроксимировать исходное изображение цветами усеченной палитры. Это можно сделать одним из двух способов.

Первый способ заключается в том, что каждый цвет исходного изображения переводится просто в ближайший к нему цвет новой палитры (а поскольку она меньше исходной, некоторые цвета, различавшиеся в оригинале, после преобразования сольются в один цвет). Как правило, это приводит к тому, что плавные переходы цветов становятся ступенчатыми, а общий колорит изображения довольно резко меняется.

Посему чаще используется другой метод аппроксимации - диффузия (diffusion, или dithering). При этом области, которые в оригинале были залиты однородным цветом, после преобразования заполняются смесью пикселов разных цветов, разбросанных по случайному закону. Как правило, каждый отсутствующий в редуцированной палитре цвет передается пикселами двух самых близких к нему цветов новой палитры. В результате изображение приобретает характерную зернистую, шершавую фактуру. Очень часто диффузия является единственным способом хоть сколько-нибудь адекватно передать исходные цвета с помощью палитры, на которой этих цветов уже нет. В LView Pro диффузия включается флажком Enable Floyd-Steinberg Dithering (см. рис. 1), а в Photoshop - опцией Diffusion в области Dither (рис. 2).

Какой же из этих методов и в каких случаях следует выбирать? Здесь нам снова придется вспомнить разделение изображений на "искусственные" и "естественные", о котором мы говорили чуть выше. "Искусственные" изображения с областями однородного цвета и резкими границами обычно не очень сильно проигрывают от прямого замещения цветов без диффузии. Конечно, общий спектр изображения при этом изменится, но во многих случаях ничего страшного в этом нет. Диффузия же для таких изображений противопоказана, так как придает им довольно неряшливый вид. Наоборот, "естественные" изображения от введения диффузии обычно почти не страдают, в то время как попытка сократить их палитру без диффузии нередко приводит к совершенно неприемлемым результатам.

Следует также учитывать, что редукция цветовой глубины с использованием диффузии гораздо терпимее к размеру палитры. Например, без применения диффузии изображение нередко не выдерживает редукции даже до 128 цветов, тогда как с диффузией его можно обкорнать до 64 или даже 32 цветов без видимой потери качества. Это позволяет, кстати говоря, скомпенсировать тот факт, что "естественные" изображения в формате GIF гораздо хуже поддаются сжатию - снизив цветность с включенной диффузией, можно довести размер такого изображения до вполне приемлемых цифр.

С другой стороны, случайная диффузия, как и любая нерегулярность ("шум") в данных, резко ухудшает сжимаемость графики. Вот почему иногда, снизив цветность картинки, скажем, с 256 до 128 цветов, из-за этого противоположно направленного эффекта мы вместо уменьшения размера файла получим увеличение.

Техника безопасности

Если, прочитав предыдущую главку, вы пришли к выводу, что для вашего изображения диффузия не годится, то вас ждет неприятный сюрприз. Оказывается, все существующие ныне броузеры Web, включая Netscape Navigator и Microsoft Internet Explorer, вынуждены при выводе графических файлов на экран изменять их палитру. Во-первых, им приходится приводить к общему знаменателю палитру всех файлов, включенных в одну страницу (поскольку большинство компьютерных мониторов не могут отобразить больше 256 цветов одновременно, приходится идти на компромиссы). Во-вторых, в некоторых случаях броузеру приходится также приспосабливать палитру файла к фиксированной системной палитре. Чтобы эти неизбежные преобразования не приводили к совсем уж неприемлемым результатам, все современные броузеры пользуются при замещении палитры диффузией. Это значит, что даже если ваше изображение состоит из идеально гладких цветовых плоскостей, но палитра его почему-либо не может быть отображена напрямую, то в броузере это изображение неизбежно станет шероховатым.

Проблема эта действительно серьезна, так как для очень многих изображений, составляющих нередко основу дизайна страницы, диффузия крайне нежелательна. Что же делать? Выход, хоть и не слишком изящный, есть. Оказывается, существует набор из 216 цветов, которые с гарантией никогда не подвергаются диффузии ни в Netscape Navigator, ни в Internet Explorer, ни в Windows, ни на Macintosh. Если ваше изображение будет содержать только цвета из этого набора (или, по крайней мере, к этому набору будут относиться цвета, занимающие наибольшую площадь), в броузере оно будет выглядеть намного чище и привлекательнее. Эту палитру часто называют "безопасной" ("browser-safe palette").

Если вы решите воспользоваться этим трюком, то это не поздно сделать, даже если ваша графика уже готова. Вы вполне можете навести, отключив диффузию, безопасную 216-цветную палитру на уже готовые файлы, уповая на то, что палитра эта покрывает весь спектр и потому (если только вы не пользуетесь какими-то особо экзотическими цветами) сильного искажения цветовой гаммы не произойдет. Однако если вы хотите иметь больше контроля над цветовой стороной своего дизайна, те графические элементы, для которых хотелось бы избежать диффузии, лучше с самого начала рисовать с использованием этой безопасной палитры.

Где же взять такую палитру? Если вы пользуетесь Adobe Photoshop, то можете скачать готовый файл с безопасной палитрой в формате ACT. Эту палитру можно загрузить в качестве рабочей палитры ("swatches") и пользоваться ею при рисовании, а можно и навести на готовое 256-цветное изображение. В последнем случае вам нужно будет сначала переехать в RGB Color, а на обратном пути выбрать для палитры опцию Custom и отключить диффузию, переведя переключатель Dither в положение None (см. рис. 2). В окне Color Table, которое должно после этого появиться (рис. 3), нажмите кнопку Load и загрузите ACT-файл с безопасной палитрой.

Рисунок 3. Безопасная палитра ("browser-safe palette"), загруженная в Adobe Photoshop

Если же вы работаете с другим графическим редактором, то для вас, видимо, единственный способ пользоваться безопасной палитрой - подбирать ее цветовые значения вручную, пользуясь таким несложным правилом: 216 цветов этой палитры получаются комбинированием красной, зеленой и синей составляющих, каждая из которых может принимать только шестнадцатеричные значения 00, 33, 66, 99, CC и FF (или десятичные 0, 51, 102, 153, 204 и 255). Таким образом, три составляющих, каждая из которых может принимать шесть разных значений, дают 63 = 216 возможных цветов.

Для "естественных" изображений, которые подвергаются диффузии еще при сохранении в формате GIF и для которых поэтому дополнительная диффузия в броузере уже не страшна, пользоваться этой безопасной палитрой нет никакого смысла. Отсюда, кстати, следует, что комбинированную графику, объединяющую "искусственные" и "естественные" элементы, желательно по возможности разделять на части, чтобы диффузия их естественной составляющей не затрагивала искусственной.

Походным строем

Получив в свои руки графический формат, файлы в котором не нужно архивировать и разархивировать вручную, пользователи CompuServe были поначалу очень довольны. Однако через некоторое время появились программы, которые выводили изображение из GIF-файла на экран прямо по мере получения его из сети - и для этого, как оказалось, первоначальный вариант формата GIF не очень-то подходил. Поскольку в CompuServe (как и во многих других местах) платят за время соединения, мало кому понравится ждать, пока обычный GIF, медленно ползущий через модем на экран, дорисуется хотя бы до половины, чтобы понять, что же он собой представляет. Идеальной была бы возможность быстро знакомиться с содержимым графических файлов, не скачивая их полностью.

В некоторых форматах для этого используется уменьшенная копия изображения, вставляемая в тот же файл и предназначенная для быстрого ознакомления с его содержимым. Однако программисты CompuServe, опять-таки в заботе о компактности файлов, нашли другой выход из положения, довольно остроумный и притом не требующий никакого дублирования информации. Изменив порядок следования данных в файле, они заставили картинку рисоваться не только сверху вниз, но и, если можно так выразиться, "с глубины к поверхности" - то есть становиться все четче и детальнее по мере подхода из сети новых данных.

Для этого файл с изображением тасуется при записи так, чтобы сначала шли все строки пикселов с номерами, кратными восьми (первый проход), затем четырем (второй проход), потом двум, и, наконец, последний проход - все оставшиеся строки с нечетными номерами. Во время приема и декодирования такого файла каждый следующий проход заполняет "дыры" в предыдущих, постепенно приближая изображение к исходному состоянию. Нечто подобное используется в телевидении для передачи одного полного кадра за два прохода луча (так называемая "чересстрочная развертка"). Поэтому такие изображения были названы чересстрочными (interlaced).

Приняв из сети очередной проход чересстрочного GIF-файла, броузер не только рисует каждую его строку на своем месте, но и заполняет копиями предыдущей строки все промежутки между строками (иначе после первого прохода изображение было бы почти невидимым). Поэтому поначалу чересстрочный GIF состоит из широких горизонтальных полос, которые постепенно сужаются по мере проявления изображения.

Из-под пятницы суббота

Возможность записи чересстрочных файлов была введена во второй версии формата GIF, опубликованной в 1989 году. Эту версию обозначают "GIF89a" в отличие от первой - "GIF87a". (Быстро выяснить разновидность формата GIF-файла можно, просто заглянув в него любой утилитой для просмотра текста; первые шесть символов файла содержат либо "GIF89a", либо "GIF87a".)

В этой же версии появились и другие важные усовершенствования, а именно возможность указывать прозрачный цвет и записывать в один файл несколько самостоятельных картинок - кадров движущегося, мультипликационного изображения.

К сожалению, все эти новшества мало где применялись до того, как их поддержка появилась в броузерах WWW. Поэтому большинство графических программ до недавнего времени не утруждали себя работой с форматом GIF89a, записывая файлы только в GIF87a. Первые две из этих возможностей - запись чересстрочных файлов и указание прозрачного цвета - поддерживаются в упомянутых выше LView Pro и Adobe Photoshop, а для работы с GIF-мультфильмами существуют отдельные программы, об одной из которых я расскажу ниже.

Прозрачный цвет позволяет, по сути, создавать GIF-файлы произвольной формы - пикселы рисунка, имеющие этот цвет, при просмотре в броузере будут невидимы. Казалось бы, поскольку HTML не позволяет (пока) накладывать изображения друг на друга или на текст, этим имеет смысл пользоваться только на тех страницах, фон которых заполнен каким-то изображением. Однако из-за почти неизбежной диффузии в броузере, о которой мы уже говорили, к прозрачности приходится прибегать даже тогда, когда фон страницы покрашен однородным цветом: если вы заполните фоновые части рисунка тем же самым цветом, но не сделаете их прозрачными, граница между шершавым фоном рисунка и гладким фоном страницы в броузере будет слишком хорошо заметна. Отсюда же следует, что в изображениях, использующих прозрачность, нельзя пользоваться редукцией палитры с диффузией - ведь прозрачным может быть объявлен только один цвет, и если фон изображения будет состоять из смеси разноцветных пикселов, полностью прозрачным его сделать уже не удастся (впрочем, как мы увидим ниже, использование Adobe Photoshop позволяет обойти это ограничение).

Следует иметь в виду, что граница между прозрачным цветом и остальными элементами изображения должна быть как можно более резкой. Любая хоть сколько-нибудь размытая граница, содержащая пикселы промежуточных цветов, уже не будет прозрачной, и в результате в броузере вокруг видимых элементов изображения будет неприятный ореол. И хотя "мягкие" цветовые переходы (по-английски этот эффект называется "anti-aliasing") в компьютерной графике по справедливости считаются признаком профессионализма, на внешнем контуре рисунков с прозрачным фоном их следует избегать.

С другой стороны, если фоновое изображение, на котором будет расположен ваш рисунок, достаточно однородно по цвету, можно залить задний план рисунка схожим цветом и сделать его прозрачным. В этом случае размытая внешняя граница уже не страшна, так как она будет благополучно сливаться с фоновым изображением. Не забудьте только, что при загрузке страницы в броузер фоновое изображение нередко подходит из сети позже, чем графика переднего плана с прозрачным фоном. Поэтому, чтобы не портить впечатление, в теге <BODY> вашей страницы, помимо атрибута BACKGROUND, полезно указать также атрибут BGCOLOR, подобрав для него цвет, максимально близкий к основному цвету фонового изображения.

В LView Pro версии 1.D2 чересстрочный режим записи GIF-файлов устанавливается командой Save Interlaced на панели GIF окна Properties (команда File|Properties), а для указания прозрачного цвета служит команда Background Color в меню Retouch. В диалоговом окне Background Color можно нажать кнопку Dropper, что позволит указать нужный цвет "пипеткой" прямо на изображении.

Если же вы хотите работать с GIF89a в Adobe Photoshop версии 3.0, то вам прежде всего нужно сходить в Интернет и взять специальный подключаемый модуль (plug-in). Когда вы поместите этот модуль в подкаталог для подключаемых модулей Photoshop, в подменю File|Export появится новая команда GIF89a Export. Дав эту команду для полноцветного изображения (рис. 4), вы сможете, во-первых, проделать все операции выбора размера палитры, о которых мы уже говорили, а во-вторых - установить режим чересстрочной записи (флажок Interlaced) и указать цвет, который будет помечен как прозрачный.

Рисунок 4. Экспорт изображения в GIF89a из Adobe Photoshop

Однако будьте внимательны: выбор цвета в этом окне не означает "сделать прозрачным все, что имеет этот цвет", а наоборот - "покрасить в этот цвет все, что должно быть прозрачным". Чтобы указать, что же именно в изображении будет прозрачным, а что нет, нужно проделать кое-какую подготовительную работу. Выделите все непрозрачные части изображения и перенесите их на отдельный слой, спрятав все остальные слои, - чтобы на тех местах, которые вы хотите сделать прозрачными, была видна клетчатая подложка, которую Photoshop показывает в отсутствие какого-либо другого фона (рис. 5). Теперь при выборе команды GIF89a Export в файл будет записано только содержимое видимого в данный момент слоя с учетом его прозрачности.

Рисунок 5. Изображение, подготовленное в Photoshop для экспорта в GIF89a с сохранением прозрачности

Вы, вероятно, знаете, что Photoshop позволяет реализовать частичную прозрачность - в частности, плавный переход непрозрачных областей в прозрачные (обратите внимание на тень от шарика на рис. 5). Скажем, дав команду Select|Feather перед выносом выделенных объектов в отдельный слой, вы получите размытые края выделения, постепенно переходящие в прозрачный фон. И несмотря на то, что формат GIF предусматривает для пикселов изображения только две градации - абсолютную прозрачность либо абсолютную непрозрачность, - Adobe Photoshop с помощью все той же диффузии способен симулировать эффект частичной прозрачности в GIF-файле. Так, экспортировав в GIF89a изображение, показанное на рис. 5, мы получим по краям тени "диффузную прозрачность" - смесь прозрачных и непрозрачных пикселов, плавно переходящую в стопроцентно прозрачный фон.

Больше жизни!

Несмотря на множество технологий, призванных придать интерактивности Web-страницам, большая часть содержимого World Wide Web по-прежнему остается статической, и появление на странице чего-нибудь движущегося, подпрыгивающего или переливающегося с гарантией приковывает взгляд посетителя. Конечно, очень даже можно сделать приличную - по любым меркам - страницу, на которой не будет ничего, кроме статического текста и графики. Однако во многих случаях чуть-чуть движения дизайну страницы совсем не повредит.

В последнее время необычайно популярным стало использование мультипликационных способностей формата GIF. Оказывается, формат GIF89a, помимо прочих своих достоинств, позволяет хранить в одном файле сразу несколько изображений-кадров и притом указывать, сколько времени держать на экране каждое из них, прежде чем сменить следующим. Броузеру остается лишь подчиниться этим инструкциям, чтобы прямо в HTML-документе возникло движущееся и циклически повторяющееся изображение. Достоинством этого метода является то, что пользователи броузеров, не умеющих пока работать с GIF-мультфильмами, почти не пострадают - просто вместо движущегося изображения они увидят его первый кадр.

Создание движущихся GIF-изображений лежит, впрочем, настолько в стороне от обязанностей обычных графических редакторов, что похвалиться умением делать это не может даже всемогущий Adobe Photoshop. Чтобы создать GIF-мультфильм, вам придется воспользоваться специальной программой, которая сама рисовать не умеет, а лишь позволяет собирать готовые картинки-кадры в файл формата GIF89a и снабжать их необходимой служебной информацией. Самой известной из таких программ является GIF Construction Set фирмы Alchemy Mindworks. Давайте попробуем сделать с помощью этой программы небольшой движущийся GIF, попутно разбираясь с принципами, возможностями и ограничениями этого жанра Web-творчества.

Ученик мультипликатора

Файл в формате GIF (как мультипликационный, так и обычный) состоит из блоков, каждый из которых хранит определенную часть информации файла. Все изображения-кадры хранятся в отдельных блоках; кроме того, GIF-файл должен содержать набор специальных блоков со служебной информацией. В программе GIF Construction Set вы будете работать непосредственно с блоками GIF-файла, поэтому вы должны понимать назначение основных из них.

Давайте попробуем сделать с помощью GIF-мультипликации рамку, расцвеченную бегущими по кругу огоньками, - что-то похожее часто делают в уличной рекламе с помощью лампочек, миганием которых управляет несложное устройство (рис. 6). Стороны этой рамки сделаны из повторяющихся GIF-файлов, в каждом из которых лампочка за один проход перекатывается из конца в конец. На рис. 7 показаны отдельные кадры GIF-файла, из которого строятся нижняя и верхняя стороны рамки (для низа рамки, где направление движения должно быть противоположным, кадры будут располагаться в обратном порядке). Кадры этого изображения были нарисованы в Photoshop и сохранены в GIF-файлах размером 37 на 10 пикселов.

Рисунок 6. Готовая рамка с движущимися огоньками

FRAME1.GIF
FRAME2.GIF
FRAME3.GIF
FRAME4.GIF
FRAME5.GIF
FRAME6.GIF
Рисунок 7. Отдельные кадры GIF-файла, из копий которого строятся горизонтальные стороны рамки

Сразу после создания нового, пока еще пустого GIF-файла командой File|New список блоков в главном окне программы GIF Construction Set (рис. 8) содержит единственный обязательный блок - блок HEADER, в котором указываются размеры прямоугольника, отводимого под весь мультфильм (отдельные кадры, как мы увидим позже, вполне могут иметь и меньший размер). Щелкнув дважды по строке HEADER в списке блоков, введите в появившемся окне ширину и высоту наибольшего из подготовленных кадров (в пикселах).

Рисунок 8. Окно программы GIF Construction Set в начале создания нового GIF-файла

Следующий этап - добавление блока LOOP, назначение которого - указать, сколько раз броузер должен будет циклически прокрутить этот GIF. Нажмите кнопку Insert ("Вставить"), а в ответ на вопрос, что именно вставить, нажмите Loop. Этот блок всегда встает вторым по счету в файле, сразу после блока HEADER. Дважды щелкнув по нему в списке, закажите, сколько раз должен прокручиваться ваш мультфильм (по умолчанию 1000), либо введите 0, если вы хотите, чтобы изображение не останавливалось никогда.

Теперь можно вставлять подготовленные кадры мультфильма, хранящиеся в отдельных файлах. Еще раз нажмите кнопку Insert, затем Image. Когда вы укажете нужный файл, программа задаст вам вопрос о том, что делать с его палитрой. Дело в том, что мультипликационный GIF-файл может иметь одну глобальную палитру, общую для всех кадров, а может и хранить для каждого кадра отдельную локальную палитру. Поэтому для первого вставляемого изображения в диалоговом окне Palette (рис. 9) разумнее всего выбрать опцию Use this image as the global palette, "Использовать палитру этого изображения как глобальную". Опция Dither this image to the global palette позволяет привести вставляемое изображение к глобальной палитре с использованием диффузии - этот вариант пригодится для следующих кадров, если их палитра будет отличаться от палитры первого.

Рисунок 9. Как поступить с палитрой вставляемого кадра?

Можно, конечно, завести для каждого кадра свою палитру (опция Use a local palette for this image), но, во-первых, это приведет к увеличению размера файла, а во-вторых, броузеру будет не слишком удобно переключать палитру дисплея для каждого кадра вашего мультика. Поскольку для быстро сменяющихся кадров идеальная цветопередача не так уж важна, лучше в случае расхождения палитр пользоваться диффузией. Если, как часто бывает, ваши кадры представляют собой модификации одного исходного изображения, то, скорее всего, палитра у них и без того будет одинаковой, так что это диалоговое окно при вставке следующих кадров вы уже не увидите.

Если же отдельные кадры сильно различаются по цветовой гамме, можно порекомендовать такой способ: соберите с помощью графического редактора все кадры в одно большое изображение, запишите его в GIF-файл (при этом палитра кадров будет приведена к общему знаменателю) и вставьте этот файл первым кадром, приняв его палитру в качестве глобальной. После сборки мультфильма этот первый кадр можно будет убрать.

Если вставленное изображение по размерам меньше "экрана" - прямоугольника, отведенного под весь мультфильм (габариты этого прямоугольника записаны в блоке HEADER), то теперь вы можете отрегулировать положение кадра в пределах этого экрана. Щелкнув дважды по строке с изображением в списке (или нажав кнопку Edit), в диалоговом окне Edit Image (рис. 10) можно выставить координаты левой верхней точки данного кадра относительно левой верхней точки экрана (поля Image left и Image top). Таким образом можно, например, создать мультфильм с большим экраном, на котором какой-нибудь маленький объект будет не только изменяться, но и свободно двигаться в пределах отведенной ему площади. Здесь же можно установить флажок Interlaced, включив чересстрочный режим для отдельного кадра (хотя большого смысла в этом нет), а также, при желании, загрузить для этого кадра новую локальную палитру кнопкой Load.

Рисунок 10. Регулировка параметров отдельного кадра

Расставив в нужном порядке все кадры, мы должны ввести информацию о том, с какой скоростью их сменять. Для хранения этой величины (а также некоторых других служебных данных) служат управляющие блоки CONTROL, каждый из которых располагается перед своим блоком с изображением и определяет параметры одного кадра. Блоки CONTROL можно расставлять и вручную (нажимая кнопку Insert, а затем Control), но удобнее проложить этими управляющими блоками все кадры за раз. Для этого нажмите кнопку Manage.

Диалоговое окно Block Management (рис. 11), как и главное окно программы, содержит в левой части список блоков собираемого GIF-файла. Выделив протаскиванием (или кнопкой Select All) все блоки IMAGE, нажмите кнопку Apply в правой верхней области окна. Это приведет к появлению окна Edit Control Block (рис. 12), в котором и устанавливается время отображения каждого кадра (поле Delay) в сотых долях секунды. Закрыв оба окна, вы увидите в главном окне программы, что перед каждым кадром встало по блоку CONTROL (рис. 13). Теперь любой из этих управляющих блоков можно редактировать по отдельности (с помощью двойного щелчка или кнопки Edit) - например, если вам нужно, чтобы какой-то кадр задержался на экране дольше, чем остальные.

Рисунок 11. Вставка управляющих блоков для всех импортированных кадров

Рисунок 12. Параметры управляющего блока

Рисунок 13. Мультфильм готов!

В том же окне редактирования управляющего блока (рис. 12) можно выбрать цвет, который будет прозрачным в данном кадре, установив флажок Transparent colour и выбрав нужный цвет либо из палитры (нажав кнопку с номером), либо прямо с изображения с помощью знакомой всем "пипетки". Выпадающий список Remove by позволяет указать, что должен сделать броузер с данным кадром перед тем, как выводить следующий. Так, опции Nothing и Leave as is обе означают "ничего не делать", и если, например, следующий кадр смещен относительно предыдущего или содержит прозрачный цвет, то при этом часть предыдущего кадра может остаться видимой. Опция Background затирает каждый кадр перед выводом следующего фоновым цветом, а Previous image приказывает броузеру восстановить то, что было на этом месте до вывода кадра (например, фоновое изображение страницы).

Чтобы протестировать готовый мультфильм, нажмите кнопку View - и, если результаты вас удовлетворяют, сохраните файл командой File|Save as. Сразу же после этого готовый GIF-мультфильм можно будет открыть в броузере и увидеть, как он будет вести себя в реальной жизни.

Чтобы доделать живую рамку, показанную на рис. 6, нам придется создать по тому же алгоритму GIF-файлы для нижней и боковых сторон (они будут состоять из тех же кадров, повернутых на 90 градусов и/или расположенных в обратном порядке). Затем нужно будет сделать еще четыре маленьких файла для уголков рамки и, наконец, потратить некоторое время на синхронизацию всей системы, с тем чтобы ползущие огоньки без запинки пересекали все сочленения. Те, кого заинтересовал этот опыт применения мультипликационных GIF-файлов, найдут страничку с готовой собранной рамкой по адресу http://www.symbol.ru/dk/gif/border.html.

Новое поколение выбирает

В конце декабря 1994 года американская компания Unisys оповестила весь мир о том, что в результате приобретения некоей мелкой фирмы в руки Unisys попал патент, выданный в 1985 году на алгоритм сжатия данных LZW, используемый в формате GIF. Этот факт дает Unisys законное право требовать выплаты отчислений с производителей всех программ, которые реализуют этот запатентованный алгоритм - иными словами, хоть как-то работают с форматом GIF. Боюсь, мы никогда уже в точности не узнаем, почему Unisys сидела в засаде столько времени и приставила нож к горлу программистов только теперь, когда формат GIF завоевал повсеместное признание. Событие это всколыхнуло всю программистскую общественность - и, хотя патентные отчисления не так уж велики (а авторы некоммерческих продуктов освобождены от них вообще), всем стало ясно, что пора создавать новый открытый стандарт, который бы смог заменить GIF. Было бы совсем замечательно, если бы новый формат был избавлен от недостатков GIF - которых у этого формата, пожалуй, не меньше, чем достоинств.

Из претендентов на роль наследника GIF наибольшее признание завоевал формат PNG ("Portable Networks Graphics"). Возможностью работать с файлами этого формата уже обладают многие графические пакеты - например, Photoshop 4.0, а также популярный shareware-редактор Paint Shop Pro. Оба главных броузера Web - Netscape Navigator и Microsoft Internet Explorer - также поддерживают PNG, хотя, к сожалению, и не напрямую: у Netscape Navigator для этого должен быть установлен специальный подключаемый модуль (plug-in), активизируемый тегом <EMBED>, а Internet Explorer понимает ссылки на PNG-файлы только в теге <OBJECT>. Остается лишь надеяться, что хотя бы следующие версии этих броузеров будут способны к нормальной поддержке PNG через тег <IMG>.

Чем же PNG лучше GIF? Судите сами:

  • PNG реализует открытый, незапатентованный алгоритм сжатия данных, дающий тем не менее лучшие результаты, чем GIF.
  • В отличие от GIF с его максимум 256 цветами, PNG позволяет хранить полноцветные изображения с 24 и даже 48 битами на пиксел.
  • PNG реализует гораздо более эффективный алгоритм чересстрочности (которую в данном случае правильнее было бы называть "через-пиксельностью"). Достаточно сказать, что первый проход, дающий общее представление об изображении, занимает в PNG не одну восьмую исходного файла, как в GIF, а всего лишь одну шестьдесят четвертую - и тем не менее распознаваемость картинки при этом заметно лучше.
  • PNG позволяет хранить полную информацию о степени прозрачности в каждой точке изображения в виде так называемого альфа-канала. Каждый пиксел PNG-файла, вне зависимости от его цвета и местоположения, может иметь любую градацию прозрачности - от нулевой (полная непрозрачность) до абсолютной невидимости.
  • Чтобы сгладить различия в яркости дисплеев, существующие между разными типами компьютеров (и, соответственно, между создаваемыми на них изображениями), PNG позволяет хранить в файле исходный коэффициент яркости того дисплея, на котором изображение было создано. Это дает возможность программам просмотра перед выводом на экран регулировать яркость изображения, приспосабливая ее к особенностям данной видеосистемы (так называемая гамма-коррекция).

Пожалуй, если PNG чем-то и уступает GIF, то только невозможностью хранить несколько изображений в одном файле и создавать что-либо похожее на GIF-мультипликацию. Вместе с тем решение разработчиков PNG отказаться от введения подобных излишеств в свой формат нельзя не признать логичным, поскольку такое использование формата GIF - это типичный "рак на безрыбье": для движущихся изображений куда разумнее пользоваться форматами, специально для этого предназначенными (например, позволяющими хранить не только изображение, но и звуковое сопровождение к нему). Однако пока что такие форматы (AVI, MPEG и другие) не получили широкого распространения в WWW, и GIF-мультипликация, судя по всему, не собирается в ближайшее время выходить из моды. Нам с вами остается лишь учиться пользоваться всеми возможностями GIF и ждать, когда молодой, полный свежих сил PNG наконец-то позволит этому удивительному формату-на-все-руки уйти на заслуженный отдых.


Анимационные картинки: что это и как их создавать.

Содержание.

Введение.
Что такое animated GIF.
Структура файла GIF89a.
Создание анимаций.
Оптимизация анимаций.
Использование анимаций.
Дополнительная информация.

Введение.

Каждый из пользователей Интернет уже вполне привычно воспринимает появляющиеся почти на каждой странице картинки-мультфильмы, так называемые динамические, или анимационные (animated) GIFы. Чаще всего именно такими являются рекламные баннеры. Оно и понятно - во-первых, мультипликация привлекает гораздо больше внимания, чем статическая картинка, во-вторых, на небольшой площади можно уместить гораздо больше информации за счет чередующихся кадров. Но эти же свойства могут оказаться весьма полезными любому вебмастеру, который стремится сделать свой сайт более привлекательным. Поэтому Вашему вниманию и предлагается данная статья, которая рассматривает вопросы создания и использования таких картинок.

Что такое animated GIF.

Динамический GIF можно определить, как стандарт графического файла, позволяющий размещать в одном файле несколько изображений, для их последовательного вывода на экран с целью создания эффекта мультипликации. Первым таким стандартом явился GIF87a, который предусматривал следующие возможности GIF-файла:
  1. Чередование (interlacing). Вначале загружается только "остов" изображения, потом, по мере загрузки, оно детализируется. Это позволяет на медленных линиях не грузить весь графический файл целиком для того, чтобы получить о нем представление.
  2. Сжатие (compression) по алгоритму LZW. Эта черта GIF-файлов держит их в лидерах по наименьшему объему файла.
  3. Несколько изображений в одном файле.
  4. Расположение картинки на логическом экране. То есть, формат позволял определить логическую экранную область для вывода изображений, и помещать картинки в произвольном месте этой области.
В дальнейшем, этот стандарт был раширен спецификацией GIF89a, которая добавила следующие возможности:
  1. Включение в графический файл комментариев (не отображаются на экране, но могут быть прочитаны программой, поддерживающей GIF89a).
  2. Управление задержкой перед сменой кадров (задается в 1/100 секунды, или ожидание ввода пользователя).
  3. Управление удалением предыдущего изображения. Предыдущее изображение может быть оставлено, заменено на цвет фона или на то, что было перед ним.
  4. Определение прозрачного цвета.
  5. Вывод текста.
  6. Создание управляющих блоков прикладными программами (application-specific extensions). Внутри GIF-файла вы можете создать блок, который буде игнорироваться всеми программами, кроме той, для которой он предназначен.
Как видно, стандарт GIF89a - весьма мощный инструмент для создания анимаций и их применения в WWW, однако необходимо учитывать тот факт, что если некоторые специальные графические программы и поддерживают этот стандарт в полном объеме, то это не относится к наиболее рапространенным браузерам, поэтому для применения динамических GIFов для WWW лучше ориентироваться не на сам стандарт, а на возможности браузеров по его поддержке.

Структура файла GIF89a.

Файл GIF89a состоит из болков, расположенных в определенной последовательности. Выделяют блоки трех типов:
Управляющие блоки: заголовок, описание логического экрана, управление графическими расширениями, признак конца - определяют, как будут обрабатываться изображения;
Блоки изображения: изображение, текст, описание палитры - содержат данные для самого изображения;
Специальные блоки: комментарии, прикладные расширения - никак не влияют на изображение, но могут обрабатываться прикладными программами по их усмотрению. Среди подобных блоков особо следует выделить прикладной блок Netscape Loop - он присутствует практически в каждой анимации (при этом, это единственный блок прикладной программы, в данном случае Netscape, который я встречал). Если браузер (теперь уже не только Netscape) видит этот блок, он проигрывает анимацию не один раз, а столько, сколько в нем указано раз, или бесконечно).
Общая схема следования этих блоков вприблизительно такова:
  1. Заголовок - GIF87a или GIF89a - определяет формат файла.
  2. Описание логического экрана (задаются размер экрана, на который "претендует" данный файл)
  3. Описание глобальной палитры (необязательно, но крайне желательно)
  4. Произвольное количество блоков изображения (в том числе текстовых), или пар блоков <управление графическими расширениями - изображение>. Блок управления графическим расширением может содержать в себе указание на локальную палитру, расположение на логическом экране, задержку и способ удаления изображения, и оказывает влияние только на следующий непосредственно за ним блок изображения.
  5. Завершитель (признак конца).
Комментарии и специальные блоки могут распологаться где угодно, кроме как:
  1. До описания глобальной палитры (если оно есть) или до описания логического экрана.
  2. Между блоком управления и соответствующим ему блоком изображения (то есть, они могут там находиться, но тогда блок управления уже не будет ничем управлять)
  3. После признака конца.
Также, если Вы хотите поместить в ваш файл расширение Netscape Loop, то этот блок должен следовать непосредственно за глобальной палитрой.

Создание анимаций.

Что необходимо для создания анимаций? В первую очередь необходимо иметь набор картинок - кадров для будущей анимации, или, по крайней мере, представление о них. Далее необходима программа, поддерживающая GIF89a. С сожалением приходится отметить, что Photoshop, хоть и поддерживает палитры, чередование, сжатие и прозрачный цвет, для этой цели не подходит, так как несколько картинок в одном файле в нем не сохранишь, зато его можно использовать для создания каждого кадра в отдельности. Из программ, создающих GIF89a, были испробованы следующие:
  1. Ulead GIF Animator v2.0; Ulead Systems, Inc;
  2. GIFFY v1.2, The GIF Animation Builder; WebReady corp;
  3. GIF Construction Set 32; Alchemy Mindworks, Inc;
  4. Microsoft GIF Animator v1.0; Microsoft corp;
Подробно рассказывать про интерфейс этих программ не имеет смысла, поэтому будут рассмотрены только основные принцыпы работы с этими, и им подобными программами. Процесс создания анимации можно разделить на две важные составляющие - собственно ее создание и, затем, оптимизация. Процесс создания прост - вы берете поочереди кадры и добавляете их к своему GIF-файлу. При этом программы Ulead, Alchemy Mindworks и Microsoft имеют приятную особенность - они позволяют добавлять кадры не из файла, а из буфера обмена, т.е. если вы постепенно трансформируете свою картинку по мере следования кадров, вы лишены необходимости каждое изменение завершать командой Save As..., достаточно <Ctrl-A>, <Ctrl-C>, <Alt-Tab> и <Ctrl-V>. Кроме того, хочется особо выделить программу Ulead Gif Animator, которая позволяет по начальному и конечному изображению построить анимацию с помощью одного из целого ряда эффектов, например, наплыв новой картинки поверх старой, смена листа бумаги и многое, многое другое, что позволяет создавать небольшие презентации в одном файле. После того, как кадры забиты в файл, начинается расстановка управляющих блоков. Важно понимать, что все-таки GIF89a файл состоит из блоков, хотя все из вышеперечиленных программ, кроме Gif Construction Set, их не показывают непосредственно. В них глобальные управляющие блоки интерпретируются как свойства файла, а локальные - как свойство картинки. Может, это и к лучшему - меньше возможностей совершить ошибку. Итак, вам нужно для каждого кадра задать задержку, если надо, то локальную палитру, положение на логическом экране и способ удаления. Программы Microsoft и Alcemy позволяют выделить произвольно несколько блоков, и задать для них все параметры. В остальных вы можете изменять только каждый блок в отдельности, или все сразу. Посмотреть, что у вас получается, можно с помощью кнопки Preview. Однако, это не всегда удачная идея из-за уже упоминавшихся проблем совместимости, поэтому убедиться в том, что все работает, можно только запустив ваш любимый браузер. По поводу совместимости можно посоветовать следующее:
  1. Желательно не использовать локальных палитр - такие картинки читаются, но в некоторых случаях это приводит к неправильному изображению цветов.
  2. Задавайте прозрачный цвет глобально - локальный прозрачный цвет не поддерживается.
  3. Ожидание ввода пользователя не поддерживается.
  4. Не поддерживается замена картинки на то, что было перед ней при удалении.
  5. Если картинка не умещается на логическом экране, это может привести даже к аварийному завершению работы браузера, в лучшем случае - последствия непредсказуемы.
  6. В некоторых версиях браузеров кадры не проигрываются быстрее, чем с задержкой в 34/100 секунды
После того, как анимация готова, начинается самый интересный этап - оптимизация.

Оптимизация анимаций.

Оптимизация призвана обеспечить наименьший размер получившегося файла. В общем случае, если вы создали анимацию, например, из 20 GIF файлов, то размер получившигося файла будет равен сумме размеров использованых вами файлов. Если размер в 20 килобайт считается приемлемым для картинки на странице, то такого нельзя сказать про файл размером 400 килобайт. К счастью, существуют способы значительно уменьшить размер анимации. Они работают в двух направлениях - оптимизация палитры и удаление избыточной информации.
GIF файл позволяет вам использовать любое число цветов в картинке от 2-х до 256. При этом, чем меньше цветов, тем меньший объем занимает файл, кроме того, описание палитры тоже требует места, и оптимизация палитры заключается в том, что необходимо ограничится минимально необходимым числом цветов, и по возможности не пользоваться локальными палитрами, а использовать глобальную.
В том случае, когда ваша анимация представляет собой лишь последовательную демонстрацию несвязанных между собой видов, никуда не денешься от того, чтобы сохранить каждый из них в вашем файле. Однако, если анимация - последовательное преобразование картинки, при этом в каждом кадре изменяется лишь небольшая часть рисунка, нет никакой необходимости в сохранении всего рисунка столько раз, сколько кадров в анимации. Тут необходимо воспользоваться "ножницами" и размещением на логическом экране. Пусть первый кадр займет всю логическую область - он будет служить фоном, на который для каждого кадра будет в строго определенном месте накладываться "заплатка", содержащая те изменения, которые необходимо сделать в этом кадре. Самое сложное - правильно эту "заплатку" расположить - запоминайте координаты левого верхнего угла той области, которую вы вырезали, и используйте их в качестве смещения. Будте внимательны - если вы хотите создать на вашем экране эффект "полета" или "скольжения" изображения по фону, не забывайте, что каждый следующий кадр должен содержать не только новое положение объекта, но и устранять следы предыдущего.
Хорошее известие - вы можете найти программу, которая выполнит практически всю работу по оптимизации за вас. Такой возможностью обладают Ulead GIF Animator и GIF Construction Set. Файл sample.gif, который был создан специально по случаю написания данной статьи, создавался постепенно путем пририсовывания новых и новых деталей. Каждый кадр целиком помещался в Ulead GIF animator, и размер итогового файла составил 143К. Результат автоматической оптимизации - файл размером 4К. Аналогичного результата можно добиться и с помощью GIF Construction Set, однако эта программа создает не полностью совместимые файлы, которые нуждаются в последующей ручной обработке.

Использование анимаций.

Итак, у вас есть готовая GIF-анимация. Процедура помещения ее на web-сайт ничем не отличается от помещения туда же ее не анимированного собрата - простого GIFа. Когда стоит использовать динамические GIFы, а когда предпочесть другие способы оживления содержания? К достоинствам динамических GIFов можно отнести:
  • не требуется никаких plug-in для их просмотра
  • небольшой объем файла за счет сжатия
  • может быть помещен в кэш, не требует повторного обращения к серверу
  • не требует для работы постоянной связи с сервером
  • просто поместить на страницу, не требуется никакого программирования
Как и у всякой медали, у динамических GIFов есть и обратная сторона:
  • палитра не превышает 256 цветов
  • сжатие фотографий хуже, чем в JPEG
  • не поддерживается браузерами в полном объеме
  • любой может легко "позаимствовать" результат ваших многочасовых трудов (так же, как и в случае обычных картинок)

Дополнительная информация.

За дополнительной информацией можно обратиться по адресу:
http://member.aol.com/royalef/gifanim.htm - GIF animation on WWW
http://www.webreference.com/graphics/specs.html - GIF89a specifications from Compuserve

Если все-таки с созданием собственной анимации что-то не получается, посмотрите еще вот эту статью с картинками.


Графика FAQ
  • Какой графический формат (GIF или JPEG) лучше использовать для графики в веб и почему?
  • Какое ПО лучше использовать при создании графики для веб?
  • Как лучше оптимизировать графику для использования в веб?
  • Как убрать "растр" со сканированных изображений?
  • Как "разрезать" картинку на части?
  • Как из картинки выполненной в градациях серого получить цветную?
  • Частые Вопросы по Adobe Photoshop

Q. Какой графический формат (GIF или JPEG) лучше использовать для графики в веб и почему?

A. Если ваша картинка содержит немного цветов и/или четкие линии - лучше использовать GIF т.к. его палитра может быть только в пределах 256 цветов и в нем применяется алгоритм сжатьия без потери качества изображения (конкретно, алгоритм LZW). Так же GIF допускает создание прозрачных областей и анимации. В отличие от него, JPEG используется при работе в фотографиями и сложными коллажами содержащими большое количество цветов и цветовых переходов (градиентов). Достоинствами этого формата являются глубина цвета True Color и небольшой размер выходного файла. К недостаткам можно отнести алгоритм компрессии с потерями качества, невозможность задания прозрачных областей и невозможность создания анимации.

Q. Какое ПО лучше использовать при создании графики для веб?

A. Однозначного рецепта здесь дать невозможно т.к. многое в этом вопросе зависит от предпочтений конкретного дизайнера. В данный момент для работы с растром основная масса дизайнеров используют Adobe Photoshop (http://www.adobe.com), Corel Photopaint (http://www.corel.com) или Ulead Photo Impact (http://www.ulead.com). Для работы с векторной графикой Adobe Illustrator (http://www.adobe.com), Corel Draw (http://www.corel.com) или Macromedia Freehand (http://www.macromedia.com).

Q. Как лучше оптимизировать графику для использования в веб?

A. Для этой цели можно использовать как стандартные средства графических пакетов (Photoshop, Photopaint и т.д.), так и специальное ПО. К такому ПО можно отнести Ulead Smart Saver (http://www.ulead.com), Adobe Image Ready (http://www.adobe.com) и некоторые другие. Так же можно использовать специальные он-лайн сервисы, например, http://www.gifwizard.com/

Q. Как убрать "растр" со сканированных изображений?

A. Лучше сканировать изображение с большим разрешением чем будет иметь изображение на выходе, причем, разрешение не должнобыть кратным разрешению ПЗС-матрицы сканера, например, 310 dpi. В Adobe Photoshop можно сделать следующие действия со сканированным изображением. Filter -> Noise -> Despeckle -> Gaussian Blur (0.5-1) -> создать копию текущего слоя -> к копии применить Gaussian Blur (2-5) -> установить степень непрозрачности слоя-копии в 20-40%.

Q. Как "разрезать" картинку на части?

A. Для этой цели можно использовать как стандартные средства графических пакетов (Photoshop - см. Photoshop F.A.Q., Photopaint и т.д.), так и специальное ПО. К такому ПО можно отнести Ulead Web Razor (http://www.ulead.com), The Castle's SplitImage (http://www.thecastle.com), Macromedia Fireworks (http://www.macromedia.com) и некоторые другие.

Q. Как из картинки выполненной в градациях серого получить цветную?

A. В Adobe Photoshop можно сделать следующее. Перевести изобрадение в режим RGB. Создать копию текущего рабочего слоя и выставить аттрибут наложения Lighten. Дальше можно идти одним из двух путей (хотя можно и попытаться сочитать их). Во-первых, можно просто нарисовать на новом слое нужные цветовые области. Во-вторых, поиграться с фильтрами из раздела меню Image -> Adjust (Hue Saturation -> Colorise, Color Balance, Selective Color).

Adobe Photoshop
  • Сколько памяти выделять ему для нормальной работы и почему?
  • Как изменить шрифты его интерфейса на русские?
  • Как создать эффект "старой" фотографии?
  • Как можно расположить текст по кривой?
  • Каким образом можно заставить Photoshop автоматически обработать несколько картинок (например изменить их размер)?
  • Как сделать так, чтобы одна картинка плавно переходила в другую?
  • Как лучше резать картинки для вебдизайна?

Q. Сколько памяти выделять ему для нормальной работы и почему?

A. Если вы работаете с графикой для web (обычно, она небольшого размера) - целесообразно ставить 50% или около того, чтобы иметь возможность без проблем работать одновременно и с другими приложениями (например, HTML редактор, FTP клиент и/или что-то другое). Если же вы работаете с файлами большого размера - советую выставить 80%-90%.

Q. Как изменить шрифты его интерфейса на русские?

A. Найдите ADMUI2.fon в директории PhotoShop'a и удалите его. При запуске PS подставится системный кириллический шрифт (например, MS Sans Serif ets).

Q. Как создать эффект "старой" фотографии?

A. Воспользуйтесь плагином Aged Film

Q. Как можно расположить текст по кривой?

A. Стандартными средствами этого сделать невозможно. Воспользуйтесь плагином Extensis PhotoGraphics

Q. Каким образом можно заставить Photoshop автоматически обработать несколько картинок (например изменить их размер)?

A. Создаете Action в который записываешь нужную последовательность действий (в данном случае изменение размера картинки). Дальше File -> Automate -> Batch. В секции Play выбираете свой Action. В секции Source - путь директории откуда беруться исходники. В секции Destination - куда сохранять результаты.

Q. Как сделать так, чтобы одна картинка плавно переходила в другую?

A. Выбираем слой который должен плавно переходить -> Edit in Quick Mask Mode (Q) -> Заливаем все черным цветом (картинка станет розоватой) -> Делаем градиен то черного к белому (черный - непрозрачность, белый - прозрачность) -> возвращаемся в Edit in Standart Mode (Q) -> Edit -> Clear (или просто DEL).

Q. Как лучше резать картинки для вебдизайна?

A. Ставим Guidelines в нужное положение (нажать левую кнопку мыши на "линейке" и тянуть не отпуская), устанавливаем режим "прилипания" к ним (View -> Snap to Guides) -> Image -> Mode -> Indexed Color, выделяем нужный участок карники, CTRL+C -> CTRL+N -> Enter -> CRTL+V -> File -> Export - GIF 89A Export.


Untitled

Графика на Вашей странице

  • Советы начинающим
  • Хитрости от мастера
  • Цветовые палитры
  • Перечень цветов
  • Неисчерпаемый GIF
  • JPEG против GIF. Еще раз о дуалистических проблемах
  • Анимационные картинки: что это и как их создавать
  • Четкие графические шрифты на страничке
  • Postscript или TrueType?
  • Частые Вопросы по анимации
  • Частые Вопросы по web-графике.


JPEG vs. GIF. Еще раз о дуалистических проблемах.

Казалось бы, сколько cтатей, заметок и писем на эту тему уже создано. Что ж - оговоримся сразу: если представление о том, что с ваших уст/пальцев сорвется фраза вроде "Зачем ты пользуешься GIFом в WEBe?! Ведь JPEG лучше!" покажется вам полным бредом - не читайте далее этот материал, в библиотеке полно других достойных трактатов.

Теперь, когда с "необходимой отмазкой" покончено, перейдем к реальному рассмотрению вопроса о том, почему их (излюбленных в WWW графических форматов) два. За любым мирным сосуществованием в условиях жестокой конкуренции как правило стоит тот факт, что одна из сторон умеет нечто, чего не умеет другая. Дело не дошло бы до выяснения отношений, если бы не поднялся во весь свой богатырский рост - потрясая иконами шипящих от ярости пользователей-великомучеников - вопрос оптимизации размеров изготавливаемых страничек.

В качестве введения попробуем, не концентрируясь излишне на технических/исторических деталях, заглянуть в душу наших подопытных кроликов. Нас будет интересовать общая схема того, как представлены графические данные в форматах JPEG и GIF, каким образом они кодируются и что при этом происходит.

Если Вы работаете с Adobe Photoshop и сделали в нем хотя бы одну "гифку", не пользуясь PhotoImpact SmartSaver, Вам, наверное, запомнился ритуал конвертирования картинки в режим индицированных цветов. Дело в том, что информация о цвете точки изображения в GIF задается косвенно, посредством индекса в таблице цветов или палитры, хранящейся отдельно от собственно графических данных. Проще говоря, на месте, соответствующем той или иной точке, стоит не значение цвета, а адрес в таблице, по которому должен быть определен цвет.

В нашем упрощенном случае мы имеем 5 пикселей с индексами 3, 1, 1, 0 и 2, равно как и таблицу цветов, определяющую RGB-значения для этих индексов.

В зависимости от "пестроты" изображения количество цветов в палитре может вариировать от 2 до 256. Естесственно предположить, что индексы цветов при более короткой палитре будут занимать соответственно меньше места - 1 бит при 2х цветах, 5 бит при 32, 8 - при 256. Цвета оригинала, по той или иной причине не содержащиеся в палитре, могут либо заменяться ближайшим доступным цветом, либо эмулироваться растровым смешиванием ближайших цветов. Другое дело, что выбор размера палитры и, отчасти, цветов в ней предоставляется пользователю - только от него будет зависеть внешний вид и опрятность картинки.

Элегантно сложив с себя ответственность за качество изображения после приведения к некоей палитре - ведь он не виноват, что работает только с индицированными цветами, такой уж уродился - GIF берется за то, что умеет, с завидным рвением и действует по принципу "лучше синица в руках,....". Ибо сжатие графических данных под его руководством происходит без потерь информации по старому, но относительно эффективному алгоритму LZW, применяемому, кстати, и в пресловутом семействе ZIPов. При этом все вновь встречающиеся последовательности кодируются короткими символами - и вправду, к чему повторяться, если что-то было уже сказано/нарисовано. Таким образом, как сплошные заливки, так и регулярные текстуры будут сжаты с максимальной эффективностью. Продемонстрируем это на маленьком примере:
сплошная заливка. размер файла - 127 байт. | регулярная текстура. размер файла - 470 байт. | нерегулярный "шум". размер файла - 1,23 Кбайт.

Следует еще раз подчеркнуть, что никаких изменений в цвете при конверсии не происходит, "what you see is what you get".

Как бы то ни было, но сегодняшний экранный мир со своими претензиями на многокрасочность едва ли захочет вместиться в узкие 256-цветные рамки. Вот если бы коммуникационные технологии за этими претензиями поспевали... Ведь и фотографическое качество изображений требуется - а там уж не до палитры, любой пиксел может быть любого цвета, и размеры должны быть приемлемыми. Тут и приходит на помощь такой формат как JPEG.

Распространенное, увы, мнение "JPEG лучше...", обусловлено, видимо, тем, что конверсия графических данных в этот формат не требует никаких предварительных работ со стороны пользователя, да и опция всего одна - качество изображения, находящееся в обратной зависимости от размера получаемого файла.

По своему духу JPEG гораздо менее консервативен, нежели GIF, а потому позволяет себе ряд вольностей в обращении с исходным изображением, хотя и не ограничивает принцииально количество цветов в нем. Впрочем, обо всем в общих чертах, но по порядку: первым делом картинка делится на квадратики 8х8 пикселей и над каждым квадратиком произносится заклинание дискретной косинус-трансформации. Следствием этого является переход изображения в частотное пространство, т.е. в представление в виде гармонических колебаний разной частоты и амплитуды. В изображении более высокие частоты присутствуют, например, на границах между двумя цветами и линиях. В этом виде информация и сохраняется в файле, правда, различные компоненты записываются с различной "аккуратностью" - в этом и заключается принцип сжатия. Информации о высоких частотах, как правило, уделяется меньше внимания - этим и объясняется легкая размытость границ в "разумно" сжатом JPEGе. Как уже можно было догадаться, JPEG попросту выбрасывает информацию, которую он считает менее важной для достижения требуемого визуального эффекта. Впрочем, пусть примеры говорят сами за себя.
сжатие 20%. размер файла - 2,33 Кбайт. | сжатие 50%. размер файла - 1,68 Кбайт. | сжатие 75%. размер файла - 1,32 Кбайт. | сжатие 100%. размер файла - 711 байт.

Стоит ли замечать отдельно, что JPEG-конвертор никоим образом не ручается за сохранение оригинальных цветов, более того, достигает потрясающих результатов компрессии как раз за счет их искажения.

Все вышеизложенное говорит само за себя в вопросах выбора между Двумя Форматами, однако напоследок мы все же позволим себе дать несколько конкретных советов по обращению с тем и другим.

  • Если в изображении содержится мелкий текст, JPEG-конверсия сделает его размытым и менее контрастным, а значит малочитаемым. 8 цветов (3 бита) в палитре GIF-формата, как правило, более чем достаточны для качественного отображения текста.
  • На степень последующего сжатия при конвертировании в GIF влияет также и то, какой способ эмуляции полутонов был выбран при переводе в режим индицированных цветов. "None" создает наилучшие, "dither", соответственно, наихудшие условия для компрессии.
  • Большие изображения в GIF-формате с богатой палитрой не рекомендуется резать на много маленьких, если это не ведет к оптимизации палитр отдельных кусочков - в конце концов, служебные блоки и данные палитры тоже занимают некоторое место в каждом файле этого формата.
  • При наличии в фотографических изображениях больших плоскостей с цветовыми градиентами следует быть осторожным со степенью сжатия JPEGа - может легко возникнуть "эффект плохого разрешения".
  • Обстоятельства - такие как необходимость наличия у изображения прозрачной подложки - подчас делают выбор за нас, такое умеет только GIF (ведь у MPEGа в его внутренней кодировке цветов как таковых нет).Стоит только упомянуть, что рисовать подобную картинку стоит на подложке, близкой по цвету к той, на которой он будет помещен на странице - возникает меньше проблем с рваными или просто выделяющимися краями.


Четкие графические шрифты на страничке | WebClub

Четкие графические шрифты на страничке

Название (и тему) этой статьи я не выдумал, а взял из раздела конференций Клуба. Причем тема эта не нова и поднимается у нас уже не первый раз. О чем же речь?

О том, что при использовании на веб-странице мелкого текста, данного в виде картинки (что приходится делать достаточно часто), мы получаем или корявые, или слишком размытые, но в любом случае--неудобочитаемые буквы.

Обычно подготовка такого текста-картинки проводится в PhotoShop или подобном графическом пакете (например, его интернет-ориентированном аналоге Adobe ImageReady). Поэтому все сказанное далее справедливо для работы в подобных программах.

Проблема в том, что при экранном разрешении (72 или 96 dpi) мелкий кегль означает растеризацию буквы в совсем небольшое число точек. Если эта растеризация будет проведена без полутонового сглаживания (в терминологии PhotoShop--"anti-aliasing"), буквы неизбежно получатся неровными, "корявыми". Если же сглаживание включено (это делается "чек-боксом" в диалоге инструмента TYPE TOOL), то механизм растеризации сильно "размывает" буквы, формально компенсируя неровности, но еще более уменьшая удобочитаемость. Для того, чтобы минимизировать этот нежелательный эффект я могу посоветовать следующие маленькте хитрости.

1. Используйте шрифты формата PostScript, а не TrueType. О разнице в механизме описания, и следовательно--растеризации, этих форматов, я кратко писал в другой своей статье. По моему опыту, Type1 (т.е. PostScript) шрифты работают в данном случае куда лучше.

2. По возможности, подберите шрифт с простым и геометричным рисунком букв. Лучше всего--какой-нибудь гротеск. В нем меньше мелких элементов и кривых, т.е. "поводов" для корявости. Кстати, старайтесь не использовать курсивы. Их наклонные штрихи отрастеризуются хуже всего.

3. Повторите накладывание шрифта на фон несколько раз. Вы увидите, что каждый раз результат растеризации оказывается разным. Дело в том, что курсор инструмента находится в момент нажатия кнопки мыши (т.е. момент начала операции) в разных точках, а именно эти его положения берутся за начало координат при расчете точечного представления буквы.

Рис.1 Вот, что получилось, когда я шесть раз подряд вывел в Photoshop одно и то же слово. Тот же шрифт, тот же кегль, те же цвета, с теми же установками. (в натуральную величину и увеличенное в 4 раза)

4. Если вам надо сделать несколько коротких надписей (например--названия разделов узла), выведите все их одной операцией, а затем разрежьте и разнесите в нужные места. По причине, указанной в предыдущем пункте, буквы в них будут одинаковыми.

5. Если насыщеность получившегося текста на ваш взгляд недостаточна, наложите сверху такой же текст, того же цвета. Причем, поскольку вам врядли удастся сразу "попасть" в тот же вариант растеризации, проще скопировать первую надпись и сделать "PASTE".

6. Получив изображение нужного текста попробуйте уменьшить количество градаций цвета. Во-первых, это уменьшит размер картинки, а во-вторых, уберет избыточное сглаживание.
Самый простой (и мой любимый способ) уменьшения градаций--это воспользоваться командой MAP>POSTERIZE. Если вы поставите количество градаций (LEVELS) равным 6, это даст вам от 6 до 11 градаций для каждой пары цветов, а все полученные цвета будут совпадать с "безопасной", 216-цветной веб-палитрой.

Рис.2 Та же надпись, после MAP>POSTERIZE>LEVELS=6

7. В тех же целях можно воспользоваться встроенными возможностями упоминавшегося ImageReady, который предлагает выбрать качество растеризации (т.е. то же количество градаций) при выводе текста.

8. И последнее. Самая качественная работа--ручная. Не поленитесь и подправьте полученный результат ручками, по точечкам. О-о-очень помогает!

Рис.3 Сравните: слева--самый удачный из верхних вариантов надписи, справа--он же, но доработанный руками.


Мультимедиа FAQ

FAQ Анимация
  • Flash анимация при проигрывании "дергается". Что происходит?
  • Как добиться прозрачности при экспорте картинок для Flash?
  • Какие браузеры поддерживают Frash?
  • Как во Flash сделать, чтобы анимация сначала загружалась и проигрывалась только после полной загрузки?

Q. Flash анимация при проигрывании "дергается". Что происходит?

A. Это может происходить в одном из следующих случаев:
1. Больший, по отношении к размеру экрана размер перемещаемой графики.
2. Сложность движущейся графики. (прозрачности, изображения с альфа-каналом, и т.д.)
3. Сложная графика, на фоне которого движется анимационный объект.
4. Низкое количество кадров в секунду (Frame Rate), выставляемое в Movie Properties (Ctrl+M).

Q. Как добиться прозрачности при экспорте картинок для Flash?

A. 1. Flash поддерживает формат PNG, в котором имеется свой 8-битный альфа-канал. В Adobe Photoshop создается маска и сохраняется в канал. В Corel PhotoPaint при сохранении должна быть видна маска. В канал не сохраняется. (Какой бы способ сжатия Losser или Jpeg мы не выбирали во Flash-е альфа-канал будет сохранятся со сжатием Losser. Поэтому при создании картинки нужно позаботиться об уменьшении цветов в альфа-канале).
2. Делаем с картинкой Break (CTRL+B) два раза. Накладываем сверху контур. Щелкаем на картинку за контуром. Выделяется лишнее. Удаляем.

Q. Какие браузеры поддерживают Flash?

A. Поддержка Flash встроено в Internet Explorer и Netscape Navigator версий 4.Х и выше. Третьи версии поддерживают Flash при установке специального плагина (около 170 Kb для третьей версии Flash). Так же существует подобный плагин для Opera.

Q. Как во Flash сделать, чтобы анимация сначала загружалась и проигрывалась только после полной загрузки?

A. Дважды кликаем на ключевом кадре. Делаем action "If Frame Loaded" и указываем, при скачивании какого кадра анимация будет считаться скачанной (самый простой вариант - последний кадр). Закрываем меню и создаем ключевой кадр, после этого делаем action "Go to and play" на кадр где "If Frame Loaded". Теперь анимация будет зациклена между этими двумя кадрами. Делаем графику для процесса загрузки (Loading......). Теперь создадим действие которое будет происходить при скачивании. Заходим в action кадра где "If Frame Loaded" и вставляем внутрь уже имеющегося действия действие "Go to and play" на кадр после второго ключевого кадра. Cмотрим, что получилось. Анимация прыгает от кадра к кадру пока не скачался нужный кадр. Когда кадр скачался, анимация "перепрыгивает" через кадр, который отбрасывает назад.


Детская. Графика

Советы начинающим
  • Большие рисунки помещайте в формате JPEG, маленькие в GIF.
  • Оптимизируйте палитру рисунков. Многие пользователи работают с разрешением 256 цветов. Лучше всего, если палитра всех рисунков укладывается в 216 цветов. Это также снижает размер файла.
  • Если графический файл получается довольно большим по размеру, лучше его просто порезать на куски и выводить как таблицу.
  • Если графическое изображение содержит текст, ни в коем случае не сохраняйте его как JPG, сохраняйте как GIF.
  • Контролируйте отсутствие прозрачности у баннеров в GIF формате. Они показываются на узлах с различными фонами.
  • Каждый из пользователей Интернет уже вполне привычно воспринимает появляющиеся почти на каждой странице картинки-мультфильмы, так называемые динамические, или анимационные (animated) GIFы. Что же необходимо для создания анимаций?
  • Известно, что оптимальной палитрой для GIF файлов, используемых в ВЕБ является т.н. "non-dither pallete" или "WEB-pallete", состоящая из 216 цветов. Часто она представляется в виде "цветового куба" со сторонами, состоящими из 6-ти градаций состовляющих цветов R, G, B. Эти градации распределяются с шагом в 20%. Наиболее простой способ привести свою картинку к такой палитре в Photoshop -- выполнить команду IMAGE > MAP > POSTERIZE со значением LEVELS=6. Данная операция означает, что вы ограничиваете число градаций (LEVELS) в каждом канале (R, G, B) до 6. Вы увидите (в окне INFO), что RGB составляющие принимают одно из 6 значений: 00, 51, 102, 153, 204, 255 -- это и есть 20-процентный сдвиг в рамках 256 градаций. Единственное условие -- перед преобразованием изображение должно быть переведено в цветовую модель RGB (MODE > RGB COLOR). Кстати, если вам вдруг понадобится получить "урезанные" палитры, применяемые в UNIX (5x5x5 или 4x4x4) -- достаточно соответственно поменять значение LEVELS на 5 или на 4. А поставив LEVELS=3, вы с большой долей вероятности получите вполне сносную картинку для предварительной подгрузки (LOWRES).
  • В стандартной ситуации цвет фона страницы задается тегом
    <BODY BGCOLOR="#??????">,
    где вместо ?????? подставляется цифровой код цвета в RGB составляющих. Однако, если этот цвет не входит в универсальную 216-цветовую палитру (ВЕБ-палитру), на системах с 256 цветами он будет представлен в виде смеси двух или более цветов (растеризован). С другой стороны, цвета 216-цветовой палитры далеко не всегда точно соответствуют искомому цвету. Выход из этой ситуации существует. Найдите среди цветов ВЕБ-палитры два наиболее близких к искомому. Создайте из них ма-а-а-аленький GIF'чик. Точки выбранных цветов можно расположить в шахматном порядке, или строчками, высотой в 1 пиксел. Используйте этот GIF файл, в качестве фона, тегом
    <BODY BACKGROUND="backgrnd.gif">.
    Практически на любом мониторе ваш фон (особенно, если два состаляющих цвета не представляют собой контрастное сочетание) будет смотреться, как "сплошной" цвет. По сути, вы сделали растеризацию за браузер, но гарантировали ее корректность и точность.
  • Компрессия в JPEG файлах.При больших значениях компрессии JPEG имеет тенденцию затемнять фоновый белый цвет картинки, что делат проблематичным ее применение на белом фоне.
  • Есть 5 фотошоп и есть русские шрифты... Эти шрифты прекрасно работают в других программах, но некоторые из них не хотят работать в фотошопе 5-ом, хотя прекрасно идут в 4-ом...
    Рyсские бyквы в Photoshop:
    Открываете регистр.
    • HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Nls \ CodePage Edit Value 1252.
    • Set it to "C_1251.NLS". (для NT, для 95 - cp_1251.nls)
    • Close Regedit and restart your Windows.
    Вот и все.


Делаем анимашку

         1) Создаем в Фотошопе новый рисунок, пусть он будет 150х40. Желтым по черному пишем ПРИВЕТ. Сводим слои. Делаем еще три копии. Теперь нужно выделить буквы. Проще это сделать так: закладка "выделение" - "цветовой диапазон". Потом пипеткой (как лучше назвать:) выделяем фон. Потом снова закладка "выделение" - "инверсия". Я применил к каждому изображению фильтр "fire" из "Eye Candy 3". Различается только значение параметра "Random Seed".
2) Теперь надо перевести все рисунки в индексированные цвета. Закладки "изображение" - "режим" - "индексированные цвета". Палитра - адаптированная. Для моего рисунка хватило 4 бит/пиксел (16 цветов). Зачем мы это делаем? Чтобы сохранить изображение в формате GIF. Ведь мы собираемся делать анимированный GIF файл. А GIF работает только с индексированной палитрой.
3) Сохраняем каждый рисунок как GIF файл. Даем каждому порядковый номер. С данным рисунком это не важно, а когда рисунков несколько и они связаны каким-либо последовательным действием, нумерование просто необходимо. "Файл" - "Сохранить как..." Внимание! Порядок записи "обычный", (не чересстрочный)!
4) Запускаем "GIF Construction Set". "File" - "New"... Потом кнопки "Insert" - "Loop". "Loop" определяет количество цикличных показов вашего файла. По умолчанию задано 1000 повторов. Я думаю, что этого вполне хватит. Если покажется мало, то выделите "Loop", нажмите кнопку "Edit" и допишите еще пару-тройку нуликов (не забудьте принять после этого таблеток от жадности, да побольше - побольше).
5) Теперь закидываем наши рисунки. Кнопка "Insert" - "Image". Закидываем по одному (можно и кучей) три рисунка. В меню "Palette" лучше указать "Use local palette...". Меня неоднократно спрашивали, почему, когда делаешь анимашку на белом фоне, на нем появляются меленькие противные точки. Они появляются при установке палитры (да не поллитры, а палитры) по умолчанию, чтобы этого не случилось, надо использывать палитру вашего рисунка. Тогда можно и прозрачный фон задавать, не боясь за чистоту фона.
6) Кнопки "Manage", потом внизу кнопка "Select All", справа вверху напротив "Insert where required" точечьку поставьте и нажимайте кнопку "Apply". В меню "Edit Control Block" можно задать прозрачный цвет, это мы пропускаем. Скорость смены кадров друг другом "Delay". Я поставил 6, здесь простор для выбора огромен, выбирайте, экспериментируйте. И ставим "Remove by:" - "Background", с этим рисунком можно этого не делать, но при применении прозрачного цвета - эта установка обязательна! Теперь нажимаем "ОК" в этом меню, и "ОК" в "Block Management".
7) В главном меню появились три строки "Control". Они показывают, через какой промежуток времени кадры сменяют друг друга. Мы их настраивали сразу все. Однако довольно часто надо делать, чтобы скорость смены рисунков была различной, например для привлечения внимания к какому-то одному кадру, можно делать ускорение и, наоборот, замедление. Тогда выделяете тот "Control", который вы хотели изменить, и жмете кнопку "Edit". Если скорость смены кадров вас не устраивает, то просто выделяете блоки "Control" и удаляете их кнопкой "Delete" либо в меню, либо на клавиатуре.
8) Теперь просто "File" - "Save as..." сохраняете ваш файл. Правда появится пугающее предупреждение. Это связано с тем, что мы использовали при загрузке рисунков палитру самого рисунка, а не общую. Это грозит некорректным отображением цветов в браузере "Netscape Navigator". Зато от точек избавились, поверьте, это лучший вариант. Жмем "ОК". Все!

Скачать "Gif Construction Set", использовавшийся в данном примере, вы можете с этого сайта
 Памский канал + Коллекция анимашек


Postscript vs TrueType

Postscript или TrueType?

Это один из наиболее часто задаваемых начинающими типографами вопросов.

Предыстория такова: на современных персональных компьютерах, а именно с их помощью делается практически вся современная верстка, преобладают два формата цифровых векторных шрифтов: Type 1 фирмы Adobe (часто эти шрифты называются "PostScript шрифтами") и TrueType фирмы Microsoft.

Зачастую на одной машине соседствуют не только шрифты в обоих форматах, но даже одни и те же гарнитуры и в TrueType, и в Type1 представлении.

Кто же из них лучше? На мой взгляд,--Type 1. Но "все зависит". Давайте же по порядку.

Adobe Type 1 Font Format

Этот формат был создан фирмой Adobe Systems Inc. около 1985 года, а в 1990--публично раскрыт и документирован. Насколько я, непрограммист, понимаю, этот формат полностью совместим с языком описания страниц PostScript, выпущенным в том же 1985 году, и поддерживается всеми PostScript устройствами.

В основе этого формата лежит т.н. "декларативный" метод разметки. Это означает, что общие особенности построения символа описываются отдельно от самого контура символа.

И Type 1 эффектно использует эту особенность: при разработке шрифта дизайнеру даны такие мощные инструменты разметки, как хинты, "голубые зоны" и разметка слабо изогнутых кривых. Эти инструменты позволяют контролировать процесс растеризации символов и тем самым гарантируют ее (растеризации) высокое качество при любой разрешающей способности выводного устройства.

Для того, чтобы проиллюстрировать последнее заявление я поподробнее остановлюсь на хинтах.

В чем состоит главная проблема при создании цифрового векторного шрифта? В том, что на выводном устройстве (будь то фотонаборный аппарат, принтер или монитор) этот векторный шрифт будет растеризован, т.е. преобразован в набор точек. Условно это можно представить, как накладывание некоторого контура на бумагу в клеточку и закрашивание тех клеточек, которые оказались внутри (помните, что мы можем закрашивать клетки лишь целиком).

Все просто, когда контур достаточно большой (или клетка достаточно мелкая). Но при выводе на устройства с небольшим разрешением или при мелком кегле возникает ситуация, когда буква должна иметь в высоту 20, а то и меньше точек-клеточек. Что получится при растеризации? Примерно то, что мы видим на рисунке 1.

Рисунок 1

В результате "механического" округления координат точек толщины одинаковых штрихов в символе принимают разные значения, высота символов сильно колеблется, нарушается симметричность и т.д.

Хинты представляют собой пары направляющих, дополнительно определяющие положения и тощины основных штрихов/элементов символа. При растеризации первым делом происходит расчет толщины и положения хинтов, а потом, уже на эти рассчитанные величины накладывается контур. А самое главное, размер и положение данного хинта остаются неизменными от символа к символу. В нашем случае это может дать такой эффект:

Рисунок 2

Даже в рамках такого низкого разрешения нам удается сохранить постоянную тошину основных штрихов и соблюсти одинаковый рост знаков.

На самом деле, это лишь примитивный пример, а механизм хинтов имееть гораздо больше возможностей и случаев применения.

Теперь о TrueType

Этот формат был разработан фирмами Microsoft и Apple, и прежде всего отличается от Type 1 своим "программируемым" методом разметки. Насколько я понимаю, это означает, что описание каждого символа состоит из набора команд программе-растеризатору, по принципу "пойди туда, сделай то..."

Такой метод в теории делает возможной более точную и правильную разметку. Но на практике он затрудняет процесс разработки настолько, что непосредственно в TrueType шрифты вообще не разрабатываются, а те, что стоят у вас на компьютере, наверняка получены как преобразование шрифтов из других форматов. В результате, шрифты TrueType обычно сильно уступают по качеству аналогичным шрифтам в PostScript формате.

Конечно, нельзя не отметить, что с точки зрения пользователя использование TrueType несколько проще (например не требует установки специальных программ), а сами шрифты более распространены и стоят дешевле Type 1. Кроме того, "юзеру", конечным продуктом которого является распечатанное на "лазернике" письмо, не требуется высокое качество растеризациии.

Но: если вы готовите материалы для полиграфии (и, следовательно,--фотонаборного аппарата, работающего на PostScript) или рисуете графические элементы для веб-страницы (и, следовательно, сталкиваетесь с критично низким разрешением монитора) вы почуствуете разницу.

Вывод

TrueType шрифты лучше оставить для офисного использования, на машине секретаря. В любом пре-пресс бюро на вас посмотрят косо, если увидят такой шрифт в вашей публикации. Профессионалы выбирают Type 1. И этоя вам говорю, как настоящий заводчик шарпеев.


Оптитмизация анимированных GIF-ов

На сегодняшний день самым удобным и, как следствие, самым распространенным форматом обмена анимацией в Интернет является GIF’89. Несмотря на обилие скандалов и пессимистичных прогнозов вокруг него -- он продолжает жить, и, судя по всему, на пенсию не собирается.
Создать живой GIF с помощью последних программ может каждый, достаточно иметь хотя бы первоначальные навыки владения мышкой. Однако часто шедевры типа “крутящегося заголовка с мигающим фоном и периодически возникающей молнией” перед тем, как показаться, оставляют посетителя наедине с непечатными мыслями о его провайдере минут на пять. А ведь провайдер тут ни при чем. Виной всему неумение или нежелание автора сокращать размер файла своей анимации (оптимизировать), и именно с этим пороком призвана бороться эта статья.

Структура файла
Чтобы эффективно оптимизировать, да и вообще "чувствовать" GIF, необходимо иметь хотя бы примерное представление об его алгоритме сжатия. Работает он по принципу устранения избыточной информации в изображении. Например, если в строке расположено подряд двадцать пикселей красного цвета, то в файле можно хранить число 20 и красный цвет, а не все красные пиксели по отдельности. Такой способ сжатия имеет аббревиатуру LZW и экономит удивительно много места на диске.

Количество цветов
Исторически сложилось так, что основной единицей измерения бинарной информации стал бит. Байт - единица хранения цифровых данных более высокого уровня. В байте содержится 8 бит. Такой размер байта очень удобен для хранения информации в компьютере. Поскольку каждый байт может представлять одну из 256 возможных комбинаций нулей и единиц, неудивительно, что для изображений обычно используется не более 256 цветов. Если для хранения одного пикселя изображения использовать один байт, то каждый пиксель может иметь одно из 256 возможных значений цвета.
Если вы все еще следите за ходом мысли, то следует отметить, что ситуация на самом деле гораздо более сложная. Подобные значения не представляют реальные цвета. Другими словами, они представляют конкретные цвета из 256-цветной палитры. В каждом байте хранится индекс, указывающий на позицию в палитре. Поэтому 256-цветные изображения называются изображениями с индексированными цветами.
Необходимо помнить, что для хранения изображений с 256 цветами в файле на каждый пиксель отводится по одному байту (8 бит). Поэтому 256-цветные изображения часто называют 8-битовыми.
Преимущество 8-битовых изображений состоит в том, что размер файла таких изображений существенно меньше размера файла 16-битовых (по 2 байт на пиксель) или 24-битовых (по 3 байта на пиксель). За счет сжатия в формате GIF размер файла становится еще меньше.
Таким образом, чем меньше цветов используется в изображении, тем меньше оно будет тянуть.

Размытие
Итак, уменьшение цветов в анимации (да и просто в отдельных изображениях) - вешь необходимая. Роль программы в этом процессе - сократить но не испортить. Чем меньше цветов Вы оставляете, тем труднее становится эта задача. И если изначально анимация обладает широким диапазоном цветов, то палитра цветов этак в восемь не оставляет программе выбора - она, как художник, будет смешивать уже имеющиеся краски для достижения недостающих оттенков, причем в большинстве случаев даже не спросив вашего разрешения. Пример размытия
На этом изобажении показано, как имея в распоряжении только два цвета - синий и красный - Photoshop имитирует пурпурный. Он как бы смешивает пиксели в местах, где это необходимо, тем самым добиваясь нужного эффекта. Кто работал на БК или ZX Spectrum - тот помнит, что все оттенки достигались там похожим способом из исходных цветов.
А теперь - плохая новость: использование размытия в большинстве случаев улучшает вид изображения, однако отрицательно влияет на его размер. Если вы вникли в способ сжатия, используемый в GIF, то понимаете, что больших областей с одинаковыми пикселями в таком случае ожидать не стоит и сжатие может не дать ощутимого результата. Таким образом, решать использовать размытие или нет -- нужно для каждого изображения индивидуально.

Купидон Прозрачность
Одно из главных преимуществ GIF'89 перед большинством других графических форматов - поддержка прозрачности. Принцип прост: один из цветов изображения (анимации) задается как прозрачный, в случае с этим Купидоном - белый. Такое изображение хорошо прежде всего тем, что вписывается практически в любой фон. Правда и тут есть оговорка - как видно на примере, изначальный белый фон все-таки дает о себе знать по краям изображения, но при желании и этого можно избежать.
Для анимации же прозрачность приобретает дополнительное, часто черезвычайно полезное значение. Рассмотрим для примера все того же в:
Кадр1 Кадр2 Кадр3

Как видно, анимированными объектами являются два сердца, которые, в свою очередь, занимают сравнительно небольшую площадь всего изображения. Так зачем в каждом кадре помимо них показывать все остальные элементы,остающиеся неизменными на протяжении всего ролика? Все статичные пиксели заложены в первый кадр, а два последующих изображения содержат лишь изменяющиеся области. Белый цвет задан как прозрачный, что позволяет в кадрах 2 и 3 просвечивать неизменные части Купидона. Таким образом можно добиться весьма (и даже очень) существенного сокращения размера файла, да к тому же большинство программ для работы с GIF-анимацией обрежет похожим способом каждый кадр и без вашего вмешательства.

Практика
Используя все эти приемы, попробуем оптимизировать анимацию в одной из наиболее популярных программ для работы с WEB-анимацией - Ulead Gif Animator 2.
Загрузив соответствующий файл, выбирите в меню File модуль Optimization Wizard.
Вышедшее меню предложит вам создать общую паллитру (super-palette) для всех кадров анимации. В подавляющем большинстве случаев это именно то, что Вам надо. После положительного ответа программа предложит ввести количество цветов (Number of colors), а ниже спросит хотите-ли вы использовать размытие. После нажатия NEXT> вы увидите новую пару вопросов. В первом вам предложат удалить повторяющиеся пиксели(создать кадры с прозрачностью), а второй при положительном ответе уничтожит все комментарии к файлу. Вот и все. Если вы с умом подошли к процессу - оптимизация закончена.


Интерактивные графические системы - учебный сервер кафедры САПР СибГУТИ.

вы на последней странице

Оптимизация GIF-файлов

Для логотипов, схем и русунков в небольшим числом цветов необходимо использовать формат GIF. Используя возможности этого формата и сохраняя картинку в режиме Indexed color, с неполной цветовой палитрой, мы можем получить значительно меньший объем файла, чем при сохранении иллюстрации в формате JPEG.

Для сохранения файла в формате GIF в пакете Adobe Photoshop существует специальный модуль GIF89a Export, находящийся в разделе Export, меню File. Использование этого модуля позволяет визуально подобрать наилучшее соотношение между качеством картинки и размером файла, не выходя из диалогового окна Export. Модуль экспорта GIF89a Export также позволяет сохранять изображения с прозрачными элементами.

Для этого откройте файл в Photoshop и убедитесь, что изображение находится в режиме RGB color (меню Image / Mode), его размеры вас устраивают и его разрешение составляет 72 dpi. (меню Image / Image Size). Откройте диалоговое окно GIF89a Export:

Параметр Palette управляет выбором палитры цветов, которая будет создана для данного изображения. Вы можете выбрать один из трех режимов: Exact, Adaptive и System.

В режиме Exact для создания палитры используются цвета, содержащиеся в изображении. В диалоговом окне доступ к этому режиму появляется только если картинка содержит не более 256 цветов. Exact позволяет получить оптимальное качество изображения при приемлемом размере файла.

Режим Adaptive используется в тех случаях, когда число цветов в изображении больше 256, и потому режим Exact недоступен. Для создания палитры вы указываете количество цветов, из которых следует ее сформировать. Оно может варьировать от 256 до 1. Этот параметр устанавливается в окошке Colors.

System - режим создания палитры из таблицы цветов, используемых в данный момент операционной системой компьютера. При использовании этого режима нельзя предсказать, как будет выглядеть сохраненное изображение на мониторах компьютеров, находящихся под управлением других ОС. Так что пользоваться этим вариантом сохранения не стоит.

При выборе количества цветов нужно помнить, что сильное уменьшение цветов в маленьких изображениях на размер файла существенного влияния не оказывает.

После выбора палитры и количества цветов вы можете посмотреть, как при таких параметрах сохранения изображение будет выглядеть в Web-броузере. Для этого щелкните по кнопке Preview.

В левой нижней части окна Export находится окошко Interlaced. Оно управляет отображением сохраненного рисунка при загрузке в броузер. Если это окошко помечено галочкой, изображение будет появляться постепенно, с прорисовкой деталей по мере загрузки с сервера (как в Progressive JPEG).

вы на последней странице


Интерактивные графические системы - учебный сервер кафедры САПР СибГУТИ.

Оптимизация JPG-файлов

JPEG-сжатие основано на методе сжатия с потерями, поэтому часть информации при сжатии файла неизбежно теряется. Необходимо найти компромис между размером файла и качеством картинки. При подготовке изображений для сети вам также надо обратить внимание на размеры. Если нет необходимости в большой и качественной иллюстрации, то делайте размер меньше чем 600*400. Заодно это уменьшит и размер файла. Но при этом качество несколько ухудшится. Существуют несколько способов поднять качество картинки уже после уменьшения размеров и JPEG-сжатия. Один из них - это повышение резкости.

Используйте фильтр Unsharp Mask в меню Filter>Sharpen

Без использования фильтра
С фильтром Unsharp Mask

Не следует при оптимизации пользоваться опцией Progressive JPEG, так как это увеличит размер файла. Но, с другой стороны, в окне броузера эта картинка начнёт отображаться быстрее, поскольку сначала она прорисуется в низком разрешении, а потом в высоком. Старые броузеры формат Progressive JPEG не поддерживают.


Оптимизация графики:
творческий подход

Казалось бы, что можно сказать нового на эту довольно старую тему - оптимизация графики. Однако я попробую поделиться собственным опытом в этой области и дать некоторые чисто практические рекомендации по подготовке графики для веб-страниц.

Я не стал останавливаться на вопросах, связанных с поведением графики в старинных восьмибитных видеорежимах. Такие понятия, как "безопасная" палитра уже утратили прежнюю актуальность. В то же время многие веб-мастера часто делают ошибки, которых можно избежать, повнимательнее присмотревшись к некоторым особенностям графических форматов и графических редакторов.

Речь здесь идет о только двух классических сетевых растровых форматах - GIF и JPEG, поскольку для полноценной поддержки, скажем, PNG даже браузерам последних версий необходим специальный модуль, который далеко не все пользователи нашли нужным установить. Векторная же графика практически полностью сводится к ультрамодному стандарту ShockwaveFlash, а это уже совсем другая история...

GIF и JPEG используют совершенно разные принципы сжатия графической информации. Чтобы научиться выбирать подходящий формат, давайте попробуем лучше разобраться в устройстве этих форматов.

важные сведения о графических форматах

JPEG

При разработке метода сжатия JPEG были учтены некоторые примечательные особенности органов цветового восприятия человека. Дело в том, что человеческое зрение куда более чувствительно к изменению освещенности, а не собственно цвета. Используемая в компьютерной графике цветовая модель RGB (красный, зеленый, синий) "не экономична" из-за одинаковой важности всех трех составляющих. Выбор же иной системы цветовых координат позволяет увеличить сжатие путем отброса доброй половины информации об оттенке и насыщенности цвета. При этом наиболее важный для глаза компонент яркости остается нетронутым. После такой операции объем информации уменьшается на треть. Кстати, в этом формат JPEG очень похож на схемы цветопередачи в цветном телевидении.

Кроме того в JPEG реализован алгоритм сжатия "с потерями" - дискретное косинусоидальное преобразование (DCT), при котором информация о цвете каждого пикселя заменяется информацией о характере изменения цветов в пределах заданной области. Изображение квантуется - преобразуется в мозаику из квадратов (обычно 8 х 8 пикселей). Внутри каждого такого квадрата распределение цветов описывается специальной математической функцией, коэффициенты которой и хранятся в JPEG-файле. Качество суррогатного изображения, полученного путем обратного преобразования, и соответственно размер файла зависят от сложности функции и погрешности округления.

Только после всех этих преобразований наступает очередь обычного сжатия (уже без потерь). Для этого, как правило, применяется не запатентованный алгоритм Huffman compression. Предварительно рассортированная по полочкам информация сжимается максимально эффективно.

JPEG предназначен, прежде всего, для цветных фотографий с мягкими цветовыми переходами, ведь с увеличением показателя сжатия становится заметным взаимное проникновение цветов на резких цветовых контактах и "дрожание" тонких линий. Варьируя степень сжатия можно добиться очень высоких показателей оптимизации цветных фотографий (например, 1: 10) без заметной глазу потери качества. Формат 24-разрядный (16,7 млн. цветов) - это возможно главное, хотя и относительное, преимущество перед GIF.
Для малоразмерных изображений (десятки пикселей) JPEG не используют, т. к. в виду описанных выше особенностей он нечетко передает мелкие детали. Для сравнения приведу минимальные размеры одно-пиксельных файлов: для JPEG - 542 байта; для GIF - 35 байт.

GIF

идеология

Этот формат был создан фирмой CompuServe в далеком 1987 году. На сегодня GIF остается одним из самых распространенных графических форматов, и его поддерживают все существующие графические браузеры. Фактически, понятие Web-графики большей частью сводится к технике создания GIF-изображений.

Главные, хотя и совершенно не связанные между собой, особенности формата - это привязка изображения к определенной палитре и алгоритм сжатия без потерь - LZW. Поэтому размер (а значит скорость загрузки) GIF-файла находится в строгой зависимости от двух вещей - размера палитры (количества задействованных цветов) и характера самого изображения (его "фрагментации")

Палитра (таблица цветов) может содержать 2, 4, 16, 32, 64, 128 или 256 ячеек. Таким образом, картинка в формате GIF не может иметь более 256 цветов (8 бит на пиксель). Впрочем, для большинства задач веб-дизайна 256 цветов в одном файле - это даже много. Палитра может содержать "системные" цвета, либо быть произвольно индексированной, т. е. адаптированной для конкретного изображения. Если оригинал имеет более 256 цветов, то при экспорте близкие оттенки принимают некоторое усредненное значение. Есть даже такое псевдо-эмпирическое правило: чем большую площадь занимает цвет на картинке, тем больше у него шансов остаться "самим собой". Частично решить проблему ограниченности палитры иногда помогает диффузия цветов - смешивание пикселей для получения переходного оттенка. Такая опция есть в большинстве графических редакторов. Но тут мы сталкиваемся с другой проблемой - диффузия вносит в файл изображения дополнительную порцию беспорядка, что влияет на его сжимаемость. Поэтому диффузию желательно компенсировать сокращением палитры (например, с 256 до 128 или 64 цветов)

Вот наглядный пример зависимости размера файлов от упорядоченности графической информации в них (эти три картинки не отличаются ничем, кроме характера рисунка)

210 байт | 519 байт | 1002 байта

Секрет в том, что в GIF-файлах информация хранится построчно. Как видите, описать по строкам первую картинку довольно просто, чего не скажешь о последней.

Как известно, существует две версии формата: GIF87a и GIF89a. Такие расширения, как прозрачность и анимация поддерживаются только в GIF89a, поэтому большинство программ создают файлы именно в этом варианте.

прозрачность

Прозрачность реализована крайне примитивно, и применить ее на практике весьма сложно. Альфа-канал однобитный, поэтому можно задавать только области абсолютной прозрачности или абсолютной непрозрачности. Например, графический заголовок, изначально созданный на белом фоне с использованием анти-алиасинга (сглаживания) при указании белого цвета прозрачным будет отвратительно выглядеть на любом фоне, кроме белого, т. к. станут видны светлые зазубрины - побочный продукт сглаживания. Как видите, при создании изображения с прозрачными областями, необходимо заранее учитывать цвет фона, на котором оно будет находиться. Если же цвет фона не известен, то лучше обойтись без анти-алиасинга внешних контуров изображения.

Обратите внимание на то, что прозрачность несовместима с диффузией цветов. Ели фон будет состоять из перемешанных близких по цвету пикселей, то сделать его полностью прозрачным не удастся, поскольку прозрачным может быть только один цвет

Особая разновидность прозрачных GIF-файлов - это файлы-распорки размером 1x1 пиксель. В эпоху браузеров третьих версий они были единственным средством абсолютного позиционирования на веб-страницах. В самом деле, GIF-невидимка весит всего 43 байта и при этом может обладать всеми HTML-атрибутами "настоящей" картинки: width, height, vspace, hspace.

анимация

GIF-анимация создается путем сохранения в одном файле нескольких изображений. Каждый кадр может иметь собственную палитру, собственные размеры и координаты на общем логическом экране. Анимированные файлы обычно требуют более "жестокой" оптимизации. К счастью, чем быстрее смена кадров, тем меньше она дает нашему глазу сосредоточится на одной конкретной картинке, позволяя обычно обойтись без анти-алиасинга и диффузии. Как правило, использование глобальной палитры для всего "ролика" позволяет значительно его облегчить. Важно следить за тем, чтобы геометрические размеры кадров были минимально-возможными (и кадры не содержали избыточной графической информации).

техника оптимизации

Подготовка картинки к размещению на веб-странице обычно состоит из нескольких этапов.

Шаг первый - это создание оригинала.

  1. Картинка может быть отсканированной фотографией или рисунком.
    Сегодня даже самые дешевые планшетные сканеры позволяют сканировать изображения с разрешением до 600dpi и 24-битной глубиной цвета. Для подготовки веб-графики вполне достаточно отсканировать картинку с разрешением 300dpi, после чего средствами графического редактора уменьшить разрешение до стандартной "экранной" величины - 72 пикселя на дюйм. В программе Adobe Photoshop это делается через меню Image > Image Size. Обычно такой метод дает лучшие результаты, чем простое сканирование с низким разрешением. Если после уменьшения экранных размеров картинки с использованием бикубического масштабирования (Resample Image: Bicubic) она заметно потеряет резкость, можно ее повысить, используя соответствующий фильтр: Filter > Sharpen > Sharpen. Контурные рисунки желательно сканировать в однобитном монохромном режиме (LineArt), но с высоким разрешением. Это позволяет избавиться от ненужной бумажной фактуры фона. Да и в случае с цветными рисунками зачастую имеет смысл уменьшить количество переходных оттенков и соответственно расширить области сплошных цветов - "постезизовать" (Image > Adjust > Posterize). При необходимости с помощью утилиты-трассировщика такая картинка может быть переведена в векторный формат для дальнейших преобразований
  2. Рисунок может быть создан в векторном графическом редакторе (например, в Corel Draw) и экспортирован в промежуточный растровый формат (TIFF и т. п.) или непосредственно в GIF.
  3. Картинку можно нарисовать в растровом редакторе, таком как FractalDesign Painter или Adobe Photoshop. Это особенно касается всевозможных кнопочек, маркеров и т. п. элементов интерфейса.

Второй шаг - выбор формата. При выборе (а выбирать пока приходится лишь между GIF и JPEG) желательно руководствоваться следующими правилами:
GIF применяется:

  • для всех изображений, ширина и высота которых не превышает сотни пикселей
  • для картинок, нарисованных в векторном редакторе (без использования фотографических текстур)
  • если необходимо, чтобы фон картинки строго соответствовал фону всей страницы (или был прозрачным)
  • для снимков экрана
  • для всех миниатюрных элементов интерфейса страницы
  • для картинок, содержащих текстовые надписи, заголовки и логотипы
  • для не очень больших монохромных ("в оттенках серого") изображений
  • для всевозможных схем и планов
  • для всех нематериальных (нефотографических) текстур.
  • естественно, в том случае, если нужна простая анимация

JPEG применяется:

  • для всех изображений реальной жизни - фотографий (с естественными плавными цветовыми переходами), видеокадров, оцифрованной живописи
  • если необходимо точно передать плавные цветовые переходы (градиенты)

Как видите, область применения формата GIF в веб-графике гораздо шире. Безусловно, GIF c использованием 8-битной адаптированной палитры и диффузии вполне может справится и с качественной передачей практически любого фотографического изображения, но тут он в 99% случаев проигрывает формату JPEG в экономичности. В спорных случаях стоит попробовать сперва сохранить картинку в JPEG-файле с приемлемым уровнем качества, а затем отталкиваться от полученного размера файла при экспорте в GIF.

Третий шаг - экспорт оригинала в нужный формат. Существует немало специальных программ и plug-in-модулей для визуальной оптимизации, типа Ulead SmartSaver, но вполне можно обойтись и стандартными средствами Photoshop. Итак мы имеем готовое изображение в растровом формате в цветовом режиме RGB. Если преимущество JPEG, как конечного формата очевидно, то все довольно просто: заходим в меню File > Save a Copy, выбираем в списке формат JPEG. Опция "Exclude Non-Image Data" должна быть отмечена флажком - тогда в файле не будет сохранена миниатюрная копия картинки, что позволит выбросить несколько лишних килобайт. Далее предстоит установить уровень качества (0 - 10) и опции формата. Этот шаг, возможно, придется повторить несколько раз для достижения наилучшего результата. Опция "Baseline Optimized" обычно позволяет немного сократить размер файла. А с помощью опции "Progressive" можно создать JPEG-файл, который будет постепенно прорисовываться (детализироваться) в окне браузера по мере скачивания из Сети. Правда некоторые старые браузеры прогрессивный JPEG не поддерживают. "Прогрессивная" опция может влиять на размер файла, но крайне незначительно, причем как в меньшую, так и в большую сторону. Зато параметр качества (Quality) влияет на размер файла очень сильно, и эта зависимость далеко не прямая. Взгляните на диаграмму, которую я построил из спортивного интереса для вот этой фотографии. По вертикали отложены размеры JPEG-файлов в байтах, по горизонтали - коэффициенты качества.

Показанная на иллюстрации фотография котенка соответствует коэффициенту "7" ("High Quality").

Экспорт оригинального изображения в формат GIF - не менее творческая задача. Для начала создадим копию изображения (Image > Duplicate) и перейдем из режима RGB в Indexed Color (Image > Mode > Indexed Color). На появившееся предложение объединить все слои отвечаем согласием (мы ведь создали копию). Если в исходной картинке было задействовано более 256 цветов, то будет предложена адаптированная 8-битная палитра. Но в большинстве случаев есть смысл сократить количество цветов и получить тем самым выигрыш в размере файла. Все зависит от конкретного рисунка. Если это цветная фотография маленьких размеров, то можно установить 7-ми или 6-битную палитру и воспользоваться диффузией, установив опцию "Dither: Diffusion". А если, например, наша картинка - это текстовый логотип с анти-алиасингом, то от диффузии непременно нужно отказаться, т. к. могут появиться нелепые зубцы вокруг символов (компьютер наивно полагает, что таким образом он сгладит цветовую границу, запихнув диффузию туда, где ей в действительности нет места). К тому же, как уже было сказано выше, диффузия неуместна в изображениях с прозрачностью.

Итак, когда палитра подобрана, можно отправлять картинку в формат GIF (File > Export > GIF89a Export) В появившемся окошке "GIF89a Export Options" мы не только увидим, как картинка будет выглядеть после экспорта, но и сможем указать прозрачные области (прозрачными можно сделать сразу несколько цветов). Включенная опция "Interlace" означает, GIF-файл будет сохранен в чересстрочном режиме (на экран по мере поступления информации будет выводится сначала, каждая восьмая, затем каждая четвертая строка и так до полной загрузки). Чересстрочные файлы весят немного больше, чем обычные, поэтому при экспорте, например, картинок-фонов от опции "Interlace" лучше отказаться за ненадобностью. Можно, конечно экспортировать картинку в GIF непосредственно из RGB, но тогда мы не сможем так просто управлять прозрачностью, диффузия может появиться без спросу, а еще в палитру зачем-то будут добавлены черный и белый цвета, совершенно независимо от того, нужны они там или нет.

При сохранении в GIF'e больших изображений, в целях оптимизации их можно разрезать на любое число более мелких фрагментов. Это даст возможность сохранить каждый фрагмент с собственной наиболее оптимальной палитрой: например, некоторым крупным кускам может хватить и двух цветов, а для какого-нибудь особо важного фрагмента можно "раскошелиться" и на все 256. Собрать все воедино на веб-странице не составит особого труда.


Интерактивные графические системы - учебный сервер кафедры САПР СибГУТИ.

вы на первой странице

Способы и программы оптимизации

Одним из главных критетиев любой картинки, создаваемой для интернета, является время ее загрузки. Которое, в свою очередь, напрямую зависит от её размера. В настоящее время в сети используются два граф-формата : GIF и JPEG, которые и были созданы для передачи изображений по сети. Основное различие между форматами GIF и JPEG состоит в том, что применяются они для хранения разных видов графики. GIF преимущественно используется для сохранения рисованных изображений, векторной графики, картинок без полутонов, градиентов (переходов от цвета к цвету) и большого количества мелких деталей разного цвета, тогда как JPEG - для фотоизображений и полутоновой графики, живописи, градиентов и изображений с множеством мелких разноцветных деталей.

Оба этих формата являются компрессионными, то есть данные в них уже находятся в сжатом виде. Сжатие, тем не менее, представляет собой предмет выбора оптимального решения. Каждый из этих форматов имеет ряд настраиваемых параметров, позволяющих управлять соотношением качество-размер файла, таким образом за счет сознательного снижения качества изображения, зачастую практически не влияющего на восприятие, добиваться уменьшения объема графического файла, иногда в значительной степени.

Существуют специальные программы-утилиты для получения оптимального размера файла при необходимом качестве картинки. Самой лучшей такой программой является Ulead Smart Saver (компания-производитель ULEAD SYSTEMS, Inc). Эта программа может также встраиваться в Adobe Photoshop как вспомогательный модуль.

Интерфейс программы:

Главное достоинство утилиты - это два окна просмотра.В одном картинка отображается до сжатия, а во втором после сжатия. Можно оптимизировать файлы форматов GIF, JPEG, PNG. Регулируя количество цветов, степень сжатия, размытость можно сразу визуально контролировать результат. Утилита позволяет кадрировать изображение, выбирать фон и прозрачные цвета. Еще одно важное качество - это создание серии с разным количеством цветов (и, соответственно, размером). Утилита это действие выполняет автоматически, нужно только задать начальное количество цветов, конечное и приращение.

вы на первой странице


Интерактивные графические системы - учебный сервер кафедры САПР СибГУТИ.

Формат GIF

Первая версия графического формата Gif (Graphics Interchange Format) была разработана в 1987 году специалистами компьютерной сети CompuServe в качестве простого растрового формата для обмена рисунками в сети. Через некоторое время у данного формата выявился ряд недостатков, тормозящих его эффективное использование. В 1989 году была разработанна новая версия формата Gif (Gif89a).

Формат Gif применяет те же алгоритмы сжатия, что и обычные программы-архиваторы, поэтому при записи и считывании Gif-изображения никаких потерь информации не происходит. Однако в отличии от них Gif-файлы архивируются и разархивируются автоматически.

Он использует LZW-компрессию, что позволяет хорошо сжимать файлы, в которых много однородных заливок (логотипы, надписи, схемы). GIF-формат позволяет записывать изображение "через строчку" (Interlaced), благодаря чему, имея только часть файла, можно увидеть изображение целиком, но с меньшим разрешением. Эта возможность широко применяется в Интернете. Сначала вы видите картинку с грубым разрешением, а по мере поступления новых данных ее качество улучшается.

В GIF'e можно назначить один или более цветов прозрачными, они станут невидимыми в интернетовских браузерах и некоторых других программах. Прозрачность обеспечивается за счет дополнительного Alpha-канала, сохраняемого вместе с файлом. Кроме того, файл GIF может содержать не одну, а несколько растровых картинок, которые интернетовские браузеры могут подгружать одну за другой с указанной в файле частотой. Это называется GIF-анимация. Основное ограничение формата GIF состоит в том, что цветное изображение может быть записано только в режиме 256 цветов.

Несколько настраиваемых параметров GIF формата, позволяют управлять размером получаемого файла. Наибольшее влияние оказывает глубина цветовой палитры. GIF-файл может содержать от 2-х до 256 цветов. Соответственно меньшее содержание цветов в изображении (глубина палитры), при прочих равных условиях, дает меньший размер файла.

Другой параметр влияющий на размер GIF-файла - диффузия. Это позволяет создавать плавный переход между различными цветами или отображать цвет отсутствующий в палитре путем смешения пикселов разного цвета. Применение диффузии увеличивает размер файла, но зачастую это единственный способ более менее адекватной передачи исходной палитры рисунка после редуцирования. Другими словами применение диффузии позволяет в большей степени урезать глубину палитры GIF-файла и тем самым способствовать его "облегчению".

При создании изображения, которое в последующем будет переведено в GIF формат, следует учитывать следующую особенность алгоритма LZW сжатия. Степень сжатия графической информации в GIF зависит не только от уровня ее повторяемости и предсказуемости (однотонное изображение имеет меньший размер, чем беспорядочно "зашумленное"), но и от направления, т.к. сканирование рисунка производится построчно. Это хорошо видно на примерах GIF-файлов с разным направлением полосок . Файл с горизонтальными полосками имеет размер 369 байт, а с вертикальными полосками 883 байт (в 2,4 раза больше!).


Формат JPEG

JPEG (Joint Photographic Experts Group) представляет формат для сжатия графических файлов.

Строго говоря, JPEG'ом называется не формат, а алгоритм сжатия, основанный не на поиске одинаковых элементов, как в RLE и LZW, а на разнице между пикселами. JPEG ищет плавные цветовые переходы в квадратах 9х9 пикселов. Вместо действительных значений JPEG хранит скорость изменения от пиксела к пикселу. Лишнюю, с его точки зрения, цветовую информацию он отбрасывает, усредняя некоторые значения. Чем выше уровень компрессии, тем больше данных отбрасывается и тем ниже качество.Используя JPEG, можно получить файл в 10-500 раз меньше, чем ВМР! Формат аппаратно независим, полностью поддерживается на РС и Macintosh, однако он относительно нов и не понимается старыми программами (до 1995 года).

Из сказанного можно сделать следующие выводы. JPEG'ом лучше сжимаются растровые картинки фотографического качества, чем логотипы или схемы - в них больше полутоновых переходов, среди же однотонных заливок появляются нежелательные помехи. Лучше сжимаются и с меньшими потерями изображения с высокой резолюцией (200-300 и более dpi), чем с низкой (72-150 dpi), т.к. в каждом квадрате 9х9 пикселов переходы получаются более мягкие за счет того, что их (квадратов) в файлах высокого разрешения больше. В JPEG'е следует сохранять только конечный вариант работы, потому что каждое пересохранение приводит к все новым потерям (отбрасыванию) данных и превращения исходного изображения в кашу. Как это ни парадоксально, возможности алгоритма сжатия JPEG реализованы в формате JPEG не полностью. Сжатие в файлах формата Photoshop EPS с JPEG-кодированием дает несколько лучшие результаты.

В Photoshop 4.0 наряду с традиционным вариантом этого формата Baseline ("Стандартный") добавлен модифицированный вариант - Progressive JPEG. Сохраняемые в нём изображения содержат большее количество деталей, и, что самое главное, его использование намного сокращает время передачи по сети. Этот формат JPEG также, как и формат Gif89a, поддерживает свойство чересстрочности.

Для примера посмотрите на изображения внизу

В формате Gif эта картинка занимает 713 байт, в Jpg - 9 278 байт (при 50%-м сжатии), в Bmp - 45 176 байт.

При почти одинаковом качестве размер этой картинки в Jpg 7,5 Кб (при 50%-м сжатии), в Gif - 32,5 Кб (256 цветов), в Bmp - 114 Кб.

Изображение в формате Jpeg при разных степенях сжатия - 10%, 30%, 60%. Размер при этом составил 2 735 байт, 5 390 байт и 8 483 байт соответственно.


Формат PNG

Формат PNG (Portable Network Grafics) был разработан в качестве альтернативы Gif формату. В отличии от последнего он позволяет сохранять всю цветовую информацию и информацию об альфа-каналах масок и слоев. Это чрезвычайно сжатый RGB-формат, в котором используется схема сжатия без потерь. На сегодняшний день это наиболее перспективный Web-формат, обладающий мощьными возможностями, которые наиболее ярко проявляются при его сопоставлении с форматом Gif:

В PNG формате реализован открытый , не запатентованный алгоритм сжатия данных, обеспечивающий более высокие результаты.

В отличии от ограниченной 256 цветами палитры Gif, формат PNG позволяет хранить полноцветные изображения с цветовой глубиной 24 бит/пиксел и даже 48 бит/пиксел.

В PNG формате используется более эффективный алгоритм чересстрочности. Достаточно сказать, что первый проход, дающий общее представление об изображении, занимает в нем лишь одну шестьдесят четвертую файла по сравнении с одной восьмой в Gif. Тем не менее, распозноваемость картинки при этом заметно лучше.

В отличии от двух градаций прозрачности, поддерживаемых Gif форматом, PNG позволяет хранить полную информацию о степени прозрачности каждой точки изображения с помощью специального альфа-канала. Таким образом, каждый пиксел PNG-файла, вне зависимости от его цвета и местоположения, может иметь любое значение непрозрачности от нулевой (полная прозрачность) до абсолютно непрозрачной.

Возможность использования гамма-коррекции. Это свойство используется для сглаживания яркости дисплеев различных типов компьютеров и, соответственно, между создаваемыми на них изображениями. Дело в том, что PNG-формат позволяет хранить в файле коэффициент яркости дисплея, на котором было создано изображение. Впоследствии это обеспечивает возможность автоматической коррекции яркости изображения в соответствии с параметрами данной видеосистемы.


Баннер

Основой баннерной рекламы является размещение небольшой картинки (баннера) на как можно большем количестве чужих страниц. При нажатии на ссылку осуществляется переход на рекламируемую страницу. Сам баннер представляет собой графический файл (обычно GIF-формат), размещенный на чужой странице и имеющий гиперссылку.

Баннерная реклама пронизывают Internet насквозь и, теоретически, можно найти любую интересующую Вас информацию следуя нажатиям на тематические баннеры.

Баннеры бывают анимированные и не анимированные (т.е. с каким-либо присутствием движения на картинке или без оного). Так же баннеры отличаются по размерам, обычный размер 468Х60 пикселов, однако бывают и другие размеры (зависит от вашей фантазии, если не ограничивается правилами службы обмена баннерами), некоторая стандартизация в размерах баннеров проведена Internet Advertising Bureau http://www.iab.net/ совместно с CASIE (The Coalition for Advertising Supported Information & Entertainment), которые рекомендуют следующие размеры:

Размер (в пикселах) | Тип (название)
468X60 | Full Banner
392X72 | Full Banner with vertical navigation bar
234X60 | Half banner
120X240 | Vertical banner
120X90 | Button 1
120X60 | Button 2
125X125 | Square button
83X31 | Micro button

При размещении вашего баннера следует учитывать, что показ одного баннера одному и тому же пользователю многократно не ведет к увеличению отклика. Поэтому каждый баннер имеет срок жизни, после которого он устаревает, и дальнейшее продолжение его показа ведет только к существенному уменьшению CTR. Следовательно, не стоит показывать один и тот же баннер в службе обмена баннерами имеющей 100 000 показов в день более чем 100 000 раз, даже в таком случае каждый уникальный пользователь этой службы, скорее всего, не по одному разу увидит ваш баннер.

При размещении баннеров рекомендуется использовать Targeting (показ рекламного баннера только целевой аудитории). Солидные службы обмена баннеров предоставляют возможность выбирать тематику серверов, на которых будут показаны ваши баннеры или, наоборот, на которых показ не желателен, вплоть до полного ручного выбора доменов и home page. Таким образом, Вы сможете добиться наибольшего отклика на Ваш баннер, т.к. выбранная аудитория уже расположена к данной тематике.

Не кликом единым жив Internet

Прежде, чем рассказать, как завлечь пользователя кликнуть на баннер, стоит объяснить, как оценивать результативность показов. Средний отклик ("click/through ratio", CTR) на показы баннеров составляет около 2% т.е. это значит, что только 2 из 100 человек, увидевших баннер, зайдут на Ваш сайт. Показатель отклика более 4% считается приличным, конечно встречаются гиганты, которые добиваются 20% отклика. Но на самом деле не стоит паниковать и считать, что Ваши усилия в 98 случаях из 100 пропадают даром. Во-первых, люди видят Ваш баннер и у них складывается образ вашего сайта (имидж), поэтому надо придерживаться одного стиля и стараться делать все баннеры в хорошим, профессиональном исполнении, иначе о Вас может сложиться негативное мнение, и Вы только потеряете от такой рекламы . Во-вторых даже если вы добились высокого отклика на Ваш баннер это не значит что Вы получили желаемый результат - пользователь, попавший к Вам только под наплывам чувств, вызванных баннером, может больше никогда сюда не вернуться, разочаровавшись тем, что он получил в действительности (так оно чаще всего и происходит).

Поэтому для того, что бы нажатия на Ваш баннер действительно отражали существующий интерес к теме Вашего сайта, а показы баннера, при которых нажатие на него не происходит, не пропадали даром стоит выполнять следующие два условия:

1. На баннере должно помещаться краткое истинное содержание Вашего сайта (в виде картинки, слогана или анимации), тогда нажатие на Ваш баннер будет определяться не только его воздействием на психику, но и интересом к тематике. Содержание баннера должно указывать на истинную тематику Вашего сайта.

2. Баннер должен содержать Ваш логотип или адрес в Internete (URL), тогда при "холостых" показах пользователь подсознательно будет запоминать Вашу рекламу, и ассоциировать свои потребности в услугах подобного рода с Вашим сайтом.

Ловля на блесну

Для успешной рекламы Вашего сайта баннер должен отвечать следующим требованиям:

1. Красочное оформление
2. Профессиональный дизайн
3. Анимация
4. Максимальный визуальный размер при минимальном размере в байтах.
5. Оригинальность (баннер должен запоминаться)
6. Интригующее содержание
7. Давление на первое впечатление
8. Тестируйте свои баннеры

Красочное оформление. Рекомендуются яркие, привлекающие внимания цвета. Будем ориентироваться на мужской пол, по статистике в русскоговорящем Internete около 80% составляют именно мужчины. Психологи советуют применять синий цвет для привлечения внимания мужчин. Соответственно цветовая гамма: синий на белом, голубой на белом является наиболее привлекательной, однако не стоит оперировать только с ней, для наибольшей броскости можно оперировать гаммой: красным, черный, белый - плохой к восприятию, но точно привлекающей внимание.

За более детальным разбором цветов обратимся к психологам. На основе теста Люшера можно сделать некоторые выводы. Наибольшую эмоциональную окрашенность несут цвета: синий, сине-зеленый, оранжево-красный, светло-желтый, коричневый, фиолетовый, черный. Каждый цвет в комбинации с другими соответствует состоянию человека (первый цвет - преобладающий на баннере):

Гамма | эмоции
Синий + красный | потребность во внимании со стороны других
Синий + черный или серый | потребность в отдыхе
Сине-зеленый + желтый | желание произвести впечатление
Красный + желтый | стремление к контактам
Красный + синий | деловое возбуждение
Красный + черный | стремление уйти из негативной ситуации
Желтый + синий | стремление к позитивным эмоциям
Желтый + фиолетовый | потребность в ярких событиях
Фиолетовый + серый | желание уйти от проблем или стресса
Коричневый + синий | страх одиночества (самое время для свадебного агентства)
Черный + синий | "оставьте меня в покое" (не думаю, что это подходящая комбинация для баннера)
Черный + зеленый | гневное отношение к окружающим (тут-то его и принимать в коммунисты)
Черный + фиолетовый | мечты о гармонии
Черный + красный | сильное возбуждение, возможность аффективных поступков

Конечно, это не руководство к выбору цветовой гаммы для Вашего баннера, но заглянуть сюда совсем не помешает.

Профессиональный дизайн. Если вы собираетесь рекламировать товары или услуги уже имеющие имидж, то реклама в Internete должна совпадать с ним. Качество художественного исполнения баннера не должно вызывать нареканий, иначе пострадает имидж фирмы, и вы получите антирекламу. Если есть деньги - обратитесь к профессионалам.

Анимация. Зрение человека приковывается движущимися объектами, поэтому даже не яркий баннер с анимацией предпочтительнее раскрашенного статического. В среднем анимированный баннер имеет больший на 25% CTR по сравнению с обычным. Кроме того, анимация расширяет возможности баннерной рекламы.

Максимальный размер. Чем больше размер баннера, тем больше шансов, что его заметят и не примут за элемент оформления сайта. Рекомендуемый размер 468Х60 пикселов (Full Banner). Однако сам баннер не должен "весить" более 15 килобайт. Для уменьшения размера баннера используйте меньше цветов при создании картинки. Некоторые службы обмена баннеров устанавливают свои лимиты, но чем меньше килобайтов занимает Ваш баннер, тем быстрее он загрузиться, тем больше шансов, что пользователь увидит Ваш баннер прежде, чем покинет эту страницу.

Оригинальность. Баннер должен нести некоторую идею, для того, что бы пользователь один раз увидевший его оставался с каким-либо мнением о фирме и ее услугах.

Интригующее содержание. Некоторая недосказанность в содержании баннера вызывает любопытство и желание узнать содержание сайта. Фразы типа: "Пойдем со мной:", или "Там так классно! А ты там был?", формируют впечатление прерванного на самом интересном месте фильма.

Давление на первое впечатление. Мы решили ориентироваться на мужскую аудиторию, поэтому наличие на баннере смазливой девичьей мордашки или какой другой части тела вызывают непроизвольное слюноотделение у мужчин с дальнейшим желанием нажатием на баннер. Призывы из раздела "Жми сюда!" тоже оказывают большое воздействие на аудиторию, привыкшую подчиняться директивам. Наоборот, фразы с содержанием слов "Нет", "Плохой" и др., придающих, как выражается Word, экспрессивную окраску фразе, отталкивают потенциальных посетителей от нажатия на баннер. Положительное влияние на увеличение CTR оказывают всякого рода полосы прокрутки и кнопки, присутствующие на баннере, особенно эффективны полосы прокрутки, если интригующий текст уходит за пределы баннера, тогда проявляется рефлекс пользователя, и вы непроизвольно нажимаете на скроллер.

И последний совет, никогда не полагайтесь на один, по Вашему мнению, хорошо выполненный баннер, всегда используйте сразу несколько баннеров, смотрите на их CTR ("click/through ratio"), редактируйте их, меняйте текст и цветовые гаммы, пока не добьетесь лучшего результата.


Баннерные службы и все о них.

Баннерные службы

Вы сделали страничку, но не знаете как ее раскрутить. Таких методов полно, но я хочу заострить внимание на одном, довольно популярном методе. Это баннерные службы.
Что же это такое баннерные службы, или службы обмена баннерами?
Все очень легко. Вы создаете баннер определенных размеров, размеры назначаются самими службами. Загружаете их в систему, а сами размещаете код у себя на странице. Что происходит? К вам на страницу зашел посетитель, загрузился баннер, код которого вы вставили, за то что тот баннер загрузился, вы получаете либо один свой показ, либо просто набираете очки. В каждой системе обмена очень разные отношения к показам. В некоторых вы показали один раз чей-то баннер, и ваш баннер показался один раз. А в некоторых показываешь десять раз, а твой только один. Еще вам добавляются очки, если с вашей страницы кто-то кликнул на баннер.

Баннерные службы (размер баннера 468х60)

Внимание!! Новая и очень классная баннерная система RBN - http://banner.com.ru

Система обмена баннерами RBN отличается несколькими вещами:

Мы следим за
нашими пользователями и не допускаем к участию сайты, не прошедшие проверку
нашими цензорами. Это могут быть сайты с непристоиными изображениями или
текстами.

Мы не принимаем сайты, распостраняющие нелицензионное ПО и т.д.

Также мы следим за изображениями баннеров, демонстрируемых нашей системой.

Отличающим качеством является то, что мы оказываем помощь с рекламой хорошим
проектам, если все, чего им не хватает, это раскрутка.

Система RBN ставит
своей целью объединить самые интересные российские ресурсы в единую сеть. В
основе нашей концепции лежат не количественные, а качественные показатели.
Мы хотим сделать нашу систему обмена такой, чтобы web-серферу всегда было
приятно 'жать' на баннеры, под которыми стоит наш логотип.

1) LinkExchange (http://www.bizlink.ru)
Отличная баннерная система. Вы можете управлять своим баннером или несколкими баннерами как хотите! Т.е. когда их лучше показывать, в какие дни, на каких серверах и т.д. Правда статистика довольно слабая. Только сколько баннеров показали, сколько нажали, сколько вы показов заработали, сколько истратили. Очень эффективная. Размер баннера не должен превышать 10К
2) Ricor banner exchange (http://banner.ricor.ru)
Неплохая служба. Отношение 2:1, т.е. вы покажете их баннер 2 раза, они ваш - один. Тоже можно управлять своими баннерами. Код вам высылают по E-mail. Статистика довольно средняя. Размер баннера не должен превышать 10К
3) D-sign (http://www.d-sign.com/banners.htm)
Довольно странный обмен! Вам только нужно вставить код к себе на страницу и все. Никакой статистике, но они пушут кто у них участвует, тоже дополниельная реклама.
4) Corpis&M (http://win.copris.com/banner/default.ru.htm)
Зарегистрировался, а она меня не пускает, напичкано Явой. А так у них отнешение 1:1. По идее - хороший обмен! Размер баннера не должен превышать размера 20К
5) Click Exchange System (http://click.ipc.ru)
Неплохая система. Главная валюта - клики. Ваш баннер будут показывать до тех пор, пока кто-то не кликнет на него! Статистика очень плохая: сколько показывали и сколько кликали. Количество баннеров не ограничено. Размер баннера не должен превышать 15К.

Баннерные службы (размер баннера 400х50)

1) Bannerpoint (http://www.bannerpoint.ru)
Отличная система. Думают на счет перехода на 468х60, но должно случиться это не очень скоро, т.к проводится голосование среди участников, а они не очень активны. Недостатки: вообще нельзя управлять баннером! И при том, только одним. Зато какая у них статистика!!! Очень удобная, и очень в тему. Кол-во заходов на страницу, кол-во демонстрация вашего баннера, кол-во заходов через баннер. Потом проценты эффективности баннера, процент захождения на страницу через баннер. Показывают на каких сайтах ваш баннер демонстрировался последние 3 раз, с каких сайтов ваш сайт посетили через баннер последние 3 раза, кол-во заходов на страницу по дням, время заходов. Баннер не должен быть более 18К. Будет такое понятие, как рейтинг. Чем выше рейтинг, тем больше очков вам начислят за каждый показ и каждый клик. Чтобы у вас был высокий рейтинг, нужно иметь большое кол-во заходов на страницу и большой процент заходов через баннер. Самый низкий ретинг - 0, отношение 0.5:1, когда ноль, когда рейтинг 4, то отношение 0.8:1
2) Vimako Service (http://www.reclama.net/banners)
Израильская служба. Не эффективная к сожалению. Сатистика средняя. Нет управления баннером. Отношение показов к кликам у них такое: при обращении к странице, на которой размещен HTML код службы, показывается баннер. При нажатии на баннер пользователь попадает на страницу, которой соответствует данный баннер. Количество нажатий на баннер приводит к увеличению показов баннера на всех страницах. Cоотношение нажатий и показов 1:20, т.е. при одном нажатии, баннер покажется дополнительно еще 20 раз. При отсутствии нажатий, баннеры показываются в произвольном порядке не менее 10 и не более 20 раз в день. Баннер должен быть не более 10К.

Выше были представлены службы, которые были мною проверены. А вот которые ниже, я не проверял.

Размер 468х60

1) Фламинго (http://www.promote.ru/)
Баннерная система "Фламинго". Размер бвннера не должен быть более 15 Kb. Можно иметь любое кол-во баннеров для каждого аккаунта. Предоставляется статистика: за каждый день прошедшей недели отдельно по кредитам, показам и кликам; за все время по кредитам, показам и кликам; клики и показы отдельно по каждому баннеру за неделю, вчерашний и сегодняшний день, за все время; click ratio суммарно по всем баннерам, отдельно по каждому из них за любой день прошедшей недели и за все время. Cистема старается показать пользователю баннер, который он не видел и на который не нажимал. "Фламинго" выбирает баннер, основываясь не только на насущной необходимости, но и известной ей информации о пользователе, как то: предпочтениях баннеров, категории посещаемых им сайтов и т.д. Четыре вида аккаунтов: "normal" - обмен баннерами под 15% кредитов; "free" Действует ограниченное время. Обмен баннерами 1:1; "special" - просто обмен (как получится). В 3:00am происходит обнуление всех результатов, без зависимости от того, показали ли мы больше Вашего или наоборот; "fixed" - Показ баннеров на платной основе.
2) Triffle (http://banner.apex.dp.ua/)
Эта сеть не является довольно простым и распространенным вариантом обменных баннерных систем. Сеть в основном для коммерческого использования, и ее главным преимуществом явлвяется возможность клиента контролировать самому размещение и прокрутку своей рекламы на тех серверах системы, которые он считает нужным. Система, созданная фирмой Triffle, позволяет пользователю размещать свою рекламу на тех ресурсах, которые он считает нужными, то есть как раз пользователь того или иного ресурса является его потенциальным клиентом. Вот поэтому создатели системы утверждают, что она является более удобной и для Вас, и для Вашей рекламной компании в сети. Вы сами сможете контролировать и Ваши баннеры и Ваше деньги благодоря этой баннерной сети. Соотношение показов для участников - 1:1.
3) AlfaStyle (http://www.alphastyle.com/b/)
Рекламная служба студии Альфа Стиль. Создана для размещения качественной рекламы в российском Интернете. Размещать рекламу в службе могут только профессиональные и качественные сайты, используя качественные баннеры. За каждые 100 показов баннеров на ваших страницах служба показывает 85 Ваших баннеров.
4) Banners.ru (http://www.banners.ru)
Только что сам зарегистрировался, но пишу в этом разделе, т.к. точно еще не понял про эту систему. По идее - мощная рекламная кампания! Вам только надо заполнить необходимую форму. Соотношение 9:10. Т.е. сам 10 показанных баннеров на вашей страничке, ваш баннер покажут только 9 раз. Вам предоставится очень полная, а самое главное нужная статистика по баннерам, сколько показывали, какие приоритеты у баннера и т.д. Однако очень сложно разместить их баннер на странице. Есть три способа: в двух случаях вам нужно будет иметь доступ к CGI-BIN, а в третьем вы должны будете установить Ява код на своей странице, лично у меня не получилось!

Список зарубежных систем по обмену баннерами

1) Крупнейшая забугорная сеть - LinkExchange
2) Bannermation
3) Fat Pig
4) Hit Raisers
5) Smart Clicks
6) Mac Web

Я думаю, что шести хватит, т.к. забугорные службы для России не эффективны!

Несколько советов по созданию баннера

1) Говорят, что выражение типа Click Here увеличивает там называемые клики по баннеру, но ЛИЧНО на меня они не действуют.
2) Если Вы ДЕЙСТВИТЕЛЬНО хотите знать, сколько реальных посетителей на вашей страничке, то баннер должен указывать, куда попадет человек, а если Вы хотите, чтобы на счетчике были большие цифры, то можно сделать и баннер-обманку.
3) Соблюдайте такую вещь, как качество/размер. Т.е. если Вы хотите, чотбы Ваш баннер состоял только из графики, то лучше делать его не инимированным, а то я сделал анимированный баннер на 40К, текстовые баннеры можно сделать и анимированными, они небольшими получаются!


Ставим на страницу счетчик и статистику

Элементарный цифровой счетчик.
Можно поставить несколько счетчиков на разные страницы для выяснения степени популярности разделов сайта. Взять Вы его можете на любой из страниц, предлагающих бесплатные элементы оформления, например, здесь. Данные счетчики являются подарками от Nikitad.com
Если Вы работаете не на халяву, а через провайдера, то лучше поставить счетчик провайдера, т.к. он будет быстрее открываться и надежней работать. О том, как это сделать, см. в FAQ-листе своего провайдера или спросите у него, как это сделать.
Счетчик со статистикой и рейтингом.
Счетчики такого рода лучше ставить на главную страницу, дающую большинство посетителей.
Счетчик от Rambler можно взять здесь. Кроме счетчика вы получите статистику за месяц, неделю и день, а также сравнение в рейтинге себя с другими. Типы рейтингов: общий, персональный/корпоративный/ /CGI/Push, тематический (музыка, игры и т.п.). Этот рэйтинг приносит много посетителей (по материалам моего Допроса).
Счетчик от List.ru Вы можете взять здесь. 3 счетчика в одном (всего, за день, в среднем за день). Также предоставляется статистика. При регистрации сайт заносится в каталог ссылок и поисковую систему.
Нечто похожее, но с Украины (значит скорость немного ниже): Ping. Счетчик & Рейтинг, поиск, 3-х и более уровневый каталог, XXX-rating. Новая универсальная система! UA host, RU lng.
Alpha counter A-counter на Украине. Правда он обнуляется раз в неделю - это не очень удобно.
Рекомендуется установить несколько типов счетчиков, так как если один из них будет "отдыхать", то другой будет исправно считать посетителей (и больше шансов, что к Вам зайдут из рейтинга). Постановку этих счетчиков-рейтингов можно считать как первые шаги по раскрутке сайта.
ВНИМАНИЕ! Не следует устанавливать более 3-х счетчиков. Всякие там Ranker и Top1000, т.к. первый почти никогда не работает, а второй медленно грузится из за границы (к тому же Вас не будет заметно в массе импортных сайтов).
Напоследок можно посоветовать поставить рейтинги 1000Stars и Art1000 - это не совсем счетчики: там будет высвечиваться Ваше место в общем рейтинге. Не следует устанавливать другие (украинские и импортные рейтинги).


Untitled

Раскрутка сайта в сети

  • Баннерные службы
  • Как лучше оформить баннер
  • Ставим счетчик
  • Размещение страницы и сведений о ней
  • Оптимизация сайтов для поисковых систем и каталогов
  • Статистика посещений
  • Как увеличить посещаемость страницы
  • Руководство по бесплатному Интернет-маркетингу


Web-дизайн: увеличиваем популярность сайта

Способы я расставил в порядке приоритета. Расположил их по убывающей : от наиболее эффективных к менее эффективным по моему мнению.

1. Почаще обновляйте сайт. Пусть это не будет какое-либо "материальное обновление", пишите все, что касается вашего сайта. Лучше не пользоваться стандартными фразами. Придумывайте оригинальные новости. Можно немного пофилосовствовать по мере способностей. :-) Новости желательно вынести на основную страницу. Ради оригинальности можно писать не только дату обновления, но и время с точностью до минуты :-)

2. Обмен баннериками 88х31 с сайтами такой же популярности (не ниже). Особенно на главные страницы. Сделайте для них специальное и видное место, не жалейте. На баннерики ставьте target=“_blank”, чтобы сайты открывались в новом окне.

3. Сотрудничество с популярными сайтами. Например, можно договориться на размещение на главных страницах рекламных баннеров сроком до 1 месяца. Более месяца не имеет смысла. Прошел месяц - ищите другого сайта-партнера. Таким образом вы будете привлекать постоянных посетителей сайта-партнера, которые, возможно, станут и вашими постоянными.

4. Баннерная реклама. Эффективно, если ваш баннер имеет CTR (коэффициент полезного действия :-) ) свыше 4%. Баннер меняйте с периодичностью 1-2 месяца.

5. Устраивайте на сайте интересные конкурсы. Желательно с призами. Например в качестве приза можно передать некоторое количество баннерных показов или написать обзор сайта победителя в новостях.

6. Настроить вашу почтовую программу, чтобы в конце письма приписывался адрес вашего сайта с кратким описанием. Эффективно в том случае, если часто посылаете письма.

7. Неназойливая и интригующая реклама на 5-10 конференциях. Например с периодичностью раз в неделю. Если посетители конференции отзываются негативно на рекламу, лучше прекратить, иначе будете пользоваться дурной славой.


Размещение и раскрутка web-сайта

Где разместить
Личный домен
Счетчик + рейтинг
Обмен баннерами
Поисковые системы
НР-клубы
Периодичность обнавления
О плагиате

Где размещать свою домашнюю страничку
После непродолжительного пребывания в Internet у многих возникает желание не только смотреть на творение рук других, но и себя лицом показать. Сделать свою Web -страницу весьма просто, необходимо только немного посмотреть документацию на HTML и немного старания, а вот уже разместить и разрекламировать, причем, не затратив на это не копейки (или если угодно не цента) это уже сложнее.

Начнем с обзора того, где можно разместить результаты стараний:

Сервер | Простран- ство | e-mail | Доступ по FTP | Недостатки
www.chat.ru | 3mb | + | + | Основная кодировка koi-8
www.i-connect.ru | 2mb | +(1mb) | + | 29.05.98 временно не предоставляли пространство
Home.vibor.ru | 2mb |  | + | Получить пространство может только обладатель Intenet Explorer из-за сбоев на странице регистрации
www.halyava.ru | 1mb | + | + | На каждую страницу размещают свой баннер с рекламой
Lgg.org.ru | 10mb | + | + | Страница уже должна быть готова, а не в стадии разработки.
www.raduga.ru | 1mb | + | + | На размещенной странице может присутствовать до двух баннеров сервера "РАДУГА".
Вообще-то, если у Вас более-менее приличный провайдер, то вместе с IP-адресом и почтовым ящиком, он предоставляет и бесплатное место под вашу HP на своем сервере, так что поинтересуйтесь у него на этот счет.

Ваш личный домен
Получив место на одном из этих серверов Ваш URL будет иметь вид: http://www.free.ru/pupkin/. Это самый оптимальный вариант, есть вероятность что Ваш каталог будет выглядеть еще более неприглядно. Более красиво выглядит домен третьего уровня www.pupkin.da.ru (второго выглядит еще лучше, но его бесплатно не получить). Такой миссионерский доменный маркетинг осуществляется на www.da.ru. Кроме этого для жителей и фирм С-Петербурга существует такой же сервис на www.s-pb.ru, хотя на мой взгляд это зеркало www.da.ru .

Counter с возможностью определения рейтинга
Теперь у нас есть домашняя страничка, она размещена в виртуальном пространстве и зрители могут оставлять свои мнения и отзыва. Возникает желание узнать насколько часто кто-то вообще ходит и смотрит на Ваше творение и если ходит, то с каких доменов. Так же становится интересно, какова популярность вашей странички по сравнению с остальными. Такие возможности предоставляет почти любой counter, одним из лучших серверов определяющих рейтинг по трафику является rambler. Если Вас не устраивает Rambler, то можете попробовать вступить в InfoArt. Используя InfoArt HITs, Вы можете увидеть точную статистику посещений вашего сайта (как суммарную, так и за текущий день). Если Вы используете счетчик другой компании, то после соответствующей проверки они готовы начать отсчет InfoArt HITs с той отметки, которой достиг старый счетчик.

Еще одной разновидностью определения рейтинга является рейтинг по голосованию. Таким рейтингом является Рейтинг личных страниц Fletcher'a . В рейтинг попадают только те странички, которые понравились за месяц как минимум 15 разным посетителям. Таким образом, перед вами не просто рейтинг сайтов, но рейтинг интересных сайтов.

Службы обмена баннерами
Как же о Вашей домашней страничке узнают другие пользователи сети Существует несколько способов, один из них - службы обменов баннерами. Вам необходимо зарегистрироваться в одной из них, и разместить на своих страницах HTML-код этой службы, после чего на 100 показов баннеров на Ваших страницах служба покажет ваш баннер на других страницах, входящих в сеть обмена. Таким образом пользователи могут заявлять о себе на других страницах через службы обмена баннерами.

Службы обмена: | Баннеров в день/ кликов в день | Ваша статистика | Соотношение при обмене баннеров | max р-р баннера | Условия к вступлению
InterReklama | 280 000/13 600 | Разно- образная | -- | 12Кb | Нет
Reklama.Ru | 70 000/-- | Скромная | 100:85 | -- | Min 400 баннеров в день
Russian Link Exchange. * | 35 000/-- | Разно- образная | 100:50 | -- | Нет Для вступления в "золотой" раздел - минимум 500 в день
Баннерная сеть "Фламинго" | 40 000/1 700 | Разно- образная | От 100:70 до 100:90 | -- | Min 30 баннеров в день
StrongSoftware | 17 000/-- | Скромная | 1:20!!! | 10Kb | Нет, но временно не работает
The Banner Exchange Point | --/-- | Разно- образная | 100:50 при показе 100:100 при нажатии | 18Кb | Нет
Ricor Banner Exchange | 5 000/-- | обещают разно- образную | От 100:50 до 100:80 + бонусы | -- | Нет
Golden Russia Banner Exchange | --/-- | разно- образная | От 100:80 до 100:120 + бонусы | 15Kb | Нет
W3D Banner Exchange | пока не много |  | 1:9 | 18Kb | Ориентирована на ростовский Интернет

*Russian Link Exchange - показывает баннеры с фрагментами HTML кода (это может быть ShockWave Flash, CGI формы, JAVA апплеты и т.д.).

Поисковые системы
Искать что-то интересное в интернете можно несколькими способами, один из них - зайти в первую попавшуюся поисковую систему, набрать интересующие Вас ключевые слова и запустить поиск. Справочно-поисковые системы предназначены именно для нахождения необходимой информации в больших объемах. Естественно, что если кого-то заинтересует тематика ваших страничек, то он поступит так-же. Следовательно Вам необходимо зарегистрировать свои ресурсы во всех попавшихся под руку поисковых системах. Ниже приводится обзор по принципу русскоязычность, возможность добавления в ручную (т.е. наличие опции "добавить", а не автоматический поиск роботом машины), и более-менее стабильная работа. Мною было найдено несколько систем, отвечающих этим позициям:

Поисковая система | База поиска | Поиск ведется по:
Апорт! | 1 142 449 документам (1 975 270 URL, 9 476 серверов) на 25.06.98 | по содержанию документа, датам, URL,отдельным полям HTML документов(заголовкам, ссылкам)
Rambler | 2,5 миллиона документов, более 15000 сайтов 01.02.98 | по содетржанию документа, полям (URL, Title, Header, Essence, Address)
Яndex | Количество серверов - 12043 Количество URL - 2 402 168 | по содержанию документа, по ссылке "A" и по заголовку "Title"
Weblist | Количество документов или URL в базе считают не отражающим серьезность системы и поэтому некоторые запросы не включают в базу | Поиск ведется по ключевым словам и наименованиям ресурсов, вводимых пользователем при регистрации.
AltaVista Для поиска картинок: images. altavista.com | сервер производит поиск не только по русскому интернету, поэтому трудно сказать сколько содержится рускоязычных ресурсов | по содержанию документа, по полям (applet,text,title,domain,host,image,link,url)
Tela* | --- | по ключевым словам в русском Интернете, возможность поиска по текущему содержанию русскоязычных телеконференций иерархий relcom, relcom.commerce, fido7, medlux

* В поисковой системе Tela робот сам ищет и добавляет URL и все остальное. Но Вы можете сообщить письмом URL своего сайта. Предпочтение отдается целым сайтам, а не отдельным страничкам.

HP-клубы
С каждым месяцем в Российском Интернете возникает несколько клубов домашних страниц. Кто-то ищет такие клубы и как коллекционер навешивает на своей странице каждого нового зарегистрировавшего его клуба, но клубное участие подразумевает не столько наличие на Вашей страничке клубного значка, сколько что вы будете иметь от участия в таком клубе.

Обычно клубы предлагают несколько бесплатных downloadШов, появление Вашего ссылки в рейтинге членов клуба, несколько сотен или тысяч показов Вашего баннера при занятии первых позиций в рейтинге клуба. Еще Вас может ожидать кое-какая помощь в веб-дизайне, JavaScript, HTML и рекомендациях по оформлению страниц. Не стоит кидаться на всякое предложение вступить в HP клуб, всегда стоит взвесить, кто больше получит клуб от ссылки на Вашей странице или вы от него.
HP club
Проводит ежемесячные конкурсы, лучшие страницы получают некоторую бесплатную рекламу в сети. Имеется база данных членов клуба (с рейтингом), доска объявлений и TOP-10 членов клуба.
Omen
Список участников клуба по рейтингу, для победителей конкурсов реклама баннерами на страницах самого клуба. Доска объявлений, информация по оформлению своих страниц.
R.G.H.
Чем кроме совета может помочь этот клуб, по словам самого его создателя. Получение значка R.G.H.Winner и размещения значка свою страницу (до того пока другой человек не выиграет) Возможность поместить свой банер (400:50) на моей начальной странице Принятие и участие в дальнейших рейтингах
Parallel HP Club
Пример клуба в себе, для удовлетворения своего Я, а не для членов сообщества. На 27.06.98 цельных два участника.
Доволно интересен сайт "ART club" - есть рейтинг, группировка по катигориям, проводятся конкурсы. Сделано все довольно добротно, хотя посещаемость клуба не большая.

Периодичность обновления
Если Вы решили сделать свою HP действительно интересной и посещаемой, то ее необходимо постоянно пополнять сведениями, иначе посетитель, прочитав всю информацию, уйдет и больше не вернется, и наоборот если Ваша HP периодично обновляется интересной информацией, то он, вероятнее всего, сделает закладку.

Признанием периодичности Вашей страницы, т.е. официальным подтверждением ее постоянной обновляемости, может быть вступление в "ЕЖЕдневки" или "ЕЖЕнедельки" , но не думайте, что Вам это просто обойдется. Для вступления и получения официального статуса члена необходимо: "ЕЖЕдневки" обновлять не реже 5 раз в неделю в течении месяца, а "ЕЖЕнедельки" не реже раза в неделю, на протяжении 2 месяцев, впрочем читайте сами.

Плагиат
Если Вы считаете, что кто-то использует Вашу информацию, логотипы, дизайнерские находки и вообще какую-то Вашу интеллектуальную собственность, то, естественно, Вы можете обратиться в суд, но это дело муторное и не совсем с веселым концом, однако есть способ более простой. В случае кражи Ваших начинаний сначала свяжитесь с воришкой (может он нечаянно), а если он настолько нагл, что не желает признаваться, то Вы можете пожаловаться на него сюда и его имя будет красоваться на доске позора, если вам интересно чем это кончается для нарушителей, то почитайте.

Дорогие читатели, дерзайте, наполняйте WEB - пространство своими идеями, творите и рекламируйте себя, делайте Интернет таким, каким Вы его хотите видеть.


- Оптимизация сайта для поисковиков.

Оптимизация сайтов для поисковых систем и каталогов

Прежде всего рекомендуем не торопиться с регистрацией в поисковых системах. Сначала подготовьте сайт для успешной регистрации, ведь нам надо попасть при запросе на как можно более высокое место.
Для этого следуйте простым правилам при создании своих страничек.

Ваш сайт должен выглядеть примерно так:

<HTML>
<HEAD>
<TITLE>Заглавие Вашей странички </TITLE>
<META NAME="description" CONTENT="Описание информации на страничке, что содержит">
<META NAME="keywords" CONTENT="Ключевые слова для поисковых систем">
</HEAD>
<BODY>

Это поможет для правильной регистрации. Но не надо ставить много "keywords" многие поисковые системы их просто игнорируют, а времени на загрузку сайта тратится больше. Внимание уделяйте заголовку "<TITLE> </TITLE> ". В заголовке уже должен описываться Ваш сайт наиболее полным образом.

2. Далее многие поисковики обращают внимание на первые слова и фразы на Вашем сайте. Поэтому наиболее полно опишите Ваш сайт с первых слов.

3. Базовые понятия и ключевые слова для сайта следует включать в HTML тэги (в порядке значимости):

  • <TITLE>
  • <H1-..H6>
  • <ADDRESS>
  • <B>, <STRONG>

4. Чем чаще слово встречается в комбинации этих полей, тем более вероятно, что поисковая система найдет ваш документ ближе к началу списка результатов поиска. Кроме того в некоторых поисковых системах, чем ближе располагается поисковое слово к началу документа, тем больше весовой коэффициент данного слова.

5. Не следует также использовать невидимый текст на страничке (т.е. шрифты, цвет которых соответствует цвету background). Комментарии в документе роботы многих хороших поисковых систем тоже не сканируют, по-этому советуем использовать их по прямому назначению.

6. Не следует включать в список желаемых ключевых слов общеупотребительные, часто встречающиеся слова, такие как сервер, html, ru, сайт и другие. Ваши слова должны быть достаточно информативными. Многие поисковые системы не сканируют общеупотребительные слова.

7. С точки зрения поиска использование фреймов в документе не приветствуется. Это не означает, что роботы не умеют сканировать фреймы. Однако, мы советуем включать в текст таких документов HTML тэг <NOFRAMES> с последующим описанием документа и ссылками. Разумеется, это увеличит размер странички, но это улучшит Ваши шансы на правильную регистрацию поисковыми машинами.

8. При указании перекрестных ссылок в документе будте предельно внимательны, проверьте работоспособность каждой ссылки, иначе поисковые системы не смогут проиндексировать весь Ваш сайт.

9. Максимальный размер документа должен составлять 100 - 150KB. Документы большего размера неудобоваримы для поисковых систем и людей.


Обсчитали!

Такой крик можно услышать не только на рынке.
Эта статья посвещена проблеме установки на своей странице счетчика и выбора программы для наблюдения своей статистики. В Рунете давольно много серверов, предоставляющих бесплатную статистику и счетчик, но не все они грамотно и быстро работают. Нет, подождите, что-то я забежал вперед. Надо бы начать с вопроса "Зачем вобще нужна cтатистика и счетчики?". Ответ на этот вопрос можно дать однозначный - для того чтобы быть в курсе популярности своего сайта. Но какую систему выбрать для того, чтобы она подходила именно вашему сайту? Именно этому посвещается эта статья.

Как я уже сказал нам, то есть и вам и мне, нужен такой сервис, который будет быстро, точно, а главное продуктивно работать. Вот например Rambler's top100 является и статистикой и рейтингом и счетчиком одновременно. Сервер Rambler чень быстрый, поэтому весь сервис работает очень быстро.

При просмотре статистики почти в любой статистической системе вы видите стандартный набор данных, а также уникальный для каждой статистической системой, именно этот уникальный для каждой системы набор услуг и является тем, что делает каждую систему по своему привлекательной. Стандартный пакет услуг включает в себя счетчик посещений, среднее количество посещений, IP адреса уникальных посетителей, количество уникальных и постоянных посетителей. А некоторые системы предоставляют еще и дополнительные услуги, которые помогут вам понять когда, в какое время, в какой день к вам приходит больше людей, откуда эти люди к вам приходят и.т.д.

Чуть не забыл напомнить, что все счетчики в основном пишуться на Перле (CGI), но есть также Ява счетчики. При выборе счетчика стоит учесть, что счетчики на Ява работают медленее и поддерживаются не всеми броузерами, так что лучше использовать традиционные Перловские системы.

Такие услуги предоставляет напримет система Rambler's top100 . Эта система помимо стандартного пакета услуг позволяет также просматривать сравнительную статистику по посещениям за неделю, количество так называемых Reloads и вот собственно и все. Зато Рэмблер при регистрации в системе автоматически записывает вас в рейтинг, рейтинг подсчитывается по количеству посещений, есть несколько систем подсчета рейтинга.

А вот например статистика Extreme предоставляет вам полную информацию практически обо всем, что могло бы вас заинтересовать. Помимо всего упомянутого выше этому сервису присущи такие возможности, как слежка за системными возможностями посетителей, статистика посещения страницы разными броузерами, операционными системами, IP, разрешениями экрана. Также там есть так называемая GEO tracking, которая отслеживает страну, континент людей посетивших эту страницу.

Кроме этих, двух самых известных систем-шпионов есть также довольно много менее известных:

Счетчики:

  • XpointCGI (язык-английский)
    Этот сервер обеспечит вам 13 cgi - скриптов, среди которых есть и счетчик.

  • Aaddzz FREE Counters (язык-английский)
    Выбирайте из множества стилей тот счетчик, который подойдет вам и вашей страничке.

  • Page Doctor (язык-английский)
    Здесь Вы можете получить счетчик, кроме того на сайте есть бесплатные картинки.

  • FreeAndFun Counter (язык-английский)
    Счетчик в обмен на ссылку.

  • JCount (язык-английский)
    Если честно, не видел, но говорят, что анимированный Java- счетчик.

  • LiveCounterClassic (язык-английский)
    Тоже Java счетчик, очень много возможностей, если ваш броузер не поддерживает Java, то имеется обычный вариант.

  • Beseen's Hit Counter (язык-английский) Предлагается всего лишь скопировать и вставить HTML код. Различные виды счетчиков, просто и удобно, правда счетчик такой, что на нем сверху надпись "Beseen".

  • counter.dux.ru (язык-русский)
    Русский счетчик на сервере DUX. Очень много стилей, есть простые и оригинальные, разные. (Но его разместить можно лишь на странице, находящейся на российском сервере). Недостаток: не удобно размещать счетчик (слишком сложно).

  • Set your own counter (язык-русский)
    Счетчик на сервере "nikitad". Довольно простой, но удобный.

  • Digiweb (язык-русский)
    Неплохой счетчик, не лучше и не хуже других.

  • ATWILL (язык-русский)
    См выше.

  • WebRight Services (язык-русский)
    500 стилей цифр ! Вы такое где-нибудь видели? Хотя, может столько и не нужно, но там точно можно найти счетчик на свой вкус! Жаль, что сервер очень тормозит, так что не всякому доступны их богатства.

Статистика:

  • ICount (язык-английский)
    Вы помещаете у себя маленькую квадратную картиночку, она почти не занимает места. Кликнув на ней, Вы узнаете данные о Ваших посетителях, хотя не очень подробные, больше похоже на счетчик, чем на статистику.

  • Site-stats (язык-английский)
    Ваш счетчик помещают на баннер сервера, тем не менее, неплохая статистика.

  • WebTracker (язык-английский)
    Собирает данные о броузерах и операционых системах посетителей, сообщает, сколько из них пришли в первый раз, а сколько посещали по несколько раз, можно узнать, сколько посетителей в день.

  • Net-Trak (язык-английский)
    В принципе тоже самое.

  • Web Side Story (язык-английский)
    Известный сервис, довольно удобный.

  • SuperStats (язык-английский)
    Хорошая статистика, правда необходимо поместить у себя нечто размером 400х40. Ведь это же аксиома: будь твой сервис хоть золотой, но потребуй поместить свой баннер, и количество клиентов резко уменьшится. Ничто так не дорого хозяину страницы, как свободное место!

  • Siteflow (язык-английский)
    Счетчик и подробная статистика.

  • Escati (язык-английский)
    Счетчик и немного статистики.


Рекламные сети

На самом деле, про рекламные сети я слышал давно, все как-то руки не доходили разобраться с ними. Однако - дошли.

Если честно, не очень мне нравится этот способ раскручивать узлы. Может быть от того, что рисовать я с детства не умею, может - от неприязни к излишней графике на Вебе вообще... Ну да ладно, последние мои изыскания в этой области показали, что игра стоит свеч. Поиграв в рекламные сети, я добился 10% увеличения трафика на своем сервере.

Попробовал я несколько сетей, в итоге остановился на одной - reklama.ru. Почему именно эта - потому, что она изначально рассчитана на участников с большим траффиком. Flamingo мне не понравилась из-за того, что суммарное число показов маловато, InterReklama - крупнейшая баннерная сеть, но в ней нужно регистрить каждую свою страницу и для каждой - свой код, а у меня их больше четырех тысяч, да и порнография через нее слишком часто прет. Sputnik.Ru умирает, WebList умер (правда, есть слухи, что снова начнет работать).

Некоторые размышления

  1. Если у Вас есть узел с хорошим траффиком, Вы можете на нем показывать баннеры из сети, а свой баннер направить на раскрутку другого узла. Очень интересная игра, весьма перспективная. Тут у нас в конторе планируется конференция по базам данных, решено было бросить всю мощь моего CIT Forum-а на раскрутку этого мероприятия. Результат оказался превосходным: еще до начала рекламы другими средствами на конференцию записалось около двадцати человек, а это по сто грина с носа.
  2. При крупной игре все происходит по законам больших чисел. Предположим есть траффик 20.000 показов в сутки. Средний отклик 2%, комиссия баннерной системы - 15%, следовательно, система покажет 17.000 баннеров и на страницу с неким товаром за $50 придет 170 человек. Средний отклик здесь тоже около 2% - получается 3 покупателя. Итого заработали $150. Я назвал бы показы виртуальными деньгами, важно только обеспечить точку обмена виртуальных денег на реальные. Это, кстати, относится не только к баннерным сетям, но и к любой рекламе на Интернет.
  3. Узлам с маленьким трафиком довольно бессмысленно пытаться играть в рекламные сети. Простой пример:
    Предположим, что Вы можете показать 10 баннеров в сутки. По стандартной схеме рекламных сетей Ваш баннер будет где-то показан 8 с половиной раз. В среднем баннер обладает кликабельностью в 2.5%, т.е. Вы можете рассчитывать на 1 клик в сутки - по-моему, это не серьезно. В этом случае я бы порекомендовал использовать такой прием: Вы показываете баннеры из сети, а свои в сети не крутите, накапливая тем самым кредиты. Затем запускаете в сеть свой баннер и получается интенсивная рекламная компания.
  4. Если поразмыслить, то получается, что при попытке крупного сервера поиграть в рекламные сети идет обычный обмен траффиком между участниками рекламной сети, причем между крупными. Лишь изредка в эту игру вклиниваются баннеры узлов с малым трафиком. Да и количество участников сети не бесконечно. Это ведет к "протуханию" баннера, т.е. через некоторое время его эффективность начинает падать. В этом случае надо сделать и запустить другой баннер.
  5. Качественная зависимость уровня трафика на рекламируемом узле будет такова:
    Как видите, есть некоторый первоначальный скачок, потом трафик возвращается на ту же зависимость и выигрыш только в разнице между тем, каков трафик был и тем каким он стал. Это от 1 до 10 процентов. Я свои 10 процентов получил от одновременной игры сразу с четырьмя сетями.
  6. Еще одна мысль об игривых баннерах. Бывают серьезные узлы, на которых недопустимо их показывать, а отсечь такие показы можно не во всех сетях. В листе рассылки, посвященном работе баннерных сетей все больше сторонников вообще не принимать игривую рекламу.
  7. И, в довершение всего, очень противно, когда вместо баннеров появляются битые картинки. Это довольно противно:-((. Хотя в сети Reklama.Ru такое случается очень редко.

Кроме моих мыслей можно почитать дискуссию на эту тему в Коференц-зале Webclub'а. Еще рекомендую сервер Tim Promotion Тимофея Бокарева, где есть материалы по баннерным сетям и статьи с изложением его видения вопросов баннерной рекламы. И, наконец, только что найденный ресурс.

WebList Banner Exchange
(http://weblist.ru/banners/index.html)

  • Свободное размещение и управление баннером
  • Количество баннеров на сайт неограничено
  • Размер 400х40 2-6Кб
  • Возможность задавать частоту показов, временные параметры показов, фильтровать показы для доменов или IP-адресов
  • В зависимости от Click Ratio увеличивается вес Вашего баннера
  • Регистрация и изменение параметров online
  • онлайновый просмотр статистики
  • Ежедневные сводки по e-mail о текущем состоянии дел (option)
  • Баннер должен лежать на Вашем узле
  • В настоящий момент система закрыта. Говорят, откроется через некоторое время.

sputnik.ru advertising network
(http://www.bannermarket.com/)

  • регистрация путем посылки заявки на баннеры почтой
  • баннер должен лежать на Вашем узле
  • изменение параметров online
  • онлайновый просмотр статистики
  • в качестве описания работы и управления системой предлагается жуткая смесь статей и писем на различных серверах (на английском и русском в разнообразных кодировках)

Баннерная сеть Фламинго
(http://www.cross.ru/flamingo/index.htm)

  • обмен баннерами 1:1 (при возможности показывать не менее 100 баннеров в сутки), под 15% кредитов, случайным образом.
  • регистрация по e-mail или через formmail
  • онлайновый просмотр статистики и управление показами
  • довольно сложная система, но на их сайте все подробно описано
  • доступны все технические характеристики и текущие клиенты сети
  • баннер должен лежать на Вашем узле, хотя можно послать его администрации, чтобы они вручную положили его у себя
  • отсутствуют adult-баннеры и узлы (с 6 января 1998 года)

Рекламная сеть InterReklama
(http://www.bizlink.ru/ir/)

  • регистрация и изменение параметров online
  • развитое управление показами и просмотр статистики
  • довольно сложный интерфейс
  • на мой взгляд имеет место переизбыток описательной информации
  • возможность отключать adult-баннеры и узлы

Рекламная сеть Reklama.Ru
(http://www.reklama.ru/, не путать с http://www.reclama.ru/)

  • регистрация по e-mail
  • участником сети можно стать только еслы Вы можете показать не менее 400 баннеров из сети в сутки ("мажорная" в этом смысле сеть)
  • обмен баннерами под 15% кредитов
  • онлайновый просмотр статистики (общей и отдельно по баннерам) и управление своими параметрами
  • поддерживается любое количество баннеров с любыми ссылками
  • возможность изменения частоты показов в целом, приоритетов баннеров
  • баннеры подгружаются на рекламный сервер, поэтому нет "битых" картинок
  • сеть имеет систему зеркал по всей территории России
  • Поддерживается динамическая статистика в отдельном маленьком окошке - в общем то "дорогая" игрушка

Russian Link Exchange (RLE)
(http://www.linkexchange.ru/)

Баннерная Система FIVC
(http://www.fivc.com/baner/index.htm)

Mega Pro et Contra
(http://www.newman.ru/)

StrongSoftware
(http://www.strongsoftware.net/)

The Banner Exchange Point
(http://www.bannerpoint.ru/)


Электронные рассылки

Рассылками я занимаюсь уже давно, еще когда у меня был только досовский Dmail и доступ только по UUCP. Короче уж года два... За это время у меня накопилось три базы адресов (добровольных) общей емкостью более 7.000 записей. На мой взгляд, электронные рассылки - самый мощный способ продвижения своего узла. Однако, этот способ требует наибольшей аккуратности использования.

  • Этика, оптимальный объем и содержание сообщений
  • Регулярные рассылки
  • Одноразовые рассылки
  • Телеконференции и maillist-ы
  • Техника или как это все автоматизировать
  • Практические результаты и наблюдения

Этика, оптимальный объем и содержание сообщений
Интуитивно ясно, что ежели хочешь, чтобы с тобой продолжали общаться, будь приятен в общении. Уже давно существует такое понятие как netiqutte. Не буду углубляться в описание этики сетевого общения, скажу лишь, что сетевое общение существенно отличается от обычного. Соответственно сетевой этикет отличается от обычного.

Перечислю несколько правил, которые я для себя выработал и которых всегда придерживаюсь.

  1. Всегда придерживаться определенного стиля сообщений. Стиль - вешь уникальная, он позволяет сразу понять от кого письмо и, примерно, о чем. Существуют люди, которым прикольно писать в стиле ФИДО, меня, однако, такой стиль не слишком прельщает. Другая крайность - применение суперофициального стиля, какой используется в бумажной переписке между организациями. На мой взгляд, это тоже неправильно, все-таки сетевое общение менее формально.
  2. Всегда обращаться на "Вы". Никогда нельзя определить, не скрывается ли за подписью "Люба" женщина пятидесяти лет, которая при этом еще и генеральный директор мощной корпорации. Представляете себе ситуацию: после долгой фамильярной переписки в стиле "Слушай, Любка..." Вы встретитесь лично!
  3. Электронное письмо должно быть весьма лаконичным, но при этом все должно быть понятно. Длинные письма никогда не дочитываются до конца. Если же письмо непонятно для получателя - это раздражает вплоть до выбрасывания в корзину. Типичный пример подобного письма: "Здравствуйте, уважаемый ...! Давайте сотрудничать. С уважением, ...."
  4. Отсутствие грамматических, орфографических и прочих ошибок. Не поленитесь, перечитайте письмо перед отправкой. Не знаю, как кто любит, но мне не нравятся письма без запятых, точек и заглавных букв в именах. Я не против слэнга, но себе такое позволяю только когда адресат мне хорошо знаком.

Регулярные рассылки
Один из мощнейших рычагов оповещения массы народа. Есть такой термин "генерация повторного посещения", так вот регулярные рассылки весьма эффективны для обеспечения неоднократного захода подписчиков на узел. Регулярные рассылки можно делать только тем, кто добровольно согласился на это. Работать с каждым подписчиком надо очень аккуратно, за ним надо ухаживать, как за больным ребенком, молниеносно реагировать на каждое пожелание человека и НИКОГДА НЕ ЗЛОУПОТРЕБЛЯТЬ его вниманием. Слать письма таким подписчикам следует точно по расписанию, только того содержания, которое было заявлено при подписке, очень осторожно и понемногу включать в такие письма рекламу.

Очень полезно слать именные письма, людям нравится, когда им приходят письма типа "Здравствуйте, Иван Иванович...". Я, например, это умею делать, если интересно, могу рассказать технику этого дела. Чего делать не стоит, так это регулярно рассылать письма списком, потому что любой из подписчиков сможет вытащить из письма длинный список адресов, по которым Вы послали письмо, и использовать его для собственной рассылки. По секрету скажу, что два года назад первые несколько сотен адресов были мной собраны именно таким способом.

Одноразовые рассылки
Неплохой метод для "генерации первого посещения". Делается примерно следующим способом: собирается некоторое количество адресов, разбитых по областям, например, адреса ВУЗов или туристических компаний. Затем очень аккуратно составляется короткое сообщение с аннотацией рекламируемого ресурса и рассылается по заготовленому списку адресов. Содержание письма должно отвечать интересам той группы людей, по которым вы рассылаете письма. Надежнее, если текст письма был на грамотном английском языке, поскольку неизвестно, как будет прочитана кириллица мэйлерами адресатов. Слать такие письма нужно с человеческого адреса, а не с nobody@domen.ru, поскольку письма с адреса роботов без размышления отправляются в корзину.

Несколько правил:

  • Никогда не используйте такие списки дважды за полгода.
  • Отвечайте извинениями, если какие-либо адресаты станут возмущаться.
  • На мой взгляд, не стоит добавлять извинения по поводу непрошенной почты в текст сообщения.
  • С недавних пор возникла мода слать в тексте письма одинаковые куски текста, но в разных кодировках. Мне кажется, что это неправильно.
  • Если Вы-таки желаете регулярно слать сообщения по собранным спискам адресов - напишите фразу типа "Если не хотите получать сообщения - ответьте с Subject='Remove'"

Телеконференции и maillist-ы
На мой взгляд, телеконференции - не слишком сильный способ раскрутки узлов. Не знаю, как кто сильно пользуется электронной почтой, а я получаю до двух сотен писем на свои почтовые ящики. Совсем другое дело - maillist-ы. Здесь, подписываясь, ты точно знаешь на какую тему будут письма. Кроме того, когда идет разговор в листе, ты можешь высказывать свое мнение, обильно ссылаясь на свой собственный узел. Разумеется, все, кому интересно, пойдут туда смотреть. Я бы добавил сюда еще всякие вещи типа Инфоартовского HelpDesk или WWWBoard-ы как на Перекрестке. Например, с каждого моего ответа на Перекрестовском WWWBoard-е пришло до 30 запросов. Неплохо, да?

Техника или как это все автоматизировать
Все довольно просто, на сервере устанавливается форма подписки, ведется либо база данных с адресами, либо плоский форматированный текстовый файл со списком подписчиков и делается скрипт для рассылки писем. Вот несколько моих практических наблюдений:

Форма подписки
Не делайте много полей в такой форме, людям будет лень их заполнять; лучше подписываются с корневой страницы узла, хотя отдельную страницу с такой же формой иметь необходимо; обязательно пропишите явно, что можно в любой момент сделать unsubscribe; предусмотрите выбор форматов сообщений (plain text или attachment); проверяйте корректность ввода адреса E-mail (небольшую дискуссию на эту тему можно почитать здесь)

База данных
Если вы ведете именную базу - проверяйте, что вводят пользователи; регулярно вычищайте неправильные или умершие адреса; если приходит unsubscribe - обрабатывайте запрос сразу же

Скрипт рассылки
Если Вы используете рассылку списком - все очень просто. У меня же рассылка именная, идет долго (4000 адресов - около полутора часов) и были проблемы с тайм-аутом по http-соединению. В итоге скрипт еще не закончился, а броузер говорит "Document content no data". Это решается постоянным выводом не менее 4К за период тайм-аута сервера (обычно 5 минут). И последнее ВСЕГДА ВЕДИТЕ ЛОГИ РАССЫЛКИ. Это поможет в случае ошибок скрипта - можно будет уверенно досылать письма, не боясь залупить повторное письмо в две тысячи адресов.

Практические результаты и наблюдения
Регулярные почтовые рассылки - мощнейший способ поддержки траффика на узле. Кроме того - великолепный сервис для пользователей. Вот несколько результатов, полученных на практике:

  • При анонсе обновлений путем рассылки гарантированно придут посмотреть сразу 10% подписчиков, и еще процентов двадцать за следующие два дня.
  • Наиболее активная аудитория подписчиков - вебмастера (что понятно:-))
  • Полезно дублировать письма специальной страницей типа "новости сервера"
  • Подписчики очень любят, когда с большими праздниками их поздравляют заранее, а не после праздника. Однако будьте осторожны, как-то перед 7 ноября я приклячил в конце письма поздравление, так ребята из прибалтики стали немеряно возмущаться:-))
  • А еще, если собирать при подписке переменную REMOTE_HOST, можно больше узнать о характере траффика на сервере и интересах групп пользователей.


Бесплатный Интернет-маркетинг

Вы думаете, бесплатно не бывает? Ошибаетесь, еще как бывает! Главное, правильно это делать. На данный момент я знаю следующие способы бесплатно раскручивать свой узел в Internet.

Сразу оговорюсь: все мои рассуждения касаются исключительно тех ресурсов, которые относятся к русскоязычной части Internet. В другой части до черта полезных ресурсов, да только там по-русски не понимают...

  • Поисковые машины
  • Рекламные сети
  • Электронные рассылки
  • Обмен ссылками, телеконференции и каталоги ресурсов


Обмен ссылками, телеконференции и каталоги ресурсов | WebClub

Обмен ссылками, телеконференции и каталоги ресурсов

Вообще говоря, Веб тем и славен, что существуют гипертекстовые ссылки. Истина вроде прописная, а под раскруткой узла все больше предполагается, что это всякая баннерная и традиционная, не Internet-реклама. А зря, импортные вебмастера и маркетологи даже специальный термин придумали - Linkage Campaign - и специальный этап Internet-рекламных кампаний, посвященный обмену ссылками.

Что же такое Linkage Campaign?
Простейший вариант: открывается окошко почтового клиента, пишется адрес, тема и письмо - "Слушай, друг, поставь ссылку на мой узел, а?"
Реально: попробовать прописаться везде, где только можно.

Для начала стоит оглядеть каталоги общего назначения и вписаться в них, причем во всех относящихся к тематике узла категориях. Никто кроме Вас этого не сделает. Лучше, если ссылка будет вести не на корневую страницу, а на тот раздел узла, который соответствует такой же категории в каталоге. Совсем хорошо, если заготовить специальную страницу для входа, чтобы пользователь с первого взгляда заинтересовался Вашим узлом. Наши западные коллеги-вебмастера называют это "промежуточной страницей" или "прослойкой". (Это, кстати, полезно и в случае баннерной рекламы). Если не понятно, можете сравнить головную страницу моего узла и промежуточную, на которую я принимаю "первое посещение" своих новых пользователей. А представляете себе ситуацию, когда ссылка ведет на страницу, где есть только название узла и выбор кодировок?:-))

Наиболее известные российские каталоги: Демосовский Www.Ru, Ижевский WebList.Ru, Инфоартовский Stars.Ru, РОЦИТовский "Ау", недавно открыт каталог узлов на Рамблере, совмещенный с поисковой системой.

Когда во все общеизвестные каталоги Ваш узел успешно внесен (в случае неудач следует настойчиво повторять процедуру регистрации:-) переходите к специализированным каталогам. Какие? Той тематики, что и Ваш узел (или как какой либо его раздел). Музыкальные ресурсы полезно прописать в РУБЛе, ежедневки на Еже, короче, ищите сами. Нынешние российские узлы частенько грешат собственными списками серверов, причем нередко это наиболее развитые разделы на узлах.

Как же искать нужные каталоги? Очень просто, посмотрите, кто ссылается на узлы Ваших конкурентов и предложите им разместить ссылку на Ваш узел. Например, я считаю своим конкурентом узел Инфоарта. Соответственно, захожу на AltaVista и говорю:

  link: http://www.infoart.ru/
Получаю полторы тысячи адресов, которые потом начинаю шерстить:-))

Кто-то может возразить, что, мол, это ненужная возня... Спорить не буду, просто приведу данные по реферерам за промежуток в трое с лишним суток.

#запр: URL
-----  ---
 2165: http://www.anekdot.ru/
 1013: http://www.altavista.digital.com/
  587: http://yandex.ru/
  372: http://www.machaon.ru/digest/
  256: http://www.citmgu.ru/
  235: http://av.yahoo.com/
  208: http://www.hotbot.com/
  201: http://www.rambler.ru/
  101: http://www.diamondteam.ru/catalog/add.htm
  199: http://www.citforum.ints.net/
  192: http://www.bspu.altai.su/public/unix.doc/
  unix/contents.html
   90: http://search.newman.ru/
   69: http://www.referats.aha.ru/IND-5.HTM
   65: http://www.tic.tsu.ru/~dma
   58: http://citforum.telecom.mogilev.by/
   46: http://www.cit-forum.com/
   45: http://weblist.ru/
   31: http://www.finmarket.ru/FINANCE/finans.htm
   30: http://www.rocit.ru/au/
   26: http://www.newman.ru/INTERLINE/
   23: http://www.informika.ru/
   23: http://www.vimcom.ru/info.htm
   23: http://www.aha.ru/~price/soft.htm
   21: http://www.sakhalin.ru/
   20: http://omega.fintech.ru:8082/MSSQL/MSSQL.html
   20: http://av.yahoo.de/

Как видите, довольно сильно... Небольшой комментарий - на www.anekdot.ru висела ссылка (не баннер) на каждой странице за деньги. А вот www.machaon.ru - это бесплатные ссылки на материалы моего узла из дайджеста Елены Коротеевой "Почти все об Интернет на русском...". Этот дайджест по реферерам оставляет далеко позади даже отечественные поисковые машины (за исключением Яndex'а)! Видно много запросов с Yahoo!, но в самом каталоге мой узел не прописан - это следствие того, что Yahoo! переправляет запросы, на которые сам не нашел ответа, Альтависте (еще один аргумент в пользу этой системы). http://www.citforum.ints.net/ и http://citforum.telecom.mogilev.by/ - это мои официальные зеркала, http://www.cit-forum.com/ - адрес моего узла, который закрыт уже более полугода (это показатель того, как долго должен стоять редирект).

С Newman.Ru и Финмаркетом мы партнерствуем (в первом случае - обмен "клик за клик", во втором - они нам информацию, мы им - траффик). Каталоги общего назначения отмечены жирным шрифтом, в рефереры более 20 запросов попали всего три: DiamondTeam.Ru, WebList.Ru и Роцитовский "Ау".

Надо сказать, что меня поразил факт первенства совсем недавно образованного ДаймондТимовского каталога, тем более, что его ведет моя сестра:-))

Все остальные ссылки, это либо "интересные места", либо ссылки на разные разделы или материалы моего узла в большинстве своем сделанные по инициативе хозяев удаленных узлов. Я лишь проверял и корректировал в случае необходимости внешний вид ссылок.

Ниже приведены рефереры с телеконференций за одни сутки - 11 апреля 1998 года.

#запр: URL
-----  ---
    1: news://194.135.154.38/6gdrur$ipl$1@serv.
    atiss.donetsk.ua
    1: news://ddt.demos.su/891843778@f433.n5020.z2.ftn
    2: news://news2.demos.su/891843778@f433.n5020.
    z2.ftn
Примечательно то, что такие ссылки работают "всплесками", то есть в один день есть, в другой - нет. К сожалению, я еще не исследовал толком это метод продвижения узлов просто потому, что у меня в данный момент нет доступа к телеконференциям.
Выводы я хотел бы сделать следующие:
  • Чтобы ссылок было больше - узел должен представлять интерес для людей, хотя бы для того, чтобы им показалось уместным оповестить своих пользователей на страницах своего узла.
  • Чем больше ссылок на Ваш узел - тем лучше, что и так понятно. Особенно дороги ссылки с популярных узлов.
  • Всегда проверяйте, как именно на Ваш узел ссылаются (текст ссылки, УРЛ и т.д.)


Поисковые машины
  • Каков рейтинг поисковых машин на практике, какими машинами пользуются больше, какими - меньше?
  • А зачем же поисковые машины для раскрутки сайта?
  • Итак, Вы уже зарегистрировали первую страницу своего сайта в различных поисковых машинах
  • Можно ли управлять действиями поисковых машин?
  • Контроль за текущим состоянием Ваших документов в индексе поисковых машин
  • Основные технические характеристики поисковых машин
  • Чего люди хотят от этой жизни?
  • Дополнительные источники информации на русском языке

Известно, что пользователи, попадающие на сайт из поисковых машин, дают до сорока процентов трафика. Поэтому позаботиться о правильном индексировании Вашего сайта в поисковых машинах весьма полезно. Под "правильным индексированием" я имею в виду, что должна быть соблюдена релевантность запроса и содержания сайта, т.е., говоря простым и доступным языком, содержание сайта должно соответствовать запросу (некоторые "мастера" злоупотребляют наборами не отвечающих реальности ключевых слов. Например, когда моя сестра готовила к выпуску компакт-диск с локальными копиями первых уровней Web-страниц, слово "х#й" и иже с ним встречалось на серверах весьма солидных компаний, ничего общего не имеющих с подобного рода лексикой:-).

  • Altavista
  • Апорт-поиск
  • Медиалингва
  • Рамблер
  • РусИнфОил
  • Русский Экспресс
  • ТЕЛА-поиск
  • HotBot
  • Яndex

Почему я перечислил именно эти поисковые машины? Потому что именно ими, по моим наблюдениям, пользуются русскоязычные нетизены (netizen). Что такое "мои наблюдения"? Это анализ логов доступа к моему серверу http://citforum.ru/, точнее той части логов, гда собирается информация по HTTP_REFERER, т.е. адреса (URLs), на которых клиенты воспользовались ссылкой на какую либо страницу моего сервера.

Каков рейтинг перечисленных мною машин на практике, какими машинами пользуются больше, какими - меньше?

На первом месте с колоссальным отрывом от остальных идет Altavista. Эта поисковая машина лидировала еще до того как там появился поиск по различным языкам (в том числе - по русскоязычным документам). Ну оно и понятно - прекрасный, легкодоступный сервер, давно работает (с начала 1996 года), огромная база документов (свыше 50 миллионов адресов). Следует также учесть, что русскоязычные пользователи находятся не только в России, но и в Болгарии, Чехии и Словакии, в Польше, Израиле, Германии, не говоря уже о бывших республиках СССР - Украина, Белоруссия ... (Особо хочу сказать о прибалтах: это они при встрече на улицах какого-нибудь Каунаса или Таллинна не знают русского языка, а перед монитором, особенно если очень нужно, очень даже знают:-)) Так вот всем этим пользователям удобнее пользоваться Альтавистой, а не нашими отечественными машинами - ближе, все таки...

Следующая по популярности поисковая машина, как ни странно, самая молодая в России - Яndex. Как мне говорил Алекей Амилющенко (компания Comptek) на сегодняшний день там наблюдается в среднем 72000 запросов в суткии и есть тенденция +10% в неделю (данные от 7.04.98). Мне кажется, Яndex наиболее перспективная российская поисковая машина. С комптековской системой разбора "великого и могучего" русского языка Яndex вполне может выйти победителем в конкуренции со вторым китом в этой сфере - Рамблером.

Рамблер - третья серьезная поисковая машина для русскоязычных пользователей. Главное, что мне в ней не нравится, так это игнорирование содержимого конструкции <meta name=keywords content=...>. (Это я не придумал, это сказал Дмитрий Крюков из компании Stack Ltd.) Наверное, именно из-за отказа учитывать ключевые слова, в результатах запросов выдаются такой странный набор ссылок. Второй недостаток чисто интерфейсного характера - результаты постоянно выдаются в кодировке КОИ, независимо от того, что выбрано пользователем перед этим. Третий недостаток: спайдер Рамблера работает по протоколу HTTP 0.9, что приводит к ошибкам индексирования, т.е. если на одном IP-адресе живут несколько виртуальных серверов, Рамблер видит только первый, а все остальные считает просто синонимами. Ну да ладно, будем надеяться, что это вскоре исправят.

Ну и на последнем месте по моему рейтингу идут Апорт-Поиск, который очень странно индексирует сервера, РусИнфОил, который регулярно закрывается на реконструкции и ТЕЛА-Поиск - красивый и почти бесполезный прибамбас для сервера www.dux.ru.

Вы спросите: а в списке были еще HotBot и метапоисковая машина Следопыт компании "Медиалингва"? Я их не забыл, просто HotBot непонятно почему оставляет в моих логах толпу записей, что не может быть случайными залетами не понимающих русского языка иностранцев (с других импортных машин таких залетов гораздо меньше), а "Следопыт" я еще недостаточно серьезно изучил.

А зачем же поисковые машины для раскрутки сайта?

Все очень просто, как я уже говорил, поисковые машины могут дать до сорока процентов трафика к сайту. А чтобы это происходило, необходимо, чтобы Ваш сайт был правильно проиндексирован, а для этого необходимо знать, как это делается.

А делается это следующим образом: либо робот поисковой машины сам добирается до вашего сайта, либо Вы сами указываете сайт в соответствующем интерфейсе (AddUrl), который наличествует в любой уважающей себя поисковой машине. Первый вариант страдает затянутостью (пока еще робот доберется, может через день, может через год: Internet-то большой). Второй - требует затратить некоторое время (разнообразный софт для автоматической регистрации Вашего сайта в туче поисковых машин нам ничего не дает - машины-то импортные).

Чтобы все произошло в лучшем виде требуется:

  • на сайте должет быть хоть какой-нибудь текст. Картинки и тест на них поисковые машины игнорируют. Правда, можно продублировать текст в атрибуте alt тага img
  • В каждом документе сайта ОБЯЗАТЕЛЬНО должны присутствовать осмысленный заголовок (title), ключевые слова (keywords) и краткое описание (description). Это только пишут, что поисковые машины - полнотекстовые, на самом деле это не так.
  • Изготовление файла robots.txt (особенно, если у вас есть собственный сервер типа www.name.ru).
  • Прописка вручную в каждой интересующей Вас поисковой машине и последующий контроль индексирования Вашего сайта.

Итак, Вы уже зарегистрировали первую страницу своего сайта в различных поисковых машинах.

Вы думаете уже все в подрядке? Как бы не так. Если ссылка на Ваш сайт в ответе поисковой машины выводится на втором экране -"это так же плохо, как если бы ссылки вообще не было" (Danny Sullivan, searchenginewatch.com)

Иначе говоря, просто указать страницу в AddURL недостаточно. Необходимо заранее подготовить документ так, чтобы на соответствующие запросы к поисковой машине в ее ответе на запрос ссылка на Ваш документ находилась если не первой, то хотя бы в первой десятке ссылок (а лучше, если в этой десятке было несколько ссылок на Вашы документы:-). Что значит "подготовить"? Это чисто технический вопрос, ничего сверхестественного. Просто в секции HEAD каждого документа Вашего сайта стоит указать "говорящий" Title, KeyWords, Description и Robots.

Title: заголовок документа. Хороший осмысленный заголовок может заставить пользователя из множества других выбрать именно Вашу ссылку. Зачастую видишь примерно следующие заголовки: "Содержание" - чего, зачем - непонятно, не возникает желания проверять. Другой случай: на всех страницах сайта в качестве заголовка - "Добро пожаловать в компанию ..." - тоже не слишком привлекательно проверять все таким образом озаглавленные документы. Представьте себе, что выбран режим поиска по заголовкам, без описания каждого документа.

KeyWords: ключевые слова. Именно содержимое этого контейнера влияет на релевантность документа запросу поиска.

<meta name=keywords content="разделенный запятыми список ключевых слов и устойчивых словосочетаний">

Сколько ни говорят, что поисковые машины - полнотекстовые, это не совсем верно, а вот содержимое этого контейнера точно попадет в индекс поисковой машины. К сожалению, создатели одной из крупнейших отечественных поисковых машин Rambler, не хотят отрабатывать этот контейнер. А зря.

  • в поле content не должно быть знаков конца строки, кавычек и других специальных символов, регистр символов роли не играет
  • не рекомендуется повторять одни и те же ключевые слова по нескольку раз, это может быть воспринято как spam и страница рискует быть удаленной из индекса поисковой машины.
  • не стоит делать одинаковые keywords для разных страниц Вашего сайта. Это, конечно проще, но содержимое самих документов различное. Если очень хочется автоматизировать этот процесс, можно написать программку, которая прописывала бы в это поле все выделенные блоки документа, например, то что стоит между тагами H, I и B.
  • если строка в content слишком длинная, не возбраняется сделать еще несколько аналогичных конструкций.
  • вообще говоря, общий объем ключевых слов одного документа может достигать до 50% объема этого документа.

Description: краткое описание документа. Довольно полезный контейер, его содержимое используется как краткое описание релевантных документов в ответе современных поисковых машин. Если этого контейнера нет, то выдаются некоторое количество строк с начала документа. Соответственно, не редкая катина, когда в самом начале документа расположен JavaScript, и вместо нормального описания выдается абракадабра в виде куска скрипта.

<meta name=description content="краткая аннотация документа">

  • в поле content не должно быть знаков конца строки, кавычек и других специальных символов.
  • желательно, чтобы здесь была осмысленная аннотация документа из пары-тройки человеческих предложений, чтобы пользователь поисковой машины помимо заголовка смог понять смысл документа.
  • к сожалению, отечественные поисковые машины пока не умеют работать с этим контейнером, хотя божатся, что в скором времени научатся.

Можно ли управлять действиями поисковых машин?

Можно, и даже нужно! Первое действие, которое для этого нужно совершить, это написать файл robots.txt и положить его в корень Вашего сервера. Этот файл популярно объясняет роботу поисковой машины что надо индексировать, а что не стоит. Например, зачем индексировать служебные файлы, типа статистических отчетов? Или результаты работы скриптов? Более того, многие "умные" машины просто не станут индексировать сервера, не найдя robots.txt. Кстати, в этом файле можно указать разные маски индексирования для разных поисковых машин.

Подробнее об этом можно прочитать в моем переводе "Standard for Robots Exclusion". Второе действие: снабдить страницы сайта МЕТА-тагами Robots. Это более гибкое средство управления индексацией, чем robots.txt. В частности, в этом таге можно предписать роботу поисковой машины не уходить по ссылкам на чужие сервера, например, в документах со списками ссылок. Формат этого безобразия таков:

<META NAME="ROBOTS" CONTENT="robot_terms">

robot_terms - это разделенный запятыми список следующих ключевых слов (заглавные или строчные символы роли не играют): ALL, NONE, INDEX, NOINDEX, FOLLOW, NOFOLLOW.

NONE
говорит всем роботам игнорировать эту страницу при индексации (эквивалентно одновременному использованию ключевых слов NOINDEX, NOFOLLOW).
ALL
разрешает индексировать эту страницу и все ссылки из нее (эквивалентно одновременному использованию ключевых слов INDEX, FOLLOW).
INDEX
разрешает индексировать эту страницу
NOINDEX
неразрешает индексировать эту страницу
FOLLOW
разрешает индексировать все ссылки из этой страницы
NOFOLLOW
неразрешает индексировать ссылки из этой страницы

Если этот мета-таг пропущен или не указаны robot_terms, то по умолчанию поисковый робот поступает как если бы были указаны robot_terms=INDEX, FOLLOW (т.е. ALL). Если в CONTENT обнаружено ключевое слово ALL, то робот поступает соответственно, игнорируя возможно указанные другие ключевые слова.. Если в CONTENT имеются противоположные по смыслу ключевые слова, например, FOLLOW, NOFOLLOW, то робот поступает по своему усмотрению (в этом случае FOLLOW).

Если robot_terms содержит только NOINDEX, то ссылки с этой страницы не индексируются. Если robot_terms содержит только NOFOLLOW, то страница индексируется, а ссылки, соответственно, игнорируются.

Контроль за текущим состоянием Ваших документов в индексе поисковых машин.

Ну хорошо, Вы прочитали все, что было выше и так и сделали. Что же дальше? А дальше будет долгая, нудная и, главное, регулярная проверка на предмет того, как обстоят дела. Как ни печально, а придется уделять этому внимание хотя бы потому, что документы временами пропадают из поисковых машин. Почему? Знать бы... Итак, в хороших поисковых машинах можно посмотреть какие документы и сколько их в текущее время находится в индексе. Вот как это делается:

Alta Vista
В этой поисковой машине проверку статуса URL осуществить довольно просто - достаточно набрать в строке запроса:

url: citforum.ru
url:citforum.ru/win/
url:citforum.ru/win/internet/index.shtml

В первом случае будут выданы все проиндексированные страницы сервера. Во втором - только страницы Windows-кодировки. В третьем - есть ли в индексе AltaVista файл index.shtml из указанной директории

Excite
Так же просто как и в AltaVista проверяется статус URL в поисковой машине Excite. Достаточно набрать URL. Например:

http://citforum.ru/win/database/articles/art_1.shtml

HotBot
Несколько по-другому проверяется статус URL в поисковой машине HotBot. Это делается так:

  • Введите URL в поле запроса
  • Измените опцию "all of the words" на "links to this URL"

Infoseek
В поисковой машине Infoseek для проверки статуса URL существует отдельный интерфейс с целым набором настроек:

http://www.infoseek.com/Forms?&sv=IS&lk=noframes&pg=special.html

WebCrawler
WebCrawler предоставляет возможность проверить статус URL на странице:

http://www.webcrawler.com/WebCrawler/Status.html

Rambler
 В этой поисковой машине статус URL можно проверить двумя способами.

  • В разделе "Расширеный поиск" путем указания имени сервера в качестве маски в одной из опций
  • Можно набрать $URL:www.citforum.ru в обычном поле запроса

Aport
 Для проверки статуса URL в этой поисковой машине есть специальный запрос url=www.citforum.ru/*

Основные технические характеристики поисковых машин

  • Импортные поисковые машины
  • Отечественные поисковые машины

Чего люди хотят от этой жизни?

  • 100 самых популярных серверов и 100 самых популярных слов на Апорт-Поиске
  • Верхние 100 слов на Rambler

Дополнительные источники информации на русском языке

  • Раздел "Поиск информации" на моем сервере http://citforum.ru
  • Help-страница Апорт-поиска
  • Help-страница и FAQ Rambler
  • Советы Web-масетру от поисковой машины Яndex


Как включить мультимедию в свою страницу? | WebClub

Как включить мультимедию в свою страницу?
Вы видели, скорее всего, на других страницах фоновую музыку или просто архив музыки, песен и даже видео. И, наверное, себе тоже хотелось сделать, что-то похожее. Особенно хорошо иметь всякую такую мультимедию на развлекательных страницах.
Начнём со вставки звука и видео на страницу.

Вставка звука и видео на страницу

Это производится следующим тэгом...

<EMBED src="Vashfail.mid" width="47"
height="15" controls="smallconsole" autostart="false" loop="true" play_loop="2"
hidden="true"></EMBDED>

Теперь рассмотрим всё по порядку.
<EMBED src="Vashfail.mid" - вместо Vashfail.mid надо вставить Ваш MIDI (*.mid), WAV (*.wav) или AVI (*.avi - видео) файл.
width="47" height="15" - здесь вместо моих цифр укажите ширину (width) и высоту (heigth) пульта управления
controls="smallconsole" - при использовании этого атрибута пульт становится маленьким
autostart="false" - надо ли проигрывать снова Ваш видео или музыкальный ролик (false - нет, true - да)
loop="true" - повторять ролик с самого начала (false - нет, true - да)
play_loop="3" - если да, то сколько раз (вместо 3 поставьте кол-во раз)
hidden="true" - спрятать пульт ролика (false - нет, true - да)

Но можно, и даже лучше ставить просто ссылку на видео файл, так как броузер сам запускает плагины для их воспроизведения. И отпадает нужность во вставке выше перечисленого HTML кода. Ссылку можно поставить так:

<A HREF="VashVideoFail.avi">Мой видео файл</A>

А если файл находится не на Вашей странице, то нужно прописать весь адрес файла

<A HREF="http://www.domain.ru/~URL/VashVideoFail.avi">Мой видео файл</A>

Делаем архив RealAudio и RealVideo

Для тех, кто не знает, что такое RealAudio и RealVideo рассказываю, что это такая программа, которая может воспроизводить музыку в online или файлы с музыкой или видео в Real формате.

А делается это очень легко. В начале надо скачать программу с сайта.

Сначала открываете программу RealEncoder (до этого надо её установить;), заполняете там строки Title (Название), Author (Автор), Copyright (Права на это принадлежат...). Потом выбираете справа качество записи (лучше брать Audio 28.800 Voice для аудио файла и Video 28.800 High Action для видео файла, но можно и другие). Затем открываете в меню Fail => Open session. Там нужно выбрать File и нажать кнопку Add и выбираете свой аудио или видео файл. Затем нажимаете кнопку "Save as..." и сохраняете его под любым именем. Выполнив всё это, нажимаете "OK" и внизу загораются кнопка Start и Вы нажимаете её. Вот Вы и сделали Real файл. Теперь осталось разместить его на своей странице, сделав просто ссылку на файл.
А! Совсем забыл расскать о том, что можно сделать так, чтобы Вашу музыку слушали люди в ONLINE режиме. Нужно взять любой текстовый редактор ( Блокнот для Windows, к примеру ) и написать адрес Вашего Real файла в Интернете, но заменив http на pnm. И получится что-то похожее на это:

pnm://www.domain.com/~vasya/arhiv/3pesnya.ra
И сохранить в формате *.ram. Ну и надо теперь просто дать сслыку на ваш ram файл и он будет брать Ваш файл с музыкой или видео и показывать его через RealPlayer.

Если же Вас не устраивает такое "статичное" включение звука в свою страницу, воспользуйтесь советами, приводимыми вот тут.


Детская. Мультимедиа

Multimedia на Вашей странице
  • Так можно заставить фоновый звук звучать как в IE, так и в NN: <bgsound src="yoursound.wav">
    <embed src="yoursound.wav" width=0 height=2 volume=50 autostart=true loop=yes>
     Можете почитать об этом подробнее.
  • Для тех, кто не знает, что такое RealAudio и RealVideo: это такая программа, которая может воспроизводить музыку в online или файлы с музыкой или видео в Real формате. А делается это очень легко.


!!! Экспресс-советы. Озвучивание странички.

Озвучивание страниц

1. Простейший способ озвучивания - музыка сопровождения.
2. Встраивание элемента проигрывания звуковых файлов.
3. Два способа озвучить события на страничке для Интернет Эксплорера.
4. Подготовка звуковых файлов.
5. Коллекция супермаленьких MIDI - файлов.
6.Несколько важных замечаний.

Да, да! Наконец-то Вы нашли то, что так долго искали - Ваша страничка излечится от унылой немоты и запоет, заиграет, отвечая на нажатия кнопок или движение мыши. Вот оно! :O)

1.

Простейший способ озвучить страничку - добавить в тело создаваемого документа тэг вида:

<BGSOUND LOOP="1" SRC="file.mid"></BGSOUND>

Тэг имеет всего два атрибута: LOOP задает число повторений проигрывания пьесы, (LOOP=0 позволяет мелодии звучать постоянно).SRC - файл-источник.

Подобные элементы при хорошем подборе музыкального файла могут создавать оригинальную атмосферу сайта, но нужно учитывать разницу в звуковых картах, применяемых посетителями, и предварительно прослушать, как выбранная пьеса звучит "в исполнении" как можно большего количества карт. Второй совет - при озвучивании событий - избегать музыки заднего плана - взаимодействие проигрывателей и загрузка страницы не всегда проходят гладко.

2.

Второй способ немногим сложнее, а для посетителя зачастую удобнее.
Встроим в страничку внешний проигрыватель

Элементы эти удобны по двум причинам : во-первых, сам посетитель странички определяет, прослушивать ли ему Вашу пьесу или нет, и во-вторых, элементы предоставляют возможность задавать их размеры, и, при необходимости, даже полностью скрывать, делать невидимыми.
Тэг, задающий элемент проигрывателя, имеет вид:

<EMBED SRC="melodie.mid" name="sndplay" AUTOSTART="true" WIDTH="300" HEIGHT="40" ></EMBED>

Естественно, атрибуты WIDTH и HEIGHT отвечают за размеры представления элемента на странице, SRC указывает на файл-источник, а свойство AUTOSTART="true" дает проигрывателю команду исполнить пьесу сразу после загрузки файла, делая таким образом музыку заднего плана подконтрольной посетителю странички.

3.

Подавляющее большинство строителей страничек ограничивается указанными способами озвучки своих сайтов.

Но это все - решения статические, дизайнеры давно привыкли к анимированным кнопкам, динамически меняющимся надписям, мультипликации. Посетитель, бродя по странице, то и дело кликает, нажимает, наводит мышку. Как заставить страничку реагировать на его действия звуком?

Обладающие большими претензиями дизайнеры кидаются в омут самодельных и приобретенных джава апплетов, среди которых можно отметить мощный инструмент для создания анимированных кнопок Cool Button разных версий..

Наконец, быстрыми темпами растет армия поклонников технологии Flash - действительно, в скором времени обещающей сделать сеть говорящей и двигающейся.

Существуют ли решения, позволяющие без лишней мороки с настройкой, а главное, - при МИНИМУМЕ объема озвучить простейшие события на страничке?

К счастью, такие решения возможны.

Существует два довольно простых способа озвучить действие посетителя на своей страничке :

  • используя встраивание объекта - ActiveX элемента проигрывателя звуковых файлов
  • Используя хитрость HTML - фреймы и создание документа "на лету"

Первый способ реализуется следующим образом: В тело создаваемой странички вводится следующий код :

<EMBED SRC="..." name="sndplay" WIDTH="1" HEIGHT="1">

Где вместо троеточия подставить название midi-файла. (о подготовке таких файлов - ниже, равно как и библиотека подходящего материала). Тэг этот создает невидимый для пользователя элемент проигрывания Вашего файла, и задача создателя странички заключается в том, чтобы заставить файл проигрываться "в ответ" на некоторые события на страничке.

Управление элементом осуществляется с помощью Джава скриптов.

С методами и свойствами встроенного нами элемента Вы можете ознакомиться на страничке фирмы Майкрософт, ему посвященной.
ActiveMovie ActiveX Control

Приведем пример. В простейшем случае необходимо проиграть щелчок в момент клика посетителя на кнопке.

Создадим стандартную кнопку, вставив в страничку код:

<INPUT TYPE="submit" name="but1" style="background-color:white;font-family:monospace " VALUE="Нажми меня!">

Нажали? Молчит, да? Давайте озвучим. Вот, вставили мы в страничку элемент проигрывателя. Назвали его "sndplay".
Обратимся к нему из джава-скрипта:

<script language="JavaScript">
<!--
function playsound()
{
document.sndplay.run();
}
--></script>

Скопируем скрипт в страничку и вставим в тэг, задающий кнопку, команду
ответить на клик на кнопке исполнением нашего джава-скрипта:

<INPUT TYPE="submit" name="but1"
style="background-color:white;font-family:monospace " VALUE="Нажми
меня!"ONCLICK="playsound();">

Ага, у меня заработало...

Немного вдогонку - атрибут STYLE в тегах позволяет броузерам, использующим технологию CSS, задавать многие ценные свойства элементов оформления, в том числе цвет, размер,шрифт и положение на страничке.

Если страничка черная, спокойно меняйте цвет кнопки на черный, а шрифта - на белый или сероватый.

Атрибут STYLE имеет при этом вид

style="background-color:black;color:#C0C0C0;font-weight:bold;font-family:monospace "

Такой способ озвучивания хорошо сочетается с анимированными скриптовыми кнопками.

Создадим две маленьких картинки, изображающих ненажатую и нажатую кнопки, but1.gif и but2.gif:
вcтавим первую кнопку в страничку, заполнив следующий тег:

<IMG SRC="img/but1.gif" name="img1" WIDTH="60" HEIGHT="50" BORDER="0" ALT="ЖМИ!" ONMOUSEOUT="butup();" ONMOUSEOVER="butdown();">

и напишем простенький скрипт

<script language="JavaScript">
<!--
im1
= new Image; im1.src="img/but1.gif";
im2= new Image;
im2.src="img/but2.gif";
function butdown()
{
img1.src="img/but2.gif";
document.sndplay.run();
}
function
butup()
{
img1.src="img/but1.gif";
}
--></script>

Второй способ озвучить действия на страничке нетривиален и остроумен, но с удобством применим только к броузерам, поддерживающим "плавающие окна". Автору пришлось придумывать все с нуля. Для начала сформировалась сама идея реализации. Она заключалась примерно в этом: При наведении на ссылку срабатывает событие onMouseOver на JavaScript которое запускает некую процедуру или функцию. Та же в свое время, должна проиграть щелчок. Легко сказать проиграть, а как вот? Ну сразу оставив версию реализации этой фишки на Нетскейпе (кстати когда Вы прочтете этот текст, то Вы сможете свободно сделать это и для Нетскейпа, надо будет только использовать EMBED и обычный фрейм. Но не будем торопить события!), я занялся воплощением ее для MSIE.

Как заставить броузер Микрософта проиграть какой-нибудь музыкальный файл? - вставить тег <BGSOUND src="pesnia.mid" loop=1>, или pesnia.wav в зависимости от формата звукового файла. Вроде бы начали появляться первые отсветы в глубине туннеля :о) Поэтому подведем первые итоги. В тег ссылки вставляем событие onMouseOver примерно так:
<A href="url.html" onMouseOver="sound()">Ссылка</A>
И в начале документа html пишем скрипт, который бы выводил куда-нибудь тот BGSOUND.

Как всегда, руководствуясь принципом: попытка не пытка, написал такой скрипт:
<script language="JavaScript"><!--
function sound(){
document.write("<BGSOUND src=pesnia.mid loop=1>");}
--></script>

Божешь мой :-О, что получилось. Броузер правда проиграл музыку в MIDI формате, но он при этом показал чистую белую страницу созданную document.write и завис до окончания проигрыша песенки в midi формате. Приободренный тем, что он хоть играет песенку, я стал думать: а куда бы сгенерировать этот злосчастный BGSOUND, чтобы при этом остаться на нужной нам странице.

Та же форма, или еще какой объект не подходили, надо ведь генерируемый тег вставлять непосредственно в код страницы. А если не в нашу страницу, а в другую... Которая бы тоже видна была... О это идея. Но единственный способ показать на экране не одну страницу, а две, является использование фреймов. Нда, а если их нет на странице, а если пользователь зайдет на страницу минуя главную и второй фрейм где прописывается бексаунд? И что тогда делать, ведь это приведет к ошибке в скрипте:о(

И тут я осознал, что идею то я воплощаю для MSIE пользователей, и в нем можно использовать не обычные, а плавающие фреймы встраиваемые в документ! Ваууу... разнеслось по моей комнате, как в древние времена разнеслось слово Эврика воскликнутое Архимедом. Правда в моем случае, мой кот, до этого мирно дремавший на кресле, почему-то вторил мне громогласным МЯУ! Поняв что зря так раскричался, я отправился варить рыбу этому рыжему созданию, но мысли мои витали в дебрях Ява скриптов и хтмл тегов. Придя вскоре назад к своему адскому ящику, и первым заняв кресло (хоть какая-то выгода :о), я окончательно создал следующий скрипт:

<script
language="JavaScript"><!--
var brous;
function bsound(){
brous = navigator.appName;
if (brous.substring(0,1)=="M")
top.blank.document.write("<BGSOUND src=pesnia.mid
loop=1>");}
--></script>

Здесь есть защита от генерации тега в нетскейпе, который не увидит плавающий фрейм, и не будет пытаться писать BGSOUND в несуществующий объект. blank - это так обозван этот самый фрейм, который я в свою очередь сжал до нулевых размеров и поместил сразу после тега BODY вот так:

<IFRAME FRAMEBORDER=0 HEIGHT=1 WIDTH=1
NAME="blank" SCROLLING=NO></IFRAME>

Как видите, здесь нет обычного свойства SRC=url.html указывающего, какую страницу надо показывать внутри плавающего фрейма. Поэтому он ничего не грузит в себя. И при наведении мышом на ссылку с onMouseOver="sound()" он воспроизводил музыку, генерировав документ с фоновым звуком в этом невидимом фрейме! Осталось дело за малым, сделать этот самый щелчок.

Сразу отказавшись от формата Wav из-за того, что файл звука щелчка, возможно, будет по объему больше самой страницы, я остановился на midi. И еще у меня были подозрения насчет того, а станет ли броузер вынимать звуковой файл из кеша, или грузить каждый раз заново. Кто знает эту продукцию микрософта. Поэтому щелчок мной стал делаться в midi формате. Нда, что только не приходится делать вебмастеру, и рисовать, и программировать, и верстать, и статьи придумывать, ну вот и музыку сочинять :о)

Благо от меня требовалось сочинения самого короткого произведения в виде звука "щелк". Запустив Cakewalk, я убрал все возможные комментарии внутри midi файла, чтобы уменьшить до минимума размер файла, поставил на первом треке 10 midi канал, который отвечает за ударные, я в опции piano roll написал две короткие нотки отвечающие за тихий щелчок, и маленький "дзиньк". Больше ничего не регулировал, ни звук, ни разнос по колонкам, дабы не увеличить размер файла. В итоге творение pesnia.mid получилось величиной 60 байт. В заключение, я в cамое начало документа, как можно ближе к скрипту, поставил <BGSOUND src="pesnia.mid" loop=1>, дабы звук уже был загружен до наведения на ссылку.
Вот и все!

Замечу, что подобное решение для Нетскейпа вынуждает использовать фреймы, пусть даже нулевой толщины, и затруднит посетителю работу с документом. Веб-мастеру также придется подумать над вопросом своевременной загрузки звуковых файлов. Впрочем, метод славно работает. Рекомендую. :))

Само собою, озвучиванию можно подвергнуть любые события на странице - движения мышью, клики на ссылках, наведение мыши на чувствительную область и прочее.

4.

Перейдем к обсуждению самого материала - файлов со звуками. Из трех основных форматов звука, с которым работает сеть - Real Audio, Wave, и MIDI форматов - предпочтительнее всего работа с миди-файлами. Проигрывая в "жизненности" звучания и широте выбора, требуя "прогонки" на разных звуковых картах, они обладают поистине бесценным для Сети свойством - МАЛЫМ РАЗМЕРОМ.

Для работы нам потребуется какой-либо редактор, умеющий работать с этим форматом, и в то же время не переполненный музыкальными "наворотами" и "удобствами". Я порекомендовал бы Cakewalk Express или Cakewalk Pro. Продукт этот можно скачать по FTP, разыскав по ключевому слову в поисковой системе, например, в этой Окно программы выглядит примерно так:

Кликните в рабочей области правой клавишей мышки и вызовите во всплывшем меню Piano Roll. В нашем случае удобнее работать с ним. Проставить ноту определенной высоты можно в этом окне "карандашиком", а затем, "подцепив" мышью за край цветного квадратика, установить длительность звучания, или "перетащить" ноту вверх или вниз по высоте звука. Все очень наглядно. Инструмент можно выбрать, кликнув на колонке "PATCH" в главной таблице. Всплывет окошко выбора инструмента, в котором будет выведен список возможных на Вашей карте тембров звучания. Поэкспериментируйте.

Когда звук найден, не торопитесь его сейвить. В меню FILE нажмите кнопку INFO и аккуратно сотрите ВСЕ комментарии во всплывшем окне. Нажмите OK и спасите файл. Проверив впоследствии его свойства, Вы с несказанной радостью убедитесь, что занимает он считанные БАЙТЫ.

5.
В конце концов, рыться в архивах и учиться работать в новом редакторе довольно нудно, поэтому я заготовил небольшой архив файликов со звуками, которые могут пригодиться в оформлении страницы. Они представляют из себя полные октавы и исполнены на разных инструментах. Файлики разложены по зипам в зависимости от инструмента.

Можете распаковать и попробовать следующие звуки:

ГОБОЙ
Распаковать
СТРУННЫЕ
Распаковать
ВИБРАФОН
Распаковать
ТЕЛЕФОННЫЙ ЗВОНОК
Распаковать
КОЛОКОЛЬЧИК
Распаковать
СИТАР
Распаковать
МЕТ. БАРАБАН
Распаковать

6.

... Напоследок несколько важных замечаний:

1) В стиле "великих" производителей глючного софта я не гарантирую успешной работы приведенных примеров - это, увы, непосильная задача, а также нагло отказываюсь нести ответственность за всякого рода потери, неприятности и висы, могущие случиться с Вашей техникой в процессе обучения и экспериментирования с предложенными вариантами озвучки.

2) При разбивке странички, обратите внимание на возможные глюки, происходящие со встраиваемыми элементами. То и дело выползающий из невидимости элемент проигрывателя под минипианино в моей страничке - пример такой неприятной особенности.

3) При подготовке файлов миди НЕ ВСТАВЛЯЙТЕ НОТЫ без некоторого ЗАЗОРА от начала. Это придаст некоторую задержку звуку, но избавит Вас от изумленного негодования по поводу необъяснимого поведения обсуждаемых ActiveX элементов. Помните : небольшая (1/8 такта) задержка!

4) По мере возможности, производите в скриптах проверку версии и названия броузера, многие обсуждаемые вещи проходят на одних броузерах и не проходят на других. В условиях войны "гигантов" рынка бедным разработчикам приходится то и дело удваивать количество кода или даже страниц.

Возможный текст скрипта, отслеживающий версию броузера приведу ниже:

<SCRIPT LANGUAGE="JavaScript">
<!--//
browser_name = navigator.appName;
browser_version = parseFloat(navigator.appVersion);
if (browser_name == "Netscape" && browser_version >= 3.0) { roll =
'true'; }
else if (browser_name == "Microsoft Internet Explorer" &&
browser_version >= 3.0) { roll = 'true'; }
else { roll = 'false'; }
-->
</SCRIPT>

И дальнейшие функции начинаются с конструкции if(roll=="true")...


Календарь
<html>
<head>
</head>
<body>
<script language="JavaScript">
<!--
// Copyright (c) 1996-1997 Tomer Shiran. All rights reserved.
// Permission given to use the script provided that this notice remains as is.
// Additional scripts can be found at http://www.geocities.com/~yehuda/

setCal()

function getTime() {
        // initialize time-related variables with current time settings
        var now = new Date()
        var hour = now.getHours()
        var minute = now.getMinutes()
        now = null
        var ampm = ""

        // validate hour values and set value of ampm
        if (hour >= 12) {
                hour -= 12
                ampm = "PM"
        } else
                ampm = "AM"
        hour = (hour == 0) ? 12 : hour

        // add zero digit to a one digit minute
        if (minute < 10)
                minute = "0" + minute // do not parse this number!

        // return time string
        return hour + ":" + minute + " " + ampm
}

function leapYear(year) {
        if (year % 4 == 0) // basic rule
                return true // is leap year
        /* else */ // else not needed when statement is "return"
                return false // is not leap year
}

function getDays(month, year) {
        // create array to hold number of days in each month
        var ar = new Array(12)
        ar[0] = 31 // January
        ar[1] = (leapYear(year)) ? 29 : 28 // February
        ar[2] = 31 // March
        ar[3] = 30 // April
        ar[4] = 31 // May
        ar[5] = 30 // June
        ar[6] = 31 // July
        ar[7] = 31 // August
        ar[8] = 30 // September
        ar[9] = 31 // October
        ar[10] = 30 // November
        ar[11] = 31 // December

        // return number of days in the specified month (parameter)
        return ar[month]
}

function getMonthName(month) {
        // create array to hold name of each month
        var ar = new Array(12)
        ar[0] = "January"
        ar[1] = "February"
        ar[2] = "March"
        ar[3] = "April"
        ar[4] = "May"
        ar[5] = "June"
        ar[6] = "July"
        ar[7] = "August"
        ar[8] = "September"
        ar[9] = "October"
        ar[10] = "November"
        ar[11] = "December"

        // return name of specified month (parameter)
        return ar[month]
}

function setCal() {
        // standard time attributes
        var now = new Date()
        var year = now.getYear()
        var month = now.getMonth()
        var monthName = getMonthName(month)
        var date = now.getDate()
        now = null

        // create instance of first day of month, and extract the day on which it occurs
        var firstDayInstance = new Date(year, month, 1)
        var firstDay = firstDayInstance.getDay()
        firstDayInstance = null

        // number of days in current month
        var days = getDays(month, year)

        // call function to draw calendar
        drawCal(firstDay + 1, days, date, monthName, 1900 + year)
}

function drawCal(firstDay, lastDate, date, monthName, year) {
        // constant table settings
        var headerHeight = 15 // height of the table's header cell
        var border = 1 // 3D height of table's border
        var cellspacing = 1 // width of table's border
        var headerColor = "midnightblue" // color of table's header
        var headerSize = "1" // size of tables header font
        var colWidth = 30 // width of columns in table
        var dayCellHeight = 10 // height of cells containing days of the week
        var dayColor = "darkblue" // color of font representing week days
        var cellHeight = 20 // height of cells representing dates in the calendar
        var todayColor = "darkred" // color specifying today's date in the calendar
        var timeColor = "blue" // color of font representing current time

        // create basic table structure
        var text = "" // initialize accumulative variable to empty string
        text += '<TABLE BORDER=' + border + ' CELLSPACING=' + cellspacing + '>' // table settings
        text +=         '<TH COLSPAN=7 HEIGHT=' + headerHeight + '>' // create table header cell
        text +=                 '<FONT COLOR="' + headerColor + '" SIZE=' + headerSize + '>' // set font for table header
        text +=                         monthName + ' ' + year
        text +=                 '</FONT>' // close table header's font settings
        text +=         '</TH>' // close header cell

        // variables to hold constant settings
        var openCol = '<TD WIDTH=' + colWidth + ' HEIGHT=' + dayCellHeight + '>'
        openCol += '<FONT COLOR="' + dayColor + '">'
        var closeCol = '</FONT></TD>'

        // create array of abbreviated day names
        var weekDay = new Array(7)
        weekDay[0] = "Sun"
        weekDay[1] = "Mon"
        weekDay[2] = "Tues"
        weekDay[3] = "Wed"
        weekDay[4] = "Thu"
        weekDay[5] = "Fri"
        weekDay[6] = "Sat"

        // create first row of table to set column width and specify week day
        text += '<TR ALIGN="center" VALIGN="center">'
        for (var dayNum = 0; dayNum < 7; ++dayNum) {
                text += openCol + weekDay[dayNum] + closeCol
        }
        text += '</TR>'

        // declaration and initialization of two variables to help with tables
        var digit = 1
        var curCell = 1

        for (var row = 1; row <= Math.ceil((lastDate + firstDay - 1) / 7); ++row) {
                text += '<TR ALIGN="right" VALIGN="top">'
                for (var col = 1; col <= 7; ++col) {
                        if (digit > lastDate)
                                break
                        if (curCell < firstDay) {
                                text += '<TD></TD>'
                                curCell++
                        } else {
                                if (digit == date) { // current cell represent today's date
                                        text += '<TD HEIGHT=' + cellHeight + '>'
                                        text += '<FONT COLOR="' + todayColor + '" SIZE="-1">'
                                        text += digit
                                        text += '</FONT>'
// You can uncomment these lines if you want current time in today's box
//                                      text += '<BR>'
//                                      text += '<FONT COLOR="' + timeColor + '" SIZE="-1">'
//                                      text += '<CENTER>' + getTime() + '</CENTER>'
//                                      text += '</FONT>'
                                        text += '</TD>'
                                } else
                                        text += '<TD HEIGHT=' + cellHeight + '><font size="-1">' + digit + '</font></TD>'
                                digit++
                        }
                }
                text += '</TR>'
        }

        // close all basic table tags
        text += '</TABLE>'

        // print accumulative HTML string
        document.write(text)
}
// -->
</script>
</body>
</html>


Часы
<html>
<head>
<script language="JavaScript">
var stnr="";
var ns="0123456789";
var a="";
var isn=new Array();  // Netscape 3.0 specific call
for (var i=0;i<10;i++)
{
 isn[i]=new Image();  // Netscape 3.0 specific call
 isn[i].src="nr/"+i+".gif";
}

function dynTim()
{
 var now=new Date();
 var hrs=now.getHours();
 var min=now.getMinutes();
 clckh=""+((hrs<10)?"0":"")+hrs;   // format to 2 digits
 clckh+=((min<10)?"0":"")+min; // format to 2 digits
 for (var i=0;i
</head>
<body>
<img src="nr/0.gif" name="t1">
<img src="nr/0.gif" name="t2">
<img src="nr/c.gif">
<img src="nr/0.gif" name="t3">
<img src="nr/0.gif" name="t4">
<script language="JavaScript">
<!--Hide JavaScript from Java-Impaired Browsers
dynTim();
// End Hiding -->
</script>
</body>
</html>


Часы в текстовом поле. Вариант 1.
<script language="JavaScript">
function fulltime()
{
var time=new Date();
document.clock.full.value=time.toLocaleString();
setTimeout(" fulltime()",500) }
</script>
<center>
<form name=clock>
<input type=text size=17 name=full></form>
<script language="JavaScript">fulltime();</script>
</center>


Часы в текстовом поле. Вариант 2.
<BODY onload="showTime()">
<script language="JavaScript">
<!--
function showTime() {
var timeNow = new Date();
var hours = timeNow.getHours();
var minutes = timeNow.getMinutes();
var seconds = timeNow.getSeconds();
var timeString = "" + ((hours > 12)  hours - 12 : hours);
timeString += ((minutes < 10)  ":0" : ":") + minutes;
timeString += ((seconds < 10)  ":0" : ":") + seconds;
timeString += (hours >= 12)  " p.m." : " a.m.";
document.htmlClock.timeField.value = timeString;
timerID = setTimeout("showTime()", 1000);
}
//-->

<FORM name="htmlClock">
<INPUT type="text" name="timeField" size=11></FORM>


Смена фона окна
<html>
<head>
<script language="JavaScript">
locnm=location.href;
locnm1=locnm.substring(0,(locnm.indexOf
("index.html")));

function getCol() {
 coord=locnm.substring(posx+3,locnm.length);
 posx=coord.indexOf("&")
 xcoord=coord.substring(0,posx);
 coord=coord.substring(posx+1,coord.length);
 posx=coord.indexOf(".y=")
 ycoord=coord.substring(posx+3,coord.length);
 if (xcoord<8){color="#FFFF00";}
 else if (xcoord<16){color="#00FF00";}
 else if (xcoord<24){color="#00FFFF";}
 else if (xcoord<32){color="#FF00FF";}
 else if (xcoord<40){color="#FF0000";}
 else if (xcoord<48){color="#009FDD";}
 else if (xcoord<56){color="#B2B2B2";}
 else if (xcoord<64){color="#FFD25D";}
 else if (xcoord<72){color="#56AB6B";}
 else if (xcoord<80){color="#C70065";}
 else if (xcoord<88){color="#A25C92";}
 else if (xcoord<96){color="#E36970";}
 else if (xcoord<104){color="#C79E6B";}
 else if (xcoord<112){color="#90BE00";}
 else if (xcoord<120){color="#E16A96";}
 else color="white";
 }
</script>
</head>
<script language="JavaScript">
<!-- Hide JavaScript from Java-Impaired Browsers
posx=locnm.indexOf(".x=");
if (posx>0) {
 getCol();
 }
else {
 color="white";
 }
document.bgColor=color;
// End Hiding -->
</script>
<form name='isnform' method=get action='index.html'>
Не нравится наш цвет?<br>
<input type='image' src='g/scb1.gif' name='color'
border=0 height=8 width=128><br>
Нажмите на нужный цвет, чтобы сменить его!
</form>
</body>
</html>


Счетчик посещений
<html>
<head>
<script language="JavaScript">
// Boolean variable specified if alert should be displayed if cookie exceeds 4KB
var caution = false

// name - name of the cookie
// value - value of the cookie
// [expires] - expiration date of the cookie (defaults to end of current session)
// [path] - path for which the cookie is valid (defaults to path of calling document)
// [domain] - domain for which the cookie is valid (defaults to domain of calling document)
// [secure] - Boolean value indicating if the cookie transmission requires a secure transmission
// * an argument defaults when it is assigned null as a placeholder
// * a null placeholder is not required for trailing omitted arguments
function setCookie(name, value, expires, path, domain, secure) {
        var curCookie = name + "=" + escape(value) +
                ((expires) ? "; expires=" + expires.toGMTString() : "") +
                ((path) ? "; path=" + path : "") +
                ((domain) ? "; domain=" + domain : "") +
                ((secure) ? "; secure" : "")
        if (!caution || (name + "=" + escape(value)).length <= 4000)
                document.cookie = curCookie
        else
                if (confirm("Cookie exceeds 4KB and will be cut!"))
                        document.cookie = curCookie
}

// name - name of the desired cookie
// * return string containing value of specified cookie or null if cookie does not exist
function getCookie(name) {
        var prefix = name + "="
        var cookieStartIndex = document.cookie.indexOf(prefix)
        if (cookieStartIndex == -1)
                return null
        var cookieEndIndex = document.cookie.indexOf(";", cookieStartIndex + prefix.length)
        if (cookieEndIndex == -1)
                cookieEndIndex = document.cookie.length
        return unescape(document.cookie.substring(cookieStartIndex + prefix.length, cookieEndIndex))
}

// name - name of the cookie
// [path] - path of the cookie (must be same as path used to create cookie)
// [domain] - domain of the cookie (must be same as domain used to create cookie)
// * path and domain default if assigned null or omitted if no explicit argument proceeds
function deleteCookie(name, path, domain) {
        if (getCookie(name)) {
                document.cookie = name + "=" +
                ((path) ? "; path=" + path : "") +
                ((domain) ? "; domain=" + domain : "") +
                "; expires=Thu, 01-Jan-70 00:00:01 GMT"
        }
}

// date - any instance of the Date object
// * you should hand all instances of the Date object to this function for "repairs"
// * this function is taken from Chapter 14, "Time and Date in JavaScript", in "Learn Advanced JavaScript Programming"
function fixDate(date) {
        var base = new Date(0)
        var skew = base.getTime()
        if (skew > 0)
                date.setTime(date.getTime() - skew)
}
</script>
</head>
<body>
<script language="JavaScript">
<!--
var now = new Date()
fixDate(now)
now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000)
var visits = getCookie("counter")
if (!visits)
        visits = 1
else
        visits = parseInt(visits) + 1
setCookie("counter", visits, now)
document.write("Вы были здесь " + visits + " раз(а).")
// -->
</script>
</body>
</html>


Реагирование на время суток
<html>
<head>
<script language="JavaScript">
function getHourOfDay()
{       var now = new Date();
        return(now.getHours());
}

function getTime()
{
        var now = new Date();
        var minutes = now.getMinutes();
        var divider = ":";

        if (minutes<10)
                divider = ":0";

                // Hack to get it to display the time
                // correctly in version 3.0, (adjust
                for offset)
        if (navigator.appVersion.lastIndexOf('3.')
         != -1 &&
                navigator.appName.lastIndexOf
                ('Netscape') != -1)
                        return( now.getHours()-1
                         + divider + minutes );

                // Other versions may work with this ?
        return( now.getHours() + divider + minutes );
}

function sayHello ()
{

        document.write( "Сейчас <B>" +
                                getTime() +
                                "</B>,
                                поэтому мы желаем Вам" );

        if(getHourOfDay()<5 || getHourOfDay()>19)
                document.write(' спокойной ночи!');
        else
        {
                if ( getHourOfDay() < 11)
                {
                        document.write(' доброго
                        утра!');
                }
                else
                {
                        document.write(' хорошей
                        работы!');
                }
        }
}
</script>

</head>
<body>
<script language="JavaScript">
<!--
sayHello()
// -->
</script>
</body>
</html>


Выпадающее меню
Вы наверняка встречали компактные меню навигации, построенные на основе выпадающего списка? Вот код скрипта, позволяющего сделать такую навигацию: <form name="f1">
<select name="Map" onChange="{
for (var i=0; i < this.length; i++) {
if (this.options[i].selected) {
if (i!=0) {
top.window.location=this.options[i].value;
break;
}
}
}
}
">
<option selected value="#">Быстрый переход к разделам:
<option value="main.html">Газета
<option value="stat.html">Статистика
<option value="price.html">Цены
<option value="repr.html">Наши представители
<option value="reclam.html">Примеры рекламы
<option value="order.html">Бюро заказов
</select></select></form>
 А если у вас используются фреймы, то достаточно будет изменить одну строчку: top.window.location=this.options[i].value;
на
top.window.frames[имя фрейма в кавычках или его номер].location=this.options[i].value;
Не забудьте, что нумерация фреймов в JavaScript начинается с 0!


Untitled

Примеры распространенных скриптов

Ссылка дня

Этот код предназначен для создания ссылки дня. Каждый день недели будет подставляться новый URL. Удобен для рекламы своей продукции или подачи новостей.

Выпадающее меню

Этот код предназначен для создания компактного меню. Все ссылки оформляются в виде элементов выпадающего меню, при нажатии на кнопку происходит переход на соответствующую страницу.

Реагирование на время суток

Этот код позволяет реагировать на время суток на стороне клиента. Вы можете в зависимости от времени суток вывести соответствующее приветствие или предложить соответствующий товар.

Часы

Этот код показывает идущие цифровые часы. Не понятно, зачем они нужны, но многим нравится.

Смена фона окна

Если Вы на столько доверяете своим посетителям, что хотите позволить им менять цвет фона, то воспользуйтесь этим кодом.

Определение разрешения экрана

Вы можете узнать разрешение экрана и попробовать подстроить ваш дизайн под эти размеры. Или просо подгрузить нужный фон.

Календарь

Календарь на текущий месяц с указанием текущего дня. Если его дополнить ссылками, то можно использовать в новостном разделе.

Счетчик посещений

Если Вы считаете, что Вашим посетителям важно знать, сколько раз они посетили Вашу страницу, воспользуйтесь этим кодом.

Картинки, реагирующие на мышь

Мигающая таблица

Меню-телепортатор

Часы в текстовом поле. Вариант 1.

Часы в текстовом поле. Вариант 2.

Появляющаяся строка в текстовом поле

Ну и еще кой-какие мелочи

к оглавлению


JavaScript

Разные мелочи
  • Иногда в программе нужно создать задержку времени точно заданной длительности. Стандартная функция JavaScript setTimeout(); иногда не подходит, так как "на её фоне" могут выполняться другие функции, что может привести к нежелательным накладкам. Более корректный результат дает следующая функция:

function pause (mSec) {
clock = new Date();
justMinute = clock.getTime();
while (true) {
just = new Date();
if (just.getTime() - justMinute > mSec) break;
}
}
  • Как открыть окно командой JavaScript:
window.open("URL", "windowName", ["windowFeatures,..."])

где windowFeatures - это:
copyhistory    [=yes/no] / [=1/0]    сохранение истории загрузки
документов в данное окно
directories    [=yes/no] / [=1/0]    наличие в данном окне кнопок
групп новостей
height          =pixelheight         высота окна в пикселах
location       [=yes/no] / [=1/0]    наличие поля location
menubar        [=yes/no] / [=1/0]    наличие меню
resizable      [=yes/no] / [=1/0]    наличие рамки окна , позво-
ляющей изменять его размеры
scrollbars     [=yes/no] / [=1/0]    наличие линеек прокрутки
status         [=yes/no] / [=1/0]    наличие строки состояния
toolbar        [=yes/no] / [=1/0]    наличие панели инструментов
width           =pixelwidth          ширина окна в пикселах

Использовать пробелы в строке windowFeatures не допускается.
Приведены базовые параметры. Понимаются без проблем IE3+, NN3+.
  • Закрыть окно броузера можно с помощью команды JavaScript window.close(). Для закрытия текущего окна можно использовать префикс self: self.close(). Для того, чтобы закрыть определенное окно, нужно обратиться к нему по имени: winName.close(). Однако следует заметить, что подобный способ сработает, только если окно открывалось с помощью метода window.open(), причем значение, возвращаемое этим методом, было присвоено какой-нибудь переменной: win1 = window.open("http://www.webclub.ru",
    "winName",
    "height=60,width=175,scrollbars=no");
     Если значение не присвоено, обратиться к созданному окну из родительского невозможно. Нужно упомянуть о том, что попытка закрыть последнее окно браузера потребует подтверждения этой операции пользователем. Такая возможность была сделана преднамеренно, чтобы избежать ситуации "хулиганского" закрытия всех окон со стороны JavaScript-программы.
  • Если Ваша JavaScript-прокрамма ни с того ни с сего начинает выдавать ошибки, хотя по синтаксису все в ней верно, проверьте, не стоит ли где-нибудь в тексте маленькая буква "я". JavaScript часто пытается интерпретировать ее, как управляющий символ. Поэтому нужно все буквы "я" в теле программы предварять обратным слэшем: "\я"


Появляющаяся строка в текстовом поле
<center><form name=posl>
<input type=text size=40 name=forma>
</form></center>
<script language="JavaScript">
var line="Вписать сюда текст бегущей строки";
var speed=100; var i=0;function m_line() {
if(i++<line.length)
{
document.posl.forma.value=line.substring(0,i);
}
else {document.posl.forma.value=" ";
i=0;} setTimeout("m_line()",speed);
}
</Script>
<script language="JavaScript">m_line();</Script>


Картинки, реагирующие на мышь

Меню-телепортатор
<form><input type="hidden" name="select">
<SELECT NAME="sel"
onchange="top.location.href =
   this.options[this.selectedIndex].value;">
<option selected value="#">Выбор страницы</option>
<option VALUE="http://www.design.ru/kovodstvo">
Дизайн для умных</option>
<option VALUE="http://idesign.jeo.ru/">
Сетевой Дизайн</option>
<option VALUE="http://www.weblink.ru/soup">
Суп Дня</option>
</select></form>


Картинки, реагирующие на мышь
<HEAD>
<!-- сам скрипт вставляется в контейнер HEAD -->
<SCRIPT language="Javascript">
<!--
if (document.images)
{
img1on = new Image ;img1off = new Image ;
img2on = new Image ;img2off = new Image;

img1on.src = "1on.gif";img1off.src = "1off.gif";
img2on.src = "2on.gif";img2off.src = "2off.gif";
}

function img_act(imgName) {
if (document.images)
{ imgOn = eval (imgName + "on.src");
document [imgName].src = imgOn; } }

function img_inact(imgName) {
if (document.images)
{ imgOff = eval(imgName + "off.src");
document [imgName].src = imgOff; } }
//-->
</SCRIPT>
</HEAD>

<BODY>
<!-- эта часть помещается в теле (BODY) документа -->
<a href="1.html"
onMouseOver="img_act('img1')";
onMouseOut="img_inact('img1')";>
<img src="1off.gif" name="img1" border=0></a>

<a href="2.html"
onMouseOver="img_act('img2')";
onMouseOut="img_inact('img2')";>
<img src="2off.gif" name="img2" border=0></a>
</BODY>


Определение разрешения экрана
<html>
<head>
<script language="JavaScript">
var height=0;
var width=0;

if (self.screen) {     // for NN4 and IE4
        width = screen.width
        height = screen.height
}
else if (self.java) {   // for NN3 with enabled Java
       var jkit = java.awt.Toolkit.getDefaultToolkit();
       var scrsize = jkit.getScreenSize();
       width = scrsize.width;
       height = scrsize.height;
}
</script>
</head>
<body>
<script language="JavaScript">
<!--
if (width > 0 && height > 0) {
  document.writeln
  ('Ваше разрешение: ',width,'x',height)
} else {
   document.writeln('Я не знаю вашего разрешени\я')
}
//-->
</script>
</body>
</html>


Картинки, реагирующие на мышь

Мигающая таблица
<table border=0 width=270 id="myexample"
style="border:2px solid yellow"><tr>
<td>Ваш текст<br></td></tr></table>
<script language="JavaScript">
<!--
function flashit()
{
if (!document.all)return;
if (myexample.style.borderColor==
   "yellow")myexample.style.borderColor="lime";
else myexample.style.borderColor=
   "yellow"}setInterval("flashit()", 1000)
//-->
</script>


Ссылка дня
<html>
<head>
<script language="JavaScript">
function GetTodaysURL()
{
  var locationlist = new URLList
  (
        "1.html",         // Monday
        "2.html",         // Tuesday
        "3.html",
        "4.html",
        "5.html",
        "6.html",
        "7.html"          // Sunday
  );

  now = new Date();
  num = now.getDay();
  if (num == 0) num = 7;

  location.href = locationlist.list[num-1];
}

function URLList ()
{
  var argv = URLList.arguments;
  var argc = argv.length;
  this.list = new Object();
  for (var i = 0; i < argc; i++)
  this.list[i] = argv[i];
  this.count = argc;
  return this;
}
</script>
</head>
<body>
Пример кода, выбирающего
<a href="javascript:GetTodaysURL()">
страницу этого дня</a>.
Завтра будет показана другая страница.
</body>
</html>


Принципы гипертекстовой разметки. Структура документов

1. Принципы гипертекстовой разметки. Структура документов

За основу модели разметки документов в HTML принята таговая модель. Таговая модель описывает документ как совокупность контейнеров, каждый из которых начинается и заканчивается тагами. Т.е. документ НТМL представляет собой не что иное, как обычный АSСII-файл, с добавленными в него управляющими НТМL-кодами (тагами).

Таги НТМL-документов в большинстве своем просты для понимания и использования, ибо они образованы с помощью общеупотребительных слов английского языка, понятных сокращений и обозначений. НТМL-таг состоит из имени, за которым может следовать необязательный список атрибутов тага. Текст тага заключается в угловые скобки (< и >). Простейший вариант тага - имя, заключенное в угловые скобки, например <HEAD> или <i>. Для более сложных тагов характерно различие атрибутов, которые могут иметь конкретные значения, определенные автором для видоизменения функции тага.

Атрибуты тага следуют за именем и отделяются друг от друга одним или несколькими знаками табуляции, пробелами или символами возврата к началу строки. Порядок записи атрибутов в таге значения не имеет. Значение атрибута, если таковое имеется, следует за знаком равенства, стоящим после имени атрибута. Если значение атрибута - одно слово или число, то его можно просто указать после знака равенства, не выделяя дополнительно. Все остальные значения необходимо заключать в одинарные или двойные кавычки, особенно если они содержат несколько разделенных пробелами слов. Длина значения атрибута ограничена 1024 символами. Регистр символов в именах тагов и атрибутов не учитывается, чего нельзя сказать о значениях атрибутов. Например, особенно важно использовать нужный регистр при вводе URL других документов в качестве значения атрибута HREF.

Чаще всего НТМL-таги состоят из начального и конечного компонентов, между которыми размещаются текст и другие элементы документа. Имя конечного тага идентично имени начального, но перед именем конечного тага ставится косая черта (/) (например, для тага стиля шрифта - курсив <i> закрывающая пара представляет собой </i>, для тага заголовка <ТIТLЕ> закрывающей парой будет </ТIТLЕ>). Конечные таги никогда не содержат атрибутов. По своему значению таги близки к понятию скобок "begin/end" в универсальных языках программирования, которые задают области действия имен локальных переменных и т. п. Таги определяют область действия правил интерпретации текстовых тагов документа.

При использовании вложенных тагов в документе следует соблюдать особую аккуратность. Вложенные таги нужно закрывать, начиная с самого последнего и двигаясь к первому. Некоторые НТМL-таги не имеют конечного компонента, поскольку они являются автономными элементами. Например, таг изображения <IMG>, который служит для вставки в документ графического изображения, конечного компонента не требует. К автономным тагам также относятся разрыв строки (<BR>), горизонтальная линейка (<HR>) и таги, содержащие такую информацию о документе, которая не влияет на его отображаемое содержимое, например таги <META> и <BASE>.

В некоторых случаях конечные таги в документе можно опускать. Большинство броузеров реализованы так, что при обработке текста документа начальный таг воспринимается как конечный таг предыдущего. Самый распространенный таг такого типа - таг абзаца <Р>. Поскольку он используется в документе очень часто, то его обычно ставят только в начале каждого абзаца. Когда один абзац заканчивается, следуюший таг <Р> сигнализирует броузеру о том, что нужно завершить данный абзац и начать следующий. Большинство авторов тагом конца абзаца вообще не пользуются.

Есть и другие конечные таги, без которых броузеры отлично работают, например конечный таг </HTML>. Тем не менее, рекомендуется включать по возможности больше конечных тагов, чтобы избежать путаницы и ошибок при воспроизведении документа.

Общая схема построения контейнера в формате HTML может быть записана в следующем виде:

    "контейнер" := <"имя тага" "список атрибутов">
            содержание контейнера
            </"имя тага">


Группы тагов НТМL

1.1. Группы тагов НТМL

Все таги НТМL по их назначению и области действия можно разделить на следующие основные группы

  • определяющие структуру документа;
  • оформление блоков гипертекста (параграфы, списки, таблицы, картинки);
  • гипертекстовые ссылки и закладки;
  • формы для организации диалога;
  • вызов программ.

Структура гипертекстовой сети задается гипертекстовыми ссылками. Гипертекстовая ссылка - это адрес другого HTML документа или информационного ресурса Internet, который тематически, логически или каким-либо другим способом связан с документом, в котором ссылка определена.

Для записи гипертекстовых ссылок в системе WWW была разработана специальная форма, которая называется Universe Resource Locator. Типичным примером использования этой записи можно считать следующий пример:

    Этот текст содержит
    <A HREF="http://polyn.net.kiae.su/altai/index.html">
    гипертекстовую ссылку</A>

В приведенном выше примере таг "A", который в HTML называют якорем (anchor), использует атрибут "HREF", который обозначает гипертекстовую ссылку (Hypertext Reference), для записи этой ссылки в форме URL. Данная ссылка указывает на документ с именем "index.html" в директории "altai" на сервере "polyn.net.kiae.su", доступ к которому осуществляется по протоколу "http".

Гипертекстовые ссылки в HTML делятся на два класса: контекстные гипертекстовые ссылки и общие. Контекстные ссылки вмонтированы в тело документа, как это было продемонстрировано в предыдущем примере, в то время как общие ссылки связаны со всем документом в целом и могут быть использованы при просмотре любого фрагмента документа. Оба класса ссылок присутствуют в стандарте языка с самого его рождения, однако, первоначально наибольшей популярностью пользовались контекстные ссылки. Эта популярность привела к тому, что механизм использования общих ссылок практически полностью "атрофировался". Однако по мере стандартизации интерфейса пользователя и стилей представления информации разработчики языка снова вернулись к общим ссылкам и стремятся приспособить их к задачам управления этим интерфейсом. Справедливости ради, следует отметить, что общие гипертекстовые ссылки в большинстве броузеров не используются и не отображаются.

Структура HTML-документа позволяет использовать вложенные друг в друга контейнеры. Собственно, сам документ - это один большой контейнер который начинается с тага <HTML> и заканчивается тагом </HTML>:

    <HTML> Содержание документа  </HTML>

Контейнер HTML или гипертекстовый документ состоит из двух других вложенных контейнеров: заголовка документа (HEAD) и тела документа (BODY):

Рассмотрим простейший пример классического документа:

Пример 1.1

    <HTML>
    <HEAD>
    <TITLE>Simple Document</TITLE>
    </HEAD>
    <BODY  text=#0000ff  BACKGROUND=#f0f0f0 >
    <H1>Пример простого документа</H1>
    <HR>
    Формы HTML-документов
    <UL>
    <LI>Классическая
    <LI>Фреймовая
    </UL>
    <HR>
    </BODY>
    </HTML>

Рис. 1.1. Пример простого документа

Компания Netscape Communication расширила классическую форму документа возможностью организации фреймов (кадров), позволяющих разделить рабочее окно программы просмотра на несколько независимых фреймов. В каждый фрейм может быть загружена своя страница HTML. Приведем пример документа с фреймами.

Пример 1.2

    <HTML>
    <HEAD>
    <TITLE>Frame Sample</TITLE>
    </HEAD>
    <FRAMESET COLS="30%,*">
    <FRAME SRC=HTML-lecture.html NAME=LEFT>
    <FRAME SRC=HTML-lec-1.html NAME=RIGHT>
    </FRAMESET>
    </HTML>

Рис. 1.2. Пример документа с фреймами


Контейнеры HTML-документа

1.2. Контейнеры HTML-документа

Каждая из составных частей документа имеет свой набор контейнеров, которые можно внутри нее использовать. Контейнеры тела документа не используются в заголовке или в контейнере FRAMSET. Рассмотрим каждую группу контейнеров более подробно.

Контейнеры заголовка документа НТМL - HEAD

Заголовок документа не имеет атрибутов. Основное назначение тагов заголовка - это описание общих для всего документа параметров отображения. К таким параметрам можно отнести стиль отображения документа, общий базовый адрес гипертекстовых ссылок, общие гипертекстовые ссылки, идентификатор и имя документа и т.п. Мы рассмотрим только наиболее часто встречающиеся контейнеры.

ТIТLЕ

Наиболее часто используемым тагом заголовка является имя документа.

ТIТLЕ имеет следующий синтаксис

    <ТIТLЕ> Название документа </ТIТLЕ>

Содержание тага ТIТLЕ отображается в поле названия документа.

ВАSE

Таг ВАSЕ связан с формой представления гипертекстовой ссылки в форме URL. Дело в том, что спецификация URL определяет две формы адресации документов: полную и неполную. НТМL разрешает использовать как полную форму адреса URL, так и неполную. Но для того, чтобы использовать вторую форму спецификации, ее надо на чем-то базировать, т.е. задавать базовый адрес, который можно было бы использовать для формирования полной формы URL из неполной. Таг ВАSЕ позволяет определить эту базу. Так, например, если в заголовке будет задано:

    <BASE HREF="http://polyn.net.kiae.su/>,
гипертекстовая ссылка вида:
    <A HREF="/altai/index.html">
будет расширена до
    <A HREF= http://polyn.net.kiae.su/altai/index.html
Это же касается и других импортируемых в документ тагов. Графический образ, монтируемый в документ по команде:
    <IMG SRC="/gif/te t.gif">
будет найден по адресу:
    <IMG SRC=' http://polyn.net.kiae.su/gif/test.gif'>

Содержание тага ВАSЕ интерфейсом пользователя прямо не отображается.

ISINDEX

Возможность поиска НТМL-документа по ключевым словам определяется тагом ISINDEX заголовка документа. В первоначальной версии языка данный таг не имел дополнительных атрибутов. Если сервер мог выполнить запрос по ключевым словам, то он автоматически вставлял в заголовок таг ISINDEX. Список ключевых слов приписывался клиентом к адресу документа после символа "?". Понятно, что выполнить запрос мог сервер, который при наличии символа "?" превращался в поисковую машину. НТМL-документ мог быть сгенерирован "на лету" программой, тогда ключевые слова после "?" приписываются к адресу этой программы. В новой версии языка появилась возможность указать программу обработки запроса и задать фразу вместо стандартной "SЕАRСН ISINDEX":

    <ISINDEX HREF=
    "http://polyn.net.kiae.su/cgi-bin/search"
    PROMPT="Enter Keywords:">

В приведенном примере атрибут НREF определяет адрес программы обработки запроса, а атрибут РRОМРТ - содержание приглашения. Справедливости ради стоит отметить, что полностью новые возможности этого тага выполняет только один - Аrеnа. Такие популярные интерфейсы, как Моsaic и Netscape, данный таг интерпретируют по-старому.

МЕТА

Таг МЕТА предназначен для определения в заголовке документа конструкций, отсутствующих в спецификации НТМL. Имеет три атрибута: NAME, CONTENT, HTTP-EQUIV. Применение данного тага затруднено тем, что для интерпретации конструкций, которые вводятся через этот таг, необходимо, чтобы сервер или интерфейс пользователя могли эти конструкции расшифровать и применить. Для такого сорта работы программа должна иметь интерпретировать конструкции SGML, что практически не реализовано ни в одной интерфейсной программе. Единственным способом применения данного тага на практике является включение в заголовок отклика по протоколу НТТР информации, определенной через атрибут НТТР-ЕQUIV:

    <META HTTP-EQUIV="Keywords"
    CONTENT="Plsma, Nuclear Physics">

При таком использовании в заголовок НТТР-пакета будет включена строка: Keywords: Plasma, Nuclear Physics, что удобно при отправке почты, например.

Наиболее эффектное применение контейнера МЕТА для построения демонстрационных роликов. В этом случае изменение отображаемой страницы строится на параметре Rеfresh (т.е. времени обновления документа). В заголовок документа записывается контейнер МЕТА следующего вида:

    <meta http-equiv = "Refresh":
    content = "0, URL=next.html">

такое предложение равносильно появлению в заголовке сообщения протокола НТТР предложения вида:

    Refresh = 0; URL=next.html <LF>

В свою очередь это означает замену документа сразу же после загрузки его браузером. При этом в качестве нового документа используется документ из параметра URL.


CS Department: Documentation: Java Script

Контейнеры тела документа НТМL - BODY

Таги тела документа

Таги тела документа предназначены для управления отображением информации в программе интерфейса пользователя. Они описывают гипертекстовую структуру базы данных при помощи встроенных в текст контекстных гипертекстовых ссылок. Тело документа состоит из:

  • иерархических контейнеров и заставок;
  • заголовков (от Н1 до Н6);
  • блоков (параграфы, списки, формы, таблицы, картинки и т.п.);
  • горизонтальных отчеркиваний и адресов;
  • текста, разбитого на области действия стилей (подчеркивание, выделение, курсив),
  • математических описаний, графики и гипертекстовых ссылок.

ВОDY

Описание тагов тела документа следует начать с тага ВОDY. В отличии от тага НEАD, таг ВОDY имеет атрибуты:

ID - идентификатор тага. Используется для именования тагов, а также в качестве точки перехода по гипертекстовой ссылке. Данный атрибут имеется практически во всех тагах тела документа. С точки зрения практического использования идентификатора ВОDY в качестве точки перехода по гипертекстовым ссылкам, использование этого атрибута сомнительно, т.к. отображаемая часть документа собственно и начинается с этого тага.

LANG - определяет язык документа в виде двухсимвольного кода ISO-639, за которым следует через точку необязательный код страны в формате ISO-3166. По замыслу разработчиков стандарта языка данный атрибут должен распознаваться программами интерпретации и управлять отображением многоязычных текстов. Однако даже Аrеnа, специально предназначенная для иллюстрации НТМL 3.0, не реализует этой возможности.

СLASS - иерархически организованное имя типа "ADDITION.FIRST". Предназначено для связывания тага текста с определенным стилем отображения. Реально пока не используется.

Все три вышеперечисленных атрибута являются общими для тагов тела и в даль нейшем мы их обсуждать не будем. Если атрибут не будет разрешен для тага тела текста документа, то это будет указано специально.

ВАСКGROUND - определяет фон, на котором отображается текст документа. В примере НТМL-документа в качестве фона был использован небольшой графический образ "bgr.gif.

    <ВОDY ВАСКGROUND="bgr.gif">

Как видно из этого примера, в качестве значения данного атрибута используется адрес в сокращенной форме URL. В данном случае это адрес локального файла.

Следует заметить, что разные интерфейсы пользователя поддерживают различные дополнительные атрибуты для тага ВОDY.

Атрибут | Значение
ВАСКCOLOR=#FFFFFF | Цвет фона
ТЕХТ=#0000FF | Цвет текста
VLINK=#FF0000 | Цвет пройденных гипертекстовых ссылок
LINK=#00FF00 | Цвет гипертекстовой ссылки

В данной таблице строка #ХХХХХХ определяет цвет в терминах RGB в шестнадцатеричной нотации. Так, цвет текста определен как синий, фона - белый, пройденные ссылки красные, а новые ссылки зеленые. Если в качестве атрибутов тага ВОDY указать:

    <ВОDY ВАСКCOLOR=#FFFFFF  ТЕХТ=#0000FF
     VLINK=#FF0000 LINK=#00FF00>

то цвет фона будет желтым, текст будет синим, ссылки - зеленые, а пройденные ссылки станут красные. Однако пользоваться этими атрибутами следует крайне осторожно, т.к. у пользователя может оказаться другой интерфейс, который эти параметры не интерпретирует.


CS Department: Documentation: Java Script

Таги управления разметкой

Заголовки

Заголовки обозначают начала разделов документа. В стандарте определено 6 уровней заголовков: от Н1 до Н6. Текст, окруженный тагами <Н1></Н1>, получается большим - это основной заголовок. Если текст окружен тагами <Н2></Н2>, то он выглядит несколько меньше (подзаголовок); текст внутри <НЗ></НЗ> еще меньше и так далее до <Н6></ Нб>. Некоторые программы, позволяют использовать большее число заголовков, однако реально более трех уровней встречается редко, а более 5 - крайне редко.

Стандарт языка насчитывает 11 атрибутов у тага заглавие. Рассмотрим только АLIGN, т.к. остальные в большинстве программ интерпретаторов не реализованы.

Для разбиения текста на параграфы используется таг <Р> в нем используются те же атрибуты что и заголовках.

В качестве примера рассмотрим создание домашней страницы фирмы по продаже бытовой электроники.

Пример 1.3

    <HTML>
    <HEAD>
    <TITLE> Главная страница</TITLE>
    </HEAD>
    <BODY>
    Компания.
    Открытое акционерное общество Компания основанная
    в 1996 году, является одним из ведущих продавцов
    бытовой электроники в России. Основными
    направлениями деятельности Компании являются:
    реализация бытовой электроники ведущих фирм мира
    через сеть магазинов;
    создание сервисных центров по обслуживанию бытовой
    электроники;
    Компания ищет агентов по сбыту бытовой электроники.
    Контактная информация
    Telephone (123) 123-34-56
    FAX (123) 123-34-56
    Почтовый адрес 123456 г. Город, ул Лесная, 106
    Электронная почта
    Общая информация: abc@abc.su
    Продажи: abc@abc.abc.su
    Copyright љ 1997 Компания
    </BODY>
    </HTML>

Рис. 1.3. Пример текста без разметки

Броузер покажет нам этот HTML-документ в виде непрерывного текста.

Атрибут АLIGN. Атрибут АLIGN позволяет выровнять текст по левому или правому краю, по центру или ширине. По умолчанию текст выравнивается по левому краю. Данный атрибут применим также к графике и таблицам.

Следующая таблица определяет возможные значения атрибута АLIGN:

Значение | Описание применения
Left | Выравнивание по левому краю
Right | Выравнивание по правому краю
Justify | Выравнивание по левому и правому краям
Сеnter | Центрирование

Значение Justify реализовано не во всех программах интерпретации.

Выравнивание по левому краю

По умолчанию текст HTML выравнивается по левому краю и не выравнивается по правому, то есть начало строк находится на одном уровне, а концы на разных. Чаще всего, получающийся при этом текст с равными промежутками между словами выглядит лучше. Поскольку выравнивание по левому краю задается автоматически, атрибут АLIGN=LEFT можно опустить.

Выравнивание по правому краю

Текст, выровненный по правому краю и не выровненный по левому - концы строк находятся на одном уровне, а начало на разных, - часто применяется на практике, хотя бы с целью создать оригинальный дизайн. Этот эффект достигается заданием атрибута АLIGN=RIGHT в обычных тагах, например в таге <Р>.

Центрирование текста и графики

Есть несколько способов отцентрировать текст или графику. В спецификациях HTML 3.0 предлагается пользоваться тагом <АLIGN=СЕNТЕR>. Однако этот таг применим не для всех объектов HTML-страницы, поэтому Netscape добавил таг <СЕNТЕR>, который центрирует любые объекты и поддерживается броузерами Netscape Navigator, Microsoft Explorer и некоторыми другими. К тагу <СЕNТЕR> нужно относиться с осторожностью. Какой-нибудь броузер может его вообще проигнорировать, и на странице окажется выровненный по левому краю текст.

Оборачивание

С помощью атрибута ALIGN= вы можете <обернуть> текст вокруг графического объекта. Для этого поместите таг <IMG SRC="/путь/файл.gif"> в том месте, где должен быть графический объект, и добавьте атрибут ALIGN=LEFT, ALIGN=RIGHT или АLIGN=CENTER. Кроме того, с помощью атрибутов НSPAСЕ= и VSPАСЕ= (они описываются ниже ) задается ширина горизонтальных и вертикальных полей, отделяющих изображение от текста. Можно также создать рамку вокруг картинки или обрамление таблицы текстом.

Вообще говоря, СLEAR является общим для всех блочных тагов (заглавия, параграфы и т.п.), однако реализован далеко не для всех из них.. СLEAR может принимать следующие значения:

Значение | Назначение
left | Пропустить картинку, расположенную у левого края листа
right | Пропустить картинку или таблицу, расположенную у правого края листа
аll | Прервать отображение текста перед картинкой или таблицей и продолжить ниже

В атрибуте СLEAR можно указать и численные значения:

    <Р СLEAR="100 рiх">

Данная запись означает пустое горизонтальное поле высотой в 100 пиксельных строк.

Расставим таги заголовков с атрибутами в нашем примере.

Пример 1.4

    <HTML>
    <HEAD>
    <TITLE> Главная страница</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN=CENTER>Компания.</H1>
    <H3 ALIGN=LEFT>Открытое акционерное общество Компания
     основанная в 1996 году, является одним из ведущих
     поставщиков   бытовой электроники в России.</H3>
    <H4 ALIGN=RIGHT>Основными
    направлениями деятельности Компании являются:
    реализация бытовой электроники ведущих фирм мира
    через сеть магазинов;
    создание сервисных центров по обслуживанию бытовой
     электроники; </H4>
    <H3> Компания ищет агентов по сбыту
    бытовой электроники.</H3>
    <H5 ALIGN=CENTER>Контактная информация
    Telephone (123) 123-34-56
    FAX (123) 123-34-56
    Почтовый адрес 123456 г. Город, ул Лесная, 106
    Электронная почта
    Общая информация: abc@abc.su
    Продажи: abc@abc.abc.su
    Copyright љ 1997 Компания</H5>
    </BODY>
    </HTML>

Рис. 1.4. Текст с использованием тагов заголовков

Результат мы можем видеть на экране.


CS Department: Documentation: Java Script

Таг <ВR>

Принудительный перевод строки используется для того, чтобы нарушить стандартный порядок отображения текста. При обычном режиме интерпретации программа интерфейса пользователя отображает текст в рабочем окне, автоматически разбивая его на строки. В этом режиме существующие в тексте концы строк игнорируются. Иногда для большей выразительности требуется начать печать с новой строки. Для этой цели и используется таг ВR. Атрибут СLЕАR= в таге <ВR> используется для того, чтобы остановить в указанной вами точке обтекание текстом объекта и затем продолжить текст в пустой области за объектом. Продолжающийся за объектом текст выравнивается в соответствии со значениями LEFT, RIGHT или АLL атрибута СLЕАR=:

<BR СLЕАR =LЕFТ> | Текст будет продолжен, начиная с ближайшего пустого левого поля
<BR СLЕАR =RIGHT> | Текст будет продолжен, начиная с ближайшего пустого правого поля
<BR СLЕАR=АLL> | Текст будет продолжен, как только и левое, и правое поля окажутся пустыми

Таг <NOВR>

Таг <NОВR> (Nо Вrеаk, без обрыва) дает команду броузеру отображать весь текст в одной строке, не обрывая ее. Если текст, заключенный в таги <NОВR>, не поместится на экране, броузер добавит в нижней части окна документа горизонтальную полосу прокрутки. Если вы хотите оборвать строку в определенном месте, поставьте там таг <ВR>.


CS Department: Documentation: Java Script

Таги управления отображением символов

Все эти таги можно разбить на два класса: таги, управляющие формой отображения (font style), и таги, характеризующие тип информации (information type). Часто внешне разные таги при отображении дают одинаковый результат. Это зависит главным образом от настроек интерпретирующей программы и вкусов пользователя.

Таги, управляющие формой отображения

Курсив, усиление, подчеркивание, верхний индекс, нижний индекс, шрифт большой, маленький, красный, синий, различные комбинации - все это делает страницы интереснее и функциональнее. Microsoft Internet Explorer и Netscape Navigator позволяют определить шрифт с помощью атрибута FАСЕ=. Теперь можно объединять на одной странице несколько видов шрифтов, вне зависимости от того, какой из них задан по умолчанию в броузере пользователя.

Верхние и нижние индексы [НТМL 3.0]

С помощью тагов <SUР> и <SUВ> можно задавать верхние и нижние индексы, необходимые для записи торговых знаков, символов копирайта, ссылок и сносок. Рассматриваемые таги позволяют создать внутри текстовой области верхние или нижние индексы любого размера. Чтобы они казались меньше окружающего текста, можно использовать таги <SUР> и <SUВ> с атрибутом FONT SIZE=.

Атрибут SIZE=

Атрибут SIZЕ= тага <FОNТ> позволяет задавать размер текста в данной области. Если вы не пользуетесь тагом <ВАSЕFОNТ SIZЕ=n> для задания определенного размера шрифта на всей странице, то по умолчанию принимается 3.

Некоторые броузеры не поддерживают таг <FONТ>, поэтому желательно употреблять его только внутри текстовой области. В других случаях лучше использовать таги <Н1>, <Н2>, <НЗ> и т. д. Главное преимущество тага <FONТ> состоит в том, что он после окончания своего действия не разбивает строку, как таги <Нn>. Поэтому таг <FONТ> бывает очень полезен для изменения размера шрифта в середине строки.

Таги <ВIG> и <SMALL>

Текст, расположенный между тагами <ВIG></ВIG> или <SMALL></SMALL>, будет соответственно больше

Атрибут СОLОR=хх

Если вы хотите сделать свою страницу более красочной, то можете воспользоваться атрибутом СОLОR= в таге FONТ, и тогда единственным ограничением будет цветовая палитра на компьютере пользователя. Пока только самые популярные броузеры отображают цвет шрифта.

Пользоваться тагом <FONТ СОLОR=> для смены цвета шрифта, так же как и большинством тагов НТМL, очень просто. Заключите текст в таги <FONТ СОLОR= RED></FONТ>.

Таги, управляющие формой отображения

Таг | Значение
<I>.....</I> | Курсив (Italic)
<B>...</B> | Усиление ВОLD)
<ТТ>... </TТ> | Телетайп
<U>. </U> | Подчеркивание
<S>...</S> | Перечеркнутый текст
< BIG >... </ BIG > | Увеличенный фонт
< SMALL >...</ SMALL > | Уменьшенный фонт
< SUB >...</ SUB > | Подстрочные символы
< SUЗ >... </ SUР > | Надстрочные символы


CS Department: Documentation: Java Script

Таги, характеризующие тип информации

Таг | Значение
<ЕМ>... </ЕМ> | Типографское усиление
<СIТЕ>...</СIТЕ> | Цитирование
< STRONG >.</ STRONG > | Усиление
<СODE>... </СОDE> | Отображает примеры кода (например, коды программ)
<SАМР>... </SАМР> | Последовательность литералов
<КВD>... </КВD> | Пример ввода символов с клавиатуры -
<VAR>...</VAR> | Переменная
<DFN>... </DFN> | Определение
<Q>- </Q> | Текст, заключенный в скобки

Все эти таги допускают вложенность и пересечение друг другом, поэтому все они имеют таг начала и конца. При использовании этих тагов следует помнить, что их отображение зависит от настроек программы-интерфейса пользователя, которые могут и не совпадать с настройками программы-разработчика гипертекста. Использование стилей, которые подменяют настройки интерфейса, в настоящий момент проблематично.

Пример 1.5. Использование тагов, управляющих формой отображения.

    <HTML>
    <HEAD>
    <TITLE> Главная страница</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN=CENTER>Компания.</H1>
    <H3 ALIGN=LEFT><I>Открытое акционерное общество
    Компания основанная в 1996 году, является одним из
     ведущих поставщиков  бытовой электроники
      в России.</I></H3>
    <H3 ALIGN=RIGHT><B>Основными направлениями
    деятельности Компании 	являются</B>:
    реализация бытовой электроники ведущих фирм мира
     через сеть магазинов;
    создание сервисных центров по обслуживанию бытовой
     электроники;</H3>
    <H3><U> Компания ищет агентов по сбыту
     бытовой  электроники.</U></H3>
    <H5 ALIGN=CENTER><TT>Контактная информация</TT>
    <DL>Telephone (123) 123-34-56
    <DL>FAX (123) 123-34-56
    <DL>Почтовый адрес 123456 г. Город, ул Лесная, 106
    Электронная почта
    <DL>Общая информация: abc@abc.su
    <DL>Продажи: abc@abc.abc.su</DL>
    Copyright љ 1997 Компания</H5>
    </BODY>
    </HTML>

Рис. 1.5. Таги отображения стилей текста

Таг <DL>

Использование тага списка (Definition List: <DL>) напоминает применение отступов в обычных текстовых редакторах. Таг <DL> был создан для форматирования текста, определяющего какой-то термин. Определяемый термин записывается на одной строке, а его определение на следующей, с небольшим отступом вправо. Таг <DL> позволяет создавать отдельные абзацы с отступом без нумерации или маркеров. Отступ делается от левого края. Если у вас на странице несколько тагов <DL>, то текст постепенно сдвигается все больше вправо. В конце определения поместите закрывающий таг </DL>. Помните, что таг <DL> сдвигает только левую границу абзаца.


CS Department: Documentation: Java Script

Таг <ВLОСKQUOTE>

Таг добавляет поля слева и справа от текста. Это полезный таг, поскольку он позволяет расположить текст компактно в центре страницы. При использовании < ВLОСKQUOTE > несколько раз текст все больше сжимается к центру.

Microsoft Internet Explorer и Netscape Navigator допускают применение атрибутов LEFTMARGIN=n и ТОРМАRGIN =n в таге <ВОDY>. Атрибут LEFTMARGIN = задает левое поле для всей страницы. ТОРМАRGIN= определяет верхнее поле. Число n показывает ширину поля в пикселях. Например, таг < ВОDY LEFTMARGIN ="40"> создаст на всей странице левое поле шириной 40 пикселей. При n, равном 0, левое поле отсутствует.

Табуляция

Потребность в этом средстве существовала уже очень давно, но лишь недавно оно было, наконец, предложено разработчикам.. Табуляцию можно задать несколькими способами.

Самый простой - записать таг <ТАВ IDENT=n>, где n определяет число еn-пробелов перед новым абзацем. Еn-пробел - это типографская единица измерения, приблизительно равная ширине буквы n в том шрифте, который вы используете. Таким образом, таг <ТАВ IDENT=4> задает символ табуляции шириной в четыре еn-пробела.

Если вы хотите в нескольких местах применить символ табуляции заданной величины, то в том месте, где вы задаете его размер, поместите таг <ТАВ> с атрибутом ID=, например, таким образом:

    <ТАВ ID="tabone" >

Теперь в любом месте страницы достаточно написать <ТАВ ТО="ТАВОNЕ">, и символы табуляции станут равными ТАВОNЕ. Соответственно можно аналогичным образом создать ТАВТWО, ТАВТНRЕЕ, ТАВFОUR и т. д.

Чтобы создать более сложный дизайн, можно применить с тагом <ТАВ> атрибут ALIGN=. При задании АLIGN=LЕFТ или ALIGN=RIGHT текст, следующий за тагом <ТАВ> (вплоть до ближайшего обрыва строки или тага), будет выровнен по левому или правому краю соответственно. При задании АLIGN=СЕNTER текст центрируется относительно табулятора на задаваемое тагом <ТАВ> число еn-пробелов.

Таг <ТАВ> можно применять для размещения и текста, и графики.


CS Department: Documentation: Java Script

Списки

Списки являются важным средством структурирования текста и применяются во всех языках разметки. В НТМL имеются следующие виды списков: ненумерованный список (неупорядоченный), нумерованный список (упорядоченный) и список определений. Таги для ненумерованных (Unordered Lists <UL>) и нумерованных списков (Ordered Lists <OL>) - это основа HTML. HTML 3.0 добавляет несколько атрибутов к тагам списков для выбора разных типов маркеров в ненумерованных списках и разных схем нумерации в нумерованных. Можно включать такие атрибуты в сами таги <LI> (List Item), чтобы сменить тип маркера в середине списка. После появления нового атрибута все последующие маркеры в списке будут иметь такой же вид.

Таг <UL>

Ненумерованный список. Ненумерованный список предназначен для создания текста типа:

  • первый элемент списка
  • второй элемент списка
  • третий элемент списка

Записывается данный список в виде следующей последовательности:

     <UL>
     <LI> первый элемент списка
     <LI> второй элемент списка
     <LI> третий элемент списка
     </UL>

Таги <UL> и </UL> - это таги начала и конца ненумерованного списка, таг <LI> (List Item) задает таг элемента списка. В дополнение к этим тагам существует таг, позволяющий именовать списки - LН (List Header). Приведем пример отображения ненумерованного списка следующего вида:

Атрибуты маркеров в ненумерованном списке

Если вы не желаете применять одни и те же маркеры на разных уровнях вложенности, то используйте атрибут ТYРЕ=. Вы можете задать любой тип маркера в произвольном месте списка. Можно даже смешивать разные типы маркеров в одном списке. Ниже перечислены таги с атрибутами стандартных маркеров

<UL TYPE =DISK> Таг создает сплошные маркеры такого типа, как в списках первого уровня по умолчанию
<UL TYPE =CIRCLE> Таг создает маркеры в виде окружностей
<UL TYPE =SQUARE> Таг создает сплошные квадратные маркеры

В HTML 3.0 вы можете вместо обычного маркера поместить GIF или специальный символ.

Атрибут РLАN=

Атрибут РLAN= создает ненумерованные списки без маркеров. Разумеется, простейший способ это сделать - воспользоваться списком определений, но если вы все же захотите вставить в список один-два маркера, то лучше применяйте данный атрибут.

Атрибут SRС=

Атрибут SRС= используется для того, чтобы задать GIF-файл вместо обычного маркера GIF, наиболее употребительный в HTML графический формат. Вместо того чтобы помещать GIF перед строкой с тегом <ВR> в конце, вы можете создать собственные изящные маркеры и затем использовать их в списке. В этом случае вы получите все преимущества ненумерованного списка и вдобавок симпатичные GIF-картинки в качестве маркеров. Атрибут SRC= можно задать в таге <UL>, определив сразу все маркеры списка, а можно указать разные GIF для разных пунктов списка, помещая атрибут SRC= в каждом таге <LI>. В любом случае для того чтобы атрибут SRС= работал с тагом <UL>, нужно задать атрибут РLAN=.

Атрибут SRС= для тагов списков очень нужен Web-разработчикам и способен сделать ваши страницы чрезвычайно привлекательными. Вместе с тем, его некорректное употребление приводит к исключительно уродливым страницам.

Атрибут DINGВАТ= [НТМL 3.0]

Атрибут DINGВАТ= позволяет создавать специальные типографские символы dingbats, поддерживаемые броузером. Эти символы имеют вид картинок, которые используются в качестве маркеров в списках. Приведем список стандартных dingbats:

    Text, Audio, Folder, Disc drive, Form, Home, Next.

Для задания dingsbat нужно указать его имя в таге <LI>. Например, для того чтобы задать home (домик), записывайте таг <LI DINGBAT="home" >.

Dingsbat можно также применять с тагом заголовка.

Таг <OL>

Нумерованные списки. Таг <OL> вместе с атрибутом ТYРЕ= в HTML 3.0 позволяет создать нумерованные списки, используя в качестве номеров не только обычные числа, но и строчные и прописные буквы, а также строчные и прописные римские цифры. При необходимости можно даже смешивать эти типы нумерации в одном списке.

<ОL ТYРЕ=1> Таг создает список с нумерацией в формате 1., 2., 3., 4. и т. д.
<ОL ТYРЕ=A> Таг создает список с нумерацией в формате А., В., С., В. и т. д.
<ОL ТYРЕ=a> Таг создает список с нумерацией в формате а., b., с., d. и т. д.
<ОL ТYРЕ=I> Таг создает список с нумерацией в формате I., II., III., IV. и т. д.

Пример 1.6. Использования тагов различных списков.

    <HTML>
    <HEAD>
    <TITLE> Главная страница</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN=CENTER>Компания.</H1>
    <HR>
    <H3 ALIGN=LEFT><I>Открытое акционерное общество
    Компания основанная в 	1996 году, является одним
    из ведущих поставщиков  бытовой электроники
    в России.</I></H3>
    <HR>
    <H3>Основными направлениями деятельности
     Компании являются:
    <OL>
    <LI>реализация бытовой электроники ведущих фирм
     мира через сеть магазинов;
    <LI>создание сервисных центров по обслуживанию
     бытовой 	электроники.</H3>
    </OL>
    <HR>
    <H3><U><FONT COLOR=RED> Компания ищет
     агентов по сбыту  бытовой
    электроники.</FONT></U></H3>
    <H5 ALIGN=CENTER>Контактная информация</H5>
    <UL>
    <LI>Telephone (123) 12-34-56
    <LI>FAX (123) 12-34-56
    <LI>Почтовый адрес 123456 г. Город, ул Лесная, 106
    <UL TIPE=CIRCLE>Электронная почта<BR>
    <LI>Общая информация: abc@abc.su
    <LI>Продажи: abc@abc.abc.su</UL></UL>
    <BR><BLINK>Copyright</BLINK> љ 1997 Компания
    </BODY>
    </HTML>

Рис. 1.6.


CS Department: Documentation: Java Script

Таг <НR>

Горизонтальное отчеркивание (horizontal rule) применяется для разделения документа на части. С помощью одного лишь тага <НR> можно придать странице совершенно необычный вид. Попробуйте поэкспериментировать с тагом <НR>и вы получите линии, совсем не похожие на те, которыми вы обычно пользуетесь.

Таг <РRЕ>

Отображение текста без форматирования.

Таг <BLINK>

Таг <BLINK> вызывает мерцание текста заключенного в него.

Гипертекстовые ссылки

Все рассмотренные выше средства управления отображением текста являются безусловно важными, но только дополнительными к основному тагу документа - гипертекстовой ссылке. Для записи гипертекстовой ссылки используется контейнер <А...>......</А>, который называют "якорь" (аnchor). Якорь имеет несколько атрибутов, главным из которых является НREF (НуреrТехt Reference). Простую ссылку можно записать в виде:

    <А НREF="http://роlyn.net.kiae.su/index.shtml">
    Индекс базы данных "Полынь" </А>

где значением атрибута HREF является адрес документа " index.shtml " на машине " роlyn.net.kiae.su ", доступ к которой осуществляется по протоколу НТТР. Форма записи этого адреса называется универсальным локатором ресурсов (Universe Resource Locator) и является составной частью технологии WWW.

Содержание контейнера А, заключенное между тагом начала и тагом конца, выделяется в тексте цветом, определенным для контекстных гипертекстовых ссылок.

Одной из особенностей создания Web-сервера является то, что представленную на нем информацию желательно разбить на отдельные части, которые могут быть выведены на экран без необходимости его прокрутки. Организация связей между отдельными частями и осуществляется с помощью гипертекстовых ссылок.

Рассмотрим это на примере.

Одним из типичных приемов создания Web-сервера фирмы является представление на первой страницы перечня основных частей, в которые входят: Новости, Товары, Услуги, Контакты, Поиск.

Вставим список этих частей в наш пример вместе с гипертекстовыми ссылками на соответствующие документы. При этом можно указывать не полный адрес URL.

Пример 1.7

    <HTML>
    <HEAD>
    <TITLE> Главная страница</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN=CENTER>Компания.</H1>
    <HR>
    <H3 ALIGN=LEFT><I>Открытое акционерное общество
     Компания основанная в 	1996 году, является одним
      из ведущих поставщиков  бытовой электроники
       в России.</I></H3>
    <HR>
    <p ALIGN=CENTER>
    <font color="glava~10.shtml#400040" size="4">[ <a
    href="news.shtml">Новости</a>
    |<a href="products.shtml">Товары</a> |
    <a href="servis.shtml">Услуги</a>
     | <a href="contact.shtml">
    Контакты</a> |
    <a href="search.shtml">Поиск</a> ] </font>
    <HR>
    <H3>Основными направлениями деятельности Компании
    являются:
    <OL>
    <LI>реализация бытовой электроники ведущих фирм
    мира через сеть магазинов;
    <LI>создание сервисных центров по обслуживанию
    бытовой 	электроники.</H3>
    </OL>
    <HR>
    <H3><U><FONT COLOR=RED> Компания ищет
    агентов по сбыту  бытовой
    электроники.</FONT></U></H3>
    <H5 ALIGN=CENTER>Контактная информация</H5>
    <UL>
    <LI>Telephone (123) 12-34-56
    <LI>FAX (123) 12-34-56
    <LI>Почтовый адрес 123456 г. Город, ул Лесная, 106
    <UL TIPE=CIRCLE>Электронная почта

    <LI>Общая информация: abc@abc.su
    <LI>Продажи: abc@abc.abc.su</UL></UL>
    <BR><BLINK>Copyright</BLINK> љ 1997 Компания
    </BODY>
    </HTML>

На экране это будет выглядеть следующим образом. При нажатии на ссылку "Товары" в окно броузера будет загружен новый документ.

Рис. 1.7. Гипертекстовые ссылки

Рис. 1.8. Новый документ

Другой формой использования тага А является определение точек внутри текста, на которые можно сослаться: Такой метод используется в случае когда документ нельзя поделить на части и необходимо быстро передвигаться из оглавления по тексту.

    <А NАМЕ= роint">
Для ссылки на такую точку используют следующую форму URL:
<А НREF=
"http://роlyn.net.kiae.su/index.shtml #роint">
Ссылка на точку "роint"
 в документе "index.shtml"</А>

На описании простых гипертекстовых ссылок можно закончить обзор средств языка НТМL, ориентированных на текстовое представление информации и организацию гипертекстовых баз данных.


CS Department: Documentation: Java Script

Графика

Cамыми распространенными графическими форматами в Web являются GIFи JPEG форматы. Для того, чтобы вставить изображение в Web-страницу необходимо либо нарисовать его, либо взять уже готовое. В любой программе рисования несложно нарисовать простое изображение и сохранить его в требуемом формате . Если программа не поддерживает этот формат, то необходимо преобразовать файл. Для этого существует множество программ, предназначенных для преобразования одного графического формата в другой. Позаимствовать же картинки можно из различных программных пакетов, или с других Web-страниц в Internet, содержащих библиотеки художественных изображений свободного доступа. Когда броузер выводит страницу Web с изображением, соответствующий графический файл временно хранится в памяти компьютера. В большинстве броузеров есть команда, позволяющая сохранить файл постоянно на локальном диске. Существует множество других вариантов получения графических файлов.

Изображения могут быть полезны своей информативностью и придают Web-странице привлекательный вид. Приведем наиболее распространенные случаи применения изображений:

  • логотип компании на деловой странице;
  • графика для рекламного объявления;
  • различные рисунки;
  • диаграммы и графики;
  • художественные шрифты;
  • подпись автора страницы;
  • применение графической строки в качестве горизонтальной разделительной линии;
  • применение графических маркеров для создания красивых маркированных списков.

Теперь рассмотрим как вставить изображение в страницу Web. Тагом НТМL, который заставляет броузер выводить изображение, является <IMG> со следующим общим форматом:

    <IMG SRC="picture.gif">
Пример 1.8
    <HTML>
    <HEAD>
    <TITLE> Товары</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN=CENTER>Компания.</H1>
    <HR>
    <p ALIGN=CENTER><font color="glava~11.shtml#400040"
     size="4">
    [ <a href="news.shtml">Новости</a> |
    <a href="products.shtml">Товары</a> |
    <a href="servis.shtml">Услуги</a> |
    <a href="contact.shtml">Контакты</a> |
    <a href="search.shtml">Поиск</a> ] </font>
    <HR>
    <H3 ALIGN=CENTER> Телевизор
    <BR>СS-7272РТR/ 6272РТR </H3>
    <br><img src="cs727.jpg" align=left hspace=20
    vspace=20 ALT="Телевизор">
    <UL>
    <LI>Суперплоский кинескоп  с диагональю
    29/25 дюймов (72/62 см)
    <LI>Биокерамическое покрытие кинескопа
    <LI>Мультисистемный (РАL/SЕСАМ/NTSC)
    <LI>Звуковая мощность 2х30 Вт МРО
    <LI>Стерео (А2)
    <LI>Усилитель слабого сигнала (LNА)
    <LI>Функция "Картинка в картинке"
    <LI>Возможность настройки на 100 каналов
    <LI>Русский телетекст
    <LI>Экранное меню на нескольких языках
    <LI>Таймер включения/выключения
    <LI>Замок от детей
    <LI>Режим демонстрации
    </UL>
    <HR>
    <H3 ALIGN=CENTER>Контактная информация</H3>
    <UL>
    <LI>Telephone (123) 12-34-56
    <LI>FAX (123) 12-34-56
    <LI>Почтовый адрес 123456 г. Город, ул Лесная, 106
    <UL TIPE=CIRCLE>Электронная почта
    <BR><LI>Общая информация: abc@abc.su
    <LI>Продажи: abc@abc.abc.su
    </UL>
    </UL>
    <BR>
    <BLINK>Copyright</BLINK> љ 1997 Компания
    </BODY>
    </HTML>

Рис. 1.9. Вставленное в текст изображение

Замыкающего тага не требуется. Здесь SRC означает источник (SouRCe), а имя файла представляет собой имя выводимого графического файла.

Изображения на странице Web могут быть использованы и в качестве гипертекстовых ссылок, как и обычный текст. Читатель страницы щелкает на изображении и отправляется на другую страницу или изображение. Для обозначения изображения как гипертекстовой метки используется тот же таг <А>, что и для текста, но между <А> и </А> вставляется таг изображения <IMG>:

    <А НREF="адрес файла или изображения">
    <IMG SRC="picture.gif"></А>

При этом изображение используемое в качестве гипертекстовой ссылки обводится дополнительной рамкой.

Ограничивающие прямоугольники и атрибуты АLТ=

Для уменьшения времени загрузки страницы с графикой полезно определять в HTML-коде размер изображений. Если он известен еще до загрузки страницы, то броузер может в самом начале отвести рамку для картинки, а затем загружать текст на страницу. Если размер графического изображения определен, то броузер резервирует для него место в виде ограничивающего прямоугольника. Пока загружается графика, посетитель страницы может начать читать текст. Такая методика работы - жест вежливости по отношению к своим читателям.

Как задать размеры графики

Если у вас есть какая-нибудь программа для работы с графикой, то определить размер изображения нетрудно. Для начала откройте файл в графическом редакторе и определите размер картинки в пикселях. В таге изображения задайте ширину и высоту вашей картинки таким образом:

    <IMG SRC="picture.gif" WIDTH=413 НЕIGНТ=356>

Если вы не укажите размеры графических изображений, то броузер будет загружать каждую картинку целиком и только после этого переходить к загрузке текста. Это занимает время. Если размеры заданы, многие ведущие броузеры смогут начертить ограничивающий прямоугольник нужного размера и начнет заливать eго изображением. Если на странице есть другие графические объекты броузер продолжит работу с другими частями страницы, продолжая заполнять ограничивающий прямоугольник.

Атрибут АLТ=

Атрибут АLТ= тага IMG позволяет пользователям, броузеры которых смотрят только текст (или если в броузере отключен режим графики), получить представление о том, что за графика расположена на странице или каковы ее цели. Micrisoft Internet Explorer показывает текст из атрибута АLТ= в ограничивающем прямоугольнике, пока изображение загружается. Netscape Navigator выводит этот текст в том случае, если режим Auto Load Image выключен.

Таг <IMG> с атрибутом АLТ= будет выглядеть следующим образом:

<IMG SRC="pic.gif" НЕIGHТ=50 WIDTH=100 АLТ="Picture">


CS Department: Documentation: Java Script

Активные изображения

Активные изображения (image maps), или изображения, чувствующие щелчки мыши, позволят вам создать на своем узле графические меню произвольной формы. Пользуясь таким меню, читатели смогут путешествовать по всем закоулкам и проспектам вашего Web-узла. Активное изображение - это просто картинка с так называемыми активными областями (hot spots), которые ссылаются на URL других страниц или узлов. Работает такое изображение следующим образом: когда пользователь щелкает мышкой на картинке, определенной как активное изображение с помощью атрибута ISМАР в таге IMG, координаты щелчка передаются на Web-сервер. Сервер ищет в карте (mapfile) активную область, содержащую переданные координаты. Если такая область находится, заданный в карте URL активируется, и броузер пользователя переходит на новую страницу.

С помощью активных изображений вы можете решить сразу две задачи: создать меню своего Web-узла и разместить на нем графику.

В зависимости от того, на какие броузеры вы ориентируетесь, вам может понадобиться доступ к каталогу cgi-bin на вашем Web-сервере. Возможно, вам придется попросить у администратора своего Web-сервера разрешения записать туда файлы. Тем не менее, размещать активные изображения очень легко. Процесс их создания может поначалу показаться трудным, но существуют доступные программы, с помощью которых для конструирования таких изображений достаточно указать мышкой границы активных областей и задать каждой из них свой URL.

Где помещать активное изображение: на сервере или у клиента?

Есть два типа активных изображений: на сервере и, новая разновидность, у клиента. Изображения первого типа используют сервер для того, чтобы найти соответствующий данной активной области URL и передать на броузер нужную страницу. Активные изображения, работающие на клиентской машине, задают информацию об активной области на НТМL-странице, так что броузер сам выясняет, какие области являются активными, и запрашивает с сервера требуемую страницу.

Активные изображения, работающие у клиента, имеют несколько преимуществ. Во-первых, страницы с ними можно перенести на другой сервер. Во-вторых, серверу не приходится выполнять лишнюю работу (например, просматривать всю информацию об активных областях), то есть нагрузка на сервер уменьшается. При использовании работающих на сервере активных изображений в каталоге сgi-bin сервера должен быть соответствующий сценарий. По соображениям безопасности многие системные администраторы не любят разрешать людям толпиться вокруг сервера, записывая сценарии в каталог сgi-bin. Если вы арендуете сервер или просто используете место на чьем-то чужом сервере, вам, возможно, придется кого-то долго обхаживать, пока удастся записать сценарии. Недостаток активных изображений, работающих на клиентской машине, состоит в том, что обращаться с ними умеют только броузеры, поддерживающие HTML 3.0. Если у пользователя другой броузер, на вашей странице появится обычное графическое изображение, не чувствующее мышь. Так что у вас есть три возможности: сделать активное изображение на стороне клиента, что может отвратить от вас инертных людей, не спешащих менять свои броузеры; поместить его на сервере, и тогда им смогут воспользоваться практически все; применить оба вида изображений на одной странице. Последний вариант, видимо, наилучший, потому что вы попрактикуетесь в новейших средствах НТМ~ 3.0, и в то же время ваши страницы будут доступны для просмотра старыми броузерами.

Как сделать активное изображение

Процесс создания активного изображения состоит из двух этапов. Сначала вы должны определить области на картинке, которые вы хотите сделать активными, а потом соотнести их со ссылками на другие URL. Активные области задаются перечислением их координат (в пикселях). Все это можно сделать вручную, определив координаты углов активных областей, но гораздо проще воспользоваться какой-нибудь программой, например МарЕdit.

Определить карту легко. Нужно открыть в МарEdit файл, содержащий изо6ражение, на котором вы хотите создать активные области, после чего щелкнуть и потянуть мышкой, обозначив границу области. Программа автоматически генерирует файл, описывающий границы активной области. Затем этой области нужно приписать URL. В любых местах изображения можно нарисовать активные области в форме прямоугольника, многоугольника или круга и определить для каждой из них URL. Активные области могут даже пересекаться, но в этом случае возникают проблемы. Важно оставлять между областями немного места, чтобы читатель был уверен, что он активирует правильную ссылку. Большинство броузеров при попадании на пересечение нескольких активных областей считают активной ту из них, которая первой встречается в карте.

Границы активных областей задаются координатами углов прямоугольника и многоугольника или центра и радиуса круга. Эти параметры записываются в карту (МАР-файл)

Закончив создание активного изображения, вы можете сохранить полученный файл в формате NCSA или СЕRN, если оно будет работать на сервере, или в формате СSIМ, если оно функционирует на клиентской машине. Всю остальную работу выполняет программа МарEdit. Она создает карту на сервере или встраивает карту на стороне клиента в указанный вами файл HTML. Если вы решили делать активное изображение у клиента, Мар Edit поставляет данные только для тагов <МАР>. Вам придется самому задать таг изображения с атрибутом USЕМАР и поместить его после тага </МАР>. Не забудьте перед именем карты в атрибуте USЕМАР записать символ # следующим образом:

<IMG SRC="mymap.gif" USЕМАР="glava1~2.shtml#sitemap">

Как поместить активное изображение на НТМL-страницу

Когда изображение стало активным и для каждой активной области определен URL, его нужно поместить на HTML-страницу. Это можно сделать несколькими способами, в зависимости от того, какое изображение вы делаете: на сервере или у клиента.

Активные изображения на сервере

Старый испытанный способ создания активных изображений (для HTML 2.0) требует использования атрибута ISМАР в таге IMG. Таг IMG относится к изображению, и его надо поместить между начальным и конечным тагами ссылки на файл-карту. Вам нужно занести в HTML-файл приблизительно такую строку:

    <А НREF="path/somemap.map">
    <IMG SRC="path/somemap.map" ISМАР></А>

Атрибут ISМАР показывает броузеру, что данное изображение является активным. Когда в какой-либо его области происходит щелчок мыши, то благодаря атрибуту ISМАР серверу посылается сообщение, содержащее координаты щелчка. Если вы когда-нибудь пробовали водить мышью по активному изображению, вы могли заметить, что строка состояния в нижней части Web- броузера показывает нечто вроде:

    http:/www.my.com/path/somemap.map?300,20

Числа, стоящие после вопросительного знака, - это координаты указателя. Вы увидите их только в случае использования активных изображений на сервере. Карты изображений на стороне клиента показывают URL или ссылку, ассоциированную с областью карты.

Если вы хотите, чтобы специальная программа на сервере обработала активное изображение, на нее надо сослаться. Это значит, что URL, который показывает строка состояния, может иметь такой вид:

    http:/www.my.com/cgi-bin/imagemap/
                path/somemap.map?300,20

В данном случае сервер для обработки активного изображения используется программой под названием imagemap, находящейся в каталоге сgi-bin. Чтобы ваши изображения заработали, вам придется выяснить у своего системного администратора, что в точности нужно серверу. В зависимости от программного обеспечения сервера запись об активных изображениях в НТМL-файле будет выглядеть либо так:

    <А НREF="somemap.map">
    <IMG SRC="somemap.gif" ISМАР>
    </А>,
либо так
    <А НREF="sgi-bin/imagemap/somemap.map">
    <IMG SRC="somemap.gif" IS-МАР></А>,

Эти два примера соответствуют серверам с различным типом программного обеспечения: в первом случае программа обработки активных изображений не требуется, во втором случае - она обязана присутствовать.

Активные изображения у клиента

В отличие от изображений, описанных в предыдущем разделе, активные изображения у клиента работают независимо от программного обеспечения сервера и не перестанут функционировать, даже если вы перенесете свои файлы на другой сервер. Таким изображениям требуются только две вещи: броузер, поддерживающий HTML 3.0, и информация о карте, записанная в HTML-файле. Приведем пример ссылки в НТМL-файле для этого вида активных изображений:

Пример 1.9

    <HTML>
    <HEAD>
    <TITLE> Товары</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN="CENTER">Компания.</H1>
    <HR>
    <p ALIGN="CENTER">
    <font color="glava1~2.shtml#400040" size="4">[
    <a href="news.shtml">Новости</a> |
    <a href="products.shtml">Товары</a> |
    <a href="servis.shtml">Услуги</a> |
    <a href="contact.shtml">Контакты</a> |
    <a href="search.shtml">Поиск</a> ] </font>
    <HR>
    <H3 ALIGN=CENTER>Виды бытовой электроники
    предлагаемые Компанией </H3>
    <IMG SRC="catal2.jpg"
    usemap="glava1~2.shtml#catal2" ALIGN=MIDDLE>
    <map name="catal2">
    <area shape="rect" coords="8,5,128,134"
     href="tv.shtml">
    <area shape="default" nohref>
    </map>
    <HR>
    <H3 ALIGN="CENTER">Контактная информация</H3>
    <UL>
    <LI>Telephone (123) 12-34-56
    <LI>FAX (123) 12-34-56
    <LI>Почтовый адрес 123456 г. Город, ул Лесная, 106
    <UL TIPE="CIRCLE">Электронная почта<BR>
    <LI>Общая информация: abc@abc.su
    <LI>Продажи: abc@abc.abc.su
    </UL>
    </UL>
    <BR><BLINK>Copyright</BLINK> љ 1997 Компания
    </BODY>
    </HTML>

Рис. 1.10. Пример активного изображения

Имейте в виду, что если опустить атрибут SНАРЕ=, будет задано SНАРЕ="RЕСТ". Атрибут СООRDS= описывает координаты формы, используя пиксели в качестве единиц измерения. Атрибут USЕМАР= в таге <IMG> действует как ссылка <переход на>. Если перед именем файла карты помещен символ #, то атрибут USЕМАР= считает, что активное изображение находится в файле, описанном в таге <IMG>. Не пугайтесь координат. Точкой отсчета является левый верхний угол.

Активные изображения, работающие и на сервере, и у клиента

Возможно, вы захотите проявить осторожность и сделать изображения, работающими и на сервере, и на машине-клиенте.

После того, как вы создадите активные изображения на сервере и у клиента, соединить их в один HTML-файл нетрудно. Для этого нужно внести в документ HTML ту же запись, которую вы сделали бы для активного изображения на сервере. Не забудьте только добавить в таг IMG атрибут USЕМАР=. Атрибут USЕМАР= имеет более высокий приоритет, чем таг ISМАР, и если броузер поддерживает активные изображения, работающие у клиента, он распознает этот атрибут. Броузер, не знающий о таких изображениях, проигнорирует атрибут USЕМАР=.


CS Department: Documentation: Java Script

Изображения в миниатюре

Трудно противостоять искушению, использовать эффектную графику с высоким разрешением. Часто для иллюстрации какой-то темы требуются изображения прямо-таки огромные. Однако, есть способ избежать ситуации, когда ваши читатели спасаются бегством после десяти минут ожидания загрузки особенно <крутой> картинки.

В основном коде, в том месте, где вы хотели бы разместить огромный, красивый и эффектный рисунок, поместите очень маленькую его копию и сделайте ссылку на полномасштабное роскошное изображение с высоким разрешением. Те посетители, которым это действительно интересно, смогут посмотреть изображение полностью, а случайные люди пролистнут страницу, не задерживаясь. Такая методика особенно хорошо подходит для обложек книг, фотографий, рекламных листков, которые не все читатели захотят изучить в деталях.


1.3. Средства описания таблиц в HTML

По мере роста системы WWW стало ясно, что средств, которые заложены в НТМL, не достаточно для качественного отображения различного типа документов. Недостатком НТМL было отсутствие в его составе средств отображения таблиц. Для этой цели обычно использовался предформатированный текст (таг <PRE>), в котором таблица обрисовывалась символами АSСII. Но такая форма представления таблиц была недостаточно высокого качества и выделялась из общего стиля документа.

Таг <ТАВLЕ>.

Для описания таблиц служит таг <ТАВLЕ>. Таг <ТАВLЕ>, как и многие другие, автоматически переводит строку до и после таблицы.

Таг <ТR>

Таг <ТR> (сокращение от Таble Row - строка таблицы) создает строку таблицы. Если в таблице содержится два набора тагов <ТR></ТR>, в ней будут две строки. Весь текст, другие таги и атрибуты, которые вы хотите поместить в одну строку, должны быть помещены между тагами <ТR></ТR>.

Пример 1.10

    <HTML>
    <BODY>
    <H1 ALIGN=CENTER>Таблица</H1>
    <CENTER>
    <TABLE BORDER>
    <TR>
    <TD COLSPAN=3>Если в таблице два тага
     <TR> то в ней две строки.</TD>
    </TR>
    <TR>
    <TD>Если в стоке три тага <TD> </TD>
    <TD>то в ней </TD>
    <TD>три столбца.</TD>
    </TR>
    </TABLE>
    </CENTER>
    </BODY>
    </HTML>

Рис. 1.11. Использование тагов TR и TD

Таг <ТD>

Внутри строки таблицы обычно размещаются ячейки с данными, Каждая ячейка, содержащая текст или изображение, должна быть окружена тагами <ТD></ТD>. Число тагов <ТD></ТD> в строке определяет число ячеек. Строка с пятью парами тагов <ТD></ТD> будет состоять из пяти ячеек.

Пример 1.11

    <HTML>
    <BODY>
    <TABLE BORDER>
    <TR>
    <TD>В</TD>
    <TD>этой</TD>
    <TD>строке</TD>
    <TD>пять</TD>
    <TD>столбцов</TD>
    </TR>
    <TR>
    <TD>а в этой</TD>
    <TD>только</TD>
    <TD>три.</TD>
    </TR>
    </TABLE>
    </BODY>
    </HTML>

Рис. 1.12. В разных строках возможно задать разное количество столбцов

Таг <ТН>

При задании заголовков для столбцов и строк таблицы используются таг заголовка <ТН></ТН> (Таblе Неаder, заголовок таблицы). Эти таги аналогичны <ТD></ТD>. Отличие состоит в том, что текст, заключенный между тагами <ТН></ТН>, автоматически записывается жирным шрифтом и по умолчанию располагается посередине ячейки. Центрирование можно отменить и выровнять текст по левому или правому краю. Если воспользоваться <ТD></ТD> с тагом <В> и атрибутом <АLIGN=CENTER>, текст тоже будет выглядеть как заголовок. Однако, следует иметь в виду, что не все броузеры поддерживают жирный шрифт в таблицах, поэтому лучше задавать заголовки таблиц с помощью <ТН>.

Пример 1.12

    <HTML>
    <BODY>
    <TABLE BORDER>
    <TR>
    <TH>Заголовок центрирован по умолчанию</TH>
    <TH COLSPAN=2>Заголовок может объединять
    столбцы</TH>
    </TR>
    <TR>
    <TH>Заголовок может быть расположен
     перед столбцами</TH>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TH ROWSPAN=3> Заголовок может
    объединять строки</TH>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    </BODY>
    </HTML>

Рис. 1.13. Таг заголовков для столбцов и строк TH и его атрибуты COLSPAN= и ROWSPAN=

Таг <САРТIОN>

<CAPTION> позволяет создавать заголовки таблицы. По умолчанию заголовки центрируются и размещаются либо над (<САРТION АLIGN=ТОР>), либо под таблицей (<САРТION ALIGN=ВОТТОМ>). Заголовок может состоять из любого текста и изображений. Текст будет разбит на строки, соответствующие ширине таблицы. Иногда таг <САРТION> используется для подписи под рисунком. Для этого достаточно описать таблицу без границ.

Заголовок может состоять из любого текста и изображений. Текст будет разбит на строки, соответствующие ширине таблицы. Иногда таг <САРТION> используется для подписи под рисунком. Для этого достаточно описать таблицу без границ.

Пример 1.13

    <HTML>
    <BODY>
    <TABLE BORDER>
    <CAPTION ALIGN=TOP>Заголовок над
    таблицей</CAPTION>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    <TABLE BORDER>
    <CAPTION ALIGN=BOTTOM>Заголовок
    под таблицей</CAPTION>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    </BODY>
    </HTML>

Рис. 1.14. Таг заголовка CAPTION

Атрибут NOWRAP

Обычно любой текст в таблице, не помещающийся в одну строку ячейки, переходит на следующую строку. Однако, при использовании атрибута NOWARP с тагами <ТН> или <ТD> длина ячейки расширяется на столько, чтобы, заключенный в ней текст поместился в одну строку.

Атрибут СОLSPAN=

Таги <ТD> и <ТН> модифицируются с помощью атрибута СОLSPAN= (Column Span, соединение столбцов). Если вы хотите сделать какую-нибудь ячейку шире, чем верхняя или нижняя, можно воспользоваться атрибутом СОLSPAN=, чтобы растянуть ее над любым количеством обычных ячеек.

Пример 1.14

    <HTML>
    <BODY>
    <CENTER>
    <TABLE BORDER=3>
    <TR>
    <TD>
    Если вы хотите сделать какую-нибудь ячейку
    шире, чем верхняя или нижняя,
    </TD>
    <TD>
    можно воспользоваться атрибутом СОLSPAN=,
    </TD>
    </TR>
    <TR>
    <TD BGCOLOR=WHITE COLSPAN=2>
    чтобы растянуть ее над любым количеством
    обычных ячеек.
    </TD>
    </TR>
    </TABLE>
    </CENTER>
    </BODY>
    </HTML>

Рис. 1.15. Атрибут COLSPAN=

Атрибут ROWSPAN=

Атрибут ROWSPAN=, используемый в тагах <ТD> и <ТН>, аналогичен атрибуту СОLSPAN=, только он задает число строк, на которые растягивается ячейка. Если вы указали в атрибуте ROWSPAN= число, большее единицы, то соответствующее количество строк должно находиться под растягиваемой ячейкой. Нельзя поместить ее внизу таблицы.

Атрибут WIDТН=

Атрибут WIDТН= применяется в двух случаях. Можно поместить его в таг <ТАВLЕ> для задания ширины всей таблицы, а можно использовать в тагах <ТR> или <ТН> для задания ширины ячейки или группы ячеек. Ширину можно указывать в пикселях или в процентах. Например, если вы задали в таге <ТАВLЕ> WIDTH=250, вы получите таблицу шириной 250 пикселей независимо от размера страницы на мониторе. При задании WIDТН=50% в таге <ТАВLЕ> таблица будет занимать половину ширины страницы при любом размере изображения на экране. Так что, указывая ширину таблицы в пикселях имейте в виду, что если у вашего читателя узкая область просмотра, ваша страница может выглядеть несколько странно. Если вы пользуетесь пикселями и таблица оказывается шире области просмотра, внизу появится полоса прокрутки для перемещения вправо и влево по странице. В зависимости от поставленных задач и тот, и другой способ задания ширины таблицы могут оказаться полезными.

Пример 1.15

    <HTML>
    <BODY>
    <TABLE BORDER WIDTH=100%>
    <TR>
    <TD ALIGN=CENTER>Текст или данные - ширина 100%
    </TR>
    </TABLE>
или

    <TABLE BORDER WIDTH=50%>
    <TR>
    <TD ALIGN=CENTER>Текст или данные -
    ширина 50%</TD>
    </TR>
    </TABLE>
или

    <TABLE BORDER WIDTH=200>
    <TR>
    <TD ALIGN=CENTER>Текст или данные -
    ширина 200 пикселей</TD>
    </TR>
    </TABLE>
или

    <TABLE BORDER WIDTH=100>
    <TR>
    <TD ALIGN=CENTER>Текст или данные -
    ширина 100 пикселей</TD>
    </TR>
    </TABLE>
    </BODY>
    </HTML>

Рис. 1.16. Атрибут WIDTH=

Атрибут UNIТ=

Атрибут UNIT= тага <ТАВLЕ> определяет единицы измерения, используемые при указании размеров как всей таблицы, так и ее отдельных столбцов. Атрибут UNIТ= может принимать три значения:

UNIТ=ЕN - это значение присваивается по умолчанию и задает единицу измерения, равную еn-пробелу. Еn-пробел - это типографская единица измерения, равная ширине буквы <n>. Реальный размер пробела зависит от выбранного шрифта: для крупного шрифта еn-пробел больше, чем для мелкого. Обычно еn-пробел равен половине размера шрифта. Например, при использовании 12-пунктового шрифта ширина еn-пробела будет 6 пунктов. Для 8-пунктового шрифта еn-пробел занимает 4 пункта.

UNIТ=RELATIVE используется для задания относительной ширины столбцов в процентах от общей ширины таблицы. Этот способ следует по возможности применять вместо указания ширины в процентах UNIТ=RELATIVE выполняет ту же функцию, но поддерживается большим количеством броузеров.) При задании относительных (RELATIVE) единиц вводимые числа воспринимаются как ширина столбцов в процентах.

UNIТ=РIXELS - это значение применяется, когда вам нужно точно знать ширину столбца на экране. В этом случае лучше всего задать ее в пикселях. Например, таг <ТАВLЕ UNIТ=РIXELS WIDTH=340> сформирует таблицу шириной 340 пикселей.

Атрибут СОLSРЕС=

Атрибут СОLSРЕС=, используемый с атрибутом UNIТ=, определяет, сколько места занимает каждый столбец таблицы и как в нем выравниваются данные. Применяется только в таге<ТАВLЕ>.

СОLSРЕС= перечисляет все столбцы и для каждого из них задает выравнивание и размер. Для столбца (или ячейки) существует пять способов выравнивания: L - по левому краю, С - по центру, R - по правому краю, J - по правому и левому краю и D - по десятичной запятой. Если у вас пять столбцов, вы можете определить ширину и выравнивание каждого из них следующим образом:

    <ТАВLЕ UNIТ=РIХЕLS СОLSРЕС="L10 С15 J25 D30">

Вы описали таблицу, в которой первый столбец имеет ширину 10 пикселей и его содержимое выравнивается по левому краю, второй столбец, шириной 15 пикселей, с выравниванием по центру, третий, шириной 20 пикселей, выровнен по правому краю, четвертый, шириной 25 пикселей, выровнен с двух сторон, а пятый, шириной 30 пикселей, выравнивается по десятичным запятым.

Атрибут DР=

Атрибут DР= (Decimal Point, десятичный знак) определяет символ, используемый для отделения целой части десятичной дроби. DР="." (по умолчанию) указывает на точку в качестве десятичного символа. DР="," задает запятую.

Пустые ячейки

Если ячейка не содержит данных, она не будет иметь границ. Если вы хотите, чтобы у ячейки были границы, но не было содержимого, вы должны поместить в нее что-то, что не будет видно при просмотре. Можно воспользоваться пустой строкой <ВR>. Можно даже задать пустые столбцы, определив их ширину в пикселях или относительных единицах и не введя в полученные ячейки никаких данных. Это средство может оказаться полезным при размещении текста и графики на странице.

Атрибут СЕLLРАDDING=

Атрибут СЕLLPADDING= определяет ширину пустого пространства между содержимым ячейки и ее границами, то есть задает поля внутри ячейки.

Пример 1.16

    <HTML>
    <BODY>
    <CENTER>
    <TABLE BORDER CELLPADDING=20>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    <BR>
    <TABLE BORDER CELLPADDING=O>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    </CENTER>
    </BODY>
    </HTML>

Рис. 1.17. Атрибут CELLPADDING= определяющий ширину полей

Атрибуты АLIGN= и VALIGN=

Таги <ТR>, <ТD> и <ТН> можно модифицировать с помощью атрибутов ALIGN= и VALIGN=. Атрибут АLIGN определяет выравнивание текста и графики по горизонтали, то есть по левому или правому краю, либо по центру, как видно из рис. . Горизонтальное выравнивание может быть задано несколькими способами:

ALIGN=ВLЕЕDLEFT Прижимает содержимое ячейки вплотную к левому краю.
ALIGN=LEFT Выравнивает содержимое ячейки по левому краю с учетом отступа, заданного атрибутом СЕLLPADDING=.
АLIGN=СЕNTER Располагает содержимое ячейки по центру.
АLIGN=RIGHT Выравнивает содержимое ячейки по правому краю с учетом отступа, заданного атрибутом СЕLLPADDING=.

Пример 1.17

    <HTML>
    <BODY>
    <TABLE BORDER WIDTH=100%>
    <TR>
    <TD ALIGN=LEFT>Текст или данные</TD>
    <TD ALlGN=CENTER>Текст или данные</TD>
    <TD ALIGN=RIGHT>Текст или данные</TD>
    </TR>
    <TR>
    <TD ALIGN=RIGHT>Текст или данные</TD>
    <TD ALIGN=CENTER>Текст или данные</TD>
    <TD ALIGN=LEFT>Текст или данные</TD>
    </TR>
    <TR>
    <TD ALIGN=RIGHT>Текст или данные</TD>
    <TD ALIGN=RIGHT>Текст или данные</TD>
    <TD ALIGN=RIGHT>Текст или данные</TD>
    </TR>
    <TR>
    <TD ALIGN=CENTER>Текст или данные</TD>
    <TD ALIGN=CENTER>Текст или данные</TD>
    <TD ALIGN=CENTER>Текст или данные</TD>
    </TR>
    <TR>
    <TD ALIGN=LEFT>Текст или данные</TD>
    <TD ALIGN=LEFT>Текст или данные</TD>
    <TD ALIGN=LEFT>Текст или данные</TD>
    </TR>
    </TABLE>
    </BODY>
    </HTML>

Рис. 1.18. Атрибут ALIGN= выравнивания содержимого ячеек таблицы по горизонтали

Атрибут VALIGN= осуществляет выравнивание текста и графики внутри ячейки по вертикали. . Вертикальное выравнивание может быть задано несколькими способами: VALIGN=ТОР Выравнивает содержимое ячейки по ее верхней границе.

VALIMG=МIDDLE Центрирует содержимое ячейки по вертикали.
VALIGN=ВОТТОМ Выравнивает содержимое ячейки по ее нижней границе.

Пример 1.18

    <HTML>
    <BODY>
    <CENTER>
    <TABLE BORDER WIDTH=90%>
    <TR>
    <TD WIDTH=100> Атрибут VALIGN= осуществляет
     выравнивание текста и
    графики внутри ячейки по вертикали.
    </TD>
    <TD VALIGN=TOP>верх,</TD>
    <TD VALIGN=MIDDLE>середина,</TD>
    <TD VALIGN=BOTTOM>низ.</TD>
    </TR>
    <TR VALIGN=TOP>
    <TD> VALIGN=ТОР Выравнивает содержимое
    ячейки по ее верхней границе.
    </TD>
    <TD>верх,</TD>
    <TD>верх,</TD>
    <TD>верх.</TD>
    </TR>
    <TR VALIGN=middle>
    <TD> VALIGN=МIDDLE Центрирует содержимое
    ячейки по вертикали.
    </TD>
    <TD>середина,</TD>
    <TD>середина,</TD>
    <TD>середина.</TD>
    </TR>
    <TR VALIGN=bottom>
    <TD> VALIGN=ВОТТОМ Выравнивает содержимое
    ячейки по ее нижней границе.
    </TD>
    <TD>низ,</TD>
    <TD>низ,</TD>
    <TD>низ.</TD>
    </TR>
    </TABLE>
    </CENTER>
    </BODY>
    </HTML>

Рис. 1.19. Атрибут VALIGN= осуществляет выравнивание текста и графики внутри ячейки по вертикали.

Атрибут ВОRDER=

В таге <ТАВLЕ> часто определяют, как будут выглядеть рамки, то есть линии, окружающие ячейки таблицы и саму таблицу. Если вы не зададите рамку, то получите таблицу без линий, но пустое пространство для них будет отведено. Того же результата можно добиться, задав <ТАВLЕ ВОRDER=0>. Иногда хочется сделать границу потолще, чтобы она лучше выделялась. Можно для привлечения внимания к рисунку или тексту задать исключительно жирные границы. При создании вложенных таблиц приходится делать границы различной толщины для разных таблиц, чтобы их легче было различать.

Атрибут СЕLLSPACING=

Атрибут СЕLLSPACING= определяет в пикселях ширину промежутков между ячейками. Если этот атрибут не задан, по умолчанию задается величина, равная двум пикселям. Атрибут СЕLLSPASING= можно использовать, чтобы поместить текст и графику непосредственно там, где вам нужно. Если вы хотите оставить пустое место, можно вписать в ячейку пробел.

Пример 1.19

    <HTML>
    <BODY>
    <CENTER>
    <TABLE BORDER CELLSPACING=20>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    <TABLE CELLSPACING=20>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    <TABLE CELLSPACING=0>
    <TR>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    <TD>Текст или данные</TD>
    </TR>
    <TR>
    <TD>Текст или данные</TD>
    <TD></TD>
    <TD>Текст или данные</TD>
    </TR>
    </TABLE>
    </CENTER>
    </BODY>
    </HTML

Рис. 1.20. Атрибут CELLSPACING= определяет ширину промежутков между ячейками


1.4. Использование таблиц в дизайне страницы

Приятное свойство таблиц состоит в том, что если вы захотите, то можете сделать их границы невидимыми. Это позволяет с помощью тага <ТАВLЕ> красиво размещать на странице текст и графику. До сих пор таг <ТАВLЕ> остается единственным мощным средством форматирования в HTML. Дизайнеры Web-страниц сейчас обладают практически такой же свободой использования <пустого пространства>, что и создатели печатных страниц. Таблицы в большей мере, чем что-либо другое, помогают отойти от иерархического размещения текста на Web-страницах.

Если броузер вообще поддерживает таблицы, он обычно правильно отображает наиболее интересные эффекты, полученные с помощью таблиц.

Пример 1.20

    <HTML>
    <BODY>
    <CENTER>
    <TABLE CELLPADDING=10 CELLSPACING=0 BORDER=16>
    <TR>
    <TD ALIGN=CENTER>
    <H1>ПЕРФОРАТОР</H1>
    <H3>Только сегодня!</H3>
    <TABLE BORDER WIDTH=100%>
    <TR><TD ALIGN=CENTER><I>Почти бесплатно!</I></TD>
    </TR>
    </TABLE>
    </TD>
    </TR >
    </TABLE>
    </CENTER >
    </BODY>
    </HTML>

Рис. 1.21. Создание выпуклых элементов

Создание разноцветных таблиц

Ведущие броузеры, позволяют отображать некоторые цвета. Есть несколько способов раскрасить таблицу, в основном они зависят от используемого броузера.

Цветные границы в Netscape Navigator

Вы не только можете окружить таблицу красивой рамкой, но еще и задать для нее цвет, отличный от цветов текста и фона. Создайте простой серый GIF (или любой GIF, который вы хотели бы иметь в качестве фона) и определите его в таге <ВODY> как фон страницы. Затем задайте цвет фона страницы. В результате ваш таг <ВОDY> будет выглядеть примерно так:

    <BODY ВАСКGROUND="сооlbg.gif" ВGCOLOR=" # FF0000">

Вы создали двойной фон - GIF и заданный цвет. В результате фоновый цвет будет виден на всех границах таблиц и горизонтальных линиях (<НR>). Вне зависимости от того, является ли ваш фоновый GIF серым или нет, цветные линии и границы таблиц будут заметно выделяться. Если фоновый GIF не слишком сложно устроен, время загрузки страницы лишь немного возрастет. На приведенном ниже примере показано какие, широкие возможности дает использование цвета в HTML и в частности в таблицах.

Пример 1.21

    <HTML>
    <BODY BACKGROUND="bgr.gif"  BGCOLOR="YELLOW" >
    <CENTER>
    <TABLE BORDER=3 CELLPADDING=20>
    <CAPTION ALIGN=TOP><H2>Как просверлить бетонную
     стену</H2></CAPTION>
    <TR>
    <TD BGCOLOR=GRAY>
    <TABLE CELLPADDING=10 CELLSPACING=0 BORDER=16>
    <TD BGCOLOR=RED ALIGN=CENTER>
    <H1>ПЕРФОРАТОР</H1>
    <H3>Только сегодня!</H3>
    <TABLE BORDER WIDTH=100%>
    <TR>
    <TD BGCOLOR=AQUA ALIGN=CENTER>
    <I>Почти бесплатно!</I></TD>
    </TR>
    </TABLE>
    </TABLE>
    <TD WIDTH=50% BGCOLOR=BROWN ALIGN=CENTER>
    <IMG SRC="perfor1.gif" WIDTH=200 HEIGHT=150>
    </TD>
    </TR>
    <TR>
    <TD BGCOLOR=PINK>
    <FONT SIZE=6 COLOR=BLUE>От 6 до 20 мм</FONT>
    </TD>
    <TD  BGCOLOR=BLUE>
    <FONT SIZE=6 COLOR=PINK>Просверлим все</FONT>
    </TD>
    </TR>
    </TABLE>
    </CENTER >
    </BODY>
    </HTML>

Рис. 1.22. Пример разноцветной таблицы


Фреймы

1.5. Фреймы

Что такое фрейм?

В каком-то смысле фрейм - это именно то, что означает данное слово: рамка вокруг картинки, окошко или страница. Вводя таг <FRAME>, дизайнер НТМL-страницы разделяет экран броузера на части. В результате человек, просматривающий страницу, может изучать одну часть страницы независимо от остальной части. Фактически броузер, распознающий фреймы, загружает разные страницы в разные секции, или фреймы, экрана. Например, вы можете построить страницу таким образом, что фирменный знак будет зафиксирован в верхней части экрана, в то время как остальную часть страницы пользователь пролистывает обычным способом. Можно расположить сбоку кнопки навигации, которые не перемещаются, когда читатель щелкает их мышкой, так что изменяется только часть экрана, а сама полоска навигации остается неподвижной.

Для чего можно использовать фреймы

Хотя фиксация фирменного знака или средств навигации - наиболее очевидные способы использования фреймов, это не значит, что их возможности тем и исчерпываются. Просто перечисленные решения - первое, что приходит в голову дизайнерам, когда они думают о том, как использовать фреймы. Но каждый раз, когда в экран броузера вставляется фрейм, вы урезаете территорию, на которой можно было бы разместить данные. Чем больше вы вводите фреймов, тем меньше остается полезного пространства и тем скорее вы запутаете своих читателей, незнакомых с навигацией на Web-странице с фреймами.

Например, разумно хранить в фрейме логотип компании только на первых нескольких уровнях самых важных страниц, а основные данные показывать без фреймов.

Как работают фреймы

На первый взгляд, фреймы - это нечто сложное, но их легче понять, если провести аналогию с ячейками таблицы. Расположение фреймов на экране задается почти так же, как ячеек в таблице: таги и атрибуты работают так же, как их табличные родственники. Однако, хотя аналогия между единичным фреймом на странице и ячейкой таблицы полезна, нужно помнить, что есть и отличия. Содержимое ячейки задано в коде HTML-страницы с таблицей. Текст или графика, составляющие содержимое таблицы, фактически вводятся на той же самой странице HTML, что и таг или атрибут, описывающие таблицу. Напротив, экран с фреймами описывается в НТМL-странице, называемой (frameset ). Содержимое же фрейма - это отдельная HTML-страница, которая может находиться где угодно - в другом каталоге, на локальном сервере или на удаленном узле где-то в сети. Фреймовая структура определяет только способ организации экрана с фреймами и указывает, где находится начальное содержимое каждого фрейма. Для всех фреймов задаются URL, описывающие местонахождение их данных. Как правило, на странице с фреймовой структурой нет содержимого фреймов. Такая страница обычно невелика - она описывает только кадровую структуру экрана. Когда документ загружается в фрейм, вы можете щелкать мышкой на ссылке в этом документе, что вызовет появление связанных документов в других кадрах, заданных в фреймовой структуре.

Создание простой страницы с фреймами

Создадим, для лучшего понимания, пару простых страничек с фреймами, чтобы стало понятно, каково строение НТМL страницы с фреймами и для чего нужны основные таги и атрибуты.

Построим страницу с двумя фреймами. Зададим слева фрейм оглавления с заголовками статей, а справа поместим страницу с самими статьями. Сделаем так, что когда пользователь щелкает мышкой на ссылке в той части экрана, где находится оглавление, сама статья появляется в правом фрейме. Это основной, наиболее распространенный способ использования фреймов.

Задание фреймовой структуры

Для начала мы должны представить себе общий вид страницы - где и какого размера будут фреймы. Затем можно подумать об их содержании. Ниже приводится код простой фреймовой структуры с использованием тага <FRAMESET>. Обратите внимание: страница с фреймовой структурой не содержит тага <ВОDY>.

Пример 1.22

    <HTML>
    <HEAD>
    <TITLE>Пример фреймов</TITLE>
    </HEAD>
    <FRAMESET COLS="25%, 75%"
    <FRAME SRC="a.html">
    <FRAME SRC="b.html" NAME="main">
    </FRAMESET>
    <NOFRAMES>
    Вы видите эту страницу браузером,
     не поддерживающим фреймы.
    </NOFRAMES>
    </HTML>

Рис. 1.23. Вид экрана броузера с фреймами

Вот и весь код, необходимый для задания фреймовой структуры. Обратите внимание на таг <NOFRAMES>. Через несколько минут мы к нему вернемся. В результате мы получили экран, разделенный на два окна. Левое окно занимает 25 процентов экрана и содержит страницу с названием a.html. Окно справа займет 75 процентов и вначале покажет файл b.html. Пока у нас их нет, так что вы увидите страницу с двумя пустыми фреймами. Прежде чем она появится, нам придется пару раз щелкнуть мышкой в ответ на сообщения об ошибках, потому что броузер будет пытаться найти несуществующие страницы. Заметьте, что правую страницу мы назвали <main > ( <главная>) с помощью строки:

    <FRAME SRC="b.html" NAMЕ="main">

Это означает, что фрейм под именем main будет содержать страницу b.html. Заметим, что поскольку мы не собираемся показывать в левом фрейме никаких страниц, кроме menu.html, нам не нужно его называть.

Подготовка содержимого фрейма

Теперь давайте загрузим фреймы с содержимым. Зададим страницу menu.html в левом фрейме, где мы собираемся щелкать мышью, переключаясь между двумя страницами в правом фрейме. Файл menu.html - это обычная НТМL-страница, построенная как оглавление. На самом деле мы можем взять готовую страницу с оглавлением и использовать ее. Имейте в виду, что этот фрейм узкий и высокий, так что страница, которая будет в него загружаться, должна быть соответствующим образом спроектирована. Теперь мы должны определить, где будут появляться другие страницы при щелчке мышкой на ссылке. Поскольку мы хотим, чтобы они отображались в правом фрейме, добавим атрибут ТАRGET= (TARGЕТ="main") в таг ссылки. Это означает, что когда пользователь щелкает на ссылке, вызываемая страница появляется в фрейме main. Мы отображаем все страницы в фрейме main, поэтому давайте добавим атрибут ТАRGЕТ="main" во все таги ссылок в оглавлении. Если мы не определим атрибут ТАRGЕТ, то страница появится там, где мы щелкнули мышкой, - в левом фрейме, что нас не устраивает, хотя в какой-нибудь другой ситуации подобное поведение было бы очень кстати. Например, вы можете добавить ссылку <Другие пункты оглавления>, которая будет просто выводить следующие ссылки. Имеет смысл сделать оглавление подлиннее, чтобы читатели видели как можно больше ссылок. Но сейчас давайте ограничимся простым примером. Ниже приведен код для левого фрейма menu.html.

Пример 1.23

    <HTML>
    <HEAD>
    <TITLE> Меню</TITLE>
    </HEAD>
    <BODY>
    <H3 ALIGN=CENTER>Компания.</H3>
    <HR>
    <UL><font color="#400040" size="4">
    <LI><a href="html-pr2-4.html" ТАRGЕТ="main">
    Главная</a>
    <LI><a href="news.html" ТАRGЕТ="main">Новости</a>
    <LI><a href="products.html" ТАRGЕТ="main">Товары</a>
    <LI><a href="servis.html" ТАRGЕТ="main">Услуги</a>
    <LI><a href="contact.html" ТАRGЕТ="main">Контакты</a>
    <LI><a href="search.htm" ТАRGЕТ="main">Поиск</a>
    </UL>
    </font>
    </BODY>
    </HTML>

Рис. 1.24. Оглавление (меню) располагающееся в левой части

Заметим, что здесь ничего не говорится о фреймах. О них все сказано в фреймовой структуре. Единственное, о чем нужно позаботиться на каждой НТМL странице, появляющейся в фрейме, так это о том, где поместить ссылки, активирующие те или иные действия. В нашем примере, в левом фрейме, где находится оглавление, будет располагаться только одна страница. Мы хотим, чтобы при щелчке мышкой в левой странице ссылочный документ появлялся бы в правом фрейме main.

Подготовка фрейма main

Правый фрейм main будет содержать сами HTML-страницы. Ваша задача так их спроектировать, чтобы они хорошо смотрелись в меньшем, чем обычно, окне, потому что часть экрана будет занята левым кадром оглавления. Но больше эти страницы ничем не примечательны. Ниже приводится код для страницы, упомянутой первой в оглавлении ( html-pr2-4.html).

Пример 1.24

    <HTML>
    <HEAD>
    <TITLE> Главная страница</TITLE>
    </HEAD>
    <BODY>
    <H1 ALIGN=CENTER>Компания.</H1>
    <HR>
    <H3 ALIGN=LEFT><I>Открытое акционерное
     общество Компания, основанная в 1996 году,
      является одним из ведущих поставщиков
      бытовой электроники в России.</I></H3>
    <HR>
    <H3>Основными направлениями деятельности
    Компании являются:
    <OL>
    <LI>реализация бытовой электроники
    ведущих фирм мира через сеть магазинов;
    <LI>создание сервисных центров
    по обслуживанию бытовой
    электроники.</H3>
    </OL>
    <HR>
    <H3><U><FONT COLOR=RED> Компания ищет
    агентов по сбыту бытовой
    электроники.</FONT></U></H3>
    <H5 ALIGN=CENTER>Контактная информация</H5>
    <UL>
    <LI>Telephone (123) 12-34-56
    <LI>FAX (123) 12-34-56
    <LI>Почтовый адрес 123456 г. Город, ул Лесная, 106
    <UL TIPE=CIRCLE>Электронная почта<BR>
    <LI>Общая информация: abc@abc.su
    <LI>Продажи: abc@abc.abc.su
    </UL>
    </UL>
    <BR><BLINK>Copyright</BLINK> © 1997 Компания
    </BODY>
    </HTML>

Рис. 1.25. Вид правой части экрана с фреймами

Обратите внимание: на приведенных страницах нигде тайно не запрограммирован фрейм. Это значит, что можно без особых проблем конвертировать все документы вашего Web-сервера для использования в фреймовой структуре.

Использование тага <NOFRAMES>

У многих ваших посетителей еще остались броузеры, не умеющие обращаться с фреймами. По этим причинам разумно предоставить доступ к версии ваших основных страниц без фреймов. Если читатель с устаревшим броузером окажется на вашей странице с фреймовой структурой, все, что находится на ней между тагами < NOFRAMES > и </ NOFRAMES >, будет выглядеть отлично - броузер просто проигнорирует фреймы. Вот почему обязательно нужно использовать таги <ВODY></ВОDY>. Возможно, вам придется иначе организовать экран без фреймов.

Можно поместить на страницу с фреймами кнопку No Frames (Без фреймов). Ее назначение очевидно. Такой вариант достаточно разумен и легко осуществим.

На тот случай, если вы неуверенно чувствуете себя при использовании < NOFRAMES >, ниже приведен пример страницы с фреймовой структурой с добавленным в конце разделом < NOFRAMES >.

Пример 1.25

    <HTML>
    <HEAD>
    <TITLE> Пример фрейма</TITLE>
    </HEAD>
    <FRAMESET COLS="25%, 75%"
    <FRAME SRC="html-pr5-2.html">
    <FRAME SRC="html-pr2-3.html" NAME="main">
    </FRAMESET>
    <NOFRAMES>
    Вы видите эту страницу броузером не
     поддерживающим фреймы.
    Броузер поддерживающий фреймы не видит этот текст.
    </NOFRAMES>
    </HTML>

Рис. 1.26. Вид экрана с фреймами

Имейте в виду, что поддерживающий фреймы броузер проигнорирует все, что находится между тагами < NOFRAMES > и </ NOFRAMES >. И наоборот, не поддерживающий фреймы броузер проигнорирует все, что находится между тегами <FRAMESET> и </FRAMESET>. Код без фреймов можно поместить и в начало, и в конец страницы.

Специфические таги и атрибуты фреймов

Изучив таги и атрибуты для работы с фреймами, давайте поговорим о том, как лучше всего проектировать страницы с фреймовой структурой.

Таг <FRAMESET>

Таги <FRAMESET> обрамляют текст, описывающий компоновку фреймов. Здесь размещается информация о числе фреймов, их размерах и ориентации (горизонтальной или вертикальной). У тага <FRAMESET> только два возможных атрибута: ROW=, задающий число строк, и СОLS=, задающий число столбцов. Между тагами <FRAMESET> не требуется указания тага <ВОDY>, но его можно поместить между тагами <NOFRAME> в конце фреймовой структуры. Между тагами <FRAMESET> не должно быть никаких тегов или атрибутов, которые обычно используются между тагами <ВОDY>. Единственными тагами, которые могут находиться между тагами <FRAMESET> и </FRAMESET>, являются таги <FRAME>, <FRAMESET> и < NOFRAME>. Это упрощает задачу. В основном все связано с тагами <FRАМЕ> и их атрибутами. Если же вы хотите поэкспериментировать, можно сделать вложенные друг в друга таги <FRAMESET> аналогично тагам <ТАВLЕ>.

Атрибуты ROW= и СОLS= похожи. Имейте в виду, что для каждой строки и столбца, упомянутых в таге <FRAMESET>, должен быть свой набор тагов <FRАМЕ>.

  1. Атрибут ROW=

    Атрибут ROW= тага < FRAMESET > задает число и размер строк на странице. Количество тагов <FRАМЕ> должно соответствовать указанному числу строк. Справа от знака = можно определить размер каждой строки в пикселях, процентах от высоты экрана или в относительных величинах (обычно это указание занять оставшуюся часть места). Не забывайте пользоваться кавычками и запятыми и оставлять пробелы между значениями атрибутов. Например, следующая запись формирует экран, состоящий из трех строк: высота верхней равна 20 пикселей, средней - 80 пикселей, нижней - 20 пикселей:

    Следующий таг < FRAMESET > создает экран, на котором верхняя строка занимает 10% высоты экрана, средняя - 60%, а нижняя - оставшиеся 30%.

        <FRAMESET ROW="10%, 60%, 30%->

    Можно поступить элегантнее, задав относительные значения в комбинации с фиксированными, выраженными в процентах или пикселях. Например, следующий таг создает экран, на котором верхняя строка имеет высоту 20 пикселей, средняя - 80 пикселей, а нижняя занимает все оставшееся место:

        < FRAMESET ROW="20, 80, *" >

    А можно сделать так:

        < FRAMESET ROW="20, 2*, *" >

    Число перед знаком ' показывает, что соответствующая строка (в данном случае - средняя) занимает в два раза больше оставшегося на экране места, чем нижняя. Не забывайте, что значения относительны (они зависят от размера экрана). Это часто бывает удобно, ведь вы не всегда знаете, какой у вашего пользователя монитор и какое разрешение он использует.

  2. Атрибут СOLS=

    Столбцы задаются так же, как строки. Для них применимы те же атрибуты.

  3. Таг <FRАМЕ>

    Таг <FRАМЕ> определяет внешний вид и поведение фрейма. Этот таг не имеет закрывающего тага, поскольку в нем ничего не содержится. Вся суть тага <FRАМЕ> в его атрибутах. Их шесть: NАМЕ=, MARGINWITH=, MARGINHEIGHT=, SCROLLING=, NORESIZE= и SRC=.

  4. Атрибут NАМЕ=

    Если вы хотите, чтобы при щелчке мыши на ссылке соответствующая страница отображалась в определенном фрейме, вы должны указать этот фрейм, чтобы страница знала, куда что загружать. В предыдущих примерах мы назвали большой правый фрейм main, и именно в нем появлялись страницы, выбранные из оглавления в левом фрейме. Фрейм, в котором отображаются страницы, называется целевым (target). Фреймы, которые не являются целевыми, именовать не обязательно. Например, можно записать такую строку:

        <FRАМЕ SRC="my.html" NАМЕ="main">

    Имена целевых фреймов должны начинаться с буквы или цифры. Выбирайте названия попроще, чтобы не запутаться. Одни и те же имена разрешается использовать в нескольких фреймовых структурах. По щелчку мыши соответствующие страницы будут отображаться в именованном фрейме:

  5. Атрибут МАRGINWITH=

    Атрибут МАRGINWITH = действует аналогично атрибуту таблиц CELLPADDING=. Он задает горизонтальный отступ между содержимым кадра и его границами. Наименьшее значение этого атрибута равно 1. Нельзя указать 0. Можно ничего не присваивать - по умолчанию атрибут равен б.

  6. Атрибут МАRGINHEIGHT=

    Атрибут МАRGINHEIGHT= действует так же, как и МАRGINWITH =. Он задает поля в верхней и нижней части фрейма.

  7. Атрибут SCROLLING=

    Хотите ли вы, чтобы ваши читатели пользовались прокруткой в фрейме? Иногда бывает разумно лишить их этого удовольствия. Возможные варианты: SCROLLING =YES, SCROLLING =NО, SCROLLING =АUТО. SCROLLING =YES означает, что в фрейме всегда будут полосы прокрутки, даже если это не нужно. При задании SCROLLING =NО полос прокрутки не будет, даже если они необходимы. Если документ слишком большой, а вы задали режим без прокрутки, то документ просто будет обрезан. Атрибут SCROLLING =АUТО предоставляет броузеру самому решать, требуются ли полосы прокрутки или нет. Если атрибут SCROLLING= отсутствует, результат будет таким же, как и при задании SCROLLING =АUТО.

  8. Атрибут NORESIZE

    Как правило, пользователь может, перемещая границу фрейма мышкой, изменить его размер. Это удобно, но не всегда. Иногда вам потребуется атрибут NORESIZE. Помните: все границы фрейма, для которого вы задали NORESIZE, становятся неподвижными - соответственно может оказаться так, что размеры соседних фреймов тоже станут фиксированными. Пользуйтесь этим атрибутом с осторожностью.

  9. Атрибут SRС=

    Атрибут SRС= применяется в таге FRАМЕ при разработке фреймовой структуры для того, чтобы определить, какая страница появится в том или ином кадре. Если вы зададите атрибут SRС= не для всех фреймов, у вас возникнут проблемы. Даже если страницы, отображаемые в фрейме, выбираются в соседнем фрейме, вы должны по крайней мере задать для каждого фрейма начальную страницу. Если вы не укажите начальную страницу и URL, фрейм окажется пустым, а результаты могут быть самыми неожиданными - например, ваш броузер начнет выводить все новые и новые окна просмотра.

  10. Атрибут ТАRGЕТ=

    Чтобы разобраться с атрибутом ТАRGЕТ=, давайте вернемся к нашему простому примеру с кадром оглавления. Когда пользователь щелкает мышкой на одной из ссылок в левом фрейме, соответствующая страница должна появиться в правом фрейме, а оглавление остается неизменным. Чтобы этого добиться, нужно определить целевой фрейм ТАRGЕТ, в котором будет отображаться страница для каждого пункта оглавления. Задание целевых фреймов осуществляется в ссылках левого фрейма. Вот зачем всем кадрам в фреймовой структуре были присвоены имена. Правый фрейм называется main, так что нужно в каждой ссылке добавить атрибут ТАRGЕТ="main", в результате чего соответствующая страница появится в фрейме main. Обратите внимание: каждая ссылка содержит атрибут ТАRGЕТ="main", который в ответ на щелчок мышью отображает страницу в фрейме main.

    Атрибут ТАRGЕТ= можно задавать для нескольких различных тагов. При использовании в таге <ВАSЕ> он направляет все ссылки в определенный целевой фрейм, если в дальнейшем особо не оговорено другое. Можно задать атрибут ТАRGЕТ= в таге <АRЕА> в активном изображении или в таге <FОRМ>. Фреймы полезны для организации форм. Пользователи будут видеть одновременно и форму, и результат своего выбора. Обычно при щелчке мышью кнопки Submit форма исчезает и возникает страница с результатами выбора. Сочетание форм и фреймов может оказаться удобным средством навигации.

"Волшебные" целевые фреймы

<Волшебные> целевые фреймы предназначены исключительно для особых случаев. Прежде чем вы решитесь их применить, дважды подумайте: они способны усложнить жизнь некоторым пользователям.

Имена <волшебных> целевых фреймов всегда начинаются с символа подчеркивания (_); никакие другие фреймы не могут так называться. < Волшебные> фреймы необязательно указывать в фреймовой структуре. Если не проявить должной аккуратности, то это - еще один повод вашему броузеру открыть кучу окон.

Фрейм "blank"

Если атрибут ТАRGЕТ= ссылается на " blank", то документ всегда будет появляться в новом пустом окне.

Фрейм "self"

Имя "self" указывает на то, что выбранная страница загружается в тот же фрейм, где была активирована ссылка. Если вы щелкнете мышкой на ссылке в фрейме оглавления, выбранная страница окажется в том же самом фрейме. Если вы задали фрейм для всего документа в атрибуте ВАSЕ=, то "self" помогает нейтрализовать ссылку в ВАSЕ=.

Фрейм "раrent"

Пользоваться этим именем небезопасно. Документы, вызываемые по ссылке на "раrent", появляются в родительской фреймовой структуре. Это хороший способ окончательно сбить с толку читателей. Да и у вас могут возникнуть трудности в процессе форматирования страниц. Соблюдайте осторожность.

Фрейм "top"

При указании ссылки "tор" документы появляются в отдельном окне вне фрейма. Независимо от вашего желания броузер откроет новое окно просмотра.

Вложенные и множественные кадровые структуры

Вложенные фреймы не очень способствуют навигации. И все же бывают случаи, когда возникает потребность сделать фреймы внутри других фреймов. По мнению автора, фреймы сами по себе - достаточно странное средство навигации, и незачем еще более усложнять свои страницы. Но если вам все же нужны вложенные фреймы, то они не представляют проблем.

В основном вложенные фреймы действуют так же, как вложенные таблицы. Задайте кадровую структуру, а внутри какого-нибудь фрейма в ней еще одну структуру. Помните, что таг <FRАМЕ> не имеет закрывающего тага. Вы, наверное, заметили, что при работе с фреймами не используются таги <СОLSРАN> и <ROWSРАN>. Их роль играют множественные, или вложенные, фреймы. Задав внутри одной объемлющей фреймовой структуры две независимых подструктуры, можно поместить в левой части экрана столбец из двух, а в правой - из трех фреймов.


Формы

1.6. Формы

Интерактивные формы позволяют читателям Web-страниц легко общаться с их владельцами. Благодаря простоте использования тага <mailto:> и форм стал возможен двусторонний диалог между владельцами Web-узлов и их читателями. Это открывает для Web-навигаторов увлекательные перспективы и коммерческие возможности. К сожалению, многие старые броузеры не поддерживают таг <mailto:>.

Как сделать так, чтобы ваша форма хорошо смотрелась

Один из способов свести все трудности к минимуму состоит в том, чтобы разместить области ввода и выпадающие меню в левой части страницы, а весь сопроводительный текст - справа. Поскольку ширину областей ввода можно задать с помощыо атрибута SIZЕ=, вы по крайней мере выровняете строки слева, а текст справа будет невыровненным - как в большинстве документов. Такая форма выглядит несколько аккуратнее. Не забудьте оставить промежуток между текстом и областью ввода формы. Другой несколько более сложный способ - разместить элементы формы в ячейки таблицы. Это позволяет разместить сопроводительный текст слева, а области ввода справа. Это показано в примере.

Пример 1.26

    <HTML>
    <HEAD>
    <TITLE>Коментарии</TITLE>
    </HEAD>
    <BODY>
    <H1>Коментарии</H1>
    <BR>Пожалуйста сообщите нам, что вы думаете
     о нашем web сайте, компании,
    продукции, или услугах. Если вы сообщите нам
    свою контактную информацию,
    у нас будет возможность связаться с вами
    и ответить на ваши вопросы.</p>
    <FORM METHOD="POST" action="mailto:
    yourname@your.email.address">
    <H3>Контактная информация</H3>
    <TABLE BORDER="0">
    <TR>
    <TD ALIGN="right"><em>Имя</em></td>
    <TD><input type="text" size="35" name="Name"></td>
    </TR>
    <TR>
    <TD ALIGN="right"><em>Тема</em></td>
    <TD><input type="text" size="35" name="Title"></td>
    </TR>
    <TR>
    <TD ALIGN="right"><em>Компания</em></td>
    <TD><input type="text" size="35"
     name="Company"></td>
    </TR>
    <TR>
    <TD ALIGN ="right"><em>Адрес</em></td>
    <TD><input type="text" size="35"
    name="Address"></td>
    </TR>
    <TR>
    <TD ALIGN ="right"><em>Телефон</em></td>
    <TD><input type="text" size="35"
    name="Telephone"></td>
    </TR>
    <TR>
    <TD ALIGN ="right"><em>Факс</em></td>
    <TD><input type="text" size="35" name="FAX"></td>
    </TR>
    <TR>
    <TD ALIGN ="right"><em>E-mail</em></td>
    <TD><input type="text" size="35" name="Email"></td>
    </TR>
    </TABLE>
    <p>
    <input type="reset" value="Очистить форму"> </p>
    </FORM>
    </BODY>
    </HTML>

Рис 2.27. Вид формы на экране

Как заставить формы работать

Возможно, это именно тот раздел, которого вы ждете - как сделать так, чтобы формы отсылали на сервер введенные данные. На самом деле заставить форму пересылать данные довольно просто. Главная проблема - понять, куда их пересылать. Формально вы просто пишете в таге <FORM> атрибут АСТION= и задаете ссылку на URL программы, которая может обработать входные данные и сделать с ними что-нибудь полезное.


Апплеты

1.7. Апплеты

Собственно и не стоило бы говорить о Java, если бы не возможность использования небольших мобильных программ, которые можно передавать по сети и исполнять на компьютере пользователя. Эти программы в терминологии Java называются апплетами (яблочками). Для встраивания вызовов апплетов в текст НТМL-документа и отведения места для отображаемой апплетом информации в НТМL был введен контейнер АРРLЕТ, который начинается тагом <аррlet> и кончается тагом </аррlet>. В общем виде документ, содержащий ссылки на апплеты будет выглядеть так, как это представлено в примере.

В данном примере после заглавия документа (таг H1) и горизонтальной черты начинается поле апплета шириной 200 пикселей и высотой 100 пикселей. В данное поле загружается аррlet с именем hello (файл hello.class). Текст между тагами начала и конца контейнера аррlet используется для размещения встраиваемых контейнеров и текста, который отображается броузерами, не позволяющими использовать Java.

Пример 1.27

    <HTML>
    <HEAD>
    <TITLE>Документ со встроенной ссылкой на applet.
    </TITLE>
    </HEAD>
    <BODY bgcolor=#FFFFFF>
    <CENTER>
    <H1>Документ со встроенным апплетом hello Java</H1>
    <HR>
    <APPLET CODE=hello width=200 height=100>
    Аррlet будет отображаться в этом месте, если
    Ваш браузер интерпретирует Java
    </APPLET>
    <HR>
    </BODY)
    </HTML>

В результате ссылки на такой документ сначала будет загружен текст документа. За тем будет обнаружен контейнер аррlet, и произойдет загрузка кода апплета. Файл hello.class должен в этом случае находиться там же, где и НТМL-файл, в котором есть на него ссылка. После приема апплета браузер отведет под него место в своей рабочей области и только после этого начнет его исполнение.

В общем случае контейнер АРРLЕТ имеет следующий вид:

    <applet
    [codebase = codebase url]
    code = applet.class
    [alt = text]
    [name= applet name]
    width = number of pixels
    height = number of pixels
    [align = alignment]
    [vspace=number of picsels]
    [hspace=number of pixels]
    [<param name=param name value=param value>]
    [HTML text]
    </applet>

Параметр соdebase задает базу для поиска кода апплета, соdе - это имя файла апплета, которое должно иметь расширение сlass, аlt - альтернативный текст - отображается в том случае когда выполнение апплета запрещено, name - имя контейнера аррlet, используется для ссылки на контейнер, widthт - ширина области отображения апплета, height - высота области отображения апплета, аlign - управляет выравниванием области отображения апплета внутри рабочей области браузера, vspase и hspase - указывают на отступ от текста НТМL-документа (вертикальный и горизонтальный, соответственно).

Использование контейнера РАRАМ позволяет передавать параметры внутрь апплета и НТМL-документа. Это аналогично вызову команды с различными аргументами командной строки. Для того, чтобы получить эти параметры внутри апплета, следует использовать метод getParametr ().

Из атрибутов контейнера АРРLЕТ обязательными являются только соdе, width и height Все остальные атрибуты (они заключены в квадратные скобки "[ ]") можно опускать. Большинство систем разработки Java-программ сами генерируют НТМL-документ, точнее его макет, для тестирования Java-апплетов. Так поступают, например, в АDК (Аррlet Development Kit) компании IВМ.

Для получения URL документа, из которого вызван аррlet можно использовать метод getDocumentBase и getCodeBase. Первый определяет базу адреса для документа, в то врeмя как второй определяет базу самого апплета. Применение этих методов необходимо для обращения за данными (текст или графика), которые могут храниться там жe, где и документ аррlet.


1.8. Каскадные таблицы стилей (Cascad Style Sheets)

Каскадные таблицы стилей были предложены w3c(WWW Consorcium) в рамках разработки спецификации HTML 3.0. Однако, реализованы в реально действующих навигаторах они были только в 1997 году. Фактически, в качестве применяемой HTML-разметки они стали доступны только с версий Netscape Navigator 4.0 и Internet Explorer 4.0.

Идея положенная в основу таблиц достаточно проста. К версии 4.0 HTML превращется в язык разметки, опирающийся на контейнерное представление документа, т.е. документ - это множество вложенных в друг друга контейнеров, каждый из которых имеет свои свойства по представлению информации. Многие контейнеры можно сгруппировать в классы однотипных контейнеров, например, заголовки или параграфы. Свойства контейнера, перечисляются в качестве атрибутов тага начала контейнера. При этом у большинства контейнеров, начиная с версии HTML 3.0 набор этих атрибутов типизирован.

Контейнеры во многом походят на блоки в универсальный языках программирования. При этом свойства контейнеров можно интерпретировать как переменные, которые принимают определенные значения в пределах каждого из контейнеров. Как и в языках программирования, например в Паскаль, описание свойств можно вынести в специальную секцию данных в начале документа. Такой секцией и является секция описания каскадных стилей. Обычно стиль описывается внутри контейнера STYLE:

    <style type="text/css">
    <!-- Описание стилей -->
    </style>
Вообще говоря, в Netscape поддерживают еще одну нотацию описание таблиц стилей - нотация JavaScript:
    <style type="text/javascript">
    <!-- Описание стилей -->
    </style>

При описании таблиц стилей мы будем опираться на документацию Netscape, т.к. - это наиболее популярный браузер Internet. Internet Explorer поддерживает только спецификацию w3c.

Начнем с простого примера. Нужно описать стили отображения текста в параграфе и заглавии документа:

Пример 1.28

    <html>
    <head>
    style type="text/css">
    p {color:blue; font-family: times; font-size:10pt;}
    h1 {color:black; font-size:12pt;
     font-style:Arial; text-align: center;}
    </style>
    </head>
    <body>
    <h1>Test Style Sheets in Communicator</h1>
    <p> This is a first part of the document
    </body>
    </html>

То же самое но в JavaScript-нотации будет выглядеть следующим образом:

Пример 1.29

    <html>
    <head>
    <style type="text/javascript">
    tags.p.color="blue";
    tags.p.fontFamily="times";
    tags.p.fontSize="10pt";
    tags.h1.color="black";
    tags.h1.fontSize="12pt";
    tags.h1.fontStyle="Arial";
    tags.h1.textAlign-"center";
    </style>
    </head>
    <body>
    <h1>Test Style Sheets in Communicator</h1>
    <p> This is a first part of the document
    </body>
    </html>

Сoзданный таким образом документ будет отображаться следующим образом:

Рис. 1.28. Пример отображения документа с каскадными описателями стилей

Разберем теперь описатели стилей более подробно. Определим область их применения и способы встраивания в документ.

Новые HTML-контейнеры

С появлением таблиц стилей в языке появилось три новых контейнера: STYLE, LINK, SPAN. Вообще говоря LINK - это не новый таг, а новое применение старого тага.

Контейнер STYLE(<style type="...">......</style>) служит для определения таблицы описания стилей. Хотя в спецификации CSS прямо не говорится, в каком контейнере документа следует применять STYLE, тем не менее, в примерах чаще всего приводится этот контейнер внутри контейнера HEAD.

Контейнер LINK в контексте описателей стилей применяется для определения внешнего файла с описаниями стилей для данного документа. Например, внешний файл может содержать следующее описание стилей:

    /* contents of the external style
    sheets file  css.htm*/
    p {color:blue; font-family: times;
     font-size:10pt;}
    h1 {color:black; font-size:12pt;
    font-style:Arial; text-align: center;}
    /* the end of style sheets definition */

Для применения этого описателя стилей в заголовок документа необходимо включить следующий таг:

Пример 1.30

    <html>
    <head>
    <link REL=STYLESHEET TYPE="text/css"
     HREF=http://localhost/css.htm>
    </head>
    <body>
    The body of the document should be here.
    </body>
    </html>

Из данного примера видно, что писание стилей в фале css.htm полностью совпадает с описанием в контейнере STYLE. В тексте файла описания стилей не нужно указывать таги контейнера STYLE.

Контейнер SPAN применяется для переопределения стиля отображения текущего фрагмента текста и в некотором смысле аналогичен контейнеру FONT. Часто SPAN применяют для достижения типографских эффектов, таких например, как выделение заглавной буквы абзаца:

Пример 1.31

    <HTML>
    <!--
    Author:	Paul Khramtsov
    Date:	September 19, 1997
    URL:	http://polyn.kiae.su/Internet/intro.html
    -->
    <HEAD>
    <style TYPE="text/css">
    FS.all { color:red; font-size: 24pt; font-family:
     times;}
    H1 {color:navy; text-transform: uppercase;
    font-size: 18pt;
    font-weight: bold; font-family: times;}
    H2 {color:navy; font-size: 14pt; font-weight:
     bold; font-style: italic;
    font-family: times;}
    H3 {color:navy; font-size: 10pt; font-weight:
    bold; font-family: times;}
    P  {color:navy; font-size: 12pt; font-family:
    times; text-align: justify}
    P.HELP {color:darkgreen; font-size: 10pt;
    font-family: times; text-align: justify;}
    P.LEFT {color:navy; font-size: 12pt;
    font-family: times; text-align: right;}
    </style>
    </HEAD>
    <BODY bgcolor=lightyellow>
    <center>
    <h3>Центр информационных технологий</h3>
    <h1>Информационные Ресурсы Internet</h1>
    <h3>(Учебное пособие для компьютерных
     непрофессионалов)</h3>
    <h3>Храмцов П.Б.</h2>
    <h3>Москва, 1997</h2>
    <hr>
    </center>
    <p><span class=FS>C</span>обществу
    глобальных компьютерных сетей
    Internet в 1995 году исполнилось 25 лет.
     На настоящий момент - это самый
    большое информационный ресурс мира. Одновременно
    Internet - это самая
    популярная и массовая компьютерная сеть планеты.
    По оценкам международного
    центра координации сетевой деятельности
    в рамках Internet(Internic-Internet
    Information Center) на 1997 год в сети
    насчитывалось несколько десятков
    миллионов постоянно зарегистрированных
    компьютеров-серверов, которые
    откликаются на запросы пользователей 365 дней
     в году и 24 часа в сутки. Этот
    огромный информационный ресурс полностью
     децентрализован и не подчиняется ни
    одному правительству или крупной финансовой
     компании мира.
    </BODY>
    </HTML>

В данном примере, контейнер SPAN применен сразу после тага начала параграфа <p>, что позволяет выделить первую букву в отображаемом абзаце:

Рис. 1.29. Эффект от применения контейнера SPAN

Кроме новых контейнеров таблицы описания стилей привнесли еще и новые атрибуты в известные таги.

Новые свойства контейнеров HTML

Перечень новых атрибутов у тагов HTML следует начать с атрибута STYLE. Этот атрибут используется для определения стиля отображаемого контейнера непосредственно внутри тага начала контейнера:

    <h3 style="line-hieght:24pt; font-weight:bold;
     color: blue">The blue text
    <h3 style="lineHeight='24pt'; fontWeght='bold';
    color='blue'">The blue text
Можно также определить класс стилей и использовать его при помощи атрибута CLASS:
    <style type="text/css">
    h3.class1 {font-size:12pt;color=blue}
    </style>
    .....
    <h3 class="class1">This is a blue text
В данном случае мы определили класс заголовков третьего уровня, но можно определить класс, который можно будет применять к любым контейнерам, а не только к заголовкам:
    <style type="text/css">
    all.class1 {font-size:12pt;color=blue}
    </style>
Kроме определения классов существует еще возможность создания поименованных стилей. Поименованный стиль создается как уточнение какого-либо класса:
    <style type="text/css">
    all.class1 {font-size:12pt;color=blue}
    #C1 { font-size: 20;}
    </style>
    ....
    <h3 class="class1">This is a blue text
    <h3 class="class1" id="C1">This is a blue text

Таким образом, атрибуты контейнеров позволяют связать описатели стилей с содержанием контейнеров и управлять формой отображаемой информации.

Свойства контейнеров, управляемые описателями стилей

Первую группу свойств составляют свойства шрифтов:

font-size, font-family, font-weight, font-style.

Вторую группу свойств составляют свойства текста:

line-height, text-decoration, text-transform, text-align, text-indent.

Третью группу свойств составляют свойства блоков текста:

margin-left, margin-right, margin-top, margin-bottom, margin, padding-top, padding-right, padding-bottom, padding-left, paddings, border-top-width, border-bottom-width, border-left-width, border-right-width, border-width, border-style, border-color

Четвертую группу составляют описатели цвета фона и цвета текста:

color, background-image, background-color.

Кроме того, существуют свойства определяющие тип пульки у элементов списка и ряд других свойств элементов HTML-разметки.


Управление просмотром страниц Web-узла. JavaScript

2. Управление просмотром страниц Web-узла. JavaScript

Современные гипертекстовые информационные системы условно можно представить в виде совокупности нескольких компонентов: системы хранения гипертекстовых объектов, системы отображения гипертекстовых объектов, системы подготовки гипертекстовых объектов и системы программирования просмотра совокупности гипертекстовых объектов. С этой точки зрения технология World Wide Web только к 1996 году получила законченный, функционально полный вид. Первыми были разработаны системы хранения и просмотра (1989-1991 г.г.), которые продолжают развиваться и в настоящее время. После 1990 года стали появляться первые системы подготовки документов. Наконец, в 1995 году были предложены первые языки управления сценариями просмотра.

Программирование процедуры просмотра гипертекстовой базы данных не является изобретением Netscape, Microsoft или Sun. Практически все локальные гипертекстовые системы в той или иной степени имеют программные средства манипулирования гипертекстовыми объектами. В ряде случаев вся гипертекстовая база данных может быть представлена как одна большая программа, в которой гипертекстовые узлы - это программные модули, а связи между ними - это передача управления от одного модуля другому.

Преимущества такого подхода перед традиционной статической разметкой очевидны: гибкость построения гипертекстовой сети, возможность создания программ прокрутки фрагментов базы дынных, генерация составных гипертекстовых объектов из существующих элементарных компонентов. Динамические объекты могут быть легко получены из статических, т.к. в случае существования программы просмотра система может быть переведена из интерактивного режима просмотра гипертекстовой базы данных в пакетный, когда действия оператора будут заменяться командами программы.

Программы просмотра гипертекстовых страниц традиционно называют скриптами (scripts) по аналогии с исполняемыми файлами, написанными для командных интерпретаторов типа sh. Собственно как это было и раньше в локальных системах, в программировании просмотра гипертекстовых документов World Wide Web существуют два подхода: создание интерпретируемых программой просмотра скриптов или компиляция байт-кода. Первый подход следует традиции World Wide Web, согласно которой для разработки гипертекстовой страницы нужен только обычный текстовый редактор и сам гипертекстовый документ должен легко читаться человеком-оператором. Второй подход позволяет повысить эффективность исполнения программы и защищенность кода от несанкционированных модификаций. Как первый, так и второй способ опираются на объектно-ориентированный подход к программированию. По поводу байт-кодов или мобильных кодов, как их еще называют, написано в контексте технологии программирования Java достаточно много, поэтому сосредоточим свое внимание на скриптах, а точнее на скриптах, написанных на языке JavaScript.


Модель объектов JavaScript - объекты Navigator'а

2.1. Модель объектов JavaScript - объекты Navigator'а

Идея JavaScript очень проста. Все операции, которые можно исполнять в программе на JavaScript, описывают действия над хорошо известными и понятными объектами, которыми являются элементы рабочей области программы Netscape Navigator и контейнеры языка HTML. Собственно объектная ориентированность JavaScript на этом и кончается. Есть только объекты с набором свойств и набор функций над объектами. Последние называются методами. Кроме методов существуют и другие функции, которые больше похожи на функции из традиционных языков программирования и позволяют работать со стандартными математическими типами или управлять процессом выполнения программы. Еще в JavaScript есть события - аналог программных прерываний. Эти события также ориентированы на работу в World Wide Web, например, загрузка страницы в рабочую область Navigator'a или выбор гипертекстовой ссылки. Используя события, автор гипертекстовой страницы и программы ее отображающей может организовать просмотр динамических объектов, например, бегущая строка, или управление многооконным интерфейсом.

Описание иерархии классов

Все встроенные объекты JavaScript берут свое начало от рабочей области Netscape, и их можно представить в виде следующей иерархии:

Кроме этих классов объектов пользователь может создавать и свои собственные. Но обычно большинство программ используют эту систему классов и не создают новых.


2.2. Методы объектов и свойства объектов. Управление потоком вычислений

Каждый из этих классов имеет функции управления объектами класса - методы. Самыми главными их этих методов являются те, которые позволяют переназначать значения объектов. Делается это обычно по операции присваивания. Вообще, все типы операторов, которые поддерживаются обычными языками программирования, реализованы JavaScript (+,-,*, /, %, >>,<<, +=, -=, ...). При этом оператор сложения "+" при работе со строками означает конкатенацию последних, т.е. добавление в конец строки новую строку:

    s = "string1"+"string2"

Кроме операций с числами и описаний стандартных классов в JavaScript есть команды управления потоком вычислений:

  • break - принудительный выход из цикла;
        while(i &lt 6)
    {
    if(i==3) break;
    }
  • continue - переход в конец цикла;
        while(i &lt 6)
    {
    if(i==3) continue;
    }
  • for - цикл;
        for(i=0;i<9;i++)
    {
    ...
    }
  • for - цикл свойств объекта (переменных определенных в классе);
        for(i in obj)
    {
    str = obj[i]
    }
  • if..else - условный оператор;
        if(i>0)
    {
    ...
    }
    else
    {
    ...
    }
  • wile - условный цикл;
        wile(j==k)
    {
    j++;
    k--;
    }
  • var - оператор объявления переменной.
    var kuku = "kuku"

Тип переменной определяется по присвоенному ей значению.

Перечисленные здесь операторы не представляют полного перечня операторов JavaScript, но их вполне достаточно для выполнения практических занятий.


События

2.3. События

Важным элементом языка являются события. Программист использует события для выполнения определенных частей программного кода скрипта. Один из наиболее часто используемых приемов - исполнение определенных действий в момент загрузки страницы в Navigator.

Не будем перечислять все события, но упомянем о наиболее часто используемых:

  • onLoad - выполнение скрипта или функции при загрузке;
  • onChange - порождается при изменении значения элемента формы;
  • onClick - порождается при выборе объекта (button, checkbox и т.п.);
  • onSelect - порождается при выборе текстового объекта (text, textarea);
  • onSubmit - при нажатии на кнопку Submit;
  • onUnload - при переходе к другой странице.

Появление Netscape Navigator 3.0 и новой версии JavaScript 1.1 заставляет продолжить обзор возможностей управления сценариями просмотра Website, который был опубликован в предыдущем выпуске "Открытых Систем Сегодня" (CW N 46, 1996). В новой версии языка были введены: возможность взаимодействия JavaScript и Java, определение установленных plug-ins, определены новые типы объектов (Area, Function, Image) и ряд других особенностей, которые по мнению разработчиков должны повысить мощь программирования на JavaScript.


Массивы

2.4. Массивы

Первый тип новых объектов, которые мы рассмотрим, являются массивы. Тип "Array" введен в JavaScript 1.1 для возможности манипулирования самыми разными объектами, которые отображаются Navigator'ом. Это - список всех гипертекстовых ссылок данной страницы Website, список всех картинок на данной странице, список всех applet'ов данной страницы, список всех элементов формы и т.п. Пользователь может создать и свой собственный массив, используя конструктор Array(). Делается это следующим образом:

    new_array = new Array()
    new_array5 = new Array(5)
    colors = new Array ("red","white","blue")

Размерность массива может динамически изменяться. Можно сначала определить массив, а потом присвоить одному из его элементов значение. Как только это значение будет присвоено, изменится и размерность массива:

    colors = new Array()
    colors[5] = "red"

В данном случае массив будет состоять из 6 элементов, т.к. первым элементом массива считается элемент с индексом 0. Для массивов определены три метода: join, reverse, sort. Join объединяет элементы массива в строку символов, в качестве аргумента в этом методе задается разделитель:

    colors = new Array("red","white","blue")
    string = acolors.join("+")

В результате выполнения присваивания значения строке символов string мы получим следующую строку:

    string = "red + white + blue"

Другой метод, reverse, изменяет порядок элементов массива на обратный, а метод sort отсортировывает их в порядке возрастания. У массивов есть два свойства: length и prototype. Length определяет число элементов массива. Если нужно выполнить некоторую рутинную операцию над всеми элементами массива, то можно воспользоваться циклом типа:

    color = new Array("red","white","blue")
    n = 0
    while(n != colors.length)
    {.... операторы тела цикла ...}

Свойство prototype позволяет добавить свойства к объектам массива. Однако наиболее часто, в программе на JavaScript используются встроенные массивы, главным образом графические образы (Images) и гипертекстовые ссылки (Links).


Графика

2.5. Графика

До Navigator 3.0 в JavaScript использовались только встроенные объекты типа Image. В новой версии языка появился конструктор для этого типа объектов:

    new_image = new Image()
    new_image = new Image (width,height)

Часто для целей создания мультипликации создают массивы графических объектов, которые потом прокручивают один за другим:

    img_array = new Array()
    img_array[0] = new Image(50,100)
    img_array[1] = new Image(50,100)
    ....
    img_array[99] = new Image(50,100)

У объекта Image существует 10 свойств, из которых, пожалуй, самым важным является src. Так, для присваивания конкретных картинок элементам массива img_array следует воспользоваться следующей последовательностью команд:

    img_array[0].src = "image1.gif"
    img_array[1].src = "image2.gif"
    ...
    img_array[99].src = "image100.gif"

В данном случае можно было воспользоваться и циклом для присвоения имен, так как они могут быть составлены из констант и значения индексной переменной. В новой версии языка объект типа Image можно поименовать в HTML-теге IMG. После этого можно обращаться к нему по имени. При этом следует учитывать, что если Image используется внутри формы, то он является свойством этой формы. Это значит, что для следующего графического объекта должны быть использованы разные составные имена:

    <img name=car src=car.gif>
    <--- Встроенный в документ объект
    document.car.src = "car1.gif"

    <form name=kuku>
    <img name=car src=car.gif>
     <-- Встроенный в форму документ.
    </form>
    document.kuku.car.src = "car1.gif"

Однако, наиболее часто в примерах использования скриптов можно встретить обращение к Image по индексу в массиве всех графических объектов данной страницы. Если наш объект, например, является вторым Image на странице, то будь он внутри формы или за ее пределами, к нему всегда можно обратиться по индексу:

    document.images[1].src = "car1.gif"

Расширяя наш пример с массивом Image построим теперь документ, в котором будет встроена мультипликация, определенная нашим массивом:

Пример 2.1

    <HTML>
    <HEAD>
    <SCRIPT>
    function multi_pulti()
    {
    img_array = new Array()
    img_array[0] = new Image(50,100)
    ....
    img_array[99] = new Image(50,100)
    img_array[0].src = "image1.gif"
    ...
    img_array[99].src = "image100.gif"
    n=0
    while(n==0)
    {
    document.images[0].src = img_array[0].src
    ...
    }
    }
    </SCRIPT>
    </head>
    <body onLoad="multi_pulti()">
    <img src=image1.gif>
    </body>
    </html>

Довольно часто используют не мультипликацию, а выбор картинки через OPTION, другой новый объект JavaScript. При этом можно через поле формы SELECT менять не только саму картинку, но и гипертекстовую ссылку, которая может быть связана с этой картинкой. На гипертекстовую ссылку также можно ссылаться по индексу:

    document.links[index].href = kuku.html

Данный прием оправдан и с точки зрения интерфейса навигатора. При использовании такого сорта массивов ссылок не требуется их длительное перечисление и листание страницы в рабочей области навигатора - можно просто выбрать ссылку из "выпадающего" меню. Другой способ для сокращения числа нажатий на клавиши - использование событий. В том же объекте OPTION можно использовать событие onChange, что делает необязательным нажатие кнопок типа submit. В этом случае достаточно будет просто выбрать альтернативу и перейти к новой странице сразу после выбора.


Стеки гипертекстовых ссылок

2.6. Стеки гипертекстовых ссылок

Не обошли своим внимание авторы JavaScript и стеки гипертекстовых ссылок. В язык теперь введен новый тип объектов типа Area. Area - это элемент контейнера MAP, который определяет client-site imagemap. Собственно, главное достоинство такого объекта состоит в том, что гипертекстовые ссылки, которые определены в AREA, стали доступны для переопределения. Они появляются в массиве обычных ссылок страницы, и можно как получить значение URL, так и переопределить его. К объекту AREA нельзя обратиться по имени. Можно использовать только индекс массива гипертекстовых ссылок документа.

В контексте стека гипертекстовых ссылок интересно рассмотреть еще одну возможность JavaScript, связанную с переходом по гипертекстовой ссылке вообще. В обычном случае параметр HREF контейнера A должен иметь какое-нибудь значение. Если, например, по событию onClick необходимо открыть новое окно и в старом сохранить отображенный документ, то его URL следует указывать в качестве значения HREF. В противном случае, в старое окно будет загружена пустая страница, если HREF=" ". В новой версии JavaScript введена функция void. Точнее тип void, который означает отсутствие какого-либо значения. Если нам необходимо выполнить некоторые действия при выборе гипертекстовой ссылки, но при этом не перегружать текущие страницы, то в параметре HREF можно указать конструкцию:

    <A HREF="javascript:void(0)">kuku</A>

Таким приемом часто пользуются при программировании событий, связанных с проходом манипулятора мыши через поле гипертекстовой ссылки.


Фреймы и окна

2.7. Фреймы и окна

При работе с фреймами и окнами в предыдущих версиях JavaScript постоянно приходилось отслеживать последовательность открытия окон и фреймов, для того, чтобы аккуратно их потом закрывать. На некоторых неточностях работы с окнами были основаны так называемые mail-bombs. Суть этих "подарков" заключалась в том, что если пользователь по почте принимает документ, который состоит только из одной команды:

    window.close()

то система не спрашивая пользователя закрывала текущее окно, а в этот момент таким окном является окно электронной почты. Теперь, перед тем как что-либо закрыть, система будет спрашивать разрешения. Правда, опять не всегда. Если в момент получения команды на закрытие окна на экране только одно окно Navgator, то система его закроет без каких-либо комментариев.

Однако, работа с окнами и фреймами на этом не исчерпывается. Во-первых, в систему введено новое свойство opener, которое определено для текущего окна или фрейма, и методы blur и focus распространены на работу с окнами. Свойство opener определяет окно документа, который вызвал открытие окна текущего документа. Свойство определено для любого окна и фрейма. Необходимо выполнить некоторые функции по отношению к окну, открывшему данное окно, и можно использовать выражение типа:

    window.opener.[method]

Например, если требуется закрыть окно-предшественник, то можно просто выполнить метод close:

    window.opener.close()

Точно таким же способом можно изменить содержание этого окна при помощи методов write или writeln. Можно менять и другие свойства объектов в окне предшественнике. Следующий пример взят из дополнений к спецификации JavaScript:

    window.opener.document.bgColor='cyan'

В данном случае для окна предшественника определен светло голубой цвет в качестве цвета фона. Но самым замечательным является то, что предшественника можно менять. Это значит, что автор получает возможность открывать и закрывать окна не строго иерархической последовательности, а в произвольном порядке. Управление многооконным интерфейсом в этом случае становится более гибким:

    window.opener= new_window
    window.opener = null

Первый пример переназначает для текущего окна окно-предшественник, в то время как второй вообще защищает предшественника от каких-либо действий. Все, что было сказано об окнах, распространяется и на фреймы, которые являются просто частным случаем окна. При работе с фреймами часто фрейм может быть порожден путем разбиения другого фрейма на части. Если при этом потребуется обратиться к окну-предшественнику фрейма-предшественника, то свойство opener в этом случае незаменимо. Разработчики языка следуют за пожеланиями авторов Websit'ов и практикой применения JavaScrip, которая на начальной стадии разработки языка не была столь очевидной. Кроме обращения к различным свойствам окон и фреймов разработчики расширили действие методов blur и focus с фреймов до окон. Теперь не только фрейм, но и окно может быть сделано текущем с использованием метода focus или, наоборот, переведено в фон при помощи метода blur. В ряде случаев, при порождении нескольких страниц, например, обращение к этим функциям бывает довольно полезным.


Наследование кода скриптов различными страницами

2.8. Наследование кода скриптов различными страницами

Отсутствие какого-либо наследования между различными страницами Website заставляло разработчиков перетаскивать из одной страницы в другую довольно большое количество часто используемых функций и переменных. Разговоры о том, что было бы неплохо получить возможность доступа к глобальным ресурсам или возможность определять такие глобальные ресурсы, ведутся с самого момента появления JavaScript. К сожалению, стройного логичного механизма передачи параметров, функций и переменных от одного окна или фрейма другому нет и в JavaScript 1.1. Однако, продвижение в этом направлении есть.

У контейнера SCRIPT появился атрибут SRC. Это дает возможность авторам строить своеобразную библиотеку функций, к которым можно обращаться из любой страницы, в которой будет ссылка на такую библиотеку. При этом вовсе необязательно размещать саму библиотеку на том же сервере, где размещены и гипертекстовые страницы Website. Можно использовать и чужие функции, написанные кем-либо из ветеранов программирования на JavaScript на другом конце Земли. В атрибуте SRC используется обычный URL. Внутри файла скриптов не используются теги SCRIPT. Это обычный файл с использованием определений функций и переменных. Естественно, что использование чужих скриптов может обернуться не только полезными приобретениями, но и непредсказуемыми проблемами. Для обычных пользователей страниц Website подкачка большого количества скриптов может стать просто дополнительным источником затрат. Наиболее вероятным случаем может стать использование одной-двух функций из библиотеки на одной странице, а качать придется всю библиотеку. Другой новой возможностью работы с функциями стало введение нового объекта Function. Объект Function порождается конструктором Function:

    new_Function = new Function(arg1,arg2,...,argn,
     function_body)

Главное отличие от обычного декларирования функции заключается в том, что в данном случае порождена переменная new_Function, с которой можно работать, как с любым другим объектом. При обычном переделении функции такой переменной не порождается. Как любой объект Function имеет свои свойства, но не имеет методов. В качестве свойств функции выступают аргументы и возможность назначения новых свойств через prototype. В заключении разговора о функциях и наследовании хочется еще раз обратить внимание на свойство opener окон и фреймов. Это свойство можно использовать при обращении к объектам страницы-родителя, что позволяет компенсировать отсутствие наследования и глобальных переменных в JavaScript.


Java, JavaScript и Plug-ins

2.9. Java, JavaScript и Plug-ins

В новой версии языка есть возможность организовать взаимодействие между Java-applet'ами и JavaScrip-скриптами. Достигается это за счет использования атрибута MAYSCRIPT в контейнере APPLET. Собственно в JavaScript определен объект типа APPLET, к которому можно обращаться либо по имени, либо по индексу в массиве applet'ов. У этого объекта есть только одно свойство - имя. Ни какие-либо другие свойства или методы для данного типа объектов не определены. Сами детали взаимодействия applet'ов и скриптов лучше всего обсуждать в рамках программирования Java applet'ов, поэтому здесь мы эти особенности опустим.

Кроме applet'ов JavaScript позволяет работать и с Plug-ins. Последние представляют из себя массив соответствующего типа, для которого определен ряд свойств. Используя эти свойства можно определить установленные plig-ins и их соответствия MIME-типам. Назначить plug-ins или манипулировать ими нельзя.


2.10. Встраивание в HTML-документ

Для встраивания скриптов в тело HTML-документа используется контейнер SCRIPT. Не все программы просмотра способны распознавать и исполнять скрипты, поэтому само тело скрипта помещается в контейнер комментария. Для определенности рассмотрим небольшой пример:

Пример 2.2

    <HTML>
    <!--
    Author:	Pavel Khramtsov
    Date:	March 5, 1996
    -->
    <HEAD>
    <TITLE>Registration</TITLE>
    <SCRIPT LANGUAGE="JavaScript">
    <!-- The JavaScript Functions Definition
    function help_win()
                {
    Help = window.open("reg_help.htm","Help",
"scrollbars=yes,resizable=yes")
                }
    function main_menu()
                {
                 Main_menu = window.open
                 ("main_m.htm","Main_menu")
                }
    // The end of JavaScript Functions Definition -->
    </SCRIPT>
    </HEAD>
    <BODY>
    <CENTER>
    <TABLE>
    <TR><TH><IMG 	SRC="radleg3.gif"></TH><TH><H2>ISTC.
    Project	RADLEG(#245)</H2>
    RRC "Kurchatov Institute"<BR>
    <I>Dec, 25, 1995</I>
    </TH></TR>
    </TABLE>
    </CENTER>
    <HR>
    <CENTER>
    <h2>Registration Form</h2>
    <hr>
    ......
    Текст страницы.
    .....
    <hr>
    <FORM NAME=help_call>
    <TABLE BORDER=0>
    <TR>
    <TD><INPUT NAME=next TYPE=button VALUE=Next
     onClick="main_menu()"></TD>
    <TD><INPUT NAME=help TYPE=button VALUE=Help
     onClick="help_win()"></TD>
    </TR>
    </TABLE>
    </FORM>
    <hr>
    </CENTER>
    </BODY>
    </HTML>

В данном примере в заголовок документа (контейнер HEAD) включен контейнер SCRIPT. До недавнего времени атрибут LANGUAGE в этом контейнере не являлся обязательным. Но с момента появления VBSCRIPT смысл в указании типа языка появился - Navigator не понимает скриптов на других языках. Отсутствие атрибута может привести к ошибкам при отображении гипертекстовых страниц.

Далее в тексте страницы определен комментарий, в который включен текст скрипта. Начинается комментарий со строки:

    <!-- The JavaScript Functions Definition
и кончается строкой:
    // The end of JavaScript Functions Definition -->

Внутри контейнера скрипта определены две функции пользователя: help_win() и main_menu(). Смысл этих функций достаточно прозрачен. Help_win() предназначена для организации контекстно зависимого help, а main_menu() осуществляет допуск пользователя к главному меню Website. Обе функции используют один и тот же метод open (встроенную в JavaScript функцию, которая определена над объектом window) для того, чтобы открыть новое окно Netscape Navigator и загрузить в него гипертекстовые документы. При этом функция main_menu порождает стандартное окно (с кнопочками, иконками и т.п.), а функция help_win() окно без стандартных атрибутов. У этого окна имеются только линейки прокрутки (scroll bars).

Вызов функций осуществляется после нажатия на соответствующую кнопку, которые определены в HTML-форме help_call. Таким образом выполнение функций будет осуществлено только в том случае, если произойдет событие, описанное атрибутом onClick в контейнере INPUT соответствующего поля формы.

Если быть более точным, то приведенный пример - это фрагмент, реализующий регистрацию пользователя. К моменту его выполнения окно главного меню уже открыто, поэтому создание этого окна при выборе кнопки "NEXT" не происходит, а текст загружается в уже открытое окно.


Примеры скриптов

2.11. Примеры скриптов

Теоретизировать на предмет написания скриптов на JavaScrip можно долго. Но самый продуктивный способ получения представления о языке - это разбор примеров. Если посмотреть на список этих примеров, который можно найти в соответствующей директории Yahoo, то больше всего там различного сорта калькуляторов. Разберем программу такого же типа, только это будет не калькулятор, а программа обучения устному счету.

Пример 2.3

    <HTML>
    <!--
    Author:	Pavel Khramtsov
    Date:	February 23, 1996
    URL:	http://144.206.192.100/radleg/mo_input.htm
    -->
    <HEAD>
    <TITLE>Проверка устного счета.</TITLE>
    <SCRIPT LANGUGE="JavaScript">
    <!--
    var max_value = 0
    var operand1 = 0
    var operand2 = 0
    var result = 0
    var flag = 0
    var sign = "+"
    var input = ""
    var v_date = new Date()
    var number = v_date.getTime()

    function init(factor) {
            var today = new Date()
            return (factor*today.getTime()/1000)%30000
    }
    //Инициализировать счетчик случайных чисел.

    IX = init(2)
    IY = init(3)
    IZ = init(4)

    //2, 3, and 4 below were arbitrarily chosen.
    //They put some distance between the initial
    values of IX, IY, and IZ

    //Датчик случайных чисел.

    function random() {
            IX = Math.floor(171 * IX % 30269)
            IY = Math.floor(172 * IY % 30307)
            IZ = Math.floor(170 * IZ % 30323)
            return ( IX/30269.0 + IY/30307.0
            + IZ/30323.0 ) % 1.0
    }

    //Выбрать сложение или вычитание.

    function set_sign(x)
             {
              if( x == "+")
                {
                 flag = 0
                 sign = "+"
                }
              if( x == "-")
                {
                 flag = 1
                 sign = "-"
                }
              return true
             }

    // Определить первый операнд.

    function f_operand()
             {
              operand1 = random()*max_value
              return parseInt(operand1)
             }

    // Определить второй операнд.

    function s_operand()
             {
              if(flag == 0)
                {
                 operand2 = random() *
                 (max_value-operand1)
                }
              else
                {
                 operand2 = random() * operand1
                }
              return parseInt(operand2)
             }

    // Проверить введенные данные.

    function input_sign(x)
             {
              if(x == 10)
                {
                 if(flag == 0)
                   {
                    if( operand1+operand2 ==
                    parseInt(input))
                      {
                       window.document.test.r0.
                       value = "Молодец!"
                      }
                    else
                      {
                       window.document.test.r0.
                       value = "Думай!?"
                       window.document.test.
                       input = ""; input = ""
                      }
                   }
                 if(flag == 1)
                   {
                    if( operand1-operand2 ==
                    parseInt(input) )
                      {
                       window.document.test.r0.
                       value ="__-__-_р!"
                      }
                    else
                      {
                       window.document.test.r0.
                       value = "Думай!?"
                       window.document.test.
                       input = ""; input = ""
                      }
                   }
                 return true
                }
              input += x
              window.document.test.result.value = input
              return true
             }

    // Генерация варианта.

    function main_calc()
             {
              operand1 = f_operand()
              window.document.test.op1.value = operand1
              operand2 = s_operand()
              window.document.test.op2.value = operand2
              window.document.test.s_sign.value = sign
              input = ""
              window.document.test.input = ""
              window.document.test.r0.value = " ??? "
              return true
             }
    // -->
    </SCRIPT>
    </HEAD>
    <BODY>
    <CENTER>
    <H1>Математический тест</H1>
    <HR>
    <FORM NAME=test>
    <TABLE BORDER=0>
    <TR>
    <TD><INPUT NAME=i10 TYPE=button VALUE="0-10"
        onClick="max_value=10"></TD>
    <TD><INPUT NAME=i20 TYPE=button VALUE="0-20"
        onClick="max_value=20"></TD>
    <TD><INPUT NAME=i100 TYPE=button VALUE="0-100"
        onClick="max_value=100"></TD>
    <TD><INPUT NAME=i+ TYPE=button VALUE=" + "
        onClick="set_sign('+')"></TD>
    <TD><INPUT NAME=i- TYPE=button VALUE=" - "
        onClick="set_sign('-')"></TD>
    </TR>
    </TABLE>
    <HR>
    <TABLE BORDER=0>
    <TR>
    <TD><INPUT NAME=op1 SIZE=2
    MAXLENGTH=2></TD>
    <TD><INPUT NAME=s_sign SIZE=1
    MAXLENGTH=1></TD>
    <TD><INPUT NAME=op2 SIZE=2
     MAXLENGTH=2></TD>
    <TD>=</TD>
    <TD><INPUT NAME=result SIZE=3
    MAXLENGTH=3></TD>
    <TD><INPUT NAME=award
    TYPE=button VALUE=" ? " onClick="main_calc()">
    <TD><INPUT NAME=r0 VALUE=" ??? ">
    </TR>
    </TABLE>
    <HR>
    <TABLE BORDER=2>
    <TR>
    <TD><INPUT NAME=b1 TYPE=button VALUE=" 1 "
     onClick="input_sign('1')"></TD>
    <TD><INPUT NAME=b2 TYPE=button VALUE=" 2 "
     onClick="input_sign('2')"></TD>
    <TD><INPUT NAME=b3 TYPE=button VALUE=" 3 "
     onClick="input_sign('3')"></TD>
    </TR>
    <TR>
    <TD><INPUT NAME=b4 TYPE=button VALUE=" 4 "
     onClick="input_sign('4')"></TD>
    <TD><INPUT NAME=b5 TYPE=button VALUE=" 5 "
     onClick="input_sign('5')"></TD>
    <TD><INPUT NAME=b6 TYPE=button VALUE=" 6 "
     onClick="input_sign('6')"></TD>
    </TR>
    <TR>
    <TD><INPUT NAME=b7 TYPE=button VALUE=" 7 "
     onClick="input_sign('7')"></TD>
    <TD><INPUT NAME=b8 TYPE=button VALUE=" 8 "
     onClick="input_sign('8')"></TD>
    <TD><INPUT NAME=b9 TYPE=button VALUE=" 9 "
     onClick="input_sign('9')"></TD>
    </TR>
    <TR>
    <TD><INPUT NAME=b0 TYPE=button VALUE=" 0 "
     onClick="input_sign('0')"></TD>
    <TD COLSPAN=2><INPUT NAME=bs
    TYPE=button VALUE=" OK "
    onClick="input_sign('10')">
    </TD></TR>
    </TABLE>
    </FORM>
    </CENTER>
    <HR>
    </BODY>
    </HTML>

Приведенный здесь пример содержит датчик случайных чисел (функции init и rand), таблицу, реализующую функции кнопок клавиатуры, и блок проверки результата вычислений. После загрузки программы пользователь должен выбрать тип вычислений (+,-), интервал вычислений (в пределах 10, 20, 100) и нажать кнопку "?" для генерации примера. После ввода с отображаемой клавиатуры числа пользователь нажимает на символ "=", что означает "исполнить", и система проверяет правильность ответа. Если ответ правильный, то программа поздравляет фразой "Молодец!", если нет - "Думай!?". В системе Windows 3.x нет встроенного датчика случайных чисел, поэтому стандартная функция rand в этой версии JavaScript не реализована. Используемый в данной программе датчик был позаимствован из телеконференции по JavaScript. В скрипте кроме этого используются объект типа "дата" и его методы, а также встроенные функции контроля вводимых данных. Как видно из примера обращение к полям HTML-формы представляет из себя обращение к структуре, корнем которой является объект окно, в котором определен объект документ, внутри которого определена форма и ее поля и атрибуты полей. Не у всех полей можно менять значения атрибутов, так, например, атрибут VALUE в кнопке не меняет своего значения, если только не перезагрузить страницу.

Другим часто встречающимся примером является бегущая строка. Строка может бежать либо в поле статуса (низ экрана), либо внутри поля формы. Рассмотрим такой пример.

Пример 2.4. Бегущая строка

    <HTML>
    <!-- ady@demos.su, Saturday, January 20,
     1996 7:23:31 PM-->
    <!-- Demos WWW cover page -->
    <HEAD>
    <TITLE>Welcome to Demos Company -
    Moscow, Russia</TITLE>

    <!-- yet another scroller. (C) 199
    6 Dmitry Altukhov, ady@demos.su -->
    <!--

         Use this code for whatever purposes...
          provided that you
         retain the copyright notice. I accept
         no responsibility for any disasters
         caused by this monster. Cannot guess
         any other cool places for scrollers.
         And MSIE marquee emulation in JS?!
         Er... Mozilla clears windows too
         slow in JS...
    -->

    <SCRIPT LANGUAGE="JavaScript">
    <!-- roll it

    var rate, pos=0;
    function roll() {
        var loop, top, msg1;
            var msg="Hello World";
            for(var i=0; i<10; i++)
               {
                msg = msg + ("  Hello World");
               }
    //move on, make a scrolly...
    and who said that scrollers suck?! ;-)

            top=(130/msg.length)+1;
        for(loop=0; loop<top; loop++)
     msg+=" "+msg;
        msg1=msg.substring (pos, pos+130);
    window.defaultStatus=msg1;
    if (pos++ == 130) pos=0;

    //come on, the poor thing can't roll
    any faster... buy a Ferrari. ;-)

        rate=setTimeout("roll()",30);
    }

    // that's all folks ;-)
    // end -->
    </SCRIPT>
    </HEAD>
    <BODY BACKGROUND="back.gif"
    BGCOLOR="#FFFFFF" TEXT="#000040"
     LINK="#000080" VLINK="#400080"
     ALINK="#FF0000" onLoad="roll()">
    .....
    </BODY>
    </HTML>

Здесь приведен фрагмент страницы компании Демос разработанный Дмитрием Алтуховым (andy@demos.su). Как видно из этого примера, прокрутка осуществляется за счет изменения содержания отображаемой переменной msg1. Запускается бегущая строка в момент загрузки страницы по событию onLoad. Из комментария к примеру следует, что автор не несет ответственность за проблемы, которые может данный скрипт породить при его отображении программой просмотра на компьютере пользователя. Собственно проблема вызвана применением функции setTimeout. Изначально предполагалось, что программа просмотра, которая поддерживает JavaScript будет исполняться в среде многопотоковых операционных систем. В этом случае к моменту порождения нового потока старый уже завершится и setTimeout будет таким образом порождать последовательно исполняемые потоки. Однако, при реализации JavaScript в Netscape Navigator 2.0 не все было выполнено аккуратно, и setTimeout стала просто "пожирать" ресурсы компьютера. Системы Windows 3.x вообще не являются много поточными и в них просто происходит переполнение стека при выполнении данного скрипта. Весь вопрос заключается только в том, как быстро "упадет" система, что зависит от объема исполняемой по setTimeout функции. Самое лучше, что можно посоветовать, если вдруг на экране появляется прокрутка - поскорее покинуть такую страницу. Справедливости ради следует отметить, что прокрутку можно организовать и другими способами, поэтому ее появление не обязательно должно вызывать крах системы или крах программы просмотра.

Прокручивать можно и вертикальные фрагменты. Достигается это за счет использования полей типа TEXTAREA.

Пример 2.5

    <HTML>
    <!--
    Author:	Pavel Khramtsov
    Date:	February 25, 1996
    -->
    <HEAD>
    <TITLE>Text Block Scrolling</TITLE>
    <SCRIPT LANGUAGE="JavaScript">
    var line_beak = "\n"
    var screen = ""

    //
     1234567890123456789012345678901234567890
        screen += "Будем прокручивать			"
        screen += "Данный фрагмент текста			"
        screen += "В поле типа				"
        screen += "TEXTAREA                   	"
        screen += "формы term				"

        for(i=0;i<6;i++)
        screen += "                            "

    var i=0
    var j=40
    flag = 0
    function scroll()
             {
              if( flag== 0 )
                {
                 display_str = ""
                 for(k=0;k<11;k++)
                    {
                     display_str += screen.substring(i,j)
                     i +=40;j = i + 40
                     if(i>400) i = 0
                    }
          window.document.term.work_field.value =
          display_str
                 i += 40 ; if(i>400) i =0
                }
              id = setTimeout("scroll()",1000)
              return true
             }
    function change_button()
             {
              if(flag==0)
                {
                 flag = 1
                 return true
                }
              if(flag == 1)
                {
                 flag = 0
                 return false
                }
             }
    </SCRIPT>
    </HEAD>
    <BODY onLoad="scroll()">
    <CENTER>
    <H1>Text Block Scrolling.</H1>
    <HR>
    <FORM NAME=term>
    <TEXTAREA NAME=work_field COLS=40 ROWS=10
     WRAP=ON></TEXTAREA>
    <P>
    <INPUT NAME=alarm VALUE="Start/Stop" TYPE=button
     onClick="change_button()">
    </FORM>
    </CENTER>
    <HR>
    </BODY>
    </HTML>

Собственно все организовано также, как и в предыдущем примере, только прокрутка осуществляется в поле формы, для этого поля введен параметр переноса текста на новую строку, ширина поля подобрана под размер выводимых строк. Прокрутка осуществляется как по событию onLoad, так и при нажатии на кнопку "Start/Stop". В этом случае не надо немедленно покидать страницу с прокруткой, а достаточно просто ее остановить.

Другой часто встречающийся пример на страницах со скриптами JavaScript - идущие часы:

Пример 2.6

    <HTML>
    <HEAD>
    <TITLE> type_Document_Title_here </TITLE>
    <SCRIPT LANGUAGE="JavaScript">
    <!--
    var i=0
    adv_message = "             "
    advert = "Russian Research Center
    \"Kurchatov Institute\""
    adv_message += advert
    adv_message += "            "
    adv_length = advert.length
    function move_clock()
                  {

    // Clock
    day = new Date()
    clock_screen = day.getHours()+":
"+day.getMinutes()+":"+day.getSeconds()
    document.kuku.f_clock.value = clock_screen

    // Rolling Field
    document.kuku.adv_string.value =
    adv_message.substring(i,i+45)
    document.kuku.i_field.value = i
    i++
    if( i == adv_length + 44 ) i = 0
    id = setTimeout("move_clock()",100)
    }
    // end -->
    </SCRIPT>
    </HEAD>
    <BODY onLoad="move_clock()">
    This is the start point:<BR>
    <a href="#next">
    This the document start point.</a>
    kukuku
    <a name=next>
    <FORM NAME=kuku onSubmit="alert
    ('You submit data to server')" METHOD="GET">
    <INPUT TYPE=SUBMIT NAME=SABMIT
    VALUE=SUBMIT> Make a clock
    here:  <INPUT NAME=f_clock maxlength=8
     size=8> <P> <INPUT
    NAME=adv_string maxlength=45 size=45>
     <INPUT NAME=i_field size=10>
    </FORM>
    </BODY>
    </HTML>

Пример 2.7. Минимизация, максимизация окна и его удаление.

    <HTML>
    <!--
    Author:	HTMLed User
    Date:	May 16, 1996
    -->
    <HEAD>
    <title>Iconize</title>
    <script language="JavaScript">
    function make_icon()
                {
    window.open("test_icon.htm","test_icon",
    "resizable=yes,scrollbars=no,
    width=50,height=70")
                 window.close()
                }
    </script>
    </HEAD>
    <BODY>
    <h1>minimize page</h1>
    <form name=icon>
    <input name=icon_button type=button
    value=icon onClick="make_icon()">
    </form>
    </BODY>
    </HTML>

Пример 2.8. Максимизация окна:

    <HTML>
    <!--
    Author: Pavel Khramtsov
    Date:	May 16, 1996
    -->
    <HEAD>
    <script>
    function max_window()
                 {
    window.open("iconize.htm","new",
    "toolbar=yes,scrollbars=yes,directories=yes,
    status=yes,menubar=yes,resizable=yes,
    location=yes,width=700,height=500")
                  window.close()
                 }
    </script>
    </HEAD>
    <BODY>
    <center>
    <a href="" onClick="max_window()"><
    img src=back.jpg></a>
    </center>
    </BODY>
    </HTML>


Единство в многообразии

2.12. Единство в многообразии

В заключении следует отметить, что JavaScript - это не единственный язык управления сценариями просмотра документов. Microsoft подготовила свою версию аналогичного языка - VBScript на основе Visual Basic. Кроме того, управлять сценарием просмотра можно и из Java-applet'ов, что конечно сложнее, но зато более надежно и безопасно. При этом программист получает все преимущества наследования и прочие атрибуты объектно-ориентированного программирования. В конце концов для создания фреймов и окон можно использовать атрибуты соответствующих контейнеров HTML, которые позволяют делить рабочую область экрана на фрагменты, перекрывать объекты и восстанавливать первоначальный вид страницы. Таким образом, к настоящему времени существует по меньшей мере три способа управления сценариями просмотра гипертекстовых баз данных Web, каждый из которых по своему хорош. Какой из них выбрать - это дело автора Website.

Следуя логике авторов JavaScript и направлениям развития World Wide Web следует ожидать появления новых типов объектов и изменения свойств существующих. Кроме этого, видимо, появятся глобальные переменные и функции. Порождение абстрактных типов тоже не за горами, хотя от такой новации не очень понятно, кто выиграет. Постепенно совершенствуется и поддержка встроенных функций на разных платформах, так, например, функция random() реализована в настоящее время везде, что не исключает использование и своих собственных датчиков случайных чисел. И еще, скорее всего, следует ожидать компилятора JavaScript для клиента. Будет ли эта возможность встроена в Navigator или это будет отдельный модуль - пока не ясно, но появление библиотек функций - движение в этом направлении, хорошо согласующееся с принципами кэширования гипертекстовых страниц.


Приемы программирования на JavaScrip

2.13. Приемы программирования на JavaScript

Проще всего начать изучение программирования на каком-либо языке с простых примеров, которые читатель может немедленно проверить. С подачи авторов языка С все современные книги по программированию начинаются с примера печати фразы "Hello world" в разных ее проявлениях ("Hello Java", "Hello Perl" и т.п.). Для JavaScript точного аналога такого подхода найти нельзя. Но мы попробуем проиллюстрировать на простых примерах основные приемы программирования.

Аналогом "Hello world" можно считать выдачу сообщения в отдельном окне, которое порождается при нажатии на гипертекстовую ссылку:

Пример 2.9. Программа выдачи простого предупреждения при выборе гипертекстовой ссылки. Схема URL - JavaScript

    <HTML>
    <HEAD>
    <title>Самый первый пример
     JavaScript</title>
    </HEAD>
    <BODY>
    <center>
    <h1>Мой первый пример JavaScript</h1>
    <hr>
    </center>
    В текст этого документа внедрена гипертекстовая
    ссылка
    <a href="javascript:window.alert(
    'Do you speak English?')">
    "Don`t click here"
    </a>.
    Любопытно, что будет,
    если все-таки выбрать?
    </BODY>
    </HTML>

В данном примере среди текста документа расположена гипертекстовая ссылка "Don`t click here". Если ее выбрать при просмотре (кликнуть мышкой), то на экране появится окно-предупреждение с вопросом: "Do you speak English?".

Генерация этого окна осуществляется специальным методом window.alert, который выполняется при выборе гипертекстовой ссылки. Если быть более точным, то в качестве URL информационного ресурса, который следует загрузить при переходе по данной гипертекстовой ссылке, используется схема JavaScript - расширение спецификации URI для программирования сценариев просмотра гипертекстовых документов World Wide Web. В этом случае выполнение JavaScript-программы происходит при выборе гипертекстовой ссылки, а сам код программы записан как URL.

Рис. 2.1. Выполнение скрипта при выборе гипертекстовой ссылки

Добиться такого же эффекта можно и другим способом, не прибегая к новой схеме URL. Для этой цели можно использовать событие, которое генерируется программой-навигатором при выборе гипертекстовой ссылки - Click.

Пример 2.10. Программа выдачи простого предупреждения по событию Click при выборе гипертекстовой ссылки

    <HTML>
    <HEAD>
    <title>Самый первый пример
     JavaScript</title>
    </HEAD>
    <BODY>
    <center>
    <h1>Мой первый пример JavaScript</h1>
    <hr>
    </center>
    В текст этого документа внедрена гипертекстовая
     ссылка
    <a href="javascript:void(0)"
    onClick="window.alert('Do you speak
     English?')">"Don`t click here"</a>.
    Любопытно, что будет,
    если все-таки выбрать?
    </BODY>
    </HTML>

Обойтись без новой схемы URL здесь также не удается, но она используется только для того, чтобы после выбора гипертекстовой ссылки в рабочем поле программы навигатора не появлялось пустой страницы, или не приходилось загружать вновь старую страницу. Для обработки события используется конструкция onClick, которая реализует обращение к обработчику (handler) данного события, который в свою очередь вызывает выполнение кода, записанного вслед за onClick.

Размещение кода программы на JavaScript непосредственно в тагах HTML является обычным делом, но не всегда бывает удобным. Наиболее часто JavaScript-код выносят в специальный HTML-контейнер SCRIPT(</script>.....</script>). Для того, чтобы продемонстрировать применение этого подхода, видоизменим наш пример следующим образом:

Пример 2.11. Применение контейнера SCRIPT для размещение JavaScript-кода

    <HTML>
    <HEAD>
    <title>Самый первый пример
    JavaScript</title>
    <script language="JavaScript">
    <!-- Start the text of programme
    function question()
             {
              window.alert("Do you speak English?");
             }
    // -->
    </script>
    </HEAD>
    <BODY>
    <center>
    <h1>Мой первый пример JavaScript</h1>
    <hr>
    </center>
    В текст этого документа внедрена гипертекстовая
    ссылка
    <a href="javascript:void(0)"
    onClick="question()">"Don`t click
    here"</a>. Любопытно, что будет,
     если все-таки выбрать?
    </BODY>
    </HTML>

Данный пример развивает применение JavaScript-кода для обработки события Click. Но только в этом случае мы не размещаем весь код обработки события в атрибуте onClick. В данный атрибут помещается только вызов функции, которая будет обрабатывать это событие. Само тело функции размещено в заголовке HTML-документа внутри тагов <script ...> и </script>

Для того, чтобы защитить текст функции от интерпретации браузерами, которые не поддерживают JavaScript, мы поместили код в HTML-комментарий. При этом последняя строка этого комментария должна начинаться как комментарий JavaScript, чтобы браузер, понимающий JavaScript, не интерпретировал ее как часть JavaScript-кода.

Различные браузеры поддерживают разные версии JavaScript, что накладывает определенные ограничения при написании сценариев JavaScript. Для того, чтобы максимально адаптировать свою программу к конкретному типу программного обеспечения, часто включают проверку версии и имени программы-браузера в JavaScript.

Пример 2.12. Получение типа программы просмотра HTML-страниц

    <HTML>
    <HEAD>
    <title>Test of Browser name</title>
    </HEAD>
    <BODY>
    <h1 align=center>Проверка имени типа
     браузера;</h1>
    <hr>
    Для того, чтобы получить имя вашей программы просмотра
    выберите кнопку "Browser"<br>
    <center>
    <form name=kuku>
    <input type=button name=browser value=Browser
     onClick="window.alert(window.navigator.appName)">
    </form>
    </BODY>
    </HTML>

Данная программа в точности повторяет пример 2.10, но в окне предупреждения выдает имя программы просмотра HTML-страниц (window.navigator.appName). Вообще говоря, в простом сообщении о типе программного обеспечения большого смысла нет, но если вставить проверку данного имени в текст HTML-страницы и реализовать условную компиляцию страницы, то тогда обращение к данной конструкции JavaScript будет оправданным:

Пример 2.13. Условная генерация текста страницы по типу программы просмотра

    <HTML>
    <HEAD>
    <title>Test of Browser name</title>
    </HEAD>
    <BODY>
    <h1 align=center>Проверка имени
    типа браузера</h1>
    <hr>
    <script language=JavaScript>
    <!--
    if(window.navigator.appName == "Netscape")
    {
    document.write("<br><center><
    font color=steelblue size=+5>");
    document.write("У вас хороший навигатор.");
    document.write("</font></center>");
    }
    else
    {
    document.write("<font color=red size=+3>
    Необходим Netscape Navigator
     версии 3.0 и выше.</font>");
    window.alert("Down load new version
    of your browser now.");
    }
    // -->
    </script>
    </BODY>
    </HTML>

В данном примере текст JаvaScript-программы размещен непосредственно в теле документа. При его загрузке, когда HTML-интерпретатор доходит до контейнера SCRIPT, вызывается JavaScript-интерпретатор. В этот момент будет проверяться условие, которое содержится в операторе if. В зависимости от результата проверки этого условия остальной текст страницы примет тот или иной вид в зависимости от типа программы просмотра. При просмотре данного документа программой отличной от Netscape Navigator будет выдано еще и окно предупреждения.

Рис.2.2. При загрузке был определен Netscape Navigator в качестве программы-браузера HTML-страниц

Вообще говоря, проверить тип программы просмотра можно на сервере протокола HTTP и передать программе просмотра уже готовую страницу без условной генерации ее содержания. Но это возможно только в том случае, когда автор страницы имеет возможность программировать на машине где установлен сервер и имеет возможность администрировать этот сервер. В ряде случаев, когда место под Website арендуется и в договоре аренды нет пункта, обеспечивающего управление ресурсами сервера, в этом случае программы с условной генерацией содержания страниц бывают чрезвычайно полезными. Другой случай - это работа в локальном режиме без сервера. Здесь JavaScript является единственным средством управления просмотром. Существует еще ряд случаев, когда применение контейнера SCRIPT в теле документа является вполне оправданным, но на них мы остановимся позже в контексте решения конкретных задач управления сценариями просмотра.

Однако, чаще всего, текст JavaScript-кода размещают в виде описания функций в заголовке документа, что делает использование такого кода более удобным. Связано это с двумя моментами, которые следует учитывать при написании JavaScript-программ: область действия описания программы (из каких частей документа или страницы она видна, а следовательно, на нее можно сослаться как на функцию, и принципы интерпретации кода при просмотре документа.

Если код размещается в теле документа, то он интерпретируется только в случае переразметки отображаемого документа (загрузка, изменение параметров окна, перезагрузка). Если текст размещен в заголовке, то на него можно сослаться из любого места документа, а точнее из любого обработчика событий, которые указываются как атрибуты HTML-тагов.

Вернемся теперь снова к примеру 2.13, но только разместим теперь код JavaScript не в тексте документа, а в отдельном файле:

Пример 2.14. Размещение скрипта в отдельном файле (netscape.jsc)

    <HTML>
    <HEAD>
    <title>Test of Browser name</title>
    </HEAD>
    <BODY>
    <h1 align=center>Проверка имени типа
    браузера</h1>
    <hr>
    <script language=JavaScript src=netscape.jsc>
    </script>
    </BODY>
    </HTML>

В данном случае текст условной генерации страницы размещен во внешнем файле. При загрузке страницы этот текст докачивается программой просмотра и исполняется так же, как если бы он размещался в документе. Любопытно, что при просмотре текста документа через опцию "View Source" текст скрипта не отображается, что дает возможность скрыть его содержание от пользователя. В самом файле, который содержит конструкции JavaScript, HTML-таги не используются:

Пример 2.15. Содержание файла netscape.jsc, ссылка на который установлена в атрибуте SCR тага <SCRIPT > из примера 6

    if(window.navigator.appName == "Netscape")
      {
       document.write("<br><center><
       font color=steelblue size=+5>");
       document.write("У вас хороший навигатор.");
       document.write("</font></center>");
      }
    else
      {
       document.write("<font color=red size=+3>
       Необходим Netscape Navigator
        версии 3.0 и выше.</font>");
       window.alert("Down load new version
       of your browser now.");
      }

На этом можно закончить вступительную часть, посвященную примерам JavaScript-кода, и перейти к более планомерному изложению приемов программирования на JavaScript, если бы не одно "но". Дело в том, что любой автор, который собирается излагать программирование на JavaScript, встречается с проблемой постепенного наращивания сложности примеров. Материал надо начинать излагать "от печки", но вот этой самой печки нет. Все программы на JavaScript (Client-site JavaScript) - это в той или иной мере программы обработки событий, которые связаны с теми или иными информационными объектами. Без изучения этих объектов нельзя начинать ничего программировать.

Однако, существует лазейка в этом заколдованном круге, которой мы и воспользуемся. Это схема JavaScript универсального локатора ресурсов (URL). В наших примерах мы уже использовали эту схему. Она помогала нам открывать окно-передупреждение при выборе гипертекстовой ссылки (пример 2.10) и избегать перезагрузки страниц (пример 2.11). Мы будем пользоваться этой схемой вызова JavaScript-кода до тех пор, пока не появится в нашем рассмотрении объект (контейнер HTML) с атрибутом обработки события.

После этого небольшого отступления начнем рассматривать приемы программирования на JavaScript в соответствии с иерархией объектов этого языка, начиная с самого старшего объекта и двигаясь вглубь дерева объектов: от объекта "окно" к элементам формы.


Примеры манипулирования окнами

2.14. Примеры манипулирования окнами

Как это не покажется странным, но в наших простых примерах мы уже начали манипулировать окнами. Открытие окна-пердупреждения - это операция над объектом "окно". Определим свойства объекта "окно", методы изменения этих свойств и события, которые могут быть связаны с объектом типа "окно". При этом мы не ставим перед собой задачи описания всех известных в JavaScript свойств, методов и событий, связанных с объектами этого класса. Определим только те, которые понадобятся в наших примерах.

Методы

Методы позволяют нам создать на экране видимое окно, закрыть окно и проделать с ним другие операции. С методом window.alert мы уже знакомы. Методы window.confirm и window.prompt используются для получения ответов от пользователя:

Пример 2.16. Использование методов Alert, Prompt, Confirm

    <HTML>
    <HEAD>
    <title>Window simple example</title>
    </HEAD>
    <BODY>
    <center>
    <h1>Simple window methods</h1>
    <hr>
    </center>
    <ul>
    <li><a href="javascript:window.alert
    ('Attention!!! press the button to continue.')
    ">Метод Alert.</a>
    <li><a href="javascript:window.status
    =window.confirm('Answer yes or no.');void(0);
    ">Метод Confirm.</a>
    <li><a href="javascript:window.status
    =window.prompt('Type string here:','');void(0);
    ">Метод Prompt.</a>
    </ul>
    </BODY>
    </HTML>

В данном примере при вызове метода confirm появляется окно с просьбой подтверждения некоторого действия, при использовании метода prompt - окно с приглашением ввести информацию в поле ввода. В двух последних случаях если не указать функции void(0), произойдет замещение текста HTML-документа на значение, которое возвращают эти два метода. Если внимательно смотреть за полем окна навигатора, в котором отображается информация о загрузке документов, то там можно будет увидеть возвращаемые значения, т.к. они присваиваются свойству status текущего окна, которое связано именно с этим полем, и которому можно присваивать значения, и, следовательно, эти значения будут отображаться в этом поле.

Однако, следует признать, что рассмотренные выше методы используются крайне редко. А тем более в приведенном здесь контексте, поэтому поспешим рассмотреть методы открытия и закрытия окна.

Открыть окно можно используя метод open:

Пример 2.17. Метод window.open

    <HTML>
    <HEAD>
    <title>Window simple example</title>
    </HEAD>
    <BODY>
    <center>
    <h1>Open window methods</h1>
    <hr>
    </center>
    Если выбрать гипертекстовую ссылку
    <a href="javascript:window.open('simple8.htm',
'example','scrollbars,
    resizable,width=300,height=200');void(0);
    ">Открыть окно</a>, то
    будет открыто новое окно. Чаще всего этот
     прием используют
    при создании контекстно зависимых меню.
    </BODY>
    </HTML>

В данном случае при выборе гипертекстовой ссылки будет открыто окно шириной 300 пикселей и высотой 200 пикселей, в окне не будет никаких стандартных меню навигатора, и в окно будет загружен документ simple8.html. Метод open позволяет также определить области прокрутки, наличие или отсутствие стандартных меню навигатора и возможность изменения размеров окна. Последняя возможность зависит от платформы на которой исполняется скрипт.

Документ simple8.htm в свою очередь содержит код, который позволяет закрыть окно, открытое в примере 2.17:

Пример 2.18. Закрыть текущее окно

    <HTML>
    <HEAD>
    <title>Window simple example</title>
    </HEAD>
    <BODY>
    <center>
    <h1>Close window methods</h1>
    <hr>
    </center>
    Если выбрать гипертекстовую ссылку
    <a href="javascript:window.close();void(0);">
    Закрыть окно</a>, то
    данное окно будет закрыто.
    </BODY>
    </HTML>

В данном примере в качестве гипертекстовой ссылки используется ссылка на метод widow.close(), который закрывает текущее окно. На применении методов open и close построен принцип построения help. По методу open открывается окно подсказки. В него загружают текст документа-подсказки с заранее заготовленной ссылкой на закрытие окна. При выборе этой ссылки окно подсказки закрывается, и пользователь продолжает работать с основным окном навигатора.

Рассмотрим еще один метод, связанный с окном - метод scroll. Данный метод позволяет организовать прокрутку текста в окне.

Пример 2.19. Прокрутка текста в окне навигатора

    <HTML>
    <HEAD>
    <title>Scroll text</title>
    </HEAD>
    <BODY>
    <center>
    <h1>Прокрутка текста в окне
    навигатора</h1>
    <hr>
    [<a href="javascript:for(i=0;i<80;i++)
    {window.scroll(0,i*10)};void(0);">
    Прокрутить текст</a>]
    <hr>
    </center>
    Вообще говоря, проверить тип программы
    просмотра можно на сервере
    протокола HTTP и передать программе
    просмотра уже готовую страницу
    без условной генерации ее
    ...

    <hr>
    <center>[<a href=#top>Вернуться в начало</a>]</center>
    </BODY>
    </HTML>

От метода прокрутки мы перейдем теперь к методу, который позволит нам прокручивать документ более равномерно - это метод setTimeout. Большинству пользователей Web он знаком по бегущим строкам в поле статуса программы-навигатора. Метод setTimeout позволяет отложить выполнение функции, которая указывается в качестве аргумента на определенное количество миллисекунд. Системы прокрутки в этом случае рекурсивно вызывают свое выполнение через этот метод и тем самым порождают бесконечный цикл прокрутки.

Пример 2.20. Прокрутка текста по таймеру

    <HTML>
    <HEAD>
    <title>Scroll text</title>
    <script language=JavaScript>
    <!--
    i=0;flag=0;start_stop=0;
    function my_scroll()
             {
              if(start_stop==1)
                {
                 window.scroll(0,i*10);
                 if(flag==0) i++;
                 if(flag==1) i--;
                 if(i>80) {i=80;flag=1;}
                 if(i<0) {i=0;flag=0;}
                }
              setTimeout("my_scroll()",500);
             }
    function kuku()
             {
              if(start_stop == 0)
                {
                 start_stop =1;
                }
              else
                {
                 start_stop =0;
                }
             }
    // -->
    </script>
    </HEAD>
    <BODY onLoad=my_scroll()>
    <center>
    <h1>Прокрутка текста в окне навигатора</h1>
    <hr>
    [<a href="javascript:kuku();void(0);">
Запустить/Остановить</a>]
    <hr>
    </center>
    Вообще говоря, проверить тип программы просмотра
     можно на сервере протокола HTTP и передать
      программе просмотра уже готовую страницу
    без условной генерации ее
    ...
    <hr>
    <center>[<a href=#top>Вернуться в начало</a>]</center>
    </BODY>
    </HTML>

В данном примере мы не стали писать текст программ в самих гипертекстовых ссылках и вынесли его в заголовок документа. В данном случае это облегчает чтение текста и отладку программы. Текст прокручивается через каждые 500 миллисекунд, что делает прокрутку более плавной. В примере мы впервые применили событие onLoad. Это событие запускает процесс выполнения нашей функции. Прокрутка текста осуществляется в двух направлениях: сначала снизу вверх, а по достижении конца документа сверху вниз.

Следует подробно остановиться на особенностях исполнения метода setTimeout. Особенно в части запуска и останова процедуры прокрутки. Рассматривать алгоритм ее исполнения лучше всего в контексте многопоточных или многозадачных систем. Функция my_scroll - это отдельный процесс (поток), который исполняется программой-навигатором. В момент, когда интерпретатор навигатора встречает метод setTimeout, он создает новый процесс (поток), эквивалентный функции my_scroll, и откладывает его исполнение на 500 миллисекунд. После этого он продолжает выполнять текущий процесс (поток). В нашем случае вызов метода setTimeout - это последняя команда функции my_scroll, поэтому после нее ничего не выполняется, а сам процесс или поток, связанный с текущей функцией my_scroll после обработки setTimeout уничтожается. Однако, новый процесс (поток) уже создан в памяти и ждет начала своего исполнения.

В нашем примере новый процесс поток порождается всякий раз, как интерпретатор достигает метода setTimeout, а уничтожается, когда интерпретатор доходит до последнего оператора этого потока. Такой алгоритм выполнения заставляет расположить вызов setTimeout за пределами блока проверки условий скроллинга. Если мы расположим его внутри, то при останове скроллинга новый процесс не будет порожден, а это значит, что и кода, который будет реагировать на флаг скроллинга после останова, не будет существовать, т.е. запустить скроллинг не будет никакой возможности. Именно по этой причине функция my_scroll начинает исполняться сразу после загрузки документа по событию onLoad, но т.к. флаг скроллинга 0, то прокрутки текста не происходит. Процесс порождается все время, пока страница загружена в текущее окно навигатора.

Во второй версии навигатора данная функция была реализована не очень аккуратно, что приводило к переполнению программного стека и краху программы-навигатора.


Гипертекстовые ссылки и картинки

2.15. Гипертекстовые ссылки и картинки

Гипертекстовые ссылки и картинки - это свойства объекта "документ", который в свою очередь является частью объекта "окно". И гипертекстовые ссылки, и картинки составляют встроенные массивы, к которым можно обращаться по индексу. Рассмотрим несколько примеров программирования массивов гипертекстовых ссылок и картинок.

Просмотр фотографий образцов через список гипертекстовых ссылок

Вся страница размечается как таблица, состоящая из одной строки в две ячейки. В первой ячейке размещается список выбора альтернатив, во второй ячейке размещаются картинки, выбранные при помощи списка альтернатив. Все элементы списка помечены как гипертекстовые ссылки. У каждой ссылки определено событие onSelect. Для того, чтобы не происходил переход к другой странице, а выполнялась только обработка события, в поле href необходимо определить вызов JavaScript. Ниже приведен пример реализации такой компоновки и пример кода самой страницы.

Пример 2.21. Изменение картинки через гипертекстовую ссылку

    <HTML>
    <HEAD>
    <TITLE>HTTPD\HTDOCS\JAVASCR\EXAMPLE1</TITLE>
    <SCRIPT>
    <!-- Защитили текст скрипта от старых браузеров
    function l_image(a)
             {
              document.images[1].src=a
             }
    // -->
    </SCRIPT>
    </HEAD>
    <BODY TEXT="#000000" BGCOLOR="#FFFFCC"
    LINK="#0000EE" VLINK="#551A8B" ALINK="#FF0000">
    <H1>
    <FONT COLOR="#000099">Просмотр фотографий
    образцов</FONT>
    </H1>
    <center>
    <TABLE COLS=2 WIDTH="100%" >
    <CAPTION>
    <FONT COLOR="#000099" SIZE=+2>
    Образцы бытовой техники</FONT>
    </CAPTION>
    <TR>
    <TD>
    <UL>
    <LI><A HREF="javascript:l_image('freeze.gif')"
>Холодильник</A> </LI>
    <LI><A HREF="javascript:l_image('dust.gif')"
>Пылесосы</LI>
    <LI><A HREF="javascript:l_image('toster.gif')"
>Тостер</LI>
    <LI><A HREF="javascript:l_image('cook.gif')"
    >Варочный стол</LI>
    <LI><A HREF="javascript:l_image('oven.gif')"
>Духовка</LI>
    <LI><A HREF="javascript:l_image('wash.gif')"
    >Стиральная машина</LI>
    <LI><A HREF="javascript:l_image('dishwash.gif')"
>Посудомоечная машина</LI>
    </UL>
    </TD>
    <TD ALIGN=CENTER VALIGN=CENTER><
    IMG SRC="dust.gif" NAME="tool" > </TD>
    </TR>
    </TABLE>
    </center>
    </BODY>
    </HTML>

В данном примере при выборе гипертекстовой ссылки происходит вызов функции l_image(), которая изменяет значение атрибута SRC в контейнере IMG. Таким образом, можно организовать просмотр различных товаров в одном единственном графическом окне.

Изменение картинки путем выбора предмета из списка.

Вся страница размечается как таблица, состоящая из одного столбца в две ячейки. В первой ячейке размещается выпадающее меню выбора альтернатив, во второй ячейке размещаются картинки, выбранные при помощи списка альтернатив. При выборе альтернативы из меню происходит событие onChange, которое вызывает функцию l_image. Ниже приведен пример реализации такой компоновки и пример кода самой страницы.

Пример 2.22. Выбор картинки из списка

    <!-- элементы заголовка -->
    ...
    <SCRIPT>
    <!-- Защитили текст скрипта от старых браузеров
    pictures = new Array()
    for(i=0;i<8;i++)
       {
        pictures[i] = new Image()
        if(i==0) pictures[i].src = "freeze.gif"
        if(i==1) pictures[i].src = "dust.gif"
        if(i==2) pictures[i].src = "toster.gif"
        if(i==3) pictures[i].src = "cook.gif"
        if(i==4) pictures[i].src = "oven.gif"
        if(i==5) pictures[i].src = "wash.gif"
        if(i==6) pictures[i].src = "dishwash.gif"
       }
    function l_image()
       {
        document.images[1].src = pictures
        [document.form1.item.selectedIndex].src
       }
    // -->
    </SCRIPT>

                                   [ На начало страницы ]

    Фрагмент HTML-разметки с вызовом функции
    изменения картинки:

    <center>
    <TABLE COLS=2 WIDTH="100%" >
    <CAPTION>
      <FONT COLOR="#000099" SIZE=+2>
      Образцы бытовой техники</FONT>
    </CAPTION>
      <TR>
        <th>
          <form name=form1>
          <select name=item onChange=l_image()>
            <option>Холодильник
            <option selected>Пылесос
            <option>Тостер
            <option>Варочный стол
            <option>Духовка
            <option>Cтиральная машина
            <option>Посудомоечная машина
          </select>
          </form>
        </th>
      </tr>
      <tr>
        <th ALIGN=CENTER VALIGN=CENTER><
        IMG SRC="dust.gif" NAME="tool" > </th>
      </TR>
    </TABLE>
    [ <a href=#top>На начало страницы</a> ]
    <HR>
    </center>

В данном примере картинка выбирается как элемент списка. При этом происходит событие onChange, и по нему определяют, какой именно элемент был отобран.

Листание картинок и их автоматический просмотр

Вся страница размечается как таблица, состоящая из одного столбца в две ячейки. В первой ячейке размещаются три кнопки управления просмотром картинок "Вперед", "Старт/Стоп", "Назад". Во второй ячейке размещаются картинки, выбранные путем листания. Листание осуществляется кнопками "Вперед" и "Назад". Возможен и другой вариант выбора картинки, когда система сама через некоторый промежуток времени периодически меняет картинки. Для этого следует сначала запустить автоматическую смену картинок, нажав на кнопку "Старт/Стоп", а потом, когда будет показана нужная картинка, остановить автоматическое листание, снова нажав на кнопку "Старт/Стоп". Ниже приведен пример реализации такой компоновки и пример кода самой страницы.

Пример 2.23. Листание и прокрутка картинок

    <SCRIPT>
    <!-- Защитили текст скрипта от старых браузеров
    pictures = new Array()
    for(i=0;i<8;i++)
       {
        pictures[i] = new Image()
        if(i==0) pictures[i].src = "freeze.gif"
        if(i==1) pictures[i].src = "dust.gif"
        if(i==2) pictures[i].src = "toster.gif"
        if(i==3) pictures[i].src = "cook.gif"
        if(i==4) pictures[i].src = "oven.gif"
        if(i==5) pictures[i].src = "wash.gif"
        if(i==6) pictures[i].src = "dishwash.gif"
       }
    n=0;
    flag=0;
    function next_image()
       {
        if(flag==0)
          {
           n++;if(n>6) n=0;
           document.images[1].src = pictures[n].src
          }
       }
    function back_image()
       {
        if(flag==0)
          {
           n--;if(n<0) n=6;
           document.images[1].src = pictures[n].src
          }
       }
    function scroll_image()
       {
        if(flag==1)
          {
           n++;if(n>6) n=0;
           document.images[1].src = pictures[n].src
          }
        setTimeout("scroll_image()",1500);
       }
    function start_stop()
       {
        if(flag==0)
          {
           flag=1
          }
        else
          {
           flag=0
          }
       }
    // -->
    </SCRIPT>

                                   [ На начало страницы ]

    <TABLE COLS=2 WIDTH="100%" >
    <CAPTION>
      <FONT COLOR="#000099" SIZE=+2>
      Образцы бытовой техники</FONT>
    </CAPTION>
      <TR>
        <th>
          <form name=form1>
          <input name=f type=button
          value=Вперед onClick=next_image()>
          <input name=ss type=button value="Старт/Стоп"
         onClick=start_stop()>
          <input name=b type=button
          value=Назад onClick=back_image()>
          </form>
        </th>
      </tr>
      <tr>
      <th ALIGN=CENTER VALIGN=CENTER><
      IMG SRC="dust.gif" NAME="tool" > </th>
      </TR>
    </TABLE>

В этом примере мы используем те же средства, что использовали и для организации скроллинга. Алгоритм останова и запуска прокрутки тот же, что и при скроллинге окна.

Пример использования client-site imagemap

В данном примере мы хотим показать возможность изменения картинки imagemap с одновременным изменением гипертекстовых ссылок, связанных с этой картинкой. Такая возможность существует только в том случае, если все картинки, которые мы хотим использовать в качестве imagemap, имеют одинаковую компоновку. Мы реализуем просмотр вариантов в виде горизонтального меню в верхней строке таблицы и imagemap - в нижней строке таблицы, т.к. наша картинка имеет формат ландшафта.

Пример 2.24. Динамический imagemap

    <SCRIPT>
    <!-- Защитили текст скрипта от старых браузеров
    function print_form(a)
             {
              document.form1.kuku.value = a
             }
    function set_image(a)
             {
              if(a == "bosh")
                {
                 document.form1.kuku.value=
                 "Техника фирмы Bosh."
                 document.links[5].href =
                  "javascript:print_form
                  (\'Refregarator: Bosh-10245
                  (-24C;Remote Control)\')"
                 document.links[6].href =
                  "javascript:print_form
                  (\'Aero-Filter: Bosh-1212(
                  Steel Filter)\')"
                 document.links[7].href =
                 "javascript:print_form
                 (\'Oven: Bosh-3432
                 (Варочный стол + духовка)\')"
                 document.links[8].href =
                 "javascript:print_form
                 (\'Wash mashine:
                  Bosh-2343(Dry Mode)\')"
                 document.links[9].href =
                 "javascript:print_form
                 (\'Dish wash machine: Bosh-DW-44\')"
                 document.links[10].href =
                 "javascript:print_form
                 (\'Water pipe: Bosh
                 (Steel + Ceramic)\')"
                }
              if(a == "Indesit")
                {
                 document.form1.kuku.value=
                 "Техника фирмы Indesit."
                 document.links[5].href =
                 "javascript:print_form
(\'Indesit-105(-18C)\')"
                 document.links[6].href =
                 "javascript:print_form
(\'Indesit-101(Steel Filter)\')"
                 document.links[7].href =
                 "javascript:print_form
(\'Indesit-3432(Варочный стол + духовка)\')"
                 document.links[8].href =
                 "javascript:print_form
(\'Indesit-343(Wash only)\')"
                 document.links[9].href =
                 "javascript:print_form
(\'Indesit-DWR-Safe\')"
                 document.links[10].href =
                 "javascript:print_form
                 (\'No in the frame\')"
                }
              if(a == "candy")
                {
                 document.form1.kuku.value=
                 "Техника фирмы Candy."
                 document.links[5].href =
                 "javascript:print_form
(\'Candy-122(-24C;Hidden model)\')"
                 document.links[6].href =
                 "javascript:print_form
(\'Candy-12(Steel Filter + Carbone Filter)\')"
                 document.links[7].href =
                 "javascript:print_form
(\'Candy(Варочный стол + духовка)\')"
                 document.links[8].href =
                 "javascript:print_form
                 (\'Candy-343(Dry Mode)\')"
                 document.links[9].href =
                 "javascript:print_form
(\'Candy-DWR Elite\')"
                 document.links[10].href
                 = "javascript:print_form
(\'Candy(Ceramic)\')"
                }
              document.images[1].src= a+".gif"
             }
    // -->
    </SCRIPT>

                                   [ На начало страницы ]

    <MAP name=kitchen_map>
      <area name=freeze shape=rect coords="14,11,88,171"
      href="javascript:void(0)">
      <area name=aero shape=rect coords="179,12,238,58"
      href="javascript:void(0)">
      <area name=oven shape=rect coords="179,95,237,172"
      href="javascript:void(0)">
      <area name=dry shape=rect coords="239,95,297,173"
      href="javascript:void(0)">
      <area name=wash shape=rect coords="297,96,370,173"
       href="javascript:void(0)">
      <area name=kran shape=rect coords="90,95,138,172"
      href="javascript:void(0)">
    </map>
    <TABLE border=0>
    <CAPTION>
      <FONT COLOR="#000099" SIZE=+2>Образцы
      бытовой техники</FONT>
    </CAPTION>
      <TR>
        <TH>
        <a href="javascript:set_image('bosh')"
        >Bosh</a>
        </th>
        <th>
        <a href="javascript:set_image('idezit')"
        >Indesit</a>
        </th>
        <th>
        <a href="javascript:set_image('candy')"
        >Candy</a>
        </th>
      </TR>
      <TR>
        <th colspan=3><IMG SRC="Bosh.gif"
        NAME="tool" USEMAP=#kitchen_map></th>
      </TR>
      <tr><th colspan=3><form
      name=form1><input name=kuku size=40
      value=Выбери набор техники.></form>
      </th></tr>
    </TABLE>

В данном примере меняются не только картинки, но и стеки гипертекстовых ссылок под ними. Для каждой новой картинки определяются свои собственные гипертекстовые ссылки.


Практическое введение в программирование на JavaScript

1. Принципы гипертекстовой разметки. Структура документов 1.1. Группы тагов НТМL
1.2. Контейнеры HTML-документа
1.3. Средства описания таблиц в HTML
1.4. Использование таблиц в дизайне страницы
1.5. Фреймы
1.6. Формы
1.7. Апплеты
1.8. Каскадные таблицы стилей (Cascad Style Sheets) 2. Управление просмотром страниц Web-узла. JavaScript 2.1. Модель объектов JavaScript - объекты Navigator'а
2.2. Методы объектов и свойства объектов. Управление потоком вычислений
2.3. События
2.4. Массивы
2.5. Графика
2.6. Стеки гипертекстовых ссылок
2.7. Фреймы и окна
2.8. Наследование кода скриптов различными страницами
2.9. Java, JavaScript и Plug-ins
2.10. Встраивание в HTML-документ
2.11. Примеры скриптов
2.12. Единство в многообразии
2.13. Приемы программирования на JavaScript
2.14. Примеры манипулирования окнами
2.15. Гипертекстовые ссылки и картинки


Создание приложений Java

Добро пожаловать в мир Java!

В данном учебнике представлены подробные материалы по языку Java и многочисленные примеры программирования на Java. Вы познакомитесь с принципами и технологиями, положенными в основу этого языка, изучите библиотеки классов Java, научитесь пользоваться современными визуальными средствами разработки приложений Java, такими как Java WorkShop и Java Studio.

Многочисленные примеры приложений и аплетов помогут вам в кратчайшие сроки освоиться в мире Java.

Оглавление

  • 1. ВВЕДЕНИЕ В JAVA
    • Инструментарий разработчика
    • Мобильность Java
    • Базовые типы данных
    • Библиотеки классов Java
    • Указатели, которых нет
    • Массивы в Java
    • Сборка мусора
    • Особенности реализации классов в Java
  • 2. ПЕРВОЕ ПРИЛОЖЕНИЕ
    • Установка Java WorkShop
    • Запуск Java WorkShop
    • Исходный текст приложения HelloJava
  • 3. ПЕРВЫЙ АПЛЕТ
    • Создание проект аплета
    • Исходный текст аплета
    • Методы в классе HelloApplet
    • Исходный текст документа HTML
    • Изменяем исходный текст аплета
    • Метод paint
    • Метод getAppletInfo
  • 4. РИСОВАНИЕ В ОКНЕ АПЛЕТА
    • Контекст отображения
    • Установка атрибутов контекста отображения
    • Определение атрибутов контекста отображения
    • Рисование геометрических фигур
    • Аплет Draw
    • Исходные тексты аплета Draw
    • Проект для аплета Draw
  • 5. СОБЫТИЯ
    • Как обрабатываются события
    • События от мыши
    • Аплет LineDraw
    • Исходный текст аплета LineDraw
    • Описание исходного текста
  • 6. КОМПОНЕНТЫ
    • Кнопки
    • Переключатели
    • Списки класса Choice
    • Списки класа List
    • Текстовое поле класса Label
    • Текстовое поле класса TextField
    • Многострочное текстовое поле класса TextArea
    • Аплет FormDemo
  • 7. РАБОТА С СИСТЕМОЙ LAYOUT MANAGER
    • Режимы системы Layout Manager
    • Режим FlowLayout
    • Режим GridLayout
    • Режим BorderLayout
    • Режим CardLayout
    • Режим GridBagLayout
    • Аплет FormLayout
  • 8. РАБОТА С ПАНЕЛЯМИ
    • Создание панелей
    • Добавление панелей
    • Добавление компонент в панели
    • Рисование в окне панели
    • Создание нового класса на базе класса Panel
    • Аплет Options
    • Исходный текст аплета Options
    • Описание исходного текста аплета Options
  • 9. ОКНА И ДИАЛОГОВЫЕ ПАНЕЛИ
    • Окна класса Frame
    • Меню в окне класса Frame
    • Класс Menu
    • Класс MenuItem
    • Создание диалоговых панелей
    • Приложение MenuApp
    • Исходный текст приложения MenuApp
    • Описание исходного текста приложения MenuApp
  • 10. МНОГОПОТОЧНОСТЬ
    • Процессы, потоки и приоритеты
    • Реализация многопоточности в Java
    • Применение многопоточности для анимации
    • Синхронизация потоков
    • Потоки-демоны
    • Аплет Rectangles
    • Исходные тексты аплета Rectangles
    • Описание исходных текстов аплета Rectangles
  • 11. РАБОТА С ФАЙЛАМИ
    • Классы Java для работы с потоками
    • Стандартные потоки
    • Создание потоков, связанных с файлами
    • Запись данных в поток и чтение данных из потока
    • Закрывание потоков
    • Принудительный сброс буферов
    • Потоки в оперативной памяти
    • Класс Stream Tokenizer для разбора входных потоков
    • Класс String Tokenizer
    • Работа с файлами и каталогами при помощи класса File
    • Произвольный доступ к файлам
    • Приложение StreamToken
    • Приложение DirectFile
  • 12. СОЗДАНИЕ СЕТЕВЫХ ПРИЛОЖЕНИЙ
    • Адрес IP и класс InetAddress
    • Универсальный адрес ресурсов URL
    • Класс URL в библиотеке классов Java
    • Передача данных с использованием сокетов
    • Работа с потоковыми сокетами
    • Класс Socket
    • Использование датаграммных сокетов
    • Связь приложений Java с расширениями сервера Web
    • Аплет ShowChart
    • Приложения SocketServ и SocketClient
    • Аплет Form
  • 13. РАСТРОВЫЕ ИЗОБРАЖЕНИЯ И АНИМАЦИЯ
    • Загрузка и рисование растрового изображения
    • Класс Image
    • Ожидание загрузки изображений
    • Видео в окне аплета
    • Аплет CDRotation
  • 14. ЗВУК В АПЛЕТАХ JAVA
    • Загрузка и проигрывание звуковых файлов
    • Аплет PlayClip

Вперед


Введение в Java

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

Казалось бы, на сегодняшний день изобретены уже все языки программирования, какие только можно придумать. Но нет - появился еще один, с названием Java. Этот язык сумел получить весьма заметную известность за последние несколько лет, так как он ориентирован на самую популярную компьютерную среду - сеть Internet и серверы Web.

Язык Java произошел от языка программирования Oak (а не от C++, как думают многие). Oak был приспособлен для работы в Internet и затем переименован в Java. Подробнее об этом вы можете узнать, прочитав статью по адресу http://www.sun.ru/java/start/intro/history.html.

Изучая Java, вы будете приятно удивлены тем, что его синтаксис близок к синтаксису языка C++. Унаследовав самое лучшее от языка программирования C++, язык Java при этом избавился от некоторых недостатков C++, в результате чего на нем стало проще программировать. В этом языке нет, например, указателей, которые сложны в использовании и потенциально могут послужить причиной доступа программы к не принадлежащей ей области памяти. Нет множественного наследования и шаблонов, хотя функциональные возможности языка Java от этого не пострадали. Если вы умеете программировать на C++, для вас не составит особого труда изучить язык Java.

Почему вам нужно изучать новый язык программирования Java?

Если ответить на этот вопрос кратко, то потому, что он специально ориентирован на самые передовые технологии, связанные с сетью Internet. Растущая популярность Internet и, в особенности, серверов Web, создает для программистов новые возможности для реализации своих способностей.

Огромное преимущество Java заключается в том, что на этом языке можно создавать приложения, способные работать на различных платформах. К сети Internet подключены компьютеры самых разных типов - Pentium PC, Macintosh, рабочие станции Sun и так далее. Даже в рамках компьютеров, созданных на базе процессоров Intel, существует несколько платформ, например, Microsoft Windows версии 3.1, Windows 95, Windows NT, OS/2, Solaris, различные разновидности операционной системы UNIX с графической оболочкой XWindows. Между тем, создавая сервер Web в сети Internet, вы бы наверняка хотели, чтобы им могло пользоваться как можно большее число людей. В этом случае вас выручат приложения Java, предназначенные для работы на различных платформах и не зависящие от конкретного типа процессора и операционной системы.

Программы, составленные на языке программирования Java, можно разделить по своему назначению на две большие группы.

К первой группе относятся приложения Java, предназначенные для автономной работы под управлением специальной интерпретирующей машины Java. Реализации этой машины созданы для всех основных компьютерных платформ.

Вторая группа - это так называемые аплеты (applets). Аплеты представляют собой разновидность приложений Java, которые интерпретируются виртуальной машиной Java, встроенной практически во все современные браузеры.

Приложения, относящиеся к первой группе (мы будем называть их просто приложениями Java), - это обычные автономные программы. Так как они не содержат машинного кода и работают под управлением специального интерпретатора, их производительность заметно ниже, чем у обычных программ, составленных, например, на языке программирования C++. Однако не следует забывать, что программы Java без перетрансляции способны работать на любой платформе, что само по себе имеет большое значение в плане разработок для Internet.

Аплеты Java встраиваются в документы HTML, хранящиеся на сервере Web. С помощью аплетов вы можете сделать страницы сервера Web динамичными и интерактивными. Аплеты позволяют выполнять сложную локальную обработку данных, полученных от сервера Web или введенных пользователем с клавиатуры. Из соображений безопасности аплеты (в отличие от обычных приложений Java) не имеют никакого доступа к файловой системе локального компьютера. Все данные для обработки они могут получить только от сервера Web. Более сложную обработку данных можно выполнять, организовав взаимодействие между аплетами и расширениями сервера Web - приложениями CGI и ISAPI.

Для повышения производительности приложений Java в современных браузерах используется компиляция "на лету"- Just-In-Time compilation (JIT). При первой загрузке аплета его код транслируется в обычную исполнимую программу, которая сохраняется на диске и запускается. В результате общая скорость выполнения аплета Java увеличивается в несколько раз.

Язык Java является объектно-ориентированным и поставляется с достаточно объемной библиотекой классов. Так же как и библиотеки классов систем разработки приложений на языке C++, библиотеки классов Java значительно упрощают разработку приложений, представляя в распоряжение программиста мощные средства решения распространенных задач. Поэтому программист может больше внимания уделить решению прикладных задач, а не таких, как, например, организация динамических массивов, взаимодействие с операционной системой или реализация элементов пользовательского интерфейса.


Инструментарий разработчика

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

Первоначально средства разработки приложений и аплетов Java были созданы фирмой Sun Microsystems и до сих пор они пользуются популярностью. В сети Internet по адресу http://java.sun.com вы можете бесплатно получить набор Java Development Kit (JDK).

Пакетные средства

В JDK входят пакетные программы для компиляции исходных текстов приложений Java, виртуальная машина, программа автоматизированного создания документации по классам, справочник по классам Java и другие необходимые средства.

Интегрированные средства разработки

Для тех, кто привык пользоваться визуальными средствами разработки, доступны два других инструмента, созданных в Sun Microsystems. Это Java WorkShop 2.0 и Java Studio 1.0.

Интегрированная система разработки приложений Java WorkShop 2.0 содержит традиционные и визуальные средства программирования, отладчик, обширную справочную систему по классам и языку Java. С помощью Java WorkShop 2.0 вы можете создавать автономные приложения Java, аплеты, компоненты JavaBeans и собственные библиотеки классов.

На рис. 1 мы показали внешний вид главного окна приложения Java WorkShop 2.0.

pic01sm.gif (2791 bytes) Рис. 1. Главное окно приложения .

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Среди основных возможностей Java WorkShop 2.0 назовем синтаксическое выделение в окне редактирования, возможность удаленной отладки приложений Java, быстродействующий компилятор, а также способность работать на различных платформах.

Разработку элементов пользовательского интерфейса можно выполнять с помощью удобного средства визуального проектирования (рис. 2).

pic02sm.gif (3283 bytes) Рис. 2. Java WorkShop 2.0 позволяет разрабатывать пользовательский интерфес с помощью визуальных средств.

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Что же касается Java Studio 1.0, то эта система позволяет проектировать приложения вообще без программирования. Разработчик собирает приложения из готовых компонент, устанавливая между ними каналы передачи сообщений. Таким образом определяется логика работы приложения (рис. 3). Этот процесс чем-то напоминает сборку бытовой стереофонической системы из отдельных блоков, когда вы соединяете проводами входы и выходы усилителя, колонок, проигрывателя компакт-дисков и других устройств.

pic03sm.gif (2741 bytes) Рис. 3. Проектирование логики работы приложения.

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

В наборе компонент JavaBeans, доступных в Java Studio, есть компоненты, предназначенные для работы с ресурсами Internet, базами данных и таблицами, формами и так далее.

Проектирование пользовательского интерфейса в Java Studio, также как и в Java WorkShop, выполняется визуально (рис. 4).

Рис. 4. Визуальное проектирование пользовательского интерфейса.

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

pic04sm.gif (2356 bytes)

Подборку различных инструментальных средств, предназначенных для создания приложений Java, вы найдете по адресу http://www.sun.ru/download/download.html.

Браузеры

Для проверки работы аплетов вам следует установить браузер, способный работать с аплетами Java. Такая возможность есть во всех современных браузерах, поэтому вы можете выбрать любой из них. Лучше всего проверять работу создаваемых вами аплетов в различных браузерах. Это позволит вам обнаружить и решить проблемы несовместимости, прежде чем с ними столкнется пользователь.

Подключение к Internet

Заметим, что для запуска аплетов вам не нужно обязательно подключаться к Internet - вы можете встраивать аплеты в документы HTML, расположенные на локальном диске вашего компьютера и просматривать эти документы браузером просто как локальные файлы.

Автономные приложения Java работают под управлением специального интерпретатора (виртуальной машины Java), поэтому для их отладки вам также не потребуется сеть Internet.

Однако есть одно важное обстоятельство - аплеты, взаимодействующие с расширениями сервера Web, должны быть загружены именно с этого сервера. В противном случае их работа будет заблокирована по соображениям безопасности.

Если вы собираетесь проверять работу приложений и аплетов Java, взаимодействующих с сервером Web, вы можете воспользоваться собственным сервером в Internet или в корпоративной сети Intranet (если они у вас есть). Можно также установить сервер Web, входящий в комплект операционной системы Windows NT Workstation версии 4.0, или Personal Web Service для операционной системы Windows 95.

Документация и литература

На прилавках книжных магазинов вы можете найти несколько отечественных и переводных книг, посвященных программированию на языке Java. Большинство из них ориентировано на инструментарий JDK, созданный фирмой Sun, и содержат более или менее подробное описание классов Java.

Среди удачных назовем книгу Стефана Дэвиса с названием "Learn Java Now", которая может служить учебником по языку Java для тех, кто никогда не программировал на С и С++.

Среди переводных книг, которые можно встретить в продаже, отметим книгу Джона Родли "Создание JAVA-апплетов". Эта книга рассчитана на серьезных программистов, хорошо знающих язык программирования Java. Однако для тех, кто только начинает изучать язык Java, она может оказаться слишком сложной.

Другая книга, заслуживающая внимание, это книга Криса Джамса с названием "Java". После небольшого введения, рассчитанного на начинающих, в этой книге приводится описание более чем дюжины достаточно интересных аплетов с исходными текстами и комментариями.

Через сеть Internet вам доступны не только бесплатные средства разработки приложений и аплетов Java, но и грандиозные запасы документации и примеров программ.

В качестве отправной точки для поиска вы можете выбрать сервер разработчика этого языка - фирмы Sun Microsystems, расположенный по адресу http://www.sun.com. Помимо документации и примеров программ на Java, здесь вы найдете ссылки на другие ресурсы, посвященные этому языку программирования. Попробуйте также воспользоваться поисковыми серверами, такими как Jahoo! и Alta Vista, указав в качестве ключевого слово "Java".

Обращаем ваше внимание на cловарь Java-терминов (http://www.sun.ru/java/start/gloss.html), подготовленный Sun Microsystems специально для российских разработчиков приложений Java.

Вы также можете ознакомиться с примерами приложений Java которые находятся на российском сервере Sun Microsystems.,


Мобильность Java

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

В свое время вы слышали, что язык программирования С является мобильным. Это нужно понимать в том смысле, что имеется принципиальная возможность переноса программ C на различные платформы.

Однако следует отметить, что создание приложений, действительно работающих на разных платформах - непростая задача. К сожалению, дело не ограничивается необходимостью перекомпиляции исходного текста программы для работы в другой среде. Много проблем возникает с несовместимостью программных интерфейсов различных операционных систем и графических оболочек, реализующих пользовательский интерфейс.

Вспомните хотя бы проблемы, связанные с переносом 16-разрядных приложений Windows в 32-разрядную среду Windows 95 и Windows NT. Даже если вы тщательно следовали всем рекомендациям, разрабатывая приложения так, чтобы они могли работать в будущих версиях Windows, едва ли вам удастся просто перекомпилировать исходные тексты, не изменив в них ни строчки. Ситуация еще больше ухудшается, если вам нужно, например, перенести исходные тексты приложения Windows в среду операционной системы OS/2 или в оболочку X-Windows операционной системы UNIX. А ведь есть еще другие компьютеры и рабочие станции!

Как нетрудно заметить, даже если стандартизовать язык программирования для всех платформ, проблемы совместимости с программным интерфейсом операционной системы значительно усложняют перенос программ на различные платформы. И, конечно, вы не можете мечтать о том, чтобы загрузочный модуль одной и той же программы мог работать без изменений в среде различных операционных систем и на различных платформах. Если программа подготовлена для процессора Intel, она ни за что не согласится работать на процессоре Alpha или каком-либо другом.

В результате создавая приложение, способное работать на различных платформах, вы вынуждены фактически делать несколько различных приложений и сопровождать их по отдельности.

На рис. 5 мы показали, как приложение, изначально разработанное для Windows NT, переносится на платформу Apple Macintosh.

pic05.gif (3440 bytes)

Рис. 5. Перенос приложения с платформы Windows NT на платформу Macintosh

Вначале программист готовит исходные тексты приложения для платформы Windows NT и отлаживает их там. Для получения загрузочного модуля исходные тексты компилируются и редактируются. Полученный в результате загрузочный модуль может работать на процессоре фирмы Intel в среде операционной системы Windows NT.

Для того чтобы перенести приложение в среду операционной системы компьютера Macintosh, программист вносит необходимые изменения в исходные тексты приложения. Эти изменения необходимы из-за различий в программном интерфейсе операционной системы Windows NT и операционной системы, установленной в Macintosh. Далее эти исходные тексты транслируются и редактируются, в результате чего получается загрузочный модуль, способный работать в среде Macintosh, но не способный работать в среде Windows NT.

Программа на языке Java компилируется в двоичный модуль, состоящий из команд виртуального процессора Java. Такой модуль содержит байт-код, предназначенный для выполнения Java-интерпретатором. На настоящий момент уже созданы первые модели физического процессора, способного выполнять этот байт-код, однако интерпретаторы Java имеются на всех основных компьютерных платформах. Разумеется, на каждой платформе используется свой интерпретатор, или, точнее говоря, свой виртуальный процессор Java.

Если ваше приложение Java (или аплет) должно работать на нескольких платформах, нет необходимости компилировать его исходные тексты несколько раз. Вы можете откомпилировать и отладить приложение Java на одной, наиболее удобной для вас платформе. В результате вы получите байт-код, пригодный для любой платформы, где есть виртуальный процессор Java.

Сказанное иллюстрируется на рис. 6.

pic06.gif (3169 bytes)

Рис. 6. Подготовка приложения Java для работы на разных платформах

Таким образом, приложение Java компилируется и отлаживается только один раз, что уже значительно лучше. Остается, правда, вопрос - как быть с программным интерфейсом операционной системы, который отличается для разных платформ?

Здесь, на наш взгляд, разработчиками Java предлагается достаточно неплохое решение. Приложение Java не обращается напрямую к интерфейсу операционной системы. Вместо этого оно пользуется готовыми стандартными библиотеками классов, содержащими все необходимое для организации пользовательского интерфейса, обращения к файлам, для работы в сети и так далее.

Внутренняя реализация библиотек классов, разумеется, зависит от платформы. Однако все загрузочные модули, реализующие возможности этих библиотек, поставляются в готовом виде вместе с виртуальной машиной Java, поэтому программисту не нужно об этом заботиться. Для операционной системы Windows, например, поставляются библиотеки динамической загрузки DLL, внутри которых запрятана вся функциональность стандартных классов Java.

Абстрагируясь от аппаратуры на уровне библиотек классов, программисты могут больше не заботиться о различиях в реализации программного интерфейса конкретных операционных систем. Это позволяет создавать по-настоящему мобильные приложения, не требующие при переносе на различные платформы перетрансляции и изменения исходного текста.

Еще одна проблема, возникающая при переносе программ, составленных на языке программирования С, заключается в том, что размер области памяти, занимаемой переменными стандартных типов, различный на разных платформах. Например, в среде операционной системы Windows версии 3.1 переменная типа int в программе, составленной на С, занимает 16 бит. В среде Windows NT этот размер составляет 32 бита.

Очевидно, что трудно составлять программу, не зная точно, сколько имеется бит в слове или в байте. При переносе программ на платформы с иной разрядностью могут возникать ошибки, которые трудно обнаружить.

В языке Java все базовые типы данных имеют фиксированную разрядность, которая не зависит от платформы. Поэтому программисты всегда знают размеры переменных в своей программе.


Базовые типы данных

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

В языке Java определено восемь базовых типов данных. Для каждого базового типа данных отводится конкретный размер памяти. Этот размер, как мы говорили в предыдущем разделе, не зависит от платформы, на которой выполняется приложение Java:

Тип данных | Размер занимаемой области памяти | Значение по умолчанию
boolean | 8 | false
byte | 8 | 0
char | 16 | 'x0'
short | 16 | 0
int | 32 | 0
long | 64 | 0
float | 32 | 0.0F
double | 64 | 0.0D

Фактически размеры памяти, отведенные для хранения переменной, могут отличаться от приведенных выше, например, для хранения переменной типа short может быть зарезервировано слово размером 32 бита. Однако язык Java сделан таким образом, что это никак не повлияет на мобильность приложения. Так как в языке Java нет указателей, вы не можете адресоваться к элементам массива чисел по относительному смещению этих элементов в оперативной памяти. Следовательно, точный размер элемента в данном случае не играет никакой роли.

Все базовые типы данных по умолчанию инициализируются, поэтому программисту не нужно об этом беспокоиться. Вы можете также инициализировать переменные базовых типов в программе или при их определении, как это показано ниже:

int nCounter = 0;
int i;
i = 8;

Переменные типа boolean могут находиться только в двух состояниях - true и false, причем эти состояния никаким образом нельзя соотнести с целыми значениями. Вы не можете, как это было в языке С, выполнить преобразование типа boolean, например, к типу int - компилятор выдаст сообщение об ошибке.

Переменная типа byte занимает восемь бит памяти и про нее больше нечего сказать.

Что же касается типа char, то он используется для хранения символов в кодировке UNICODE. Эта кодировка позволяет хранить национальные наборы символов, что очень удобно для интернациональных приложений, предназначенных для работы в Internet.

Переменные типа byte, short, int и long являются знаковыми. В языке Java нет беззнаковых переменных, как это было в языке С.

Приложение Java может оперировать числами в формате с плавающей точкой, определенным в спецификации IEEE 754. Тип float позволяет хранить числа с одинарной точностью, а формат double - с двойной.

Переменные базовых типов могут передаваться функциям в качестве параметров только по значению, но не по ссылке. Поэтому следующий фрагмент кода работать не будет:

int x;
void ChangeX(int x)
{
  x = 5;
}
. . .
x = 0;
ChangeX(x);

После вызова функции ChangeX содержимое переменной x останется равным нулю.

Проблему можно решить, если вместо базовых переменных использовать объекты встроенных классов, соответствующие базовым переменным. О встроенных классах вы узнаете из следующего раздела.


Библиотеки классов Java

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

Если предоставить в распоряжение программиста только язык программирования и не снабдить его набором готовых модулей, предназначенных для решения самых распространенных задач, ему придется отвлекаться на множество мелких деталей.

Обычно все профессиональные системы разработки приложений на языке программирования C++ содержат в своем составе набор стандартных библиотечных функций или библиотеки классов. В комплекте со всеми средствами разработки Java поставляются достаточно развитые библиотеки классов, значительно упрощающие программирование. В этом разделе мы кратко расскажем о составе и назначении библиотек классов Java.

Встроенные классы

В языке Java все классы происходят от класса Object, и, соответственно, наследуют методы этого класса. Некоторые библиотеки классов подключаются автоматически, и мы будем называть их встроенными. К таким относится, в частности, библиотека с названием java.lang. Другие библиотеки классов вы должны подключать в исходном тексте приложения Java явным образом с помощью оператора import.

Замещающие классы

Очень часто в наших приложениях вместо базовых типов переменных мы будем использовать объекты встроенных классов, которые называются замещающими классами (wrapper classes). Ниже мы перечислили названия этих классов и названия базовых типов данных, которые они замещают:

Базовый тип данных | Замещающий класс
boolean | Boolean
char | Character
int | Integer
long | Long
float | Float
double | Double

Заметим, что для преобразования базовых типов данных в объекты замещающего класса и обратно вы не можете применять оператор присваивания. Вместо этого необходимо использовать соответствующие конструкторы и методы замещающих классов.

Класс String

Класс String предназначен для работы с такими часто встречающимися объектами, как текстовые строки. Методы этого класса позволяют выполнять над строками практически все операции, которые вы делали раньше при помощи библиотечных функций C. Это преобразование строки в число и обратно с любым заданным основанием, определение длины строки, сравнение строк, извлечение подстроки и так далее.

Хотя в языке Java не допускается перезагрузка (переопределение) операторов, для объектов класса Stirng и объектов всех произошедших от него классов сделана встроенная перезагрузка операторов "+" и "+=". С помощью этих операторов можно выполнять слияние текстовых строк, например:

System.out.println("x = " + x + '\n');

Здесь в качестве параметра функции println передается текстовая строка, составленная из трех компонент: строки "x = ", числа x и символа перехода на следующую строку '\n'. Значение переменной x автоматически преобразуется в текстовую строку (что выполняется только для текстовых строк) и полученная таким образом текстовая строка сливается со строкой "x = ".

Другие встроенные классы

Среди других встроенных классов отметим класс Math, предназначенный для выполнения математических операций, таких как вычисление синуса, косинуса и тангенса.

Предусмотрены также классы для выполнения запуска процессов и потоков, управления системой безопасности, а также для решения прочих системных задач.

Библиотека встроенных классов содержит очень важные классы для работы с исключениями. Эти классы нужны для обработки ошибочных ситуаций, которые могут возникнуть (и возникают!) при работе приложений или аплетов Java.

Подключаемые библиотеки классов

Ниже мы кратко перечислим подключаемые библиотеки классов для того чтобы вы могли оценить возможности набора классов Java. Подробное описание этих классов есть в справочной системе Java WorkShop и в различной литературе, посвященной Java. Мы же ограничимся описанием тех классов, которые будем использовать в наших примерах приложений.

Библиотека классов java.util

Библиотека классов java.util очень полезна при составлении приложений, потому что в ней имеются классы для создания таких структур, как динамические массивы, стеки и словари. Есть классы для работы с генератором псевдослучайных чисел, для разбора строк на составляющие элементы (токены), для работы с календарной датой и временем.

Библиотека классов java.io

В библиотеке классов java.io собраны классы, имеющие отношение к вводу и выводу данных через потоки. Заметим, что с использованием этих классов можно работать не только с потоками байт, но также и с потоками данных других типов, например числами int или текстовыми строками.

Библиотека классов java.net

Язык программирования Java разрабатывался в предположении, что им будут пользоваться для создания сетевых приложений. Поэтому было бы странно, если бы в составе среды разработки приложений Java не поставлялась библиотека классов для работы в сети. Библиотека классов java.net предназначена как раз для этого. Она содержит классы, с помощью которых можно работать с универсальными сетевыми адресами URL, передавать данные с использованием сокетов TCP и UDP, выполнять различные операции с адресами IP. Эта библиотека содержит также классы для выполнения преобразований двоичных данных в текстовый формат, что часто бывает необходимо.

В качестве примера приложения, составленного на языке программирования Java и ориентированного на работу в сети Internet, можно привести игру Java Color Lines (рис. 7).

pic07sm.gif (3435 bytes) Рис. 7. Сетевая игра Java Color Lines.

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Это сетевая версия известной игры Lines, которая выполнена в виде нескольких аплетов, взаимодействующих между собой и между сервером Web, на котором они расположены. Так как список имен игроков и достигнутых ими результатов хранится на сервере, вы можете поучаствовать в мировом турнире, сразившись с игроками из разных стран.

Библиотека классов java.awt

Для создания пользовательского интерфейса аплеты Java могут и должны использовать библиотеку классов java.awt. AWT - это сокращение от Abstract Window Toolkit (инструментарий для работы с абстрактными окнами).

Классы, входящие в состав библиотеки java.awt, предоставляют возможность создания пользовательского интерфейса способом, не зависящим от платформы, на которой выполняется аплет Java. Вы можете создавать обычные окна и диалоговые панели, кнопки, переключатели, списки, меню, полосы просмотра, однострочные и многострочные поля для ввода текстовой информации.

Библиотека классов java.awt.image

В среде любой операционной системы работа с графическими изображениями является достаточно сложной задачей. В операционной системе Windows для этого применяется графический интерфейс GDI. Если вы будете рисовать графические изображения в среде OS/2 или X-Windows, вам, очевидно, придется использовать другой программный интерфейс. Большую сложность также вызывает разбор заголовков графических файлов, так как они могут иметь различный формат и иногда содержат неправильную или противоречивую информацию.

Когда вы программируете на Java, рисование и обработка графических изображений выполняется намного проще, так как вам доступна специально предназначенная для этого библиотека классов java.awt.image. Помимо широкого разнообразия и удобства определенных в ней классов и методов, отметим способность этой библиотеки работать с графическими изображениями в формате GIF. Этот формат широко используется в Internet, так как он позволяет сжимать файлы графических изображений во много раз без потери качества за счет устранения избыточности.

Библиотека классов java.awt.peer

Библиотека классов java.awt.peer служит для подключения компонент AWT (например, кнопок, списков, полей редактирования текстовой информации, переключателей и так далее) к реализациям, зависящим от платформы, в процессе создания этих компонент.

Библиотека классов java.applet

Как нетрудно догадаться из названия, библиотека классов java.applet инкапсулирует поведение аплетов Java. Когда вы будете создавать свои аплеты, вам будет нужен класс Applet, расположенный в этой библиотеке классов. Дополнительно в библиотеке классов java.applet определены интерфейсы для подключения аплетов к содержащим их документам и классы для проигрывания звуковых фрагментов.


Указатели, которых нет

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

Самая большая и шокирующая новость для тех, кто раньше программировал на С, а теперь занялся изучением Java, это то, что в языке Java нет указателей. Традиционно считалось, что работать с указателями трудно, а их использование приводит к появлению трудно обнаруживаемых ошибок. Поэтому разработчики Java решили отказаться от использования указателей совсем.

Спешим успокоить - вы сможете успешно составлять приложения Java и без указателей, несмотря на то что вам, возможно, придется немного изменить стиль программирования.

Вы можете спросить: как же передавать функциям ссылки на объекты, если нет указателей?

Если вам нужно передать ссылку на переменную базового типа, такого, например, как int или long, то ничего не получится - мы уже говорили, что переменные базовых типов передаются по значению, а не по ссылке. Поэтому нельзя напрямую создать на языке Java эквивалент следующей программы, составленной на языке С:

// Некоторая переменная
int nSomeValue;

// Функция, изменяющая значение переменной,
// заданной своим адресом
void StoreValue(int *pVar, int nNewValue)
{
  pVar->nNewValue;
}
. . .
StoreValue(&nSomeValue, 10);

Выход, однако, есть.

Язык Java позволяет использовать вместо указателей ссылки на объекты. Пользуясь этими ссылками, вы можете адресовать объекты по их имени, вызывая методы и изменяя значения данных объектов.

Что же касается данных базовых типов, если вам нужно передавать на них ссылки, то следует заменить базовые типы на соответствующие замещающие классы. Например, вместо типа int используйте класс Integer, вместо типа long - класс Long и так далее.

Инициализация таких объектов должна выполняться с помощью конструктора, как это показано ниже:

Integer nSomeValue;
nSomeValue = new Integer(10);

Первая строка создает неинициализированную ссылку с именем nSomeValue и типом Integer. При попытке использования такой ссылки возникнет исключение.

Вторая строка создает объект класса Integer, вызывая конструктор. Этот конструктор определяет начальное значение. После выполнения оператора присваивания ссылка nSomeValue будет ссылаться на реальный объект класса Integer и ее можно будет использовать.

Имя объекта nSomeValue типа Integer вы можете передавать функциям в качестве параметра, причем это будет ссылкой на объект.

Составляя программы на языке С, вы часто использовали указатели для адресации элементов массивов, созданных статически или динамически в оперативной памяти. Зная начальный адрес такого массива и тип хранящихся в нем элементов, вы могли адресоваться к отдельным элементам массива.

В языке Java реализован механизм массивов, исключающих необходимость использования указателей.


Массивы в Java

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

Для создания массива вы можете пользоваться квадратными скобками, расположив их справа от имени массива или от типа объектов, из которых составлен массив, например:

int nNumbers[];
int[] nAnotherNumbers;

Допустимы оба варианта, поэтому вы можете выбрать тот, который вам больше нравится.

При определении массивов в языке Java нельзя указывать их размер. Приведенные выше две строки не вызывают резервирования памяти для массива. Здесь просто создаются ссылки на массивы, которые без инициализации использовать нельзя.

Для того чтобы заказать память для массива, вы должны создать соответствующие объекты с помощью ключевого слова new, например:

int[] nAnotherNumbers;
nAnotherNumbers = new int[15];

Как выполнить инициализацию ячеек таблицы?

Такую инициализацию можно выполнить либо статически, либо динамически. В первом случае вы просто перечисляете значения в фигурных скобках, как это показано ниже:

int[] nColorRed = {255, 255, 100, 0, 10};

Динамическая инициализация выполняется с использованием индекса массива, например, в цикле:

int nInitialValue = 7;
int[] nAnotherNumbers;
nAnotherNumbers = new int[15];
for(int i = 0; i < 15; i++)
{
  nAnotherNumbers[i] = nInitialValue;
}

Вы можете создавать массивы не только из переменных базовых типов, но и из произвольных объектов. Каждый элемент такого массива должен инициализироваться оператором new.

Массивы могут быть многомерными и, что интересно, несимметричными.

Ниже создается массив массивов. В нулевом и первом элементе создается массив из четырех чисел, а во втором - из восьми:

int[][] nDim = new int[5][10];
nDim[0] = new int [4];
nDim[1] = new int [4];
nDim[2] = new int [8];

Заметим, что во время выполнения приложения виртуальная машина Java проверяет выход за границы массива. Если приложение пытается выйти за границы массива, происходит исключение.

Массивы в языке Java являются объектами некоторого встроенного класса. Для этого класса существует возможность определить размер массива, обратившись к элементу данных класса с именем length, например:

int[] nAnotherNumbers;
nAnotherNumbers = new int[15];
for(int i = 0; i < nAnotherNumbers.length; i++)
{
  nAnotherNumbers[i] = nInitialValue;
}

Для определения размера массива вам не нужен такой оператор, как sizeof из языка программирования С, потому что существует другой способ определения этого размера.


Сборка мусора

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора Реализация классов

Назад Вперед

Одна из интереснейших особенностей языка программирования Java и среды выполнения приложений Java заключается в наличии специального процесса сборки мусора, предназначенного для удаления ненужных объектов из памяти. Эта система избавляет программиста от необходимости внимательно следить за использованием памяти, освобождая ненужные более области явным образом.

Создавая объекты в Java, вы можете руководствоваться принципом "создай и забудь", так как система сборки мусора позаботится об удалении ваших объектов. Объект будет удален из памяти, как только на него не останется ни одной ссылки из других объектов.

Приоритет процесса сборки мусора очень низкий, поэтому "уборка" среды выполнения приложений Java не отнимает ресурсы у самих приложений.


Особенности реализации классов в Java

Оглавление

Введение
Инструментарий
Мобильность
Типы данных
Классы Java
Указатели
Массивы
Сборка мусора
Реализация классов

Назад Вперед

Если вы умеете программировать на языке С++, у вас не возникнет никаких проблем с программированием на Java, так как эти языки очень похожи. Однако есть и некоторые отличия, которые следует учитывать. Мы приведем краткое перечисление основных отличий.

Определение класса

Для создания классов вы можете использовать только ключевое слово class. Что же касается union, то это ключевое слово в Java не применяется.

В языке программирования С++ описание класса может быть отделено от его определения. Для Java это не так - описание класса недопустимо. Все методы должны быть определены внутри определения класса.

Недопустимо определение вложенных классов.

В Java также нет шаблонов. Вы можете создавать классы только на базе других классов.

Объект класса создается при помощи ключевого слова new, однако вы не можете удалить объект явным образом, так как ключевое слово delete языка программирования С++ в Java не используется.

При определении класса вы не можете указать деструктор. Функции удаления объектов Java из памяти выполняет система сборки мусора.

Внутри одного исходного файла вы можете определить только один общедоступный класс public.

Все классы в Java наследуются от класса Object, поэтому для любого объекта вы можете использовать методы этого класса.

Определение методов

Вы не можете определять методы вне тела класса, создавая таким образом глобальные функции. Нет также возможности определения вне класса глобальных данных. Тем не менее, внутри класса можно определять статические методы и поля (с помощью ключевого слова static), которые будут играть роль глобальных методов и данных.

Пользуясь ключевыми словами static и final, можно определять внутри классов глобальные константы.

Если в базовом классе метод определен с ключевым словом final, его нельзя переопределить в дочернем классе, созданном на базе данного метода.

Методы не могут быть определены как inline.

Методы Java могут создавать исключения, вызванные возникновением ошибок или других событий. Все исключения должны либо обрабатываться внутри метода, либо описываться в определении метода после ключевого слова throws.

Переопределение операторов

В языке С++ вы могли переопределить операторы, такие как +, -, ++ и так далее. Язык Java не допускает переопределение, что сделано для упрощения программирования. Тем не менее, операторы "+" и "+=" перегружены по умолчанию для выполнения операции слияния текстовых строк класса String.

Интерфейсы

Интерфейсы создаются при помощи ключевого слова interface таким же образом, что и классы. Однако в отличие от последних, интерфейсы являются аналогом абстрактных базовых классов без полей данных и предназначены только для определений набора методов для решения каких-либо задач, например, добавления компонент в контейнеры, организации списков, сортировки и так далее.

Вы можете создать свой класс на базе другого класса, указав при этом с помощью ключевого слова implements, что он реализует тот или иной интерфейс. При этом наряду с методами базового класса в созданном таким образом классе будут доступны методы, определенные в интерфейсе.

Ссылки на методы класса

Так как в Java нет указателей, нет возможности ссылаться на методы с помощью оператора ->. Для ссылки на метод класса используется только оператор "точка".

Оператор "::" также не определен в Java. Если вам необходимо вызвать метод из базового класса, следует использовать ключевое слово super.

Наследование

С помощью ключевого слова extends вы можете унаследовать один класс (дочерний) от другого (базового).

Множественное наследование не допускается. Таким образом, для каждого дочернего класса может быть только один базовый класс. При необходимости, однако, этот дочерний класс может реализовывать произвольное количество интерфейсов.

Для ссылки на методы базового класса вы должны использовать ключевое слово super.

При необходимости вы можете вызвать в первой исполняемой строке конструктора дочернего класса конструктор базового класса (опять же с помощью ключевого слова super).


Рис. 1. Главное окно приложения

Для возврата сделайте щелчок мышью по рисунку

pic01.gif (15212 bytes)

Рис. 1. Главное окно приложения


Рис. 2. Визуальные средства разработки Java WorkShop

Для возврата сделайте щелчок мышью по рисунку

pic02.gif (14579 bytes)

Рис. 2. Визуальные средства разработки Java WorkShop 2.0


Рис. 3. Проектирование логики работы приложения

Для возврата сделайте щелчок мышью по рисунку

pic03.gif (16895 bytes)

Рис. 3. Проектирование логики работы приложения


Рис. 4. Визуальное проектирование пользовательского интерфейса

Для возврата сделайте щелчок мышью по рисунку

pic04.gif (6397 bytes)

Рис. 4. Визуальное проектирование пользовательского интерфейса


Рис. 7. Сетевая игра Java Color Lines

Для возврата сделайте щелчок мышью по рисунку

pic07.gif (10929 bytes)

Рис. 7. Сетевая игра Java Color Lines


Многопоточность

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

Наверное, сегодня уже нет необходимости объяснять, что такое многопоточность. Все современные операционные системы, такие как Windows 95, Windows NT, OS/2 или UNIX способны работать в многопоточном режиме, повышая общую производительность системы за счет эффективного распараллеливания выполняемых потоков. Пока один поток находится в состоянии ожидания, например, завершения операции обмена данными с медленным периферийным устройством, другой может продолжать выполнять свою работу.

Пользователи уже давно привыкли запускать параллельно несколько приложений для того чтобы делать несколько дел сразу. Пока одно из них занимается, например, печатью документа на принтере или приемом электронной почты из сети Internet, другое может пересчитывать электронную таблицу или выполнять другую полезную работу. При этом сами по себе запускаемые приложения могут работать в рамках одного потока - операционная система сама заботится о распределении времени между всеми запущенными приложениями.

Создавая приложения для операционной системы Windows на языках программирования С или С++, вы могли решать многие задачи, такие как анимация или работа в сети, и без использования многопоточности. Например, для анимации можно было обрабатывать сообщения соответствующим образом настроенного таймера.

Приложениям Java такая методика недоступна, так как в этой среде не предусмотрено способов периодического вызова каких-либо процедур. Поэтому для решения многих задач вам просто не обойтись без многопоточности.


Процессы, потоки и приоритеты

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

Прежде чем приступить к разговору о многопоточности, следует уточнить некоторые термины.

Обычно в любой многопоточной операционной системе выделяют такие объекты, как процессы и потоки. Между ними существует большая разница, которую следует четко себе представлять.

Процесс

Процесс (process) - это объект, который создается операционной системой, когда пользователь запускает приложение. Процессу выделяется отдельное адресное пространство, причем это пространство физически недоступно для других процессов. Процесс может работать с файлами или с каналами связи локальной или глобальной сети. Когда вы запускаете текстовый процессор или программу калькулятора, вы создаете новый процесс.

Поток

Для каждого процесса операционная система создает один главный поток (thread ), который является потоком выполняющихся по очереди команд центрального процессора. При необходимости главный поток может создавать другие потоки, пользуясь для этого программным интерфейсом операционной системы.

Все потоки, созданные процессом, выполняются в адресном пространстве этого процесса и имеют доступ к ресурсам процесса. Однако поток одного процесса не имеет никакого доступа к ресурсам потока другого процесса, так как они работают в разных адресных пространствах. При необходимости организации взаимодействия между процессами или потоками, принадлежащими разным процессам, следует пользоваться системными средствами, специально предназначенными для этого.

Приоритеты потоков в приложениях Java

Если процесс создал несколько потоков, то все они выполняются параллельно, причем время центрального процессора (или нескольких центральных процессоров в мультипроцессорных системах) распределяется между этими потоками.

Распределением времени центрального процессора занимается специальный модуль операционной системы - планировщик. Планировщик по очереди передает управление отдельным потокам, так что даже в однопроцессорной системе создается полная иллюзия параллельной работы запущенных потоков.

Распределение времени выполняется по прерываниям системного таймера. Поэтому каждому потоку дается определенный интервал времени, в течении которого он находится в активном состоянии.

Заметим, что распределение времени выполняется для потоков, а не для процессов. Потоки, созданные разными процессами, конкурируют между собой за получение процессорного времени.

Каким именно образом?

Приложения Java могут указывать три значения для приоритетов потоков. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY.

По умолчанию вновь созданный поток имеет нормальный приоритет NORM_PRIORITY. Если остальные потоки в системе имеют тот же самый приоритет, то все потоки пользуются процессорным времени на равных правах.

При необходимости вы можете повысить или понизить приоритет отдельных потоков, определив для них значение приоритета, соответственно, MAX_PRIORITY или MIN_PRIORITY. Потоки с повышенным приоритетом выполняются в первую очередь, а с пониженным - только при отсутствии готовых к выполнению потоков, имеющих нормальный или повышенный приоритет.


Реализация многопоточности в Java

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед
должны воспользоваться классом java.lang.Thread. В этом классе определены все методы, необходимые для создания потоков, управления их состоянием и синхронизации.

Как пользоваться классом Thread?

Есть две возможности.

  • Во-первых, вы можете создать свой дочерний класс на базе класса Thread. При этом вы должны переопределить метод run. Ваша реализация этого метода будет работать в рамках отдельного потока.
  • Во-вторых, ваш класс может реализовать интерфейс Runnable. При этом в рамках вашего класса необходимо определить метод run, который будет работать как отдельный поток.

Второй способ особенно удобен в тех случаях, когда ваш класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом вам нужна многопоточность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи.

Методы класса Thread

В классе Thread определены три поля, несколько конструкторов и большое количество методов, предназначенных для работы с потоками. Ниже мы привели краткое описание полей, конструкторов и методов.

С помощью конструкторов вы можете создавать потоки различными способами, указывая при необходимости для них имя и группу. Имя предназначено для идентификации потока и является необязательным атрибутом. Что же касается групп, то они предназначены для организации защиты потоков друг от друга в рамках одного приложения.

Методы класса Thread предоставляют все необходимые возможности для управления потоками, в том числе для их синхронизации.

Поля

Три статических поля предназначены для назначения приоритетов потокам.

  • NORM_PRIORITY

Нормальный

public final static int NORM_PRIORITY;
  • MAX_PRIORITY

Максимальный

public final static int MAX_PRIORITY;
  • MIN_PRIORITY

Минимальный

public final static int MIN_PRIORITY;

Конструкторы

Создание нового объекта Thread

public Thread();

Создвание нового объекта Thread с указанием объекта, для которого будет вызываться метод run

public Thread(Runnable target);

Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

public Thread(Runnable target, String name);

Создание объекта Thread с указанием его имени

public Thread(String name);

Создание нового объекта Thread с указанием группы потока и объекта, для которого вызывается метод run

public Thread(ThreadGroup group,
  Runnable target);

Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

public Thread(ThreadGroup group,
  Runnable target, String name);

Создание нового объекта Thread с указанием группы потока и имени объекта

public Thread(ThreadGroup group, String name);

Методы

  • activeCount

Текущее количество активных потоков в группе, к которой принадлежит поток

public static int activeCount();
  • checkAccess

Текущему потоку разрешается изменять объект Thread

public void checkAccesss();
  • countStackFrames

Определение количества фреймов в стеке

public int countStackFrames();
  • currentThread

Определение текущего работающего потока

public static Thread currentThread();
  • destroy

Принудительное завершение работы потока

public void destroy();
  • dumpStack

Вывод текущего содержимого стека для отладки

public static void dumpStack();
  • enumerate

Получение всех объектов Tread данной группы

public static int enumerate(Thread  tarray[]);
  • getName

Определение имени потока

public final String getName();
  • getPriority

Определение текущего приоритета потока

public final int getPriority();
  • getThreadGroup

Определение группы, к которой принадлежит поток

public final ThreadGroup getThreadGroup();
  • interrupt

Прерывание потока

public void interrupt();
  • interrupted

Определение, является ли поток прерванным

public static boolean interrupted();
  • isAlive

Определение, выполняется поток или нет

public final boolean isAlive();
  • isDaemon

Определение, является ли поток демоном

public final boolean isDaemon();
  • isInterrupted

Определение, является ли поток прерванным

public boolean isInterrupted();
  • join

Ожидание завершения потока

public final void join();

Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах

public final void join(long millis);

Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах и наносекундах

public final void join(long  millis, int  nanos);
  • resume

Запуск временно приостановленного потока

public final void resume();
  • run

Метод вызывается в том случае, если поток был создан как объект с интерфейсом Runnable

public void run();
  • setDaemon

Установка для потока режима демона

public final void setDaemon(boolean on);
  • setName

Устаовка имени потока

public final void setName(String name);
  • setPriority

Установка приоритета потока

public final void setPriority(int newPriority);
  • sleep

Задержка потока на заднное время. Время задается в миллисекундах и наносекундах

public static void sleep(long millis);

Задержка потока на заднное время. Время задается в миллисекундах и наносекундах

public static void sleep(long millis, int nanos);
  • start

Запуск потока на выполнение

public void start();
  • stop

Остановка выполнения потока

public final void stop();

Аварийная остановка выполнения потока с заданным исключением

public final void stop(Throwable obj);
  • suspend

Приостановка потока

public final void suspend();
  • toString

Строка, представляющая объект-поток

public String toString();
  • yield

Приостановка текущего потока для того чтобы управление было передано другому потоку

public static void yield  ();

Создание дочернего класса на базе класса Thread

Рассмотрим первый способ реализации многопоточности, основанный на наследовании от класса Thread. При использовании этого способа вы определяете для потока отдельный класс, например, так:

class DrawRectangles extends Thread
{
  . . .
  public void run()
  {
    . . .
  }
}

Здесь определен класс DrawRectangles, который является дочерним по отношению к классу Thread.

Обратите внимание на метод run. Создавая свой класс на базе класса Thread, вы должны всегда определять этот метод, который и будет выполняться в рамках отдельного потока.

Заметим, что метод run не вызывается напрямую никакими другими методами. Он получает управление при запуске потока методом start.

Как это происходит?

Рассмотрим процедуру запуска потока на примере некоторого класса DrawRectangles.

Вначале ваше приложение должно создать объект класса Thread:

public class MultiTask2 extends Applet
{
  Thread m_DrawRectThread = null;
  . . .
  public void start()
  {
    if (m_DrawRectThread == null)
    {
      m_DrawRectThread = new DrawRectangles(this);
      m_DrawRectThread.start();
    }
  }
}

Создание объекта выполняется оператором new в методе start, который получает управление, когда пользователь открывает документ HTML с аплетом. Сразу после создания поток запускается на выполнение, для чего вызывается метод start.

Что касается метода run, то если поток используется для выполнения какой либо периодической работы, то этот метод содержит внутри себя бесконечный цикл. Когда цикл завершается и метод run возвращает управление, поток прекращает свою работу нормальным, не аварийным образом. Для аварийного завершения потока можно использовать метод interrupt.

Остановка работающего потока выполняется методом stop. Обычно остановка всех работающих потоков, созданных аплетом, выполняется методом stop класса аплета:

public void stop()
{
  if (m_DrawRectThread != null)
  {
    m_DrawRectThread.stop();
    m_DrawRectThread = null;
  }
}

Напомним, что этот метод вызывается, когда пользователь покидает страницу сервера Web, содержащую аплет.

Реализация интерфейса Runnable

Описанный выше способ создания потоков как объектов класса Thread или унаследованных от него классов кажется достаточнао естественным. Однако этот способ не единственный. Если вам нужно создать только один поток, работающую одновременно с кодом аплета, проще выбрать второй способ с использованием интерфейса Runnable.

Идея заключается в том, что основной класс аплета, который является дочерним по отношению к классу Applet, дополнительно реализует интерфейс Runnable, как это показано ниже:

public class MultiTask extends
  Applet implements Runnable
{
  Thread m_MultiTask = null;
  . . .
  public void run()
  {
    . . .
  }

  public void start()
  {
    if (m_MultiTask == null)
    {
      m_MultiTask = new Thread(this);
      m_MultiTask.start();
    }
  }

  public void stop()
  {
    if (m_MultiTask != null)
    {
      m_MultiTask.stop();
      m_MultiTask = null;
    }
  }
}

Внутри класса необходимо определить метод run, который будет выполняться в рамках отдельного потока. При этом можно считать, что код аплета и код метода run работают одновременно как разные потоки.

Для создания потока используется оператор new. Поток создается как объект класса Thread, причем конструктору передается ссылка на класс аплета:

m_MultiTask = new Thread(this);

При этом, когда поток запустится, управление получит метод run, определенный в классе аплета.

Как запустить поток?

Запуск выполняется, как и раньше, методом start. Обычно поток запускается из метода start аплета, когда пользователь отображает страницу сервера Web, содержащую аплет. Остановка потока выполняется методом stop.


Применение многопоточности для анимации

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

Одно из наиболее распространенных применений аплетов - это создание анимационных эффектов типа бегущей строки, мерцающих огней или аналогичных, привлекающих внимание пользователя. Для того чтобы достичь такого эффекта, необходим какой либо механизм, позволяющий выполнять перерисовку всего окна аплета или его части периодически с заданным временным интервалом.

Работа аплетов, так же как и обычных приложений операционной системы Windows, основана на обработке событий. Для классического приложения Windows событие - это приход сообщения в функцию окна. Основной класс аплета обрабатывает события, переопределяя те или иные методы базового класса Applet.

Проблема с периодическим обновлением окна аплета возникает из-за того, что в языке Java не предусмотрено никакого механизма для создания генератора событий, способного вызывать какой-либо метод класса аплета с заданным интервалом времени. Вы не можете поступить так, как поступали в этой ситуации, разрабатывая обычные приложения Windows - создать таймер и организовать обработку периодически поступающих от него сообщений WM_TIMER.

Напомним, что перерисовка окна аплета выполняется методом paint, который вызывается виртуальной машиной Java асинхронно по отношению к выполнению другого кода аплета.

Можно ли воспользоваться методом paint для периодической перерисовки окна аплета, организовав в нем, например, бесконечный цикл с задержкой?

К сожалению, так поступать ни в коем случае нельзя. Метод paint после перерисовки окна аплета должен сразу возвратить управление, иначе работа аплета будет заблокирована.

Единственный выход из создавшейся ситуации - создание потока (или нескльких потоков), которые будут выполнять рисование в окне аплета асинхронно по отношению к работе кода аплета. Например, вы можете создать поток, который периодически обновляет окно аплета, вызывая для этого метод repaint, или рисовать из потока непосредственно в окне аплета, получив предварительно для этого окна контекст отображения.


Синхронизация потоков

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

Многопоточный режим работы открывает новые возможности для программистов, однако за эти возможности приходится расплачиваться усложнением процесса проектирования приложения и отладки. Основная трудность, с которой сталкиваются программисты, никогда не создававшие ранее многопоточные приложения, это синхронизация одновременно работающих потоков.

Для чего и когда она нужна?

Однопоточная программа, такая, например, как программа MS-DOS, при запуске получает в монопольное распоряжение все ресурсы компьютера. Так как в однопоточной системе существует только один процесс, он использует эти ресурсы в той последовательности, которая соответствует логике работы программы. Процессы и потоки, работающие одновременно в многопоточной системе, могут пытаться обращаться одновременно к одним и тем же ресурсам, что может привести к неправильной работе приложений.

Поясним это на простом примере.

Пусть мы создаем программу, выполняющую операции с банковским счетом. Операция снятия некоторой суммы денег со счета может происходить в следующей последовательности:

  • на первом шаге проверяется общая сумма денег, которая хранится на счету;
  • если общая сумма равна или превышает размер снимаемой суммы денег, общая сумма уменьшается на необходимую величину;
  • значение остатка записывается на текущий счет.

Если операция уменьшения текущего счета выполняется в однопоточной системе, то никаких проблем не возникнет. Однако представим себе, что два процесса пытаются одновременно выполнить только что описанную операцию с одним и тем же счетом. Пусть при этом на счету находится 5 млн. долларов, а оба процесса пытаются снять с него по 3 млн. долларов.

Допустим, события разворачиваются следующим образом:

  • первый процесс проверяет состояние текущего счета и убеждается, что на нем хранится 5 млн. долларов;
  • второй процесс проверяет состояние текущего счета и также убеждается, что на нем хранится 5 млн. долларов;
  • первый процесс уменьшает счет на 3 млн. долларов и записывает остаток (2 млн. долларов) на текущий счет;
  • второй процесс выполняет ту же самую операцию, так как после проверки считает, что на счету по-прежнему хранится 5 млн. долларов.

В результате получилось, что со счета, на котором находилось 5 млн. долларов, было снято 6 млн. долларов, и при этом там осталось еще 2 млн. долларов! Итого - банку нанесен ущерб в 3 млн. долларов.

Как же составить программу уменьшения счета, чтобы она не позволяла вытворять подобное?

Очень просто - на время выполнения операций над счетом одним процессом необходимо запретить доступ к этому счету со стороны других процессов. В этом случае сценарий работы программы должен быть следующим:

  • процесс блокирует счет для выполнения операций другими процессами, получая его в монопольное владение;
  • процесс проводит процедуру уменьшения счета и записывает на текущий счет новое значение остатка;
  • процесс разблокирует счет, разрешая другим процессам выполнение операций.

Когда первый процесс блокирует счет, он становится недоступен другим процессам. Если второй процесс также попытается заблокировать этот же счет, он будет переведен в состояние ожидания. Когда первый процесс уменьшит счет и на нем останется 2 млн. долларов, второй процесс будет разблокирован. Он проверит остаток, убедится, что сумма недостаточна и не будет проводить операцию.

Таким образом, в многопоточной среде необходима синхронизация потоков при обращении к критическим ресурсам. Если над такими ресурсами будут выполняться операции в неправильной последовательности, это приведет к возникновению трудно обнаруживаемых ошибок.

В языке программирования Java предусмотрено несколько средств для синхронизации потоков, которые мы сейчас рассмотрим.

Синхронизация методов

Возможность синхронизации как бы встроена в каждый объект, создаваемый приложением Java. Для этого объекты снабжаются защелками, которые могут быть использованы для блокировки потоков, обращающихся к этим объектам.

Чтобы воспользоваться защелками, вы можете объявить соответствующий метод как synchronized, сделав его синхронизированным:

public synchronized void decrement()
{
  . . .
}

При вызове синхронизированного метода соответствующий ему объект (в котором он определен) блокируется для использования другими синхронизированными методами. В результате предотвращается одновременная запись двумя методами значений в область памяти, принадлежащую данному объекту.

Использование синхронизированных методов - достаточно простой способ синхронизации потоков, обращающихся к общим критическим ресурсам, наподобие описанного выше банковского счета.

Заметим, что не обязательно синхронизовать весь метод - можно выполнить синхронизацию только критичного фрагмента кода.

. . .
synchronized(Account)
{
  if(Account.check(3000000))
     Account.decrement(3000000);
}
. . .

Здесь синхронизация выполняется для объекта Account.

Блокировка потока

Синхронизированный поток, определенный как метод типа synchronized, может переходить в заблокированное состояние автоматически при попытке обращения к ресурсу, занятому другим синхронизированным методом, либо при выполнении некоторых операций ввода или вывода. Однако в ряде случаев полезно иметь более тонкие средства синхронизации, допускающие явное использование по запросу приложения.

Блокировка на заданный период времени

С помощью метода sleep можно заблокировать поток на заданный период времени:

try
{
  Thread.sleep(500);
}
catch (InterruptedException ee)
{
  . . .
}

В данном примере работа потока Thread приостанавливается на 500 миллисекунд. Заметим, что во время ожидания приостановленный поток не отнимает ресурсы процессора.

Так как метод sleep может создавать исключение InterruptedException, необходимо предусмотреть его обработку. Для этого мы использовали операторы try и catch.

Временная приостановка и возобновление работы

Методы suspend и resume позволяют, соответственно, временно приостанавливать и возобновлять работу потока.

В следующем фрагменте кода поток m_Rectangles приостанавливает свою работу, когда курсор мыши оказывается над окном аплета:

public boolean mouseEnter(Event evt,
   int x, int y)
{
  if (m_Rectangles != null)
  {
    m_Rectangles.suspend();
  }
  return true;
}

Работа потока возобновляется, когда курсор мыши покидает окно аплета:

public boolean mouseExit(Event evt,
   int x, int y)
{
  if (m_Rectangles != null)
  {
    m_Rectangles.resume();
  }
  return true;
}

Ожидание извещения

Если вам нужно организовать взаимодействие потоков таким образом, чтобы один поток управлял работой другого или других потоков, вы можете воспользоваться методами wait, notify и notifyAll, определенными в классе Object.

Метод wait может использоваться либо с параметром, либо без параметра. Этот метод переводит поток в состояние ожидания, в котором он будет находиться до тех пор, пока для потока не будет вызван извещающий метод notify, notifyAll, или пока не истечет период времени, указанный в параметре метода wait.

Как пользоваться методами wait, notify и notifyAll?

Метод, который будет переводиться в состояние ожидания, должен быть синхронизированным, то есть его следует описать как synchronized:

public synchronized void run()
{
  while (true)
  {
    . . .
    try
    {
      this.wait();
    }
    catch (InterruptedException e)
    {
    }
  }
}

В этом примере внутри метода run определен цикл, вызывающий метод wait без параметров. Каждый раз при очередном проходе цикла метод run переводится в состояние ожидания до тех пор, пока другой поток не выполнит извещение с помощью метода notify.

Ниже мы привели пример потока, вызывающией метод notify:

public void run()
{
  while (true)
  {
    try
    {
      Thread.sleep(30);
    }
    catch (InterruptedException e)
    {
    }

    synchronized(STask)
    {
      STask.notify();
    }
  }
}

Этот поток реализован в рамках отдельного класса, конструктору которого передается ссылка на поток, вызывающую метод wait. Эта ссылка хранится в поле STask.

Обратите внимание, что хотя сам метод run не синхронизированный, вызов метода notify выполняется в синхронизированном режиме. В качестве объекта синхронизации выступает поток, для которого вызывается метод notify.

Ожидание завершения потока

С помощью метода join вы можете выполнять ожидание завершения работы потока, для которой этот метод вызван.

Существует три определения метода join:

public final void join();
public final void join(long millis);
public final void join(long millis,
   int nanos);

Первый из них выполняет ожидание без ограничения во времени, для второго ожидание будет прервано принудительно через millis миллисекунд, а для третьего - через millis миллисекунд и nanos наносекунд. Учтите, что реально вы не сможете указывать время с точностью до наносекунд, так как дискретность системного таймера компьютера намного больше.


Потоки-демоны

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

Вызвав для потока метод setDaemon, вы превращаете обычную поток в поток-демон. Такой поток работает в фоновом режиме независимо от породившего его потока. Если поток-демон создает другие потоки, то они также станут получат статус потока-демона.

Заметим, что метод setDaemon необходимо вызывать после создания потока, но до момента его запуска, то есть перед вызовом метода start.

С помощью метода isDaemon вы можете проверить, является поток демоном, или нет.


Аплет Rectangles

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

В качестве примера многопоточного приложения мы приведем аплет Rectangles (рис. 1). Он создает три потока. Первый поток рисует в окне аплета прямоугольники случайного размера и цвета, второй - эллипсы, а третий управляет потоком рисования эллипсов.

pic01.gif (7162 bytes)

Рис. 1. Окно аплета Rectangles

Расположение прямоугольников и эллипсов также выбирается случайно.


Исходные тексты аплета Rectangles

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

Исходные тексты аплета Rectangles приведены в листинге 1.

Листинг 1. Файл Rectangles,java

import java.applet.*;
import java.awt.*;
public class Rectangles extends Applet
{
  DrawRectangles m_DrawRectThread = null;
  DrawEllipse m_DrawEllipseThread = null;
  NotifyTask m_NotifyTaskThread = null
  public String getAppletInfo()
  {
    return "Name: Rectangles";
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension = getSize();
    g.setColor(Color.yellow);
    g.fillRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
  }
  public void start()
  {
    if (m_DrawRectThread == null)
    {
      m_DrawRectThread =
        new DrawRectangles(this);
      m_DrawRectThread.start();
    }
    if (m_DrawEllipseThread == null)
    {
      m_DrawEllipseThread =
        new DrawEllipse(this);
      m_DrawEllipseThread.start();
    }
    if (m_NotifyTaskThread == null)
    {
      m_NotifyTaskThread =
        new NotifyTask(m_DrawEllipseThread);
      m_NotifyTaskThread.start();
    }
  }

  public void stop()
  {
    if (m_DrawRectThread != null)
    {
      m_DrawRectThread.stop();
      m_DrawRectThread = null;
    }
    if (m_DrawEllipseThread == null)
    {
      m_DrawEllipseThread.stop();
      m_DrawEllipseThread = null;
    }
    if (m_NotifyTaskThread != null)
    {
      m_NotifyTaskThread.stop();
      m_NotifyTaskThread = null;
    }
  }
}
class DrawRectangles extends Thread
{
  Graphics g;
  Dimension dimAppWndDimension;
  public DrawRectangles(Applet Appl)
  {
    g = Appl.getGraphics();
    dimAppWndDimension = Appl.getSize();
  }
  public void run()
  {
    while (true)
    {
      int x, y, width, height;
      int rColor, gColor, bColor;
      x =      (int)(dimAppWndDimension.width
            * Math.random());
      y =      (int)(dimAppWndDimension.height
            * Math.random());
      width  = (int)(dimAppWndDimension.width
            * Math.random()) / 2;
      height = (int)(dimAppWndDimension.height
            * Math.random()) / 2;
      rColor = (int)(255 * Math.random());
      gColor = (int)(255 * Math.random());
      bColor = (int)(255 * Math.random());
      g.setColor(new Color(rColor,
        gColor, bColor));
      g.fillRect(x, y, width, height);
      try
      {
    Thread.sleep(50);
      }
      catch (InterruptedException e)
      {
    stop();
      }
    }
  }
}
class DrawEllipse extends Thread
{
  Graphics g;
  Dimension dimAppWndDimension;
  public DrawEllipse(Applet Appl)
  {
    g = Appl.getGraphics();
    dimAppWndDimension = Appl.getSize();
  }
  public synchronized void run()
  {
    while (true)
    {
      int x, y, width, height;
      int rColor, gColor, bColor;
      x =      (int)(dimAppWndDimension.width
          * Math.random());
      y =      (int)(dimAppWndDimension.height
          * Math.random());
      width  = (int)(dimAppWndDimension.width
          * Math.random()) / 2;
      height = (int)(dimAppWndDimension.height
          * Math.random()) / 2;
      rColor = (int)(255 * Math.random());
      gColor = (int)(255 * Math.random());
      bColor = (int)(255 * Math.random());
      g.setColor(new Color(rColor,
         gColor, bColor));
      g.fillOval(x, y, width, height);
      try
      {
    this.wait();
      }
      catch (InterruptedException e)
      {
      }
    }
  }
}
class NotifyTask extends Thread
{
  Thread STask;
  public NotifyTask(Thread SynchroTask)
  {
    STask = SynchroTask;
  }
  public void run()
  {
    while (true)
    {
      try
      {
    Thread.sleep(30);
      }
      catch (InterruptedException e)
      {
      }
      synchronized(STask)
      {
        STask.notify();
      }
    }
  }
}


Описание исходных текстов аплета Rectangles

Оглавление

Многопоточность
Процессы, потоки и приоритеты
Многопоточность в Java
Многопоточность и анимация
Синхронизация потоков
Потоки-демоны
Аплет Rectangles
Исходные тексты
Описание текстов

Назад Вперед

В этом приложении мы создаем на базе класса Thread три класса. Первый из них предназначен для создания потока рисования прямоугольников, второй - для создания потока рисования закрашенных эллипсов, а третий - для управления потоком рисования эллипсов.

Что же касается основного класса аплета, то он унаследован, как обычно, от класса Applet и не реализует интерфейс Runnable:

public class Rectangles extends Applet
{
  . . .
}

Поля класса Rectangles

В классе Rectangles мы определили три поля с именами m_DrawRectThread, m_DrawEllipseThread и m_NotifyTaskThread:

DrawRectangles m_DrawRectThread = null;
DrawEllipse m_DrawEllipseThread = null;
NotifyTask m_NotifyTaskThread = null

Эти поля являются ссылками на классы, соответственно DrawRectangles, DrawEllipse и NotifyTask . Первый из них создан для рисования прямоугольников, второй - эллипсов, а третий - для управления потоком рисования эллипсов.

Указанные поля инициализируются занчением null, что соответствует неработающим или несозданным задачам.

Метод start класса Rectangles

Этот метод последовательно создает три потока и запускает их на выполнение:

if(m_DrawRectThread == null)
{
  m_DrawRectThread =
    new DrawRectangles(this);
  m_DrawRectThread.start();
}
if(m_DrawEllipseThread == null)
{
  m_DrawEllipseThread =
    new DrawEllipse(this);
  m_DrawEllipseThread.start();
}
if(m_NotifyTaskThread == null)
{
  m_NotifyTaskThread =
    new NotifyTask(m_DrawEllipseThread);
  m_NotifyTaskThread.start();
}

В качестве параметра конструкторам классов DrawRectangles и DrawEllipse мы передаем ссылку на аплет Rectangles. Эта ссылка будет нужна для получения контекста отображения и рисования геометрических фигур.

Поток класса NotifyTask будет управлять работой потока DrawEllipse, поэтому мы передаем его конструктору ссылку на соответствующий объект m_DrawEllipseThread.

Метод stop класса Rectangles

Когда пользователь покидает страницу сервера Web с аплетом, метод stop класса Rectangles последовательно останавливает gjnjrb рисования прямоугольников и эллипсов, а также управляющий поток:

if(m_DrawRectThread != null)
{
  m_DrawRectThread.stop();
  m_DrawRectThread = null;
}
if(m_DrawEllipseThread == null)
{
  m_DrawEllipseThread.stop();
  m_DrawEllipseThread = null;
}
if(m_NotifyTaskThread != null)
{
  m_NotifyTaskThread.stop();
  m_NotifyTaskThread = null;
}

Поля класса DrawRectangles

Класс DrawRectangles определен для потока рисования прямоугольников:

class DrawRectangles extends Thread
{
  . . .
}

В поле g класа хранится контекст отображения окна аплета, а в поле dimAppWndDimension - размеры этого окна:

Graphics g;
Dimension dimAppWndDimension;

Значения этих полей определяются конструктором класса по ссылке на главный класс аплета.

Конструктор класса DrawRectangles

В качестве параметра конструктору передается ссылка на класс аплета. Конструктор использует эту ссылку для получения и сохранения в полях класса контекста отображения и размеров окна аплета:

public DrawRectangles(Applet Appl)
{
  g = Appl.getGraphics();
  dimAppWndDimension = Appl.getSize();
}

Метод run класса DrawRectangles

Программный код метода run работает в рамках отдельного потока. Он рисует в окне аплета закрашенные прямоугольники. Прямоугольники имеют случайные координаты, расположение и цвет.

Для того чтобы рисовать, необходимо получить контекст отображения. Этот контекст был получен конструктором класса DrawRectangles и может быть использован методом run.

Вооружившись контекстом отображения и размерами окна аплета, поток входит в бесконечный цикл рисования прямоугольников.

В качестве генератора случайных чисел мы используем метод random из класса Math, который при каждом вызове возвращает новое случайное число типа double, лежащее в диапазоне значений от 0.0 до 1.0.

Координаты по осям X и Y рисуемого прямоугольника определяются простым умножением случайного числа, полученного от метода random, соответственно, на ширину и высоту окна аплета:

x = (int)(dimAppWndDimension.width
  * Math.random());
y = (int)(dimAppWndDimension.height
  * Math.random());

Аналогично определяются размеры прямоугольника, однако чтобы прямоугольники не были слишком крупными, мы делим полученные значения на 2:

width  = (int)(dimAppWndDimension.width
  * Math.random()) / 2;
height = (int)(dimAppWndDimension.height
  * Math.random()) / 2;

Так как случайное число имеет тип double, в обоих случаях мы выполняем явное преобразование результата вычислений к типу int.

Для случайного выбора цвета прямоугольника мы вычисляем отдельные цветовые компоненты, умножая значение, полученное от метода random, на число 255:

rColor = (int)(255 * Math.random());
gColor = (int)(255 * Math.random());
bColor = (int)(255 * Math.random());

Полученные значения цветовых компонент используются в конструкторе Color для получения цвета. Этот цвет устанавливается в контексте отображения методом setColor:

g.setColor(new Color(rColor,
  gColor, bColor));

Теперь все готово для рисования прямоугольника, которое мы выполняем при помощи метода fillRect:

g.fillRect(x, y, width, height);

После рисования прямоугольника метод run задерживает свою работу на 50 миллисекунд, вызывая метод sleep:

try
{
  Thread.sleep(50);
}
catch (InterruptedException e)
{
  stop();
}

Для обработки исключения InterruptedException, которое может возникнуть во время работы этого метода, мы предусмотрели блок try - catch. При возникновении указанного исключения работа потока останавливается вызовом метода stop.

Метод run класса DrawEllipse

Класс DrawEllipse очень похож на только что рассмотренный класс DrawRectangles. Отличие есть только в финальном фрагменте метода run, который мы и рассмотрим.

Вместо задержки на 50 миллисекунд метод run из класса DrawEllipse переходит в состояние ожидания извещения, вызывая метод wait:

try
{
  this.wait();
}
catch (InterruptedException e)
{
}

Это извещение создается управляющим потоком класса NotifyTask, к описанию которого мы переходим.

Поля класса NotifyTask

В классе NotifyTask мы определили одно поле STask класса Thread. Это поле которое хранит ссылку на поток, работой которого управляет данный класс:

class NotifyTask extends Thread
{
  Thread STask;
   . . .
}

Конструктор класса NotifyTask

Конструктор класса NotifyTask записывает в поле STask ссылку на задачу рисования эллипсов:

public NotifyTask(Thread SynchroTask)
{
  STask = SynchroTask;
}

Метод run класса NotifyTask

Метод run класса NotifyTask периодически разблокирует поток рисования эллипсов, вызывая для этого метод notify в цилке с задержкой 30 миллисекунд. Обращение к объекту STask, который хранит ссылку на поток рисования эллипсов, выполняется с использованием синхронизации:

public void run()
{
  while (true)
  {
    try
    {
      Thread.sleep(30);
    }
    catch (InterruptedException e)
    {
    }
    synchronized(STask)
    {
      STask.notify();
    }
  }
}


Работа с файлами

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Библиотека классов языка программирования Java содержит многочисленные средства, предназначенные для работы с файлами. И хотя аплеты не имеют доступа к локальным файлам, расположенным на компьютере пользователя, они могут обращаться к файлам, которые находятся в каталоге сервера Web. Автономные приложения Java могут работать как с локальными, так и с удаленными файлами (через сеть Internet или Intranet).

В любом случае, будете ли вы создавать автономные приложения Java или аплеты, взаимодействующие с сервером Web через сеть, вы должны познакомиться с классами, предназначенными для организации ввода и вывода.


Классы Java для работы с потоками

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Программист, создающий автономное приложение Java, может работать с потоками нескольких типов:

  • стандартные потоки ввода и вывода;
  • потоки, связанные с локальными файлами;
  • потоки, связанные с файлами в оперативной памяти;
  • потоки, связанные с удаленными файлами

Рассмотрим кратко классы, связанные с потоками.

Стандартные потоки

Для работы со стандартными потоками в классе System имеется три статических объекта: System.in, System.out и System.err. По своему назначению эти потоки больше всего напоминают стандартные потоки ввода, вывода и вывода сообщений об ошибках операционной системы MS-DOS.

Поток System.in связан с клавиатурой, поток System.out и System.err - с консолью приложения Java.

Базовые классы для работы с файлами и потоками

Количество классов, созданных для работы с файлами, достаточно велико, чтобы привести начинающего программиста в растерянность. Прежде чем мы займемся конкретными классами и приведем примеры приложений, работающих с потоками и файлами, рассмотрим иерархию классов, предназначенных для орагнизации ввода и вывода.

Все основные классы, интересующие нас в этой главе, произошли от класса Object (рис. 1).

pic01.gif (2917 bytes)

Рис. 1. Основные классы для работы с файлами и потоками

Класс InputStream

Класс InputStream является базовым для большого количества классов, на основе которых создаются потоки ввода. Именно производные классы применяются программистами, так как в них имеются намного более мощные методы, чем в классе InputStream. Эти методы позволяют работать с потоком ввода не на уровне отдельных байт, а на уровне объектов различных классов, например, класса String и других.

Класс OutputStream

Аналогично, класс OutputStream служит в качестве базового для различных классов, имеющих отношение к потокам вывода.

Класс RandomAccesFile

С помощью класса RandomAccesFile можно организовать работу с файлами в режиме прямого доступа, когда программа указывает смещение и размер блока данных, над которым выполняется операция ввода или вывода. Заметим, кстати, что классы InputStream и OutputStream также можно использовать для обращения к файлам в режиме прямого доступа.

Класс File

Класс File предназначен для работы с оглавлениями каталогов. С помощью этого класса можно получить список файлов и каталогов, расположенных в заданном каталоге, создать или удалить каталог, переименовать файл или каталог, а также выполнить некоторые другие операции.

Класс FileDescriptor

C помощью класса FileDescriptor вы можете проверить идентификатор открытого файла.

Класс StreamTokenizer

Очень удобен класс StreamTokenizer. Он позволяет организовать выделение из входного потока данных элементов, отделенных друг от друга заданными разделителями, такими, например, как запятая, пробел, символы возврата каретки и перевода строки.

Производные от класса InputStream

От класса InputStream производится много других классов, как это показано на рис. 2.

pic02.gif (5262 bytes)

Рис. 2. Классы, производные от класса InputStream

Класс FilterInputStream

Класс FilterInputStream, который происходит непосредственно от класса InputStream, является абстрактным классом, на базе которого созданы классы BufferedInputStream, DataInputStream, LineNumberInputStream и PushBackInputStream. Непосредственно класс FilterInputStream не используется в приложениях Java, так как, во-первых, он является абстрактным и предназначен для переопределения методов базового класса InputStream, а во-вторых, наиболее полезные методы для работы с потоками ввода имеются в классах, созданных на базе класса FilterInputStream.

Класс BufferedInputStream

Буферизация операций ввода и вывода в большинстве случаев значительно ускоряет работу приложений, так как при ее использовании сокращается количество обращений к системе для обмена данными с внешними устройствами.

Класс BufferedInputStream может быть использован приложениями Java для организации буферизованных потоков ввода. Заметим, что конструкторы этого класса в качестве параметра получают ссылку на объект класса InputStream. Таким образом, вы не можете просто создать объект класса BufferedInputStream, не создав перед этим объекта класса InputStream. Подробности мы обсудим позже.

Класс DataInputStream

Составляя программы на языке программирования С, вы были вынуждены работать с потоками на уровне байт или, в лучшем случае, на уровне текстовых строк. Однако часто возникает необходимость записывать в потоки данных и читать оттуда объекты других типов, например, целые числа и числа типа double, числа в формате с плавающей десятичной точкой, массивы байт и символов и так далее.

Класс DataInputStream содержит методы, позволяющие извлекать из входного потока данные в перечисленных выше форматах или, как говорят, выполнять форматированный ввод данных. Он также реализует интерфейс DataInput, служащий для этой же цели. Поэтому класс DataInputStream очень удобен и часто применяется в приложениях для работы с потоками ввода.

Так же как и конструктор класса BufferedInputStream, конструктор класса DataInputStream должен получить через свои параметр ссылку на объект класса InputStream.

Класс LineNumberInputStream

С помощью класса LineNumberInputStream вы можете работать с текстовыми потоками, состоящими из отдельных строк, разделенных символами возврата каретки \r и перехода на следующую строку \n. Методы этого класса позволяют следить за нумерацией строк в таких потоках.

Класс PushBackInputStream

Класс PushBackInputStream позволяет возвратить в поток ввода только что прочитанный оттуда символ, с тем чтобы после этого данный символ можно было прочитать снова.

Класс ByteArrayInputStream

При необходимости вы можете создать в приложениях Java входной поток данных не на базе локального или удаленного файла, а на базе массива, расположенного в оперативной памяти. Класс ByteArrayInputStream предназначен именно для этого - вы передаете конструктору класса ссылку на массив, и получаете входной поток данных, связанный с этим массивом.

Потоки в оперативной памяти могут быть использованы для временного хранения данных. Заметим, что так как аплеты Java не могут обращаться к локальным файлам, для создания временных файлов можно использовать потоки в оперативной памяти на базе класса ByteArrayInputStream. Другую возможность предоставляет класс StringBufferInputStream, рассмотренный ниже.

Класс StringBufferInputStream

Класс StringBufferInputStream позволяет создавать потоки ввода на базе строк класса String, используя при этом только младшие байты хранящихся в такой строке символов. Этот класс может служить дополнением для класса ByteArrayInputStream, который также предназначен для создания потоков на базе данных из оперативной памяти.

Класс FileInputStream

Этот класс позволяет создать поток ввода на базе класса File или FileDescriptor.

Класс PipedInputStream

С помощью классов PipedInputStream и PipedOutputStream можно организовать двухстороннюю передачу данных между двумя одновременно работающими задачами мультизадачного аплета.

Класс SequenceInputStream

Класс SequenceInputStream позволяет объединить несколько входных потоков в один поток. Если в процессе чтения будет достигнут конец первого потока такого объединения, в дальнейшем чтение будет выполняться из второго потока и так далее.

Производные от класса OutputStream

Класс OutputStream предназначен для создания потоков вывода. Приложения, как правило, непосредственно не используют этот класс для операций вывода, так же как и класс InputStream для операций ввода. Вместо этого применяются классы, иерархия которых показана на рис. 3.

pic03.gif (3919 bytes)

Рис. 3. Классы, производные от класса OutputtStream

Рассмотрим кратко назначение этих классов.

Класс FilterOutputStream

Абстрактный класс FilterOutputStream служит прослойкой между классом OutputStream и классами BufferedOutputStream, DataOutputStream, а также PrintStream. Он выполняет роль, аналогичную роли рассмотренного ранее класса FilterIntputStream.

Класс BufferedOutputStream

Класс BufferedOutputStream предназначен для создания буферизованных потоков вывода. Как мы уже говорили, буферизация ускоряет работу приложений с потоками.

Класс DataOutputStream

С помощью класса DataOutputStream приложения Java могут выполнять форматированный вывод данных. Для ввода форматированных данных вы должны создать входной поток с использованием класса DataInputStream, о котором мы уже говорили. Класс DataOutputStream реализует интерфейс DataOutput.

Класс PrintStream

Потоки, созданные с использованием класса PrintStream, предназначены для форматного вывода данных различных типов с целью их визуального представления в виде текстовой строки. Аналогичная операция в языке программирования С выполнялась функцией printf.

Класс ByteArrayOutputStream

С помощью класса ByteArrayOutputStream можно создать поток вывода в оперативной памяти.

Класс FileOutputStream

Этот класс позволяет создать поток вывода на базе класса File или FileDescriptor.

Класс PipedOutputStream

Как мы уже говорили, классы PipedInputStream и PipedOutputStream предназначены для организации двухсторонней передачи данных между двумя одновременно работающими задачами мультизадачного аплета.


Работа со стандартными потоками

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Приложению Java доступны три стандратных потока, которые всегда открыты: стандартный поток ввода, стандартный поток вывода и стандартный поток вывода сообщений об ошибках.

Все перечисленные выше потоки определены в классе System как статические поля с именами, соответственно, in, out и err:

public static PrintStream err;
public static InputStream in;
public static PrintStream out;

Заметим, что стандратные потоки, как правило, не используются аплетами, так как браузеры общаются с пользователем через окно аплета и извещения от мыши и клавиатуры, а не через консоль.

Стандартный поток ввода

Стандартный поток ввода in определен как статический объект класса InputStream, который содержит только простейшие методы для ввода данных. Нужнее всего вам будет метод read:

public int read(byte b[]);

Этот метод читает данные из потока в массив, ссылка на который передается через единственный параметр. Количество считанных данных определяется размером массива, то есть значением b.length.

Метод read возвращает количество прочитанных байт данных или -1, если достигнут конец потока. При возникновении ошибок создается исключение IOException, обработку которого необходимо предусмотреть.

Стандартный поток вывода

Стандартный поток вывода out создан на базе класса PrintStream, предназначенного, как мы это отмечали раньше, для форматированного вывода данных различного типа с целью их визуального отображения в виде текстовой строки.

Для работы со стандартным потоком вывода вы будете использовать главным образом методы print и println, хотя метод write также доступен.

В классе PrintStream определено несколько реализаций метода print с параметрами различных типов:

public void print(boolean b);
public void print(char c);
public void print(char s[]);
public void print(double d);
public void print(float f);
public void print(int i);
public void print(long l);
public void print(Object obj);
public void print(String s);

Как видите, вы можете записать в стандартный поток вывода текстовое представление данных различного типа, в том числе и класса Object.

Метод println аналогичен методу print, отличаясь лишь тем, что он добавляет к записываемой в поток строке символ перехода на следующую строку:

public void println();
public void println(boolean b);
public void println(char c);
public void println(char s[]);
public void println(double d);
public void println(float f);
public void println(int i);
public void println(long l);
public void println(Object obj);
public void println(String s);

Реализация метода println без параметров записывает только символ перехода на следующую строку.

Стандртный поток вывода сообщений об ошибках

Стандртный поток вывода сообщений об ошибках err так же, как и стадартный поток вывода out, создан на базе класса PrintStream. Поэтому для записи сообщений об ошибках вы можете использовать только что описанные методы print и println.


Создание потоков, связанных с файлами

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
 Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Если вам нужно создать входной или выходной поток, связанный с локальным файлом, следует воспользоваться классами из библиотеки Java, созданными на базе классов InputStream и OutputStream. Мы уже кратко рассказывали об этих классах в разделе "Классы Java для работы с потоками". Однако методика использования перечисленных в этом разделе классов может показаться довольно странной.

В чем эта странность?

Говоря кратко, странность заключается в том, что для создания потока вам необходимо воспользоваться сразу несколькими классами, а не одним, наиболее подходящим для решения поставленной задачи, как это можно было бы предположить.

Поясним сказанное на примере.

Пусть, например, нам нужен выходной поток для записи форматированных данных (скажем, текстовых строк класса String). Казалось бы, достаточно создать объект класса DataOutputStream, - и дело сделано. Однако не все так просто.

В классе DataOutputStream предусмотрен только один конструктор, которому в качестве параметра необходимо передать ссылку на объект класса OutputStream:

public DataOutputStream(OutputStream out);

Что же касается конструктора класса OutputStream, то он выглядит следующим образом:

public OutputStream();

Так как ни в том, ни в другом конструкторе не предусмотрено никаких ссылок на файлы, то непонятно, как с использованием только одних классов OutputStream и DataOutputStream можно создать выходной поток, связанный с файлом.

Что же делать?

Создание потока для форматированного обмена данными

Оказывается, создание потоков, связанных с файлами и предназначенных для форматированного ввода или вывода, необходимо выполнять в несколько приемов. При этом вначале необходимо создать потоки на базе класса FileOutputStream или FileInputStream, а затем передать ссылку на созданный поток констркутору класса DataOutputStream или DataInputStream.

В классах FileOutputStream и FileInputStream предусмотрены конструкторы, которым в качестве параметра передается либо ссылка на объект класса File, либо ссылка на объект класса FileDescriptor, либо, наконец, текстовая строка пути к файлу:

public FileOutputStream(File file);
public FileOutputStream(
  FileDescriptor fdObj);
public FileOutputStream(String name);

Таким образом, если вам нужен выходной поток для записи форматированных данных, вначале вы создаете поток как объект класса FileOutputStream. Затем ссылку на этот объект следует передать конструктору класса DataOutputStream. Полученный таким образом объект класса DataOutputStream можно использовать как выходной поток, записывая в него форматированные данные.

Добавление буферизации

А что, если нам нужен не простой выходной поток, а буферизованный?

Здесь нам может помочь класс BufferedOutputStream. Вот два конструктора, предусмотренных в этом классе:

public BufferedOutputStream(
  OutputStream out);
public BufferedOutputStream(
  OutputStream out, int size);

Первый из них создает буферизованный выходной поток на базе потока класса OutputStream, а второй делает то же самое, но дополнительно позволяет указать размер буфера в байтах.

Если вам нужно создать выходной буферизованный поток для записи форматированных данных, создание потока выполняется в три приема:

  • создается поток, связанный с файлом, как объект класса FileOutputStream;
  • ссылка на этот поток передается конструктору класса BufferedOutputStream, в результате чего создается буферизованный поток, связанный с файлом;
  • ссылка на буферизованный поток, созданный на предыдущем шаге, передается конструктору класса DataOutputStream, который и создает нужный поток

Вот фрагмент исходного текста программы, который создает выходной буферизованный поток для записи форматированных данных в файл с именем output.txt:

DataOutputStream OutStream;
OutStream = new DataOutputStream(
  new BufferedOutputStream(
  new FileOutputStream("output.txt")));

Аналогичным образом создается входной буферизованный поток для чтения форматированных данных из того же файла:

DataInputStream InStream;
InStream = new DataInputStream(
  new BufferedInputStream(
  new FileInputStream("output.txt")));

Исключения при создании потоков

При создании потоков на базе классов FileOutputStream и FileInputStream могут возникать исключения FileNotFoundException, SecurityException, IOException.

Исключение FileNotFoundException возникает при попытке открыть входной поток данных для несуществующего файла, то есть когда файл не найден.

Исключение SecurityException возникает при попытке открыть файл, для которого запрещен доступ. Например, если файл можно только читать, а он открывается для записи, возникнет исключение SecurityException.

Если файл не может быть открыт для записи по каким-либо другим причинам, возникает исключение IOException.


Запись данных в поток и чтение данных из потока

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Для обмена данными с потоками можно использовать как простейшие методы write и read, так и методы, допускающие ввод или вывод форматированных данных. В зависимости от того, на базе какого класса создан поток, зависит набор доступных методов, предназначенных для чтения или записи данных.

Простейшие методы

Создав выходной поток на базе класса FileOutputStream, вы можете использовать для записи в него данных три разновидности метода write, прототипы которых представлены ниже:

public void write(byte b[]);
public void write(byte b[],
  int off, int len);
public void write(int b);

Первый из этих методов записывает в поток содержимое массива, ссылка на который передается через параметр, начиная с текущей позиции в потоке. После выполнения записи текущая позиция продвигается вперед на число записанных байт, которое при успешном завершении операции равно длине массива (b.length).

Второй метод позволяет дополнительно указать начальное смещение off записываемого блока данных в массиве и количество записываемых байт len.

Третий метод просто записывает в поток один байт данных.

Если в процессе записи происходит ошибка, возникает исключение IOException.

Для входного потока, созданного на базе класса FileInputStream, определены три разновидности метода read, выполняющего чтение данных:

public int read();
public int read(byte b[]);
public int read(byte b[],
  int off, int len);

Первая разновидность просто читает из потока один байт данных. Если достигнут конец файла, возвращается значение -1.

Вторая разновидность метода read читает данные в массив, причем количество прочитанных данных определяется размером массива. Метод возвращает количество прочитанных байт данных или значение -1, если в процессе чтения был достигнут конец файла.

И, наконец, третий метод позволяет прочитать данные в область массива, заданную своим смещением и длиной.

Если при чтении происходит ошибка, возникает исключение IOException.

Методы для чтения и записи форматированных данных

Вместо того чтобы записывать в потоки и читать оттуда отдельные байты или массивы байт, программисты обычно предпочитают пользоваться намного более удобными методами классов DataOutputStream и DataInputStream, допускающими форматированный ввод и вывод данных.

Вот, например, какой набор методов можно использовать для записи форматированных данных в поток класса DataOutputStream:

public final void writeBoolean(boolean v);
public final void writeByte(int v);
public final void writeBytes(String s);
public final void writeChar(int v);
public final void writeChars(String s);
public final void writeDouble(double v);
public final void writeFloat(float v);
public final void writeInt(int v);
public final void writeLong(long v);
public final void writeShort(int v);
public final void writeUTF(String s);

Хотя имена методов говорят сами за себя, сделаем замечания относительно применения некоторых из них.

Метод writeByte записывает в поток один байт. Это младший байт слова, которое передается методу через параметр v. В отличие от метода writeByte, метод writeChar записывает в поток двухбайтовое символьное значение (напомним, что в Java символы хранятся с использованием кодировки Unicode и занимают два байта).

Если вам нужно записать в выходной поток текстовую строку, то это можно сделать с помощью методов writeBytes, writeChars или writeUTF. Первый из этих методов записывает в выходной поток только младшие байты символов, а второй - двухбайтовые символы в кодировке Unicode. Метод writeUTF предназначен для записи строки в машинно-независимой кодировке UTF-8.

Все перечисленные выше методы в случае возникновения ошибки создают исключение IOException, которое вы должны обработать.

В классе DataInputStream определены следующие методы, предназначенные для чтения форматированных данных из входного потока:

public final boolean readBoolean();
public final byte    readByte();
public final char    readChar();
public final double  readDouble();
public final float   readFloat();
public final void    readFully(byte b[]);
public final void    readFully(byte b[],
   int off, int len);
public final int     readInt();
public final String  readLine();
public final long    readLong();
public final short   readShort();
public final int     readUnsignedByte();
public final int     readUnsignedShort();
public final String  readUTF();
public final static String readUTF(
  DataInput in);
public final int     skipBytes(int n);

Обратите внимание, что среди этих методов нет тех, что специально предназначены для четния данных, записанных из строк методами writeBytes и writeChars класса DataOutputStream.

Тем не менее, если входной поток состоит из отдельных строк, разделенных символами возврата каретки и перевода строки, то такие строки можно получить методом readLine. Вы также можете воспользоваться методом readFully, который заполняет прочитанными данными массив байт. Этот массив потом будет нетрудно преобразовать в строку типа String, так как в классе String предусмотрен соответствующий конструктор.

Для чтения строк, записанных методом writeUTF вы должны обязательно пользоваться методом readUTF.

Метод skipBytes позволяет пропустить из входного потока заданное количество байт.

Методы класса DataInputStream, предназначенные для чтения данных, могут создавать исключения IOException и EOFException. Первое из них возникает в случае ошибки, а второе - при достижении конца входного потока в процессе чтения.


Закрывание потоков

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Работая с файлами в среде MS-DOS или Windows средствами языка программирования С вы должны были закрывать ненужные более файлы. Так как в системе интерпертации приложений Java есть процесс сборки мусора, возникает вопрос - выполняет ли он автоматическое закрывание потоков, с которыми приложение завершило работу?

Оказывается, процесс сборки мусора не делает ничего подобного!

Сборка мусора выполняется только для объектов, размещенных в оперативной памяти. Потоки вы должны закрывать явным образом, вызывая для этого метод close.


Принудительный сброс буферов

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Еще один важный момент связан с буферизованными потоками. Как мы уже говорили, буферизация ускоряет работу приложений с потоками, так как при ее использовании сокращается количество обращений к системе ввода/вывода. Вы можете постепенно в течении дня добавлять в поток данные по одному байту, и только к вечеру эти данные будут физически записаны в файл на диске.

Во многих случаях, однако, приложение должно, не отказываясь совсем от буферизации, выполнять принудительную запись буферов в файл. Это можно сделать с помощью метода flush.


Потоки в оперативной памяти

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Операционные системы Windows 95 и Windows NT предоставляют возможность для программиста работать с оперативной памятью как с файлом. Это очень удобно во многих случаях. В частности, файлы, отображаемые на память, можно использовать для передачи данных между одновременно работающими задачами и процессами.

При создании приложений и аплетов Java вы также можете работать с объектами оперативной памяти, как с файлами, а точнее говоря, как с потоками. Так как аплетам запрещено обращаться к файлам, расположенным на локальном диске компьютера, при небходимости создания временных потоков ввода или вывода последние могут быть размещены в оперативной памяти.

Ранее мы отмечали, что в библиотеке классов Java есть три класса, специально предназначенных для создания потоков в оперативной памяти. Это классы ByteArrayOutputStream, ByteArrayInputStream и StringBufferInputStream.

Класс ByteArrayOutputStream

Класс ByteArrayOutputStream создан на базе класса OutputStream. В нем имеется два конструктора, прототипы которых представлены ниже:

public ByteArrayOutputStream();
public ByteArrayOutputStream(
  int size);

Первый из этих конструкторов создает выходной поток в оперативной памяти с начальным размером буфера, равным 32 байта. Второй позволяет указать необходимый размер буфера.

В классе ByteArrayOutputStream определено несколько достаточно полезных методов. Вот некоторые из них:

public void reset();
public int size();
public byte[] toByteArray();
public void writeTo(OutputStream out);

Метод reset сбрасывает счетчик байт, записанных в выходной поток. Если данные, записанные в поток вам больше не нужны, вы можете вызвать этот метод и использовать память, выделенную для потока, для записи других данных.

С помощью метода size можно определить количество байт данных, записанных в поток.

Метод toByteArray позволяет скопировать данные, записанные в поток, в массив байт. Этот метод возвращает адрес созданного для этой цели массива.

С помощью метода writeTo вы можете скопировать содержимое данного потока в другой выходной поток, ссылка на который передается методу через параметр.

Для выполнения форматированного вывода в поток, вы должны создать поток на базе класса DataOutputStream, передав соответствующему конструктору ссылку на поток класса ByteArrayOutputStream.

Класс ByteArrayInputStream

С помощью класса ByteArrayInputStream вы можете создать входной поток на базе массива байт, расположенного в оперативной памяти. В этом классе определено два конструктора:

public ByteArrayInputStream(byte buf[]);
public ByteArrayInputStream(
  byte buf[], int offset, int length);

Первый конструктор получает через единственный параметр ссылку на массив, который будет использован для создания входного потока. Второй позволяет дополнительно указать смещение offset и размер области памяти length, которая будет использована для создания потока.

Вот несколько методов, определенных в классе ByteArrayInputStream:

public int available();
public int read();
public int read(byte b[],int off, int len);
public void reset();
public long skip(long n);

Наиболее интересен из них метод available, с помощью которого можно определить, сколько байт имеется во входном потоке для чтения.

Обычно класс ByteArrayInputStream используется вместе с классом DataInputStream, что позволяет организовать форматный ввод данных.

Класс StringBufferInputStream

Класс StringBufferInputStream предназначен для создания входного потока на базе текстовой строки класса String. Ссылка на эту строку передается конструктору класса StringBufferInputStream через параметр:

public StringBufferInputStream(String s);

В классе StringBufferInputStream определены те же методы, что и в только что рассмотренном классе ByteArrayInputStream. Для более удобной работы вы, вероятно, создадите на базе потока класса StringBufferInputStream поток класса DataInputStream.


Класс StreamTokenizer для разбора входных потоков

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Если вы создаете приложение, предназначенное для обработки текстов (например, транслятор или просто разборщик файла конфигурации, содержащего значения различных параметров), вам может пригодиться класс StreamTokenizer. Создав объект этого класса для входного потока, вы можете легко решить задачу выделения из этого потока отдельных слов, символов, чисел и строк комментариев.

Конструктор класса StreamTokenizer

Для создание объектов класса StreamTokenizer предусмотрен всего один конструктор:

public StreamTokenizer(InputStream istream);

В качестве параметра этому конструктору необходимо передать ссылку на заранее созданный входной поток.

Методы класса StreamTokenizer

Для настройки параметров разборщика StreamTokenizer и получения отдельных элементов входного потока вы должны пользоваться методами, определенными в классе StreamTokenizer. Рассмотрим самые важние из них.

Методы для настройки параметров разборщика

Ниже мы привели прототипы методов, предназначенных для настройки параметров разборщика:

public void commentChar(int ch);
public void slashSlashComments(boolean flag);
public void slashStarComments(boolean flag);
public void quoteChar(int ch);

public void eolIsSignificant(boolean flag);
public void lowerCaseMode(boolean fl);

public void ordinaryChar(int ch);
public void ordinaryChars(int low,int hi);
public void resetSyntax();

public void parseNumbers();
public void whitespaceChars(int low, int hi);
public void wordChars(int low, int hi);

Несколько методов определяют, будет ли разборщик выделять во входном потоке строки комментария и если будет, то какми образом.

С помощью метода commentChar вы можете указать символ комментария. Если в строке входного потока попадется такой символ, то он и все следующие за ним до конца текущей строки символы будут проигнорированы.

Методы SlashSlashComments и slashStarComments позволяют указать, что для входного текста используются разделители комментариев в виде двойного символа '/' и '/* … */', соответственно. Это соответствует способу указания комментариев в программах, составленных на языках программирования С++ и С. Для включения режима выделения комментариев обоим методам в качетстве параметра необходимо передать значение true, а для отключения - false.

Метод quoteChar позволяет задать символ, который будет использован в качестве кавычек. Когда при разборе потока встречаются слова, взятые в кавычки, они возвращаются программе разбора без кавычек.

Если передать методу eolIsSignificant значение true, разделители строк будут интерпретироваться как отдельные элементы. Если же этому методу передать значение false, разделители строк будут использоваться аналогично пробелам для разделения элементов входного потока.

Метод lowerCaseMode позволяет включить режим, при котором все выделенные элементы будут перекодированы в строчные символы.

Методы ordinaryChar и ordinaryChars позволяют указать символы, которые должны интерпретироваться как обычные, из которых составляются слова или цифры. Например, если передать методу ordinaryChar символ '.', то слово java.io будет восприниматься как один элемент. Если же этого не сделать, то разборщик выделит из него три элемента - слово java, точку '.' и слово io. Метод ordinaryChars позволяет указать диапазон значений символов, которые должны интерпретироваться как обычные.

С помощью метода resetSyntax вы можете указать, что все символы будут рассматриваться, как обычные.

Метод parseNumbers включает режим разбора чисел, при котором распознаются и преобразуются числа в формате с плавающей десятичной точкой.

Метод whitespaceChars задает диапазон значений для символов-разделителей отдельных слов в потоке.

Метод wordChars позволяет указать символы, которые являются составными частями слов.

Методы для разбора входного потока

После того как вы создали разборщик входного потока на базе класса StreamTokenizer и установили его параметры с помощью описанных выше методов, можно приступать собственно к разборке потока. Обычно для этого организуется цикл, в котором вызывается метод nextToken:

public int nextToken();

Этот метод может вернуть одно из следующих значений:

Значение | Описание
TT_WORDTT_WORD | Из потока было извлечено слово
TT_NUMBERTT_NUMBER | Из потока было извлечено численное значение
TT_EOLTT_EOL | Обнаружен конец строки. Это значение возвращается только в том случае, если при настройке параметров разборщика был вызван метод eolIsSignficant
TT_EOFTT_EOF | Обнаружен конец файла

Если метод nextToken вернул значение TT_EOF, следует завершить цикл разбора входного потока.

Как извлечь считанные элементы потока?

В классе StreamTokenizer определено три поля:

public String sval;
public double nval;
public int    ttype;

Если метод nextToken вернул значение TT_WORD, в поле sval содержится извлеченный элемент в виде текстовой строки. В том случае, когда из входного потока было извлечено числовое значение, оно будет храниться в поле nval типа double. Обычные символы записываются в поле ttype.

Заметим, что если в потоке обнаружены слова, взятые в кавычки, то символ кавычки записывается в поле ttype, а слова - в поле sval. По умолчанию используется символ кавычек '"', однако с помощью метода quoteChar вы можете задать любой другой символ.

При необходимости в процессе разбора вы можете определить номер текущей строки, вызвав для этого метод lineno:

public int lineno();

После вызова метода pushBack следующий вызов метода nextToken приведет к тому, что в поле ttype будет записано текущее значение, а содержимое полей sval и nval не изменится. Прототип метода pushBack приведен ниже:

public void pushBack();

Метод toString возвращает текстовую строку, представляющую текущий элемент, выделенный из потока:

public String toString();


Класс StringTokenizer

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Рассказывая о классе StreamTokenizer, нельзя не упомянуть о другом классе с похожим названием и назначением, а именно о классе StringTokenizer.

Определение этого класса достаточно компактно.

  • Конструкторы
public StringTokenizer(String str);
public StringTokenizer(String str,
  String delim);
public StringTokenizer(String str,
  String delim,
  boolean returnTokens);
  • Методы
  public String nextToken();
  public String nextToken(String delim);
  public int countTokenss();
  public boolean hasMoreElements();
  public boolean hasMoreTokenss();
  public Object nextElement();
}

Класс StringTokenizer не имеет никакого отношения к потокам, так как предназначен для выделения отдельных элементов из строк типа String.

Конструкторы класса получают в качетсве первого параметра str ссылку на разбираемую строку. Второй параметр delim, если он есть, задает разделители, с испльзованием которых в строке будут выделяться элементы. Параметр returnTokens определяет, надо ли вовзвращать обнаруженные разделители как элементы разбираемой строки.

Рассмотрим кратко методы класса StringTokenizer.

Для разбора строки приложение должно организовать цикл, вызывая в нем метод nextToken. Условием завершения цикла может быть либо возникновение исключения NoSuchElementException, либо возврат значения false методами hasMoreElements или hasMoreTokens.

Метод countTokens позволяет определить, сколько раз был вызван метод nextToken перед возникновением исключения NoSuchElementException.


Работа с файлами и каталогами при помощи класса File

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

В предыдущих разделах мы рассмотрели классы, предназначенные для чтения и записи потоков. Однако часто возникает необходимость выполнения и таких операций, как определение атрибутов файла, создание или удаление каталогов, удаление файлов, получение списка всех файлов в каталоге и так далее. Для выполнения всех этих операций в приложениях Java используется класс с именем File.

Создание объекта класса File

У вас есть три возможности создать объект класса File, вызвав для этого один из трех конструкторов:

public File(String path);
public File(File dir, String name);
public File(String path, String name);

Первый из этих конструкторов имеет единственный параметр - ссылку на строку пути к файлу или каталогу. С помощью второго конструктора вы можете указать отдельно каталог dir и имя файла, для которого создается объект в текущем каталоге. И, наконец, третий конструктор позволяет указать полный путь к каталогу и имя файла.

Если первому из перечисленных конструкторов передать ссылку со значением null, возникнет исключение NullPointerException.

Пользоваться конструкторам очень просто. Вот, например, как создать объект класса File для файла c:\autoexec.bat и каталога d:\winnt:

f1 = new File("c:\\autoexec.bat");
f2 = new File("d:\\winnt");

Определение атрибутов файлов и каталогов

После того как вы создали объект класса File, нетрудно определить атрибуты этого объекта, воспользовавшись соответствующими методами класса File.

Проверка существования файла или каталога

С помощью метода exists вы можете проверить существование файла или катлога, для которого был создан объект класса File:

public boolean exists();

Этот метод можно применять перед созданием потока на базе класса FileOutputStream, если вам нужно избежать случайной перезаписи существующего файла. В этом случае перед созданием выходного потока класса FileOutputStream следует создать объект класса File, указав конструктору путь к файлу, а затем проверить сущестование файла методом exists.

Проверка возможности чтения и записи

Методы canRead и canWrite позволяют проверить возможность чтения из файла и записи в файл, соответственно:

public boolean canRead();
public boolean canWrite();

Их полезно применять перед созданием соответствующих потоков, если нужно избежать возникновение исключений, связанных с попыткой выполнения доступа неразрешенного типа. Если доступ разрешен, эти методы возвращают значение true, а если запрещен - false.

Определение типа объекта - файл или каталог

С помощью методов isDirectory и isFile вы можете проверить, чему соответствует созданный объект класса File - каталогу или файлу:

public boolean isDirectory();
public boolean isFile();

Получение имени файла или каталога

Метод getName возвращает имя файла или каталога для заданного объекта класса File (имя выделяется из пути):

public String getName();

Получение абсолютного пути к каталогу

Метод getAbsolutePath возвращает абсолютный путь к файлу или каталогу, который может быть машинно-зависимым:

public String getAbsolutePath();

Определение типа указанного пути - абсолютный или относительный

С помощью метода isAbsolute вы можете определить, соответствует ли данный объект класса File файлу или каталогу, заданному абсолютным (полным) путем, либо относительным путем:

public boolean isAbsolute();

Определение пути к файлу или каталогу

Метод getPath позволяет определить машинно-независимый путь файла или каталога:

public String getPath();

Определение родительского каталога

Если вам нужно определить родительский каталог для объекта класса File, то это можно сделать методом getParent:

public String getParent();

Определение длины файла в байтах

Длину файла в байтах можно определить с помощью метода length:

public long length();

Определение времени последней модификации файла или каталога

Для определения времени последней модификации файла или каталога вы можете вызвать метод lastModified:

public long lastModified();

Заметим, однако, что этот метод возвращает время в относительных единицах с момента запуска системы, поэтому его удобно использовать только для относительных сравнений.

Получение текстового представления объекта

Метод toString возвращает текстовую строку, представляющую объект класса File:

public String toString();

Получение значения хэш-кода

Метод hashCode возвращает значение хэш-кода, соответствующего объекту File:

public int hashCode();

Удаление файлов и каталогов

Для удаления ненужного файла или каталога вы должны создать соответствующий объект File и затем вызвать метод delete:

public boolean delete();

Создание каталогов

С помощью методов mkdir и mkdirs можно создавать новые каталоги:

public boolean mkdir();
public boolean mkdirs();

Первый из этих методов создает один каталог, второй - все подкаталоги, ведущие к создаваемому каталогу (то есть полный путь).

Переименование файлов и каталогов

Для переименования файла или каталога вы должны создать два объекта класса File, один из которых соответствует старому имени, а второй - новому. Затем для перовго из этих объектов нужно вызвать метод renameTo, указав ему в качестве параметра ссылку на второй объект:

public boolean renameTo(File dest);

В случае успеха метод возвращает значение true, при возникновении ошибки - false. Может также возникать исключение SecurityException.

Сравнение объектов класса File

Для сравнения объектов класса File вы должны использовать метод equals:

public boolean equals(Object obj);

Заметим, что этот метод сравнивает пути к файлам и каталогам, но не сами файли или каталоги.

Получение списка содержимого каталога

С помощью метода list вы можете получить список содержимого каталога, соответствующего данному объекту класса File. В классе File предусмотрено два варианта этого метода - без параметра и с параметром:

public String[] list();
public String[] list(FilenameFilter filter);

Первый из этих методв возвращает массив строк с именами содержимого каталога, не включая текущий каталог и родительский каталог. Второй позволяет получить список не всех объектов, хранящихся в каталоге, а только тех, что удовлетворяют условиям, определенным в фильтре filter класса FilenameFilter.


Произвольный доступ к файлам

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

В ряде случаев, например, при создании системы управления базой данных, требуется обеспечить произвольный доступ к файлу. Рассмотренные нами ранее потоки ввода и вывода пригодны лишь для последовательного доступа, так как в соответствующих классах нет средств позиционирования внутри файла.

Между тем библиотека классов Java содержит класс RandomAccessFile, который предназначен специально для организации прямого доступа к файлам как для чтения, так и для записи.

В классе RandomAccessFile определено два конструктора, прототипы которых показаны ниже:

public RandomAccessFile(
  String name, String mode);
public RandomAccessFile(
  File file, String mode);

Первый из них позволяет указывать имя файла, и режим mode, в котором открывается файл. Второй конструктор вместо имени предполагает использование объекта класса File.

Если файл открывается только для чтения, вы должны передать конструктору текстовую строку режима "r". Если же файл открывается и для чтения, и для записи, конструктору передается строка "rw".

Позиционирование внутри файла обеспечивается методом seek, в качестве параметра pos которому передается абсолютное смещение файла:

public void seek(long pos);

После вызова этого метода текущая позиция в файле устанавливается в соответствии со значением параметра pos.

В любой момент времени вы можете определить текущую позицию внутри файла, вызвав метод getFilePointer:

public long getFilePointer();

Еще один метод, который имеет отношение к позиционированию, называется skipBytes:

public int skipBytes(int n);

Он работает так же, как и одноименный метод для потоков - продвигает текущую позицию в файле на заданное количество байт.

С помощью метода close вы должны закрывать файл, после того как работа с им завершена:

public void close();

Метод getFD позволяет получить дескриптор файла:

public final FileDescriptor getFD();

С помощью метода length вы можете определить текущую длину файла:

public long length();

Ряд методов предназначен для выполнения как обычного, так и форматированного ввода из файла. Этот набор аналогичен методам, определенным для потоков:

public int read();
public int read(byte b[]);
public int read(byte b[],int off,int len);
public final boolean readBoolean();
public final byte    readByte();
public final char    readChar();
public final double  readDouble();
public final float   readFloat();
public final void    readFully(byte b[]);
public final void    readFully(byte b[],
   int off, int len);
public final int     readInt();
public final String  readLine();
public final long    readLong();
public final short   readShort();
public final int     readUnsignedBytee();
public final int     readUnsignedShort();
public final String  readUTF();

Существуют также методы, позволяющие выполнять обычную или форматированную запись в файл с прямым доступом:

public void write(byte b[]);
public void write(byte b[],int off,int len);
public void write(int b);
public final void writeBoolean(boolean v);
public final void writeBytee(int v);
public final void writeBytes(String s);
public final void writeChar(int v);
public final void writeChars(String s);
public final void writeDouble(double v);
public final void writeFloat(float v);
public final void writeInt(int v);
public final void writeLong(long v);
public final void writeShort(int v);
public final void writeUTF(String str);

Имена приведенных методов говорят сами за себя, поэтому мы не будем их описывать.


Приложение StreamToken

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

В приложении StreamToken мы демонстрируем использование класса StreamTokenizer для разбора входного потока.

Вначале приложение запрашивает у пользователя строку для разбора, записывая ее в файл. Затем этот файл открывается для чтения буферизованным потоком и разбирается на составные элементы. Каждый такой элемент выводится в отдельной строке, как это показано на рис. 4.

pic04.gif (5983 bytes)

Рис. 4. Разбор входного потока в приложении StreamToken

Обратите внимание, что в процессе разбора значение 3.14 было воспринято как числовое, а 3,14 - нет. Это потому, что при настройке разборщика мы указали, что символ '.' является обычным.

Исходный текст приложения

Исходный текст приложения StreamToken представлен в листинге 1.

Листинг 1. Файл StreamToken.java

import java.io.*;
public class StreamToken
{
  public static void main(String args[])
  {
    DataOutputStream OutStream;
    DataInputStream  InStream;
    byte bKbdInput[] = new byte[256];
    String sOut;
    try
    {
      System.out.println(
        "Enter string to parse...");
      System.in.read(bKbdInput);
      sOut = new String(bKbdInput, 0);
      OutStream = new DataOutputStream(
        new BufferedOutputStream(
          new FileOutputStream(
           "output.txt")));
      OutStream.writeBytes(sOut);
      OutStream.close();
      InStream = new DataInputStream(
        new BufferedInputStream(
          new FileInputStream(
            "output.txt")));
      TokenizerOfStream tos =
        new TokenizerOfStream();
      tos.TokenizeIt(InStream);
      InStream.close();
      System.out.println(
        "Press <Enter> to terminate...");
      System.in.read(bKbdInput);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
}
class TokenizerOfStream
{
  public void TokenizeIt(InputStream is)
  {
    StreamTokenizer stok;
    String str;
    try
    {
      stok = new StreamTokenizer(is);
      stok.slashSlashComments(true);
      stok.ordinaryChar('.');
      while(stok.nextToken() !=
         StreamTokenizer.TT_EOF)
      {
        switch(stok.ttype)
        {
          case StreamTokenizer.TT_WORD:
          {
            str = new String(
              "\nTT_WORD >" + stok.sval);
            break;
          }
          case StreamTokenizer.TT_NUMBER:
          {
            str = "\nTT_NUMBER >" +
               Double.toString(stok.nval);
            break;
          }
          case StreamTokenizer.TT_EOL:
          {
            str = new String("> End of line");
            break;
          }
          default:
            {
              if((char)stok.ttype == '"')
              {
                str = new String(
                  "\nTT_WORD >" + stok.sval);
              }
              else
                str = "> " +
                  String.valueOf(
                    (char)stok.ttype);
            }
        }
        System.out.println(str);
      }
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
}

Описание исходного текста приложения

После ввода строки с клавиатуры и записи ее в файл через поток наше приложение создает входной буферизованный поток, как это показано ниже:

InStream = new DataInputStream(
  new BufferedInputStream(
  new FileInputStream("output.txt")));

Далее для этого потока создается разборщик, который оформлен в отдельном классе TokenizerOfStream, определенном в нашем приложении:

TokenizerOfStream tos =
  new TokenizerOfStream();

Вслед за этим мы вызываем метод TokenizeIt, определенный в классе TokenizerOfStream, передавая ему в качестве параметра ссылку на входной поток:

tos.TokenizeIt(InStream);

Метод TokenizeIt выполняет разбор входного потока, отображая результаты разбора на консоли. После выполнения разбора входной поток закрывается методом close:

InStream.close();

Самое интересное в нашем приложении связано, очевидно, с классом TokenizerOfStream, поэтому перейдем к его описанию.

В этом классе определен только один метод TokenizeIt:

public void TokenizeIt(InputStream is)
{
  . . .
}

Получая в качестве параметра ссылку на входной поток, он прежде всего создает для него разборщик класса StreamTokenizer:

StreamTokenizer stok;
stok = new StreamTokenizer(is);

Настройка параметров разборщика очень проста и сводится к вызовам всего двух методов:

stok.slashSlashComments(true);
stok.ordinaryChar('.');

Метод slashSlashComments включает режим распознавания комментариев в стиле языка программирования С++, а метод ordinaryChar объявляет символ '.' обычным символом.

После настройки запускается цикл разбора входного потока, причем условием завершения цикла является достижение конца этого потока:

while(stok.nextToken() !=
  StreamTokenizer.TT_EOF)
{
  . . .
}

В цикле анализируется содержимое поля ttype, которое зависит от типа элемента, обнаруженного во входном потоке:

switch(stok.ttype)
{
  case StreamTokenizer.TT_WORD:
  {
    str = new String("\nTT_WORD >"
       + stok.sval);
    break;
  }
  case StreamTokenizer.TT_NUMBER:
  {
    str = "\nTT_NUMBER >" +
      Double.toString(stok.nval);
    break;
  }
  case StreamTokenizer.TT_EOL:
  {
    str = new String("> End of line");
    break;
  }
  default:
  {
    if((char)stok.ttype == '"')
      str = new String(
        "\nTT_WORD >" + stok.sval);
    else
      str = "> " + String.valueOf(
       (char)stok.ttype);
  }
}

На слова и численные значения мы реагируем очень просто - записываем их текстовое представление в рабочую переменную str типа String. При обнаружении конца строки в эту переменную записывается строка End of line.

Если же обнаружен обычный символ, мы сравниваем его с символом кавычки. При совпадении в переменную str записывается содержимое поля sval, в котором находятся слова, обнаруженные внутри кавычек. Если же обнаруженный символ не является символом кавычки, он преобразуется в строку и записывается в переменную str.

В заключении метод выводит строку str в стандартный поток вывода, отображая на консоли выделенный элемент потока:

System.out.println(str);


Приложение DirectFile

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

Назад Вперед

Для иллюстрации способов работы с классом RandomAccessFile мы подготовили приложение DirectFile, в котором создается небольшая база данных. Эта база данных состоит из двух файлов: файла данных и файла индекса.

В файле данных хранятся записи, сосотящие из двух полей - текстового и числового. Текстовое поле с названием name хранит строки, закрытые смиволами конца строки "\r\n", а числовое с названием account - значения типа int.

В меню File нашего приложения есть строки New и View records (рис. 5).

pic05.gif (2516 bytes)

Рис. 5. Строки меню File

С помощью строки New вы можете создать базу данных, состоящую из трех записей. Если выбрать из меню File строку View records, на экране появится диалоговая панель с содержимым этих записей (рис. 6).

pic06.gif (2365 bytes)

Рис. 6. Содержимое трех первых полей базы данных

Вместо символа перевода строки в диалоговой панели отображается маленький квадратик.

Дамп создаваемого файла данных приведен на рис. 7.

pic07.gif (1956 bytes)

Рис. 7. Дамп файла данных

Из этого дампа видно, что после первого запуска приложения в файле данных имеются следующие записи:

Номер записи | Смещение в файле данных | Поле name | Поле account
0 | 0 | Ivanov | 1000
1 | 12 | Petrov | 2000
2 | 24 | Sidoroff | 3000

При последующих запусках каждый раз в файл данных будут добавляться приведенные выше записи.

Так как поле name имеет переменную длину, для обеспечения возможности прямого доступа к записи по ее номеру необходимо где-то хранить смещения всех записей. Мы это делаем в файле индексов, дамп которого представлен на рис. 8.

pic08.gif (1575 bytes)

Рис. 8. Дамп файла индекса

Файл индексов хранит 8-байтовые смещения записей файла данных в формате long. Зная номер записи, можнор легко вычислить смещение в файле индексов, по которому хранится смещение нужной записи в файле данных. Если извлечь это смещение, то можно выполнить позиционирование в файле данных с целью чтения нужной записи, что и делает наше приложение.

Исходный текст приложения DirectFile

Исходный текст приложения DirectFile представлен в листинге 2.

Листинг 2. Файл DirectFile.java

import java.awt.*;
import java.io.*;
import java.util.*;
public class DirectFile
{
  public static void main(String args[])
  {
    MainFrameWnd frame =
      new MainFrameWnd("MenuApp");
    frame.setSize(
    frame.getInsets().left +
      frame.getInsets().right  + 320,
      frame.getInsets().top  +
      frame.getInsets().bottom + 240);
    frame.show();
  }
}
class MainFrameWnd extends Frame
{
  MenuBar mbMainMenuBar;
  Menu mnFile;
  Menu mnHelp;
  boolean fDBEmpty = true;
  public MainFrameWnd(String sTitle)
  {
    super(sTitle);
    setSize(400, 200);
    setBackground(Color.yellow);
    setForeground(Color.black);
    setLayout(new FlowLayout());
    mbMainMenuBar = new MenuBar();
    mnFile = new Menu("File");
    mnFile.add("New...");
    mnFile.add("View records...");
    mnFile.add("-");
    mnFile.add("Exit");
    mnHelp = new Menu("Help");
    mnHelp.add("Content");
    mnHelp.add("-");
    mnHelp.add("About");
    mbMainMenuBar.add(mnFile);
    mbMainMenuBar.add(mnHelp);
    setMenuBar(mbMainMenuBar);
  }
  public void paint(Graphics g)
  {
    g.setFont(new Font("Helvetica",
      Font.PLAIN, 12));
    g.drawString("Frame window", 10, 70);
    super.paint(g);
  }
  public boolean handleEvent(Event evt)
  {
    if(evt.id == Event.WINDOW_DESTROY)
    {
      setVisible(false);
      System.exit(0);
      return true;
    }
    else
      return super.handleEvent(evt);
  }
  public boolean action(Event evt,
   Object obj)
  {
    MenuItem mnItem;
    if(evt.target instanceof MenuItem)
    {
      mnItem = (MenuItem)evt.target;
      if(obj.equals("Exit"))
      {
        System.exit(0);
      }
      else if(obj.equals("New..."))
      {
    if(fDBEmpty)
     {
      SimpleDBMS db = new SimpleDBMS(
        "dbtest.idx", "dbtest.dat");
          db.AddRecord("Ivanov",   1000);
          db.AddRecord("Petrov",   2000);
      db.AddRecord("Sidoroff", 3000);
          db.close();
      fDBEmpty = false;
          MessageBox mbox;
          mbox = new MessageBox(
            "Database created",
            this, "Information", true);
      mbox.show();
    }
      }
      else if(obj.equals("View records..."))
      {
        SimpleDBMS db = new SimpleDBMS(
      "dbtest.idx", "dbtest.dat");
        String szRecords;

    szRecords =
          db.GetRecordByNumber(0) +
      db.GetRecordByNumber(1) +
      db.GetRecordByNumber(2);

        db.close();

        MessageBox mbox;
        mbox = new MessageBox(szRecords,
          this, "Database records", true);
    mbox.show();
      }

      else if(obj.equals("Content"))
      {
        MessageBox mbox;
        mbox = new MessageBox(
          "Item Content selected",
          this, "Dialog from Frame", true);
        mbox.show();
      }

      else if(obj.equals("About"))
      {
        MessageBox mbox;
        mbox = new MessageBox(
          "Item About selected",
          this, "Dialog from Frame", true);
        mbox.show();
      }
      else
        return false;
      return true;
    }
    return false;
  }
}
class MessageBox extends Dialog
{
  Label lbMsg;
  Button btnOK;
  public MessageBox(String sMsg,
    Frame  parent, String  sTitle,
    boolean  modal)
  {
    super(parent, sTitle, modal);
    resize(300, 100);
    setLayout(new GridLayout(2, 1));
    lbMsg = new Label(sMsg, Label.CENTER);
    add(lbMsg);
    btnOK = new Button("OK");
    add(btnOK);
  }
  public boolean handleEvent(Event evt)
  {
    if(evt.id == Event.WINDOW_DESTROY)
    {
      dispose();
      return true;
    }
    else
      return super.handleEvent(evt);
  }
  public boolean action(Event evt,
   Object obj)
  {
    Button btn;
    if(evt.target instanceof Button)
    {
      btn = (Button)evt.target;
      if(evt.target.equals(btnOK))
      {
        dispose();
      }
      else
        return false;
      return true;
    }
    return false;
  }
}
class SimpleDBMS
{
  RandomAccessFile idx;
  RandomAccessFile dat;
  long idxFilePointer = 0;
  public SimpleDBMS(String IndexFile,
   String DataFile)
  {
    try
    {
      idx = new RandomAccessFile(
        IndexFile, "rw");
      dat = new RandomAccessFile(
        DataFile, "rw");
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
  public void close()
  {
    try
    {
      idx.close();
      dat.close();
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
  public void AddRecord(String name,
     int account)
  {
    try
    {
      idx.seek(idx.length());
      dat.seek(dat.length());
      idxFilePointer = dat.getFilePointer();
      idx.writeLong(idxFilePointer);
      dat.writeBytes(name+ "\r\n");
      dat.writeInt(account);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
  public String GetRecordByNumber(long nRec)
  {
    String sRecord = "<empty>";

    try
    {
      Integer account;
      String str = null;
      idx.seek(nRec * 8);
      idxFilePointer = idx.readLong();
      dat.seek(idxFilePointer);
      str = dat.readLine();
      account = new Integer(dat.readInt());
      sRecord = new String("> " +
       account + ", " + str);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    return sRecord;
  }
}

Описание исходного текста приложения DirectFile

Для работы с базой данных мы создали класс SimpleDBMS, определив в нем конструктор, методы для добавления записей, извлечения записей по их порядковому номеру, а также метод для закрытия базы данных.

Создание базы данных

Когда пользователь выбирает из меню File строку New, соответствующий обработчик события создает базу данных, передавая конструктору имена файла индекса dbtest.idx и файла данных dbtest.dat:

SimpleDBMS db = new SimpleDBMS(
  "dbtest.idx", "dbtest.dat");

После этого с помощью метода AddRecord, определенного в классе SimpleDBMS, в базу добавляются три записи, состоящие из текстового и числового полей:

db.AddRecord("Ivanov",   1000);
db.AddRecord("Petrov",   2000);
db.AddRecord("Sidoroff", 3000);

После завершения работы с базой данных она закрывается методом close из класса SimpleDBMS:

db.close();

Просмотр записей базы данных

При выборе строки View records из меню File приложение открывает файл базы данных:

SimpleDBMS db = new SimpleDBMS(
  "dbtest.idx", "dbtest.dat");

Затем оно извлекает три записи с номерами 0, 1 и 2, вызывая для этого метод GetRecordByNumber, также определенный в классе SimpleDBMS:

String szRecords;

szRecords = db.GetRecordByNumber(0) +
  db.GetRecordByNumber(1) +
  db.GetRecordByNumber(2);

Записи объединяются и сохраняются в переменной szRecords типа String. После этого база данных закрывается:

db.close();

Для отображения содержимого записей мы создаем диалоговую панель на базе определенного нами класса MessageBox:

MessageBox mbox;
mbox = new MessageBox(szRecords,
  this, "Database records", true);
mbox.show();

Класс SimpleDBMS

Рассмотрим теперь класс SimpleDBMS.

В этом классе определено три поля с именами idx, dat и idxFilePointer, а также три метода.

Поля класса SimpleDBMS

Поля idx и dat являются объектами класса RandomAccessFile и представляют собой, соответственно, ссылки на файл индекса и файл данных. Поле idxFilePointer типа long используется как рабочее и хранит текущее смещение в файле.

Конструктор класса SimpleDBMS

Конструктор класса SimpleDBMS выглядит достаточно просто. Все, что он делает, - это создает два объекта класса RandomAccessFile, соответственно, для индекса и данных:

idx = new RandomAccessFile(IndexFile, "rw");
dat = new RandomAccessFile(DataFile, "rw");

Так как в качестве второго параметра конструктору класа RandomAccessFile передается строка "rw", файлы открываются и для чтения, и для записи.

Метод close

Метод close закрывает файлы индекса и данных, вызывая метод close из класса RandomAccessFile:

idx.close();
dat.close();

Метод AddRecord

Метод AddRecord добавляет новую запись в конец файла данных, а смещение этой записи - в конец файла индекса. Поэтому перед началом своей работы текущая позиция обоих указанных файлов устанавливается на конец файла.

Для установки мы применили метод seek из класса RandomAccessFile, передав ему в качестве параметра значение длины файла в байтах, определенное при помощи метода length из того же класса:

idx.seek(idx.length());
dat.seek(dat.length());

Перед тем как добавлять новую запись в файл данных, метод AddRecord определяет текущую позицию в файле данных (в данном случае это позиция конца файла) и записывает эту позицию в файл индекса:

idxFilePointer = dat.getFilePointer();
idx.writeLong(idxFilePointer);

Далее метод AddRecord выполняет сохранение полей записи в файле данных. Для записи строки вызывается метод writeBytes, а для записи численного значения типа int - метод writeInt:

dat.writeBytes(name + "\r\n");
dat.writeInt(account);

Обратите внимение, что к строке мы добавляем символы возврата каретки и перевода строки. Это сделано исключительно для того чтобы обозначить конец строки текстового поля.

Метод GetRecordByNumber

Метод GetRecordByNumber позволяет извлечь произвольную запись из файла данных по ее порядковому номеру.

Напомним, что смещения всех записей хранятся в файле индексов и имеют одинаковую длину 8 байт. Пользуясь этим, метод GetRecordByNumber вычисляет смещение в файле индекса простым умножением порядкового номера записи на длину переменной типа long, то есть на 8 байт, а затем выполняет позиционирование:

idx.seek(nRec * 8);

После этого метод GetRecordByNumber извлекает из файла индексов смещение нужной записи в файле данных, вызывая для этого метод readLong, а затем выполняет позиционирование в файле данных:

idxFilePointer = idx.readLong();
dat.seek(idxFilePointer);

Поля записи читаются из файла данных в два приема. Вначале читается строка текстового поля, а затем - численное значение, для чего вызываются, соответственно, методы readLine и readInt:

str = dat.readLine();
account = new Integer(dat.readInt());

Полученные значения полей объединяются в текстовой строке и записываются в переменную sRecord:

sRecord = new String("> " +
  account + ", " + str);

Содержимое этой переменной метод GetRecordByNumber возвращает в качестве извлеченной строки записи базы данных.


Создание сетевых приложений

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Когда мы начинали разговор про язык программирования Java, то отмечали, что он специально ориентирован на глобальные сети, такие как Internet. В этой главе мы начнем знакомство с конкретными классами Java, разработанными для сетевого программирования. На примере наших приложений вы сможете убедиться, что классы Java действительно очень удобны для создания сетевых приложений.

В этой главе мы рассмотрим два аспекта сетевого программирования. Первый из них касается доступа из приложений Java к файлам, расположенным на сервере Web, второй - создания серверных и клиентских приложений с использованием сокетов.

Напомним, что из соображений безопасности алпетам полностью запрещен доступ к локальным файлам рабочей станции, подключенной к сети. Тем не менее, аплет может работать с файлами, расположенными на серверах Web. При этом можно использовать входные и выходные потоки, описанные нами в предыдущей главе.

Для чего аплетам обращаться к файлам сервера Web?

Таким аплетам можно найти множество применений.

Представьте себе, например, что вам нужно отображать у пользователя диаграмму, исходные данные для построения которой находятся на сервере Web. Эту задачу можно решить, грубо говоря, двумя способами.

Первый заключается в том, что вы создаете расширение сервера Web в виде приложения CGI или ISAPI, которое на основании исходных данных динамически формирует графическое изображение диаграммы в виде файла GIF и посылает его пользователю.

Однако на пути решения задачи с помощью расширения сервера Web вас поджидают две неприятности. Во-первых, создать из программы красивый цветной графический файл в стандарте GIF не так-то просто - вы должны разобраться с форматом этого файла и создать все необходимые заголовки. Во-вторых, графический файл занимает много места и передается по каналам Internet достаточно медленно - средняя скорость передачи данных в Internet составляет 1 Кбайт в секунду.

В то же время файл с исходными данными может быть очень компактным. Возникает вопрос - нельзя ли передавать через Internet только исходные данные, а построение графической диаграммы выполнять на рабочей станции пользователя?

В этом заключается второй способ, который предполагает применение аплетов. Ваше приложение может, например, получать через сеть файл исходных данных, а затем на основании содержимого этого файла рисовать в своем окне цветную круговую диаграмму. Объем передаваемых данных при этом по сравнению с использованием расширения сервера Web сокращается в десятки и сотни раз.

Помимо работы с файлами, расположенными на сервере Web, мы расскажем о создании каналов между приложениями Java, работающими на различных компьютерах в сети, с использованием сокетов.

Сокеты позволяют организовать тесное взаимодействие аплетов и полноценных приложений Java, при котором аплеты могут предавать друг другу данные через сеть Internet. Это открывает широкие возможности для обработки информации по схеме клиент-сервер, причем в роли серверов здесь может выступать любой компьютер, подключенный к сети, а не только сервер Web. Каждая рабочая станция может выступать одновременно и в роли сервера, и в роли клиента.


Адрес IP и класс InetAddress

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Прежде чем начинать создание сетевых приложений для Internet, вы должны разобраться с адресацией компьютеров в сети с протоколом TCP/IP, на базе которого построена сеть Internet. Здесь мы приведем самые необходимые для этого сведения.

Все компьютеры, подключенные к сети TCP/IP, называются узлами (в оригинальной терминологии узел - это host). Каждый узел имеет в сети свой адрес IP, состоящий из четырех десятичных цифр в диапазоне от 0 до 255, разделенных символом "точка ", например:

193.120.54.200

Фактически адрес IP является 32-разрядным двоичным числом. Упомянутые числа представляют собой отдельные байты адеса IP.

Так как работать с цифрами удобно лишь компьютеру, была придумана система доменных имен. При использовании этой системы адресам IP ставится в соответсвие так называемый доменный адрес, такой как, например, www.sun.com.

В сети Internet имеется распределенная по всему миру база доменных имен, в которой установлено соответствие между доменными именами и адресами IP в виде четырех чисел.

Для работы с адресами IP в библиотеке классов Java имеется класс InetAddress, определение наиболее интересных методов которого приведено ниже:

public static  InetAddress getLocalHost();
public static  InetAddress
  getByName(String host);
public static  InetAddress[]
  getAllByName(String host);
public byte[]  getAddress();
public String  toString();
public String  getHostName();
public boolean equals(Object obj);

Рассмотрим применение этих методов.

Чтобы работать с адресами IP, прежде всего вы должны создать объект класса InetAddress. Эта процедура выполняется не с помощью оператора new, а с применением статических методов getLocalHost, getByName и getAllByName.

Создание объекта класса InetAddress для локального узла

Метод getLocalHost создает объект класса InetAddress для локального узла, то есть для той рабочей станции, на которой выполняется приложение Java. Так как этот метод статический, вы можете вызывать его, ссылаясь на имя класса InetAddress:

InetAddress iaLocal;
iaLocal = InetAddress.getLocalHost();

Создание объекта класса InetAddress для удаленного узла

В том случае, если вас интересует удаленный узел сети Internet или корпоративной сети Intranet, вы можете создать для него объект класса InetAddress с помощью методов getByName или getAllByName. Первый из них возвращает адрес узла, а второй - массив всех адресов IP, связанных с данным узлом. Если узел с указанным именем не существует, при выполнении методов getByName и getAllByName возникает исключение UnknownHostException.

Заметим, что методам getByName и getAllByName можно передавать не только имя узла, такое как, например, "sun.com", но и строку адреса IP в виде четырех десятичных чисел, разделенных точками.

После создания объекта класса InetAddress для локального или удаленного узла вы можете использовать другие методы этого класса.

Определение адреса IP

Метод getAddress возвращает массив из чеырех байт адреса IP объекта. Байт с нулевым индексом этого массива содержит старший байт адреса IP.

Метод toString возвращает текстовую строку, которая содержит имя узла, разделитель '/' и адрес IP в виде четырех десятичных чисел, разделенных точками.

Определение имени узла

С помощью метода getHostName вы можете определить имя узла, для которого был создан объект класса InetAddress.

Сравнение адресов IP

И, наконец, метод equals предназначен для сравнения адресов IP как объектов класса InetAddress.


Универсальный адрес ресурсов URL

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Адрес IP позволяет идентифицировать узел, однако его недостаточно для идентификации ресурсов, имеющихся на этом узле, таких как работающие приложения или файлы. Причина очевидна - на узле, имеющем один адрес IP, может существовать много различных ресурсов.

Для ссылки на ресурсы сети Internet применяется так называемый универсальный адрес ресуросв URL (Universal Resource Locator). В общем виде этот адрес выглядит следующим образом:

[protocol]://host[:port][path]

Строка адреса начинаетс с протокола protocol, который должен быть использован для доступа к ресурсу. Документы HTML, например, передаются из сервера Web удаленным пользователям с помощью протокола HTTP. Файловые серверы в сети Internet работают с протоколом FTP.

Для ссылки на сетевые ресурсы через протокол HTTP используется следующая форма универсального адреса ресурсов URL:

http://host[:port][path]

Параметр host обязательный. Он должен быть указан как доменный адрес или как адрес IP (в виде четырех десятичных чисел). Например:

http://www.sun.com
http://157.23.12.101

Необязательный параметр port задает номер порта для работы с сервером. По умолчанию для протокола HTTP используется порт с номером 80, однако для специализированных серверов Web это может быть и не так.

Номер порта идентифицирует программу, работающую в узле сети TCP/IP и взаимодействующую с другими программами, расположенными на том же или на другом узле сети. Если вы разрабатываете программу, передающую данные через сеть TCP/IP с использованием, например, интерфейса сокетов Windows Sockets, то при создании канала связи с уделенным компьютером вы должны указать не только адрес IP, но и номер порта, который будет использован для передачи данных.

Ниже мы показали, как нужно указывать в адресе URL номер порта:

http://www.myspecial.srv/:82

Теперь займемся параметром path, определяющем путь к объекту.

Обычно любой сервер Web или FTP имеет корневой каталог, в котором расположены подкаталоги. Как в корневом каталоге, так и в подкаталогах сервера Web могут находиться документы HTML, двоичные файлы, файлы с графическими изображениями, звуковые и видео-файлы, расширения сервера в виде программ CGI или библиотек динамической компоновки, дополняющих возможности сервера.

Если в качестве адреса URL указать навигатору только доменное имя сервера, сервер перешлет навигатору свою главную страницу. Имя файла этой страницы зависит от сервера. Большинство серверов на базе операционной системы UNIX посылают по умолчанию файл документа с именем index.html. Другие серверы Web могут использовать для этой цели имя default.htm или какое-нибудь еще, определенное при установке сервера, например, home.html или home.htm.

Для ссылки на конкретный документ HTML или на файл любого другого объекта необходимо указать в адресе URL его путь, включающий имя файла, например:

http://www.glasnet.ru/~frolov/index.html
http://www.dials.ccas.ru/frolov/home.htm

Корневой каталог сервера Web обозначается символом /. В спецификации протокола HTTP сказано, что если путь не задан, то используется корневой каталог.


Класс URL в библиотеке классов Java

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Для работы с ресурсами, заданными своими адресами URL, в библиотеке классов Java имеется очень удобный и мощный класс с названием URL. Простота создания сетевых приложений с использованием этого класса в значительной степени опровергает общераспространенное убеждение в сложности сетевого программирования. Инкапсулируя в себе достаточно сложные процедуры, класс URL предоставляет в распоряжение программиста небольшой набор простых в использовании конструкторов и методов.

Конструкторы класса URL

Сначала о конструкторах. Их в классе URL имеется четыре штуки.

public URL(String spec);

Первый из них создает объект URL для сетевого ресурса, адрес URL которого передается конструктору в виде текстовой строки через единственный параметр spec:

public URL(String spec);

В процессе создания объекта проверяется заданный адрес URL, а также наличие указанного в нем ресурса. Если адрес указан неверно или заданный в нем ресурс отсутствует, возникает исключение MalformedURLException. Это же исключение возникает при попытке использовать протокол, с которым данная система не может работать.

Второй вариант конструктора класса URL допускает раздельное указание протокола, адреса узла, номера порта, а также имя файла:

public URL(String protocol,
  String host, int port, String file);

Третий вариант предполагает использование номера порта, принятого по умолчанию:

public URL(String protocol,
  String host, String file);

Для протокола HTTP это порт с номером 80.

И, наконец, четвертый вариант конструктора допускает указание контекста адреса URL и строки адреса URL:

public URL(URL context, String spec);

Строка контекста позволяет указывать компоненты адреса URL, отсустсвующие в строке spec, такие как протокол, имя узла, файла или номер порта.

Методы класса URL

Рассмотрим самые интересные методы, определенные в классе URL.

Метод openStream

Метод openStream позволяет создать входной поток для чтения файла ресурса, связанного с созданным объектом класса URL:

public final InputStream openStream();

Для выполнения операции чтения из созданного таким образом потока вы можете использовать метод read, определенный в классе InputStream (любую из его разновидностей).

Данную пару методов (openStream из класса URL и read из класса InputStream) можно применить для решения задачи получения содержимого двоичного или текстового файла, хранящегося в одном из каталогов сервера Web. Сделав это, обычное приложение Java или аплет может выполнить локальную обработку полученного файла на компьютере удаленного пользователя.

Метод getContent

Очень интересен метод getConten. Этот метод определяет и получает содержимое сетевого ресурса, для которого создан объект URL:

public final Object getContent();

Практически вы можете использовать метод getContent для получения текстовых файлов, расположенных в сетевых каталогах.

К сожалению, данный метод непригоден для получения документов HTML, так как для данного ресурса не определен обработчик соедржимого, предназначенный для создания объекта. Метод getContent не способен создать объект ни из чего другого, кроме текстового файла.

Данная проблема, тем не менее, решается очень просто - достаточно вместо метода getContent использовать описанную выше комбинацию методов openStream из класса URL и read из класса InputStream.

Метод getHost

С помощью метода getHost вы можете определить имя узла, соответствующего данному объекту URL:

public String getHost();

Метод getFile

Метод getFile позволяет получить информацию о файле, связанном с данным объектом URL:

public String getFile();

Метод getPort

Метод getPortt предназначен для определения номера порта, на котором выполняется связь для объекта URL:

public int getPort();

Метод getProtocol

С помощью метода getProtocol вы можете определить протокол, с использованием которого установлено соединение с ресурсом, заданным объектом URL:

public String getProtocol();

Метод getRef

Метод getRef возвращает текстовую строку ссылки на ресурс, соответствующий данному объекту URL:

public String getRef();

Метод hashCode

Метод hashCode возвращает хэш-код объекта URL:

public int hashCode();

Метод sameFile

С помощью метода sameFile вы можете определить, ссылаются ли два объекта класса URL на один и тот же ресурс, или нет:

public boolean sameFile(URL other);

Если объекты ссылаются на один и тот же ресурс, метод sameFile возвращает значение true, если нет - false.

Метод equals

Вы можете использовать метод equals для определения идентичности адресов URL, заданных двумя объектами класса URL:

public boolean equals(Object obj);

Если адреса URL идентичны, метод equals возвращает значение true, если нет - значение false.

Метод toExternalForm

Метод toExternalForm возвращает текстовую строку внешнего представления адреса URL, определенного данным объектом класса URL:

public String toExternalForm();

Метод toString

Метод toString возвращает текстовую строку, представляющую данный объект класса URL:

public String toString();

Метод openConnection

Метод openConnection предназначен для создания канала между приложением и сетевым ресурсом, представленным объектом класса URL:

public URLConnection openConnection();

Если вы создаете приложение, которое позволяет читать из каталогов сервера Web текстовые или двоичные файлы, можно создать поток методом openStream или получить содержимое текстового ресурса методом getContent.

Однако есть и другая возможность. Вначале вы можете создать канал, как объект класса URLConnection, вызвав метод openConnection, а затем создать для этого канала входной поток, воспользовавшись методом getInputStream, определенным в классе URLConnection. Такая методика позволяет определить или установить перед созданием потока некоторые характеристики канала, например, задать кэширование.

Однако самая интересная возможность, которую предоставляет этот метод, заключается в организации взаимодействия приложения Java и сервера Web.


Передача данных с использованием сокетов

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

В библиотеке классов Java есть очень удобное средство, с помощью которых можно организовать взаимодействие между приложениями Java и аплетами, работающими как на одном и том же, так и на разных узлах сети TCP/IP. Это средство, родившееся в мире операционной системы UNIX, - так называемые сокеты (sockets).

Что такое сокеты?

Вы можете представить себе сокеты в виде двух розеток, в которые включен кабель, предназначенный для передачи данных через сеть. Переходя к компьютерной терминологии, скажем, что сокеты - это программный интерфейс, предназначенный для передачи данных между приложениями.

Прежде чем приложение сможет выполнять передачу аили прием данных, оно должно создать сокет, указав при этом адрес узла IP, номер порта, через который будут передаваться данные, и тип сокета.

С адресом узла IP вы уже сталкивались. Номер порта служит для идентификации приложения. Заметим, что существуют так называемые "хорошо известные" (well known) номера портов, зарезервированные для различных приложений. Например, порт с номером 80 зарезервирован для использования серверами Web при обмене данными через протокол HTTP.

Что же касается типов сокетов, то их два - потоковые и датаграммные.

С помощью потоковых сокетов вы можете создавать каналы передачи данных между двумя приложениями Java в виде потоков, которые мы уже рассматривали во второй главе. Потоки могут быть входными или выходными, обычными или форматированными, с использованием или без использования буферизации. Скоро вы убедитесь, что организовать обмен данными между приложениями Java с использованием потоковых сокетов не труднее, чем работать через потоки с обычными файлами.

Заметим, что потоковые сокеты позволяют передавать данные только между двумя приложениями, так как они предполагают создание канала между этими приложениями. Однако иногда нужно обеспечить взаимодействие нескольких клиентских приложений с одним серверным или нескольких клиентских приложений с несколькими серверными приложениями. В этом случае вы можете либо создавать в серверном приложении отдельные задачи и отдельные каналы для каждого клиентского приложения, либо воспользоваться датаграммными сокетами. Последние позволяют передавать данные сразу всем узлам сети, хотя такая возможность редко используется и часто блокируется администраторами сети.

Для передачи данных через датаграммные сокеты вам не нужно создавать канал - данные посылаются непосредственно тому приложению, для которого они предназначены с использованием адреса этого приложения в виде сокета и номера порта. При этом одно клиентское приложение может обмениваться данными с несколькими серверными приложениями или наоборот, одно серверное приложение - с несколькими клиентскими.

К сожалению, датаграммные сокеты не гарантируют доставку передаваемых пакетов данных. Даже если пакеты данных, передаваемые через такие сокеты, дошли до адресата, не гарантируется, что они будут получены в той же самой последовательности, в которой были переданы. Потоковые сокеты, напротив, гарантируют доставку пакетов данных, причем в правильной последовательности.

Причина отстутствия гарантии доставки данных при использовании датаграммных сокетов заключается в использовании такими сокетами протокола UDP, который, в свою очередь, основан на протоколе с негарантированной доставкой IP. Потоковые сокеты работают через протокол гарантированной доставки TCP.


Работа с потоковыми сокетами

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Как мы уже говорили, интерфейс сокетов позволяет передавать данные между двумя приложениями, работающими на одном или разных узлах сети. В процессе создания канала передачи данных одно из этих приложений выполняет роль сервера, а другое - роль клиента. После того как канал будет создан, приложения становятся равноправными - они могут передавать друг другу данные симметричным образом.

Рассмотрим этот процесс в деталях.

Инициализация сервера

Вначале мы рассмотрим действия приложения, которое на момент инициализации является сервером.

Первое, что должно сделать серверное приложение, это создать объект класса ServerSocket, указав конструктору этого класса номер используемого порта:

ServerSocket ss;
ss = new ServerSocket(9999);

Заметим, что объект класса ServerSocket вовсе не является сокетом. Он предназначен всего лишь для установки канала связи с клиентским приложением, после чего создается сокет класса Socket, пригодный для передачи данных.

Установка канала связи с клиентским приложением выполняется при помощи метода accept, определенного в классе ServerSocket:

Socket s;
s = ss.accept();

Метод accept приостанавливает работу вызвавшего потока до тех пор, пока клиентское приложение не установит канал связи с сервером. Если ваше приложение однопоточное, его работа будет блокирована до момента установки канала связи. Избежать полной блокировки приложения можно, если выполнять создание канала передачи данных в отдельном потоке.

Как только канал будет создан, вы можете использовать сокет сервера для образования входного и выходного потока класса InputStream и OutputStream, соответственно:

InputStream is;
OutputStream os;
is = s.getInputStream();
os = s.getOutputStream();

Эти потоки можно использовать таким же образом, что и потоки, связанные с файлами.

Обратите также внимание на то, что при создании серверного сокета мы не указали адрес IP и тип сокета, ограничившись только номером порта.

Что касается адреса IP, то он, очевидно, равен адресу IP узла, на котором запущено приложение сервера. В классе ServerSocket определен метод getInetAddress, позволяющий определить этот адрес:

public InetAddress getInetAddress();

Тип сокета указывать не нужно, так как для работы с датаграммными сокетами предназначен класс DatagramSocket, который мы рассмотрим позже.

Инициализация клиента

Процесс инициализации клиентского приложения выглядит весьма просто. Клиент должен просто создать сокет как объект класса Socket, указав адрес IP серверного приложения и номер порта, используемого сервером:

Socket s;
s = new Socket("localhost",9999);

Здесь в качестве адреса IP мы указали специальный адрес localhost, предназначенный для тестирования сетевых приложений, а в качестве номера порта - ззначение 9999, использованное сервером.

Теперь можно создавать входной и выходной потоки. На стороне клиента эта операция выполняется точно также, как и на стороне сервера:

InputStream is;
OutputStream os;
is = s.getInputStream();
os = s.getOutputStream();

Передача данных между клиентом и сервером

После того как серверное и клиентское приложения создали потоки для приема и передачи данных, оба этих приложения могут читать и писать в канал данных, вызывая методы read и write, определенные в классах InputStream и OutputStream.

Ниже мы представили фрагмент кода, в котором приложение вначале читает данные из входного потока в буфер buf, а затем записывает прочитанные данные в выходной поток:

byte buf[] = new byte[512];
int lenght;
lenght = is.read(buf);
os.write(buf, 0, lenght);
os.flush();

На базе потоков класса InputStream и OutputStream вы можете создать буферизованные потоки и потоки для передачи форматированных данных, о которых мы рассказывали раньше.

Завершение работы сервера и клиента

После завершения передачи данных вы должны закрыть потоки, вызвав метод close:

is.close();
os.close();

Когда канал передачи данных больше не нужен, сервер и клиент должны закрыть сокет, вызвав метод close, определенный в классе Socket:

s.close();

Серверное приложение, кроме того, должно закрыть соединение, вызвав метод close для объекта класса ServerSocket:

ss.close();


Класс Socket

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

После краткого введения в сокеты приведем описание наиболее интересных конструкторов и методов класса Socket.

Конструкторы класса Socket

Чаще всего для создания сокетов в клиентских приложениях вы будете использовать один из двух конструкторов, прототипы которых приведены ниже:

public Socket(String host,int port);
public Socket(InetAddress address,int port);

Первый из этих конструкторов позволяет указывать адрес серверного узла в виде текстовой строки, второй - в виде ссылки на объект класса InetAddress. Вторым параметром задается номер порта, с использованием которого будут передаваться данные.

В классе Socket определена еще одна пара конструкторов, которая, однако не рекомендуется для использования:

public Socket(String host,
   int port, boolean stream);
public Socket(InetAddress address,
   int port, boolean stream);

В этих конструкторах последний параметр определяет тип сокета. Если этот параметр равен true, создается потоковый сокет, а если false - датаграммный. Заметим, что для работы с датаграммными сокетами следует использовать класс DatagramSocket.

Методы класса Socket

Перечислим наиболее интересные, на наш взгляд, методы класса Socket.

Прежде всего, это методы getInputStream и getOutputStream, предназначенные для создания входного и выходного потока, соответственно:

public InputStream getInputStream();
public OutputStream getOutputStream();

Эти потоки связаны с сокетом и должны быть использованы для передачи данных по каналу связи.

Методы getInetAddress и getPort позволяют определить адрес IP и номер порта, связанные с данным сокетом (для удаленного узла):

public InetAddress getInetAddress();
public int getPort();

Метод getLocalPort возвращает для данного сокета номер локального порта:

public int getLocalPort();

После того как работа с сокетом завершена, его необходимо закрыть методом close:

public void close();

И, наконец, метод toString возвращает текстовую строку, представляющую сокет:

public String toString();


Использование датаграммных сокетов

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Как мы уже говорили, датаграммные сокеты не гарантируют доставку пакетов данных. Тем не менее, они работают быстрее потоковых и обеспечивают возможность широковещательной расслыки пакетов данных одновременно всем узлам сети. Последняя возможность используется не очень широко в сети Internet, однако в корпоративной сети Intranet вы вполне можете ей воспользоваться.

Для работы с датаграммными сокетами приложение должно создать сокет на базе класса DatagramSocket, а также подготовить объект класса DatagramPacket, в который будет записан принятый от партнера по сети блок данных.

Канал, а также входные и выходные потоки создавать не нужно. Данные передаются и принимаются методами send и receive, определенными в классе DatagramSocket.

Класс DatagramSocket

Рассмотрим конструкторы и методы класса DatagramSocket, предназначенного для создания и использования датаграммных сокетов.

В классе DatagramSocket определены два конструктора, прототипы которых представлены ниже:

public DatagramSocket(int port);
public DatagramSocket();

Первый из этих конструкторов позволяет определить порт для сокета, второй предполагает использование любого свободного порта.

Обычно серверные приложения работают с использованием какого-то заранее определенного порта, номер которого известен клиентским приложениям. Поэтому для серверных приложений больше подходит первый из приведенных выше конструкторов.

Клиентские приложения, напротив, часто применяют любые свободные на локальном узле порты, поэтому для них годится конструктор без параметров.

Кстати, с помощью метода getLocalPort приложение всегда может узнать номер порта, закрепленного за данным сокетом:

public int getLocalPort();

Прием и передача данных на датаграммном сокете выполняется с помощью методов receive и send, соответственно:

public void receive(DatagramPacket p);
public void send(DatagramPacket p);

В качестве параметра этим методам передается ссылка на пакет данных (соответственно, принимаемый и передаваемый), определенный как объект класса DatagramPacket. Этот класс будет рассмотрен позже.

Еще один метод в классе DatagramSocket, которым вы будете пользоваться, это метод close, предназначенный для закрытия сокета:

public void close();

Напомним, что сборка мусора в Java выполняется только для объектов, находящихся в оперативной памяти. Такие объекты, как потоки и сокеты, вы должны закрывать после использования самостоятельно.

Класс DatagramPacket

Перед тем как принимать или передавать данные с использованием методов receive и send вы должны подготовить объекты класса DatagramPacket. Метод receive запишет в такой объект принятые данные, а метод send - перешлет данные из объекта класса DatagramPacket узлу, адрес которого указан в пакете.

Подготовка объекта класса DatagramPacket для приема пакетов выполняется с помощью следующего конструктора:

public DatagramPacket(byte ibuf[],
 int ilength);

Этому конструктору передается ссылка на массив ibuf, в который нужно будет записать данные, и размер этого массива ilength.

Если вам нужно подготовить пакет для передачи, воспользуйтесь конструктором, который дополнительно позволяет задать адрес IP iaddr и номер порта iport узла назначения:

public DatagramPacket(byte ibuf[],
  int ilength,
  InetAddress iaddr, int iport);

Таким образом, информация о том, в какой узел и на какой порт необходимо доставить пакет данных, хранится не в сокете, а в пакете, то есть в объекте класса DatagramPacket.

Помимо только что описанных конструкторов, в классе DatagramPacket определены четыре метода, позволяющие получить данные и информацию об адресе узла, из которого пришел пакет, или для которого предназначен пакет.

Метод getData возвращает ссылку на массив данных пакета:

public byte[] getData();

Размер пакета, данные из которого хранятся в этом массиве, легко определить с помощью метода getLength:

public int getLength();

Методы getAddress и getPort позволяют определить адрес и номер порта узла, откуда пришел пакет, или узла, для которого предназначен пакет:

public InetAddress getAddress();
public int getPort();

Если вы создаете клиент-серверную систему, в которой сервер имеет заранее известный адрес и номер порта, а клиенты - произвольные адреса и различные номера портов, то после получения пакета от клиента сервер может определить с помощью методов getAddress и getPort адрес клиента для установления с ним связи.

Если же адрес сервера неизвестен, клиент может посылать широковещательные пакеты, указав в объекте класса DatagramPacket адрес сети. Такая методика обычно используется в локальных сетях.

Как указать адрес сети?

Напомним, что адрес IP состоит из двух частей - адреса сети и адреса узла. Для разделения компонент 32-разрядного адреса IP используется 32-разрядная маска, в которой битам адреса сети соответствуют единицы, а битам адреса узла - нули.

Например, адрес узла может быть указан как 193.24.111.2. Исходя из значения старшего байта адреса, это сеть класса С, для которой по умолчанию используется маска 255.255.255.0. Следовательно, адрес сети будет такой: 193.24.111.0.


Связь приложений Java с расширениями сервера Web

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Итак, мы расказали вам, как приложения Java могут получать с сервера Web для обработки произвольные файлы, а также как они могут передавать данные друг другу с применением потоковых или датаграммных сокетов.

Однако наиболее впечатляющие возможности открываются, если организовать взаимодействие между приложением Java и расширением сервера Web, таким как CGI или ISAPI. В этом случае приложения или аплеты Java могли бы посылать произвольные данные расширению сервера Web для обработки, а затем получать результат этой обработки в виде файла.

Взаимодействие приложения Java и расширения сервера Web

Методика организации взаимодействия приложений Java и расширений сервера Web основана на применении классов URL и URLConnection.

Приложение Java, желающее работать с расширением сервера Web, создает объект класса URL для программы расширения (то есть для исполняемого модуля расширения CGI или библиотеки динамической компоновки DLL расширения ISAPI).

Далее приложение получает ссылку на канал передачи данных с этим расширением как объекта класса URLConnection. Затем, пользуясь методами getOutputStream и getInputStream из класса URLConnection, приложение создает с расширением сервера Web выходной и входной канал передачи данных.

Когда данные передаются приложением в выходной канал, созданный подобным образом, он попадает в стандартный поток ввода приложения CGI, как будто бы данные пришли методом POST из формы, определенной в документе HTML.

Обработав полученные данные, расширение CGI записывает их в свой стандартный выходной поток, после чего эти данные становятся доступны приложению Java через входной поток, открытый методом getInputStream класса URLConnection.

На рис. 1 показаны потоки данных для описанной выше схемы взаимодействия приложения Java и расширения сервреа Web с интерфейсом CGI.

pic01.gif (4413 bytes)

Рис. 1. Взаимодействие приложения Java с расширением сервера Web на базе интерфейса CGI

Расширения ISAPI работают аналогично, однако они получают данные не из стандратного входного потока, а с помощью вызова специально предназначенной для этого функции интерфейса ISAPI. Вместо стандартного потока вывода также применяется специальная функция.

Класс URLConnection

Напомним, что в классе URL, рассмотренном нами в начале этой главы, мы привели прототип метода openConnection, возвращающий для заданного объекта класса URL ссылку на объект URLConnection:

public URLConnection openConnection();

Что мы можем получить, имея ссылку на этот объект?

Прежде всего, пользуясь этой ссылкой, мы можем получить содержимое объекта, адресуемое соответствующим объектом URL, методом getContent:

public Object getContent();

Заметим, что метод с таким же названием есть и в классе URL. Поэтому если все, что вы хотите сделать, это получение содержимое файла, адресуемое объектом класса URL, то нет никакой необходимости обращаться к классу URLConnection.

Метод getInputStream позволяет открыть входной поток данных, с помощью которого можно считать файл или получить данные от расширения сервера Web:

public InputStream getInputStream();

В классе URLConnection определен также метод getOutputStream, позволяющий открыть выходной поток данных:

public OutputStream getOutputStream();

Не следует думать, что этот поток можно использовать для записи файлов в каталоги сервера Web. Однако для этого потока есть лучшее применение - с его помощью можно передать данные расширению сервера Web.

Рассмотрим еще несколько полезных методов, определенных в классе URLConnection.

Метод connect предназначен для установки соединения с объектом, на который ссылается объект класса URL:

public abstract void connect();

Перед установкой соединения приложение может установить различные параметры соединения. Некоторые из методов, предназначенных для этого, приведены ниже:

  • setDefaultUseCaches

Включение или отключение кэширования по умолчанию

public void setDefaultUseCaches(
  boolean defaultusecaches);
  • setUseCaches

Включение или отключение кэширования

public void setUseCaches(boolean usecaches);
  • setDoInput

Возможность использования потока для ввода

public void setDoInput(boolean doinput);
  • setDoOutput

Возможность использования потока для вывода

public void setDoOutput(boolean dooutput);
  • setIfModifiedSince

Установка даты модификации документа

public void setIfModifiedSince(
  long ifmodifiedsince);

В классе URLConnection есть методы, позволяющие определить значения параметров, установленных только что описанными методами:

public boolean getDefaultUseCaches();
public boolean getUseCaches();
public boolean getDoInput();
public boolean getDoOutput();
public long getIfModifiedSince();

Определенный интерес могут представлять методы, предназначенные для извлечения информации из заголовка протокола HTTP:

  • getContentEncoding

Метод возвращает содержимое заголовка content-encoding (кодировка ресурса, на который ссылается URL)

public String getContentEncoding();
  • getContentLength

Метод возвращает содержимое заголовка content-length (размер документа)

public int getContentLength();
  • getContentType

Метод возвращает содержимое заголовка content-type (тип содержимого)

public String getContentType();
  • getDate

Метод возвращает содержимое заголовка date (дата посылки ресурса в секундах с 1 января 1970 года)

public long getDate();
  • getLastModified

Метод возвращает содержимое заголовка last-modified (дата изменения ресурса в секундах с 1 января 1970 года)

public long getLastModified();
  • getExpiration

Метод возвращает содержимое заголовка expires (дата устаревания ресурса в секундах с 1 января 1970 года)

public long getExpiration();

Другие методы, определенные в классе URLConnection, позволяют получить все заголовки или заголовки с заданным номером, а также другую информацию о соединении. При необходимости вы найдете описание этих методов в справочной системе Java WorkShop.


Аплет ShowChart

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

Попробуем теперь на практике применить технологию передачи файлов из каталога сервера Web в аплет для локальной обработки. Наше следующее приложение с названием ShowChart получает небольшой текстовый файл с исходными данными для построения круговой диаграммы, содержимое которого представлено ниже:

10,20,5,35,11,10,3,6,80,10,20,5,35,11,10,3,6,80

В этом файле находятся численные значения углов для отдельных секторов диаграммы, причем сумма этих значений равна 360 градусам. Наш аплет принимает этот файл через сеть и рисует круговую диаграмму, показанную на рис. 2.

pic02.gif (3920 bytes)

Рис. 2. Круговая диаграмма, построенная на базе исходных данных, полученных через сеть

Файл исходных данных занимает всего 49 байт, поэтому он передается по сети очень быстро. Если бы мы передавали графическое изображение этой диаграммы, статическое или динамическое, подготовленное, например, расширением сервера CGI или ISAPI, объем передаваемых по сети данных был бы намного больше.

Исходные тексты аплета ShowChart

Исходный текст приложения ShowChart приведен в листинге 1.

Листинг 1. Файл ShowChart.java

import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class ShowChart extends Applet
{
  URL SrcURL;
  Object URLContent;
  int errno = 0;
  String str;
  byte buf[] = new byte[200];
  public String getAppletInfo()
  {
    return "Name: ShowChart";
  }
  public void init()
  {
    try
    {
      SrcURL = new URL(
        "http://frolov/chart.txt");
      try
      {
        InputStream is = SrcURL.openStream();
        is.read(buf);
        str = new String(buf, 0);
      }
      catch (IOException ioe)
      {
        showStatus("read exception");
        errno = 1;
      }
    }
    catch (MalformedURLException uex)
    {
      showStatus(
        "MalformedURLException exception");
      errno = 2;
    }
  }
  public void paint(Graphics g)
  {
    Integer AngleFromChart = new Integer(0);
    int PrevAngle = 0;
    int rColor, gColor, bColor;
    Dimension dimAppWndDimension = getSize();

    g.setColor(Color.yellow);
    g.fillRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    showStatus(str);
    StringTokenizer st =
       new StringTokenizer(str, ",\r\n");
    while(st.hasMoreElements())
    {
      rColor = (int)(255 * Math.random());
      gColor = (int)(255 * Math.random());
      bColor = (int)(255 * Math.random());
      g.setColor(new Color(rColor,
         gColor, bColor));
      String angle =
        (String)st.nextElement();
      AngleFromChart = new Integer(angle) ;
      g.fillArc(0, 0, 200, 200,
        PrevAngle,
        AngleFromChart.intValue());
      PrevAngle += AngleFromChart.intValue();
    }
  }
}

Исходный текст документа HTML, созданного автоматически для нашего аплета, представлен в листинге 2.

Листинг 2. Файл ShowChart.tmp.html

<applet
  name="ShowChart"
  code="ShowChart"
  codebase=
  "file:/e:/Sun/Articles/vol12/src/ShowChart"
  width="200"
  height="200"
  align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
  <hr>If your browser recognized
the applet tag,
you would see an applet here.
  <hr>
</applet>

Описание исходного текста аплета ShowChart

Аплет ShowChart получает содержимое файла исходных данных для построения круговой диаграммы с помощью класса URL. Как вы увидите, для получения содержимого этого файла оно создает поток ввода явным образом.

Поля класса ShowChart

В классе ShowChart определены пять полей.

URL SrcURL;
Object URLContent;
int errno = 0;
String str;
byte buf[] = new byte[200];

Поле SrcURL класса URL хранит адрес URL файла исходных данных для круговой диаграммы. В поле URLContent типа Object будет переписано содержимое этого файла. В поле errno хранится текущий код ошибки, если она возникла, или нулевое значение, если все операции были выполнены без ошибок.

Поле str хранит принятую строку, которая предварительно записывается во временный буфер buf.

Метод init

Во время инициализации метод init создает объект класса URL для файла исходных данных:

SrcURL = new URL("http://frolov/chart.txt");

Здесь для упрощения исходного текста мы указали адрес URL файла данных непосредственно в программе, однако вы можете передать этот адрес аплету через параметр в документе HTML.

Далее для нашего объекта URL мы создаем поток ввода и получаем содержимое файла (то есть исходные данные для построения диаграммы):

InputStream is = SrcURL.openStream();
is.read(buf);

Принятые данные записываются в буфер buf и затем преобразуются к типу String с помощью соответствующего конструктора:

str = new String(buf, 0);

Если при создании объекта класса URL возникло исключение, метод init записывает в поле errno код ошибки, равный 2, записывая при этом в строку состояния браузера сообщение "MalformedURLException exception".

В том случае, когда объект класса URL создан успешно, а исключение возникло в процессе чтения содержимого файла, в поле errno записывается значение 1, а в строку состояния браузера - сообщение "read exception".

Метод paint

После раскрашивания фона окна аплета и рисования вокруг него рамки метод paint приступает к построению круговой диаграммы. Принятые данные отображаются в строке состояния браузера:

showStatus(sChart);

Далее создается разборщик строки исходных данных:

StringTokenizer st =
  new StringTokenizer(sChart, ",\r\n");

В качестве разделителей для этого разборщика указывается запятая, символ возврата каретки и перевода строки.

Рисование секторов диаграммы выполняется в цикле, условием выхода из которого является завершение разбора строки исходных данных:

while(st.hasMoreElements())
{
  . . .
}

Для того чтобы секторы диаграммы не сливались, они должны иметь разный цвет. Цвет сектора можно было бы передавать вместе со значением угла через файл исходных данных, однако мы применили более простой способ раскаршивания секторов - в случайные цвета. Мы получаем случайные компоненты цвета сектора, а затем выбираем цвет в контекст отображения:

rColor = (int)(255 * Math.random());
gColor = (int)(255 * Math.random());
bColor = (int)(255 * Math.random());
g.setColor(new Color(rColor,
   gColor, bColor));

С помощью метода nextElement мы получаем очередное значение угла сектора и сохраняем его в переменной angle:

String angle = (String)st.nextElement();

Далее с помощью конструктора класса Integer это значение преобразуется в численное:

AngleFromChart = new Integer(angle);

Рисование сектора круговой диаграммы выполняется с помощью метода fillArc:

g.fillArc(0, 0, 200, 200,
  PrevAngle, AngleFromChart.intValue());

В качестве начального значения угла сектора используется значение из переменной PrevAngle. Сразу после инициализации в эту переменную записывается нулевое значение.

Конечный угол сектора задается как AngleFromChart.intValue(), то есть указывается значение, полученное из принятого по сети файла исходных данных.

После завершения рисования очередного сектора круговой диаграммы начальное значение PrevAngle увеличивается на величину угла нарисованного сектора:

PrevAngle += AngleFromChart.intValue();


Приложения SocketServ и SocketClient

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

В качестве примера мы приведем исходные тексты двух приложений Java, работающих с потоковыми сокетами. Одно из этих приложений называется SocketServ и выполняет роль сервера, второе называется SocketClient и служит клиентом.

Приложение SocketServ выводит на консоль строку "Socket Server Application" и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient.

Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли.

Когда пользователь вводит строку "quit", цикл ввода и передачи строк завершается.

Весь процесс показан на рис. 3 и 4.

pic03.gif (4444 bytes)

Рис. 3. Окно клиентского приложения

pic04.gif (3495 bytes)

Рис. 4. Окно серверного приложения

Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой "quit", завершившая работу приложений.

Исходный текст серверного приложения SocketServ

Исходный текст серверного приложения SocketServ приведен в листинге 3.

Листинг 3. Файл SocketServ.java

import java.io.*;
import java.net.*;
import java.util.*;
public class SocketServ
{
  public static void main(String args[])
  {
    byte bKbdInput[] = new byte[256];
    ServerSocket ss;
    Socket s;
    InputStream is;
    OutputStream os;
    try
    {
      System.out.println(
        "Socket Server Application");
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    try
    {
      ss = new ServerSocket(9999);
      s = ss.accept();
      is = s.getInputStream();
      os = s.getOutputStream();
      byte buf[] = new byte[512];
      int lenght;
      while(true)
      {
        lenght = is.read(buf);
        if(lenght == -1)
          break;
        String str = new String(buf, 0);
        StringTokenizer st;
        st   = new StringTokenizer(
           str, "\r\n");
        str = new String(
          (String)st.nextElement());
        System.out.println(">  " + str);
        os.write(buf, 0, lenght);
        os.flush();
      }
      is.close();
      os.close();
      s.close();
      ss.close();
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    try
    {
      System.out.println(
        "Press <Enter> to terminate
 application...");
      System.in.read(bKbdInput);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
}

Описание исходного текста серверного приложения SocketServ

В методе main, получающем управление сразу после запуска приложения, мы определили несколько переменных.

Массив bKbdInput размером 256 байт предназначен для хранения строк, введенных при помощи клавиатуры.

В переменную ss класса ServerSocket будет записана ссылка на объект, предназначенный для установления канала связи через потоковый сокет (но не ссылка на сам сокет):

ServerSocket ss;

Ссылка на сокет, с использованием которого будет происходить передача данных, хранится в переменной с именем s класса Socket:

Socket s;

Кроме того, мы определили переменные is и os, соответственно, классов InputStream и OutputStream:

InputStream is;
OutputStream os;

В эти переменные будут записаны ссылки на входной и выходной поток данных, которые связаны с сокетом.

После отображения на консоли строки названия приложения, метод main создает объект класса ServerSocket, указывая конструктору номер порта 9999:

ss = new ServerSocket(9999);

Конструктор возвращает ссылку на объект, с использованием которого можно установить канал передачи данных с клиентом.

Канал устанавливается методом accept:

s = ss.accept();

Этот метод переводит приложение в состояние ожидания до тех пор, пока не будет установлен канал передачи данных.

Метод accept в случае успешного создания канала передачи данных возвращает ссылку на сокет, с применением которого нужно принимать и передавать данные.

На следующем этапе сервер создает входной и выходной потоки, вызывая для этого методы getInputStream и getOutputStream, соответственно:

is = s.getInputStream();
os = s.getOutputStream();

Далее приложение подготавливает буфер buf для приема данных и определяет переменную length, в которую будет записываться размер принятого блока данных:

byte buf[] = new byte[512];
int lenght;

Теперь все готово для запуска цикла приема и обработки строк от клиентского приложения.

Для чтения строки мы вызываем метод read применительно ко входному потоку:

lenght = is.read(buf);

Этот метод возвращает управление только после того, как все данные будут прочитаны, блокируя приложение на время своей работы. Если такая блокировка нежелательна, вам следует выполнять обмен данными через сокет в отдельной задаче.

Метод read возвращает размер принятого блока данных или -1, если поток исчерпан. Мы воспользовались этим обстоятельством для завершения цикла приема данных:

if(lenght == -1)
  break;

После завершения приема блока данных мы преобразуем массив в текстовую строку str класса String, удаляя из нее символ перевода строки, и отображаем результат на консоли сервера:

System.out.println(">  " + str);

Затем полученная строка отправляется обратно клиентскому приложению, для чего вызывается метод write:

os.write(buf, 0, lenght);

Методу write передается ссылка на массив, смещение начала данных в этом массиве, равное нулю, и размер принятого блока данных.

Для исключения задержек в передаче данных из-за накопления данных в буфере (при использовании буферизованных потоков) необходимо принудительно сбрасывать содержимое буфреа метдом flush:

os.flush();

И хотя в нашем случае мы не пользуемся буферизованными потоками, мы включили вызов этого метода для примера.

Теперь о завершающих действиях после прерывания цикла получения, отображения и передачи строк.

Наше приложение явням образом закрывает входной и выходной потоки данных, сокет, а также объект класса ServerSocket, с использованием которого был создан канал передачи данных:

is.close();
os.close();
s.close();
ss.close();

Исходный текст клиентского приложения SocketClient

Исходный текст клиентского приложения SocketClient приведен в листинге 4.

Листинг 4. Файл SocketClient.java

import java.io.*;
import java.net.*;
import java.util.*;
public class SocketClient
{
  public static void main(String args[])
  {
    byte bKbdInput[] = new byte[256];
    Socket s;
    InputStream is;
    OutputStream os;
    try
    {
      System.out.println(
        "Socket Client Application" +
        "\nEnter any string or" +
        " 'quit' to exit...");
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    try
    {
      s = new Socket("localhost",9999);
      is = s.getInputStream();
      os = s.getOutputStream();
      byte buf[] = new byte[512];
      int length;
      String str;
      while(true)
      {
        length = System.in.read(bKbdInput);
        if(length != 1)
        {
          str = new String(bKbdInput, 0);
          StringTokenizer st;
          st   = new StringTokenizer(
            str, "\r\n");
          str = new String(
            (String)st.nextElement());
          System.out.println(">  " + str);
          os.write(bKbdInput, 0, length);
          os.flush();
          length = is.read(buf);
          if(length == -1)
            break;
          str = new String(buf, 0);
          st   = new StringTokenizer(
            str, "\r\n");
          str = new String(
              (String)st.nextElement());
          System.out.println(">> " + str);
          if(str.equals("quit"))
            break;
        }
      }
      is.close();
      os.close();
      s.close();
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    try
    {
      System.out.println(
        "Press <Enter> to " +
         "terminate application...");
      System.in.read(bKbdInput);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
}

Описание исходного текста клиентского приложения SocketClient

Внутри метода main клиентского приложения SocketClient определены переменные для ввода строки с клавиатуры (массив bKbdInput), сокет s класса Socket для работы с сервером SocketServ, входной поток is и выходной поток os, которые связаны с сокетом s.

После вывода на консоль приглашающей строки клиентское приложение создает сокет, вызывая конструктор класса Socket:

s = new Socket("localhost",9999);

В процессе отладки мы запускали сервер и клиент на одном и том же узле, поэтому в качестве адреса сервера указана строка "localhost". Номер порта сервера SocketServ равен 9999, поэтому мы и передали конструктору это значение.

После создания сокета наше клиентское приложение создает входной и выходной потоки, связанные с этим сокетом:

is = s.getInputStream();
os = s.getOutputStream();

Теперь клиентское приложение готово обмениваться данными с сервером.

Этот обмен выполняется в цикле, условием завершения которого является ввод пользователем строки "quit".

Внутри цикла приложение читает строку с клавиатуры, записывая ее в массив bKbdInput:

length = System.in.read(bKbdInput);

Количество введенных символов сохраняется в переменной length.

Далее если пользователь ввел строку, а не просто нажал на клавишу <Enter>, эта строка отображается на консоли и передается серверу:

os.write(bKbdInput, 0, length);
os.flush();

Сразу после передачи сбрасывается буфер выходного потока.

Далее приложение читает ответ, посылаемый сервером, в буфер buf:

length = is.read(buf);

Напомним, что наш сервер посылает клиенту принятую строку в неизменном виде.

Если сервер закрыл канал, то метод read возвращает значение -1. В этом случае мы прерываем цикл ввода и передачи строк:

if(length == -1)
  break;

Если же ответ сервера принят успешно, принятые данные записываются в строку str, которая отображается на консоли клиента:

System.out.println(">> " + str);

Перед завершением своей работы клиент закрывает входной и выходной потоки, а также сокет, на котором выполнялась передача данных:

is.close();
os.close();
s.close();


Аплет Form

Оглавление

Сетевые приложения
Адрес IP
Адрес URL
Класс URL
Сокеты
Потоковые сокеты
Класс Socket
Датаграммные сокеты
Связь с Web
Аплет ShowChart
Приложения SocketServ и SocketClient
Аплет Form

Назад Вперед

На примере аплета Form мы покажем, как приложения Java могут взаимодействовать с расширениями сервера Web, такими как программы CGI или приложения ISAPI.

В окне нашего аплета находится форма, содержащая два однострочных поля редактирования, кнопку и многострочное поле редактирования (рис. 5).

pic05.gif (4332 bytes)

Рис. 5. Окно аплета Form

Эта форма предназначена для добавления записей в базу данных, содержащую электронные почтовые адреса. Заполнив поля имени и адреса E-Mail, пользователь должен нажать кнопку Send. При этом введенная информация будет передана расширению сервера CGI, который запишет ее в базу данных, а затем отправит обратно аплету. Сохраненные записи, полученные от программы CGI, аплет FORM отобразит в многострочном поле редактирования, как это показано на рис. 5.

Исходные тексты аплета Form

Исходные тексты аплета Form представлены в листинге 5.

Листинг 5. Файл Form.java

import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class Form extends Applet
  implements Runnable
{
  private Thread m_store = null;
  TextField txtName;
  TextField txtEMail;
  TextArea  txta;
  Button btnGetText;
  public void init()
  {
    Label lbName;
    Label lbEMail;
    Label lbPress;
    lbName = new Label("Enter your name:");
    lbEMail = new Label(
      "Enter your E-Mail address:");
    add(lbName);
    txtName = new TextField("Your name", 40);
    add(txtName);
    add(lbEMail);
    txtEMail =
      new TextField("your@email", 40);
    add(txtEMail);
    btnGetText = new Button("Send!");
    add(btnGetText);
    txta = new TextArea(8, 65);
    add(txta);
    setBackground(Color.yellow);
  }
  public void paint(Graphics g)
  {
    setBackground(Color.yellow);
    Dimension dimAppWndDimension = getSize();
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
  }
  public boolean action(Event evt, Object obj)
  {
    Button btn;
    if(evt.target instanceof Button)
    {
      btn = (Button)evt.target;
      if(evt.target.equals(btnGetText))
      {
        startTransaction();
      }
      else
        return false;
      return true;
    }
    return false;
  }
  void startTransaction()
  {
    m_store = new Thread(this);
    m_store.start();
  }
  public void stop()
  {
    if (m_store != null)
    {
      m_store.stop();
      m_store = null;
    }
  }
  public void run()
  {
    URL u;
    URLConnection c;
    PrintStream ps;
    DataInputStream is;
    try
    {
      String szSourceStr =
        txtName.getText() +
    ", " + txtEMail.getText();
      String szReceived;
      String szURL =
      "http://frolov/scripts/store.exe";
      u = new URL(szURL);
      c = u.openConnection();
      ps = new PrintStream(
        c.getOutputStream());
      ps.println(szSourceStr);
      ps.close();
      is = new DataInputStream(
        c.getInputStream());
      szReceived = is.readLine();
      is.close();
      txta.appendText(szReceived + "\r\n");
      repaint();
    }
    catch (Exception ioe)
    {
      showStatus(ioe.toString());
      stop();
    }
  }
}

Исходный текст документа HTML, который был подготовлен для нас системой Java Workshop, мы немного отредактировали, изменив параметр CODEBASE (листинг 6).

Листинг 6. Файл Form.tmp.html

<applet name="Form"
  code="Form.class"
  codebase="http://frolov/"
  width="500"
  height="200"
  align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
   <hr>If your browser
recognized the applet tag,
you would see an applet here.<hr>
</applet>

В этом параметре следует указать путь к каталогу, в котором располагается байт-код аплета.

Описание исходныех текстов аплета Form

При инициализации метод init создает все необходимые органы управления и добавляет их в окно аплета.

Когда пользователь заполняет форму и нажимает кнопку Send, обработчик соответствующего события вызывает метод startTransaction, запускающий процесс обмена данными с расширением сервера Web:

if(evt.target.equals(btnGetText))
{
  startTransaction();
}

Метод startTransaction, определенный в нашем приложении, создает и запускает на выполнение поток, который и будет взаимодействовать с программой CGI:

void startTransaction()
{
  m_store = new Thread(this);
  m_store.start();
}

При этом в качестве отдельного потока, работающего одновременно с кодом аплета, выступает метод run. Именно в нем сосредоточена вся логика обмена данными с сервером Web.

Так как в процессе взаимодействия могут возникать различные исключения, мы предусмотрели их обработку при помощи блока try-catch:

URL u;
URLConnection c;
PrintStream ps;
DataInputStream is;
try
{
  . . .
}
catch (Exception ioe)
{
  showStatus(ioe.toString());
  stop();
}

Название возникшего исключения будет отображено в строке состояния браузера.

Теперь о том, что делает метод run после получения управления.

Первым делом он извлекает из однострочных текстовых полей имя и электронный адрес, объединяя их и записывая полученную текстовую строку в поле szSourceStr:

String szSourceStr =
   txtName.getText() + ", " +
   txtEMail.getText();

В строке szURL находится адрес URL программы CGI:

String szURL =
  "http://frolov/scripts/store.exe";

В реальном приложении этот адрес необходимо передавать аплету через параметр. Мы использовали непосредственное кодирование только для упрощения исходного текста.

На следующем этапе метод run создает для программы CGI объект класса URL и открывает с ним соединение:

u = new URL(szURL);
c = u.openConnection();

Пользуясь этим соединением, метод run создает форматированный поток вывода, записывает в него строку имени и электронного адреса, а затем закрывает поток:

ps = new PrintStream(c.getOutputStream());
ps.println(szSourceStr);
ps.close();

Переданные таким образом данные попадут в стандартный поток ввода программы CGI, откуда она их и прочитает.

Сделав это, программа CGI запишет в стандартный выходной поток строку ответа, которую необходимо прочитать в методе run нашего аплета. Для этого мы открываем входной поток, создаем на его основе форматированный входной поток данных, читаем одну строку текста и закрываем входной поток:

is = new DataInputStream(c.getInputStream());
String szReceived;
szReceived = is.readLine();
is.close();

Сразу после этого программа CGI завершит свою работу и будет готова к обработке новых запросов на добавление записей. Что же касается метода run, то он добавит полученную от расширения сервера текстовую строку в многострочное окно редактирования, как это показано ниже, а затем инициирует перерисовку окна аплета:

txta.appendText(szReceived + "\r\n");
repaint();

Заметим, что использованный нами способ передачи данных подходит только для латинских символов. Если вам нужно передавать символы кириллицы, следует преобразовывать их из кодировки UNICODE, например, в гексадецимальную кодировку, а в программе CGI выполнять обратное преобразование. Аналогичную методику можно применять и для передачи произвольных двоичных данных.

Исходный текст программы CGI store.exe

Исходный текст программы CGI store.exe очень прост и показан в листинге 7.

Листинг 7. Файл store.c

#include <windows.h>
#include <tchar.h>
#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char *argv[])
{
  int   nInDatasize;
  char  * szMethod;
  char  szBuf[2000];
  FILE *fDatabase;
  CRITICAL_SECTION csAddRecord;
  szMethod = getenv("REQUEST_METHOD");
  if(!strcmp(szMethod, "POST"));
  {
    nInDatasize = atoi(
      getenv("CONTENT_LENGTH"));
    fread(szBuf, nInDatasize, 1, stdin);
    szBuf[nInDatasize] = '\0';
    InitializeCriticalSection(&csAddRecord);
    EnterCriticalSection(&csAddRecord);
    fDatabase =
      fopen("c:\\EMAIL.DAT", "a+");
    if(fDatabase != NULL)
    {
      fputs(szBuf, fDatabase);
      fclose(fDatabase);
    }
    LeaveCriticalSection(&csAddRecord);
    DeleteCriticalSection(&csAddRecord);
    printf(
      "Content-type: text/plain\r\n\r\n");
    printf("Stored information: %s", szBuf);
  }
}

Этот текст подготовлен для работы в среде Windows 95 или Windows NT, так как для синхронизации доступа к файлу мы использовали специфические для этих операционных систем функции работы с критическими секциями.

Свою работу программа CGI начинает с анализа переменной среды REQUEST_METHOD. Убедившись, что при запуске программы ей передали данные методом POST, программа определяет размер этих данных исходя из содержимого переменной среды CONTENT_LENGTH.

Далее программа считывает соответствующее количество байт данных из стандартного потока ввода, записывает их в файл. Затем, после добавления заголовка "Stored information:", программа CGI записывает полученную строку в стандартный выходной поток, передавая ее таким образом аплету Form.

Так как при реальной работе в сети Internet вашу программу CGI могут одновременно запустить несколько пользователей, для синхронизации обновления файла базы данных мы применили критическую секцию. В результате с файлом может работать в любой момент времени только одна копия программы CGI.

Еще одно замечание касается пути к файлу, который в нашем случае создается в корневом каталоге диска C:. При установке программы CGI на сервер вам необходимо обеспечить доступ на запись к каталогу, в котором располагается файл, для удаленных пользователей. О том, как это сделать, вы можете узнать из документации на ваш сервер Web.


Растровые изображения и анимация

Оглавление

Растровые изображения
Загрузка и рисование
Класс Image
Ожидание загрузки
Видео
Аплет CDRotation

Назад Вперед

Одно из наиболее распространенный применений аплетов связано с рисованием простых или анимированных растровых изображений. На серверах Web изображения обычно хранятся в форматах GIF или JPEG. Оба эти формата обеспечивают сжатие изображения, что весьма актуально из-за невысокой скорости передачи данных в сети Internet.

Рисование растровых изображений в приложениях для операционной системы Windows, составленных на языке программирования С - непростая задача. В классическом программном интерфейсе этой операционной системы отсутствуют функции, с помощью которых можно было бы непосредственно рисовать содержимое файлов с растровыми изображениями. Программист вынужден работать с заголовками таких файлов, выделять таблицу цветов и биты изображений, создавать и реализовывать палитру, заниматься восстановлением сжатых данных и так далее.

Разработчик приложений Java находится в намного лучшем положении, так как библиотеки классов Java содержат простые в использовании и мощные средства, предназначенные для работы с растровыми изображениями. В этой статье мы научим вас рисовать в окне аплета содержимое файлов GIF и JPEG, выполняя при необходимости масштабирование, а также создавать на базе таких файлов анимационные изображения, показывая по очереди отдельные кадры небольшого видеофильма.


Загрузка и рисование растрового изображения

Оглавление

Растровые изображения
Загрузка и рисование
Класс Image
Ожидание загрузки
Видео
Аплет CDRotation

Назад Вперед

Загрузка растрового изображения из файла выполняется очень просто - с помощью метода getImage, определенного в классе Applet:

public Image getImage(URL url);
public Image getImage(URL url, String name);

Первый вариант метода предполагает использование только одного параметра - адреса URL файла графического изображения. Второй позволяет дополнительно указать относительное расположение файла изображения относительно адреса URL, например:

Image img;
img = getImage(
"http://www.glasnet.ru/~frolov/pic","cd.gif");

Если аплет желает загрузить изображение, расположенное в том же каталоге, что и он сам, это можно сделать следующим образом:

img = getImage(getCodeBase(), "cd.gif");

Метод getCodeBase, определенный в классе Applet, возвращает адрес URL аплета. Вместо него можно использовать метод getDocumentBase, который также определен в классе Applet и возвращает адрес URL документа HTML, содержащего аплет:

img = getImage(getDocumentBase(), "cd.gif");

В любом случае метод getImage создает объект класса Image.

Заметим, что на самом деле метод getImage вовсе не загружает изображение через сеть, как это можно было бы подумать. Он только создает объект класса Image. Реальная загрузка файла растрового изображения будет выполняться методом рисования drawImage, который определен в классе Graphics:

public abstract boolean
  drawImage(Image img, int x, int y,
            ImageObserver observer);
public abstract boolean
  drawImage(Image img, int x, int y,
    Color bgcolor, ImageObserver observer);
public abstract boolean
  drawImage(Image img, int x, int y,
    int width, int height,
    ImageObserver observer);
public abstract boolean
  drawImage(Image img, int x, int y,
    int width, int height,
    Color bgcolor, ImageObserver observer);

Как видите, существует четыре варианта этого метода.

В качестве первого параметра любому варианту метода передается ссылка на объект класса Image, полученный ранее с помощью метода getImage.

Параметры x и y задают координаты верхнего левого угла прямоугольной области, внутри которой будет нарисовано изображение. Эти параметры также задаются для любого варианта метода drawImage.

Параметр bgcolor задает цвет фона, на котором будет нарисовано изображение. Как вы, вероятно, знаете, изображения GIF могут быть прозрачными. В этом случае цвет фона может иметь большое значение.

Если для рисования выбраны варианты метода drawImage с параметрами width и height, изображение будет нарисовано с масштабированием. При этом указанные параметры будут определять, соответственно, ширину и высоту изображения.

Параметр observer представляет собой ссылку на объект класса ImageObserver, который получит извещение при загрузке изображения. Обычно в качестве такого объекта используется сам аплет, поэтому данный параметр указывается как this.

Вот два примера использования метода drawImage:

g.drawImage(FloppyDiskImg, 25, 3, this);
g.drawImage(FloppyDiskImg,
  25, 42, 200, 200, this);

В первой строке изображение FloppyDiskImg рисуется в точке с координатами (25, 3) без масштабирования, во второй - в точке с координатами (25, 42), причем высота и ширина нарисованного изображения будет равна 200 пикселам.

Метод drawImage запускает процесс загрузки и рисования изображения, а затем, не дожидаясь его завершения, возвращает управление. Так как загрузка файла изображения по сети может отнять немало времени, она выполняется асинхронно в отдельной задаче.


Класс Image

Оглавление

Растровые изображения
Загрузка и рисование
Класс Image
Ожидание загрузки
Видео
Аплет CDRotation

Назад Вперед

Процесс рисования растрового изображения в окне аплета предельно прост - вам достаточно загрузить изображение методом getImage и затем нарисовать его методом drawImage.

Но не забывайте, что метод getImage в действительности только создает объект класса Image, но не загружает его. Давайте посмотрим на класс Image.

В этом классе имеется единственный конструктор без параметров:

public Image();

Вы, однако, скорее всего будете создавать объекты класса Image при помощи метода getImage.

Методы getHeight и getWidth, определенные в классе Image, позволяют определить, соответственно, высоту и ширину изображения:

public abstract int getHeight(
  ImageObserver observer);
public abstract int getWidth(
  ImageObserver observer);

Так как при вызове этих методов изображение еще может быть не загружено, в качестве параметров методам передается ссылка на объект ImageObserver. Этот объект получит извещение, когда будет доступна высота или ширина изображения.

Метод getGraphics позволяет получить так называемый внеэкранный контекст отображения для рисования изображения не в окне аплета, а в оперативной памяти:

public abstract Graphics getGraphics();

Эта техника используется для того, чтобы вначале подготовить изображение в памяти, а затем за один прием отобразить его на экране.

Еще один метод класса Image, который мы рассмотрим, называется flush:

public abstract void flush();

Он освобождает ресурсы, занятые изображением.


Ожидание загрузки изображений

Оглавление

Растровые изображения
Загрузка и рисование
Класс Image
Ожидание загрузки
Видео
Аплет CDRotation

Назад Вперед

Как мы уже говорили в наших предыдущих статьях, загрузка изображений из сети Internet - длительный процесс, который в среднем идет со скоростью 1 Кбайт в секунду. Поэтому изображения загружаются навигатором в отдельной задаче. При этом метод getImage только создает объект класса Image, а метод drawImage инициирует загрузку изображения и рисует его. Причем если файл изображения имеет большую длину, он будет появляться в окне аплета постепенно по мере загрузки.

Однако в некоторых случаях было бы удобно вначале загрузить изображение полностью, а лишь затем выполнять рисование, чтобы изображение появилось на экране сразу. Кроме того, аплет может рисовать сразу несколько изображений в разных местах своего окна или показывать их по очереди в одном и том же месте для достижения эффекта анимации.

Есть ли способ определить, когда изображение будет загружено полностью?

Есть, и причем целых два. Один из них связан с использованием класса MediaTracker, специально предназначенного для этой цели и достаточно удобного в использовании, другой основан на переопределении одного из методов интерфейса ImageObserver.

Применение класса MediaTracker

Для того чтобы выполнить ожидание загрузки нескольких изображений, проще воспользоваться классом MediaTracker, а не интерфейсом ImageObserver.

Как это сделать?

Обычно метод init аплета создает объект класса MediaTracker с помощью конструктора и добавляет в него все изображения, загрузки которых необходимо дождаться.

Создание объекта класса MediaTracker

Объект класса MediaTracker создается следующим образом:

MediaTracker mt;
mt = new MediaTracker(this);

Конструктору класса MediaTracker передается ссылка на компонент, для которого необходимо отслеживать загрузку изображений. В данном случае это наш аплет, поэтому мы передаем конструктору значение this.

Добавление изображений в объект класса MediaTracker

Далее метод init должен создать все необходимые объекты класса Image и добавить их в объект MediaTracker методом addImage. Ниже мы показали фрагмент кода, в котором выполняется добавление трех изображений:

Image img1;
Image img2;
Image img3;
img1 = getImage(getCodeBase(), "pic1.gif");
img2 = getImage(getCodeBase(), "pic2.gif");
img3 = getImage(getCodeBase(), "pic3.gif");
mt.addImage(img1 , 0);
mt.addImage(img2 , 0);
mt.addImage(img3 , 0);

В качестве первого параметра методу addImage передается ссылка на изображение, загрузку которого необходимо отслеживать, а в качестве второго - идентификатор, который можно будет использовать в процессе отслеживания. Если все, что вам нужно, это дождаться окончания загрузки изображений, то для второго параметра вы можете указать нулевое значение.

Ожидание загрузки добавленных изображений

Для того чтобы убедиться, что все изображения загружены, вы можете воспользоваться методом waitForAll. Этот метод инициирует загрузку изображений, а также задержит выполнение вызвавшего потока до момента полной загрузки всех изображений, добавленных в объект класса MediaTracker:

try
{
  mt.waitForAll();
}
catch (InterruptedException ex)
{
}

Обратите внимание, что метод waitForAll может создавать исключение InterruptedException. Это исключение возникает, если по какой-либо причине процесс ожидания прерывается.

Чаще всего рисование выполняется в отдельном потоке, поэтому метод waitForAll должен вызываться в начале соответствующего метода run. Ниже мы привели исходные тексты приложения ImageDrawWait, в котором такое ожидание выполняется в методе paint, что приводит, однако, к блокировке работы аплета до момента загрузки всех изображений. В данном случае это не критично, так как кроме рисования изображений наш аплет ничего не делает, однако более предпочтительным является выполнение длительных процессов в отдельном потоке.

Другие методы класса MediaTracker

Какие другие полезные методы, кроме методов addImage и waitForAll есть в классе MediaTracker?

public boolean waitForAll(long ms);

Метод waitForAll с параметром ms позволяет выполнять ожидание в течение заданного времени. Время ожидания задается в миллисекундах. При этом если за указанное время все изображения были успешно загружены, метод waitForAll возвращает значение true, если нет - false.

Вариант метода checkAll с параметром load позволяет проверить, завершилась ли загрузка отслеживаемых изображений:

public boolean checkAll(boolean load);

Если значение параметра load равно true, метод инициирует загрузку изображений.

Если при добавлении изображений методом addImage вы использовали второй параметр этого метода для присваивания разным группам изображений различные идентификаторы, то с помощью метода checkID можно дождаться завершения загрузки отдельной группы изображений:

public boolean checkID(int id);

Есть также вариант этого метода, позволяющий проверить загрузку группы изображений с заданным идентификатором:

public boolean checkID(int id, boolean load);

Метод waitForID с параметрами id и ms позволяет выполнять ожидание загрузки группы изображений с заданным идентификатором в течении указанного периода времени:

public boolean waitForID(int id, long ms);

Класс MediaTracker предоставляет также возможность прослеживать сам процесс загрузки всех добавленных в него изображений или отдельных групп изображений с помощью методов statusAll и statusID:

public int statusAll(boolean load);
public int statusID(int id, boolean load);

В зависимости от значения параметра load эти методы могут инициировать загрузку изображений. Если параметр равен true, загрузка изображений инициируется, если false - выполняется только проверка текущего состояния загрузки.

Методы statusAll и statusID возвращают значение, составленное из отдельных битов состояния при помощи логической операции ИЛИ. Ниже мы перечислили эти биты состояния и привели их краткое описание.

Биты состояния | Описание
MediaTracker.LOADING | Один или несколько отслеживаемых файлов продолжают загружаться
MediaTracker.ABORTED | Загрузка одного или нескольких файлов была прервана
MediatTracker.ERRORED | При загрузке одного или нескольких файлов произошла ошибка
MediaTracker.COMPLETE | Загрузка всех отслеживаемых файлов произошла полностью и успешно

Еще четыре метода, определенных в классе MediaTracker, связаны с обработкой ошибок:

public boolean isErrorAny();
public boolean isErrorID(int id);
public Object[] getErrorsAny();
public Object[] getErrorsID(int id);

Методы isErrorAny и isErrorID позволяют проверить, возникла ли ошибка при загрузке, соответственно, любого из отслеживаемых изображений или изображений из заданной группы. Если ошибка произошла, возвращается значение true, если нет - значение false.

Методы getErrorsAny и getErrorsID возвращают массив объектов, при ожидании загрузки которых произошла ошибка. Первый из этих методов возвращает массив для всех отслеживаемых объектов, второй - только объектов из заданной группы.

Применение интерфейса ImageObserver

Второй способ ожидания завершения процесса загрузки изображений связан с интерфейсом ImageObserver:

Биты флагов для параметра infoflags метода imageUpdate

public final static int ABORT;
public final static int ALLBITS;
public final static int ERROR;
public final static int FRAMEBITS;
public final static int HEIGHT;
public final static int PROPERTIES;
public final static int SOMEBITS;
public final static int WIDTH;

Метод imageUpdate

public abstract boolean
  imageUpdate(Image img, int infoflags,
  int x, int y, int width, int height);

Как видите, в интерфейсе ImageObserver определен единственный метод imageUpdate и набор битовых флагов для этого метода.

Класс Component, от которого происходит класс Applet, реализует интерфейс ImageObserver:

public abstract class java.awt.Component
  extends java.lang.Object
  implements java.awt.image.ImageObserver
{
  . . .
}

Этот интерфейс используется для отслеживания процесса загрузки и перерисовки изображений и других компонент, расположенных внутри компонента. В частности, он используется для отслеживания загрузки и рисования растровых изображений в окне аплета, чем мы и воспользуемся.

В процессе загрузки вызывается метод imageUpdate, поэтому чтобы отслеживать загрузку изображений, наш аплет должен переопределить этот метод.

Процедура ожидания загрузки изображений достаточно проста.

Прежде всего, аплет должен передать в последнем параметре методу drawImage ссылку на интерфейс ImageObserver, который будет применяться для отслеживания процесса загрузки:

g.drawImage(Img, x, y,
  width, height, this);

Здесь в качестве ссылки на интерфейс ImageObserver мы передали значение this. При этом будет применен интерфейс нашего аплета. Соответственно, нам нужно определить в классе аплета метод imageUpdate, который будет вызываться в процессе загрузки изображений.

Ниже мы привели возможный вариант реализации этого метода:

public boolean imageUpdate(
  Image img, int flags,
  int x, int y, int w, int h)
{
  // Проверяем, все ли
  // биты изображения загружены
  fAllLoaded = ((flags & ALLBITS) != 0);

  // Если все, перерисовываем окно
  if(fAllLoaded)
    repaint();

  // Если все биты загружены,
  // дальнейшие вызовы
  // метода imageUpdate не нужны
  return !fAllLoaded;
}

Через первый параметр img методу imageUpdate передается ссылка на изображение, загрузка которого отслеживается.

Параметр flags отражает состояние процесса загрузки.

Через остальные параметры x, y, w и h передаются, соответственно, координаты и размеры изображения.

Основное, что должен делать метод imageUpdate для отслеживания процесса загрузки - это проверять флаги flags, дожидаясь установки нужных флагов.

Флаги определены следующим образом:

public final static int WIDTH;
public final static int HEIGHT = 2;
public final static int PROPERTIES = 4;
public final static int SOMEBITS = 8;
public final static int FRAMEBITS = 16;
public final static int ALLBITS = 32;
public final static int ERROR = 64;
public final static int ABORT = 128;

Ниже мы привели краткое описание перечисленных флагов.

Флаг | Описание
WIDTH | Изображение загружено настолько, что стала доступна его ширина. Значение ширины изображения можно получить из параметра w метода imageUpdate
HEIGHT | Аналогично предыдущему, но для высоты изображения. Высоту изображения можно получить из параметра h метода imageUpdateimageUpdate
PROPERTIES | Стали доступны свойства изображения, которые можно получить методом getProperty класса Image. В нашей книге мы опустили описание этого метода
SOMEBITS | Стали доступны биты изображения для рисования в масштабе. Через параметры x, y, h и w передаются координаты и размеры прямоугольной области, которая ограничивает загруженную часть изображения
FRAMEBITS | Загружен очередной фрейм изображения, состоящего из нескольких фреймов. Параметры x, y, h и w следует игнорировать
ALLBITS | Изображение загружено полностью. Параметры x, y, h и w следует игнорировать
ERROR | При загрузке произошла ошибка
ABORT | Загрузка изображения была прервана или отменена

Анализируя состояние флагов, метод imageUpdate может следить за ходом загрузки изображений, отображая, например, процент завершения процесса загрузки или выполняя какие-либо другие действия.

Если вам нужно только дождаться завершения процесса загрузки, достаточно использовать флаг ALLBITS. Для проверки ошибок воспользуйтесь флагами ERROR и ABORT.

Sun Microsystems Inc. Corporate Information Our partners News and Events Search on site Java Computing Products and Solutions Technologies and Researches Education and Service Content


Видео в окне аплета

Оглавление

Растровые изображения
Загрузка и рисование
Класс Image
Ожидание загрузки
Видео
Аплет CDRotation

Назад Вперед

Наиболее динамичные страницы сервера Web содержат анимационные изображения в виде небольших видеофильмов. Вы можете подготовить видеофильм как файл AVI или как многосекционный файл GIF.

Файл AVI представляет собой многопоточный файл, содержащий видео и звук. Файлы AVI можно создавать при помощи специального видеоадаптера, который способен оцифровывать сигнал с видеокамеры или видеомагнитофона, а также из отдельных изображений, составляющих кадры видеофильма.

Заметим, однако, что озвученный видеофильм в формате AVI продолжительностью в 1 минуту занимает мегабайты дискового пространства. При существующих на сегодняшний день скоростях передачи данных через Internet не имеет никакого смысла размещать на страницах сервера Web файлы такого размера.

Многосекционные файлы GIF не содержат звуковой информации и состоят обычно из одного-двух десятков кадров. Для каждого такого кадра вы можете задавать время отображения и координаты, где этот кадр будет отображаться. Можно также добиться зацикленного отображения видеофильма, созданного как многосекционный файл GIF.

Аплеты Java предоставляют вам еще одну возможность отображения небольших видеофильмов на страницах сервера Web.

Для реализации этой возможности вы должны подготовить и разместить в одном из каталогов сервера Web файлы отдельных кадров видеофильма в формате GIF или JPEG.

Аплет Java должен загрузить эти изображения, дождавшись окончания процесса загрузки, что можно сделать либо при помощи рассмотренного в этой главе класса MediaTracker либо при помощи интерфейса ImageObserver.

Как только все изображения будут полностью загружены, аплет может начинать их поочередное отображение в цикле. Этот цикл должен выполняться в отдельной задаче.

Так как аплет полностью контролирует отображение кадров фильма, он может реализовывать эффекты, недостижимые при использовании файлов AVI или многосекционных файлов GIF. Например, аплет может накладывать или смешивать кадры различных фильмов, рисовать поверх кадров произвольные изображения или делать надписи, масштабировать отдельные фрагменты кадров или весь кадр и так далее. Здесь все ограничивается главным образом вашей фантазией.

Так как мы уже научились выполнять все необходимые для показа видеофильма операции, перейдем сразу к исходным текстам приложения CDRotation.


Аплет CDRotation

Оглавление

Растровые изображения
Загрузка и рисование
Класс Image
Ожидание загрузки
Видео
Аплет CDRotation

Назад Вперед

В этом разделе мы расскажем об аплете CDRotation, в окне которого вращается компакт-диск.

В левом верхнем углу каждого кадра отображается его порядковый номер (рис. 1). Этот номер не нарисован в файлах кадров, а надписывается приложением после рисования очередного кадра. Такое невозможно, если располагать в документе HTML файл AVI или многосекционный файл GIF.

pic01.gif (3268 bytes)

Рис. 1. Изображение вращающегося компакт-диска в окне аплета CDRotation

Исходные тексты приложения

Главный файл исходных текстов приложения CDRotation представлен в листинге 1.

Листинг 1. Файл CDRotation.java

import java.applet.*;
import java.awt.*;
public class CDRotation extends Applet
  implements Runnable
{
  Thread m_CDRotation = null;
  private Graphics m_Graphics;
  private Image m_Images[];
  private int m_nCurrImage;
  private int m_nImgWidth  = 0;
  private int m_nImgHeight = 0;
  private boolean m_fAllLoaded = false;
  private final int NUM_IMAGES = 11;
  public String getAppletInfo()
  {
    return "Name: CDRotation";
  }
  private void displayImage(Graphics g)
  {
    if (!m_fAllLoaded)
      return;
    g.drawImage(m_Images[m_nCurrImage],
       (size().width - m_nImgWidth)   / 2,
       (size().height - m_nImgHeight) / 2,
        null);
    g.drawString(
      (new Integer(m_nCurrImage)).toString(),
      (size().width - m_nImgWidth)  /2,
     ((size().height - m_nImgHeight)/2)+
     10);
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension = size();
    g.setColor(Color.white);
    g.fillRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    if (m_fAllLoaded)
    {
      displayImage(g);
    }
    else
      g.drawString("Please, wait...",
        10, dimAppWndDimension.height / 2);
  }
  public void start()
  {
    if (m_CDRotation == null)
    {
      m_CDRotation = new Thread(this);
      m_CDRotation.start();
    }
  }
  public void stop()
  {
    if (m_CDRotation != null)
    {
      m_CDRotation.stop();
      m_CDRotation = null;
    }
  }
  public void run()
  {
    m_nCurrImage = 0;
    if (!m_fAllLoaded)
    {
      repaint();
      m_Graphics = getGraphics();
      m_Images   = new Image[NUM_IMAGES];
      MediaTracker tracker =
         new MediaTracker(this);
      String strImage;
      for (int i = 0; i < NUM_IMAGES; i++)
      {
        strImage = "images/cdimg0" +
          ((i < 10) ? "0" : "") + i + ".gif";
        m_Images[i] = getImage(
          getDocumentBase(), strImage);
        tracker.addImage(m_Images[i], 0);
      }
      try
      {
    tracker.waitForAll();
    m_fAllLoaded = !tracker.isErrorAny();
      }
      catch (InterruptedException e)
      {
      }
      if (!m_fAllLoaded)
      {
        stop();
        m_Graphics.drawString(
          "Load error", 10,
           size().height / 2);
        return;
      }
      m_nImgWidth  =
        m_Images[0].getWidth(this);
      m_nImgHeight =
         m_Images[0].getHeight(this);
    }
    repaint();
    while (true)
    {
      try
      {
        displayImage(m_Graphics);
    m_nCurrImage++;
    if(m_nCurrImage == NUM_IMAGES)
    m_nCurrImage = 0;
        Thread.sleep(30);
      }
      catch (InterruptedException e)
      {
    stop();
      }
    }
  }
}

Листинг 2 содержит исходный текст документа HTML, созданного для аплета CDRotation.

Листинг 2. Файл CDRotation.tmp.html

<applet name="CDRotation"
  code="CDRotation"
  codebase=
"file:/e:/sun/articles/vol13/src/CDRotation"
  width="500"
  height="600"
  align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
</applet>

Описание исходных текстов

Рассмотрим наиболее важные методы нашего аплета.

Метод start

В задачу метода start, который получает управление при отображении окна аплета, входит создание и запуск потока, отображающего кадры видеофильма с изображением вращающегося компакт-диска:

if (m_CDRotation == null)
{
  m_CDRotation = new Thread(this);
  m_CDRotation.start();
}

Поток создается как объект класса Thread, причем конструктору передается ссылка на главный класс аплета. Поэтому при запуске потока управление получит метод run, определенный в классе аплета.

Метод stop

Метод stop останавливает работу потока, когда окно аплета исчезает с экрана:

if(m_CDRotation != null)
{
  m_CDRotation.stop();
  m_CDRotation = null;
}

Для остановки вызывается метод stop.

Метод paint

Сразу после получения управления, метод paint закрашивает окно аплета белым цветом и рисует вокруг него черную рамку.

Затем метод проверяет содержимое флага m_fAllLoaded. Этот флаг установлен в значение true, когда все кадры видеофильма загружены и сброшен в значение false, когда загрузка кадров еще не завершена. Последняя ситуация возникает всегда при первом вызове метода paint.

Если все изображения загружены, метод paint вызывает метод displayImage, определенный в нашем приложении:

if(m_fAllLoaded)
{
  displayImage(g);
}

Этот метод, о котором мы еще расскажем подробнее, отображает в окне аплета текущий кадр видеофильма.

Если же кадры видеофильма еще не загружены, в окне аплета отображается соответствующее сообщение:

else
  g.drawString("Please, wait...",
    10, dimAppWndDimension.height / 2);

Метод run

Метод run работает в рамках отдельного потока. Он занимается последовательным рисованием кадров нашего видеофильма.

Прежде всего метод run записывает нулевое значение в поле m_nCurrImage, хранящее номер текущего отображаемого кадра:

m_nCurrImage = 0;

Далее выполняется проверка, загружены ли все кадры видеофильма, для чего анализируется содержимое флага m_fAllLoaded.

Если изображения не загружены (а в самом начале так оно и есть) метод run перерисовывает окно аплета и получает контекст отображения для этого окна. Затем создается массив объектов Image для хранения кадров видеофильма:

m_Images   = new Image[NUM_IMAGES];

Метод run создает также объект класса MediaTracker для ожидания загрузки всех кадров видеофильма:

MediaTracker tracker =
  new MediaTracker(this);

Далее метод run в цикле загружает изображения и добавляет их в объект класса MediaTracker для того чтобы можно было дождаться загрузки всех кадров:

for (int i = 0; i < NUM_IMAGES; i++)
{
  strImage = "images/cdimg0" +
   ((i < 10) ? "0" : "") + i + ".gif";
  m_Images[i] = getImage(
    getDocumentBase(), strImage);
  tracker.addImage(m_Images[i], 0);
}

Здесь предполагается, что файлы изображений находятся в каталоге images, который, в свою очередь, размещен там же, где и двоичный файл аплета.

Имена файлов, составляющих отдельные кадры, начинаются с префикса cdimg0, вслед за которым идет номер кадра (00, 01, 02, и так далее), и расширение имени .gif.

Ожидание загрузки кадров выполняется с помощью метода waitForAll, о котором мы вам уже рассказывали:

try
{
  tracker.waitForAll();
  m_fAllLoaded = !tracker.isErrorAny();
}
catch (InterruptedException e)
{
}

После окончания ожидания флаг завершения загрузки устанавливается только в том случае, если метод isErrorAny вернул значение false, то есть если не было никаких ошибок.

Если же произошла ошибка, в окне аплета отображается соответствующее сообщение, после чего работа метода run (и, следовательно, работа созданного для него потока) заканчивается:

if(!m_fAllLoaded)
{
  stop();
  m_Graphics.drawString(
    "Load error",
    10, size().height / 2);
  return;
}

В случае удачной загрузки всех кадров метод run получает ширину и высоту первого кадра видеофильма и сохраняет эти значения в переменных m_nImgWidth и m_nImgHeight:

m_nImgWidth  =
  m_Images[0].getWidth(this);
m_nImgHeight =
  m_Images[0].getHeight(this);

Далее окно аплета перерисовывается:

repaint();

При этом метод paint отображает в окне аплета первый кадр видеофильма.

На следующем этапе работы метода run запускается цикл отображения кадров фильма:

while (true)
{
  try
  {
    displayImage(m_Graphics);
    m_nCurrImage++;
    if(m_nCurrImage == NUM_IMAGES)
      m_nCurrImage = 0;
    Thread.sleep(30);
  }
  catch (InterruptedException e)
  {
    stop();
  }
}

В этом бесконечном цикле вызывается метод displayImage, рисующий текущий кадр видеофильма, после чего номер текущего кадра увеличивается на единицу. Если показаны все кадры, номер текущего кадра становится равным нулю, а затем процесс продолжается.

Между отображением кадров выполняется задержка величиной 30 миллисекунд.

Метод displayImage

Метод displayImage вызывается из двух мест - из метода paint при перерисовке окна аплета и из метода run (периодически).

Если кадры видеофильма не загружены, содержимое флага m_fAllLoaded равно false и метод displayImage просто возвращает управление, ничего не делая:

if(!m_fAllLoaded)
  return;

Если же загрузка изображений завершена, этот метод рисует в центре окна текущий кадр видеофильма, вызывая для этого знакомый вам метод drawImage:

g.drawImage(m_Images[m_nCurrImage],
  (size().width - m_nImgWidth)   / 2,
  (size().height - m_nImgHeight) / 2,
   null);

После того как кадр нарисован, мы надписываем на нем его порядковый номер, вызывая для этого метод drawString:

g.drawString((new Integer(
  m_nCurrImage)).toString(),
  (size().width - m_nImgWidth)   / 2,
  ((size().height - m_nImgHeight) / 2) + 10);


Звук в аплетах Java

Оглавление

Звук в аплетах Java
Загрузка и проигрывание
Аплет PlayClip

Назад Вперед

Нельзя сказать, что звуковые возможности аплетов Java чрезмерно велики. Скорее наоборот, они минимальны. Тем не менее, аплеты могут проигрывать звуковые клипы, записанные в файлах формата AU, который пришел из мира компьютеров фирмы Sun.

Сказанное, однако, не означает, что если у вас нет рабочей станции Sun, то вы не сможете озвучить свои аплеты. Во-первых, в сети Internet можно найти много готовых звуковых файлов AU, а во-вторых, там же есть программы для преобразования форматов звуковых файлов. Одну из таких условно-бесплатных программ, которая называется GoldWave, вы можете загрузить с сервера ftp.winsite.com.


Загрузка и проигрывание звуковых файлов

Оглавление

Звук в аплетах Java
Загрузка и проигрывание звуковых файлов
Аплет PlayClip

Назад Вперед

Работа со звуковыми файлами во многом напоминает работу с растровыми графическими файлами. Вначале вы должны получить ссылку на интерфейс AudioClip, а затем, пользуясь его методами, вы сможете выполнять проигрывание содержимого этого файла.

Для получения интерфейса AudioClip вы должны воспользоваться одним из двух вариантов метода getAudioClip, определенных в классе Applet:

public AudioClip
  getAudioClip(URL url):
public AudioClip
  getAudioClip(URL url, String name);

Первый вариант метода предполагает указание адреса URL звукового файла через единственный параметр, второй допускает раздельное указание адреса URL каталога, содержащего файл, и имени файла.

В документации на метод getAudioClip сказано, что этот метод фактически не выполняет загрузку звуковых данных, а только возвращает ссылку на интерфейс AudioClip и немедленно возвращает управление. Загрузка звуковых данных выполняется методами, предназначенными для проигрывания файла.

Однако в книге "The Java Tutorial. Object-Oriented Programming for the Internet", подготовленной специалистами группы JavaSoft, утверждается, что текущие реализации Java работают по другому: метод getAudioClip возвращает управление только после завершения загрузки звукового файла. Очевидно, вам не стоит полагаться на то, что так будет всегда. В тех случаях, когда нежелательно блокирование работы аплета на время загрузки звукового файла, загрузку и проигрывание следует выполнять в отдельном потоке.

Интерфейс AudioClip определен следующим образом:

public interface java.applet.AudioClip
{
  public abstract void play();
  public abstract void loop();
  public abstract void stop();
}

Метод play запускает однократное проигрывание звукового файла, которое выполняется от начала файла и до его конца.

Метод loop запускает проигрывание звукового файла в цикле, которое будет продолжаться до тех пор, пока вы не остановите его, вызвав метод stop.

Метод stop, как нетрудно догадаться из его названия, останавливает проигрывание звукового файла, как однократное, так и выполняемое в цикле.


Аплет PlayClip

Оглавление

Звук в аплетах Java
Загрузка и проигрывание
Аплет PlayClip

Назад

Аплет PlayClip демонстрирует использование интерфейса AudioClip. В его окне (рис. 1) имеются три кнопки с названиями Play, Loop и Stop.

pic01.gif (1685 bytes)

Рис. 1. Окно аплета PlayClip

Сразу после запуска аплета кнопка Stop находится в заблокированном состоянии. Если нажать кнопку Play или Loop, начнется, соответственно, однократное проигрывание или проигрывание в цикле файла с именем kaas.au, распложенного в том же каталоге, что и двоичный файл аплета PlayClip.

Когда начинается проигрывание звукового файла, кнопка Stop разблокируется, что позволяет остановить проигрывание.

Исходные тексты приложения

Основной файл исходных текстов приложения приведен в листинге 1.

Листинг 1. Файл PlayClip.java

import java.applet.*;
import java.awt.*;
public class PlayClip extends Applet
{
  private String m_ClipName = "kaas.au";
  private final String
    PARAM_ClipName = "ClipName";
  AudioClip auClip;
  Button btPlay;
  Button btLoop;
  Button btStop;
  boolean fLoopPlay = false;
  public String getAppletInfo()
  {
    return "Name: PlayClip";
  }
  public String[][] getParameterInfo()
  {
    String[][] info =
    {
      {
    PARAM_ClipName,
    "String",
    "Audioclip filename"
      },
    };
    return info;
  }
  public void init()
  {
    String param;
    param = getParameter(PARAM_ClipName);
    if (param != null)
      m_ClipName = param;
    btPlay = new Button("Play");
    btLoop = new Button("Loop");
    btStop = new Button("Stop");
    btStop.disable();
    add(btPlay);
    add(btLoop);
    add(btStop);
    auClip = this.getAudioClip(getCodeBase(),
      m_ClipName);
  }
  public boolean action(Event evt,
    Object obj)
  {
    Button btn;
    if(evt.target instanceof Button)
    {
      btn = (Button)evt.target;
      if(evt.target.equals(btPlay))
      {
        auClip.play();
        btStop.enable();
      }
      else if(evt.target.equals(btLoop))
      {
        auClip.loop();
        fLoopPlay = true;
        btStop.enable();
      }
      else if(evt.target.equals(btStop))
      {
        auClip.stop();
        fLoopPlay = false;
        btStop.disable();
      }
      else
      {
        return false;
      }
      return true;
    }
    return false;
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension = size();
    g.setColor(Color.yellow);
    g.fillRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
  }
  public void start()
  {
    if(fLoopPlay)
      auClip.loop();
  }
  public void stop()
  {
    if(fLoopPlay)
      auClip.stop();
  }
}

В листинге 2 вы найдете исходный текст документа HTML, созданного автоматически для нашего приложения системой Java WorkShop.

Листинг 2. Файл PlayClip.tmp.html

<applet name="PlayClip"
  code="PlayClip"
  codebase=
"file:/e:/sun/articles/vol14/src/PlayClip"
 width="200"
 height="100"
 align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
<param name="ClipName" value="kaas.au">
<hr>
If your browser recognized the applet tag,
 you would see an applet here.
<hr>
</applet>

Описание исходного текста

В главном классе аплета определено несколько полей и методов. Рассмотрим эти поля и наиболее важные методы.

Поля класса PlayClip

В поле m_ClipName хранится имя звукового файла, которое передается через параметр ClipName из документа HTML. По умолчанию для этого параметра используется значение kaas.au.

Строка PARAM_ClipName хранит имя указанного выше параметра.

Ссылка на интерфейс AudioClip хранится в поле auClip:

AudioClip auClip;

Следующие три поля хранят ссылки на кнопки, предназначенные для управления проигрыванием звукового файла:

Button btPlay;
Button btLoop;
Button btStop;

Поле fLoopPlay типа boolean используется для флага, которым отмечается режим проигрывания звукового файла в цикле.

Метод getParameterInfo

Метод getParameterInfo возвращает описание единственного параметра нашего аплета, через который передается имя звукового файла.

Метод init

Сразу после запуска аплета метод init получает значение параметра - имя звукового файла, и если этот параметр задан в документе HTML, записывает полученное имя в поле m_ClipName:

param = getParameter(PARAM_ClipName);
if(param != null)
  m_ClipName = param;

Далее создаются три кнопки, управляющие звучанием аплета:

btPlay = new Button("Play");
btLoop = new Button("Loop");
btStop = new Button("Stop");

Кнопка Stop блокируется, так как на данный момент проигрывание еще не запущено:

btStop.disable();

Для блокирования вызывается метод disable, определенный в классе Button.

Подготовленные таким образом кнопки добавляются в окно аплета:

add(btPlay);
add(btLoop);
add(btStop);

Последнее, что делает метод init перед тем как возвратить управление, это получение ссылки на интерфейс AudioClip:

auClip = this.getAudioClip(
  getCodeBase(),m_ClipName);

Адрес URL каталога, в котором расположен аплет, определяется с помощью метода getCodeBase, о котором мы говорили в предыдущей главе.

Метод action

Метод action получает управление, когда пользователь нажимает на одну из кнопок, расположенных в окне аплета. В зависимости от того, какая именно кнопка была нажата, выполняются различные действия.

Если пользователь нажал кнопку Play, вызывается метод play для запуска однократного проигрывания звукового файла:

auClip.play();
btStop.enable();

Сразу после того как проигрывание будет запущено, приложение разблокирует кнопку Stop, предоставляя пользователю возможность прервать звучание.

В том случае, когда пользователь нажал кнопку Loop, вызывается метод loop, запусчкающий проигрывание звукового файла в цикле:

auClip.loop();
fLoopPlay = true;
btStop.enable();

После запуска устанавливается флаг fLoopPlay и разблокируется кнопка Stop.

И, наконец, если пользователь нажимает кнопку Stop, выполняется остановка проигрывания методом stop:

auClip.stop();
fLoopPlay = false;
btStop.disable();

Флаг fLoopPlay сбрасывается, после чего кнопка Stop блокируется.

Метод start

Метод start получает управление при первом запуска аплета, а также когда страница документа появляется вновь после того как пользователь временно переходил к просмотру другой страницы.

Наша реализация метода start возобновляет циклическое проигрывание, если оно выполнялось, когда пользователь покинул страницу с аплетом:

if(fLoopPlay)
  auClip.loop();

Метод stop

Если пользователь запустил проигрывание звукового файла в цикле, а затем перешел к просмотру другой страницы, метод stop останавливает циклическое проигрывание:

if(fLoopPlay)
  auClip.stop();

Когда пользователь вернется к просмотру нашей страницы, метод start, описанный выше, возобновит проигрывание звукового файла.

Назад


Первое приложение Java

Оглавление

Первое приложение
Установка WorkShop
Запуск WorkShop
Текст HelloJava

Назад Вперед

Как мы уже говорили, приложение Java может выполняться под управлением специального интерпретатора, работающего в рамках отдельного процесса, либо под управлением браузера. В первом случае мы имеем дело с автономным приложением Java, в втором - с аплетом.

Изучение программирования на Java мы начнем с создания простейшего автономного приложения. В следующей статье мы расскажем о том, как сделать аплет и встроить его в документ HTML.

В качестве инструментального средства для разработки автономных приложений и аплетов Java мы будем использовать интегрированную систему разработки Java WorkShop. Она доступна для операционных систем Windows 95, Windows NT, Solaris (платформы SPARC и Intel).


Установка Java WorkShop

Оглавление

Первое приложение
Установка WorkShop
Запуск WorkShop
Текст HelloJava

Назад Вперед

Для установки Java WorkShop вы должны вставить дистрибутивный диск в устройство чтения CD-ROM. Программа установки запустится автоматически. Если этого не произошло (например, потому что автоматический запуск заблокирован), следует запустить программу установки вручную.

Если вы работаете в среде Windows 95 или Windows NT, вам нужно запустить программу setup.exe из каталога Win32. На дистрибутивном диске Java WorkShop есть также установочные каталоги для других платформ.

Процедура установки очень проста. Достаточно следовать инструкциям, которые появляются на экране. Единственное, что необходимо выбрать - это путь к каталогу, в который будут скопированы файлы Java WorkShop. При этом следует учесть, что для установки Java WorkShop в среде Windows 95 или Windows NT требуется примерно 50 Мбайт дисковой памяти.


Запуск Java WorkShop

Оглавление

Первое приложение
Установка WorkShop
Запуск WorkShop
Текст HelloJava

Назад Вперед

После завершения установки следует извлечь дистрибутивный компакт-диск и запустить Java WorkShop, воспользовавшись пиктограммой, созданной на рабочем столе операционной системы.

Когда система разработки Java WorkShop запускается впервые, она требует ввести серийный номер. Этот номер есть на вкладыше в конверте дистрибутивного компакт-диска. В том случае, если вы располагаете демонстрационной версией Java WorkShop, серийный номер можно не вводить. При этом вы сможете пользоваться Java WorkShop бесплатно в течении 30 дней.

После запуска Java WorkShop на экране появится главное окно Java WorkShop (рис.1), а также окно Java WorkShop Startup (рис. 2).

pic01sm.gif (2606 bytes) Рис. 1. Главное окно интегрированной системы разработки Java WorkShop

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

pic02sm.gif (3257 bytes) Рис. 2. Окно Java WorkShop Startup

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Если раньше вы никогда не работали с Java WorkShop, окно Java WorkShop Startup поможет вам освоиться с основными процедурами. Нажав соответствующую кнопку, вы можете создать свой первый проект, открыть пример проекта, подготовленного для вас специалистами Sun Microsystems, ознакомиться с электронным руководством или справочной системой, а также открыть любой существующий проект.

После приобретения небольшой практики можно включить переключатель Don't show me this window at startup, и окно Java WorkShop Startup перестанет появляться всякий раз, когда вы запускаете Java WorkShop.

Создание нового проекта

Как и многие другие интегрированные системы разработки программного обеспечения, система Java WorkShop использует концепцию проектов. Под проектом здесь понимается совокупность файлов и параметров, описанные в специальном файле проектов.

Всеми проектами в Java WorkShop управляет менеджер проектов. Чтобы открыть его окно, показанное на рис. 3, выберите строку Show Project Manager из меню Project главного окна Java WorkShop.

pic03.gif (4211 bytes)

Рис. 3. Окно Java WorkShop Project Manager

Все проекты хранятся в портфелях (portfolios). В процессе установки автоматически создается ваш личный портфель personal, который находится в папке My Portfolios. В этом портфеле есть проекты Checkers, CardFile, JellyBeanExample и Performance Example. При создании нового проекта мы добавим его в портфель personal, хотя в дальнейшем вы сможете создавать новые портфели.

Прежде чем выполнять какие либо операции над портфелем или проектом, нужно сделать этот портфель или проект текущим. Чтобы выбрать текущий портфель или проект, следует сделать двойной щелчок левой клавишей мыши по соответствующему названию. Строка названия будет выделена синим цветом.

Для создания проекта HelloJava выберите из меню File окна Java WorkShop Project Manager строку New, а затем в меню второго уровня строку Project. В результате будет запущен мастер создания проектов, первая диалоговая панель которого показана на рис. 4.

pic04sm.gif (2051 bytes) Рис. 4. Первая диалоговая панель мастера проектов

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

В поле Please, name this project введите название создаваемого проекта HelloJava, затем включите переключатели Standalone и No GUI. Первый из этих переключателей задает тип проекта (автономное приложение Java), второй указывает, что в проекте не используются средства автоматизированного проектирования пользовательского интерфейса.

Далее нажмите кнопку Next. Вы окажитесь в диалоговой панели Create Project, показанной на рис. 5.

pic05sm.gif (1881 bytes) Рис. 5. Диалоговая панель Create Project

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

В верхней части этой панели вы должны ввести путь к каталогу, в котором будут создаваться файлы проекта. Затем следует включить переключатель No, указывая тем самым, что каталог проектов пуст и в нем нужно создать новые файлы. Существует возможность создания проектов на базе уже имеющихся файлов, однако пока мы этого делать не будем.

Теперь все готово к созданию проекта и вы можете нажать кнопку Finish. Мастер проектов создаст все необходимые файлы и сделает новый проект текущим. Основной файл проекта окажется загруженным в главное окно приложения Java WorkShop (рис. 6). Здесь вы можете его редактировать.

pic06sm.gif (2479 bytes) Рис. 6. Основной файл созданного проекта загружен в окно приложения Java WorkShop

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Если теперь вы раскроете окно Java WorkShop Project Manager, то увидите, что в портфеле personal появился новый проект HelloJava. Раскрыв его, можно увидеть включенные в него файлы (рис. 7).

pic07.gif (5130 bytes)

Рис. 7. Файлы проекта HelloJava.

Наш проект содержит только один файл с именем HelloJava.java. Отредактируйте этот файл, добавив в него следующую строку:

System.out.println("Hello, Java!");

Новый вариант исходного текста показан на рис. 8.

pic08sm.gif (2810 bytes) Рис. 8. Измененный исходный текст приложения HelloJava

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Теперь выберите из меню Build главного окна Java WorkShop строку Build All. Через несколько мгновений исходные тексты приложения будут оттранслированы. Результат вы сможете увидеть нас странице Build блокнота, расположенного в нижней части окна Java WorkShop.

Чтобы запустить приложение, выберите из меню Project строку Run. На экране появится консоль приложения с сообщением "Hello, Java!" (рис. 9).

pic09.gif (3009 bytes)

Рис. 9. Работа приложения Hello, Java!


Исходный текст приложения HelloJava

Оглавление

Первое приложение
Установка WorkShop
Запуск WorkShop

Текст HelloJava

Назад Вперед

Исходный текст нашего первого приложения состоит всего из нескольких строк:

public class HelloJava
{
  public static void main(String args[])
  {
    System.out.println("Hello, Java!");
  }
}

По своей простоте он не уступает известной программе "Hello, world!", с которой обычно начинают изучение языка программирования C.

В нашем приложении определен один класс типа public с именем HelloJava. Заметим, что исходный файл приложения Java может содержать только один класс public, причем имя файла должно в точности совпадать с именем такого класса. В данном случае исходный файл называется HelloJava.java. Если бы вы назвали файл helloJava.java, компилятор выдал бы сообщение об ошибке.

В классе HelloJava мастером проектов автоматически создается один статический метод с именем main.

Если класс типа public с именем, совпадающем с именем файла, содержит определение метода main, то такой метод служит точкой входа автономного приложения Java. В этом он напоминает функцию main обычной программы, составленной на языке программирования C.

В качестве параметра методу main передается ссылка на массив строк класса String. Через эти строки вы можете передавать приложению Java параметры запуска.

Как наше приложение выводит текстовую строку на консоль?

В классе System определена переменная класса PrintStream с именем out. В классе PrintStream определен метод println, при помощи которого приложение HelloJava выводит сообщение "Hello, Java!" на консоль.

Но где же объект, для которого вызывается метод println? В классе System поле PrintStream определено как статическое, поэтому методы этого класса можно вызывать, не создавая объектов класса System.


Рис. 1. Главное окно интегрированной системы разработки Java WorkShop

Для возврата сделайте щелчок мышью по рисунку

pic01.gif (13937 bytes)

Рис. 1. Главное окно интегрированной системы разработки Java WorkShop


Рис. 2. Окно Java WorkShop Startup

Для возврата сделайте щелчок мышью по рисунку

pic02.gif (28888 bytes)

Рис. 2. Окно Java WorkShop Startup


Рис. 4. Первая диалоговая панель мастера проектов

Для возврата сделайте щелчок мышью по рисунку

pic04.gif (7604 bytes)

Рис. 4. Первая диалоговая панель мастера проектов


Рис.5. Диалоговая панель Create Project

Для возврата сделайте щелчок мышью по рисунку

pic05.gif (6541 bytes)

Рис.5. Диалоговая панель Create Project


Рис.6. Основной файл созданного проекта загружен в окно приложения Java WorkShop

Для возврата сделайте щелчок мышью по рисунку

pic06.gif (12070 bytes)

Рис.6. Основной файл созданного проекта загружен в окно приложения Java WorkShop


Рис. 8. Измененный исходный текст приложения HelloJava

Для возврата сделайте щелчок мышью по рисунку

pic08.gif (11184 bytes)

Рис. 8. Измененный исходный текст приложения HelloJava


Первый аплет Java

Оглавление

Первый аплет
Создание проекта
Текст аплета
Методы
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

В предыдущем разделе мы создавали автономное приложение Java, работающее под управлением виртуальной машины Java. Теперь мы создадим приложение другого типа - аплет.

Аплет Java тоже выполняется под управлением виртуальной машины Java, но встроенной в браузер. Когда браузер загружает в свое окно документ HTML с аплетом, байт-код аплета начинает свою работу.

Внешне аплет выглядит как окно заданного размера. Он может рисовать внутри этого окна (и только в нем) произвольные изображения и текст.

Двоичный файл с интерпретируемым байт-кодом Java располагается на сервере Web. В документе HTML с помощью оператора <APPLET> организуется ссылка на этот двоичный файл.

Когда пользователь загружает в браузер документ HTML с аплетом, файл аплета переписывается с сервера Web на рабочую станцию пользователя. После этого браузер начинает его выполнение.

Возможно, вам не понравится такая идея, как запуск чужого аплета на своем компьютере - мало ли чего этот аплет может там сделать. Однако аплеты, в отличие от обычных приложений Java, сильно ограничены в своих правах. Например, они не могут читать локальные файлы и тем более в них писать. Есть также ограничения и на передачу данных через сеть: аплет может обмениваться данными только с тем сервером Web, с которого он был загружен.


Создание проекта аплета

Оглавление

Первый аплет
Создание проекта аплета
Текст аплета
Методы
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Проект аплета создается таким же способом, что и проект автономного приложения Java, однако мастеру проектов необходимо указать другие параметры.

В первой диалоговой панели мастера проектов следует включить переключатели Applet и No GUI (рис. 1).

pic01sm.gif (2059 bytes) Рис. 1. Первая диалоговая панель мастера проектов

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Сделав это, нажмите кнопку Next. На экране появится вторая диалоговая панель мастера проектов. Здесь вы должны указать путь к каталогу, куда мастер проектов запишет файлы проекта, а также включить переключатель No (рис. 2).

pic02sm.gif (2059 bytes) Рис. 2. Вторая диалоговая панель мастера проектов

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

В результате мастер проектов создаст исходные тексты аплета, а также добавит новый проект в портфель personal (если этот портфель остался активным в последний раз, когда вы запускали Java WorkShop). Новый проект называется HelloApplet (рис. 3).

pic03.gif (5316 bytes)

Рис. 3. Новый проект появился в активном портфеле personal

Исходный текст аплета будет создан автоматически и загружен в окно редактирования системы Java WorkShop (рис. 4).

pic01sm.gif (2059 bytes) Рис. 4. Исходный текст аплета загружен в окно редактирования

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Вы можете оттранслировать полученный исходный текст и запустить аплет на выполнение. Он будет работать под управлением программы просмотра аплетов appletviewer, которая входит в состав Java WorkShop.

Пока в окне нашего аплета ничего нет (рис. 5), однако скоро мы исправим это положение.

pic05.gif (2199 bytes)

Рис. 5. Окно аплета, созданного автоматически мастером проектов


Исходный текст аплета

Оглавление

Первый аплет
Создание проекта
Исходный текст аплета
Методы
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Полный исходный текст аплета, созданный автоматически мастером проектов Java WorkShop, мы представили в листинге 1.

Листинг 1. Файл HelloApplet.java

import java.applet.Applet;
public class HelloApplet extends Applet {
  /**
  * Initializes the applet.  You never need to
  * call this directly; it is
  * called automatically by the system once the
  * applet is created.
  */
  public void init() {}

  /**
  * Called to start the applet.  You never need
  * to call this directly; it
  * is called when the applet's
  * document is visited.
  */
  public void start() {}

  /**
  * Called to stop the applet.  This is called
  * when the applet's document is
  * no longer on the screen.  It is guaranteed
  * to be called before destroy()
  * is called.  You never need to
  * call this method directly
  */
  public void stop() {}

  /**
  * Cleans up whatever resources are being held.
  * If the applet is active
  * it is stopped.
  */
  public void destroy() {}
}

Из-за обилия комментариев вы можете подумать, что исходный текст аплета, который ничего не делает, слишком сложный. Однако это вовсе не так. Вот что получится, если мы уберем все комментарии:

import java.applet.Applet;
public class HelloApplet extends Applet
{
  public void init()    {}
  public void start()   {}
  public void stop()    {}
  public void destroy() {}
}

Исходный текст нашего аплета начинается со строки, подключающей оператором import библиотеку классов java.applet.Applet.

Оператор import должен располагаться в файле исходного текста перед другими операторами (за исключением операторов комментария). В качестве параметра оператору import передается имя подключаемого класса из библиотеки классов. Если же необходимо подключить все классы данной библиотеки, вместо имени класса указывается символ "*".

Напомним, что библиотека java.applet.Applet содержит классы, необходимые для создания аплетов, то есть разновидности приложений Java, встраиваемых в документы HTML и работающих под управлением браузера Internet.

Еще одна библиотека классов, которая нам скоро понадобится, это java.awt. С ее помощью аплет может выполнять в своем окне рисование различных изображений или текста. Преимущества данного метода перед использованием для рисования традиционного программного интерфейса операционной системы заключаются в том, что он работает на любой компьютерной платформе.

Далее в исходном тексте аплета определяется класс типа public с именем HelloApplet. Напомним, что это имя должно обязательно совпадать с именем файла, содержащего исходный текст этого класса.

public class HelloApplet extends Applet
{
  . . .
}

Определенный нами класс HelloApplet с помощью ключевого слова extends наследуется от класса Applet. При этом методам класса HelloApplet становятся доступными все методы и данные класса, за исключением определенных как private. Класс Applet определен в библиотеке классов java.applet.Applet, которую мы подключили оператором import.


Методы в классе HelloApplet

Оглавление

Первый аплет
Создание проекта
Текст аплета
Методы в классе HelloApplet
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Создавая файл HelloApplet.java, мастер проектов системы Java WorkShop определила в классе HelloApplet несколько методов, заменив таким образом некоторые методы базового класса Applet.

Метод init

Метод init определен в базовом классе Applet, от которого наследуются все аплеты. Определение его таково, что этот метод ровным счетом ничего не делает.

Когда вызывается метод init и зачем он нужен?

Метод init вызывается тогда, когда браузер загружает в свое окно документ HTML с оператором <APPLET>, ссылающимся на данный аплет. В этот момент аплет может выполнять инициализацию, или, например, создавать потоки, если он работает в многопоточном режиме.

Существует контрпара для метода init - метод destroy. О нем мы расскажем ниже.

Метод destroy

Перед удалением аплета из памяти вызывается метод destroy, который определен в базовом классе Applet как пустая заглушка. Мастер проектов добавляет в исходный текст класса переопределение метода destroy, которое вы можете при необходимости изменить.

Методу destroy обычно поручают все необходимые операции, которые следует выполнить перед удалением аплета. Например, если в методе init вы создавали какие-либо потоки, в методе destroy их нужно завершить.

Метод start

Метод start вызывается после метода init в момент, когда пользователь начинает просматривать документ HTML с встроенным в него аплетом.

Вы можете модифицировать текст этого метода, если при каждом посещении пользователем страницы с аплетом необходимо выполнять какую-либо инициализацию.

Метод stop

Дополнением к методу start служит метод stop. Он получает управление, когда пользователь покидает страницу с аплетом и загружает в окно браузера другую страницу. Заметим, что метод stop вызывается перед методом destroy.


Исходный текст документа HTML

Оглавление

Первый аплет
Создание проекта
Текст аплета
Методы
Исходный текст документа HTML Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Кроме файла исходного текста аплета мастер проектов создал файл документа HTML HelloApplet.tmp.html, представленный в листинге 2.

Листинг 2. Файл HelloApplet.tmp.html

<applet
   name="HelloApplet"
   code="HelloApplet"
   codebase="file:/e:/sun/vol3/src/HelloApplet"
   width="500"
   height="600"
   align="Top"
   alt="If you had a java-enabled
browser, you would see an applet here.">
</applet>

С помощью оператора <applet> наш аплет встраивается в этот документ. Оператор <APPLET> используется в паре с оператором </APPLET> и имеет следующие параметры:

Параметр | Описание
ALIGN | Выравнивание окна аплета относительно окружающего его текста. Возможны следующие значения: LEFT - выравнивание влево относительно окружающего текста; CENTER - центрирование; RIGHT - выравнивание вправо относительно окружающего текста; TOP - выравнивание по верхней границе; MIDDLE - центрирование по вертикали; BOTTOM - выравнивание по нижней границе
ALT | С помощью этого параметра можно задать текст, который будет отображаться в окне аплета в том случае, если браузер не может работать с аплетами Java
CODE | Имя двоичного файла, содержащего байт-код аплета. По умолчанию путь к этому файлу указывается относительно каталога с файлом HTML, в который встроен аплет. Такое поведение может быть изменено параметром CODEBASE
CODEBASE | Базовый адрес URL аплета, то есть путь к каталогу, содержащему аплет
HEIGHT | Ширина окна аплета в пикселах
WIDTH | Высота окна аплета в пикселах
HSPACE | Зазор слева и справа от окна аплета
VSPACE | Зазор сверху и снизу от окна аплета
NAME | Идентификатор аплета, который может быть использован другими аплетами, расположенными в одном и том же документе HTML, а также сценариями JavaScript
TITLE | Строка заголовка

Дополнительно между операторами <APPLET> и </APPLET> вы можете задать параметры аплета. Для этого используется оператор <PARAM>, который мы рассмотрим позже.

В нашем случае идентификатор NAME и имя двоичного файла заданы мастером проекта как "HelloApplet":

name="HelloApplet"
code="HelloApplet"

Параметр CODEBASE задает путь к каталогу локального диска:

codebase="file:/e:/sun/vol3/src/HelloApplet"

Когда вы будете размещать документ HTML на сервере, параметр CODEBASE необходимо изменить или удалить, если этот документ и аплет располагаются в одном каталоге. В любом случае, если параметр CODEBASE задан, он должен указывать адрес URL каталога с аплетом.


Изменяем исходный текст аплета

Оглавление

Первый аплет
Создание проекта
Текст аплета
Методы
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Теперь давайте попробуем немного изменить исходный текст аплета, чтобы заставить его рисовать в своем окне текстовую строку "Hello, Java world".

Вначале измените исходный текст так, как это показано на рис. 6.

pic06sm.gif (2906 bytes) Рис. 6. Измененный исходный текст аплета HelloApplet

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Здесь мы намеренно внесли в исходный текст ошибку, чтобы показать, как Java WorkShop отреагирует на нее. Как видно из рисунка, сообщение об ошибке отображается на странице блокнота с названием Build. Текст сообщения гласит, что компилятор не смог найти определение класса Graphics, на который есть ссылка в девятой строке.

Добавим строку импортирования класса java.awt.*, как это показано в листинге 3.

Листинг 3. Файл HelloApplet.java (новый вариант)

import java.applet.Applet;
import java.awt.*;

public class HelloApplet extends Applet
{
  public String getAppletInfo()
  {
    return "HelloJava Applet";
  }
  public void paint(Graphics g)
  {
    g.drawString("Hello, Java world!",
      20, 20);
  }
}

Теперь исходный текст аплета транслируется без ошибок (рис. 7).

pic07sm.gif (2925 bytes) Рис. 7. Измененный исходный текст успешно оттранслирован

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Если запустить аплет на выполнение, в его окне будет нарисована строка "Hello, Java world" (рис. 8).

pic08.gif (2403 bytes)

Рис. 8. Теперь наш аплет "умеет" рисовать в своем окне текстовые строки


Метод paint

Оглавление

Первый аплет
Создание проекта
Текст аплета
Методы
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Наиболее интересен для нас метод paint, который выполняет рисование в окне аплета. Вот его исходный текст:

public void paint(Graphics g)
{
  g.drawString("Hello, Java world!",
  20, 20);
}

Если посмотреть определение класса Applet, которое находится в файле JavaWorkshop20\JDK\src\java\applet\Applet.java, то в нем нет метода paint. В каком же классе определен этот метод?

Иерархия классов

Заглянем в документацию. Выберите из меню Help главного окна приложения Java WorkShop строку Java API Documentation. На экране появится окно браузера, встроенного в Java WorkShop. С помощью этого браузера вы сможете просматривать содержимое справочной системы.

В разделе Java API Packages выберите библиотеку классов java.applet, а затем в разделе Class Index - строку Applet. Вы увидите иерархию классов:

java.lang.Object
|
+---java.awt.Component
    |
    +---java.awt.Container
        |
        +---java.awt.Panel
            |
            +---java.applet.Applet

Из этой иерархии видно, что класс java.applet.Applet произошел от класса java.awt.Panel. Этот класс, в свою очередь, определен в библиотеке классов java.awt и произошел от класса java.awt.Container.

Продолжим наши исследования. В классе java.awt.Container снова нет метода paint, но сам этот класс создан на базе класса java.awt.Component.

Но и здесь метода paint нет. Этот метод определен в классе java.awt.Component, который, в свою очередь, произошел от класса java.lang.Object и реализует интерфейс java.awt.image.ImageObserver.

Таким образом мы проследили иерархию классов от класса java.applet.Applet, на базе которого создан наш аплет, до класса java.lang.Object, который является базовым для всех классов в Java.

Метод paint определен в классе java.awt.Component, но так как этот класс является базовым для класса Applet и для нашего класса HelloApplet, мы можем переопределить метод paint.

Вызов метода paint

Метод paint вызывается, когда необходимо перерисовать окно аплета. Если вы создавали приложения для операционной системы Windows, то наверняка знакомы с сообщением WM_PAINT, которое поступает в функцию окна приложения при необходимости его перерисовки.

Перерисовка окна приложения Windows и окна аплета обычно выполняется асинхронно по отношению к работе приложения или аплета. В любой момент времени аплет должен быть готов перерисовать содержимое своего окна.

Такая техника отличается о той, к которой вы, возможно, привыкли, создавая обычные программы для MS-DOS. Программы MS-DOS сами определяют, когда им нужно рисовать на экране, причем рисование может выполняться из разных мест программы. Аплеты, так же как и приложения Windows, выполняют рисование в своих окнах централизованно. Аплет делает это в методе paint, а приложение Windows - при обработке сообщения WM_PAINT.

Обратите внимание, что методу paint в качестве параметра передается ссылка на объект Graphics:

public void paint(Graphics g)
{
  . . .
}

По своему смыслу этот объект напоминает контекст отображения, с которым хорошо знакомы создатели приложений Windows. Контекст отображения - это как бы холст, на котором аплет может рисовать изображение или писать текст. Многочисленные методы класса Graphics позволяют задавать различные параметры холста, такие, например, как цвет или шрифт.

Наше приложение вызывает метод drawString, который рисует текстовую строку в окне аплета:

g.drawString("Hello, Java world!", 20, 20);

Вот прототип этого метода:

public abstract void drawString(String str,
  int x, int y);

Через первый параметр методу drawString передается текстовая строка в виде объекта класса String. Второй и третий параметр определяют, соответственно, координаты точки, в которой начнется рисование строки.

В какой координатной системе?

Аплеты используют систему координат, которая соответствует режиму отображения MM_TEXT, знакомому тем, кто создавал приложения Windows. Начало этой системы координат расположено в левом верхнем углу окна аплета, ось X направлена слева направо, а ось Y - сверху вниз (рис. 9).

pic09.gif (1786 bytes)

Рис. 9. Система координат, используемая методом drawString

На этом же рисунке показано, как метод drawString нарисует текстовую строку с координатами (xCoord, yCoord).


Метод getAppletInfo

Оглавление

Первый аплет
Создание проекта
Текст аплета
Методы
Документ HTML
Новый текст
Метод paint
Метод getAppletInfo

Назад Вперед

Базовый класс Applet содержит определение метода getAppletInfo, возвращающее значение null. В нашем классе HelloApplet, который является дочерним по отношению к классу Applet, мы переопределили метод getAppletInfo из базового класса следующим образом:

public String getAppletInfo()
{
  return "HelloJava Applet";
}

Теперь метод getAppletInfo возвращает текстовую информацию об аплете в виде объекта класса String.

Этой информацией могут воспользоваться другие аплеты или сценарии JavaScript, например, для определения возможности взаимодействия с аплетом.


Рис. 1. Первая диалоговая панель мастера проектов

Для возврата сделайте щелчок мышью по рисунку

pic01.gif (7641 bytes)

Рис. 1. Первая диалоговая панель мастера проектов


Рис. 2. Вторая диалоговая панель мастера проектов

Для возврата сделайте щелчок мышью по рисунку

pic02.gif (6454 bytes)

Рис. 2. Вторая диалоговая панель мастера проектов


Рис. 4. Исходный текст аплета загружен в окно редактирования

Для возврата сделайте щелчок мышью по рисунку

pic04.gif (13006 bytes)

Рис. 4. Исходный текст аплета загружен в окно редактирования


Рис. 6. Измененный исходный текст аплета HelloApplet

Для возврата сделайте щелчок мышью по рисунку

pic06.gif (16818 bytes)

Рис. 6. Измененный исходный текст аплета HelloApplet


Рис. 7. Измененный исходный текст успешно оттранслирован

Для возврата сделайте щелчок мышью по рисунку

pic07.gif (16365 bytes)

Рис. 7. Измененный исходный текст успешно оттранслирован


Рисование в окне аплета

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Рисование в окне аплета

Назад Вперед

В предыдущем разделе мы привели простейший пример аплета, который выполняет рисование текстовой строки в своем окне. Теперь мы расскажем вам о том, что и как может рисовать аплет.

Способ, которым аплет выполняет рисование в своем окне, полностью отличается от того, которым пользуются программы MS-DOS. Вместо того чтобы обращаться напрямую или через драйвер к регистрам видеоконтроллера, аплет пользуется методами из класса Graphics. Эти методы инкапсулируют все особенности аппаратуры, предоставляя в распоряжение программиста средство рисования, которое пригодно для любой компьютерной платформы.

Для окна аплета создается объект класса Graphics, ссылка на который передается методу paint. Раньше мы уже пользовались этим объектом, вызывая для него метод drawString, рисующий в окне текстовую строку. Объект, ссылка на который передается методу paint, и есть контекст отображения. Сейчас мы займемся контекстом отображения вплотную.


Контекст отображения

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Назад Вперед

Проще всего представить себе контекст отображения как полотно, на котором рисует художник. Точно так же как художник может выбирать для рисования различные инструменты, программист, создающий аплет Java, может выбирать различные методы класса Graphics и задавать различные атрибуты контекста отображения.

Методы класса Graphics

В качестве базового для класса Graphics (полное название класса java.awt.Graphics) выступает класс java.lang.Object.

Прежде всего мы приведем прототипы конструктора этого класса и его методов с краткими комментариями. Полное описание вы сможете найти в электронной документации, которая входит в комплект Java WorkShop.

Далее мы рассмотрим назначение основных методов, сгруппировав их по выполняемым функциям.

Конструктор

  • Graphics
protected Graphics();

Методы

  • clearRect

Стирание содержимого прямоугольной области

public abstract void clearRect(int x, int y,
  int width, int height);
  • clipRect

Задание области ограничения вывода

public abstract void clipRect(int x, int y,
  int width, int height);
  • copyArea

Копирование содержимого прямоугольной области

public abstract void copyArea(int x, int y,
  int width, int height, int dx, int dy);
  • create

Создание контекста отображения

public abstract Graphics create();

public Graphics create(int x, int y,
  int width, int height);
  • dispose

Удаление контекста отображения

public abstract void dispose();
  • draw3DRect

Рисование прямоугольной области с трехмерным выделением

public void draw3DRect(int x, int y,
  int width, int height, boolean raised);
  • drawArc

Рисование сегмента

public abstract void drawArc(int x, int y,
  int width, int height,
  int startAngle, int arcAngle);

Рисование сегмента

  • drawBytes

Рисование текста из массива байт

public void drawBytes(byte data[],
  int offset, int length, int x, int y);
  • drawChars

Рисование текста из массива символов

public void drawChars(char  data[],
  int offset, int length, int x, int y);
  • drawImage

Рисование растрового изображения

public abstract boolean	drawImage(Image img,
  int x, int y,
  Color bgcolor, ImageObserver observer);
public abstract boolean	drawImage(Image img,
  int x, int y,
  ImageObserver observer);

public abstract boolean	drawImage(Image img,
  int x, int y,
  int width, int height, Color bgcolor,
  ImageObserver observer);

public abstract boolean	drawImage(Image img,
  int x, int y,
  int width, int height,
  ImageObserver observer);
  • drawLine

Рисование линии

public abstract void drawLine(int x1, int y1,
  int x2, int y2);
  • drawOval

Рисование овала

public abstract void drawOval(int x, int y,
  int width, int height);
  • drawPolygon

Рисование многоугольника

public abstract void drawPolygon(
  int xPoints[],
  int yPoints[], int nPoints);

public void drawPolygon(Polygon p);
  • drawRect

Рисование прямоугольника

public void drawRect(int x, int y,
  int width, int height);
  • drawRoundRect

Рисование прямоугольника с круглыми углами

public abstract void drawRoundRect(
  int x, int y,
  int width, int height,
  int arcWidth, int arcHeight);
  • drawString

Рисование текстовой строки

public abstract void drawString(String str,
 int x, int y);
  • fill3DRect

Рисование заполненного прямоугольника с трехмерным выделением

public void fill3DRect(int x, int y,
  int width, int height, boolean raised);
  • fillArc

Рисование заполненного сегмента круга

public abstract void fillArc(int x,
    int y, int width,
    int height, int startAngle,
    int arcAngle);
  • fillOval

Рисование заполненного овала

public abstract void fillOval(int x, int y,
  int width, int height);
  • fillPolygon

Рисование заполненного многоугольника

public abstract void fillPolygon(
  int xPoints[],
  int yPoints[], int nPoints);
  • fillPolygon

Рисование заполненного многоугольника

public void fillPolygon(Polygon p);

public abstract void fillRect(int x, int y,
  int width, int height);
  • fillRoundRect

Рисование заполненного прямоугольника с круглыми углами

public abstract void fillRoundRect(
  int x, int y,
  int width, int height,
  int arcWidth, int arcHeight);
  • finalize

Прослеживание вызова метода dispose

public void finalize();
  • getClipRect

Определение границ области ограничения вывода

public abstract Rectangle getClipRect();
  • getColor

Определение цвета, выбранного в контекст отображения

public abstract Color getColor();
  • getFont

Определение шрифта, выбранного в контекст отображения

public abstract Font getFont();
  • getFontMetrics

Определение метрик текущего шрифта

public FontMetrics getFontMetrics();
  • getFontMetrics

Определение метрик заданного шрифта

public abstract FontMetrics
  getFontMetrics(Font f);
  • setColor

Установка цвета для рисования в контексте отображения

public abstract void setColor(Color c);
  • setFont

Установка текущего шрифта в контексте отображения

public abstract void setFont(Font font);
  • setPaintMode

Установка режима рисования

Метод setPaintMode устанавливает в контексте отображения режим рисования, при котором выполняется замещение изображения текущим цветом, установленном в контексте отображения.

public abstract void setPaintMode();
  • setXORMode

Установка маски для рисования

Задавая маску для рисования при помощи метода setXORMode, вы можете выполнить при рисовании замещение текущего цвета на цвет, указанный в параметре метода, и наоборот, цвета, указанного в параметре метода, на текущий.

Все остальные цвета изменяются непредсказуемым образом, однако эта операция обратима, если вы нарисуете ту же самую фигуру два раза на одном и том же месте.

public abstract void setXORMode(Color c1);
  • translate

Сдвиг начала системы координат

Метод translate сдвигает начало системы координат в контексте отображения таким образом, что оно перемещается в точку с координатами (x, y), заданными через параметры метода:

public abstract void translate(int x, int y);
  • toString

Получение текстовой строки, представляющей данный контекст отображения

public String toString();


Установка атрибутов контекста отображения

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Назад Вперед

Изменяя атрибуты контекста отображения, приложение Java может установить цвет для рисования графических изображений, таких как линии и многоугольники, шрифт для рисования текста, режим рисования и маску. Возможен также сдвиг начала системы координат.

Выбор цвета

Изменение цвета, выбранного в контекст отображения, выполняется достаточно часто. В классе Graphics для изменения цвета определен метод setColor, прототип которого представлен ниже:

public abstract void setColor(Color c);

В качестве параметра методу setColor передается ссылка на объект класса Color, с помощью которого можно выбрать тот или иной цвет.

Как задается цвет?

Для этого можно использовать несколько способов.

Прежде всего, вам доступны статические объекты, определяющие фиксированный набор основных цветов:

Объект | Цвет
public final static Color black; | черный
public final static Color blue; | голубой
public final static Color cyan; | циан
public final static Color darkGray; | темно-серый
public final static Color gray; | серый
public final static Color green; | зеленый
public final static Color lightGray; | светло-серый
public final static Color magenta; | малиновый
public final static Color orange; | оранжевый
public final static Color pink; | розовый
public final static Color red; | красный
public final static Color white; | белый
public final static Color yellow; | желтый

Этим набором цветов пользоваться очень просто:

public void paint(Graphics g)
{
  g.setColor(Color.yellow);
  g.drawString("Hello, Java world!",
   10, 20);
  . . .
}

Здесь мы привели фрагмент исходного текста метода paint, в котором в контексте отображения устанавливается желтый цвет. После этого метод drawString выведет текстовую строку " Hello, Java world!" желтым цветом.

Если необходима более точная установка цвета, вы можете воспользоваться одним из трех конструкторов объекта Color:

public Color(float r, float g, float b);
public Color(int r, int g, int b);
public Color(int rgb);

Первые два конструктора позволяют задавать цвет в виде совокупности значений трех основных цветовых компонент - красной, желтой и голубой (соответственно, параметры r, g и b). Для первого конструктора диапазон возможных значений компонент цвета находится в диапазоне от 0.0 до 1.0, а для второго - в диапазоне от 0 до 255.

Третий конструктор также позволяет задавать отдельные компоненты цвета, однако они должны быть скомбинированы в одной переменной типа int. Голубая компонента занимает биты от 0 до 7, зеленая - от 8 до 15, красная - от 16 до 23.

Ниже мы привели пример выбора цвета с помощью конструктора, передав ему три целочисленных значения цветовых компонент:

g.setColor(new Color(0, 128, 128));

В классе Color определено еще несколько методов, которые могут оказаться вам полезными:

Метод | Описание
public Color brighter (); | Установка более светлого варианта того же цвета
public Color darker (); | Установка более темного варианта того же цвета
public boolean equals (Object obj); | Проверка равенства цветов текущего объекта и объекта, заданного параметром
public int getBlue (); | Определение голубой компоненты цвета (в диапазоне от 0 до 255)
public int getRed (); | Определение красной компоненты цвета (в диапазоне от 0 до 255)
public int getGreen (); | Определение зеленой компоненты цвета (в диапазоне от 0 до 255)
getHSBColor (float h, float s, float b); | Определение компонент оттенка, насыщенности и яркости (схема HSB)
public int getRGB (); | Определение компонент RGB для цвета, выбранного в контекст отображения
public static int HSBtoRGB (float hue, float saturation, float brightness); | Преобразование цветового представления из схемы HSB в схему RGB
public static float[] RGBtoHSB (int r, int g, int b, float hsbvals[]); | Преобразование, обратное выполняемому предыдущей функцией
public String toString (); | Получение текстовой строки названия цвета

Второй способ установки цвето фона и изображения заключается в вызове методов setBackground и setForeground, например:

setBackground(Color.yellow);
setForeground(Color.black);

Здесь мы устанавливаем для окна аплета желтый цвет фона и черный цвет изображения.

Выбор шрифта

С помощью метода setFont из класса Graphics вы можете выбрать в контекст отображения шрифт, который будет использоваться методами drawString, drawBytes и drawChars для рисования текста. Вот прототип метода setFont:

public abstract void setFont(Font font);

В качестве параметра методу setFont следует передать объект класса Font.

Класс Font

Приведем краткое перечисление полей, конструкторов и методов этого класса.

Поля класса

  • name
protected String name;
  • size
protected int size;
  • style
protected int style;

Битовые маски стиля шрифта

  • BOLD
public final static int BOLD;
  • ITALIC
public final static int ITALIC;
  • PLAIN
public final static int PLAIN;

Конструкторы

public Font(String name,
  int style, int size);

Методы

  • equals

Сравнение шрифтов

public boolean equals(Object obj);
  • getFamily

Определение названия семейства шрифтов

public String getFamily();
  • getFont

Получение шрифта по его характеристикам

public static Font getFont(String  nm);
public static Font getFont(String  nm,
  Font  font);
  • getName

Определение названия шрифта

public String getName();
  • getSize

Определение размера шрифта

public int getSize();
  • getStyle

Определение стиля шрифта

public int getStyle();
  • hashCode

Получение хэш-кода шрифта

public int hashCode();
  • isBold

Определение жирности шрифта

public boolean isBold();
  • isItalic

Проверка, является ли шрифт наклонным

public boolean isItalic();
  • isPlain

Проверка, есть ли шрифтовое выделение

public boolean isPlain();
  • toString

Получение текстовой строки для объекта

public String toString();

Создавая шрифт конструктором Font, вы должны указать имя, стиль и размер шрифта.

В качестве имени можно указать, например, такие строки как Helvetica или Courier. Учтите, что в системе удаленного пользователя, загрузившего ваш аплет, может не найтись шрифта с указанным вами именем. В этом случае браузер заменит его на наиболее подходящий (с его точки зрения).

Стиль шрифта задается масками BOLD, ITALIC и PLAIN, которые можно комбинировать при помощи логической операции "ИЛИ":

Маска | Описание
BOLD | Утолщенный шрифт
ITALIC | Наклонный шрифт
PLAIN | Шрифтовое выделение не используется

Что же касается размера шрифта, то он указывается в пикселах.


Определение атрибутов контекста отображения

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Назад Вперед

Ряд методов класса Graphics позволяет определить различные атрибуты контекста отображения, например, цвет, выбранный в контекст отображения или метрики текущего шрифта, которым выполняется рисование текста.

Рассмотрим методы, позволяющие определить атрибуты контекста отображения.

Определение границ области ограничения вывода

С помощью метода clipRect, о котором мы расскажем чуть позже, вы можете определить в окне аплета область ограничения вывода прямоугольной формы. Вне этой области рисование графических изображений и текста не выполняется.

Метод getClipRect позволяет вам определить координаты текущей области ограничения, заданной в контексте отображения:

public abstract Rectangle getClipRect();

Метод возвращает ссылку на объект класса Rectangle, который, в частности, имеет поля класса с именами x, y, height и width. В этих полях находится, соответственно, координаты верхнего левого угла, высота и ширина прямоугольной области.

Определение цвета, выбранного в контекст отображения

Метод getColor возвращает ссылку на объект класса Color, представляющий текущий цвет, выбранный в контекст отображения:

public abstract Color getColor();

Определение шрифта, выбранного в контекст отображения

С помощью метода getFont, возвращающего ссылку на объект класса Font, вы можете определить текущий шрифт, выбранный в контекст отображения:

public abstract Font getFont();

Определение метрик текущего шрифта

Несмотря на то что вы можете заказать шрифт с заданным именем и размером, не следует надеяться, что навигатор выделит вам именно такой шрифт, какой вы попросите. Для правильного размещения текста и других изображений в окне аплета вам необходимо знать метрики реального шрифта, выбранного навигатором в контекст отображения.

Метрики текущего шрифта в контексте отображения вы можете узнать при помощи метода getFontMetrics, прототип которого приведен ниже:

public FontMetrics getFontMetrics();

Метод getFontMetrics возвращает ссылку на объект класса FontMetrics. Ниже мы привели список наиболее важных методов этого класса, предназначенных для получения отдельных параметров шрифта:

Метод | Описание
public Font getFont(); | Определение шрифта, который описывается данной метрикой
public int bytesWidth(byte data[], int off, int len); | Метод возвращает ширину строки символов, расположенных в массиве байт data. Параметры off и len задают, соответственно, смещение начала строки в массиве и ее длину
public int charsWidth(char data[], int off, int len); | Метод возвращает ширину строки символов, расположенных в массиве символов data. Параметры off и len задают, соответственно, смещение начала строки в массиве и ее длину
public int charWidth(char ch); | Метод возвращает ширину заданного символа
public int charWidth(int ch); | Метод возвращает ширину заданной строки символов
public int getAscent(); | Определение расстояния от базовой линии до верхней выступающей части символов
public int getDescent(); | Определение расстояния от базовой линии до нижней выступающей части символов
public int getLeading(); | Расстояние между строками текста
public int getHeight(); | Определение полной высоты символов, выполняется по формуле: getLeading() + getAscent() + getDescent()
public int getMaxAdvance(); | Максимальная ширина символов в шрифте
public int getMaxAscent(); | Максимальное расстояние от базовой линии до верхней выступающей части символов для символов данного шрифта
public int getMaxDescent(); | Максимальное расстояние от базовой линии до нижней выступающей части символов для символов данного шрифта
public int[] getWidths(); | Массив, в котором хранятся значения ширины первых 256 символов в шрифте
public int stringWidth(String str); | Ширина строки, передаваемой методу в качестве параметра
public String toString(); | Текстовая строка, представляющая данную метрику шрифта

Обратите внимание на метод stringWidth, позволяющий определить ширину текстовой строки. Заметим, что без этого метода определение ширины текстовой строки было бы непростой задачей, особенно если шрифт имеет переменную ширину символов.

Для определения полной высоты строки символов вы можете воспользоваться методом getHeight.

Определение метрик заданного шрифта

Метод getFontMetrics с параметром типа Font позволяет определить метрики любого шрифта, передаваемого ему в качестве параметра:

public abstract FontMetrics
  getFontMetrics(Font f);

В отличие от нее метод getFontMetrics без параметров возвращает метрики текущего шрифта, выбранного в контекст отображения.


Рисование геометрических фигур

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Назад Вперед

В этом разделе мы опишем методы класса Graphics, предназначенные для рисования элементарных геометрических фигур, таких как линии, прямоугольники, окружности и так далее.

Линии

Для того чтобы нарисовать прямую тонкую сплошную линию, вы можете воспользоваться методом drawLine, прототип которого приведен ниже:

public abstract void drawLine(int x1,
  int y1,int x2, int y2);

Концы линии имеют координаты (x1, y1) и (x2, y2), как это показано на рис. 1.

pic01.gif (2176 bytes)

Рис. 1. Рисование прямой линии

К сожалению, в контексте отображения не предусмотрены никакие атрибуты, позволяющие нарисовать пунктирную линию или линию увеличенной толщины.

Прямоугольники и квадраты

Среди методов класса Graphics есть несколько, предназначенных для рисования прямоугольников. Первый из них, с именем drawRect, позволяет нарисовать прямоугольник, заданный координатами своего левого верхнего угла, шириной и высотой:

public void drawRect(int x, int y,
  int width, int height);

Параметры x и y задают, соответственно, координаты верхнего левого угла, а параметры width и height - высоту и ширину прямоугольника (рис. 2).

pic02.gif (2472 bytes)

Рис. 2. Рисование прямоугольника

В отличие от метода drawRect, рисующего только прямоугольную рамку, метод fillRect рисует заполненный прямоугольник. Для рисования и заполнения прямоугольника используется цвет, выбранный в контекст отображения (рис. 3).

pic03.gif (3126 bytes)

Рис. 3. Рисование заполненного прямоугольника

Прототип метода fillRect приведен ниже:

public abstract void fillRect(int x, int y,
  int width, int height);

Метод drawRoundRect позволяет нарисовать прямоугольник с закругленными углами:

public abstract void drawRoundRect(int x,
  int y, int width,
  int height, int arcWidth, int arcHeight);

Параметры x и y определяют координаты верхнего левого угла прямоугольника, параметры width и height задают, соответственно его ширину и высоту.

Размеры эллипса, образующего закругления по углам, вы можете задать с помощью параметров arcWidth и arcHeight. Первый из них задает ширину эллипса, а второй - высоту (рис. 4).

pic04.gif (3197 bytes)

Рис. 4. Рисование прямоугольника с закругленными углами

Метод fillRoundRect позволяет нарисовать заполненный прямоугольник с закругленными углами (рис. 5).

pic05.gif (3887 bytes)

Рис. 5. Рисование заполненного прямоугольника с закругленными углами

Назначение параметров этого метода аналогично назначению параметров только что рассмотренного метода drawRoundRect:

public abstract void fillRoundRect(int x,
 int y,
 int width, int height,
 int arcWidth, int arcHeight);

Метод fill3Drect предназначен для рисования выступающего или западающего прямоугольника:

public void fill3DRect(int x, int y,
  int width, int height, boolean raised);

Если значение параметра raised равно true, рисуется выступающий прямоугольник, если false - западающий. Назначение остальных параметров аналогично назначению параметров метода drawRect.

Многоугольники

Для рисования многоугольников в классе Graphics предусмотрено четыре метода, два из которых рисуют незаполненные многоугольники, а два - заполненные.

Первый метод рисует незаполненный многоугольник, заданный массивами координат по осям X и Y:

public abstract void drawPolygon(
  int xPoints[],
  int yPoints[], int nPoints);

Через параметры xPoints и yPoints передаются, соответственно, ссылки на массивы координат по оис X и Y. Параметр nPoints задает количество точек в массивах.

На рис. 6 показан многоугольник, нарисованный методом drawPolygon.

pic06.gif (2719 bytes)

Рис. 6. Многоугольник, нарисованный методом drawPolygon

В этом многоугольнике шесть вершин с координатами от (x0, y0) до (x5, y5), причем для того чтобы он стал замкнутым, координаты первой и последней вершины совпадают.

Второй метод также рисует незаполненный многоугольник, однако в качестве параметра методу передается ссылка на объект Polygon:

public void drawPolygon(Polygon p);

Класс Polygon достаточно прост. Приведем описание его полей, конструкторов и методов:

Поля класса

  • npoints

Количество вершин

public int npoints;
  • xpoints

Массив координат по оси X

public int xpoints[];
  • ypoints

Массив координат по оси Y

public int ypoints[]

Конструкторы

public Polygon ();
public Polygon(int xpoints[],
  int ypoints[], int npoints);

Методы

  • addPoint

Добавление вершины

public void addPoint(int x, int y);
  • getBoundingBox

Получение координат охватывающего прямоугольника

public Rectangle getBoundingBox();
  • inside

Проверка, находится ли точка внутри многоугольника

public boolean inside(int x, int y);

Ниже мы показали фрагмент кода, в котором создается многоугольник, а затем в него добавляется несколько точек. Многоугольник рисуется методом drawPolygon:

Polygon p = new Polygon();
p.addPoint(270, 239);
p.addPoint(350, 230);
p.addPoint(360, 180);
p.addPoint(390, 160);
p.addPoint(340, 130);
p.addPoint(270, 239);
g.drawPolygon(p);

Если вам нужно нарисовать заполненный многоугольник (рис. 7), то для этого вы можете воспользоваться методами, приведенными ниже:

public abstract void fillPolygon(
 int xPoints[],
 int yPoints[], int nPoints);

public void fillPolygon(Polygon p);

Первый из этих методов рисует многоугольник, координаты вершин которого заданы в массивах, второй - получая объект класса Polygon в качестве параметра.

pic07.gif (3128 bytes)

Рис. 7. Многоугольник, нарисованный методом fillPolygon

Овалы и круги

Для рисования окружностей и овалов вы можете воспользоваться методом drawOval:

public abstract void drawOval(
  int x, int y,
  int width, int height);

Параметры этого методы задают координаты и размеры прямоугольника, в который вписывается рисуемый овал (рис. 8).

pic08.gif (2591 bytes)

Рис. 8. Рисование овала

Метод fillOval предназначен для рисования заполненного овала (рис. 9). Назначение его параметров аналогично назначению параметров метода drawOval:

public abstract void fillOval(
  int x, int y,
  int width, int height);

pic09.gif (3122 bytes)

Рис. 9. Рисование заполненного овала

Сегменты

Метод drawArc предназначен для рисования незаполненного сегмента (рис. 10). Прототип этого метода приведен ниже:

public abstract void drawArc(
 int x, int y,
 int width, int height,
 int startAngle, int arcAngle);

pic10.gif (3409 bytes)

Рис. 10. Рисование незаполненного сегмента

Параметры x, y, width и height задают координаты прямоугольника, в который вписан сегмент.

Параметры startAngle и arcAngle задаются в градусах. Они определяют, соответственно, начальный угол и угол разворота сегмента.

Для того чтобы нарисовать заполненный сегмент, вы можете воспользоваться методом fillArc:

public abstract void fillArc(int x, int y,
  int width, int height,
  int startAngle, int arcAngle);

Задание области ограничения

Если для окна аплета задать область ограничения, то рисование будет возможно только в пределах этой области. Область ограничения задается методом clipRect, прототип которого мы привели ниже:

public abstract void clipRect(
 int x, int y,
 int width, int height);

Параметры x, y, width и height задают координаты прямоугольной области ограничения.

Копирование содержимого прямоугольной области

Метод copyArea позволяет скопировать содержимое любой прямоугольной области окна аплета:

public abstract void copyArea(
 int x, int y,
 int width, int height, int dx, int dy);

Параметры x, y, width и height задают координаты копируемой прямоугольной области. Область копируется в другую прямоугольную область такого же размера, причем параметры dx и dy определяют координаты последней.


Аплет Draw

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Назад Вперед

В этом разделе мы приведем исходные тексты аплета Draw, в которых демонстрируется использование различных функций рисования.

На рис. 11 показано окно этого аплета.

pic11.gif (4305 bytes)

Рис. 11. Окно аплета Draw

В верхней части окна мы вывели список вех шрифтов, доступных аплету, а также примеры оформления строки Test string с использованием этих шрифтов.

В нижней части окна нарисовано несколько геометрических фигур.


Исходные тексты аплета Draw

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты

Проект аплета
Назад Вперед

Исходные тексты аплета Draw вы найдете в листинге 1.

Листинг 1. Файл draw.java

import java.applet.*;
import java.awt.*;

public class draw extends Applet
{
  Toolkit tk;
  String szFontList[];
  FontMetrics fm;
  int yStart = 20;
  int yStep;
  String parm_TestString;

  public void init()
  {
    tk = Toolkit.getDefaultToolkit();
    szFontList = tk.getFontList();
    parm_TestString =
      getParameter("TestString");
  }

  public String getAppletInfo()
  {
    return "Name: draw";
  }

  public void paint(Graphics g)
  {
    int yDraw;
    Dimension dimAppWndDimension = getSize();

    g.clearRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);

    g.setColor(Color.yellow);
    g.fillRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);

    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);

    fm = g.getFontMetrics();
    yStep = fm.getHeight();

    for(int i = 0; i < szFontList.length; i++)
    {
      g.setFont(new Font("Helvetica",
        Font.PLAIN, 12));
      g.drawString(szFontList[i], 10,
        yStart + yStep * i);

      fm = g.getFontMetrics();
      yStep = fm.getHeight();

      g.setFont(new Font(szFontList[i],
        Font.PLAIN, 12));
      g.drawString(parm_TestString,
        100, yStart + yStep * i);
    }

    yDraw = yStart + yStep * szFontList.length
      + yStep;

    Polygon p = new Polygon();

    p.addPoint(70, yDraw);
    p.addPoint(150, yDraw + 30);
    p.addPoint(160, yDraw + 80);
    p.addPoint(190, yDraw + 60);
    p.addPoint(140, yDraw + 30);
    p.addPoint(70, yDraw + 39);

    g.drawPolygon(p);

    g.setColor(Color.red);
    g.drawRect(10, yDraw + 85, 200, 100);

    g.setColor(Color.black);
    g.drawArc(10, yDraw + 85,
      200, 100, -50, 320);
  }

  public String[][] getParameterInfo()
  {
    String[][] info =
    {
      {
    "TestString", "String", "Test string"
      }
    };
    return info;
  }
}

Метод init

При инициализации аплета метод init извлекает список доступных шрифтов и принимает значение параметра TestString, передаваемое аплету в документе HTML.

Извлечение списка шрифтов

Процедура извлечения списка доступных шрифтов достаточно проста и выполняется следующим образом:

Toolkit tk;
String szFontList[];
  . . .
tk = Toolkit.getDefaultToolkit();
szFontList = tk.getFontList();

Аплет вызывает статический метод getDefaultToolkit из класса Toolkit и затем, пользуясь полученной ссылкой, извлекает список шрифтов, записывая его в массив szFontList.

Для чего еще можно использовать класс Toolkit?

Класс Toolkit является абстрактным суперклассом для всех реализаций AWT. Порожденные от него классы используются для привязки различных компонент конкретных реализаций.

Создавая свои аплеты, вы будете редко прибегать к услугам этого класса. Однако в нем есть несколько полезных методов, прототипы которых мы перечислим ниже:

  • getDefaultToolkit

Получение ссылки на Toolkit

public static Toolkit getDefaultToolkit();
  • getColorModel

Определение текущей цветовой модели, выбранной в контекст отображения

public abstract ColorModel getColorModel();
  • getFontList

Получение списка шрифтов, доступных аплету

public abstract String[] getFontList();
  • getFontMetrics

Получение метрик заданного шрифта

public abstract FontMetrics
  getFontMetrics(Font font);
  • getImage

Получение растрового изображения по имени файла

public abstract Image
  getImage(String filename);
  • getImage

Получение растрового изображения по адресу URL

public abstract Image getImage(URL  url);
  • getScreenResolution

Определение разрешения экрана в точках на дюйм

public abstract int getScreenResolution();
  • getScreenSize

Размеры экрана в пикселах

public abstract Dimension getScreenSize();
  • prepareImage

Подготовка растрового изображения для вывода

public abstract boolean prepareImage(
  Image image,
  int width, int height,
  ImageObserver observer);
  • sync

Синхронизация состояния Toolkit

public abstract void sync();

Наиболее интересны, с нашей точки зрения, методы getFontList, getScreenResolution и getScreenSize, с помощью которых аплет может, соответственно, получить список шрифтов, определить разрешение и размер экрана. Последние два параметра позволяют сформировать содержимое окна аплета оптимальным образом исходя из объема информации, который может в нем разместиться.

Получение значения параметров

До сих пор наши аплеты не получали параметров из документов HTML, в которые мы их встраивали.

Конечно, все константы, текстовые строки, адреса URL и другую информацию можно закодировать непосредственно в исходном тексте аплета, однако, очевидно, это очень неудобно.

Пользуясь операторами <PARAM>, расположенными в документе HTML сразу после оператора <APPLET>, можно передать аплету произвольное количество параметров, например, в виде текстовых строк:

<applet
    code=MyApplet.class
    id=MyApplet
     . . .
    width=320
    height=240 >
    <param name=ParamName1 value="Value 1">
    <param name=ParamName2 value="Value 2">
    <param name=ParamName3 value="Value 3">
    <param name=ParamName4 value="Value 4">
     . . .
</applet>

Здесь через параметр NAME оператора <PARAM> передается имя параметра аплета, а через параметр VALUE - значение соответствующего параметра.

Как параметр может получить значение параметров?

Для получения значения любого параметра аплет должен использовать метод getParameter. В качестве единственного параметра этому методу передается имя параметра аплета в виде строки типа String, например:

parm_TestString = getParameter("TestString");

Обычно в аплете также определяется метод getParameterInfo, возвращающий информацию о параметрах. Вот исходный текст этого метода для нашего аплета Draw:

public String[][] getParameterInfo()
{
  String[][] info =
  {
    {
      "TestString", "String", "Test string"
    }
  };
  return info;
}

Метод getParameterInfo возвращает массив массивов текстовых строк. Первая строка указывает имя параметра, вторая - тип передаваемых через него данных, а третья - значение параметра по умолчанию.

Метод paint

Первым делом метод paint определяет размеры окна аплета, вызывая для этого метод getSize:

Dimension dimAppWndDimension = getSize();

Метод getSize возвращает ссылку на объект класса Dimension, хранящий высоту и ширину объекта:

  • height

Высота

public int height;
  • width

Ширина

public int width;

В классе Dimension предусмотрено три конструктора и один метод:

public Dimension();
public Dimension(Dimension  d);
public Dimension(int width, int height);
  • toString

Получение строки, представляющей класс

public String toString();

После определения размеров окна аплета метод paint стирает содержимое всего окна:

g.clearRect(0, 0,
  dimAppWndDimension.width  - 1,
  dimAppWndDimension.height - 1);

Далее в контексте отображения устанавливается желтый цвет:

g.setColor(Color.yellow);

Этим цветом заполняется внутренняя область окна аплета, для чего применяется метод fillRect:

g.fillRect(0, 0,
  dimAppWndDimension.width  - 1,
  dimAppWndDimension.height - 1);

Затем метод paint устанавливает в контексте отображения черный цвет и рисует тонкую черную рамку вокруг окна аплета:

g.setColor(Color.black);
g.drawRect(0, 0,
  dimAppWndDimension.width  - 1,
  dimAppWndDimension.height - 1);

На следующем этапе мы получаем метрики текущего шрифта, выбранного в контекст отображения:

fm = g.getFontMetrics();

Пользуясь этими метриками, мы определяем высоту символов текущего шрифта и записываем ее в поле yStep:

yStep = fm.getHeight();

После этого метод paint запускает цикл по всем шрифтам, установленным в системе:

for(int i = 0; i < szFontList.length; i++)
{
  . . .
}

Количество доступных шрифтов равно размеру массива szFontList, которое вычисляется как szFontList.length.

Метод paint выписывает в окне аплета название каждого шрифта, устанавливая для этого шрифт Helvetica размером 12 пикселов:

g.setFont(new Font("Helvetica",
  Font.PLAIN, 12));
g.drawString(szFontList[i],
  10, yStart + yStep * i);

Смещение каждой новой строки с названием шрифта вычисляется исходя из высоты символов установленного шрифта:

fm = g.getFontMetrics();
yStep = fm.getHeight();

После названия шрифта метод paint рисует в окне аплета текстовую строку parm_TestString, полученную через параметр с именем "TestString":

g.setFont(new Font(szFontList[i],
  Font.PLAIN, 12));
g.drawString(parm_TestString,
  100, yStart + yStep * i);

Перед тем как перейти к рисованию геометрических фигур, метод paint запоминает в поле yDraw координату последней строки названия шрифта, сделав отступ высотой yStep :

int yDraw;
yDraw = yStart +
  yStep * szFontList.length + yStep;

Первая фигура, которую рисует наш аплет, это многоугольник.

Мы создаем многоугольник как объект класса Polygon:

Polygon p = new Polygon();

В этот объект при помощи метода addPoint добавляется несколько точек:

p.addPoint(70,  yDraw);
p.addPoint(150, yDraw + 30);
p.addPoint(160, yDraw + 80);
p.addPoint(190, yDraw + 60);
p.addPoint(140, yDraw + 30);
p.addPoint(70,  yDraw + 39);

После добавления всех точек метод paint рисует многоугольник, вызывая для этого метод drawPolygon:

g.drawPolygon(p);

Затем мы устанавливаем в контексте отображения красный цвет и рисуем прямоугольник:

g.setColor(Color.red);
g.drawRect(10, yDraw + 85, 200, 100);

Затем метод paint вписывает в этот прямоугольник сегмент окружности:

g.setColor(Color.black);
g.drawArc(10, yDraw + 85,
  200, 100, -50, 320);

Документ HTML для аплета Draw

Документ HTML для аплета Draw не имеет никаких особенностей. Он представлен в листинге 2.

Листинг 2. Файл draw.tmp.html

<applet
   name="draw"
   code="draw"
   codebase="file:/e:/Sun/Articles/vol4/src/draw"
   width="250"
   height="350"
   align="Top"
   alt="If you had a java-enabled browser,
 you would see an applet here."
>
<param name="TestString" value="Test string">
   <hr>If your browser recognized the applet tag,
   you would see an applet here.<hr>
</applet>


Проект для аплета Draw

Оглавление

Рисование в окне
Контекст отображения
Установка атрибутов
Определение атрибутов
Рисование фигур
Аплет Draw
Исходные тексты
Проект аплета

Назад Вперед

Подготовьте файлы проекта аплета Draw, скопировав их из предыдущего раздела в какой-нибудь каталог. Затем запустите мастер проектов и в соответствующей диалоговой панели укажите путь к этому каталогу (рис. 12).

pic12sm.gif (1779 bytes) Рис. 12. Указание пути к каталогу с исходными файлами

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Так как в каталоге уже есть файлы, вы должны включить переключатель Yes и затем нажать кнопку Next.

После этого вы увидите на экране диалоговую панель, показанную на рис. 13.

pic13sm.gif (1801 bytes) Рис. 13. Добавление к проекту существующих файлов

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Здесь вам нужно нажать кнопку Add All in Directory. Как только вы это сделаете, в списке файлов Project Files появится имя файла draw.java, подготовленного вами заранее.

На следующем шаге вам опять нужно нажать кнопку Next и проследить, чтобы имя главного класса аплета, отображаемое в диалоговой панели, показанной на рис. 14, было draw.

pic14sm.gif (1610 bytes) Рис. 14. Задание имени главного класса

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Нажав кнопку Finish, вы можете завершить формирование проекта.

Hаш аплет принимает из документа HTML один параметр с именем TestString. Для добавления параметров вам нужно открыть окно менеджера проектов, выбрав из меню Project строку Show Project Manager. В этом окне проект draw должен быть текущим.

Выделите его курсором мыши в окне Java WorkShop Project Manager и из меню Project этого окна выберите строку Edit. Вы увидите блокнот Project Edit, с помощью которого можно изменять различные параметры проекта. Откройте в этом блокноте страницу Run (рис. 15).

pic15sm.gif (2214 bytes) Рис. 15. Добавление параметра TestString

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

В полях Name и Value введите, соответственно, имя параметра и значение параметра по умолчанию, а затем нажмите кнопку Add. Добавленный вами параметр появится в списке Parameters. Теперь вам осталось только нажать кнопку OK.


Рис. 12. Указание пути к каталогу с исходными файлами

Для возврата сделайте щелчок мышью по рисунку

pic12.gif (6658 bytes)

Рис. 12. Указание пути к каталогу с исходными файлами


Рис. 13. Добавление к проекту существующих файлов

Для возврата сделайте щелчок мышью по рисунку

pic13.gif (7498 bytes)

Рис. 13. Добавление к проекту существующих файлов


Рис. 14. Задание имени главного класса

Для возврата сделайте щелчок мышью по рисунку

pic14.gif (4980 bytes)

Рис. 14. Задание имени главного класса


Рис. 15. Добавление параметра TestString

Для возврата сделайте щелчок мышью по рисунку

pic15.gif (12025 bytes)

Рис. 15. Добавление параметра TestString


События

Оглавление

События
Обработка событий
События от мыши
Аплет LineDraw
Исходный текст
Описание текста

Назад Вперед

От аплетов Java было бы немного толку, если бы они не умели обрабатывать информацию, поступающую от мыши и клавиатуры. К счастью, такая обработка предусмотрена и она выполняется достаточно просто.

Когда пользователь выполняет операции с мышью или клавиатурой в окне аплета, возникают события, которые передаются соответствующим методам класса Applet. Переопределяя эти методы, вы можете организовать обработку событий, возникающих от мыши или клавиатуры.

Если вы создавали приложения для операционной системы Microsoft Windows, здесь для вас нет ничего нового - вспомните, как вы обрабатывали сообщение WM_LBUTTONDOWN или WM_CHAR. Когда пользователь выполнял действие с мышью или клавиатурой в окне приложения, функция этого окна получала соответствующее сообщение. Методы класса Applet, обрабатывающие события от мыши и клавиатуры, являются аналогами обработчиков указанных сообщений.

Заметим, что аплеты имеют дело только с левой клавишей мыши. В текущей версии Java вы не можете никаким образом задействовать в аплете правую или среднюю клавишу мыши.


Как обрабатываются события

Оглавление

События
Обработка событий
События от мыши
Аплет LineDraw
Исходный текст
Описание текста

Назад Вперед

Когда возникает событие, управление получает метод handleEvent из класса Component. Класс Applet является дочерним по отношению к классу Component.

Прототип метода handleEvent мы привели ниже:

public boolean handleEvent(Event evt);

В качестве параметра методу handleEvent передается объект класса Event, который содержит всю информацию о событии. По содержимому полей класса Event вы можете определить координаты курсора мыши в момент, когда пользователь нажал клавишу, отличить одинарный щелчок от двойного и так далее.

Ниже мы привели список полей класса Event, которые вы можете проанализировать:

Поле | Описание
public Object argarg; | Произвольный аргумент события, значение которого зависит от типа события
public int clickCountclickCount; | Это поле имеет значение только для события с типом MOUSE_DOWNMOUSE_DOWN и содержит количество нажатий на клавишу мыши. Если пользователь сделал двойной щелчок мышью, в это поле будет записано значение 2
public Event evtevt; | Следующее событие в связанном списке
public int idid; | Тип события. Ниже мы перечислим возможные значения для этого поля
public int keykey; | Код нажатой клавиши (только для события, созданного при выполнении пользователем операции с клавиатурой)
public int modifiersmodifiers; | Состояние клавиш модификации <Alt>, <Ctrl>, <Shift>
public Object targettarget; | Компонент, в котором произошло событие
public long whenwhen; | Время, когда произошло событие
public int xx; | Координата по оси X
public int yy; | Координата по оси Y

Поле id (тип события) может содержать следующие значения:

Значение | Тип события
ACTION_EVENTACTION_EVENT | Пользователь хочет, чтобы произошло некоторое событие
GOT_FOCUSGOT_FOCUS | Компонент (в нашем случае окно аплета) получил фокус ввода. О фокусе ввода вы узнаете из раздела, посвященного работе с клавиатурой
KEY_ACTIONKEY_ACTION | Пользователь нажал клавишу типа "Action"
KEY_ACTION_RELEASEKEY_ACTION_RELEASE | Пользователь отпустил клавишу типа "Action"
KEY_PRESSKEY_PRESS | Пользователь нажал обычную клавишу
KEY_RELEASEKEY_RELEASE | Пользователь отпустил обычную клавишу
LIST_DESELECTLIST_DESELECT | Отмена выделения элемента в списке
LIST_SELECTLIST_SELECT | Выделение элемента в списке
LOAD_FILELOAD_FILE | Загрузка файла
LOST_FOCUSLOST_FOCUS | Компонент потерял фокус ввода
MOUSE_DOWNMOUSE_DOWN | Пользователь нажал клавишу мыши
MOUSE_DRAGMOUSE_DRAG | Пользователь нажал клавишу мыши и начал выполнять перемещение курсора мыши
MOUSE_ENTERMOUSE_ENTER | Курсор мыши вошел в область окна аплета
MOUSE_EXITMOUSE_EXIT | Курсор мыши покинул область окна аплета
MOUSE_MOVEMOUSE_MOVE | Пользователь начал выполнять перемещение курсора мыши, не нажимая клавишу мыши
MOUSE_UPMOUSE_UP | Пользователь отпустил клавишу мыши
SAVE_FILESAVE_FILE | Сохранение файла
SCROLL_ABSOLUTESCROLL_ABSOLUTE | Пользователь переместил движок полосы просмотра в новую позицию
SCROLL_LINE_DOWNSCROLL_LINE_DOWN | Пользователь выполнил над полосой просмотра операцию сдвига на одну строку вниз
SCROLL_LINE_UPSCROLL_LINE_UP | Пользователь выполнил над полосой просмотра операцию сдвига на одну строку вверх
SCROLL_PAGE_DOWNSCROLL_PAGE_DOWN | Пользователь выполнил над полосой просмотра операцию сдвига на одну страницу вниз
SCROLL_PAGE_UPSCROLL_PAGE_UP | Пользователь выполнил над полосой просмотра операцию сдвига на одну страницувверх
WINDOW_DEICONIFYWINDOW_DEICONIFY | Пользователь запросил операцию восстановления нормального размера окна после его минимизации
WINDOW_DESTROYWINDOW_DESTROY | Пользователь собирается удалить окно
WINDOW_EXPOSEWINDOW_EXPOSE | Окно будет отображено
WINDOW_ICONIFYWINDOW_ICONIFY | Окно будет минимизировано
WINDOW_MOVEDWINDOW_MOVED | Окно будет перемещено

Если событие связано с клавиатурой (тип события KEY_ACTION или KEY_ACTION_RELEASE), в поле key может находиться одно из следующих значений:

Значение | Клавиша
DOWNDOWN | Клавиша перемещения курсора вниз
ENDEND | <End>
F1F1-F12 | <F1>
F2 | <F2>
F3 | <F3>
F4 | <F4>
F5 | <F5>
F6 | <F6>
F7 | <F7>
F8 | <F8>
F9 | <F9>
F10 | <F10>
F11 | <F11>
F12 | <F12>
HOMEHOME | <Home>
LEFTLEFT | Клавиша перемещения курсора влево
PGDNPGDN | <Page Down>
PGUPPGUP | <Page Up>
RIGHTRIGHT | Клавиша перемещения курсора вправо
UPUP | Клавиша перемещения курсора вниз

Могут быть указаны следующие маски для поля модификаторов modifiers:

Значение маски | Описание
ALT_MASKALT_MASK | Была нажата клавиша <Alt>
META_MASKMETA_MASK | Была нажата мета-клавиша (клавиша для ввода диактрических символов)
CTRL_MASKCTRL_MASK | Была нажата клавиша <Ctrl>
SHIFT_MASKSHIFT_MASK | Была нажата клавиша <Shift>

Ваше приложение может переопределить метод handleEvent и обрабатывать события самостоятельно, однако есть более простой путь. Обработчик этого метода, который используется по умолчанию, вызывает несколько методов, которые более удобны в использовании, в частности, при обработке событий от мыши или клавиатуры.


События от мыши

Оглавление

События
Обработка событий
События от мыши
Аплет LineDraw
Исходный текст
Описание текста

Назад Вперед

В этом разделе мы рассмотрим события, которые возникают в результате того, что пользователь выполняет в окне аплета операции с мышью. Это такие операции, как нажатие и отпускание клавиши мыши, перемещение курсора мыши в окне аплета с нажатой или отпущенной клавишей, перемещение курсора мыши в окно аплета и удаление этого курсора из окна аплета.

Все перечисленные ниже методы должны вернуть значение true, если обработка события выполнена успешно и дальнейшая обработка не требуется. Если же методы вернут значение fasle, событие будет обработано методом из базового класса, то есть для него будет выполнена обработка, принятая по умолчанию.

Программисты, создававшие приложения для операционной системы Microsoft Windows, могут найти здесь аналогию с вызовом функции DefWindowProc, которая выполняет обработку сообщений, принятую по умолчанию.

Нажатие клавиши мыши

Переопределив метод mouseDown, вы сможете отслеживать нажатия клавиши мыши. Прототип этого метода приведен ниже:

public boolean mouseDown(Event evt, int x, int y);

Через параметр evt методу передается ссылка на объект Event, с помощью которой метод может получить полную информацию о событии.

Анализируя содержимое параметров x и y, приложение может определить координаты курсора на момент возникновения события.

Заметим, что для отслеживания двойного щелчка мыши не предусмотрено никакого отдельного метода. Однако анализируя содержимое поля clickCount переменной evt, вы можете определить кратность щелчка мыши:

if(evt.clickCount > 1)
  // Двойной щелчок
  showStatus("Mouse Double Click");
else
  // Одинарный щелчок
  showStatus("Mouse Down");

Отпускание клавиши мыши

При отпускании клавиши мыши управление получает метод mouseUp:

public boolean mouseUp(Event evt, int x, int y);

Анализируя параметры x и y, вы можете определить координаты точки, в которой пользователь отпустил клавишу мыши.

Перемещение курсора мыши

Когда пользователь перемещает курсор мыши над окном аплета, в процессе перемещения происходит вызов метода mouseMove:

public boolean mouseMove(Event evt, int x, int y);

Через переменные x и y передаются текущие координаты курсора мыши.

Выполнение операции Drag and Drop

Операция Drag and Drop выполняется следующим образом: пользователь нажимает клавишу мыши и, не отпуская ее, начинает перемещать курсор мыши. При этом происходит вызов метода mouseDrag:

public boolean mouseDrag(Event evt, int x, int y);

Через переменные x и y передаются текущие координаты курсора мыши. Метод mouseDrag вызывается даже в том случае, если в процессе перемещения курсор вышел за пределы окна аплета.

Вход курсора мыши в область окна аплета

Метод mouseEnter получает управление, когда курсор мыши в процессе перемещения по экрану попадает в область окна аплета:

public boolean mouseEnter(Event evt, int x, int y);

Вы можете использовать этот метод для активизации аплета, на который указывает курсор мыши.

Выход курсора мыши из области окна аплета

Метод mouseExit вызывается при покидании куросром окна аплета:

public boolean mouseExit(Event evt, int x, int y);

Если пользователь убрал курсор из окна аплета, активизированного методом mouseEnter, то метод mouseExit может переключить аплет в пассивное состояние.


Аплет LineDraw

Оглавление

События
Обработка событий
События от мыши
Аплет LineDraw
Исходный текст
Описание текста

Назад Вперед

В аплете LineDraw мы показали основные приемы обработки событий, вызываемых мышью. Вы можете рисовать в окне аплета прямые линии, причем возле концов линий отображаются их координаты (рис. 1).

pic01.gif (4730 bytes)

Рис. 1. Окно аплета LineDraw с нарисованными в нем линиями

Для того чтобы нарисовать линию в окне аплета LineDraw, вы должны установить курсор в начальную точку, нажать клавишу мыши и затем, не отпуская ее, переместить курсор в конечную точку. После отпускания клавиши мыши координаты линии будут сохранены аплетом в массиве, после чего произойдет перерисовка окна аплета.

По мере того как вы будете рисовать линии, аплет будет заполнять массив с координатами линий. Каждый раз, когда окно аплета будет перерисовываться, метод paint перерисует все линии заново, пользуясь координатами, сохраненными в массиве.

Для того чтобы стереть содержимое окна аплета, вам достаточно сделать двойной щелчок в его окне. При этом из массива координать линий будут удалены все элементы.


Исходный текст аплета LineDraw

Оглавление

События
Обработка событий
События от мыши
Аплет LineDraw
Исходный текст
Описание текста

Назад Вперед

Исходный текст аплета LieDrnaw вы найдете в листинге 1.

Листинг 1. Файл LieDrnaw.java

import java.applet.*;
import java.awt.*;
import java.util.*;

public class LineDraw extends Applet
{
  Dimension dmDown;
  Dimension dmUp;
  Dimension dmPrev;
  boolean bDrawing;
  Vector lines;
  public String getAppletInfo()
  {
    return "Name: LineDraw";
  }
  public void init()
  {
    bDrawing = false;
    lines = new Vector();
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension = getSize();
    setBackground(Color.yellow);
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    for (int i=0; i < lines.size(); i++)
    {
      Rectangle p =
        (Rectangle)lines.elementAt(i);
      g.drawLine(p.width, p.height,
        p.x, p.y);
      g.drawString("<" + p.width
        + "," + p.height + ">",
        p.width, p.height);
      g.drawString("<" + p.x + "," +
        p.y+ ">", p.x, p.y);
    }
    bDrawing = false;
  }
  public boolean mouseDown(Event evt,
    int x, int y)
  {
    if(evt.clickCount > 1)
    {
      lines.removeAllElements();
      repaint();
      return true;
    }
    dmDown = new Dimension(x, y);
    dmPrev = new Dimension(x, y);
    bDrawing = false;
    return true;
  }
  public boolean mouseUp(Event evt,
    int x, int y)
  {
    if(bDrawing)
    {
      dmUp = new Dimension(x, y);
      lines.addElement(
        new Rectangle(dmDown.width,
        dmDown.height, x, y));
      repaint();
      bDrawing = false;
    }
    return true;
  }
  public boolean mouseDrag(Event evt,
    int x, int y)
  {
    Graphics g = getGraphics();
    bDrawing = true;
    g.setColor(Color.yellow);
    g.drawLine(dmDown.width, dmDown.height,
      dmPrev.width, dmPrev.height);
    g.setColor(Color.black);
    g.drawLine(dmDown.width,
      dmDown.height, x, y);
    dmPrev = new Dimension(x, y);
    return true;
  }
  public boolean mouseMove(Event evt,
    int x, int y)
  {
    bDrawing = false;
    return true;
  }
}

Исходный текст документа HTML, подготовленного системой JavaWorkshop, представлен в листинге 2.

Листинг 2. Файл LineDraw.tmp.html

<applet name="LineDraw"
  code="LineDraw"
  codebase=
"file:/e:/Sun/Articles/vol5/src/LineDraw"
  width="500"
  height="600"
  align="Top"
  alt="If you had a java-enabled browser,
you would see an applet here.">
</applet>


Описание исходного текста

Оглавление

События
Обработка событий
События от мыши
Аплет LineDraw
Исходный текст
Описание текста

Назад Вперед

В нашем аплете мы будем создавать объект класса Vector, который является массивом с динамически изменяемым размером. Здесь мы будем хранить координаты нарисованных линий.

Класс Vector имеет полное имя java.util.Vector, поэтому мы подключаем соответствующую библиотеку классов:

import java.util.*;

Поля класса LineDraw

В нашем классе мы определили несколько полей, предназначенных для хранения текущих координат рисуемых линий:

Dimension dmDown;
Dimension dmUp;
Dimension dmPrev;
boolean   bDrawing;
Vector    lines;

В переменную dmDown класса Dimension записываются координаты курсора на момент нажатия клавиши мыши. Если пользователь нажал клавишу мыши для того чтобы приступить к рисованию линии, это будет координатами начала линии.

Когда пользователь отпускает клавишу мыши, координаты записываются в переменную dmUp.

В процессе рисования линии метод mouseDrag стирает ранее нарисованную линию и рисует новую. Координаты конца старой линии хранятся в переменной dmPrev.

Переменная bDrawing типа boolean хранит текущее состояние аплета. Когда аплет находится в состоянии рисования линии, в эту переменную записывается значение true, а когда нет - значение false.

И, наконец, переменная lines типа Vector является динамическим массивом, в котором хранятся координаты нарисованных линий.

Метод getAppletInfo

Метод getAppletInfo возвращает название аплета и не имеет никаких особенностей.

Метод init

Метод init сбрасывает признак рисования, записывая в поле bDrawing значение false, а также создает новый динамический массив в виде объекта класса Vector:

public void init()
{
  bDrawing = false;
  lines = new Vector();
}

Метод paint

После изменения цвета фона и рисования рамки метод paint перебирает в цикле все элементы массива lines, рисуя линии:

for(int i=0; i < lines.size(); i++)
{
  Rectangle p =
    (Rectangle)lines.elementAt(i);

  g.drawLine( p.width, p.height, p.x, p.y);

  g.drawString("<" + p.width + ","
    + p.height + ">",
    p.width, p.height);

  g.drawString("<" + p.x + "," +
    p.y+ ">", p.x, p.y);
}

Для объектов класса Vector метод size возвращает количество элементов в массиве, чем мы воспользовались для проверки условия выхода из цикла.

Чтобы извлечь элемент массива по его номеру, мы применили метод elementAt, передав ему через единственный параметр номер извлекаемого элемента.

Так как в массиве хранятся объекты класса Rectangle, перед инициализацией ссылки p мы выполняем явное преобразование типов.

Координаты концов линий рисуются с помощью уже знакомого вам метода drawString.

Перед завершением работы метод paint сбрасывает признак рисования, записывая в поле bDrawing значение false:

bDrawing = false;

Метод mouseDown

В начале своей работы метод mouseDown определяет, был ли сделан одинарный щелчок клавишей мыши, или двойной. Если был сделан двойной щелчок мышью, метод удаляет все элементы из массива list, а затем перерисовывает окно аплета, вызывая метод repaint:

lines.removeAllElements();
repaint();

После перерисовки окно аплета очищается от линий.

Если же был сделан одинарный щелчок клавишей мыши, метод mouseDown сохраняет текущие координаты курсора в переменных dmDown и dmPrev, а затем сбрасывает признак рисования:

dmDown = new Dimension(x, y);
dmPrev = new Dimension(x, y);
bDrawing = false;

Метод mouseUp

Когда пользователь отпускает клавишу мыши, вызывается метод mouseUp. В его задачу входит сохранение текущих координат курсора мыши в поле dmUp, а также добавление нового элемента в массив lines:

dmUp = new Dimension(x, y);
lines.addElement(
  new Rectangle(dmDown.width,
  dmDown.height, x, y));
repaint();

После добавления элемента в массив метод mouseUp инициирует перерисовку окна аплета, вызывая для этого метод repaint.

Заметим, что в качестве координат начала линии мы записываем в элемент массива координаты точки, где в последний раз пользователь нажимал курсор мыши. В качестве координат конца линии используются текущие координаты курсора на момент отпускания клавиши мыши.

Метод mouseDrag

До сих пор наши аплеты выполняли рисование только в методе paint, и так поступают большинство аплетов. Однако наш аплет должен рисовать линии во время перемещения курсора мыши, так как в противном случае пользователю не будет видно, как пройдет рисуемая линия.

Для того чтобы нарисовать что-либо в окне аплета, наобходимо получить контекст отображения. Методу paint этот контекст передается через парметр как объект класса Graphics. Если же вы собираетесь рисовать в другом методе, отличном от paint, необходимо получить контекст отображения, например, так:

Graphics g = getGraphics();

После получения контекста отображения и включения режима рисования (записью в переменную bDrawing значения true) метод mouseDrag стирает линию, которая была нарисована ранее, в процессе предыдущего вызова этого же метода:

g.setColor(Color.yellow);
g.drawLine(dmDown.width, dmDown.height,
   dmPrev.width, dmPrev.height);

Для стирания линии мы рисуем ее на том же месте с использованием цвета, совпадающего с цветом фона.

Далее метод mouseDrag рисует новую линию черного цвета, соединяя точку, в которой была нажата клавиша мыши, с точкой текущего расположения курсора мыши:

g.setColor(Color.black);
g.drawLine(dmDown.width,
  dmDown.height, x, y);

После рисования линии координаты ее конца сохраняются в поле dmPrev для стирания этой линии при следующем вызове метода mouseDrag:

dmPrev = new Dimension(x, y);
return true;

Метод mouseMove

Метод mouseMove не делает ничего, за исключением того, что он отключает режим рисования. Таким образом, простое перемещение курсора мыши над окном аплета не приводит к рисованию линий.


Компоненты в окне аплета

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

Практически каждое приложение Windows, за исключением самых простейших, имеет такие органы управления, как меню, кнопки, поля редактирования текстовой информации, переключатели с независимой и зависимой фиксацией и списки. Кроме того, приложение Windows может создавать диалоговые панели, содержащие перечисленные выше и другие органы управления.

В окне аплета вы также можете разместить некоторые из перечисленных выше органов управления, а именно:

  • кнопки;
  • переключатели с независимой фиксацией;
  • переключатели с зависимой фиксацией;
  • статические текстовые поля;
  • однострочные и многострочные поля редактирования текста;
  • списки;
  • полосы просмотра

Самый большой и едва ли приятный сюрприз для вас это то, что при размещении перечисленных органов управления в окне аплета вы не можете задать для них точные координаты и размеры. Размещением занимается система управления внешним видом Layout Manager, которая располагает органы управления по-своему. Вы, однако, можете задавать несколько режимов размещения (последовательное, в ячейках таблицы и так далее), но не координаты или размеры. Это сделано для обеспечения независимости приложений Java от платформ, на которых они выполняются.

Органы управления создаются как объекты классов, порожденных от класса Component (рис. 1). Поэтому в дальнейшем мы будем называть органы управления компонентами.

pic01.gif (3954 bytes)

Рис. 1. Взаимосвязь классов органов управления в приложениях Java

Класс Button позволяет создавать стандартные кнопки. Если вам нужна нестандартная кнопка (например, графическая кнопка), вы можете создать ее на базе класса Canvas.

Для создания переключателей с независимой или зависимой фиксацией предназначен класс CheckBox.

С помощью класса Label вы можете создавать в окне аплета текстовые строки, например, надписи для других компонент. Эти строки не редактируются пользователем.

Класс List, как нетрудно догадаться из названия, предназначен для создания списков.

С помощью класса Scrollbar вы можете создавать полосы просмотра, которые используются, в частности, могострочными полями редактирования текста.

Класс TextComponent служит базовым для двух других классов - TextField и TextArea. Первый из них предназначен для создания однострочных редакторов текста, второй - для создания многострочных редкаторов текста.

Для того чтобы понять, как компоненты размещаются на поверхности окна аплета системой Layout Manager, рассмотрим другую взаимосвязь классов Java, показанную на рис. 2.

pic02.gif (3468 bytes)

Рис. 2. Компоненты и контейнеры

Класс Component служит в качестве базового класса для класса Container. Объекты этого класса, которые мы будем называть контейнерами, могут содержать объекты классов Component и Container. Таким образом, внутри контейнеров могут находиться компоненты и другие контейнеры.

Класс Applet, так же как и другие классы, произведенные от класса Container, является контейнером. Это означает, что аплет может содержать в себе компоненты (такие как органы управления) и контейнеры.

Заметим, что класс Applet наследуется от класса Container через класс Panel, в котором определены методы системы Layout Manager. Настраивая соответствующим образом Layout Manager, мы можем менять стратегию размещения компонент внутри окна аплета.

В окне аплета вы можете создать несколько объектов класса Panel (панелей), разделяющих окно на части. Для каждой такой панели можно установить свою стратегию размещения компонент и контейнеров, что позволяет выполнять достаточно сложную компоновку в окне аплета.

Теперь после такого краткого введения в контейнеры и компоненты мы перейдем к описанию методов создания отдельных компонент.


Кнопки

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

Как мы уже говорили, стандартные кнопки создаются на базе класса Button. Этот класс очень простой, поэтому мы приведем полное его описание:

Класс Button

Конструкторы

В классе Button определены два конструктора, первый из которых позволяет создавать кнопку без надписи, а второй - кнопку с надписью. Обычно используется второй конструктор.

  public Button();
  public Button(String label);

Методы

  • addNotify

Вызов метода createButton

public void addNotify();

Получение надписи на кнопке

  • getLabel
public String getLabel();

Получение строки параметров, отражающей состояние кнопки

  • paramString
protected String paramString();

Установка надписи на кнопке

  • setLabel
public void setLabel(String  label);

Из методов класса Button вы будете использовать чаще всего два - getLabel и setLabel. Первый из них позволяет получить строку надписи на кнопке, а второй - установить новую надпись.

Обычно аплет создает в своем окне кнопки в процессе своей инициализации при обработке метода init, например:

Button btn1;
. . .
public void init()
{
  btn1 = new Button("Button 1");
  add(btn1);
}

Здесь мы создали кнопку с надписью Button 1. Затем мы добавили эту кнопку в контейнер, которым является окно аплета, с помощью метода add.

Обработка событий от кнопки

Для обработки событий, создаваемых кнопками и другими компонентами, вы можете переопределить метод handleEvent. Однако существует и более простой способ.

Этот способ основан на переопределении метода action, который получает управление, когда пользователь совершает какое-либо действие с компонентом. Под действием подразумевается нажатие на кнопку, завершение ввода текстовой строки, выбор элемента из списка, изменение состояния переключателя и так далее.

Прототип метода action представлен ниже:

public boolean action(Event evt, Object obj)
{
  . . .
}

В качестве первого параметра методу передается ссылка на объект класса Event, содержащий всю информацию о событии. Второй параметр представляет собой ссылку на объект, вызвавший появление события.

Как обрабатывать событие в методе action?

Прежде всего необходимо проверить, объект какого типа создал событие. Это можно сделать, например, следующим образом:

if(evt.target instanceof Button)
{
  . . .
  return true;
}
return false;

Здесь мы с помощью оператора instanceof проверяем, является ли объект, вызвавший появление события, объектом класса Button.

Далее, если в окне аплета имеется несколько кнопок, необходимо выполнить ветвление по ссылкам на объекты кнопок, как это показано ниже:

if(evt.target.equals(btn1))
{
  . . .
}
else if(evt.target.equals(btn2))
{
  . . .
}

. . .

else
{
  return false;
}
return true;

Тем из вас, кто создавал приложения Windows на языке программирования С, этот фрагмент кода может напомнить длинный переключатель switch обработки сообщений Windows.


Переключатели

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

Аплеты Java могут создавать в своем окне переключатели двух типов: с независимой фиксацией и с зависимой фиксацией.

Переключатели с независимой фиксацией имеют прямоугольную форму и, исходя из названия, работают независимо друг от друга. Если такой переключатель находится во включенном состоянии, внутри изображения маленького квадрата появляется галочка, если в выключенном - галочка исчезает.

Обычно переключатели с независимой фиксацией используются для управления независящими друг от друга режимами или параметрами.

Переключатели с зависимой фиксацией имеют круглую форму. В каждый момент времени может быть включен только один такой переключатель из группы переключателей с фиксацией. Аплет может создавать несколько групп переключателей с зависимой фиксацией.

Переключатели с зависимой фиксацией используются для выбора из нескольких взаимоисключающих возможностей, например, для установки одного из нескольких режимов.

Класс Checkbox

Переключатели с независимой и зависимой фиксацией создаются на базе класса Checkbox. Приведем прототипы конструкторов и методов этого класса:

Конструкторы

Создание переключателя с независимой фиксацией без названия

public Checkbox();

Создание переключателя с независимой фиксацией и названием

public Checkbox(String label);

Создание переключателя с зависимой фиксацией и названием

public Checkbox(String label,
  CheckboxGroup group, boolean state);

Методы

  • addNotify

Вызов метода createCheckbox

public void addNotify();
  • getCheckboxGroup

Получение группы, к которой относится данный переключатель с зависимой фиксацией

public CheckboxGroup getCheckboxGroup();
  • getLabel

Получение названия переключателя

public String getLabel();
  • getState

Определение текущего состояния переключателя

public boolean getState();
  • paramString

Получение строки параметров

protected String paramString();
  • setCheckboxGroup

Установка группы, к которой относится данный переключатель с зависимой фиксацией

public void setCheckboxGroup(CheckboxGroup g);
  • setLabel

Установка названия переключателя

public void setLabel(String label);
  • setState

Установка нового состояния переключателя

public void setState(boolean state);

Создание переключателей с независимой фиксацией

Создать переключатель с независимой фиксацией не сложнее, чем создать кнопку:

Checkbox rdbox1;
. . .
public void init()
{
  chbox1 = new Checkbox("Switch 1");
  add(chbox1);
}

В этом фрагменте кода мы создаем переключатель chbox1 с названием Switch 1, а затем с помощью метода add добавляем его в контейнер, которым является окно аплета.

Для определения текущего состояния переключателя вы можете использовать метод getState. Если переключатель включен, этот метод возвращает значение true, а если выключен - значение false.

Создание переключателей с зависимой фиксацией

Для каждой группы переключателей с зависимой фиксацией вы должны создать объект класса CheckboxGroup:

Конструктор

public CheckboxGroup();

Методы

Получение ссылки на переключатель, который находится во включенном состоянии

public Checkbox getCurrent();

Установка указанного переключателя в группе во включенное состояние

public void setCurrent(Checkbox box);

Получение строки, которая представляет группу

public String toString();

Ссылка на этот объект указывается при создании отдельных переключателей с зависимой фиксацией, входящих в группу:

CheckboxGroup grModeGroup;
Checkbox rdbox1;
Checkbox rdbox2;
Checkbox rdbox3;
Checkbox rdbox4;
. . .
public void init()
{
  grModeGroup = new CheckboxGroup();

  rdbox1 = new Checkbox("Mode 1",
    grModeGroup, true);
  rdbox2 = new Checkbox("Mode 2",
    grModeGroup, false);
  rdbox3 = new Checkbox("Mode 3",
    grModeGroup, false);
  rdbox4 = new Checkbox("Mode 4",
    grModeGroup, false);

  add(rdbox1);
  add(rdbox2);
  add(rdbox3);
  add(rdbox4);
}

Через первый параметр конструктору Checkbox в этом примере передается название переключателя, через второй - ссылка на группу, а через третий - состояние, в которое должен быть установлен переключатель. Из всех переключателей группы только один может находиться во включенном состоянии.


Списки класса Choice

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

На базе класса Choice вы можете создать списки типа Drop Down или, как их еще называют, "выпадающие" списки. Такой список выглядит как текстовое поле высотой в одну строку, справа от которого располагается кнопка (рис. 3).

pic03.gif (1003 bytes)

Рис. 3. Список типа Drop Down, созданный на базе класса Choice

Если нажать на эту кнопку, список раскроется и вы сможете сделать выбор из его элементов (рис. 4).

pic04.gif (1275 bytes)

Рис. 4. Раскрытый список, созданный на базе класса Choice

В списке класса Choice одновременно можно выбрать только один элемент.

Класс Choice

Приведем описание прототипов конструктора и методов класса Choice:

Конструктор

public Choice();

Методы

  • addItem

Добавление элемента в список

public void addItem(String item);
  • addNotify

Вызов метода createChoice

public void addNotify();
  • countItems

Определение количества элементов в списке

public int countItems();
  • getItem

Получение строки списка по номеру соответствующего ему элемента списка

public String getItem(int index);
  • getSelectedIndex

Получение номера текущего выбранного элемента

public int getSelectedIndex();
  • getSelectedItem

Получение строки, соответствующей текущему выбранному элементу списка

public String getSelectedItem();
  • paramString

Получение строки параметров

protected String paramString();
  • select

Выбор в списке элемента по заданному номеру

public void select(int pos);
  • select

Выбор в списке элемента по заданной строке

public void select(String str);

Создание списков

Конструктор класса Choice не имеет параметров. Создание списка с его помощью не вызовет у вас никаких затруднений:

Choice chBackgroundColor;
chBackgroundColor = new Choice();

Для наполнения списка используйте метод addItem. В качестве параметра ему необходимо передать текстовую строку, которая будет связана с добавляемым элементом списка:

chBackgroundColor.addItem("Yellow");

Далее список можно добавить в окно аплета как компонент с помощью метода add:

add(chBackgroundColor);

Заметим, что список можно заполнять до или после добавления в окно аплета.

После наполнения списка по умолчанию выделяется элемент, который был добавлен в список первым. При помощи метода select вы можете выделить любой элемент списка по его номеру или строке, связанной с элементом.

Когд пользователь выбирает новую строку в списке, возникает событие. Обработчик этого события, реализованный, например, переопределением метода action, может получить номер выбранной строки при помощи метода getSelectedIndex. Пример обработки такого события вы найдете в разделе "Приложение ChoiceList".

Если вас интересует не номер выбранного элемента, а строка, связанная с выбранным элементом, воспользуйтесь методом getSelectedItem.

И, наконец, с помощью метода getItem вы можете получить текст строки, связанной с элементом, по номеру элемента.


Списки класса List

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

На базе класса List вы можете сделать список другого типа, который допускает выбор не только одного, но и нескольких элементов. В отличие от списка, созданного на базе класса Choice, список класса List может занимать прямоугольную область, в которой помещаются сразу несколько элементов. Этот список всегда находится в раскрытом состоянии (рис. 5).

pic05.gif (1457 bytes)

Рис. 5. Список класса List, все элементы которого помещаются в окне списка

Если размеры окна списка класса List недостаточны для того чтобы вместить в себя все элементы, в правой части окна списка автоматически создается полоса просмотра, с помощью которой можно пролистать весь список (рис. 6).

pic06.gif (1367 bytes)

Рис. 6. Список класса List с полосой просмотра

Описание класса List

В классе List определено два конструктора и довольно много различных методов. Ниже мы привели краткое описание класса List:

Конструкторы

Конструктор без параметров

public List();

Конструктор, позволяющий указать количество отображаемых строк и флаг одновременного выбора нескольких элементов

public List(int rows,
  boolean multipleSelections);

Методы

  • addItem

Добавление элемента в список

public void addItem(String item);

Добавление элемента в список с указанием номера позиции

public void addItem(String item, int index);
  • addNotify

Вызов метода createList

public void addNotify();
  • allowsMultipleSelections

Переключение списка в режим, при котором возможно выбирать одновременно несколько элементов

public boolean allowsMultipleSelections();
  • clear

Удаление из списка всех элементов

public void clear();
  • countItems

Определение количества элементов в списке

public int countItems();
  • delItem

Удаление элемента из заданной позиции

public void delItem(int position);
  • delItems

Удаление нескольких элементов

public void delItems(int start, int end);
  • deselect

Отмена выделения элемента с заданной позицией

public void deselect(int index);
  • getItem

Получение строки, связанной с элементом, по позиции этого элемента

public String getItem(int index);
  • getRows

Определение количества элементов, которые видны в окне списка

public int getRows();
  • getSelectedIndex

Определение номера выделенного элемента

public int getSelectedIndex();
  • getSelectedIndexes

Определение номеров выделенных элементов

public int[] getSelectedIndexes();
  • getSelectedItem

Получение текстовой строки, связанной с выделенным элементом

public String getSelectedItem();
  • getSelectedItems

Получение ссылки на массив строк, связанных с выделенными элементами

public String[] getSelectedItems();
  • getVisibleIndex

Определение номера элемента массива, который был сделан в последний раз выделенным с помощью метода makeVisible

public int getVisibleIndex();
  • isSelected

Проверка, является ли выделенной строка с заданным номером

public boolean isSelected(int index);
  • makeVisible

Выполняется свертка элементов списка таким образом, чтобы элемент с заданным номером стал видимым

public void makeVisible(int index);

  • minimumSize

Минимальные размеры области, необходимые для отображения списка

public Dimension minimumSize();

Минимальные размеры области, необходимые для отображения списка с заданным количеством строк

public Dimension minimumSize(int rows);
  • paramString

Получение строки параметров

protected String paramString();
  • preferredSize

Предпочтительные размеры области, необходимые для отображения списка

public Dimension preferredSize();

Предпочтительные размеры области, необходимые для отображения списка с заданным количеством строк

public Dimension preferredSize(int rows);

  • removeNotify

Извещение об уничтожении узла

public void removeNotify();
  • replaceItem

Замещение элемента списка с заданным номером

public void replaceItem(String newValue,
  int index);
  • select

Выделение элемента с заданным номером

public void select(int index);
  • setMultipleSelections

Установка или сброс режима одновременного выделения нескольких строк

public void setMultipleSelections(boolean v);

Создание списка класса List

Процесс создания списка класса List несложен:

List chBackgroundColor;
chBackgroundColor = new List(6, false);

При создании списка вы передаете конструктору количество одновременно отображаемых строк и флаг разрешения одновременного выбора нескольких строк. Если значение этого флага равно true, пользователь сможет выбирать из списка одновременно несколько строк, а если false - только одну строку.

Для наполнения списка вы можете использовать уже знакомый вам метод addItem:

chBackgroundColor.addItem("Yellow");
chBackgroundColor.addItem("Green");
chBackgroundColor.addItem("White");

Список класса List добавляется к окну аплета методом add:

add(chBackgroundColor);

Кратко остановимся на нескольких методах класса List.

Если вы разрешили пользователю выбирать из списка одновременно несколько элементов, то для получения ссылки на массив выбранных элементов вам пригодятся методы getSelectedItems и getSelectedIndexes:

public String[] getSelectedItems();
public int[] getSelectedIndexes();

С помощью метода setMultipleSelections вы можете динамически включать или выключать режим одновременного выбора нескольких элементов.

В некоторых случаях вам может пригодиться метод clear, удаляющий все элементы из списка:

public void clear();

Методика использования других методов очевидна из краткого описания класса List, приведенного в нашей статье.

Обработка событий от списка класса List

В отличие от списка класса Choice, для выбора строки (или нескольких строк) из списка класса List, пользователь должен сделать двойной щелчок левой клавишей мыши по выделенному элементу (или элементам, если выделено несколько элементов). При этом событие можно обработать переопределенным методом action, как мы это делали для списка класса Choice.

Однако список класса List создает события не только при двойном щелчке, но и при выделении или отмены выделения элементов, сделанном пользователем одинарным щелчком клавиши мыши. Аплет может перехватывать и обрабатывать такие события, переопределив метод handleEvent.


Текстовое поле класса Label

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

На базе класса Label вы можете создать в окне аплета однострочное текстовое поле, которое не поддается редактированию. Основное назначение таких полей - подпись других компонент, таких, например, как группы переключателей или списки.

Класс Label

Ниже мы привели краткое описание класса Label:

Поля

Поля класса Label задают способ выравнивания текстового поля

  • CENTER

Центрирование

public final static int CENTER;
  • LEFT

Выравнивание по левой границе

public final static int LEFT;
  • RIGHT

Выравнивание по правой границе

public final static int RIGHT;

Конструкторы

Создание текстового поля без текста

public Label();

Создание текстового поля с заданным текстом

public Label(String label);

Создание текстового поля с заданным текстом и заданным выравниванием

public Label(String label, int alignment);

Методы

  • addNotify

Вызов метода createLabel

public void addNotify();
  • getAlignment

Определение текущего выравнивания текстового поля

public int getAlignment();
  • getText

Получение текста из поля

public String getText();
  • paramString

Получение строки параметров

protected String paramString();
  • setAlignment

Установка выравнивания текстового поля

public void setAlignment(int alignment);
  • setText

Запись текста в поле

public void setText(String label);

Создание поля класса Label

Текстовое поле класса Label создается вызовом соответствующего конструктора. Например, ниже мы создали текстовое поле, указав строку, которую надо в него записать:

Label lbTextLabel;
lbTextLabel =
  new Label("Выберите выравнивание");

С помощью метода add вы можете добавить текстовое поле в окно аплета:

add(lbTextLabel);

Метод setAlignment позволяет при необходимости изменить выравнивание текста. Способ выравнивания необходимо указать через единственный параметр метода:

lbTextLabel.setAlignment(Label.LEFT);

При помощи метода setText вы сможете динамически изменять текст, расположенный в поле класса Label.


Текстовое поле класса TextField

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

Для редактирования одной строки текста вы можете создать текстовое поле на базе класса TextField, которое несложно в использовании. Класс TextField создан на базе другого класса с именем TextComponent, поэтому при работе с текстовым полем класса TextField вы можете использовать и методы класса TextComponent.

Класс TextField

Приведем краткое описание класса TextField:

Конструкторы

Создание поля без текста

public TextField();

Создание поля без текста с заданной шириной

public TextField(int cols);

Создание поля и инициализация его текстом

public TextField(String text);

Создание поля заданной ширины и инициализация его текстом

public TextField(String text,
  int cols);

Методы

  • addNotify

Вызов метода createTextField

public void addNotify();
  • echoCharIsSet

Проверка, установлен ли для поля эхо-символ

public boolean echoCharIsSet();
  • getColumns

Определение размера поля

public int getColumns();
  • getEchoChar

Получение текущего эхо-символа

public char getEchoChar();
  • minimumSize

Определение минимальных размеров области для отображения поля

public Dimension minimumSize();

Определение минимальных размеров области для отображения поля заданной ширины

public Dimension minimumSize(int cols);
  • paramString

Получение строки параметров

protected String paramString();
  • preferredSize

Определение оптимальных размеров области для отображения поля

public Dimension preferredSize();

Определение оптимальных размеров области для отображения поля заданной ширины

public Dimension preferredSize(int cols);
  • setEchoCharacter

Установка эхо-символа для отображения в поле

public void setEchoCharacter(char c);

Создание текстового поля класса TextField

При создании текстового поля вы можете выбрать один из четырех конструкторов, соответственно, для создания поля без текста и без указания размера, без текста заданного размера, для создания поля с текстом и для создания поля с текстом указанного размера.

Вот фрагмент кода, в котором создается поле с текстом, имеющее ширину, достаточную для размещения 35 символов:

TextField txt;
txt = new TextField(
  "Введите строку текста", 35);

Созданное поле добавляется в окно аплета методом add.

Большинство самых полезнных методов, необходимых для работы с полем класса TextField, определено в классе TextComponent, краткое описание которого мы привели ниже.

Класс TextComponent

Методы

  • getSelectedText

Получение текста, выделенного пользователем в окне поля

public String getSelectedText();
  • getSelectionEnd

Получение позиции конца выделенной области

public int getSelectionEnd();
  • getSelectionStart

Получение позиции начала выделенной области

public int getSelectionStart();
  • getText

Получение полного текста из поля

public String getText();
  • isEditable

Проверка, возможно ли редактирование текста в поле

public boolean isEditable();
  • paramString

Получение строки параметров

protected String paramString();
  • removeNotify

Удаление извещения

public void removeNotify();
  • select

Выделение заданной области текста

public void select(int selStart,
  int selEnd);
  • selectAll

Выделение всего текста

public void selectAll();
  • setEditable

Включение или выключение возможности редактирования текста

public void setEditable(boolean t);
  • setText

Установка текста в поле

public void setText(String t);

С помощью метода getText вы можете получить весь текст, который имеется в поле. Метод getSelectedText позволяет получить только ту часть текста, которая предварительно была выделена пользователем.

Приложение может выделить любой фрагмент текста или весь текст при помощи методов select и selectAll, соответственно.

Для записи текста в поле приложение может воспользоваться методом setText.

Возможно, для вас будет интересен метод setEditable, позволяющий переключать текстовое поля из режима, при котором редактирование заблокировано, в режим с разрешенным редактированием и обратно.


Многострочное текстовое поле класса TextArea

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

Если вам нужно поле для ввода многострочной информации, обратите внимание на класс TextArea. С его помощью вы можете создать многострочное поле заданной ширины и высоты, снабженное полосами просмотра.

Класс TextArea создан на базе класса TextComponent, рассмотренном нами ранее, поэтому для работы с многострочными полями вы можете использовать методы этого класса. В частности, вам доступен метод, с помощью которого можно получать из онка редактирования не весь текст, а только выделенную пользователем область.

Класс TextArea

Краткое описание класса TextArea мы привели ниже:

Конструкторы

Создание поля без текста и без указания размеров

public TextArea();

Создание поля без текста с указанием размеров

public TextArea(int rows, int cols);

Создание поля с текстом без указания размеров

public TextArea(String text);

Создание поля с текстом и с указанием размеров

public TextArea(String text,
  int rows, int cols);

Методы

  • addNotify

Вызов метода createTextArea

public void addNotify();
  • append

Добавление текста в поле редактирования

public void append(String str);
  • appendText

Добавление текста в поле редактирования. Этот метод устарел. Вы должны использовать вместо него метод append, описанный выше.

public void appendText(String str);
  • getColumns

Определение количества столбцов поля

public int getColumns();
  • getRows

Определение количества строк поля

public int getRows();
  • insertText

Добавление текста в поле редактирования начиная с заданной позиции

public void insertText(String str, int pos);
  • minimumSize

Определение минимальных размеров области для размещения многострочного текстового поля

public Dimension minimumSize();

Определение минимальных размеров области для размещения многострочного текстового поля с заданным количеством строк и столбцов

public Dimension minimumSize(int rows,
   int cols);
  • paramString

Получение строки параметров

protected String paramString();
  • preferredSize

Определение предпочтительных размеров области для размещения многострочного текстового поля

public Dimension preferredSize();

Определение предпочтительных размеров области для размещения многострочного текстового поля с заданным количеством строк и столбцов

public Dimension preferredSize(int rows,
   int cols);
  • replaceText

Замещение блока текста, начиная с первой позиции и до второй позиции

public void replaceText(String str,
   int start, int end);

Создание поля TextArea

Когда вы создаете многострочное текстовое поле редактирования, то можете использовать конструктор, допускающий указание размеров поля в строках и столбцах:

TextArea txt;
txt = new TextArea("Введите строку текста",
 5, 35);

Созданное поле добавляется в окно аплета методом add.

Отметим, что в классе TextArea есть методы для работы с блоками текста (вставка и замена), а также методы, с помощью которых можно определить количество строк и столбцов в поле редактирования.


Аплет FormDemo

Оглавление

Компоненты
Кнопки
Переключатели
Списки Choice
Списки List
Поле Label
Поле TextField
Поле TextArea
Аплет FormDemo

Назад Вперед

В аплете FormDemo мы покажем приемы работы с компонентами, такими как переключатели, кнопки, текстовые поля и списки.

Мы разместили несколько таких компонент в окне этого аплета (рис. 7) таким образом, что они образуют собой форму. В этой форме вы можете ввести имя и фамилию, выбрать один из трех режимов работы, а также цвет.

pic07sm.gif (1910 bytes) Рис. 7. Окно аплета FormDemo

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

Переключатели First и Second активизируют однострочные текстовые поля редактирования Enter your first name и Enter your second name. После того как пользователь нажмет кнопку Ready, содержимое активных полей, а также состояние переключателей Mode 1, Mode 2 и Mode 3 будет отображено в многострочном поле редактирования. Это поле находится в нижней части окна аплета.

С помощью списка, расположенного справа от переключателя Mode 3, можно задавать цвет фона многострочного поля. Цвет устанавливается сразу после выбора новой строки из этого списка.

К сожалению, при изменении размеров окна аплета находящиеся в нем компоненты изменяют свое расположение. Этот недостаток мы устраним после того, как расскажем вам о системе Layout Manager, с помощью которой вы можете управлять размещением компонент в окне аплета.

Исходный текст аплета FormDemo

Исходный текст аплета FormDemo вы найдете в листинге 1.

Листинг 1. Файл FormDemo.java

import java.applet.Applet;
import java.awt.*;
import java.util.*;
public class FormDemo extends Applet
{
  Button btReady;

  Checkbox chbox1;
  Checkbox chbox2;
  CheckboxGroup grRadio;
  Checkbox rd1;
  Checkbox rd2;
  Checkbox rd3;

  Choice ch1;
  Label lbFirstName;
  Label lbSecondName;
  TextField txtFirstName;
  TextField txtSecondName;
  TextArea  txta;
  public void init()
  {
    chbox1 = new Checkbox("First");
    add(chbox1);
    lbFirstName =
     new Label("Enter your first name:");
    add(lbFirstName);
    txtFirstName = new TextField(" ", 30);
    add(txtFirstName);
    chbox2 = new Checkbox("Second");
    add(chbox2);
    lbSecondName =
      new Label("Enter your second name:");
    add(lbSecondName);
    txtSecondName = new TextField(" ", 30);
    add(txtSecondName);
    grRadio = new CheckboxGroup();
    rd1 = new Checkbox("Mode 1",
      grRadio, true);
    rd2 = new Checkbox("Mode 2",
      grRadio, false);
    rd3 = new Checkbox("Mode 3",
      grRadio, false);
    add(rd1);
    add(rd2);
    add(rd3);
    ch1 = new Choice();
    ch1.addItem("White");
    ch1.addItem("Green");
    ch1.addItem("Yellow");
    add(ch1);
    setBackground(Color.yellow);
    lbFirstName.setBackground(Color.yellow);
    lbSecondName.setBackground(Color.yellow);
    rd1.setBackground(Color.yellow);
    rd2.setBackground(Color.yellow);
    rd3.setBackground(Color.yellow);
    chbox1.setBackground(Color.yellow);
    chbox2.setBackground(Color.yellow);
    txta = new TextArea("", 6, 45);
    add(txta);
    txta.setBackground(Color.white);
    btReady = new Button("Ready");
    add(btReady);
  }
  public String getAppletInfo()
  {
    return "Name: FormDemo";
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension =
      getSize();
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
  }
  public boolean action(Event evt,
    Object obj)
  {
    Button btn;
    String str1, str2;
    if(evt.target instanceof Button)
    {
      if(evt.target.equals(btReady))
      {
        btn = (Button)evt.target;
    str1 = txtFirstName.getText();
    str2 = txtSecondName.getText();
    if(chbox1.getState())
      txta.append(str1);
    if(chbox2.getState())
      txta.append(str2);
    if(rd1.getState())
      txta.append("\nMode 1\n");
    if(rd2.getState())
      txta.append("\nMode 2\n");
    if(rd3.getState())
      txta.append("\nMode 3\n");
      }
      else
      {
    return false;
      }
      return true;
    }
    else if(evt.target instanceof Choice)
    {
      if(evt.target.equals(ch1))
      {
    if(ch1.getSelectedIndex() == 0)
      txta.setBackground(Color.white);
    if(ch1.getSelectedIndex() == 1)
      txta.setBackground(Color.green);
    if(ch1.getSelectedIndex() == 2)
      txta.setBackground(Color.yellow);
      }
    }
    return false;
  }
}

В листинге 2 мы привели исходный текст документа HTML, созданный для нашего аплета системой Java WorkShop.

Листинг 2. Файл FormDemo.tmp.html

<applet
  name="FormDemo"
  code="FormDemo"
  codebase=
"file:/e:/sun/articles/vol6/src/FormDemo"
  width="500"
  height="600"
  align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
  <hr>If your browser
recognized the applet tag,
you would see an applet here.<hr>
</applet>

Описание исходного текста

Приведем краткое описание полей и методов, определенных в аплете FormDemo.

Поля главного класса

В главном классе нашего аплета мы определили несколько полей.

Поле btReady хранит ссылку на кнопку с надписью Ready:

Button btReady;

В полях chbox1 и chbox2 записаны ссылки на переключатели с независимой фиксацией, которые используются для активизации однострочных текстовых полей:

Checkbox chbox1;
Checkbox chbox2;

Поле grRadio хранит ссылку на группу переключателей с зависимой фиксацией, определяющих режимы работы Mode 1, Mode 2 и Mode 3:

CheckboxGroup grRadio;

Ссылки на эти переключатели находятся в следующих трех полях:

Checkbox rd1;
Checkbox rd2;
Checkbox rd3;

В поле ch1 хранится ссылка на список, предназначенный для выбора цвета:

Choice ch1;

Слева от однострочных полей редактирования в нашем окне имеются подписи, реализованные как объекты класса Label. Ссылки на эти объекты находятся в полях lbFirstName и lbSecondName:

Label lbFirstName;
Label lbSecondName;

Ссылки на однострочные поля редактирования записаны в поля с именами txtFirstName и txtSecondName:

TextField txtFirstName;
TextField txtSecondName;

И, наконец, ссылка на многострочное текстовое поле хранится в поле с именем txta:

TextArea  txta;

Метод init

Метод init занимается созданием компонент и добавлением их в окно алпета. Кроме того, этот метод изменяет цвет фона окна аплета и окон добавляемых компонент.

Прежде всего метод init создает два переключателя с независимой фиксацией, два объекта класса Label и два однострочных поля редактирования текста:

chbox1 = new Checkbox("First");
add(chbox1);
lbFirstName =
   new Label("Enter your first name:");
add(lbFirstName);
txtFirstName = new TextField(" ", 30);
add(txtFirstName);
chbox2 = new Checkbox("Second");
add(chbox2);
lbSecondName =
  new Label("Enter your second name:");
add(lbSecondName);
txtSecondName = new TextField(" ", 30);
add(txtSecondName);

Поля создаются при помощи конструкторов, а добавляются в окно аплета методом add. Согласно схемы расположения компонент, установленой по умолчанию, добавляемые компоненты размещаются сверху вниз и слева направо.

Для группы переключателей с зависимой фиксацией мы создаем объект класса CheckboxGroup:

grRadio = new CheckboxGroup();

Ссылка на этот объект затем передается в качестве второго параметра конструкторам, создающим переключатели:

rd1 = new Checkbox("Mode 1",
    grRadio, true);
rd2 = new Checkbox("Mode 2",
    grRadio, false);
rd3 = new Checkbox("Mode 3",
    grRadio, false);

Переключатели добавляются в окно аплета при помощи метода add:

add(rd1);
add(rd2);
add(rd3);

Список цветов создается как объект класса Choice:

ch1 = new Choice();

После создания списка мы добавляем в него три элемента, вызывая для этого метод addItem:

ch1.addItem("White");
ch1.addItem("Green");
ch1.addItem("Yellow");

Вслед за этим мы добавляем сформированный список в окно аплета:

add(ch1);

Для установки цвета фона мы вызываем метод setBackground без указания объекта:

setBackground(Color.yellow);

В этом случае метод вызывается для текущего объекта, то есть для нашего аплета. Чтобы установить цвет фона в окнах компонент, мы вызываем метод setBackground для соответствующих объектов, как это показано ниже:

lbFirstName.setBackground(Color.yellow);
lbSecondName.setBackground(Color.yellow);
rd1.setBackground(Color.yellow);
rd2.setBackground(Color.yellow);
rd3.setBackground(Color.yellow);
chbox1.setBackground(Color.yellow);
chbox2.setBackground(Color.yellow);

Многострочное текстовое поле создается как объект класса TextArea. В нем 6 строк и 45 столбцов:

txta = new TextArea("", 6, 45);
add(txta);

Первоначальный цвет фона многострочного текстового поля устанавливается тем же способом, чтомы использовали для других компонент:

txta.setBackground(Color.white);

Этот цвет в дальнейшем будет изменяться обработчиком событий, создаваемых списком цветов.

И, наконец, последнее что делает метод init перед тем как вернуть управление, - создает кнопку с надписью Ready и добавляет ее в окно аплета:

btReady = new Button("Ready");
add(btReady);

Метод action

В методе action мы определили рабочие поля btn, str1 и str2:

Button btn;
String str1, str2;

В начале своей работы метод action определяет, какой компонент вызвал событие. Для этого анализируется поле evt.target:

if(evt.target instanceof Button)
{
  . . .
  return true;
}
else if(evt.target instanceof Choice)
{
  . . .
  return true;
}
return false;

Наш метод action обрабатывает события, вызываемые объектами классов Button и Choice. Если событие вызвано компонентом, относящимся к какому-либо другому классу, метод возвращает значение false. Этим он сигнализирует, что обработка события не выполнялась.

В случае успешной обработки события метод action возвращает значение true.

Если событие вызвано кнопкой, наш метод action проверяет, какой именно. Обработка выполняется только в том случае, если через поле evt.target передается ссылка на кнопку btReady:

if(evt.target.equals(btReady))
{
  . . .
}
else
{
  return false;
}
  return true;

В противном случае метод action возвращает значение false, отказываясь от обработки события.

Что делает обработчик события, создаваемого кнопкой?

Прежде всего, он сохраняет ссылку на кнопку в рабочей переменной (просто для того чтобы показать, как это делается):

btn = (Button)evt.target;

Далее наш обработчик события извлекает текстовые строки из однострочных текстовых полей, вызывая для этого метод getText. Эти строки записываются в рабочие переменные str1 и str2:

str1 = txtFirstName.getText();
str2 = txtSecondName.getText();

Затемметод action проверяет состояние переключателей с независимой фиксацией chbox1 и chbox2. Если они включены, содержимое соответствующих временных переменных добавляется в многострочное текстовое поле txta:

if(chbox1.getState())
   txta.append(str1);
if(chbox2.getState())
   txta.append(str2);

Для добавления мы вызываем метод append.

Аналогичным образом преверяется состояние переключателей с зависимой фиксацией:

if(rd1.getState())
  txta.append("\nMode 1\n");
if(rd2.getState())
  txta.append("\nMode 2\n");
if(rd3.getState())
  txta.append("\nMode 3\n");

Если событие вызвано списокм цветов ch1, то метод action определяет, какая строка списка стала выделенной и устанавливает в многострочном поле редактирования соответствующий цвет фона. Для определения выделенной строки применяется метод getSelectedIndex:

if(evt.target.equals(ch1))
{
  if(ch1.getSelectedIndex() == 0)
    txta.setBackground(Color.white);
  if(ch1.getSelectedIndex() == 1)
    txta.setBackground(Color.green);
  if(ch1.getSelectedIndex() == 2)
    txta.setBackground(Color.yellow);
}

Работу остальных методов приложения FormDemo вы сможете разобрать самостоятельно.


Рис. 7. Окно аплета FormDemo

Для возврата сделайте щелчок мышью по рисунку

pic07.gif (6737 bytes)

Рис. 7. Окно аплета FormDemo


Работа с системой Layout Manager

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

В предыдущей статье мы рассказали вам о том, как создавать компоненты и размещать их в контейнере. Однако предложенный способ размещения компонент в окне контейнера едва ли можно назвать удобным, так как заранее трудно предугадать, на каком месте окажется тот или иной орган управления.

К счастью, имеются способы, позволяющие контролировать размещение отдельных компонент в окне контейнера. И хотя эти способы не позволяют задавать конкретные координаты и размеры органов управления, использовнные схемы размещения компонент будут правильно работать на любой аппаратной платформе (не забывайте, что Java создавалась как средство разработки приложений, способных выполняться на любой платформе).

В чем трудность создания пользовательского интерфейса для мультиплатформных систем?

В том, что разработчик приложения никогда не знает характеристики устройства отображения, установленные у пользователя. Он, в частности, не может заранее знать разрешение монитора, размер системного шрифта и другие характеристики, необходимые для компоновки диалоговых панелей в терминах абсолютных координат.

Средства пользовательского интерфейса AWT способны динамически измнять размеры компонент, подгоняя их "по месту" в системе пользователя. В результате значительно повышается вероятность того что внешний вид диалоговой панели, в каком она предстанет перед пользователем, будет похож на то, что ожидал разработчик.


Режимы системы Layout Manager

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

Прежде чем мы рассмотрим различные режимы компоновки системы Layout Manager, вспомним, как происходит наследование класса Applet (рис. 1).

pic01.gif (2563 bytes)

Рис. 1. Наследование класса Applet

Класс Applet наследуется от класса Panel, который, в свою очередь, наследуется от класса Container и Component. Класс Container пользуется интерфейсом LayoutManager, что позволяет выбирать для контейнеров один из нескольких режимов размещения компонент в окне контейнера.

Что же касается класса Panel, то для него по умолчанию выбирается режим размещения компонент с названием Flow Layout. Разумеется, вы можете выбрать другой режим размещения, указав его явным образом.

Ниже мы перечислили все возможные режимы системы Layout Manager:

Режим размещения компонент | Описание
FlowLayout | Компоненты заполняют окно контейнера "потоком" по мере их добавления методом add. Они размещаются слева направо и сверху вниз
GridLayout | Компоненты размещаются в виде таблицы по мере добавления слева направо и сверху вниз. Для этой таблицы можно указать количество столбцов и строк
GridBagLayout | Аналогично предыдущему, однако при добавлении компонент в таблицу можно указать координаты ячейки, в которую помещается компонента
BorderLayout | При размещении компоненты указывается одно из нескольких направлений: юг, север, запад, восток, центр. Направление определяется относительно центра окна контейнера
CardLayout | Размещение компонент друг над другом в одном окне. Этот режим позволяет организовать набор диалоговых панелей в виде блокнота

Каждому режиму соответсвует одноименный класс, методы и конструкторы которого позволяют выбирать различные компоновки.

Далее на примере конкретного приложения мы рассмотрим использование перечисленных выше режимов системы Layout Manager.


Режим FlowLayout

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

В этом режиме мы добавляли компоненты во всех примерах аплетов, приведенных ранее, так как по умолчанию для аплетов используется именно режим FlowLayout.

Класс FlowLayout

Ниже мы привели краткое описание класса FlowLayout:

Поля

Следующие три поля задают способы выравнивания:

  • CENTER

Центрирование

public final static int CENTER;
  • LEFT

По левой границе

public final static int LEFT;
  • RIGHT

По правой границе

public final static int RIGHT;

Конструкторы

Без указания выравнивания и зазора между компонентами

public FlowLayout();

С указанием выравнивания

public FlowLayout(int align);

С указанием выравнивания и зазора между компонентами по вертикали и горизонтали

public FlowLayout(int align,
  int hgap, int vgap);

Обычно приложения не вызывают методы класса FlowLayout, устанавливая варианты компоновки при помощи конструкторов.

Первый конструктор класса FlowLayout не имеет параметров. Он устанавливает по умолчанию режим центрирования компонент и зазор между компонентами по вертикали и горизонтали, равный 5 пикселам. Именно этот режим и использовался раньше во всех наших аплетах, так как именно он применяется по умолчанию объектами класса Panel, от которого наследуется класс Applet.

С помощью второго конструктора вы можете выбрать режим размещения с заданным выравниванием компонент в окне контейнера по горизонтали. В качестве параметров этому конструктору необходимо передавать значения FlowLayout.LEFT, FlowLayout.RIGHT, или FlowLayout.CENTER. Зазор между компонентами будет при этом равен по умолчанию 5 пикселам.

И, наконец, третий конструктор допускает раздельное указание режима выравнивания, а также зазоров между компонентами по вертикали и горизонтали в пикселах.

Методы

  • addLayoutComponent

Не используется

public void addLayoutComponent(
  String name, Component comp);
  • layoutContainer

Предназначен для того чтобы компоненты могли установить для себя предпочтительный размер

public void layoutContainer(
  Container target);
  • minimumLayoutSize

Определение минимального размера окна контейнера, необходимого для размещения всех компонент

public Dimension minimumLayoutSize(
  Container target);
  • preferredLayoutSize

Определение предпочтительного размера окна контейнера, необходимого для размещения всех компонент

public Dimension preferredLayoutSize(
  Container target);
  • removeLayoutComponent

Удаление компоненты из контейнера

public void removeLayoutComponent(
  Component comp);
  • toString

Получение строки названия метода компоновки

public String toString();


Режим GridLayout

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

В режиме GridLayout компоненты размещаются в ячейках таблицы, параметры которой можно задать с помощью конструкторов класса GridLayout.

При размещении компонент внутри ячеек таблицы все они получают одинаковые размеры. Если один из параметров, задающих размерность таблицы, равен нулю, это означает, что соответствующий столбец или строка может содержать любое количество элементов.

Заметим, что оба параметра rows и cols не могут быть равны нулю одновременно.

Приведем описание конструкторов класса GridLayout.

Конструкторы

Создание таблицы с заданным количеством строк и столбцов

public GridLayout(
  int rows, int cols);

Создание таблицы с заданным количеством строк и столбцов и с заданным зазором между компонентами

public GridLayout(int rows,
  int cols, int hgap, int vgap);

Методы

Методы класса GridLayout используются редко, поэтому мы их только перечислим.

public void addLayoutComponent(
  String name,Component comp);
public void layoutContainer(
  Container target);
public Dimension minimumLayoutSize(
  Container target);
public Dimension preferredLayoutSize(
  Container target);
public void removeLayoutComponent(
  Component comp);
public String toString();


Режим BorderLayout

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

При использовании режима BorderLayout окно контейнера разделяется на рамку и центральную часть. При размещении компонент указывается направление от центра окна, в котором слудует размещать компоненты.

Конструкторы класса BorderLayout

Ниже приведено краткое описание конструкторов класса BorderLayout.

public BorderLayout();
public BorderLayout(int hgap, int vgap);

Эти конструкторы предназначены для создания схемы размещения, без зазора между компонентами и с зазором заданной величины,соответственно.

Методы класса BorderLayout

Перечислим также методы класса BorderLayout:

public void  addLayoutComponent(
  String name, Component comp);
public void layoutContainer(
  Container target);
public Dimension minimumLayoutSize(
  Container target);
public Dimension preferredLayoutSize(
  Container target);
public void removeLayoutComponent(
  Component comp);
public String toString();

Применение класса BorderLayout

Добавляя компоненты к контейнеру, вы должны использовать метод add с двумя параметрами, первый из которых указывает направление размещения, а второй - ссылку на добавляемый объект:

add("North",  btn1);
add("East",   btn2);
add("West",   btn3);
add("South",  btn4);
add("Center", btn5);


Режим CardLayout

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

Режим CardLayout предназначен для создания набора диалоговых панелей, которые можно показывать по очереди в одном окне прямоугольной формы. Обычно для управления процессом перебора диалоговых панелей в режиме CardLayout используются отдельные органы управления, расположенные в другой панели или даже в другом аплете на той же самой странице сервера Web.

Класс CardLayout содержит два конструктора и несколько методов.

Конструкторы класса CardLayout

Режим без зазоров

public CardLayout();

Режим с зазорами по вертикали и горизонтали между компонентами и окном контейнера

public CardLayout(int hgap, int vgap);

Методы класса CardLayout

  • addLayoutComponent

Добавление компоненты с указанием имени

public void addLayoutComponent(
  String name, Component comp);
  • first

Отображение первой страницы блокнота

public void first(Container target);
  • last

Отображение последней страницы блокнота

public void last(Container target);
  • next

Отображение следующей страницы блокнота

public void next(Container target);
  • previous

Отображение предыдущей страницы блокнота

public void previous(Container target);
  • layoutContainer

Выполнение размещения компонент

public void layoutContainer(
  Container target);
  • minimumLayoutSize

Определение минимальных размеров окна, необходимых для размещения компонент

public Dimension minimumLayoutSize(
  Container target);
  • preferredLayoutSize

Определение предпочтительных размеров окна, необходимых для размещения компонент

public Dimension preferredLayoutSize(
  Container target);
  • removeLayoutComponent

Удаление заданной компоненты

public void removeLayoutComponent(
  Component comp);
  • show

Отображение произвольной страницы блокнота по ее имени

public void show(
  Container target, String name);
  • toString

Получение текстовой строки названия режима размещения

public String toString();

Использование режима размещения CardLayout

Как пользоваться режимом размещения CardLayout?

Обычно в окне аплета создается две панели, одна из которых предназначена для показа страниц блокнота в режиме размещения CardLayout, а вторая содержит органы управления перелистыванием страниц, например, кнопки.

Такие методы, как first, last, next и previous позволяют отображать, соответственно, первую, последнюю, следующую и предыдущую страницу блокнота. Если вызвать метод next при отображении последней страницы, в окне появится первая страница. Аналогично, при вызове метода previous для первой страницы блокнота вы увидите последнюю страницу.

А как отобразить произвольную страницу, не перебирая их по одной методами next и previous?

Для этого существует метод show. Учтите, что этот метод позволяет отображать только такие страницы, при добавлении которых методом add было указано имя, например:

pCardPanel.add("BackgroundColor",
  pBackgroundColor);
pCardPanel.add("ForegroundColor",
  pForegroundColor);
pCardPanel.add("Font", pFont);

Здесь в панель pCardPanel добавляются панели pBackgroundColor, pForegroundColor и pFont, имеющие имена, соответственно, "BackgroundColor", "ForegroundColor" и "Font".


Режим GridBagLayout

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

Режим GridBagLayout намного сложнее только что описанного режима GridLayout. Он позволяет размещать компоненты разного размера в таблице, задавая при этом для отдельных компонент размеры отступов и количество занимаемых ячеек.

Сейчас мы не будем рассматривать этот режим, так как сходные результаты могут быть достигнуты другими, менее сложными способами. Например, вы можете создать в контейнере несколько панелей, использовав внутри каждой свой метод размещения компонент.

Если вы создаете аплеты для размещения в документах HTML, никто не заставляет вас ограничиваться только одним аплетом для одного документа HTML - вы можете разместить там произвольное количество аплетов, организовав взаимодействие с одной стороны, между отдельными аплетами, а с другой - между аплетами и расширениями сервера Web.

В интегрированной системе разработки приложений Java WorkShop версии 2.0 имеется встроенная система визуального проектирования пользовательского интерфейса, в результате работы которой создаются исходные тексты классов. Размещение органов управления при этом выполняется интерактивными средствами.


Режим GridBagLayout

Аплет FormLayout

Оглавление

Layout Manager
Режимы
FlowLayout
GridLayout
BorderLayout
CardLayout
GridBagLayout
Аплет FormLayout

Назад Вперед

В окне аплета FormLayout (рис. 2) мы расположили те же самые органы управления, которые были использованы в предыдущем аплете FormDemo. Однако для указания способа размещения компонент мы выполнили настройку системы Layout Manager, выбрав режим GridLayout.

pic02sm.gif (2024 bytes) Рис. 2. Окно аплета FormLayout

Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению

И хотя пока еще внешний вид нашей формы оставляет желать лучшего, расположение отдельных компонент не изменяется при изменении размеров окна аплета.

Проблема заключается в том, что в режиме GridLayout не удается управлять размерами компонент. Для устранения этого недостатка следует использовать режим GridBagLayout. Так как этот режим сложен для использования без визуального проектирования, мы отложим дальнейшее совершенстовование нашей формы до тех пор, пока не займемся изучением соответствующих средств Java WorkShop.

Исходный текст аплета FormLayout

Исходный текст аплета FormLayout практически повторяет исходный текст аплета FormDemo, рассмотренный в нашей предыдущей статье. Единственное отличие заключается в том, что в методе init мы выполнили настройку системы Layout Manager, установив режим GridLayout:

public void init()
{
  setLayout(new GridLayout(4, 3));
  . . .
}

Здесь для размещения компонент в окне аплета создается таблица из четырех строк и трех столбцов.

Полный исходный текст аплета FormLayout вы найдете в листинге 1.

Листинг 1. Файл FormLayout.java

import java.applet.Applet;
import java.awt.*;
import java.util.*;
public class FormLayout extends Applet
{
  Button btReady;
  Checkbox chbox1;
  Checkbox chbox2;
  CheckboxGroup grRadio;
  Checkbox rd1;
  Checkbox rd2;
  Checkbox rd3;
  Choice ch1;
  Label lbFirstName;
  Label lbSecondName;
  TextField txtFirstName;
  TextField txtSecondName;
  TextArea  txta;
  public void init()
  {
    setLayout(new GridLayout(4, 3));
    chbox1 = new Checkbox("First");
    add(chbox1);
    lbFirstName = new Label(
      "Enter your first name:");
    add(lbFirstName);
    txtFirstName = new TextField(" ", 30);
    add(txtFirstName);
    chbox2 = new Checkbox("Second");
    add(chbox2);
    lbSecondName = new Label(
     "Enter your second name:");
    add(lbSecondName);
    txtSecondName = new TextField(" ", 30);
    add(txtSecondName);
    grRadio = new CheckboxGroup();
    rd1 = new Checkbox("Mode 1",
      grRadio, true);
    rd2 = new Checkbox("Mode 2",
      grRadio, false);
    rd3 = new Checkbox("Mode 3",
      grRadio, false);
    add(rd1);
    add(rd2);
    add(rd3);
    ch1 = new Choice();
    ch1.addItem("White");
    ch1.addItem("Green");
    ch1.addItem("Yellow");
    add(ch1);
    setBackground(Color.yellow);
    lbFirstName.setBackground(
      Color.yellow);
    lbSecondName.setBackground(
      Color.yellow);
    rd1.setBackground(Color.yellow);
    rd2.setBackground(Color.yellow);
    rd3.setBackground(Color.yellow);
    chbox1.setBackground(Color.yellow);
    chbox2.setBackground(Color.yellow);
    txta = new TextArea("", 6, 45);
    add(txta);
    txta.setBackground(Color.white);
    btReady = new Button("Ready");
    add(btReady);
  }
  public String getAppletInfo()
  {
    return "Name: FormDemo";
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension =
      getSize();
    g.setColor(Color.black);
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
  }
  public boolean action(Event evt,
    Object obj)
  {
    Button btn;
    String str1, str2;
    if(evt.target instanceof Button)
    {
      if(evt.target.equals(btReady))
      {
        btn = (Button)evt.target;
    str1 = txtFirstName.getText();
    str2 = txtSecondName.getText();
    if(chbox1.getState())
      txta.append(str1);
    if(chbox2.getState())
      txta.append(str2);
    if(rd1.getState())
      txta.append("\nMode 1\n");
    if(rd2.getState())
      txta.append("\nMode 2\n");
    if(rd3.getState())
      txta.append("\nMode 3\n");
      }
      else
      {
    return false;
      }
      return true;
    }
    else if(evt.target instanceof Choice)
    {
      if(evt.target.equals(ch1))
      {
    if(ch1.getSelectedIndex() == 0)
      txta.setBackground(Color.white);
    if(ch1.getSelectedIndex() == 1)
      txta.setBackground(Color.green);
    if(ch1.getSelectedIndex() == 2)
      txta.setBackground(Color.yellow);
      }
    }
    return false;
  }
}

Исходный текст документа HTML, созданный для нашего аплета системой Java WorkShop, представлен в листинге 2.

Листинг 2. Файл FormLayout.tmp.html

<applet
  name="FormLayout"
  code="FormLayout"
  codebase=
  "file:/e:/sun/articles/vol7/src/FormLayout"
  width="500"
  height="600"
  align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
</applet>


Рис. 2. Окно аплета FormLayout

Для возврата сделайте щелчок мышью по рисунку

pic02.gif (7045 bytes)

Рис. 2. Окно аплета FormLayout


Работа с панелями

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Панели, создаваемые на базе класса Panel, являются мощным средством организации диалогового интерфейса. Так как класс Panel произошел от класса Container, панель может содержать компоненты и другие панели. Для каждой панели можно определить режим размещения компонент, что позволяет создавать достаточно сложный пользовательский интерфейс.

В окне аплета вы можете создать несколько панелей, разделяющих его на части. В свою очередь, пространство, занимаемое панелями, также может быть разделено с использованием одного из описанных выше режимов размещения (рис. 1).

pic01.gif (3017 bytes)

Рис. 1. Размещение нескольких панелей в окне аплета

Отдельные панели могут содержать в себе такие компоненты, как кнопки, переключатели, списки, текстовые поля и так далее.


Создание панелей

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Панель создается очень просто. Прежде всего необходимо выбрать для окна аплета схему размещения компонент, соответствующую требуему расположению панелей. Например, для создания в окне аплета двух панелей, разделяющих его по горизонтали, следует выбрать режим GridLayout:

setLayout(new GridLayout(2, 1));

Панели будут размещаться в ячейках таблицы, состоящей из одного столбца и двух строк.

Далее нужно создать объекты класса Panel:

Panel pTopPanel;
pTopPanel = new Panel();
Panel pBottomPanel;
pBottomPanel = new Panel();

Ссылка на панель, которая будет располагаться сверху, записывается в переменную pTopPanel, а на ту, что будет располагаться снизу - в переменную pBottomPanel.


Добавление панелей

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Создав панели, вы можете добавить их в окно аплета, вызвав метод add, как это показано ниже:

add(pTopPanel);
add(pBottomPanel);

Заметим, что вы можете добавлять панели в панели, указывая, для какой панели нужно вызывать метод add:

Panel pLeft;
Panel pRight;
pLeft  = new Panel();
pRight = new Panel();
pTopPanel.setLayout(new GridLayout(1, 2));
pTopPanel.add(pLeft);
pTopPanel.add(pRight);

Здесь мы создали две панели pLeft и pRight, которые по нашему замыслу должны разделить пространство панели pTopPanel на две части по вертикали. Для обеспечения вертикального размещения панелей pLeft и pRight в панели pTopPanel мы вызвали для панели pTopPanel метод setLayout. При этом мы указали, что компоненты, добавляемые в эту панель, должны размещаться в таблице, состоящей из односй строки и двух столбцов.

Затем панели pLeft и pRight были добавлены в панель pTopPanel методом add.


Добавление компонент в панели

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Для добавления компонент в панель вы должны указать, для какой панели вызывается метод add, например:

Botton btn1;
Botton btn2;
btn1 = new Button();
btn2 = new Button();
pBottomPanel.add(btn1);
pBottomPanel.add(btn2);


Рисование в окне панели

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Как вы знаете, для того чтобы что-нибудь нарисовать, необходимо вначале получить контекст отображения. Методу paint передается контекст отображения, связанный с окном аплета. Если в окне имеются панели, то для рисования внутри них необходимо получить контекст отображения окон панелей.

Проще всего это сделать с помощью метода getGraphics, вызвав его для объекта класса Panel:

Graphics gpDraw;
gpDraw = pDraw.getGraphics();

Здесь в переменную gpDraw мы записали ссылку на контекст отображения для панели pDraw.

Получив контекст отображения, можно приступить к рисованию. Вот, например, как можно нарисовать вокруг панели тонкую рамку:

Dimension dimAppWndDimension = pDraw.size();
gpDraw.drawRect(0, 0,
  dimAppWndDimension.width  - 1,
  dimAppWndDimension.height - 1);

В этом фрагменте кода мы вначале определили размеры панели, вызвав для нее метод size, а затем при помощи метода drawRect, вызванного для контекста отображения gpDraw, нарисовали рамку.

Для установки шрифта и рисования текста в окне панели вы также должны указывать ссылку на контекст отображения вашей панели:

gpDraw.setFont(new Font("Courier",
  Font.PLAIN, 12));
gpDraw.drawString(
  "Текст внутри окна панели", 10, 50);

Другой способ основан на создании собственного класса на базе класса Panel и переопределения в этом классе метода paint.


Рисование в окне панели

Создание нового класса на базе класса Panel

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Если ваш аплет создает много панелей, техника рисования в окнах этих панелей, описанная выше, может привести к усложнению исходного текста приложения. Так как рисование в окнах панелей выполняется в методе paint класса аплета, вам придется получать контекст отображения для каждой панели.

Намного проще создать несколько дочерних классов от класса Panel, переопределив в каждом из них метод paint. В этом случае для каждой панели вы можете создать свой метода paint, которому будет автоматически передаваться контекст отображения, связанный с окном соответствующей панели.

В аплете Options, который мы рассмотрим ниже, использована именно такая методика работы с панелями.


Аплет Options

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Аплет Options демонстрирует методики работы с панелями, а также с различными режимами системы Layout Manager.

В окне аплета Options мы создали три панели (рис. 2).

pic02.gif (4687 bytes)

Рис. 2. Окно аплета Options

В верхней панели отображается текстовая строка First panel. Цвет и шрифт этой строки, а также цвет фона можно задавать при помощи второй панели, расположенной в центре окна нашего аплета.

Вторая панель представляет собой блокнот, на страницах которого находятся списки цвета фона, текста и шрифтов. С помощью кнопок нижней панели вы можете перелистывать страницы этого блокнота. На рис. 3 и 4 мы показали страницы, предназначенные для выбора цвета фона и цвета текста, соответственно.

pic03.gif (4762 bytes)

Рис. 3. Выбор цвета фона

pic04.gif (4864 bytes)

Рис. 4. Выбор цвета текста

Нажимая кнопки Background Color, Foreground Color и Set Font, вы можете отображать нужные вам страницы блокнота. С помощью кнопок Next и Prev можно перебирать страницы блокнота в прямом или обратном направлении, соответственно.


Исходный текст аплета Options

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Исходный текст аплета Options представлен в листинге 1.

Листинг 1. Файл Options.java

import java.applet.*;
import java.awt.*;
public class Options extends Applet
{
  FirstPanel pPanel1;
  CardPanel pCard;
  ControlPanel pControl;
  public String getAppletInfo()
  {
    return "Name: Options";
  }
  public void init()
  {
    setLayout(new GridLayout(3, 1));
    pPanel1 = new FirstPanel();
    add(pPanel1);
    pCard = new CardPanel(pPanel1);
    add(pCard);
    pControl = new ControlPanel(pCard);
    add(pControl);
    pPanel1.setBackground(Color.yellow);
    pPanel1.setForeground(Color.black);
    repaint();
  }
}
class FirstPanel extends Panel
{
  String szFontName = "TimesRoman";
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension = getSize();
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    g.setFont(new Font(szFontName,
      Font.PLAIN, 24));
    g.drawString("First panel", 10, 50);
    super.paint(g);
  }
}
class CardPanel extends Panel
{
  Panel pBgColor;
  Panel pFgColor;
  Panel pFont;
  Panel pControlled;
  Choice chBgColor;
  Choice chFgColor;
  Choice chFont;
  Label lbBgColor;
  Label lbFgColor;
  Label lbFont;
  public CardPanel(Panel pControlledPanel)
  {
    pControlled = pControlledPanel;
    setLayout(new CardLayout(5, 5));
    pBgColor = new Panel();
    pFgColor = new Panel();
    pFont    = new Panel();
    add("BgColor", pBgColor);
    add("FgColor", pFgColor);
    add("Font", pFont);
    chBgColor = new Choice();
    chFgColor = new Choice();
    chFont    = new Choice();
    chBgColor.add("Yellow");
    chBgColor.add("Green");
    chBgColor.add("White");
    chFgColor.add("Black");
    chFgColor.add("Red");
    chFgColor.add("Green");
    chFont.add("TimesRoman");
    chFont.add("Helvetica");
    chFont.add("Courier");
    lbBgColor = new Label("Background color");
    lbFgColor = new Label("Foreground color");
    lbFont = new Label("Font");
    pBgColor.add(lbBgColor);
    pBgColor.add(chBgColor);
    pFgColor.add(lbFgColor);
    pFgColor.add(chFgColor);
    pFont.add(lbFont);
    pFont.add(chFont);
  }
  public void paint(Graphics g)
  {
    Dimension dimAppWndDimension = getSize();
    g.drawRect(0, 0,
      dimAppWndDimension.width  - 1,
      dimAppWndDimension.height - 1);
    super.paint(g);
  }
  public boolean action(Event evt, Object obj)
  {
    Choice ch;
    if(evt.target instanceof Choice)
    {
      ch = (Choice)evt.target;
      if(evt.target.equals(chBgColor))
      {
    if(ch.getSelectedIndex() == 0)
      pControlled.setBackground(
            Color.yellow);
    else if(ch.getSelectedIndex() == 1)
      pControlled.setBackground(
            Color.green);
    else if(ch.getSelectedIndex() == 2)
      pControlled.setBackground(
            Color.white);
      }
      else if(evt.target.equals(chFgColor))
      {
    if(ch.getSelectedIndex() == 0)
      pControlled.setForeground(
            Color.black);
    else if(ch.getSelectedIndex() == 1)
      pControlled.setForeground(
            Color.red);
    else if(ch.getSelectedIndex() == 2)
      pControlled.setForeground(
            Color.green);
      }
      else if(evt.target.equals(chFont))
      {
    if(ch.getSelectedIndex() == 0)
      ((FirstPanel)pControlled).szFontName
            = "TimesRoman";
    else if(ch.getSelectedIndex() == 1)
      ((FirstPanel)pControlled).szFontName
            = "Helvetica";
    else if(ch.getSelectedIndex() == 2)
      ((FirstPanel)pControlled).szFontName
            = "Courier";
      }
      else
      {
    return false;
      }
      pControlled.repaint();
      return true;
    }
    return false;
  }
}
class ControlPanel extends Panel
{
  Button btNext;
  Button btPrev;
  Button btBgColor;
  Button btFgColor;
  Button btFont;
  Panel pCard;
  public ControlPanel(Panel pCardPanel)
  {
    pCard = pCardPanel;
    setLayout(new GridLayout(2,3));
    btBgColor = new Button("Background Color");
    btFgColor = new Button("Foreground Color");
    btFont = new Button("Set Font");
    btNext = new Button("Next");
    btPrev = new Button("Prev");
    add(btBgColor);
    add(btFgColor);
    add(btFont);
    add(btNext);
    add(btPrev);
  }
  public boolean action(Event evt, Object obj)
  {
    if(evt.target instanceof Button)
    {
      if(evt.target.equals(btBgColor))
      {
    ((CardLayout)pCard.getLayout()).show(
            pCard, "BgColor");
      }
      else if(evt.target.equals(btFgColor))
      {
    ((CardLayout)pCard.getLayout()).show(
            pCard, "FgColor");
      }
      else if(evt.target.equals(btFont))
      {
    ((CardLayout)pCard.getLayout()).show(
            pCard, "Font");
      }
      else if(evt.target.equals(btNext))
      {
    ((CardLayout)pCard.getLayout()).next(
            pCard);
      }
      else if(evt.target.equals(btPrev))
      {
    ((CardLayout)pCard.getLayout()).
            previous(pCard);
      }
      else
      {
    return false;
      }
      return true;
    }
    return false;
  }
}

Исходный текст документа HTML, созданный для аплета Options системой Java WorkShop, представлен в листинге 2.

Листинг 2. Файл Options.tmp.html

<applet name="Options"
  code="Options"
  codebase=
"file:/E:/Sun/Articles/vol8/src/Options"
  width="500"
  height="600"
  align="Top"
  alt="If you had a java-enabled browser,
 you would see an applet here.">
 <hr>If your browser recognized the applet tag,
you would see an applet here.<hr>
</applet>


Описание исходного текста аплета Options

Оглавление

Работа с панелями
Создание панелей
Добавление панелей
Добавление компонент
Рисование в панели
Класс на базе Panel
Аплет Options
Исходный текст
Описание текста

Назад Вперед

Помимо основного класса Options в нашем аплете создается еще три класса для панелей с именами FirstPanel, CardPanel и ControlPanel.

Класс FirstPanel соответствует самой верхней панели, в которой отображается строка текста First panel. Классы CardPanel и ControlPanel испльзуются для создания панелей со списками и управляющими кнопками, соответственно. Мы будем рассматривать эти классы по отдельности.

Главный класс аплета Options

В главном классе аплета Options мы определили три поля с именами pPanel1, pCard и pControl:

FirstPanel   pPanel1;
CardPanel    pCard;
ControlPanel pControl;

В них хранятся ссылки на три класса, созданных нами для трех панелей.

Метод init

Прежде всего метод init устанавливает для окна аплета режим размещения GridLayout:

setLayout(new GridLayout(3, 1));

Окно аплета делится на три горизнтальные области, в которых мы будем размещать панели.

Панели создаются с помощью оператора new как объекты соответствующих классов, определенных в нашем приложении:

pPanel1 = new FirstPanel();
add(pPanel1);
pCard = new CardPanel(pPanel1);
add(pCard);
pControl = new ControlPanel(pCard);
add(pControl);

Для добавления панелей в окно аплета мы использовали метод add.

Далее метод init устанавливает начальные значения для цвета фона и текста верхней панели:

pPanel1.setBackground(Color.yellow);
pPanel1.setForeground(Color.black);

Обратите внимание, что мы вызываем методы setBackground и setForeground для объекта pPanel1.

После выполнения всех этих действий метод init перерисовывает окно аплета, вызывая метод repaint:

repaint();

Класс FirstPanel

Мы создали класс FirstPanel на базе класса Panel, определив в нем одно поле типа String и переопределив метод paint:

class FirstPanel extends Panel
{
  . . .
}

Текстовое поле szFontName хранит название шрифта, с использованием которого в окне верхней панели отображается текстовая строка:

String szFontName = "TimesRoman";

Метод paint определяет текущие размеры панели и рисует вокруг нее прямоугольную рамку:

Dimension dimAppWndDimension = getSize();
g.drawRect(0, 0,
  dimAppWndDimension.width  - 1,
  dimAppWndDimension.height - 1);

Далее метод paint выбирает в контекст отображения, связанный с панелью, шрифт с названием szFontName и рисует текстовую строку:

g.setFont(new Font(szFontName,
    Font.PLAIN, 24));
g.drawString("First panel", 10, 50);

Заметим, что сразу после запуска аплета рамка и строка будут нарисованы с использованием черного цвета, выбранного в контекст отображения панели по умолчанию. В дальнейшем вы можете изменить этот цвет при помощи соответствующей страницы блокнота, реализованного во второй панели.

Последнее действие, которое выполняет метод paint первой панели - вызов метода paint из родительского класса:

super.paint(g);

Это приводит к перерисовке окна аплета.

Класс CardPanel

С помощью класса CardPanel мы создали панель для блокнота, содержащего три страницы. Этот класс, так же как и предыдущий, создан на базе класса Panel.

Поля класса CardPanel

В полях pBgColor, pFgColor и pFont хранятся ссылки на панели страниц блокнота, которые мы разместим внутри панели класса CardPanel:

Panel pBgColor;
Panel pFgColor;
Panel pFont;

Кроме того, в поле pControlled хранится ссылка на верхнюю панель с текстовой строкой First Panel.

Panel pControlled;

Это поле будет проинициализировано конструктором класса CardPanel.

В следующих трех полях мы храним ссылки на списки класса Choice, предназначенные, соответственно, для выбора цвета текста, цвета фона и шрифта:

Choice chBgColor;
Choice chFgColor;
Choice chFont;

Три поля класса Label содержат ссылки на подписи к указанным выше спискам:

Label lbBgColor;
Label lbFgColor;
Label lbFont;

Конструктор класса CardPanel

При создании объекта класса CardPanel мы передаем конструктору ссылку на верхнюю панель, параметрами которой нужно управлять. Конструктор записывает эту ссылку в поле pControlled:

public CardPanel(Panel pControlledPanel)
{
  pControlled = pControlledPanel;
   . . .
}

Затем конструктор устанавливает режим размещения CardLayout, оставляя зазор по вертикали и горизонтали, равный пяти пикселам:

setLayout(new CardLayout(5, 5));

На следующем этапе мы создаем три панели для страниц блокнота и добавляем их в панель CardPanel, задавая имена:

pBgColor = new Panel();
pFgColor = new Panel();
pFont    = new Panel();
add("BgColor", pBgColor);
add("FgColor", pFgColor);
add("Font", pFont);

Теперь нам нужно создать и заполнить три списка, предназначенный для выбора цвета и шрифта. Эти списки создаются как объекты класса Choice:

chBgColor = new Choice();
chFgColor = new Choice();
chFont    = new Choice();

После создания списки наполняются текстовыми строками. В каждый список мы добавляем по три строки:

chBgColor.add("Yellow");
chBgColor.add("Green");
chBgColor.add("White");
chFgColor.add("Black");
chFgColor.add("Red");
chFgColor.add("Green");
chFont.add("TimesRoman");
chFont.add("Helvetica");
chFont.add("Courier");

Для того чтобы снабдить списки подписями, мы создаем три объекта класса Label:

lbBgColor = new Label("Background color");
lbFgColor = new Label("Foreground color");
lbFont = new Label("Font");

Эти объекты, а также списки добавляются на свои страницы блокнота (то есть в свои панели):

pBgColor.add(lbBgColor);
pBgColor.add(chBgColor);
pFgColor.add(lbFgColor);
pFgColor.add(chFgColor);
pFont.add(lbFont);
pFont.add(chFont);

На этом работа метода init заканчивается.

Метод action

Метод action обрабатывает события, возникающие в результате выбора новых значений из списков, расположенных на страницах блокнота. Схема обработки событий не имеет никаких особенностей.

Вначале метод action проверяет, что событие вызвано списком класса Choice:

if(evt.target instanceof Choice)
{
  . . .
    return true;
  }
  return false;
}

События, связанные с изменением цвета фона, обрабатываются следующим образом:

ch = (Choice)evt.target;
if(evt.target.equals(chBgColor))
{
  if(ch.getSelectedIndex() == 0)
    pControlled.setBackground(
    Color.yellow);
  else if(ch.getSelectedIndex() == 1)
    pControlled.setBackground(
    Color.green);
  else if(ch.getSelectedIndex() == 2)
    pControlled.setBackground(
    Color.white);
}

Здесь метод setBackground вызывается для объекта, ссылка на который передана конструктору класса и записана в поле pControlled. Это ссылка на панель, размещенную в верхней части окна нашего аплета.

Аналогичным образом изменяется цвет текста и рамки для верхней панели:

else if(evt.target.equals(chFgColor))
{
  if(ch.getSelectedIndex() == 0)
    pControlled.setForeground(
    Color.black);
else if(ch.getSelectedIndex() == 1)
    pControlled.setForeground(
    Color.red);
else if(ch.getSelectedIndex() == 2)
    pControlled.setForeground(
    Color.green);
}

Для изменения шрифта мы устанавливаем новое значение переменной поля szFontName, определенной в классе FirstPanel:

else if(evt.target.equals(chFont))
{
  if(ch.getSelectedIndex() == 0)
    ((FirstPanel)pControlled).szFontName
     = "TimesRoman";
  else if(ch.getSelectedIndex() == 1)
    ((FirstPanel)pControlled).szFontName
     = "Helvetica";
  else if(ch.getSelectedIndex() == 2)
    ((FirstPanel)pControlled).szFontName
     = "Courier";
}

Для того чтобы адресоваться к полю szFontName, нам пришлось выполнить явное преобразование типа ссылки pControlled.

Последнее действие, которое совершает метод action - это перерисовка окна верхней панели, которая выполняется с помощью метода repaint:

pControlled.repaint();

Класс ControlPanel

Класс ControlPanel создан для нижней панели с управляющими кнопками.

Поля класса ControlPanel

Следующие пять полей хранят ссылки на кнопки, управляющие страницами блокнота:

Button btNext;
Button btPrev;
Button btBgColor;
Button btFgColor;
Button btFont;

Поле pCard хранит ссылку на панель блокнота:

Panel pCard;

Эта ссылка инициализируется конструктором класса.

Конструктор класса ControlPanel

В задачу конструктора класса ControlPanel входит запоминание ссылки на панель блокнота, установка режима размещения компонент GridLayout, а также создание и добавление в нижнюю панель управляющих кнопок:

public ControlPanel(Panel pCardPanel)
{
  pCard = pCardPanel;
  setLayout(new GridLayout(2,3));
  btBgColor = new Button("Background Color");
  btFgColor = new Button("Foreground Color");
  btFont = new Button("Set Font");
  btNext = new Button("Next");
  btPrev = new Button("Prev");
  add(btBgColor);
  add(btFgColor);
  add(btFont);
  add(btNext);
  add(btPrev);
}

Кнопки располагаются в ячейках таблицы, содержащей две строки и три столбца. В целом конструктор класса ControlPanel не имеет никаких интересных особенностей.

Метод action

Метод action управляет работой блокнота, отображая его страницы.

Когда пользователь нажимает на кнопки, выбирающие страницы блокнота, метод action выдвигает нужную страницу на передний план с помощью метода show:

if(evt.target.equals(btBgColor))
{
  ((CardLayout)pCard.getLayout()).show(
    pCard, "BgColor");
}
else if(evt.target.equals(btFgColor))
{
  ((CardLayout)pCard.getLayout()).show(
  pCard, "FgColor");
}
else if(evt.target.equals(btFont))
{
  ((CardLayout)pCard.getLayout()).show(
    pCard, "Font");
}

В качестве первого параметра этому методу передается идентификатор панели блокнота, а в качестве второго - имя страницы, которую необходимо отобразить.

Циклический перебор страниц блокнота выполняется с помощью методов next и previous, соответственно:

else if(evt.target.equals(btNext))
{
  ((CardLayout)pCard.getLayout()).next(
    pCard);
}
else if(evt.target.equals(btPrev))
{
  ((CardLayout)pCard.getLayout()).
    previous(pCard);
}


Окна и диалоговые панели

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

До сих пор мы рисовали только в окне аплета или в окнах панелей, расположенных внутри окна аплета. Однако есть и другая возможность - приложения Java, полноценные и аплеты, могут создавать обычные перекрывающиеся окна, такие, например, как окно браузера. Эти окна могут иметь меню (в отличие от окон аплетов). Пользователь может изменять размер таких окон при помощи мыши, перемещая рамку окна.

В составе библиотеки классов AWT имеется несколько классов, предназначенных для работы с окнами. Это класс Window, который произошел от класса Container, и его дочерние классы - Frame, Dialog и FileDialog (рис. 1).

pic01.gif (2857 bytes)

Рис. 1. Иерархия классов, предназначенных для создания окон

Окно, созданное на базе класса Frame, больше всего похоже на главное окно обычного приложения Windows. Оно может иметь главное меню, для него можно устанавливать форму курсора. Внутри такого окна можно рисовать. Так как окно класса Frame (так же как и другие окна AWT) произошли от класса Container, вы можете добавлять в них различные компоненты и панели, как мы это делали с окнами аплетов и панелей.

На базе класса Dialog создаются окна диалоговых панелей, очень похожих на обычные диалоговые панели Windows. Такие панели не могут иметь меню и обычно предназначены для запроса какой-либо информации у пользователя.

Класс FileDialog предназначен для создания диалоговых панелей диалоговые панели, с помощью которых можно выбирать файлы на локальных дисках компьютера.

Что же касается класса Window, то непосредственно этот класс редко применяется для создания окон, так как классы Frame, Dialog и FileDialog более удобны и обеспечивают все необходимые возможности.


Окна класса Frame

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Ниже мы привели краткое описание класса Frame. Так как этот класс реализует интерфейс java.awt.MenuContainer, окно класса Frame может содержать меню.

Поля

С помощью полей класса Frame вы можете задавать для своего окна различные типы курсоров:

public final static int CROSSHAIR_CURSOR;
public final static int DEFAULT_CURSOR;
public final static int E_RESIZE_CURSOR;
public final static int HAND_CURSOR;
public final static int MOVE_CURSOR;
public final static int N_RESIZE_CURSOR;
public final static int NE_RESIZE_CURSOR;
public final static int NW_RESIZE_CURSOR;
public final static int S_RESIZE_CURSOR;
public final static int SE_RESIZE_CURSOR;
public final static int SW_RESIZE_CURSOR;
public final static int TEXT_CURSOR;
public final static int W_RESIZE_CURSOR;
public final static int WAIT_CURSOR;

Конструкторы

Для класса Frame определено два конструктора:

Создание окна без заголовка

public Frame();

Создание окна с заголовоком

public Frame(String title);

Методы

  • addNotify

Вызов метода createFrame

public void addNotify();

dispose

Удаление окна и освобождение связанных с ним ресурсов

public void dispose();

getCursorType

Определение типа курсора

public int getCursorType();
  • getIconImage

Получение пиктограммы, установленной для окна

public Image getIconImage();
  • getMenuBar

Получение ссылки на главное меню

public MenuBar getMenuBar();
  • getTitle

Получение заголовка окна

public String getTitle();
  • isResizable

Определение возможности изменения размеров окна пользователем

public boolean isResizable();
  • paramString

Получение строки параметров

protected String paramString();
  • remove

Удаление компоненты меню

public void remove(MenuComponent m);
  • setCursor

Установка типа курсора

public void setCursor(int cursorType);
  • setIconImage

Установка пиктограммы

public void setIconImage(Image image);
  • setMenuBar

Установка главного меню

public void setMenuBar(MenuBar mb);
  • setResizable

Включение или выключение возомжности изменения размеров окна

public void setResizable(boolean resizable);
  • setTitle

Установка заголовка окна

public void setTitle(String title);

Применение класса Frame

Для того чтобы создать свое окно на базе класса Frame, вы должны определить свой класс, унаследовав его от класса Frame следующим образом:

class MainFrameWnd extends Frame
{
  . . .
  public MainFrameWnd(String sTitle)
  {
    super(sTitle);
     . . .
    resize(400, 200);
  }
  . . .
}

Если мы будем создавать окно с заголовком, нам необходимо соответствующим образом определить конструктор класса этого окна. В частности, наш конструктор должен вызывать конструктор базового класса, передавая ему в качестве параметра строку заголовка окна. Напомним, что конструктор базового класса должен вызываться в конструкторе дочернего класса перед выполнением каких-либо других действий.

Обратите также внимание на вызов метода resize. Этот вызов необходим для задания размеров окна.

В конструкторе вы можете определить различные параметры создаваемого вами окна, например, указать форму курсора, пиктограмму, представляющую окно, задать меню, определить возможность изменения размеров окна и так далее. Мы остановимся подробнее на процедуре добавления меню к окну класса Frame, так как она требует пояснений. С изменением других характеристик окна вы справитесь самостоятельно.

При создании окна классов Frame и Dialog для них устанавливается режим размещения BorderLayout. Если вам нужен другой режим размещения, необходимо установить его явным образом.

Кроме того, созданное окно появится на экране только после вызова для него метода show.

Убрать окно с экрана вы можете методом hide. Этот метод прячет окно, но оставляет в памяти все связанные с ним ресурсы, поэтому вы сможете вновь отобразить спрятанное окно, вызвав метод show.

В отличие от метода hide, метод dispose удаляет окно и освобождает все связанные с ним ресурсы. Этот метод применяется для окончательного удаления окна с экрана и из памяти.

Еще одно замечание касается обработки операции уничтожения окна при помощи двойного щелчка левой клавиши мыши по системному меню окна или при помощи кнопки уничтожения окна, расположенной в правой части заголовка.

Когда пользователь пытается уничтожить окно класса Frame или Dialog подобным образом, возникает событие Event.WINDOW_DESTROY. Вы должны предусмотреть обработку этого события, выполняя действия, соответствующие логике работы вашего окна. Обычно окно уничтожается вызовом метода dispose, как это показано ниже:

public boolean handleEvent(Event evt)
{
  if(evt.id == Event.WINDOW_DESTROY)
  {
    dispose();
    return true;
  }
  else
    return super.handleEvent(evt);
}


Меню в окне класса Frame

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Как мы уже говорили, окно класса Frame может иметь главное меню (Menu Bar) или, как еще говорят, строку меню. Главное меню создается на базе класса MenuBar, краткое описание которого приведено ниже.

Конструктор

public MenuBar();

Методы

  • add

Добавление меню в главное меню окна

public Menu add(Menu m);
  • addNotify

Вызов метода createMenuBar

public void addNotify();
  • countMenus

Определение количества меню, добавленных в главное меню

public int countMenus();
  • getHelpMenu

Получение ссылки на меню Help

public Menu getHelpMenu();
  • getMenu

Получение ссылки на меню с заданным номером

public Menu getMenu(int i);
  • remove

Удаление меню с заданным номером из главного меню

public void remove(int index);

Удаление компоненты меню

public void remove(MenuComponent m);
  • removeNotify

Извещение об удалении меню

public void removeNotify();
  • setHelpMenu

Установка меню Help

public void setHelpMenu(Menu m);

Работа с классом MenuBar

Для формирования главного меню окна вы должны создать объект класса MenuBar с помощью конструктора, а затем добавить в него отдельные меню.

Объект главного меню создается следующим образом:

MenuBar mbMainMenuBar;
mbMainMenuBar = new MenuBar();

Отдельные меню создаются на базе класса Menu, например:

Menu mnFile;
Menu mnHelp;
mnFile = new Menu("File");
mnHelp = new Menu("Help");

Создав меню, вы должны добавить в них строки. Для этого нужно вызвать метод add, передав ему в качестве параметра текст строки меню, например:

mnFile.add("New");
mnFile.add("-");
mnFile.add("Exit");

mnHelp.add("Content");
mnHelp.add("-");
mnHelp.add("About");

Далее сформированные меню добавляются в главное меню:

mbMainMenuBar.add(mnFile);
mbMainMenuBar.add(mnHelp);

И, наконец, теперь можно устанавливать главное меню в окне класса, созданного на базе класса Frame:

setMenuBar(mbMainMenuBar);


Класс Menu

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Для того чтобы дать вам представление о том, что можно делать с меню, приведем краткое описание класса Menu:

Конструкторы

Создание меню с заданным названием
public Menu(String label);

Создание меню с заданным названием,которое может оставаться на экране после того как пользователь отпустил клавишу мыши

public Menu(String label, boolean tearOff);

Методы

  • add

Добавление элемента меню

public MenuItem add(MenuItem mi);

Добавление строки в меню

public void add(String label);
  • addNotify

Вызов метода createMenu

public void addNotify();
  • addSeparator

Добавление разделителя в меню

public void addSeparator();
  • countItems

Определение количества строк в меню

public int countItems();
  • getItem

Получение ссылки на элемент меню с заданным номером

public MenuItem getItem(int index);
  • isTearOff

Проверка, остается ли меню на экране после того как пользователь отпустил клавишу мыши

public boolean isTearOff();
  • remove

Удаление заданного элемента меню

public void remove(int index);

Удаление заданной компоненты меню

public void remove(MenuComponent item);
  • removeNotify

Извещение об удалении меню

public void removeNotify();

Работа с классом Menu

Метод addSeparator используется для добавления в меню разделительной строки. Аналогичный результат достигается и при добавлении в меню стоки "-":

mnHelp.add("-");

Заметим, что вы можете просто добавлять в меню строки по их названию, пользуясь методом add(String label), либо добавлять в меню элементы класса MenuItem, вызывая метод add(MenuItem mi).


Класс MenuItem

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Класс MenuItem определяет поведение отдельных элементов меню.

Пользуясь методами класса MenuItem вы можете блокировать или разблокировать отдельные строки меню. Это нужно делать, например, если в данный момент функция, соответствующая строке меню, недоступна или не определена. Вы также можете изменять текстовые строки, соответствующие элементам меню, что может пригодиться для переопределения их назначения.

Конструктор

public MenuItem(String label);

Методы

  • addNotify

Вызов метода createMenuItem

public void addNotify();
  • disable

Блокирование элемента меню

public void disable();
  • enable

Разблокирование элемента меню

public void enable();

Блокирование или разблокирование элемента меню

public void enable(boolean cond);
  • getLabel

Получение текстовой строки меню

public String getLabel();
  • isEnabled

Проверка, является ли элемент меню заблокированным

public boolean isEnabled();
  • paramString

Получение строки параметров

public String paramString();
  • setLabel

Установка текстовой строки для элемента меню

public void setLabel(String label);


Создание диалоговых панелей

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Диалоговые панели создаются на базе класса Dialog, краткое описание которого приведено ниже.

Конструкторы

Создание диалоговой панели без заголовка

public Dialog(Frame parent, boolean modal);

Создание диалоговой панели с заголовком

public Dialog(Frame parent,
  String title, boolean  modal);

Методы

  • addNotify

Вызов метода createDialog

public void addNotify();
  • getTitle

Получение строки заголовка диалоговой панели

public String getTitle();
  • isModal

Определение, является ли диалоговая панель модальной

public boolean isModal();
  • isResizable

Определение возможности изменения размеров окна диалоговой панели

public boolean isResizable();
  • paramString

Получение строки параметров

protected String paramString();

setResizable

Включение или выключение возможности изменения размеров окна диалоговой панели

public void setResizable(boolean resizable);
  • setTitle

Установка заголовка диалоговой панели

public void setTitle(String title);

Использование класса Dialog

Для того чтобы создать свою диалоговую панель, вы должны определить новый класс, унаследовав его от класса Dialog, как это показано ниже:

class MessageBox extends Dialog
{
  . . .
  public MessageBox(String sMsg,
    Frame  parent, String  sTitle,
    boolean  modal)
  {
   super(parent, sTitle, modal);
   . . .
   resize(200, 100);
   . . .
  }
}

В этом классе нужно определить конструктор, который вызывает конструктор базового метода класса Dialog и определяет размеры окна диалоговой панели. Кроме того, в конструкторе вы должны создать все необходимые компоненты для размещения внутри диалоговой панели (кнопки, списки, текстовые поля, переключатели и так далее), а также выполнить размещение этих компонент, установив нужный режим размещения.

Для окон класса Dialog устанавливается режим размещения BorderLayout. Если нужен другой режим размещения, необходимо установить его явным образом методом setLayout.

Для отображения окна диалоговой панели необходимо вызвать метод show. Чтобы спрятать диалоговой окно, применяйте метод hide. Метод dispose удаляет окно диалоговой панели окончательно и освобождает все связанные с ним ресурсы.

Когда пользователь пытается уничтожить окно диалоговой панели при помощи органов управления, расположенных в заголовке такого окна, возникает событие Event.WINDOW_DESTROY. Вы должны обработать его, обеспечив удаление окна диалоговой панели вызовом метода dispose, если, конечно, это соответствует логике работы вашей панели.


Приложение MenuApp

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Автономное приложение MenuApp, работающее под управлением интерпертатора Java, демонстрирует способы создания меню. В его окне (рис. 1) имеется панель с меню File и Help.

pic02.gif (2210 bytes)

Рис. 1. Главное окно автономного приложения MenuApp

В меню File мы добавили строки New и Exit, а также разделитель в виде горизонтальной линии (рис. 2).

pic03.gif (2431 bytes)

Рис. 2. Меню File

Меню Help (рис. 3) содержит строки Content и About. Между ними также имеется разделительная линия.

pic04.gif (2497 bytes)

Рис. 3. Меню Help

Если выбрать любую строку, кроме строки Exit из меню File, на экране появится диалоговая панель с названием выбранной строки и кнопкой OK (рис. 4).

pic05.gif (1942 bytes)

Рис. 4. Диалоговая панель, которая появляется при выборе строки New из меню File

Выбор строки Exit из меню File приводит к завершению работы приложения MenuApp.


Исходный текст приложения MenuApp

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Исходный текст приложения MenuApp представлен в листинге 1.

Листинг 1. Файл MenuApp.java

import java.awt.*;
public class MenuApp
{
  public static void main(String args[])
  {
    MainFrameWnd frame =
      new MainFrameWnd("MenuApp");
    frame.setSize(
    frame.getInsets().left +
      frame.getInsets().right  + 320,
      frame.getInsets().top  +
      frame.getInsets().bottom + 240);
    frame.show();
  }
}
class MainFrameWnd extends Frame
{
  MenuBar mbMainMenuBar;
  Menu mnFile;
  Menu mnHelp;
  public MainFrameWnd(String sTitle)
  {
    super(sTitle);
    setSize(400, 200);
    setBackground(Color.yellow);
    setForeground(Color.black);
    setLayout(new FlowLayout());
    mbMainMenuBar = new MenuBar();
    mnFile = new Menu("File");
    mnFile.add("New");
    mnFile.add("-");
    mnFile.add("Exit");
    mnHelp = new Menu("Help");
    mnHelp.add("Content");
    mnHelp.add("-");
    mnHelp.add("About");
    mbMainMenuBar.add(mnFile);
    mbMainMenuBar.add(mnHelp);
    setMenuBar(mbMainMenuBar);
  }
  public void paint(Graphics g)
  {
    g.setFont(new Font(
      "Helvetica", Font.PLAIN, 12));
    g.drawString("Frame window", 10, 70);
    super.paint(g);
  }
  public boolean handleEvent(Event evt)
  {
    if(evt.id == Event.WINDOW_DESTROY)
    {
      setVisible(false);
      System.exit(0);
      return true;
    }
    else
      return super.handleEvent(evt);
  }
  public boolean action(Event evt, Object obj)
  {
    MenuItem mnItem;
    if(evt.target instanceof MenuItem)
    {
      mnItem = (MenuItem)evt.target;
      if(obj.equals("Exit"))
      {
        System.exit(0);
      }
      else if(obj.equals("New"))
      {
        MessageBox mbox;
        mbox = new MessageBox(
          "Item New selected",
          this, "Dialog from Frame", true);
        mbox.show();
      }
      else if(obj.equals("Content"))
      {
        MessageBox mbox;
        mbox = new MessageBox(
          "Item Content selected",
          this, "Dialog from Frame", true);
        mbox.show();
      }
      else if(obj.equals("About"))
      {
        MessageBox mbox;
        mbox = new MessageBox(
          "Item About selected",
          this, "Dialog from Frame", true);
        mbox.show();
      }
      else
        return false;
      return true;
    }
    return false;
  }
}
class MessageBox extends Dialog
{
  Label lbMsg;
  Button btnOK;
  public MessageBox(String sMsg,
    Frame  parent, String  sTitle,
    boolean  modal)
  {
    super(parent, sTitle, modal);
    resize(200, 100);
    setLayout(new GridLayout(2, 1));
    lbMsg = new Label(sMsg, Label.CENTER);
    add(lbMsg);
    btnOK = new Button("OK");
    add(btnOK);
  }
  public boolean handleEvent(Event evt)
  {
    if(evt.id == Event.WINDOW_DESTROY)
    {
      dispose();
      return true;
    }
    else
      return super.handleEvent(evt);
  }
  public boolean action(Event evt, Object obj)
  {
    Button btn;
    if(evt.target instanceof Button)
    {
      btn = (Button)evt.target;
      if(evt.target.equals(btnOK))
      {
        dispose();
      }
      else
        return false;
      return true;
    }
    return false;
  }
}


Описание исходного текста приложения MenuApp

Оглавление

Окна и диалоговые панели
Окна класса Frame
Меню MenuBar
Класс Menu
Класс MenuItem
Диалоговые панели
Приложение MenuApp
Исходный текст
Описание текста

Назад Вперед

Как мы уже говорили, приложение MenuApp работает автономно. Поэтому мы импортируем только класс java.awt.*, необходимый для работы с окнами:

import java.awt.*;

В нашем приложении определено три класса - MenuApp, MainFrameWnd и MessageBox.

Класс MenuApp

В главном классе приложения MenuApp мы определили только один метод main. Этот метод получает управление при запуске приложения.

Первым делом метод main создает объект класса MainFrameWnd, определенного в нашем приложении:

MainFrameWnd frame =
  new MainFrameWnd("MenuApp");

Этот класс, созданный на базе класса Frame, определяет поведение главного окна нашего приложения.

На втором шаге метод init настраивает размеры главного окна с учетом размеров внешней рамки и заголовка окна:

frame.setSize(frame.getInsets().left +
  frame.getInsets().right  + 320,
  frame.getInsets().top  +
  frame.getInsets().bottom + 240);

Поля left и right объекта класса Insets, ссылку на который возвращает метод getInsets, содержат ширину левой и правой части рамки окна, соответственно. Поле top содержит высоту верхней части рамки окна с учетом заголовка, а поле bottom - высоту нижней части рамки окна.

Для отображения окна фрейма мы вызываем метод show, как это показано ниже:

frame.show();

Класс MainFrameWnd

Класс MainFrameWnd создан на базе класса Frame:

class MainFrameWnd extends Frame
{
  . . .
}

В нем мы определили три поля, конструктор, методы paint, handleEvent и action.

Поля класса MainFrameWnd

Поле mbMainMenuBar предназанчено для хранения ссылки на главное меню приложения, создаваемое как объект класса MenuBar:

MenuBar mbMainMenuBar;

Поля mnFile и mnHelp хранят ссылки на меню File и Help, соответственно:

Menu mnFile;
Menu mnHelp;

Данные меню создаются на базе класса Menu.

Конструктор класса MainFrameWnd

В качестве единственного параметра конструктору класса MainFrameWnd передается заголовок создаваемого окна. В первой исполняемой строке наш конструктор вызывает конструктор из базового класса, передавая ему строку заголовка через параметр:

public MainFrameWnd(String sTitle)
{
  super(sTitle);
  . . .
}

Далее конструктор определяет размеры окна, вызывая для него метод setSize:

setSize(400, 200);

Затем мы устанавливаем для нашего окна желтый цвет фона и черный цвет изображения:

setBackground(Color.yellow);
setForeground(Color.black);

По умолчанию для окон класса Frame устанавливается режим добавления компонент BorderLayout. Мы изменяем этот режим на FlowLayout, вызывая метод setLayout:

setLayout(new FlowLayout());

Далее конструктор приступает к формированию главного меню окна. Это меню создается как объект класса MenuBar:

mbMainMenuBar = new MenuBar();

Затем мы создаем и наполняем меню "File":

mnFile = new Menu("File");
mnFile.add("New");
mnFile.add("-");
mnFile.add("Exit");

Это меню создается на базе класса Menu. Обратите внимание, что между строками New и File расположен разделитель.

Аналогичным образом мы добавляем в главное меню другое меню - "Help":

mnHelp = new Menu("Help");
mnHelp.add("Content");
mnHelp.add("-");
mnHelp.add("About");

После своего окончательного формирования меню "File" и "Help" добавляются в главное меню окна mbMainMenuBar:

mbMainMenuBar.add(mnFile);
mbMainMenuBar.add(mnHelp);

И, наконец, когда главное меню будет сформировано, оно подключается к окну вызовом метода setMenuBar, как это показано ниже:

setMenuBar(mbMainMenuBar);

Метод paint

Метод paint получает в качестве параметра ссылку на контекст отображения, пригодный для рисования в нашем окне. Пользуясь этим контекстом, мы устанавливаем шрифт текста и рисуем текстовую строку. Затем мы вызываем метод paint из базового класса Frame, на основе которого создан наш класс MainFrameWnd:

public void paint(Graphics g)
{
  g.setFont(new Font(
    "Helvetica", Font.PLAIN, 12));
  g.drawString("Frame window", 10, 70);
  super.paint(g);
}

Метод handleEvent

Для того чтобы определить реакцию окна на попытку пользователя закрыть окно с помощью органов управления, расположенных в заголовке окна, или другим способом, мы переопределили метод handleEvent.

При получении кода события Event.WINDOW_DESTROY (удаление окна) мы скрываем окно, вызывая метод setVisible с параметром false.

Затем с помощью статического метода exit класса System мы завершаем работу интерпретатора:

public boolean handleEvent(Event evt)
{
  if(evt.id == Event.WINDOW_DESTROY)
  {
    setVisible(false);
    System.exit(0);
    return true;
  }
  else
    return super.handleEvent(evt);
}

Метод action

Этот метод обрабатывает события, возникающие при выборе строка из меню.

В начале своей работы метод action проверяет, действительно ли событие вызвано меню:

MenuItem mnItem;
if(evt.target instanceof MenuItem)
{
  . . .
}
return false;

Если это так, в поле mnItem сохраняется ссылка на элемент меню, вызвавший событие:

mnItem = (MenuItem)evt.target;

Тем не менее, для определения строки, выбранной пользователем, нам достаточно проанализировать второй параметр метода action:

if(obj.equals("Exit"))
{
  System.exit(0);
}
else if(obj.equals("New"))
{
  MessageBox mbox;
  mbox = new MessageBox(
    "Item New selected",
    this, "Dialog from Frame", true);
  mbox.show();
}
else if(obj.equals("Content"))
{
  . . .
}
else if(obj.equals("About"))
{
  . . .
}

В данном случае второй параметр метода action будет представлять собой ссылку на строку, выбранную из меню, поэтому для определения выбранной строки мы можем выполнить простое сравнение методом equals.

Если пользователь выбрал из меню File строку Exit, мы вызываем метод System.exit, предназначенный для завершения работы виртуальной машины Java.

В том случае когда пользователь выбирает любую другую строку из меню, метод action создает диалоговую панель на базе определенного нами класса MessageBox. В этой диалоговой панели отображаетя название выбранной строки меню.

Заметим, что сразу после создания конструктором диалоговая панель не появляется на экране. Мы отображаем ее, вызывая метод show.

Класс MessageBox

Для отображения названий выбранных строк меню мы создаем диалоговую панель, определив свой класс MessageBox на базе класса Dialog, как это показано ниже:

class MessageBox extends Dialog
{
  . . .
}

В классе MessageBox есть два поля, конструктор, методы handleEvent и action.

Поля класса MessageBox

Внутри диалоговой панели мы расположили текстовое поле класса Label, предназначенное для отображения сообщения, и кнопку с надписью OK, с помощью которой можно завершить работу диалоговой панели.

Ссылка на текстовое поле хранится в поле lbMsg, на кнопку - в поле btnOK.

Конструктор класса MessageBox

Наш конструктор создает диалоговую панель с заданным сообщением внутри нее. Ссылка на строку сообщения передается конструктору через первый параметр. Остальные параметры используются конструктором базового класса Dialog для создания диалоговой панели:

super(parent, sTitle, modal);

После вызова конструктора из базового класса наш конструктор устанавливает размеры окна созданной диалоговой панели, вызывая метод resize:

resize(200, 100);

Отменяя установленный по умолчанию режим размещения компонент BorderLayout, конструктор устанавливает режим GridLayout:

setLayout(new GridLayout(2, 1));

Окно диалоговой панели при этом разделяется на две части по горизонтали. В верхнюю часть добавляется текстовое поле для отображения сообщения, в нижнюю - кнопка OK:

lbMsg = new Label(sMsg, Label.CENTER);
add(lbMsg);
btnOK = new Button("OK");
add(btnOK);

Метод handleEvent класса MessageBox

Когда пользователь пытается закрыть окно диалоговой панели, например, сделав двойной щелчок левой клавишей мыши по системному меню или одиночный щелчок по кнопке удаления окна, возникает событие Event.WINDOW_DESTROY. Мы его обрабатываем следующим образом:

if(evt.id == Event.WINDOW_DESTROY)
{
  dispose();
  return true;
}
else
  return super.handleEvent(evt);

Вызывая метод dispose, мы удаляем окно диалоговой панели и освобождаем все связанные с ним ресурсы.

Метод action класса MessageBox

Если пользователь нажимает кнопку OK, расположенную в окне диалоговой панели, метод action вызывает для панели метод dispose, удаляя эту панель с экрана и из памяти:

if(evt.target.equals(btnOK))
{
  dispose();
}


Введение в Perl. От простого к сложному.

От простого к сложному

Прежде чем приступить к последовательному ознакомлению с не знакомым для вас языком, должен оговориться и сказать, что все примеры да и сам язык описанию которого посвящена эта книга это Perl версии 5.003 для операционной системы FreeBSD версии 2.01. Существуют реализации этого языка для операционных систем OS/2 , MS-DOS и Windows NT но они немного отстают по возможностям от оригинала, рожденного в недрах Unix.

Пример 1.
Введите в файл test1.pl следующие строки:

        #!/usr/local/bin/perl
        # Содержимое файла test1.pl
        print "Наше Вам с кисточкой!\n";
А теперь подробно разберем каждую строку.
#!/usr/local/bin/perl

Данная строка должна быть первой в любой Perl-программе. Она указывает системному интерпретатору что данный файл - это Perl-программа.

# Содержимое файла test1.pl
Эта строка называется комментарием. Она всегда начинается символом '#' и заканчивается таким объяснением что как говорил великий Ходжа Насреддин "это тонкий философский вопрос", а говоря простым языком здесь можно писать все что угодно. Даже пожелание руководству. Уж здесь оно точно до него не дойдет.

print "Наше Вам с кисточкой!\n";

Самая последняя ну и конечно главная. Она просто выводит на экран надпись "Наше Вам с кисточкой!".

Здесь слово print - это команда "вывести". Все что в кавычках - это символы, \n - перевод строки и ';' - признак конца команды. Он обязателен. В одной строке может быть несколько команд и все они должны завершаться символом ';'. После него может быть символ '#' - это значит остаток строки считается комментарием.

Над этой строкой автору пришлось больше всего поломать голову так как в нее постоянно лезли какие то странные "hello", "hello all", "Построимся и спасемся", "Строй наше спасение" и т. д. и т.п.

Если вы никогда не работали с Perl, то бьюсь на спор в 10$, что данная программа сразу у вас не заработает! Не потому что она не верна, а потому что "Нельзя объять необъятное". Сразу, потом можно, да и то частями. Сначала сделайте ваш файл test1.pl исполняемым. Для этого введите команду:

    chmod +x test1.pl
Затем проверьте где у вас Perl. Для этого введите:
    which perl
Система вам выдаст что то вроде: /usr/bin/perl
Если:
perl: Command not found.
То тогда закройте книжку и ложитесь спать. У вас просто нет Perl или он не установлен. А мне остается послать вас к системному администратору или к man (для переводчиков- man сокращение от manual а не то что вы подумали).

Теперь проверьте что бы строка 01 содержала то что выдала команда which. Если совпало то введите:

    test1.pl
и бьюсь на 50$, что и теперь программа не заработает, так как правильней ввести:
    ./test1.pl

Если я проиграл, то не радуйтесь. Да же если вам удалось запустить программу как test1.pl это значит, что у вас будут неприятности в будущем.

Пример 2.
Данная программа выводит на экран все ваши секреты. А именно файл /etc/passwd.

    #!/usr/local/bin/perl
    open(PASS, "</etc/passwd") || die "Файл не найден!";
    while(<PASS>)
        {
        print;
        }
    close(PASS);
Пояснения:
open(PASS, "</etc/passwd") || die "Файл не найден!";

"Открыть" файл, т.е. создать указатель файла PASS, и в случае ошибки выдать "Файл не найден!" и закончить программу.

while(<PASS>)
Читать по одной строке файла в переменную по умолчанию $_.
  {
Открыть блок операторов.
  print;
Вывести на экран переменную по умолчанию $_
   }
Конец блока.
close(PASS);
Закрыть файл. Этого можно и не делать, т. к. файл автоматически закроется после окончания программы.

Результат работы этой программы тот же что и команды cat /etc/passwd. По экрану пробежали непонятные строки но зато теперь перед вами открыты горизонты Perl программирования! Все последующие примеры будут развитием этой программы и она превратится из гадкого утенка в прекрасного лебедя (не генерала).

Пример 3.
Разделение полей.

      #!/usr/local/bin/perl
      open(PASS, "</etc/passwd") || die
      "Файл не найден!";
      while(<PASS>)
          {
        ($login, $pass, $uid, $gid, $name,
        $home_dir, $shell) = split(':');
        print "$login \t $name\n";
          }
      close(PASS);
Пояснение:
($login, $pass, $uid, $gid, $name, $home_dir,
 $shell) = split(':');
Присвоить указанным переменным поля входной строки, считая разделителем символ ':'.
print "$login \t $name\n";
Вывести login - имя пользователя и его описание. Поля разделены символом '\t' - табуляции.

Пример 4.
Вывести имена пользователей, отсортированных по группам.

    #!/usr/local/bin/perl
    open(PASS, "sort -n -t : +3 -4 +0 /etc/passwd|") ||
     die "Файл не найден!";
    while(<PASS>)
        {
        ($login, $pass, $uid, $gid, $name,
        $home_dir, $shell) = split(':');
        print "$login \t $gid \t $name\n";
        }
    close(PASS);
Пояснения:
open(PASS, "sort -n -t : +3 -4 +0 /etc/passwd|") ||
 die "Файл не найден!";

В данной строке весь фокус! Входным файлом для нашей программы стал выход команды sort которая и отсортирует входные данные.

Форматированный вывод.

Ну а теперь напечатаем на экране все наши данные в удобной форме.

#!/usr/local/bin/perl

open(PASS, "sort -n -t : +3 -4 +0 /etc/passwd|") ||
 die "Файл не найден!";
while(<PASS>)
    {
    ($login, $pass, $uid, $gid, $name, $home_dir,
     $shell) = split(':');
    write();	# Форматированный вывод данных.
    }
close(PASS);
exit 0;		# Завершение программы

############ Описание формы вывода ##################

format STDOUT =

Пользователь: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                $name
              ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                $name
              ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                $name
--------------------------------------------------------
Login:@<<<<<<<<   Uid:@<<<  Gid:@<<<  Home dir:@<<<<<<<<<
      $login,         $uid,     $gid,    $home_dir
---------------------------------------------------------

.	# Это последняя строка программы

Фрагмент результата:

Пользователь: Калужский ликероводочный завод. Лучшие
водки и  настойки. Звонить только перед праздником
 Кострикову Анатолию т. 2-23-06,,,
----------------------------------------------------------------
Login:uucryst  Uid:1055  Gid:66  Home dir:/var/spool/uucppublic/
----------------------------------------------------------------

Пользователь: Торговый Дом Дилен,,,

---------------------------------------------------------------
Login:uudilen  Uid:1075  Gid:66  Home dir:/var/spool/uucppublic
---------------------------------------------------------------

Если вам интересно узнать как работает эта программа, то переверните страницу и начините свое путешествие в мире Perl. Желаю удачи!


Введение в Perl. Переменные.

Переменные.

  • Контекст
  • Скалярные значения
  • Конструкторы скаляров
  • Конструкторы списков
  • Тип typeglobs

В Perl существует три типа структур данных: скаляры, массивы скаляров и хеши (hashes) - ассоциативные массивы скаляров. Обычно элементы массивов индексируются целыми числами, первый элемент - нулевой. Отрицательное значение индекса обозначает номер позиции элемента с конца. Хеши индексируются строками символов.

Имена скалярных переменных всегда начинаются с символа '$' даже когда обозначают элемент массива.

Пример:

    $var1		# Простой скаляр 'var1'

    $var1[0]	# Первый элемент массива 'var1'

    $var1{'first'}	# Элемент с индексом 'first'

В случае использования имени массива "целиком" или его "среза" перед именем массива ставится символ '@'.

Пример:

    @var1		# Все элементы массива var1
    ( $var1[0], $var1[1], ... $var1[n])

    @var1[1,3,10]	# Элементы $var1[1],
     $var1[3], $var1[10]

    @var1{'first','last'} # то же что и
    ( $var1{'first'}, $var1{'last'} )

Хеш "целиком" начинается с символа '%'.

Пример:

    %var, %key, %years

Имена подпрограмм начинаются символом '&', если из контекста не видно, что это подпрограмма.

Пример:

    &sub1, &test_prog, test(12)

Имена таблиц символов всегда начинаются символом '*'.

Каждый тип переменных имеет свою область памяти поэтому $var1 и $var1[0] совершенно разные переменные, хотя $var1[0] часть массива @var1. Так же @var1 и %var1 - разные массивы переменных.

Имена переменных могут содержать любые буквенно-цифровые символы за исключением пробела и табуляции. Эти символы используются в качестве разделителей. Большие и малые буквы различаются поэтому $var1 и $Var1 - разные переменные. В Perl по умолчанию имена меток и указателей файлов пишут большими буквами.

Контекст.

Большое значение для правильного употребления встроенных функций имеет контекст использования результата этих функций, т.к. в противном случае они возвращают совершенно "непонятный" результат. В Perl имеется два главных контекста: скалярный и список (list). Проще говоря, если в левой части выражения имеется ввиду одно единственное значение, то это скалярный контекст. Если множество значений - список.

Пример:

    $var1 = <>;	# Прочитать одну строку файла

    @var1 = <>;	# Прочитать все строки файла
     в массив @var1

    $var1 = (1,2,3); # $var = 3 - количество элементов

    @var1 = (1,2,3); # Создание массива @var1
     с элементами 1,2,3

Скалярные значения.

Все данные в Perl это скаляры, массивы скаляров и хеши скаляров. Скалярные переменные могут содержать числа, строки и ссылки. Преобразование числа - строки происходит автоматически по умолчанию. Скаляр может иметь только одно единственное значение, хотя это может быть ссылка на массив скаляров. Так как Perl сам преобразовывает числа в строки и наоборот, то программисту нет необходимости думать о том, что возвращает функция.

В Perl не существует типов "строка" или "число" или "файл" или что-то еще. Это контекстно зависимый полиморфный язык для работы с текстами.

Скаляр имеет логическое значение "TRUE" (истина), если это не нулевая строка или число не равное 0.

В Perl существует два типа нулевых (null) скаляров - определенные (defined) и неопределенные (undefined). Неопределенное значение возвращается, когда что-то не существует. Например, неизвестная переменная, конец файла или ошибка. С помощью функции defined() вы можете заранее обнаружить подобное состояние.

Количество элементов массива так же является скаляром и начинается символами $# подобно интерпретатору csh. Фактически $#var1 - это индекс последнего элемента массива. Нужно помнить, что первый элемент имеет индекс 0, поэтому количество элементов определяется как $#var1+1 . Присвоение значения $#var1 изменит длину массива и разрушит "оставленные" значения. Присвоение значения элементу массива с индексом больше чем $#var1 увеличит размер массива, а присвоение ему нулевого списка -обнулит.

В скалярном контексте имя массива возвращает его длину (для списка возвращается последний элемент).

Пример:

    @var1 = (4, 3, 2, 1);	# Присвоение значения
    элементам массива
    $i = @var1;		# Использование скалярного контекста
    print $i;		# Печать результата 4 - кол-во элементов
    print @var1;		# Списковый контекст, печать
    всех элементов.

Для принудительного получения скалярного значения удобно применять функцию scalar().

Пример:

    print scalar(@var1);	# Вывод длины массива,
     а не его значений

Хеш в скалярном контексте возвращает "true", если существует хотя бы одна пара "ключ-значение". Фактически возвращается строка типа 2/8 где 8 - количество выделенных "ячеек" памяти, а 2 - количество использованных.

Конструкторы скаляров.

Числа пишутся стандартно:

    123
    123.123
    0.12
    .12E-10
    0xABCD		# Шестнадцатиричная запись
    0377		# Если 0 в начале - восьмеричная
    123_456_123	# Так тоже можно для удобства чтения.
Строки ограничиваются одинарными (') или двойными (") кавычками:
    'Равняйсь, смирно!'
    "Построимся и спасемся."

Способов обозначения строк очень много. Смотрите описание оператора qq.

В хеше можно опускать кавычки, если индекс не содержит пробелов.

Пример:

    $var1{first} то же что и $var1{'first'}

Обратите внимание на то, что перед первой одинарной кавычкой должен стоять пробел, иначе строка воспримется как имя переменной, т. к. в именах разрешено использование одинарных кавычек. Запрещается в кавычках применять зарезервированные литералы __LINE__ (номер текущей строки программы), __FILE__ (текущий файл). Для обозначения конца программы можно применять литерал __END__ Весь последующий текст игнорируется, но его можно прочитать используя указатель файла DATA.

Слова в программе, не поддающиеся никакой интерпретации, воспринимаются как строки в кавычках, поэтому рекомендуется имена меток и указателей файлов писать большими буквами во избежание возможного "конфликта" с зарезервированными словами.

В Perl есть возможность вставлять текст документа прямо в программу. Так называемый "here-doc" (здесь текст) метод. Обозначается символами << за которыми идет слово-ограничитель.

Пример:

    print <<EOF;	# Все строки до EOF - текст для печати.

    Эй вы трое, идите оба сюда!
    Что стоишь! Я тебе говорю!!

        Полковник Савонькин.
    EOF

Конструкторы списков.

Список - множество значений, перечисленных через запятую и заключенных в круглые скобки. В списковом контексте список возвращает последний элемент списка.

Пример:

    @var1 = (1, 2, 'привет', 1.2); # Присвоить значение
     элементам.
где
        $var1[0] = 1,

        $var1[1] = 2,

        $var1[2] = 'привет'

        $var1[3] = 1.2

    $var1 = (1, 2, 'привет', 1.2);
а здесь $var1 = 1.2 т.е. последнее значение списка.

Допускается применять в списке другие списки, но в полученном списке уже невозможно различить начало и конец включенных списков.

Пример:

    @s1 = (1, 2, 3); # 	Первый список

    @s2 = (6, 7, 8);	# Второй

    @s = (0, @s1, 4, 5, @s2, 9, 10); # Включаем списки
    @s1 и @s2

    print @s;	# Результат: 012345678910  - значения
    без пробелов.

Список без элементов обозначается как () и называется нуль-списком. Списковое выражение можно употреблять как имя массива, но при этом его нужно брать в круглые скобки.

Пример:

    print ('январь','февраль','март')[1];
Результат: февраль

Список может быть присвоен списку только если каждый элемент в списке в левой части выражения допустим по типу списку в правой части.

Пример:

    ($a, $b, $c) = (1, 2, 3); # $a = 1, $b = 2, $c = 3

Присваивание списков в скалярном контексте возвращает количество присвоенных элементов.

Пример:

    $x = (($a, $b, $c) = (1,2));  # Результат $x=2

В случае присваивания списка хешу, список рассматривается как пары: ключ-значение.

Пример:

    %дни_месяца = ('январь', 31, 'февраль', 30);
Результат: $дни_месяца{январь} = 31, $дни_месяца{февраль} = 30

Для удобства записи можно использовать выражение с => .

Пример:

    %дни_месяца = (
            январь => 31,
            февраль => 30,
    );

Тип typeglobs

В Perl используется специальный внутренний тип typeglog для записи массива всех переменных. Такие массивы начинаются с символа '*'. Их удобно применять для передачи ссылок на массивы и хеши, но в данной версии Perl уже есть возможность применять ссылки, поэтому это делается очень редко. Единственно где это необходимо, так это для работы со ссылками на файлы. Например, если вам нужно создать локальную ссылку на файл в процедуре, то это лучше сделать так:

sub new_sub
{ local *IN; # Ссылка на файл
open (IN, "test") || return undef;  # Открыть файл.
Возврат при ошибке.
.........
return;
}
Более подробно это описано в главе Ссылки.


Введение в Perl. Отладка Perl программ.

Отладка Perl программ.

В Perl реализован превосходный механизм отладки программ. Вам достаточно запустить программу с ключом '-d' и вы начнете работать с интерактивным отладчиком. Вам становятся доступны все переменные и массивы, вы можете вводить выражения и операторы, трассировать, исследовать работу программы в пошаговом режиме и многое другое. Ниже перечислены команды отладчика которые вы можете вводить на его подсказку.

Команды отладчика.

  • h - Получить справку по командам.
  • T - Просмотреть стек программы
  • s - Выполнить одну команду и остановиться.
  • n - Выполнить подпрограмму и остановиться.
  • r - Выполнить текущую подпрограмму и остановиться.
  • c - Продолжить выполнение до следующей точки остановки.
  • c номер - Продолжить выполнение до строки с данным номером.
  • - Повторить последнюю команду n или s.
  • l min+incr - Вывести incr+1 строк программы начиная со строки min.
  • l min-max - Вывести строки начиная с min до max.
  • l номер - Вывести строку с указанным номером.
  • l - Вывести следующий экран строк.
  • - - Вывести предыдущий экран строк.
  • w строк - Вывести экран строк. Текущая в середине.
  • l подпрограмма - Вывести строки указанной подпрограммы.
  • /шаблон/ - Найти строку в тексте программы.
  • ?шаблон? - Обратный поиск строки в тексте программы.
  • L - Вывести строки с точками останова и активами.
  • S - Вывести имена всех подпрограмм.
  • t - Включить или выключить трассировку.
  • b строка [ условие] - Установить точку остановки.
  • b подпрограмма [ условие ] - Установить точку остановки в начале указанной подпрограммы и если указано при данном условии.
  • d - Убрать точку остановки.
  • D - Убрать все точки остановки.
  • a строка команда - Установить актив (команду которая выполнится вместе с указанной строкой. "команда" - обычная команда Perl.
  • A - Стереть все активы.
  • < команда - Выполнять команду перед каждой остановкой.
  • > команда - Выполнить команду после остановки отладчика.
  • V модуль [имена] - Вывести значение всех или указанных имен в модуле. Внимание! Символ '$' не вводится.
  • X [имена] - То же что и V , но только для текущего модуля.
  • ! номер - Выполнить одну строку программы.
  • ! -номер - Выполнить одну команду с номером предшествующим текущей строке.
  • H - n - Вывести n последних выполненных команд.
  • q или ^D - Выход из программы.
  • команда - Выполнить команду Perl.
  • p выражение - Вывести значение выражения.

Конструктор BEGIN не отслеживается отладчиком, но можно в теле конструктора указать:

  • $DB::single =1;
и отладчик остановится. Данная команда не вызовет ошибку при работе без отладчика.


Введение в Perl. Форматы.

Форматы.

В Perl реализован удобный метод создания форматированных отчетов. С помощью оператора format вы описываете заголовки, размеры полей, указываете положение данных на листе в удобной текстовой форме. Затем выполняете команду write(файл), которая выводит отформатированные данные в указанный файл.

Оператор format имеет следующий синтаксис:

    format имя =
    FORMLIST
    .

Обратите внимание на то, что описание формата идет после строки format и заканчивается символом '.' в начале строки.

Здесь 'имя' - это имя формата, такое же как и имя указателя выходного файла. Если 'имя' отсутствует то значение по умолчанию - STDOUT.

FORMLIST - это строки формата. Они бывают трех типов:

  1. Комментарий. Строка начинается символом '#'.
  2. Описатель полей данных (picture).
  3. Строка аргументов используемых описателем.

Описатель - это строка, которая выводится в виде "как есть" за исключением специально обозначенных форматов полей данных. Каждое поле начинается либо символом '@', либо '^'. В описательной строке указывается только положение и вид выводимых данных, но не имена полей и переменных. Для этого предназначена следующая строка аргументов которая следует всегда после описателя и содержит имена переменных или целые выражения в порядке указанном описателем.

Размер и вид поля в описателе обозначается следующими символами:
"<<<<" | - выровнять значение по правому краю.
">>>>" | - -//- по левому.
"||||" | - -//- по центру.
"####.###" | - формат числа с точкой.
"@*" | - многострочная строка. Данные выводятся в колонку.
Размер поля равен количеству указанных символов.
Символ '^' в начале поля имеет специальное значение.
Так:

  • "^####" - пусто если переменная не определена.
для строчного скаляра:
  • "^<<<<<" - Выводится сколько возможно символов, а значение переменной меняется на остаток, вывод которого можно продолжить на следующих строках, которые могут иметь свои поля.

Пример:

#!/usr/local/bin/perl
#
#	Программа печати пригласительного билета
#
$кому = "Чапаеву Василию Ивановичу";
$от_кого = "Компания МММ";
$адрес = "Москва, ул. Петровка, д 38";
$текст = "Уважаемый Василий Иванович!
Компания МММ имеет честь пригласить
Вас и Ваших близких на презентацию наших новых
 хромовых сапог, сделанных
на уровне мировых стандартов качества и дизайна.";
format STDOUT =

    П Р И Г Л А С И Т Е Л Ь Н Ы Й      Б И Л Е Т
-------------------------------------------------------------
Кому: @<<<<<<<<<<<<<<<<<<<<<<<<<<  | ^|||||||||||||||||||||||
      $кому,				$текст
                                   | ^|||||||||||||||||||||||
                          $текст
От кого: @<<<<<<<<<<<<<<<<<<<<<<<  | ^|||||||||||||||||||||||
      $от_кого,				$текст
                                   | ^|||||||||||||||||||||||
                          $текст
Адрес:@<<<<<<<<<<<<<<<<<<<<<<<<<<  | ^|||||||||||||||||||||||
      $адрес,				$текст
                                   | ^|||||||||||||||||||||||
                          $текст
                                   | ^|||||||||||||||||||||||
                          $текст
                                   | ^|||||||||||||||||||||||
                          $текст
-----------------------------------------------------------
Отпечатано в типографии ИТУ 38
.

write();	# Вывод данных.
exit 0;		# Конец программы

Результат:

    П Р И Г Л А С И Т Е Л Ь Н Ы Й      Б И Л Е Т
----------------------------------------------------------
Кому: Чапаеву              | Уважаемый Василий Иванович!
        Василию Ивановичу  |   Компания МММ имеет честь
От кого: Компания МММ      |пригласить Вас и Ваших близких
                           |  на презентацию наших новых
Адрес:Москва,              | хромовых сапог, сделанных на
      ул. Петровка, д 38   |  уровне мировых стандартов
                           |     качества и дизайна.
                           |
---------------------------------------------------------
Отпечатано в типографии ИТУ 38
Специальные переменные:
  • $~ - построчный формат содержимого.
  • $^ - формат заголовка листа.
  • $% - номер листа.
  • $= - строк в листе.
Если вы хотите использовать одни и те же форматы для разных файлов, то самый простой путь:
use FileHandle;                   # Указать в начале программы
format_name файл имя_формата;     # Формат содержимого листа.
format_top_name файл имя_формата; # Формат заголовка листа.
write(файл);                      # вывод данных.

Здесь 'файл' имеется ввиду указатель файла полученный командой open();

Если вам нужно в теле листа выводить разного рода форматы (например заголовки групп или отбивку листа), то применяйте format_name.


Введение в Perl. Встроенные функции.

Встроенные функции.

Встроенные функции используются как термы выражений и подразделяются на две категории: списковые операторы и унарные операторы. Это влияет на их приоритет по отношению к оператору ',' - запятая. Списковые операторы могут иметь множество (список) аргументов, а унарные только один. Таким образом, запятая завершает аргументы унарного оператора и разделяет аргументы спискового. Аргумент унарного оператора воспринимается обычно в скалярном контексте, а спискового как в скалярном, так и списковом, причем скалярные аргументы идут первыми. В дальнейшем списковые аргументы мы будем обозначать словом 'LIST', это значит, что функция имеет список аргументов, разделенных запятой. Аргументы функций можно заключать в круглые скобки и таким образом обозначать, что "это функция" и приоритет не имеет значения, иначе это списковый или унарный оператор с определенным фиксированным приоритетом. Пробел после имени функции и скобкой значения не имеет. Поэтому будьте внимательны!

Пример:

     print 1 + 2 + 3; # результат 6

     print(1+2)+3;    # результат 3

     print (1+2)+3;   # опять 3

     print (1+2+3);   # 6

Если функция возвращает результат как в скалярном так и в списковом контексте, то код выхода по ошибке - скаляр c неопределенным значением или пустой список.

Запомните правило:

Не существует общего правила преобразования списка в скаляр!

Каждый оператор и функция имеют свой вид значения в скалярном контексте. Для одних это количество элементов из скалярного контекста. Для других - первый элемент списка или последний или количество успешных операций. Каждый - свое, если вы специально не указываете.

Оператор '-X', abs, accept, alarm, atan2 Y, X, bind, binmode, bless, caller, chdir, chmod, chomp, chop, chown, chr, chroot, close, closedir, connect, cos, crypt, dbmclose, dbmopen, define, delete, die, do BLOCK, do подпрограмма, do выражение, dump, each, eof, eval, exec, exists, exit, exp, fcntl, fileno, flock, fock, format, formline, getc, getlogin, getpeername, getpgrp PID, getppid, getpriority, Группа системных вызовов, getsockname, getsockopt, glob, gmtime, goto, grep, hex, import, index, int, ioctl, join, keys, kill, last, lc, lcfirst, length, link, listen, local, localtime, log, lstat, m//, map, mkdir, msgctl, msgget, msgsnd, msgrcv, my, next, no Module, oct, open, opendir, ord, pack, pipe, pop, pos, print, printf, push, q/строка/, qq/строка/, qx/строка/, qw/строка/, quotemeta, rand, read, readdir, readlink, recv, redo, ref, rename, require, reset, return, reverse, rewindir, rindex, rmdir, s///, scalar, seek, seekdir, select, select RBITS, WBITS, EBITS, TIMEOUT, semctl, semget, semop, send, setpgrp, setpriority, setsockopt, shift, shmget, shmget, shmread, shutdown, sin, sleep, soket, soketpair, sort, splice, split, sprintf, sqrt, srand, stat, study, substr, symlink, syscall, sysread, system, syswrite, tell, telldir, tie, time, times, tr///, truncate, uc, ucfirst, umask, undef, unlink, unpack, untie, unshift, use, utime, values, vec, wait, waitpid, wantarray, warn, write, y///

Оператор '-X'.

    -X указатель файла
    -X выражение
    -X

Проверка файла, где 'X' - одно из ниже описанных значений. Это унарный оператор с одним аргументом - либо именем файла, либо указателем файла. Проверяет одно из условий. Если аргумент не указан, то берется значение переменной $_. Для ключа -t - STDIN. Результат 1, если true, '', если false или неопределенное значение, если файл не найден. Несмотря на странный вид это унарный оператор с соответствующим приоритетом. Аргумент можно заключать в круглые скобки. 'X' имеет следующие значения:
-r | Файл разрешен на чтение эффективным uid/gid
-w | на запись -//-
-x | исполнение -//-
-o | принадлежит эффективному uid (идентификатор пользователя)
-R | Файл разрешен на чтение реальным uid/gid
-W | на запись -//-
-X | исполнение -//-
-O | принадлежит реальному uid
-e | файл существует
-z | пустой
-s | не пустой
-f | обычный текст
-d | директория
-l | символическая ссылка
-p | pipes (конвейер)
-S | socket (гнездо)
-b | специальное блочное устройство
-c | -//- символьное -//-
-t | указатель на уст-во tty
-u | установлен бит setuid
-g | -//- setgid
-k | -//- sticky
-T | текстовой файл
-B | двоичный
-M | "возраст" файла в днях на момент старта скрипта
-A | дней с последнего чтения
-C | дней с последней модификации inode

abs выражение

Абсолютное значение выражения

accept NEWSOCKET, GENERICSOCKET

Открыть новый сокет по внешнему запросу. Аналогично системному вызову accept(). Возвращает адрес или false в случае неудачи.

alarm секунды

Послать сигнал SIGALARM текущему процессу по истечении указанного промежутка времени. Не допустимо делать несколько вызовов одновременно в одном промежутке времени. Возвращает остаток времени предыдущего счетчика.

atan2 Y, X

Арктангенс отношения Y к X в пределах от -pi до +pi.

bind сокет, имя

Назначить сетевой адрес сокету. Подобно системному вызову bind. Возвращает true в случае успеха и false иначе. Имя - пакетный адрес сокета.

binmode файл

Открыть файл для двоичного (binary) режима доступа. В обычном режиме при вводе символы CR LF транслируются в LF, а при выводе LF транслируется в CR LF.

bless ссылка, класс
bless ссылка

Данная функция определяет что объект указанный ссылкой принадлежит классу. Если класс не указан то он относится к текущему классу. Обычно это последний оператор в конструкторе объекта. Необходимо обязательно указывать имя класса если определяемый объект может наследоваться классом-потомком.

caller выражение
caller

Возвращает контекст вызова текущей подпрограммы. В скалярном контексте true если это подпрограмма или процедура вызванная операторами eval() или require() и false - иначе. В списковом это список ($package, $filename, $line), где

  • $package - имя пакета
  • $filename - имя файла пакета
  • $line - номер строки откуда был сделан вызов.
Если указано выражение то возвращается список:
     ($package, $filename, $line,
     $subroutine, $hasargs, $wantargs)
Выражение определяет "глубину" вложенности просмотра стека вызовов.
  • $subroutine - имя подпрограммы
  • $hasargs - имеющиеся аргументы
  • $wantargs - необходимые аргументы
Применение данной функции в DB пакете возвращает более детальную информацию. К списку аргументов добавляется список @DB::args.

chdir выражение

Перейти в директорию указанную выражением. Если выражение отсутствует то перейти в "домашнюю" директорию. Возвращает true в случае успеха и false - неудачи.

chmod список

Изменить права доступа к файлам указанным в списке. Первый элемент в списке - числовое ,обычно восьмеричное значение прав. Возвращает количество файлов которым были изменены права.

Пример:

     chmod 0666 'f1', 'f2', 'f3';

chomp переменная
chomp список
chomp

Удаляет в конце строки символ указанный переменной $/. Обычно это 'LF'. Возвращает количество удаленных символов. Если переменная не указана то используется переменная $_. Если указан список то обрабатывается каждая строка списка и возвращается общее количество удаленных символов.

chop переменная
chop список
chop

Полностью аналогично функции chomp но всегда удаляет последний символ строки.

chown список

Изменить "владельца" и "группу" файлов списка. Первый элемент - номер нового владельца, второй номер новой группы а далее список файлов. В большинстве Unix-систем вы не сможете сменить владельца если вы не суперпользователь, но можете изменить группу если это одна из ваших "вторичных" групп.

chr число

Возвращает символ с указанным кодом кодировки.

chroot директория

Сделать "корневой" указанную директорию. "Корневая" - значит внутри программы на нее можно ссылаться как '/'.

close файл

Закрыть открытый файл или поток. Можно не использовать, если один и тот же файл открывается повторно, т.к. он будет автоматически закрываться новым вызовом open().

closedir директория

Закрыть директорию, открытую функцией opendir().

connect сокет, имя

Связаться с удаленным сокетом. Данная функция подобна системному вызову connect(). Возвращает true в случае успеха и false при ошибке.

cos выражение

Косинус выражения в радианах.

crypt текст, salt

Шифрация текста. Аналогично системному crypt().

dbmclose хеш

Закрывает связь между хеш и базой данных.

dbmopen хеш, база, режим

Ассоциировать указанный хеш и базу в определенном режиме. Дело в том, что в Perl реализован очень удобный механизм работы с базами данных типа dbm, ndbm, sbdm, gdbm и Berkeley DB. Для этого нужно связать (открыть) базу под именем хеша. Вся работа с записями базы выполняется так, будто это обычный хеш (ассоциативный массив). Более подробно смотрите описание функции AnyDBM().

define выражение

Возвращает true, если значение определено (существует) и false, если нет. Многие операции завершаются неопределенным значением, например чтение после конца файла, использование неопределенных переменных, системные ошибки и т.д. Данная функция позволяет различать нулевое и неопределенные значения, определенность или неопределенность функций или ссылок. При использовании определенности ключа в хеше она возвращает определен данный ключ или нет, но не существует он или нет.

delete выражение

Удалить значение из хеша. Возвращает удаляемое значение или неопределенность, если ничего не удаляется. Удаление из массива окружения $ENV{} - изменяет окружение. Удаление из хеша связанного с базой данных - удаляет запись в базе данных.

die список

Если программа не была запущена из eval(), данная функция выводит список в поток STDERR и завершает работу программы с кодом из переменной $!. Если $! содержит 0, то с кодом ($? >> 8). Если и это 0, то с кодом 255. При вызове из eval() сообщение об ошибке заносится в переменную $@, и eval() завершается с неопределенным значением. Это позволяет отрабатывать ошибочные ситуации, не завершая головной модуль. Если строка из списка не завершается символом '\n', то дополнительно печатается номер строки скрипта, откуда вызвана функция и номер входной строки, если таковой имеется.

do BLOCK

Это не совсем функция. Здесь возвращается значение последнего оператора блока. Когда используется циклический модификатор BLOCK исполняется перед проверкой условия цикла в отличии от обычных циклов.

do подпрограмма

Устаревший метод вызова подпрограмм.

do выражение

Значение выражения воспринимается как имя файла Perl скрипта. Изначально это применялось для включения подпрограмм библиотеки. Сейчас правильней использовать вызовы use() и require(), которые обеспечивают контроль ошибок и завершения.

dump метка

Выполнение "дампа" текущей памяти. Применялось для возможности использовать программу undump, чтобы включить текущий исполняемый код в программу после инициализации всех переменных. Выполнение этой новой программы будет начинаться оператором goto метка. Если метка опущена, то запуск сначала.

Внимание! Файлы, открытые до момента дампа, не будут открытыми в момент исполнения новой программой.

each хеш

Возвращает 2-элементный массив. Первый элемент - ключ второй - значение следующего элемента хеша. Позволяет "просматривать" все значения хеша в определенном порядке. Сброс переменной цикла происходит только после прохождения всего массива.

eof файл
eof()
eof

Возвращает 1, если выполняется чтение после конца или чтение из не открытого файла. Аргументом должно быть выражение, возвращающее существующий указатель файла. Данную функцию нельзя использовать для чтения файлов, открытых как терминалы. Если аргумент не указан, то используется последний читаемый файл. Использование в форме eof() целесообразно применять в циклах while(<>) для определения конца только последнего файла из списка. Применяйте eof(ARGV) или eof для определения конца каждого файла из списка.

eval выражение
eval блок

Выражение сканируется и выполняется как обычная Perl программа. Это делается в контексте текущей программы, поэтому можно использовать уже определенные переменные и подпрограммы. Возвращается результат последнего оператора в блоке или значение оператора return. Если обнаружится синтаксическая ошибка или выполнится оператор die, то возвращается неопределенное значение, а переменная $@ содержит сообщение об ошибке. Если ошибки не было, то $@ содержит нулевую строку. При отсутствии аргумента берется значение переменной $_.

exec список

Выполняется системная команда из списка, и выполнение программы завершается. Если вам необходимо вернуться, то лучше применяйте функцию system(). Если в списке количество аргументов больше 1 или список - это массив с количеством элементов больше 1, то выполняется системный вызов execvp() с аргументами из списка. Если один скалярный аргумент, то он сканируется на обработку метасимволов, и если они присутствуют, то полученные аргументы передаются интерпретатору /bin/sh -c для дальнейшей обработки. Если метасимволов нет, то аргумент расщепляется на слова и передается системному вызову execvp(), что более эффективно.

Внимание! exec() и system(0) не сбрасывают буфер ввода/вывода, поэтому необходимо установить переменную $| для предотвращения потери выходной информации.

exists выражение

Возвращает true, если существует указанный ключ хеша, даже если не определено его значение.

exit выражение

Выполнение программы завершается с кодом выражения.

exp выражение

Возвращает e (основание натурального логарифма) в степени выражения.

fcntl файл, функция, скаляр

Выполняет системный вызов fcntl(). Для использования обязательно выполнить use Fcntl;

fileno файл

Возвращает описатель файла для данного указателя файла.

flock файл, операция

Системный вызов flock()

fock

Системный вызов fock(). Возвращает pid потомка для родителя и 0 - для потомка в случае успеха, иначе - неопределенное значение.

format

Декларативная функция Perl. Определяет формат вывода для оператора write. Подробно смотрите главу Форматы.

formline формат, список

Внутренняя функция, используемая в format. Форматирует вывод параметров из списка. Результат помещается в переменную $^A Функция write просто выводит значение этой переменной, но ее так же можно читать, а затем обнулять. Обычно format выполняет один вызов formline для одной строки формы, но formline не разделяет строки формата. Это значит что символы '~' и '~~' рассматривают весь формат как одну строку, поэтому необходимо использовать многострочные формы для описания одной записи формата.

getc файл
getc

Читает и возвращает символ из указанного файла. Если файл не указан, то читается STDIN. После конца файла возвращает пустую строку.

getlogin

Возвращает текущий login из файла /etc/utmp Не применяйте для идентификации пользователя, он не так сильно "закрыт" как getpwuid().

getpeername сокет

Возвращает упакованный адрес удаленного сокета.

getpgrp PID

Возвращает группу процесса с указанным PID. Если аргумент отсутствует, возвращает группу текущего процесса.

getppid

Возвращает PID родительского процесса.

getpriority WHICH, WHO

Возвращает текущий приоритет процесса, группы процессов или пользователя.

Группа системных вызовов:

  • getpwnam NAME
  • getgrnam NAME
  • gethostbyname NAME
  • getnamebyname NAME
  • getprotobyname NAME
  • getpwuid UID
  • getgrid GID
  • getservbyname NAME, PROTO
  • gethostbyaddr ADDR, ADDRTYPE
  • getnetbyaddr ADDR, ADDRTYPE
  • getprotobynumber NUMBER
  • getservbyport PORT, PROTO
  • getpwent
  • gethostent
  • getnetent
  • getprotoent
  • getservent
  • setpwent
  • setgrent
  • sethostent STAYOPEN
  • setnetent STAYOPEN
  • setprotoent STAYOPEN
  • setservent STAYOPEN
  • endpwent
  • endgrent
  • endhostent
  • endnetent
  • endprotoent
  • endservent
Все эти функции аналогичны одноименным системным вызовам. В списковом контексте возвращают следующие списки:
($name, $passwd, $uid, $gid, $quota,
 $comment, $gcos, $dir, $shell) = getpw*

($name, $passwd, $gid, $members) = getgr*

($name, $aliases, $addrtype, $length,
@addrs) = gethost*

($name, $aliases, $addrtype, $net) = getnet*

($name, $aliases, $proto) = getproto*

($name, $aliases, $port, $proto) = getserv*
В скалярном контексте возвращается имя или что-то другое, в зависимости от типа функции. Элемент $members в вызове getgr содержит разделенные пробелом имена членов группы. @addrs содержит IP адреса компьютеров в сети Интернет в упакованном виде. Для распаковки применяйте: ($a, $b, $c, $d) = unpack('C4',$addr[0]);

getsockname сокет

Возвращает упакованный адрес сокета локальной машины.

getsockopt сокет, уровень, опция

Возвращает опцию сокета.

glob выражение

Возвращает список файлов, удовлетворяющих выражению с '*', как в команде ls.

gmtime выражение

Переводит местное время, полученное функцией time(), во время по Гринвичу. Обычно используется в виде:

 ($sec,$min,$hour,$mday,$mon,$year,$wday,
       $yday,$isdst)=gmtime(time);

goto метка
goto выражение
goto &подпрограмма

Безусловный переход на метку. Нельзя делать переход на метку в структуру, которая должна быть инициирована, например, подпрограмму или цикл foreach и т.д. Вторая форма применяется для динамического определения перехода.

Например:

     goto ('метка1', 'метка2', 'метка3')[$i];
Здесь при $i = 0 будет переход на 'метка1', $i = 1 на 'метка2' и т.д. Третья форма (goto &подпрограмма) довольно "хитрый" метод подмены имени вызываемой подпрограммы именем текущей. Это используется в методе автозагрузки, когда нужно запустить другую процедуру, но под именем текущей, как будто та была вызвана раньше.

grep блок, список
grep выражение, список

Выполнят блок или выражение для каждого элемента списка. Переменная $_ содержит текущий элемент. Возвращает список элементов, с которыми выражение дало результат true. В скалярном контексте возвращает количество результатов true.

hex выражение

Воспринимает выражение как строку шестнадцатеричных цифр и возвращает десятичное значение.

import

В Perl нет встроенной функции import, но есть метод, с помощью которого модули могут экспортировать область имен другим модулям. Подробно смотрите функцию use().

index строка, подстрока, позиция
index строка, подстрока

Возвращает позицию первого вхождения подстроки в строке, начиная с указанной позиции. Если позиция отсутствует, то сравнение начинается с начала строки. Отсчет позиции ведется с 0, если только не изменено значение встроенной переменной $[. Если подстрока не найдена, то результат меньше первой позиции на 1. Обычно это -1.

int выражение

Возвращает целую часть выражения.

ioctl FILEHANDLE, FUNCTION, SCALAR

Выполняет системный вызов ioctl(). В начале программы необходимо указать: require "ioctl.ph";

join выражение, список

Соединяет строки списка или массива в одну строку с кодом разделителя, равному выражению.

keys хеш

Возвращает массив всех ключей хеша. В скалярном контексте - количество ключей. Порядок следования ключей аналогичен порядку в each функции.

kill список

Послать сигнал (первый элемент) указанным в списке процессам. Возвращает количество процессов которые сигнал приняли.

last метка
last

Функция аналогичная break в С. Прерывает цикл и переходит на указанную метку. Если метки нет на ближайший внешний цикл. Блок continue не выполняется.

lc выражение

Преобразовать строку в буквы нижнего регистра.

lcfirst выражение

Заменить первую букву строки на малую.

length выражение

Возвращает длину строки выражения.

link OLDFILE, NEWFILE

Создать файл NEWFILE связанный с файлом OLDFILE.

listen SOCKET, QUEUESIZE

Выполнить системный listen().

local список

Объявляет перечисленные переменные как локальные в текущем блоке или подпрограмме. Если указано два и более имени, их необходимо заключить в круглые скобки. Предпочтительней использовать функцию my().

localtime выражение

Преобразовать 9-элементный массив, возвращаемый функцией time, относительно местного времени. Употребляется как:

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
                                   =localtime(time);

log выражение

Возвращает натуральный логарифм выражения.

lstat файл
lstat выражение

То же что и системный вызов stat(), но касательно ссылки на файл, а не самого файла.

m//

Оператор совпадения. Смотри главу Операторы

map блок, список
map выражение, список

Выполняет блок или выражение для каждого элемента списка. Возвращает список результатов.

mkdir директория, права

Создать директорию с указанными правами доступа. Возвращает 1 при успехе, и 0 при неудаче.

msgctl ID, CMD, ARG

Системный вызов msgctl() System V

msgget KEY, FLAGS

Системный вызов msgget() System V

msgsnd ID, MSG, FLAGS

Системный вызов msgsnd() System V

msgrcv ID, VAR, SIZE, TYPE, FLAGS

Системный вызов msgrcv() System V

my список

Объявляет локальными переменные в списке. Если два или более имени, их необходимо заключить в круглые скобки.

next метка
next

Аналогичен continue в С. Переход к следующей итерации в цикле. Если в цикле присутствует блок continue, он так же выполняется. Если метка отсутствует - переход на ближайший внутренний цикл.

no Module список.

Исключить использование указанных модулей.

oct выражение

Возвращает десятичное число, представленное восьмеричными цифрами.

open файл, выражение
open файл

Открыть файл с указанным именем. Аргумент "файл" - это фактически указатель на блок описания файла. Если имя (выражение) файла отсутствует, то имя считается таким же как и указатель файла. Если перед именем стоит символ '<' то файл открывается только на чтение (значение по умолчанию). Если '>' - на запись. Если '>>' - на добавление записей. '+>' или '+<' - файл открыт на чтение и запись одновременно. Если имя файла начинается с символа '|', то имя считается системной командой, на вход которой выводятся данные (поток). Имя '-' - стандартный вход (STDIN), '>-' - стандартный вывод (STDOUT). Функция возвращает true при успехе и false - неудаче.

Если имя (выражение) начинается с символов ">&", то остаток имени считается именем указателя файла, который открывается повторно. Символ '&' может стоять после '<', '>>', '+>', '+>>' и '+<'. Режим повторного открытия должен соответствовать первому. Выражение вида "<&=N" где N -число, эквивалентно системному вызову fdopen(). Если имя "|-" или "-|", то происходит "расщепление" (fork) процесса на потомка и родителя. При этом возвращается номер процесса потомка (PID) внутри родительского процесса и 0 - внутри потомка. При этом запись/чтение в файл родительским процессом воспринимается как чтение/запись потомком. Для потомка это стандартный ввод/вывод. Родительский процесс ждет завершения потомка после закрытия потока и получает код завершения в переменной $?. Имя файла не должно содержать пробелов как в начале, так и в конце.

opendir указатель, выражение

Открыть директорию с именем выражения для обработки функциями readdir(), telldir(), seekdir(), rewinddir() и closedir(). Возвращает true при успехе.

ord выражение

Возвращает ascii код первого символа выражения.

pack шаблон, список

Упаковывает список в двоичную структуру по шаблону. Шаблон - это строка символов, описывающая порядок и тип упакованных значений.

  • A - текстовая строка, добавляется пробелами.
  • a - текстовая строка, добавляется 0 символом.
  • b - битовая строка (возрастающая нумерация бит)
  • B - битовая строка (убывающая нумерация бит)
  • h - шестнадцатеричная строка ( младший байт - первый)
  • H - шестнадцатеричная строка (старший байт первый)
  • c - символ со знаком
  • C - символ без знака.
  • s - короткое целое (один байт) со знаком.
  • S - короткое целое без знака.
  • i - целое (два байта) со знаком.
  • I - целое без знака.
  • l - длинное целое (4 байта) со знаком.
  • L - длинное целое без знака.
  • n - короткое в "сетевом" формате.
  • N - длинное в "сетевом" формате.
  • v - короткое в "VAX" формате.
  • V - длинное в "VAX" формате.
  • f - single float.
  • F - double float.
  • p - указатель на строку ограниченную 0.
  • P - указатель на структуру с фиксированной длиной.
  • u - упаковка uuencode.
  • x - нуль-байт
  • X - резервный байт.
  • @ - заполнить нулями позицию.
За каждой буквой может стоять число означающее количество повторов. Для всех типов за исключением 'a', 'A', 'b', 'B', 'h' и 'H', упаковывается максимально возможное количество значений из списка. Символ '*' после типа означает использовать оставшиеся параметры. Тип 'a' и 'A' использует только одно значение из списка и добавляет остаток поля либо нуль-символами, либо пробелами (при распаковке по типу 'A' пробелы и нули отбрасываются, а по 'a' - нет). Типы 'b' и 'B' упаковывают строку в указанное число бит. Так же как и 'h' и 'H' в число ниблов (байт). 'P' - упаковывает указатель на структуру с указанной длиной. Числа с плавающей запятой (floats и double) записываются в стандартном для каждой машины формате и могут быть разными для разных типов машин. Заметьте, что Perl всегда использует двойную точность (double), поэтому упаковка и распаковка по типу 'f' приведет к потере точности. Шаблоны для распаковки и упаковки совершенно одинаковы.

Примеры:

     print(pack("ccc",65,66,67));      # Результат ABC

     print(pack("A4A3","ab","cdefg")); # "ab  cde"

     print(pack("a4a3","ab","cdefg")); # "ab\0\0cde"

pipe READHANDLE, WRITEHANDLE

Аналогичен системному pipe().

pop массив

Удаляет и возвращает последний элемент массива. Длина массива уменьшается на 1. Результат не определен, если массив пустой. Если имя массива отсутствует, то извлекает из @ARGV для головной программы и @_ для подпрограммы.

pos скаляр

Возвращает смещение от последнего m//g поиска.

print файл список
print список
print

Выводит строку или список строк в файл. Возвращает true при успехе. Аргумент файл может быть скалярной переменной, содержащей имя указателя файла, если он отсутствует, то осуществляется вывод в стандартный выходной поток STDOUT (если не было вызова select()). Если список отсутствует, то выводится значение переменной $_. Для вывода по умолчанию в другой файл необходимо использовать функцию select(). Так как print работает в списковом контексте, то все элементы списка так же вычисляются в списковом контексте, т.е. функции будут возвращать списковые значения. Аргументы можно заключать в круглые скобки. Если указатель файла - элемент массива, то элемент нужно заключить в фигурные скобки для вычисления блока.

printf файл список
printf список

Аналогичен вызову "print файл sprintf(список)". Первый элемент списка должен быть форматом printf(), как и в С.

push массив, список

Добавить элементы массива значениями из списка. Длина массива увеличивается на количество элементов списка.

q/строка/
qq/строка/
qx/строка/
qw/строка/

Обычные операторы строк. Смотри главу Операторы.

quotemeta выражение

Вычисляет выражение, в котором метасимволы рассматриваются как обычные символы.

rand выражение
rand

Возвращает случайное число в диапазоне между 0 и выражением. Если выражение отсутствует - между 0 и 1. Данная псевдослучайная последовательность сбрасывается функцией srand().

read файл, скаляр, длина, смещение
read файл, скаляр, длина

Прочитать указанное число байт из файла в скалярную переменную. Возвращает количество прочитанных байт или неопределенность при ошибке. Длина скаляра станет равной количеству считанных байт. Смещение указывают, если данные нужно поместить не с самого начала строки.

readdir директория

Возвращает имя следующего файла в директории открытой командой opendir(). В скалярном контексте возвращает все оставшиеся имена файлов. Если файлов больше нет, то возвращает неопределенность в скалярном контексте и пустую строку в списковом.

readlink выражение

Возвращает значение символической ссылки. При ошибке возвращает неопределенное значение.

recv SOCKIT, SCALAR, LEN, FLAGS

Получить сообщение из сокета. Аналогичен системному recvfrom().

redo метка
redo

Перейти к началу блока, минуя вычисление условия и блока continue. Если метка отсутствует, то переход к ближайшему внешнему блоку.

ref выражение

Возвращает true, если выражение это ссылка, иначе - false. Возвращаемое значение зависит от типа ссылки. Существуют следующие типы ссылок:

     REF, SCALAR, ARRAY, HASH, CODE, GLOB
Если аргумент - ссылка на класс, то возвращается имя класса. Функция ref() аналогична функции typeof().

rename старое_имя, новое_имя_файла

Переименовать файл. Возвращает 1 в случае успеха, иначе - 0.

105require выражение
require

Если выражение - число, требует что бы текущая версия Perl была не ниже указанной. Если строка - считывает библиотечный файл с указанным именем, если он еще не прочитан. Последний оператор библиотечного файла должен возвращать true. Обычно это строка вида 1; Если расширение имени файла отсутствует, то по умолчанию принимается ".pm" В данной функции можно указывать путь расположения файла, а если библиотека расположена в стандартной системной области Perl, то вместо require предпочтительней использовать use().

reset выражение
reset

Обычно используется в блоках continue циклов для очистки переменных и поиска для повторного применения. Выражение воспринимается как список символов. Для указания диапазона можно применять символ минус '-'. Все переменные и массивы, начинающиеся с этих символов, обнуляются. Если выражение отсутствует, то обнуляется поиск ?шаблон? для повторного поиска. Сбрасываются только переменные текущего модуля.

Пример:

     reset 'a';     # очистить все переменные,
                                    начинающиеся
                    # буквой 'a'
     reset 'a-z'    # все переменные с малых букв.
Выражение вида: 'A-Z' употреблять не рекомендуется, т.к. обнуляются важные массивы @ARGV и @ENV.

return список

Выход из подпрограммы с указанным кодом. Если данная функция отсутствует, то выход из подпрограммы осуществляется с кодом последнего выполненного оператора.

reverse список

В списковом контексте возвращает список с элементами в обратном порядке указанному списку. В скалярном контексте возвращает строку с переставленными символами первого элемента списка.

rewindir директория

Устанавливает указатель файлов открытой директории в начало для последующего чтения функцией readdir().

rindex строка, подстрока, позиция
rindex строка, подстрока

Подобна функции index , но возвращает позицию последнего вхождения подстроки в строку. Если указана позиция, то просмотр выполняется до нее.

rmdir директория

Удалить указанную директорию, если в ней нет никаких файлов. Возвращает 1 при успехе и 0 - при неудаче. При этом переменная $! содержит код (errno).

s///

Оператор подстановки. Смотри главу Операторы.

scalar выражение

Вычисляет выражение в скалярном контексте и возвращает результат. В Perl нет специальной функции для принудительного вычисления в списковом контексте, т.к. для достаточно заключить его в круглые скобки.

seek файл, позиция, отсчет

Установить указатель на произвольную позицию в файле. Если отсчет равен 0 - позиционировать от начала файла, если 1 - от текущего положения, 2 - от конца файла. Возвращает 1 при успехе и 0 - неудаче.

seekdir директория, позиция

Установить указатель файла в открытой директории для последующего чтения функцией readdir(). Текущая позиция может быть получена функцией telldir().

select файл
select

Возвращает текущий указатель файла- по умолчанию или делает текущим указанный. Таким образом, если в функциях print или write не указан файл вывода, то это осуществляется в файл, указанный select. Переменные, связанные с выводом, так же действуют на вывод в этот файл.

select RBITS, WBITS, EBITS, TIMEOUT

Выполняет системный select()

semctl ID, SEMNUM, CMD, ARG

Системный вызов semctl() для SYSTEM V.

semget KEY, NSEMS, FLAGS

Системный вызов semget() для SYSTEM V.

semop KEY, OPSTRING

Системный вызов semop() для SYSTEM V.

send SOCKET, MSG, FLAGS, TO
send SOCKET, MSG, FLAGS

Послать сообщение через сокет. Аналогично системному send().

setpgrp PID, PGRP

Установить номер группы PGRP процессу с номером PID, 0 - для текущего процесса.

setpriority WHICH, WHO, PRIORITY

Установить приоритет PRIORITY процессу пользователя WHO и группе WHICH.

setsockopt SOCKET, LEVEL, OPTNAME, OPTVAL

Установить параметры сокета. Возвращает неопределенность при ошибке.

shift массив
shift

Удаляет первый элемент массива и возвращает его значение. Если массив пустой, то результат - неопределенность. Если аргумент опущен - обрабатывается массив @ARGV в головной программе или @_ в подпрограмме.

shmctl ID, CMD, ARG

Системный вызов shmctl() System V

shmget KEY, SIZE, FLAGS

Системный вызов shmget() System V

shmread ID, VAR, POS, SIZE
shmread ID, STRING, POS, SIZE

Пишет или читает сегмент общедоступной памяти в System V.

shutdown SOCKET, HOW

Прекращает работу сокета методом HOW.

sin выражение

Возвращает в радианах синус выражения.

sleep выражение
sleep

Приостанавливает работу процесса на указанное число секунд или зацикливается, если аргумент отсутствует. Работа продолжается, если получен сигнал SIGALARM. Возвращает время фактической паузы.

socket SOCKET, DOMAIN, TYPE, PROTOCOL

Открывает сокет и привязывает его к указателю SOCKET.

socketpair SOCKET1, SOCKET2, DOMAIN, TYPE, PROTOCOL

Создает "безымянную" пару сокетов в указанном DOMAIN. Возвращает true при успехе и false - неудаче.

sort подпрограмма список
sort блок список
sort список

Сортирует элементы списка и возвращает полученный список. Несуществующие элементы списка отбрасываются. Если не указано имя подпрограммы или блок, то сортирует в стандартном строковом порядке. Указанная подпрограмма возвращает значения больше, меньше или равное нулю в зависимости от двух соседних элементов списка. Имя подпрограммы может быть указано переменной. Она не должна быть рекурсивной, и два сравниваемых элемента списка передаются как глобальные переменные $main::a и $main::b. Это ссылки, поэтому их изменение приводит к изменению самих элементов списка.

splice массив, смещение, длина, список
splice массив, смещение, длина
splice массив, смещение

Удаляет элементы массива, начиная со смещения и указанной длины. Заменяет их указанным списком. Возвращает удаленный список. Если длина не указана, удаляет все элементы списка, начиная со смещения.

split /шаблон/, выражение, предел
split /шаблон/, выражение
split /шаблон/
split

Разделяет строку, указанную выражением, на массив элементов и возвращает его. В скалярном контексте возвращает количество полученных элементов массива, а сам массив помещает в @_ (в списковом контексте поместить результат в @_ можно если применить ?шаблон?) Если выражение отсутствует, то обрабатывается содержимое переменной $_. Если шаблон отсутствует, то разделителем является пробел. Все, что подходит по шаблону, считается разделителем. Если указан предел, то это максимальное число разделений. Отрицательное значение предела воспринимается как неограниченно большой предел. Если пустая строка походит под шаблон разделителя, то исходное значение разделяется посимвольно. Предел удобно использовать для частичного разделения строки.

Пример:

     ($a, $b, $остаток) = split(/ /,
      "Один Два Три Четыре",3);
здесь $a = 'Один', $b = 'Два' и $остаток = 'Три Четыре'

Если шаблон содержит круглые скобки, то символы-разделители, указанные в них, вставляются в результирующий список как обычные элементы, причем вставляется символ, который совпал с разделителем.

Пример:

        @a = split(/([,.])/, "Один,Два.Три");
здесь @a = ("Один", "," ,"Два", ".", "Три")

sprintf формат, список

Форматирует список по указанному формату аналогично функции sprintf() языка С.

sqrt выражение

Возвращает корень квадратный выражения.

srand выражение

Задает начальное значение для генератора случайных чисел - оператора rand. Если аргумент отсутствует, то используется текущее машинное время.

stat файл
stat выражение

Возвращает 13 элементный массив параметров указанного файла или пустой список при ошибке. Применяется обычно следующим образом:

     ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks)
 = stat($filename);
где
  • $dev - имя устройства
  • $ino - номер i-узла
  • $mode - права доступа
  • $nlink - количество связей
  • $uid - идентификатор владельца
  • $gid - идентификатор группы
  • $rdev - тип устройства
  • $size - размер файла в байтах
  • $atime - дата последнего обращения
  • $mtime - дата последней модификации
  • $ctime - дата последнего изменения статуса
  • $blksize - размер блока на диске
  • $blocks - количество блоков в файле.
Если указан аргумент '_', то возвращается результат предыдущего вызова stat().

study скаляр
study

Включение метода частотного сравнения. Целесообразность применения зависит от количества поисков и количества констант в строке в которой ведется поиск. Функция работает следующим образом: Для указанного скаляра (если аргумент отсутствует берется значение переменной $_) строится список указателей на каждый символ строки. Затем, когда начинается поиск, первыми просматриваются символы, которые наиболее редко встречаются в английском языке. Выигрыш по времени очевиден, когда выполняется многократный поиск в одной и той же строке, и время затраченное на индексацию окупается.

substr выражение, смещение, длина
substr выражение, смещение

Возвращает подстроку выражения, начиная со смещения и заданной длины. Если смещение отрицательное, то отсчет ведется от конца строки. Если длина не указана, то берется все до конца строки.

symlink старый_файл, новый_файл

Создает новый файл, символически связанный со старым, т.е. создает ссылку на файл. Возвращает 1 при успехе и 0 - неудаче.

syscall список

Выполняет системную функцию. Первый элемент списка - это имя функции, а остальные элементы - ее аргументы. Если функция не выполнима, то возвращается фатальная ошибка. Аргументы интерпретируются следующим образом. Если аргумент число, то оно считается целым. Если не число, то аргумент считается указателем на строку. Вам необходимо обеспечить достаточную длину аргумента для подстановки значения указателя. В Perl передается максимум 14 параметров. Обычно этого достаточно.

sysread файл, скаляр, длина, смещение
sysread файл, скаляр, длина

Прочитать из файла указанное число байт в скаляр с помощью системного вызова read(). Смещение обозначает позицию в скаляре, после которой будут вставлены прочитанные байты.

system список

Делает то же самое, что и "exec список" с той лишь разницей, что вызов fork() делается первым, и родительский процесс ждет завершения потомка. Возвращает значение аналогичное функции wait(). Для получения действительного кода завершения необходимо разделить на 256.

syswrite файл, скаляр, длина, смещение
syswrite файл, скаляр, длина

Записать в файл указанное число байт скаляра с помощью системного вызова write(). Смещение указывает позицию в скаляре, откуда начинается запись.

tell файл
tell

Возвращает текущую позицию указателя в открытом файле. Если файл не указан, то последнего читаемого файла.

telldir директория

Возвращает текущую позицию указателя в открытой директории. Обычно это параметр для позиционирования указателя директории функцией seekdir().

tie переменная, класс, список

Присваивает переменную классу. Список - это аргументы new метода класса (TIESCALAR, TIEARRAY или TIEHASH). Возвращает указатель на созданный новый объект класса, который удобен для вызова других методов класса.

Внимание! Такие функции как keys() и values() могут вернуть в качестве значения огромное количество данных.

Для создания объекта ассоциативного типа необходимо определение следующих методов:

  • TIEHASH classname, LIST
  • DESTROY this
  • FETCH this, key
  • STORE this, key, value
  • DELETE this, key
  • EXISTS this, key
  • FIRSTKEY this
  • NEXTKEY this, lastkey
Обычного массива:
  • TIEHASH classname, LIST
  • DESTROY this
  • FETCH this, key
  • STORE this, key, value
  • [others TBD]
Скалярного типа:
  • TIEHASH classname, LIST
  • DESTROY this
  • FETCH this, key
  • STORE this, key, value

time

Возвращает количество секунд, начиная с 1 января 1970 г 00:00:00 UTC. Годы не разделяются на високосные.

times

Возвращает 4-х элементный массив значений:

     ($user, $system, $cuser, $csystem) = times;
где:
  • $user - пользовательское время процесса в секундах.
  • $system - системное время текущего процесса.
  • $cuser - время процесса-потомка текущего процесса.
  • $csystem - его системное время.

tr///

Оператор транслятор. Подробно смотри главу "Операторы".

truncate файл, длина
truncate выражение, длина

Усекает длину указанного файла.

uc выражение

Возвращает строку выражения, в которой малые буквы заменены на заглавные.

ucfirst выражение

Возвращает строку выражения, в которой первая буква заменена на заглавную.

umask выражение
umask

Устанавливает маску umask и возвращает старое значение. Без аргумента возвращает текущее значение.

undef выражение undef

Делает неопределенным значение выражения. Применяется только для скалярных переменных, целых массивов или подпрограмм. Всегда возвращает неопределенное значение.

unlink список

Удаляет файлы, указанные в списке. Возвращает количество удаленных файлов. Только супервизор может удалять директории с файлами, если Perl запущен с ключом -U.

unpack шаблон, выражение

Функция, обратная pack(). Распаковывает строку выражения по указанному шаблону в массив значений. В скалярном контексте возвращает значение только первого элемента. Шаблоны такие же как и в pack().

Символ '%' в шаблоне означает, что вы хотите получить вместо значения его контрольную сумму. По умолчанию - 16-битную.

untie переменная

Исключает переменную из класса указанного tie().

unshift массив, список

Противоположное shift. Помещает элементы списка в начало массива.

use модуль список
use модуль

Указывает библиотечный модуль, из которого можно вызывать подпрограммы. Список перечисляет имена подпрограмм, которые добавляются к текущей области имен. Если не указан, то все имена. Действие аналогично блоку:

     BEGIN { require модуль; import модуль список; }
Сам модуль должен располагаться в системной области Perl файлов.

utime список

Изменяет дату обращения и модификации файлов из списка. Первые два элемента списка должны указывать новое значение даты обращения и модификации. Возвращает количество измененных файлов.

values хеш

Возвращает обычный массив со всеми значениями хеша. В скалярном контексте возвращает количество значений хеша.

vec выражение, смещение, бит

Возвращает значение строки как вектор беззнаковых целых.

wait

Ожидать окончание порожденного процесса. Возвращает PID для родительского процесса или -1, если нет - процесса потомка. Код завершения записывается в переменную $?.

waitpid PID, FLAGS

Ожидать окончания процесса потомка с указанным PID. Возвращает либо PID этого процесса либо -1 если нет такого процесса. Код завершения записывается в переменную $?.

wantarray

Возвращает true, если подпрограмма вызывается в списковом контексте и false - в скалярном.

warn список

Выводит в стандартный поток ошибок STDERR сообщение аналогично die(), но не прекращает работу программы.

write файл
write выражение
write

Выводит в файл форматированные данные по оператору format. По умолчанию имя формата совпадает с именем указателя файла. Имя файла по умолчанию можно изменить функцией select(), а имя формата при этом надо присвоить переменной $~. Заголовок листа пишется автоматически. Если на листе не хватает места для вывода, то выводится "перевод формата" и специальная форма заголовка листа, затем сами записи. По умолчанию имя формата заголовка листа состоит из имени указателя файла и добавленной строки "_TOP". Динамически это можно делать, присваивая формат переменной $^. Количество оставшихся строк листа содержится в переменной $- и если присвоить ей 0, то произойдет смена листа. Подробно описание форматов смотри в главе Форматы.

y///

Оператор транслятор. Смотри главу Операторы.


Введение в Perl

В книге приводятся начальные сведения по новому языку программирования Perl. Данный язык получил широкое распространение в связи с развитием компьютерной сети Интернет. Все примеры в книге проверены для Perl версии 5.003 операционной системы Unix FreeBSD 2.1.0.

Для программистов, системных администраторов и пользователей компьютеров.
© Маслов Владимир Викторович.

Содержание

  • От простого к сложному
  • Запуск Perl - программы
  • Синтаксис
    • Декларации (объявления)
    • Простое предложение
    • Сложные предложения
    • Циклы
      • while
      • Операторы управления циклом
        • next
        • last
        • redo
      • for
      • foreach
    • Блоки и оператор switch
    • Оператор goto
    • POD операторы. Документирование программ
  • Переменные
    • Контекст
    • Скалярные значения
    • Конструкторы скаляров
    • Конструкторы списков
    • Тип typeglobs
  • Встроенные переменные
  • Регулярные выражения (шаблоны)
  • Операторы и приоритеты
    • Термы и левосторонние списковые операторы
    • Унарные операторы
    • Операторы "привязки" =~ и !=
    • Мультипликативные операторы
    • Аддитивные операторы
    • Операторы сдвига
    • Именованные унарные операторы
    • Операторы отношений
    • Операторы равенства
    • Операторы работы с битами
    • Логические операторы && (AND) и || (OR)
    • Оператор диапазона '..'
    • Условный оператор '?:'
    • Операторы присваивания
    • Оператор ',' (запятая)
    • Логические not, and, or, xor
    • Операторы ограничители строк
    • Операторы ввода-вывода
    • Слияние констант
    • Целочисленная арифметика
  • Встроенные функции
  • Подпрограммы
    • Private переменные
    • Переменные типа local
    • Прототипы (prototypes)
    • Ссылка как параметр
    • Переопределение встроенных функций
    • Автозагрузка
  • Модули
    • Таблицы имен
    • Конструкторы и деструкторы
    • Классы
    • Создание библиотеки
    • Pragma библиотеки
    • Стандартные библиотеки
    • CPAN
  • Форматы отчетов
  • Отладка
  • Интернет - ссылки и литература


Введение в Perl. Интернет - ссылки и литература.

Интернет - ссылки и литература.

Очень полезные USENET конференции:

  • comp.lang.perl.announce - Различные объявления и сообщения.
  • comp.lang.perl.misc - Место для вопросов и обсуждений.
  • comp.lang.perl.modules - Модули Perl.
  • comp.lang.perl.tk - Perl и Tcl/tk.

WWW сервера:

  • Специальный Perl-сервер
  • Книги по Unix издательства O`Reily
  • Специализированный FTP сервер
  • Список модулей

Список литературы.

При написании книги автор в основном пользовался описанием Perl "Perl Programmers Reference Guide".
Наиболее известная зарубежная литература:

  • Programming Perl (the Camel Book)
  • Learning Perl (Llama Book)


Введение в Perl. Модули (packages).

Модули (packages).

  • Таблицы имен
  • Конструкторы и деструкторы
  • Классы
  • Создание библиотеки
  • Pragma библиотеки
  • Стандартные библиотеки
  • CPAN

В Perl реализован механизм модулей. Модуль это группа подпрограмм и переменных обычно включенных в один файл. Внутри одного модуля можно определить другой модуль. Начало модуля определяется директивой:

  • packages имя_модуля;

Конец модуля это конец блока или файла. Головной модуль имеет по умолчанию имя main. На имя внутри модуля можно ссылаться, добавляя '::' после имени модуля.

Например:

    $main::var1	- переменная в головном модуле.

    ::var1		- то же самое. Имя main можно опускать.

    $модуль1::var1	- переменная в модуле 'модуль1'

    $модуль1::модуль2::var1 - Модуль2
     содержится в модуле 1.

Только идентификаторы, начинающиеся с буквы или символа '_', хранятся в пространстве имен текущего модуля. Остальные хранятся в пространстве головного модуля main. Кроме этого имена STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC и SIG так же хранятся в головном модуле.

Таблицы имен.

Все имена модуля хранятся в ассоциативном массиве (хеше) с именем модуля, к которому добавлены символы "::". Таким образом имена головного модуля хранятся в %main:: , модуля 'mod1' в %mod1:: и т.д. Выражение вида *имя указывает значение элемента хеша 'имя', это удобно для определения констант.

Например:

    *pi = \3.14159;
Здесь переменная $pi - это константа пи, которую уже нельзя изменить.

Конструкторы и деструкторы.

Конструктор - это подпрограмма, которая выполняется в момент создания объекта, а деструктор - удаления объекта. Для модуля это подпрограммы с именами BEGIN и END. При определении этих подпрограмм слово sub можно опускать.

Конструктор BEGIN выполняется сразу как только возможно, т.е. как только он определен, даже не завершая дальнейший разбор программы. Можно указать несколько блоков BEGIN. Они будут выполняться один за другим в порядке определения.

Деструктор END выполняется последним как только возможно, т.е. при завершении работы интерпретатора. Можно указать несколько блоков END, при этом они будут выполняться в обратном определению порядке.

Классы.

В Perl нет специального синтаксиса для классов. Но функционально полноценными классами могут быть модули. При этом подпрограммы модуля становятся методами, а с помощью массива @ISA можно реализовать механизм наследования в классах. Более подробно классы описаны отдельно.

Создание библиотеки.

Если вы хотите создать модуль отдельным файлом и использовать как библиотеку подпрограмм, при этом вызывать подпрограммы библиотеки, не указывая имени модуля, вам необходимо оформить модуль следующим образом:

    package имя_модуля;	# Такое же как и имя этого
    файла без расширения '.pm'
    require Exporter;	# Обязательная строка
    для экспорта имен
    @ISA = qw(Exporter);	# -//-
    @EXPORT = qw(func1 func2) # Перечисляем
     имена функций. Внимание ! нет запятой!
    @EXPORT_OK = qw( $переменная @массив );
    # Указать публичные переменные, массивы и т.д.
     если необходимо
    {  # Начало блока модуля
    .....
    sub func1
    ........
    sub func2
    ........
    1;
    }

Данный файл с расширением ".pm" должен храниться в одной из библиотечных директорий Perl. Они перечислены в массиве @INC, одна из них обычно "/usr/local/lib/perl/".

В головной программе вы указываете:

  • use имя_модуля;
и вам становятся доступны имена подпрограмм данного модуля.

Perl библиотеки.

Стандартный набор библиотек обычно поставляется с дистрибутивом Perl, они разделяются на pragma библиотеки (работают как директивы компилятору) и стандартные библиотеки.

Pragma библиотеки.

Данные библиотеки используют как:

  • use имя;
когда хотят включить действие и
  • no имя;
когда выключить.

В стандартный набор входят следующие pragma:

diagnostics
Включить режим расширенной диагностики.

integer
Использовать целочисленную арифметику.

less
Режим минимальной загрузки компилятора.

overload
Режим переопределения операторов.

sigtrap
Режим слежения за прерываниями.

strict
Режим ограниченного использования "опасных" операторов.

subs
Режим обязательного декларирования подпрограмм.

Стандартные библиотеки.

AnyDBM_File
Возможность работы с разными типами баз данных.

AutoLoader
Загрузка в память функций только во время вызова.

AutoSplit
Разделить модуль для автозагрузки.

Benchmark
Анализ скорости исполнения программы.

Carp
Предупреждения об ошибках.

Config
Доступ к конфигурации Perl.

Cwd
Получить имя текущей рабочей директории.

DB_File
Работа с базой данных формата Berkley DB.

Devel::SelfStubber
Режим отладки автозагрузки.

DynaLoader
Динамическая загрузка библиотек C.

English
Использовать длинные имена встроенных переменных.

Env
Импортировать имена переменных окружения

Exporter
Обеспечивает экспорт/импорт для модулей.

ExtUtils::LibList
Определяет используемые библиотеки.

ExtUtils::MakeMaker
Создает файл проекта Makefile

ExtUtils::Manifest
Программы для создания и проверки файла MANIFEST

ExtUtils::Mkbootstrap
Применение файла начальной загрузки для DynaLoader.

Fcntl
Определения как и в С Fcntl.h

File::Basename
Синтаксический разбор спецификации файла.

File::CheckTree
Быстрый проход по директориям диска.

File::Find
Быстрый поиск файлов по директориям.

FileHandle
Обеспечивает объектный метод доступа к указателям файлов.

File::Path
Создание/удаление директорий.

Getopt::Long
Расширенная обработка опций.

Getopt::Std
Стандартная обработка опций.

I18N::Collate
Сравнение символов локальной кодировки.

IPC::Open2
Межпроцессорный обмен по чтению и записи.

IPC::Open3
Межпроцессорный обмен по чтению, записи, и обработки ошибок.

Net::Ping
Тест доступа к хосту.

POSIX
Стандартный интерфейс по IEEE Std 1003.1

SelfLoader
Загрузка функций только по вызову.

Socket
Определение структур и констант как и в С socket.h

Test::Harness
Стандартный тест с статистикой.

Text::Abbrev
Создание таблицы сокращений по списку.

Подробное описание каждой библиотеки записано в самом файле.

CPAN

Программисты всего мира, работающие с Perl, создали общедоступную библиотеку модулей CPAN. Она доступна через Интернет и содержит огромное количество различных по назначению модулей. К ним относятся документаторы, системные интерфейсы, интерфейсы работы с базами данных, работа в сети, с файлами, Интернет-броузеры, системы поиска, огромное количество CGI скриптов для Web серверов и многое-многое другое. Список некоторых CPAN серверов вы можете найти в приложении.


Введение в Perl. Операторы и приоритеты.

Операторы и приоритеты.

  • Термы и левосторонние списковые операторы
  • Унарные операторы
  • Операторы "привязки" =~ и !=
  • Мультипликативные операторы
  • Аддитивные операторы
  • Операторы сдвига
  • Именованные унарные операторы
  • Операторы отношений
  • Операторы равенства
  • Операторы работы с битами
  • Логические операторы && (AND) и || (OR)
  • Оператор диапазона '..'
  • Условный оператор '?:'
  • Операторы присваивания
  • Оператор ',' (запятая)
  • Логические not, and, or, xor
  • Операторы ограничители строк
  • Операторы ввода-вывода
  • Слияние констант
  • Целочисленная арифметика

В Perl ассоциативность и приоритетность операторов аналогична языку С. Ниже перечислены все операторы в порядке уменьшения приоритета, в начале строки указана ассоциативность.
ассоц. | операторы
левая | термы и левосторонние списковые операторы
левая | ->
- | ++ --
правая | **
правая | ! ~ \ унарные + и -
левая | =~ !~
левая | * / % x
левая | + - .
левая | << >>
- | именованные унарные операторы
- | < > <= >= lt gt le ge
- | == != <=> eq ne cmp
левая | &
левая | | ^
левая | &&
левая | ||
- | ..
правая | ?:
правая | = += -= *= и т.д.
левая | , =>
- | правосторонние списковые операторы
левая | not
левая | and
левая | or xor

Термы и левосторонние списковые операторы.

Любой терм имеет самый высокий приоритет. К терму относятся переменные, кавычки и их операторы, арифметические и логические выражения в скобках, любые функции с параметрами в скобках. Фактически таких функций нет, так как это просто унарные и списковые операторы. Просто они ведут себя подобно функциям с параметрами в скобках. Подробно смотри главу Функции.

Если после любого спискового оператора ( print(), и т.д.) или унарного оператора (chdir(), и т.д.) следует левая круглая скобка, то операторы внутри скобок имеют наивысший приоритет. Так же как и обычные функции.

Если скобки отсутствуют, то приоритет списковых операторов или наивысший или наименьший в отношении операторов справа или слева от него.

Например:

        @i = ('a ','b ', print 'c ', 'd ');
        print "\n",@i,"\n";
Результат:
     c d
     a b 1

Здесь мы имеем списковый оператор print. Для запятых слева от него он имеет наименьший приоритет, но повышает приоритет правой запятой. Поэтому правая запятая воспринимается как параметр для print и печатается 'c d', а левая просто записывает код завершения операции в массив @i и последний print показывает это.

Оператор '->'

Так же как в С или С++ это инфиксный оператор переадресации. Если справа от него стоит [...] или {...} выражение, то правая часть может быть непосредственной или символической ссылкой на массив или хеш. В противном случае правая сторона это метод или простой скаляр, содержащий имя метода, а правая - или объект или имя класса. Подробно смотри главу Классы.

Операторы ++ (инкремент) и -- (декремент).

Эти операторы работают так же как и в С. Если оператор стоит перед переменной, то значение переменной изменяется на 1 и полученное значение используется. Если после переменной - то ее величина изменяется после применения.

Употребление инкремента к строковым переменным в Perl имеет одну особенность. Каждый символ остается в своем классе (большие, малые, цифры) и учитывается перенос предыдущего символа. Таким образом строковые переменные с цифрами работают как числовые переменные.

Пример:

     print ++($i = "09");     # Результат "10"

     print ++($i = "a9");     # "b0"

     print ++($i = "az");     # "ba"
     print ++($i = "aZ");     # "bA"

Оператор ** (возведение в степень)

Пример:

     print 4**2     # Результат 16

     print -4**2     # Результат -16 т.е. -(4**2)

Унарные операторы.

'!' | - логическое отрицание
'-' | - арифметический минус
'~' | - побитная инверсия (дополнение до 1)
'+' | - арифметический плюс
'\' | - получение ссылки на переменную (как & в С)

Операторы "привязки" =~ и != .

Эти "необычные", я бы даже сказал, оригинальные операторы имеют очень широкое применение в Perl. Можно даже назвать их "оригинальным" решением. Оператор =~ логически связывает левую часть выражения с патерном (pattern - образец, шаблон, модель) в правой. По умолчанию поиск или изменение по патерну выполняется в переменной $_ Операторы привязки позволяют делать это с любой переменной, указанной в левой части. Логическим результатом будет успех операции. Если в правой части вместо патерна присутствует выражение, то результат этого выражения воспринимается как патерн. Однако это не очень эффективно, т.к. патерн будет компилироваться во время исполнения программы, что заметно снизит быстродействие. Оператор != аналогичен =~, только результат совпадения инвертируется (логическое "нет"). Подробное применение этих операторов приводится в главе Патерны.

Мультипликативные операторы.

'*' | - арифметическое умножение
'/' | - арифметическое деление
'%' | - арифметический модуль
'x' | - оператор повторения

В скалярном контексте возвращает строку левой части, повторенную величиной, указанной в правой части. В списковом контексте, если в левой части список, то в круглых скобках - повторенный список.

Пример:

     print '*' x 5;     # Результат '*****'

     print (1,2) x 3; # Результат 121212

Аддитивные операторы.

'+' | - арифметический плюс
'-' | - арифметический минус
'.' | - конкатенация (объединение) строк

Операторы сдвига.

'<<' | - Сдвигает побитно влево значение выражения в левой части
 | на количество бит указанное в правой.
'>>' | - Сдвигает побитно вправо значение выражения в левой части
 | на количество бит указанное в правой.

Именованные унарные операторы.

Фактически это функции с одним аргументом. Круглые скобки можно опускать.

Операторы отношений.

'<' | - арифметическое меньше
'>' | - арифметическое больше
'<=' | - арифметическое меньше или равно
'>=' | - арифметическое больше или равно
'lt' | - строковое меньше
'gt' | - строковое больше
'le' | - строковое меньше или равно
'ge' | - строковое больше или равно

Операторы равенства.

'==' | результат true если левая часть равна правой (равно)
'!=' | - не равно
'<=>' | - -1 если левая часть меньше правой, 0 если равна,
 | 1 если больше.
'eq' | - строковое равно
'ne' | - строковое не равно
'cmp' | - как и '<=>' применительно к строкам

Операторы работы с битами.

'&' | - побитное AND
'|' | - побитное OR
'^' | - побитное XOR

Логические операторы && (AND) и || (OR).

'&&' | - если левое выражение возвращает false, правое не выполняется.
'||' | - если левое выражение возвращает true, правое не выполняется.

Отличие от подобных операторов в С заключается в том, что в С возвращаемое значение либо 0, либо 1, тогда как в Perl возвращается результат выражения.

Оператор диапазона '..'

Результат работы данного оператора зависит от контекста. В списковом контексте результат есть список с элементами, первый элемент которого это левое выражение и последнее - правое. Значение каждого элемента внутри списка увеличивается на 1. Данный оператор удобен для небольших циклов, т.к. память отводится для всего списка целиком. Поэтому будьте внимательны и не задавайте слишком большой диапазон.

Пример:

     for $i (1..4)
        { print "$i ";
        }
Результат: 1 2 3 4

В скалярном контексте результат - логическое значение. Каждая '..' операция устанавливает свое собственное состояние. Это false до тех пор пока левый операнд false. Как только левый операнд стал true результат - true до тех пока правый true, после чего результат опять - false. Если вы не хотите проверять правый операнд, то используйте оператор '...'.

Правый операнд не вычисляется пока результат false и левый операнд не вычисляется пока результат true. Приоритетность оператора '..' немного ниже чем '&&' и '||'. Возвращаемое значение если flase - нулевая строка, если true - порядковый номер начиная с 1. Порядковый номер обнуляется для каждого нового диапазона. Последний порядковый номер добавляется строкой "E0" которая не изменяет его значение, но позволяет фиксировать последнее значение.

Пример:

     @алфавит = ('a'..'z'); # Массив малых букв
                          латинского алфавита

     @цифры = (0..9);     # Массив цифр

Условный оператор '?:'

Этот оператор работает так же как и в С. Если выражение перед '?' истинно, то выполняется аргумент перед ':' - иначе после ':'.

Пример:

     $i = 1;
     $i > 1 ? print "больше" : print "меньше";
Результат: меньше

Операторы присваивания.

'='- обычный оператор "присвоить" правое значение переменной слева
Вся эта группа операторов подобна операторам С, т.е.

     $i += 2;
эквивалентно
     $i = $i + 2;

Остальные операторы этой группы работают аналогично. Допустимы следующие операторы:
**=
+=, -=, .=
*=, /=, %=, x=
&=, |=, ^=
<<=, >>=
&&=, ||=

Приоритет всей этой группы операторов равен приоритету '='.

Оператор ',' (запятая)

В скалярном контексте выполняется левый аргумент, результат игнорируется, затем правый и его результат есть результат действия оператора. В списковом контексте это разделитель элементов списка, который включает указанные элементы в список.

Операторы not, and, or, xor

Оператор логическое not (отрицание).
Унарный not возвращает противоположное значение, полученное выражением справа. Он эквивалентен '!', но имеет очень низкий приоритет.

Оператор логическое and (И).
Выполняет логическую конъюнкцию двух выражений. Эквивалентен '&&', но имеет очень низкий приоритет и "краткость" действия, т. е. если левое выражение равно false - левое не выполняется.

Логическое or (ИЛИ).
Выполняет логическую дизъюнкцию двух выражений. Эквивалентен '||', но имеет очень низкий приоритет и "краткость" действия, т. е. если левое выражение равно true - левое не выполняется.

Логическое xor (исключающее ИЛИ).
Выполняет логическое исключающие или. Всегда выполняются оба правое и левое выражение.

В Perl отсутствуют операторы языка С такие как:
унарное & | - получить адрес. Для этого применяется '\'.
унарный * | - переадресация.
(TYPE) | - совмещение типов.

Операторы ограничители строк.

Обычно ограничителями строк мы считаем литералы, но в Perl это операторы выполняющие разного рода интерполяцию и поиск по шаблону. Вы можете сами задавать удобные для вас ограничители. В следующей таблице приведен полный перечень вариантов. Фигурные скобки '{}' обозначают любой символ, используемый для ограничителя. В случае использования скобок (круглых '()', квадратных '[]', фигурных '{}', угловых '<>') в начале ставится открывающаяся скобка, а в конце закрывающая.
По умолчанию | Полное | Функция | Интерполяция
'' | q{} | Literal | нет
"" | qq{} | Литерал | да
`` | qx{} | Команда | да
 | qw{} | Список слов | нет
// | m{} | Шаблон | да
 | s{}{} | Подстановка | да
 | tr{}{} | Трансляция | нет

В строках допускающих интерполяцию имена переменных, начинающиеся с символов '$' или '@' - интерполируются, т.е. в строку вставляется значение строки или массива. Данные последовательности символов имеют специальное значение:
\t | символ табуляции
\n | символ новой строки
\r | возврат
\f | перевод формата
\v | вертикальная табуляция
\b | backspace (забой)
\a | звонок
\e | escape
\034 | восьмеричный символ
\x1a | шестнадцатеричный символ
\c[ | символ управления
\l | нижний регистр следующего символа
\u | верхний регистр следующего символа
\L | нижний регистр для всех символов до \E
\U | верхний регистр для всех символов до \E
\E | ограничитель смены регистра
\Q | отмена действия метасимволов до \E

Шаблоны интерполируются как регулярные выражения. Это выполняется вторым проходом после интерполяции переменных, поэтому в шаблоны можно вставлять переменные. Для отмены интерполяции используйте '\Q'. Если вы применяете вложенные ограничители, то внутренние ограничители работать не будут.

?PATERN?

Действие этого оператора аналогично /шаблон/, но выполняется до первого совпадения. Это удобно для поиска наличия какой-нибудь строки в одном или множестве файлов. Это не очень удачный оператор, поэтому в следующих версиях Perl его возможно не будет.

m/PATERN/gimosx
/PATERN/gimosx

Поиск в строке по патерну (шаблону). В скалярном контексте возвращает логическое значение true (1) или false (''). Если строка не указана с помощью операторов '=~' или '!~', поиск ведется в строке $_ Опции:
g | - Глобальный поиск. Поиск всех вхождений.
i | - Сравнение не зависит от регистра (верхний или нижний)
m | - Строка многострочная.
o | - однопроходная компиляция
s | - однострочная строка
x | - используются расширенные регулярные выражения.

Если '/' - ограничитель, то начальное 'm' можно опустить. С помощью него в качестве ограничителя может быть любой символ кроме пробела.

PATTERN может содержать переменные, которые будут интерполироваться (перекомпилироваться) каждый раз в момент вычисления. Переменные $) и $| не интерполируются. Если вы хотите, что бы такой шаблон интерполировался один раз - добавьте /o. Это необходимо делать в циклах поиска для увеличения быстродействия, однако, если вы измените значение переменной, Perl этого даже не заметит.

Если PATERN - нулевая строка, то используется последнее регулярное выражение.

В скалярном контексте возвращается список элементы которого - результаты выполнения выражений в скобках патерна ($1, $2, $3...). Обратите внимание что первый элемент $1.

Пример:

     $a = "/usr/local/perl/perl.bin";
              # Анализируемая строка
Цель: Создать массив @dirs с именами директорий.
Решение: Самый простой способ воспользоваться split('\/') но в качестве примера используем скобки.
     @dirs =~ m[/(\w*)/(\w*)/(\w*)/(\w*)]
Здесь 'm[' - использовать квадратные скобки как ограничители. (\w*)- шаблон алфавитно-цифровой последовательности.
В результате @dirs равен ('usr', 'local', 'perl')

q/строка/
'строка'

Строка литералов. Не интерполируется. Внутри строки разрешается использовать \' или \\ для обозначения символов ' и \ .

Пример:

     print q#Привет.#;     # Результат Привет.

     print 'O\'K';          # O'K

qq/строка/
"строка"

Интерполируемая строка.

Пример:

     $var = 13;
     print "\$var = $var";
Результат: $var = 13

qx/строка/
`строка`

Сначала строка интерполируется, а потом выполняется как системная команда.

Пример:

     print `date`;
Результат: Thu Nov 14 13:36:49 MSK 1996

qw/строка/

Возвращает список, элементы которого - слова строки, разделенные пробелами.

Пример:

     print qw/Построимся и спасемся!/;
       # ('Построимся','и','спасемся!')
Результат:
     Построимсяиспасемся!
Часто применяется как:
     use POSIX qw( setlocale localeconv )
     @EXPORT = qw( proc1 var );

s/шаблон/подстрока/egimosx

Поиск по шаблону и в случае успеха замена подстрокой. Возвращает количество произведенных подстановок, иначе false (0). Если строка в которой ведется поиск не указана (операторы =~ или != ), то используется переменная $_ . Если в качестве разделителя '/' использовать одинарную кавычку ('), то интерполяции не будет, иначе можно применять переменные в шаблоне или подстроке.

Опции:
e | - Рассматривать правую часть как выражение.
g | - Глобальный поиск.
i | - Без различия регистра букв
m | - многострочная переменная
o | - компилировать шаблон один раз
s | - однострочная переменная
x | - расширенное регулярное выражение

Разделитель '/' можно заменить на любой алфавитно-цифровой символ кроме пробела.

Пример:

     $var = "12345"; # исходная строка
     $var =~ s/1/0/; # Заменить '1' на '0'.
      Результат 02345
     $var =~ s(5)(.); # Заменить '5' на '.'
     Результат 0234.

Здесь в качестве разделителя применены скобки, поэтому подстрока взята в две скобки.

     $var =~ s/\d*/каламбур/; Заменить все цифры.
     Результат 'каламбур.'

     $var =~ s/а/о/g; # Заменить все 'а' на 'о'.
     Результат 'коломбур.'

     $var = "12 34"; # Новое значение

     $var =~ s/(\d\d) (\d\d)/$2 $1/; # Поменять
     местами числа. Результат '34 12'.

tr/таблица1/таблица2/cds
y/таблица1/таблица2/cds

Замена всех символов из "таблица1" на соответствующий символ из "таблица2". Результат - количество замен или стираний. Без оператора =~ или != операция выполняется со строкой $_. Для совместимости с программой sed вместо tr можно писать 'y'.

Опции:
c | - дополнение "таблица1"
d | - стереть найденные, но не замененные символы.
s | - "сжать" повторяющиеся замененные символы.

Если указана опция /d таблица2 всегда интерпретируется как положено. Другими словами, если таблица2 короче, чем таблица1, то символ из таблицы1 интерпретируется всегда. Если таблица2 - null, то все символы строки остаются неизменными. Это удобно для подсчета количества символов в строке определенного класса или для сжатия повторяющихся символов, например, пробелов.

Пример:

     $s = "hello";     # Исходная строка

     $s =~ tr/a-z/A-Z/;     # Заменить малые буквы
              на большие. Результат
                    # 'HELLO'

     $s = 'Hel....lo';
     $s =~ tr/a-zA-z/_/c;     # Заменить все
                  не буквы на '_'
                    # Результат 'Hel____lo'
     $s =~ tr/_/ /s;          # Заменить '_'
                  на ' ' и сжать.
                    # Результат 'Hel lo'
     $s =~ tr/a-zA-Z /a-zA-Z/d; # Удалить все
            не буквы. Результат 'Hello'

Если один и тот же символ несколько раз указан в таблице1, то применяется только первая замена.

Операторы ввода-вывода.

В Perl существует несколько операторов ввода-вывода. Первый это скобки из символа '`' - акцента. Строка в этих скобках воспринимается как системная команда и результат ее действия возвращается как "псевдо" литерал. В скалярном контексте это строка содержащая весь результат, а в списковом - список, элементы которого - строки результата. Статус выполненной команды хранится в переменной $? .

Следующая команда ввода вывода выглядит как '<файл>'. Вычисление <файл> приводит к чтению строки из файла. Обратите внимание, что 'файл' здесь не имя файла, а указатель файла, который создается функцией open(). В скалярном контексте читается одна строка вместе с символом '\n' - перевода строки, а в списковом весь файл читается в список, элементы которого суть строки файла. В случае обнаружения конца файла результат оператора не определен и воспринимается как false. Если не указана переменная результата, то по умолчанию это $_. Указатель файла по умолчанию STDIN - стандартный ввод.

Пример:

     while(<>) { print; }; # Прочитать и вывести
     весь файл STDIN

У оператора '<>' есть одна отличительная особенность. Если в командной строке нет никаких аргументов, то читается стандартный ввод, если есть аргументы, то они считаются именами файлов, которые последовательно читаются.

Если в угловых скобках записана переменная, то содержимое этой переменной считается именем указателя файла или ссылкой на указатель файла. Если такого указателя не существует, то содержимое переменной воспринимается как шаблон имен файлов и результат - имена файлов на диске, подходящих по шаблону.

Пример:

     while(<*.pl>) { print;}; # То же что и ls *.pl

     @files = <*>;     # Массив @files содержит имена
      файлов в директории
но лучше сделать: @files = glob("*"); т.к. внутри скобок можно использовать переменные.

Слияние констант.

Как и С Perl выполняет возможные вычисления в период компиляции. Так подстановка символов после '\' , операция конкатенации строк, арифметические выражения, содержащие только одни константы, все это делается в момент компиляции, что существенно увеличивает скорость выполнения программы.

Целочисленная арифметика.

По умолчанию Perl выполняет арифметику с плавающей запятой, но если вы укажете:

     use integer;
то компилятор будет использовать целочисленную арифметику до конца текущего блока, хотя вложенный блок может это и отменить в своих пределах с помощью:
     no integer;


Введение в Perl. Регулярные выражения (шаблоны).

Регулярные выражения (шаблоны).

В данной главе описывается синтаксис регулярных выражений. Чаще всего в Perl они используются в операторах поиска и замены таких как s//, m/, операторах связки =~ или != и т.д. Как правило все эти операторы имеют схожие опции такие как:
i | - не различать строчные и заглавные буквы.
m | - считать строку многострочной.
s | - однострочная строка.
x | - расширенный синтаксис ( использование пробелов и комментариев)

Обычно все эти опции обозначают как '/x'. Их можно использовать даже внутри шаблонов, используя новую конструкцию (?...)

Регулярные выражения или шаблоны (pattern) то же самое, что и regexp процедуры в Unix. Выражения и синтаксис заимствованы из свободно распространяемых процедур V8 Генри Спенсера (Henry Spencer), там же они подробно и описаны.

В шаблонах используются следующие метасимволы (символы обозначающие группы других символов) часто называемые egrep - стандартом:
\ | - считать следующий метасимвол как обычный символ.
^ | - начало строки
. | - один произвольный символ. Кроме '\n' - конец строки.
$ | - конец строки
| | - альтернатива (или)
() | - группировка
[] | - класс символов

Метасимволы имеют модификаторы (пишутся после метасимвола):
* | - повторяется 0 или большее число раз
+ | - -//- 1 или большее число раз
? | - 1 или 0 раз
{n} | - точно n раз
{n,} | - по меньшей мере раз
{n,m} | - не меньше n, но и не больше m

Во все других случаях фигурные скобки считаются обычными (регулярными) символами. Таким образом '*' эквивалентна {0,} , '+' - {1,} и '?' - {0,1}. n и m не могут быть больше 65536.

По умолчанию действие метасимволов "жадно" (greedy). Совпадение распространяется столько раз, сколько возможно, не учитывая результат действия следующих метасимволов. Если вы хотите "уменьшить их аппетит", то используйте символ '?'. Это не изменяет значение метасимволов, просто уменьшает распространение. Таким образом:
*? | - станет 0 и более
+? | - 1 и более
?? | - 0 или 1 раз
{n}? | - точно n раз
{n,}? | - не меньше n раз
{n,m}? | - больше или равно n и меньше m раз

Шаблоны работают так же, как и двойные кавычки, поэтому в них можно использовать `\` - символы (бэкслэш-символы):
\t | - символ табуляции
\n | - новая строка
\r | - перевод каретки
\а | - перевод формата
\v | - вертикальная табуляция
\a | - звонок
\e | - escape
\033 | - восьмеричная запись символа
\x1A | - шестнадцатеричная
\c[ | - control символ
\l | - нижний регистр следующего символа
\u | - верхний регистр -//-
\L | - все символы в нижнем регистре до \E
\U | - в верхнем -//-
\E | - ограничитель смены регистра
\Q | - отмена действия как метасимвола

Дополнительно в Perl добавлены следующие метасимволы:
\w | - алфавитно-цифровой или '_' символ
\W | - не -//-
\s | - один пробел
\S | - один не пробел
\d | - одна цифра
\D | - одна не цифра

Обратите внимание, что все это "один" символ. Для обозначения последовательности применяйте модификаторы. Так:
\w+ | - слово
\d+ | - целое число
[+-]?\d+ | - целое со знаком
[+-]?\d+\.?\d* | - число с точкой
Кроме того существуют мнимые метасимволы. Обозначающие не существующие символы в месте смены значения. Такие как:
\b | - граница слова
\B | - не граница слова
\A | - начало строки
\Z | - конец строки
\G | - конец действия m//g

Граница слова (\b) - это мнимая точка между символами \w и \W. Внутри класса символов '\b' обозначает символ backspace (стирания). Метасимволы \A и \Z - аналогичны '^' и '$', но если начало строки '^' и конец строки '$' действуют для каждой строки в многострочной строке, то \A и \Z обозначают начало и конец всей многострочной строки.

Если внутри шаблона применяется группировка (круглые скобки), то номер подстроки группы обозначается как '\цифра'. Заметьте, что за шаблоном в пределах выражения или блока эти группы обозначаются как '$цифра'. Кроме этого существуют дополнительные переменные:
$+ | - обозначает последнее совпадение
$& | - все совпадение
$` | - все до совпадения
$' | - все после совпадения

Пример:

$s = "Один 1 два 2 и три 3";
if ($s =~ /(\d+)\D+(\d+)/)
    {
        print "$1\n";# Результат '1'
        print "$2\n";# '2'
        print "$+\n";# '2'
        print "$&\n";# '1 два 2'
        print "$`\n";# 'Один '
        print "$'\n";# ' и три 3'
   }

Perl версии 5 содержит дополнительные конструкции шаблонов:
(?#комментарий) | - комментарий в теле шаблона.
(?:шаблон) | - группировка как и '( )', но без обратной ссылки
(?=шаблон) | - "заглядывание" вперед. Например /\w+(?=\t)/ соответствует слову, за которым идет табуляция, но символ '\t' не включается в результат.

Пример:

$s = "1+2-3*4";
if ($s =~ /(\d)(?=-)/)
          # Найти цифру за которой стоит '-'
    {
        print "$1\n";   # Результат '2'
   }
else { print "ошибка поиска\n";}

        (?!шаблон)         - "заглядывание" вперед
                                      по отрицанию.

Пример:

$s = "1+2-3*4";
if ($s =~ /(\d)(?!\+)/) # Найти цифру за которой
                            не стоит '+'
    {
        print "$1\n";   # Результат '2'
   }
else { print "ошибка поиска\n";}

(?ismx)- "внутренние" модификаторы. Удобно применять в шаблонах, где например нужно внутри шаблона указать модификатор.

Правила регулярного выражения. (regex)

  1. Любой символ обозначает себя самого, если это не метасимвол. Если вам нужно отменить действие метасимвола, то поставьте перед ним '\'.
  2. Строка символов обозначает строку этих символов.
  3. Множество возможных символов (класс) заключается в квадратные скобки '[]', это значит, что в данном месте может стоять один из указанных в скобках символов. Если первый символ в скобках это '^' - значит ни один из указанных символов не может стоять в данном месте выражения. Внутри класса можно употреблять символ '-', обозначающий диапазон символов. Например, a-z - один из малых букв латинского алфавита, 0-9 - цифра и т.д.
  4. Все символы, включая специальные, можно обозначать с помощью '\' как в языке С.
  5. Альтернативные последовательности разделяются символом '|' Заметьте что внутри квадратных скобок это обычный символ.
  6. Внутри регулярного выражения можно указывать "подшаблоны" заключая их в круглые скобки и ссылаться на них как '\номер' Первая скобка обозначается как '\1'.


Введение в Perl. Запуск интерпретатора Perl

Запуск интерпретатора Perl

Синтаксис:
    perl [ключи] файл аргументы
Перед стартом Perl ищет скрипт (программу) в следующем порядке:
  1. В командной строке если указан ключ '-e'
  2. Содержимое файла указанного в командной строке. В первой строке можно указывать #!/usr/bin/perl для "автоматического" запуска скрипта.
  3. Стандартный ввод/вывод, если не указан файл или аргументы, содержащие имя файла. Для передачи аргументов скрипту из стандартного потока, имя файла в командной строке обозначается символом '-'.

В методах 1 и 2 Perl сканирует строки, начиная с первой, если не указан ключ '-x', в противном случае началом программы считается строка с символами '#!' в начале и содержащая слово 'perl'. Концом программы считается строка '__END__'. В строке с '#!' можно указывать и ключи. Например '#!/bin/perl -d' для отладки программ.

После "обнаружения" скрипта Perl компилирует его целиком во внутреннее представление. Если обнаруживаются ошибки, то выполнение прекращается. Если ошибок нет, он выполняется. Если скрипт заканчивается без команд exit() или die(), то по умолчанию выполняется команда exit(0), обозначающая нормальное завершение программы.

Ключи:

  • -O цифры
    Код символа-разделителя записей. По умолчанию \0.
  • -a
    Включает режим автоматического разделения (split) переменной $_ в массив $F. Применяется с ключами -n и -p.
  • -c
    Выполняется синтаксическая проверка скрипта и выход без запуска.
  • -d
    Запуск в режиме интерактивной отладки.
  • -D число или D список
    Установить флаги отладки Perl. Например -d14 проследить как Perl исполняет вашу программу.
    1 | p | Синтаксический разбор
    2 | s | Состояние стека
    4 | l | Состояние стека имен
    8 | t | Трассировка исполнения
    16 | o | Создание оператора узла
    32 | c | Строковое/числовое преобразование
    64 | p | Вывод команды препроцессора для -P
    128 | m | Распределение памяти
    256 | f | Обработка формата
    512 | r | Синтаксический разбор регулярных выражений
    1024 | x | Дамп синтаксического дерева
    2048 | u | Проверка защиты
    4096 | L | "Утечка" памяти
    8192 | H | Дамп хеша
    16384 | X | Распределение scratchpad
    32768 | D | Очистка
  • -e команда
    Выполнение скрипта из одной строки указанного в командной строке.
  • -F шаблон
    Указывает шаблон разделения в режиме работы с ключом -a
  • -i расширение
    Применяется для резервной копии файла обрабатываемого оператором '<>'. Оригинал хранится в файле с тем же именем, что и исходный, но с указанным расширением.
    Пример:
        perl -p -i.old -e "s/рядовой/ефрейтор/" file
      Поменять все слова "рядовой" на "ефрейтор" в файле file, а оригинал записать в файле file.old
  • -I директория
    Директория includ- файлов для С препроцессора. Применяется с ключом -P по умолчанию это /usr/include и /usr/lib/perl.
  • -l число
    Автоматическая обработка символа конца строки. Работает в двух случаях.
    1. Отбрасывает последний символ читаемых строк для режимов -n и -p
    2. Присваивает указанное значение переменной $\. Таким образом к концу каждой строки выводимой оператором print добавляется этот символ.
  • -n
    Зацикливает скрипт и последовательно обрабатывает файлы указанные в командной строке. Позволяет создавать команды подобные sed или awk. Операторы BEGIN и END дают возможность делать начальные и конечные установки. Содержимое файлов не выводится.
  • -p
    То же что и -n, но печатает обрабатываемые строки файлов.
  • -P
    Предварительная обработка препроцессором языка С. Будьте внимательны и не применяйте в комментариях слова 'if', 'else' или 'define', т.к. это команды С - препроцессора.
  • -s
    Включение режима обработки ключей командной строки запуска скрипта. Все аргументы с символом '-' в начале, считаются ключом, и переменным с таким же именем присваивается значение true.
  • -S
    Использование системной переменной PATH для поиска скрипта. Данный ключ применяется в системах, невоспринимающих последовательность "#!" в начале скрипта для указания интерпретатора.
  • -T
    Режим проверки "дыр" в защите. Обычно это нужно для программ, работающих в режиме повышенной привилегии (setuid, setguid). Желательно для CGI скриптов.
  • -u
    Принудительный дамп памяти после компиляции скрипта. Этот дамп можно потом использовать для создания исполняемого файла с помощью программы undump.
  • -U
    Разрешение выполнять опасные операции. Например, стереть директорию или выполнять явно не закрытую программу.
  • -v
    Вывод номера версии Perl.
  • -w
    Вывод имен переменных, используемых только один раз, имен скаляров, используемых до их определения, имен переопределяемых подпрограмм, ссылок на неопределенные указатели файлов, попыток записи в файлы, открытых только на "чтение", употребление некорректных записей чисел, использование массивов как скаляров, рекурсия более 100 уровней.
  • -x директория
    Режим запуска скрипта, вставленного в файл, содержащий обычный текст. Началом скрипта считается строка с символами '#!' в начале и содержащая слово perl. Концом - строка с '__END__' Указанная директория становится текущей в момент исполнения. Если необходимо читать последующие строки, то это лучше делать через указатель файла DATA.


Введение в Perl. Подпрограммы.

Подпрограммы.

  • Private переменные
  • Переменные типа local
  • Прототипы (prototypes)
  • Ссылка как параметр
  • Переопределение встроенных функций
  • Автозагрузка

Для применения подпрограммы ее необходимо определить либо в текущем модуле (файле), либо во внешнем модуле (файле). Подпрограммы определяются и декларируются следующим образом:

  • sub имя; - Только декларация. Определение ниже.
  • sub имя (прототипы); - То же но с декларацией параметров.
  • sub имя блок; - Декларация и определение.
  • sub имя (прототипы) блок; - То же, но с параметрами.
Для определения динамической анонимной подпрограммы можно указать:
  • $переменная = sub блок;
Для импортирования подпрограмм из других модулей используйте:
  • use модуль qw(подпрограмма1 подпрограмма2 );
Вызов подпрограммы:
имя(список параметров);# символ '&' можно
                              не указывать.
имя список;            # Если подпрограмма уже
                               декларирована.
 &имя;                  # Параметры в @_

Все параметры передаются подпрограмме как массив @_. Соответственно $_[0] - первый параметр, $_[1] - второй и т.д. Массив @_ - локальный, но он содержит адреса параметров, поэтому можно изменять значение параметров. Возвращаемое значение подпрограммы - результат последнего оператора. Это может быть как скаляр так и массив. Можно принудительно возвращать результат используя функцию return().

Подпрограмму можно вызвать, используя префикс '&' перед именем подпрограммы. Если подпрограмма предварительно продекларирована, то префикс и скобки можно опустить.

Private переменные.

Для применения переменных доступных только внутри блока или подпрограммы необходимо определить их с помощью функции my(список).
Если переменная одна, то скобки можно опустить.

my() декларирует private переменные в пределах текущей подпрограммы, блока, функции eval() или do/require/use файлов. Private переменные аналогичны auto переменным в С.

Пример:

    # Программа вычисления факториала.

    print fact(3);  # вычислить факториал 3*2*1

    sub fact	# Определяем подпрограмму.
    { my $m;                # private переменная
                                но не local !
    $m = $_[0];
    return 1 if $m <= 1;
    return($m * fact($m -1));
    }

Можно указывать начальные значения private переменных как:

  • my(список) = выражение;
Так для вышеприведенного примера лучше было написать:
  • my($m) = $_[0];

Переменные типа local.

В общем лучше использовать private переменные, т. к. это надежней и быстрее. private переменные обеспечивают лексическую область применения (видимости), а local - динамическую. Обычно это переменные форматов, значение которых должно быть видимо из вызываемых подпрограмм. Применение функции local() нецелесообразно в циклах, так как она вызывается каждый раз и таким образом заметно увеличивает время выполнения цикла.

Прототипы (prototypes).

Для краткого описания типа передаваемых подпрограмме параметров можно применять прототипы. В Perl существуют следующие прототипы:
Декларация | Пример вызова
sub mylink($$) | mylink $old, $new
sub myvec($$$) | myvec $var, $offset, 1
sub myindex($$;$) | myindex &getstring, "substr"
sub myreverse(@) | myreverse $a, $b, $c
sub myjoin($@) | myjoin ":",$a,$b,$c
sub mypop(\@) | mypop @array
sub mysplice(\@$$@) | mysplice @array, @array, 0, @pushme
sub mykeys(\%) | mykeys %{$hashref}
sub myopen(*;$) | myopen HANDLE, $name
sub mypipe(**) | mypipe READHANDLE, WRITEHANDLE
sub mygrep(&@) | mygrep { /foo/ } $a, $b, $c
sub myrand($) | myrand 42
sub mytime() | mytime
Здесь:

  • \'символ' - параметр с типом 'символ'
  • '@' или '%' - все оставшиеся параметры как список
  • '$' - скаляр
  • '&' - безымянная подпрограмма
  • '*' - ссылка на таблицу имен
  • ';' - разграничитель обязательных и не обязательных параметров.

Ссылка как параметр.

Иногда нужно в качестве параметра передать подпрограмме не значение элемента массива, а ссылку на него, чтобы подпрограмма могла изменить значение элемента. Для этого в Perl к имени переменной добавляется символ '*' Подобное выражение называют 'type glob' так же как в Unix символом '*' обозначают "все возможные значения". Поэтому '*' для массива означает "все элементы массива". Для скаляров употреблять '*' не имеет смысла, т.к. они и так передаются ссылкой и вы можете изменять значение параметра, изменяя, например, переменную $_[0].

Переопределение встроенных функций.

Большинство встроенных функций Perl можно переопределить своими собственными. Обычно это делают для удобства совместимости Perl для разных платформ систем.
Для этого нужно перечислить имена этих функций в виде:

  • use subs 'функция1', 'функция2' ....;
и далее в модуле определить сами функции.

Автозагрузка.

Если вы попытаетесь вызвать несуществующую функцию, то Perl немедленно выдаст сообщение об ошибке. Но если вы определите подпрограмму с именем 'AUTOLOAD', то она будет вызвана с теми же параметрами, а переменная $AUTOLOAD будет содержать имя несуществующей подпрограммы. Данный механизм очень удобен для средств отладки.


Введение в Perl. Синтаксис.

Синтаксис.

  • Декларации (объявления)
  • Простое предложение
  • Сложные предложения
  • Циклы
    • while
    • Операторы управления циклом
      • next
      • last
      • redo
    • for
    • foreach
  • Блоки и оператор switch
  • Оператор goto
  • POD операторы. Документирование программ

Perl программа (скрипт) состоит из последовательности деклараций и предложений. Единственно что должно быть обязательно декларировано, это форматы отчетов и подпрограммы (функции). Все необъявленные переменные, массивы имеют значение 0 или null.

Декларации (объявления).

Perl имеет свободный формат. Комментарии начинаются с символа '#' и продолжаются до конца строки. Декларации могут использоваться в любом месте программы так же как и предложения (statements), но действуют они только в фазе компиляции программы. Обычно их помещают или в начале или в конце программы. Декларация подпрограмм позволяет использовать имя подпрограммы как списковый оператор, начиная с момента декларирования.
Пример:

    sub test;	# Декларация подпрограммы test

    $var1 = test $0; # Использование как оператора списка.

Декларации подпрограмм могут быть загружены из отдельного файла предложением require или загружены и импортированы в текущую область имен предложением use. Подробно см. главу Модули.

Простое предложение.

Простое предложение обязательно заканчивается символом ';' если только это не последнее предложение в блоке где ';' можно опустить. Заметьте, что существуют операторы такие как eval{} и do{}, которые выглядят как сложные предложения, но на самом деле это термы и требуют обязательного указания конца предложения.

Любое простое предложение может содержать single модификатор перед ';'. Существуют следующие single модификаторы:

    if EXPR
    unless EXPR
    while EXPR
    until EXPR
где EXPR - выражение, возвращающее логическое значение true или false. Модификаторы while и until вычисляются в начале предложения кроме блока do, который выполняется первым.

if EXPR- Модификатор "если". Предложение выполняется, если EXPR возвращает true.

Пример:

    $var = 1;
    $var2 = 3 if $var > 0; # Результат: $var2 = 3

while EXPR - Модификатор "пока". Предложение выполняется пока EXPR = true

Пример:

    $var = 1;
    print $var++ while $var < 5;
        # Печать $var с инкрементом
Результат: 1234

until EXPR- Модификатор "до ". Предложение выполняется до тех пор пока EXPR = false

Пример:

    $var = 1;
    print $var++ until $var > 5;
    # Печать $var с инкрементом
Результат: 12345

unless EXPR - Модификатор "если не" . Обратный к if. Выражение выполняется если EXPR = false.

Пример:

    $var = 1;
    print $var++ unless $var > 5;
     # Печать $var с инкрементом
Результат: 1

Сложные предложения.

Последовательность простых предложений, ограниченная функциональными ограничителями, называется блоком. В Perl это может быть целый файл, последовательность предложений в операторе eval{} или чаще всего это множество простых предложений, ограниченных круглыми скобками '{}'.

Существуют следующие виды сложных предложений:

    if (EXPR) BLOCK
    if (EXPR) BLOCK else BLOCK
    if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK

    LABEL while (EXPR) BLOCK
    LABEL while (EXPR) BLOCK continue BLOCK
    LABEL for (EXPR; EXPR; EXPR) BLOCK
    LABEL foreach VAR (LIST) BLOCK
    LABEL BLOCK continue BLOCK

Обратите внимание, что сложные предложения описаны в термах блоков, а не предложений, как в языках C или Pascal. Поэтому необходимо всегда использовать круглые скобки для обозначения блока.

if (EXPR) BLOCK - Вычисляется логическое выражение EXPR и если true блок выполняется.
Пример:

    $var =1;
    if ($var == 1)
       { print $var,"\n";
       }
Результат: 1

if (EXPR) BLOCK else BLOCK2 - Если EXPR=true выполняется BLOCK иначе BLOCK2.
Пример:

    $var =2;
    if ($var == 1)
       { print "\$var = 1\n";
       }
    else
       { print "\$var не равно 1\n";
       }
Результат: $var не равно 1

if (EXPR1) BLOCK1 elsif (EXPR2) BLOCK2 ... else BLOCK - Если EXPR1=true выполняется BLOCK1 иначе если EXPR2=true выполняется BLOCK2 иначе ... иначе BLOCK.
Пример:

    $var = 1;
    if ($var == 0)
       { print "\$var = 0\n";
       }
    elsif ($var == 1)
       { print "\$var = 1\n";
       }
    else
       { print "Не известное \$var\n";
       }
Результат: $var = 1

Цикл While

Цикл while выполняет BLOCK до тех пор пока EXPR = true. Метка LABEL не обязательна и состоит из идентификатора, завершающегося символом ':'. Метка необходима при использовании внутри блока цикла управляющих операторов next, last и redo. Если метка все же отсутствует, то эти операторы ссылаются к началу ближайшего цикла. Блок после continue выполняется всегда перед тем как вычисляется логическое выражение EXPR. Это подобно EXPR3 в предложении for, поэтому в этом блоке удобно изменять счетчики и флаги цикла даже если применяется оператор next.

Операторы управления циклом.

next - подобен continue в С. Переходит к началу текущего цикла т.е. повторяет итерацию.

Пример:

    M1:
    while ($i < 6)
        {
        ++$i;	# Увеличиваем счетчик на 1
        next M1 if $i < 3; # Переходим в начало
         если $i < 3
        ++$i;	# иначе увеличиваем счетчик еще раз на 1
        }
    continue
        {
        print "$i "; # Печатаем $i
        }
Результат: 1 2 4 6

last - подобен оператору break в языке С. Немедленно прерывает цикл. Блок continue пропускается.

Пример:

        M1:
        while ($i < 6)
            {
            ++$i;       # Увеличиваем счетчик на 1
            last M1 if $i > 3; # Выход из цикла
                               если $i > 3
            ++$i;       # иначе увеличиваем
                           счетчик еще раз на 1
            }
continue {
            print "$i "; # Печатаем $i
}
Результат: 2 4

redo - начать новый цикл не вычисляя EXPR и не выполняя continue блок.

Пример:

        M1:
        while ($i < 6)
            {
            ++$i;       # Увеличиваем счетчик на 1
            redo M1 if $i == 3; # Далее пропустить
                                   для $i = 3
            ++$i;       # иначе увеличиваем
                              счетчик еще раз на 1
            }
continue {
            print "$i "; # Печатаем $i
}
Результат: 2 5 7

Цикл for.

LABEL for (EXPR1; EXPR2; EXPR3) BLOCK
Оператор for полностью аналогичен оператору for в С. В перед началом цикла выполняется EXPR1, если EXPR2 = true выполняется блок, затем выполняется EXPR3.

Пример:

    for ($i = 2; $i < 5; ++$i)
           {
           print $i, " ";
           }
        print "\nПосле цикла i = $i\n";
Результат:
    2 3 4
    После цикла i = 5

Цикл foreach.

LABEL foreach VAR (LIST) BLOCK
Переменной VAR присваивается поочередно каждый элемент списка LIST и выполняется блок. Если VAR опущено, то элементы присваиваются встроенной переменной $_. Если в теле блока изменять значение VAR то это вызовет изменение и элементов списка т.к. VAR фактически указывает на текущий элемент списка. Вместо слова foreach можно писать просто for - это слова синонимы.

Пример:

        @месяц = ("январь","февраль","март");
         # Создали массив
        foreach $i (@месяц)
            {
            print $i," ";       # Печать $i
            }
Результат: январь февраль март

Пример:

        @месяц = ("январь","февраль","март");
          # Создали массив
        foreach $i (@месяц)
            {
            $i = uc($i); # Перевели в верхний регистр
            }
        print @месяц;
Результат: ЯНВАРЬФЕВРАЛЬМАРТ

Пример:

    for $i (3,5,7)
        {
        print "$i ";
        }
Результат: 3 5 7

Блоки и оператор switch.

Блок не зависимо от того имеет он метку или нет семантически представляет собой цикл который выполняется один раз. Поэтому действие операторов цикла next, last, redo - аналогично описанному выше. Блоки удобны для построения switch (переключатель) структур. В Perl нет специального оператора switch подобного языку С поэтому вы сами можете создавать удобные для вас конструкции. Опыт автора показывает что для простоты написания лучше всего подходит конструкция вида if ... elsif ... else ... хотя можно сочинить и нечто подобное:

    SWITCH:
    {
    if ($i ==1 ) { .....; last SWITCH; }
    if ($i ==2 ) { .....; last SWITCH; }
    if ($i ==3 ) { .....; last SWITCH; }
    $default = 13;
    }
Выбирайте сами по своему вкусу.

Оператор goto.

В Perl существует оператор goto хотя где , как и когда его применять как говорил Ходжа Насреддин "Тонкий философский вопрос". Для начинающих программистов которым от так "нужен" я бы вообще посоветовал "забыть" о его существовании. Однако при создании больших производственных задач на последнем этапе особенно при отработке "отвальных" ошибочных ситуаций конечно goto нужен.

В Perl реализовано три формы goto. goto - метка, goto - выражение и goto - подпрограмма.
goto | метка выполняет непосредственный переход на указанную метку.
goto - выражениеВычисляет имя метки и делает соответствующий переход. Например если мы хотим сделать переход на одну из трех меток "M1:", "M2:" или "M3:" в зависимости от значений переменной $i равной 0, 1 или 2 то это лучше сделать следующим образом:
    goto ("M1", "M2", "M3")[$i];
здесь $i используется как индекс массива указанного непосредственно в выражении.
goto подпрограмма | довольно редкий случай т.к. всегда проще и надежней вызвать подпрограмму "естественным" образом.

POD операторы. Документирование программ.

В Perl реализован очень удобный механизм для написания документации в момент создания программы. Для этого применяются специальные POD операторы. Если в теле программы интерпретатор встречает оператор начинающийся с символа '=' например:

    = head Набор стандартных процедур
то пропускается все до слова '=cut'. Это удобно для включения длинных на несколько строк или страниц комментариев. Затем с помощью специальной программы pod можно отделить текст документации от текста программы.


Введение в Perl. Встроенные переменные Perl.

Встроенные переменные Perl.

Описанные в данной главе переменные имеют в Perl специальные значения. Они обозначаются несколько непривычно для "глаза" программистов, т.к. состоят обычно только из двух символов, причем первый это '$' символ, с которого начинаются имена всех переменных, и произвольный часто не буквенно-цифровой символ. Если вы хотите пользоваться их "нормальными" буквенными синонимами, то вам нужно указать в начале программы:

    use English;

Точно так же если вы захотите пользоваться переменными и методами текущего указателя файлов вы можете написать:

    use FileHandle;
после этого можно просто писать:
    метод указатель выражение
или
    указатель -> метод(выражение)

Ниже приводятся имена как в короткой, так и в длинной (словесной) форме. Некоторые из встроенных переменных имеют доступ только на чтение, поэтому изменить их значение просто невозможно.

$_
$ARG

Переменная - по умолчанию для операторов ввода и поиска. То есть если в качестве аргумента не указана никакая переменная, то используется именно эта.

$цифра

Содержит найденную подстроку в последнем поиске, когда шаблон содержит метасимволы в круглых скобках. Цифра в данном случае, это номер скобок. Первая подстрока имеет номер 1.

$&
$MATCH

Найденная подстрока в последнем поиске по шаблону.

$`

Подстрока, предшествующая найденной подстроке.

$'
$POSTMATCH

Подстрока, последующая за найденной подстрокой.

$+
$LAST_PAREN_MATCH

Подстрока, найденная в поиске с выбором по "или".

$*
$MULTILINE_MATCHING

Если значение этой переменной установить равным 1, то переменная, в которой осуществляется поиск, будет считаться многострочной, т.е. содержащей символы '\n' - перевод строки. Если значение равно 0, то переменная считается однострочной. В Perl версии 5 и выше не рекомендуется использовать эту переменную.

$.
$INPUT_LINE_NUMBER
$NR

Номер прочитанной строки последнего оператора ввода. Закрытие файла вызывает очистку значения этой переменной.

$/
$RS
$INPUT_RECORD_SEPARATOR

Символ - признак конца входной строки. По умолчанию это '\n'

$|
$OUTPUT_AUTOFLUSH

Если присвоить этой переменной ненулевое значение, то будет сброс буфера вывода после каждой операции вывода. Значение по умолчанию - 0

$,
$OFS
$OUTPUT_FIELD_SEPARATOR

Символ, добавляемый оператором print после каждого элемента из списка параметров.

$\
$ORS
$OUTPUT_RECORD_SEPARATOR

Символ, добавляемый print после вывода всех параметров.

$"
$LIST_SEPARATOR

Аналогичен "$,", но добавляется после каждого элемента массива, указанного в "....".

$;
$SUBSEP
$SUBSCRIPT_SEPARATOR

Символ - разделитель для эмуляции многомерных массивов в хеш массивах. По умолчанию '\034'.

$#
$OFMT

Формат по умолчанию для вывода чисел.

$%
$FORMAT_PAGE_NUMBER

Формат по умолчанию для вывода номеров страниц.

$=
$FORMAT_LINES_PER_PAGE

Длина одной страницы. По умолчанию 60 строк.

$-
$FORMAT_LINES_LEFT

Количество оставшихся строк на странице.

$~
$FORMAT_NAME

Имя формата текущего вывода. По умолчанию имя указателя.

$^
$FORMAT_TOP_NAME

Имя текущего формата для заголовка страницы.

$:
$FORMAT_LINE_BREAK_CHARACTERS

Символы переноса строки для многострочных полей. В строке формата такие поля начинаются символом '^'. По умолчанию '\n-'.

$^L
$FORMAT_FORMFEED

Символ перевода формата ( смены листа). По умолчанию '\f'.

$^A
$ACCUMULATOR

Текущее значение аккумулятора функции write() для format(). Значение этой переменной можно увидеть только при использовании функции formline(), т.к. write() очищает ее после каждого вывода.

$?
$CHILD_ERROR

Данная переменная содержит статус завершения таких процессов как: закрытие pipe, завершение функций system(), wait() и `...`.

$!
$ERRNO $OS_ERROR

В числовом контексте возвращает код ошибки errno. В строковом - строку сообщения об ошибке. Можно принудительно присвоить этой переменной код ошибки, что бы получить системное сообщение для данного кода или установить код завершения для функции die().

$@
$EVAL_ERROR

Сообщение об ошибке последней команды eval().

$$
$PID
$PROCESS_ID

Номер текущего процесса.

$<
$UID
$REAL_USER_ID

Реальный UID текущего процесса.

$>
$EUID
$EFFECTIVE_USER_ID

Эффективный UID текущего процесса.

$( $GID
$REAL_GROUP_ID

Реальный GID текущего процесса.

$)
$EGID
$EFFECTIVE_GROUP_ID

Эффективный GID текущего процесса.

$O
$PROGRAM_NAME

Имя файла программы. Если этой переменной присвоить какое-нибудь значение, то его можно видеть в команде ps, что удобно для контроля за состоянием программы.

$[

Номер первого элемента массива или символа строки. Значение по умолчанию - 0.

$]
$PERL_VERSION

Строка - сообщение версии Perl. Печатается по команде perl -v Применяется в программе для определения рабочей версии Perl. В числовом контексте это номер версии плюс номер модификации / 1000.

$^D
$DEBUGGING

Текущее значение ключа отладки '-D'.

$^F
$SYSTEM_FD_MAX

Номер максимального системного описателя файлов (system file descriptor). Обычно это 2.

$^I
$INPLACE_EDIT

Текущее значение inplace-edit возможности. Для отключения используйте undef.

$^P
$PERLDB

Внутренний флаг отладки. Применяется для того, чтобы отладчик не отслеживал самого себя.

$^T
$BASETIME

Время в секундах с начала 1970 года старта текущей программы.

$^W
$WARNING

Значение флага '-w'. true -если включено и false - выключено.

$^X
$EXECUTABLE_NAME

Команда запуска Perl. Аналогично argv[0] в С.

$ARGV

Имя текущего файла, читаемого оператором '<>'.

@ARGV

Массив параметров строки запуска программы. Внимание! @#ARGV - меньше количества параметров на 1, т.к. $ARGV[0] это первый параметр (не имя программы).

@INC

Список директорий диска, которые просматривает Perl для выполнения команд do, require или use.

%INC

Этот хеш содержит имена директорий для имен использованных файлов командами do или require. Ключ - имя файла, а значение - директория.

$ENV{выражение}

Хеш %ENV содержит значения переменных окружения. Изменение этих значений вызывает изменение окружения для процессов потомков.

$SIG{выражение}

Хеш %SIG содержит имена подпрограмм для таких системных сигналов как INT, QUIT, PIPE, ... Значение 'DEFAULT' - для системной обработки. 'IGNORE' - игнорировать данный сигнал.


Visual Basic + HTML = VBScript

Создание скриптов
Процедуры
    Виды процедур
    Использование подпрограмм и функций
Объект
    Объектная модель
    Использование объектов
Установка свойств для элементов управления и объектов
Обработка событий
Вызов методов
Объектная модель Internet Explorer 3.0
    Объект Document
    Объект Frame
    Объект History
    Объект Location

Данная статья предназначена тем, кто хотел бы получить представление о программировании на Visual Basic Script, подмножестве языка Visual Basic. VBScript позволяет решать задачи, связанные с Internet, а именно создавать сценарии (или скрипты) управления объектами (кнопками, списками, ниспадающими меню и т. д.) на Web-страничках. Для понимания этого материала нужно иметь представление о структуре HTML-документа, а также основах программирования на Visual Basic. Описанные в статье сценарии могут быть использованы в браузере Microsoft Internet Explorer 3.0. Другие браузеры попросту не понимают этого языка. В настоящее время существуют всего два языка создания сценариев по управлению объектами - Microsoft VBScript и Sun JavaScript. Оба поддерживаются IE 3.0. Браузер же компании Netscape воспринимает только JavaScript.

С помощью VBScript вы можете быстро создавать собственные страницы или даже писать игры. И все это размещается внутри вашего HTML-документа. Если вы уже имели дело с Visual Basic или Visual Basic for Applications, то легко справитесь и с VBScript.

Создание скриптов

Для того чтобы браузер мог различать команды VBScript, нужно все операторы VBScript на HTML-страницах обрамлять тегами <SCRIPT> и </SCRIPT>. Первый из них используется в паре с атрибутом LANGUAGE для определения языка создания сценария. В нашем случае - Visual Basic Script (хотя может быть и JavaScript). Значением для этого языка является "VBScript":

<HTML>
<HEAD><TITLE>Пример странички
с фрагментом на VBScript</TITLE>
<SCRIPT LANGUAGE="VBScript">
<!-
Sub Button1_OnClick
        MsgBox "VBScript - Rulez Forever!"
End Sub
-></SCRIPT>
</HEAD>
<BODY>
<H3>Обычная первая страничка</H3><HR>
<FORM><INPUT NAME="Button1" TYPE="BUTTON"
 VALUE="Click Here"></FORM>
</BODY>
</HTML>

Тег <SCRIPT> имеет завершающую часть - </SCRIPT>. Всегда употребляйте их парой! Сценарий в нашем примере помещается в специальные скобки <!-...->, которые в языке HTML обозначают комментарий. Это делается для того, чтобы старые браузеры, которые не умеют работать со скриптовыми языками, случайно не отобразили сценарий на экране. Для них он - просто комментарий.

При разработке приложения на VBScript необходимо знать концепцию этого языка, а также иметь представление о том, как работают объекты, события и процедуры. Это поможет вам в дальнейшем разобраться в функционировании объектной модели Internet Explorer 3.0 и объектов ActiveX. Помните, что объекты и VBScript связаны друг с другом.

Процедуры

Процедуры - это относительно самостоятельные фрагменты программы, оформленные особым образом и обозначенные именами. Упоминание этих имен в тексте программы называется вызовами процедур. Процедуры помогают разбить программу на ряд независимых частей, что необходимо для экономии памяти. Каждая процедура существует в программе в единственном экземпляре, но обращаться к ней можно из разных точек программы. При вызове процедуры вступает в действие последовательность операторов, которые находятся внутри этой процедуры. Процедуре могут быть переданы некоторые параметры.

Виды процедур

В VBScript существует два вида процедур: подпрограмма (Sub) и функция (Function). Подпрограмма (Sub) - это последовательность VBScript-операторов, обрамляемая операторами Sub и End Sub. Подпрограммы могут принимать параметры (константы, переменные или выражения, которые передаются при вызове процедуры), но не возвращают значений. Если подпрограмма не содержит параметров, то ее оператор должен включать пустой набор круглых скобок. Следующая подпрограмма использует две функции языка VBScript - InputBox и MsgBox. Она запрашивает у пользователя ввод температуры в градусах Фаренгейта, переводит ее в градусы Цельсия и отображает результат. Вычисление температуры происходит в функции Celsius.

<SCRIPT LANGUAGE="VBScript">
<!-
Sub ConvertTemp()
temp = InputBox("Please enter the temperature
          in degrees F.", 1)
MsgBox "The temperature is " &
Celsius(temp) & " degrees C."
End Sub
-></SCRIPT>

Функция (Function) - это последовательность операторов VBScript, обрамляемая операторами Function и End Function. Она похожа на подпрограмму, но отличается от последней тем, что может возвращать некоторое значение. Функция может принимать параметры (константы, переменные или выражения, передаваемые при вызове). Если она не содержит параметров, то оператор Function должен включать пустой набор круглых скобок. Возвращаемый тип у Function - Variant.

В следующем примере функция Celsius переводит градусы Фаренгейта в градусы Цельсия. Когда вызывается функция из подпрограммы ConvertTemp, переменная, содержащая параметр-значение, передается в функцию. Результат вычисления возвращается в вызывающую процедуру и отображается в MsgBox.

<SCRIPT LANGUAGE="VBScript">
<!-
 Sub ConvertTemp()
     temp = InputBox("Please enter the temperature in
            degrees F.", 1)
     MsgBox "The temperature is " & Celsius(temp) & "
            degrees C."
 End Sub

 Function Celsius(fDegrees)
     Celsius = (fDegrees - 32) * 5 / 9
 End Function
->
</SCRIPT>

Перед использованием процедур в своей программе вы должны их объявить. Их нужно всегда размещать в самом начале раздела <HEAD> вашей HTML-странички. Весь последующий текст должен также находиться в разделе <HEAD>. Операторы Function и Sub, подобно другим элементам языка VBScript, располагаются между парой тегов <SCRIPT LANGUAGE="VBScript"> </SCRIPT> для правильного их распознавания браузером. Кроме того, теги комментария (<! - и - >) окружают текст программы внутри тега <SCRIPT>, гарантируя, что сценарий не будет отображаться в браузерах, которые не поддерживают VBScript.

Любые данные передаются в ваши процедуры в виде параметров. При создании подпрограммы (или функции) после ее имени должны быть круглые скобки. Любые параметры внутри них отделяются запятыми. В следующем примере параметр fDegrees передает значения в функцию Celsius для преобразования:

<SCRIPT LANGUAGE="VBScript">
<!-
 Function Celsius(fDegrees)
    Celsius = (fDegrees - 32) * 5 / 9
 End Function
->
</SCRIPT>

Используйте функции вместо подпрограмм, если требуется возвратить некоторое значение.

Использование подпрограмм и функций

Для применения функций в вашей программе необходимо поставить ее название справа от переменной или выражения, в которое будет возвращаться результат выполнения функции, например:

<SCRIPT LANGUAGE="VBScript">
<!-
 Temp = Celsius(fDegrees)
->
</SCRIPT>

или

<SCRIPT LANGUAGE="VBScript">
<!-
 MsgBox "The Celsius temperature is " &
 Celsius(fDegrees) & " degrees."
->
</SCRIPT>

Чтобы вызвать одну процедуру из другой, необходимо написать имя первой со значениями требуемых параметров, каждый из которых должен отделяться от предыдущего запятой. Оператор Call не требуется. Но если вы все-таки используете его, то нужно заключить любые параметры в круглые скобки. Следующий пример показывает две возможности вызова процедуры MyProc. Один использует оператор Call, другой - нет. Оба выполняют одно и то же.

<SCRIPT LANGUAGE="VBScript">
<!-
 Call MyProc(firstarg, secondarg)
 MyProc firstarg, secondarg
->
</SCRIPT>

Обратите внимание, что круглые скобки опускаются при вызове подпрограммы, если оператор Call не применяется.

Объект

Объект (Object) - это простой объект, методами, свойствами и событиями которого вы можете управлять. Метод - это процедура или просто набор команд, сообщающих объекту, что нужно выполнить некоторую задачу. Свойство - это некоторый вид параметра объекта. Событие - это сигнал, подаваемый, если с объектом что-то происходит. И методы, и свойства, и события разработчик выбирает сам, исходя из потребностей задачи. Так, если в вашей программе нужно отследить время окончания работы, то имеет смысл обращать внимание на событие, сигнализирующее об этом. В противном случае эта информация бесполезна.

Объектная модель

Рассмотрим объект ActiveX Label (объект и элемент управления для VBScript фактически означают одно и то же). Он состоит из свойств и имеет один метод. Некоторые из его свойств включают заголовок (Caption), цвет фона (BackColor), цвет шрифта (ForeColor), размер (FontSize) и стиль шрифта (FontName). Его методом является About Box, отображающий при вызове окно с информацией об ActiveX. Событием для Label является перемещение указателя мыши над Label. Существует специальный тег <OBJECT> для вставки объектов в HTML-страницу. В его синтаксисе используются два тега: <OBJECT> и <PARAM>. Первый - для определения используемого объекта, а также других главных параметров, связанных с объектом; второй - для установки разных свойств объекта, определенного тегом <OBJECT>, во вложенных тегах <PARAM>. Атрибуты тега <OBJECT> перечислены в табл. 1, а тэга <PARAM> - в табл. 2.

Таблица 1.
Атрибуты тэега <OBJECT>.

Атрибут | Назначение
ID | Определяет имя объекта в документе
CLASSID | Класс, к которому принадлежит объект; может быть уникальным универсальным идентификатором или другой группой классов типа Java. Для элементов управления ActiveX - это идентификационный номер класса в системном реестре вашей системы
DATA | Адрес, откуда объект должен получать данные; может включать данные для определения всех свойств классов
CODEBASE | Адрес, где находится код объекта
TYPE | Тип Internet MIME (например: application/x-oleobject будет определять OLE-объект, который должен использоваться как код)
HEIGHT | Высота границы объекта
WIDTH | Ширина границы объекта

Таблица 2.
Атрибуты тега <PARAM>.

Атрибут | Назначение
NAME | Определяет имя объекта, которое должно использоваться
VALUE | Определяет значение, которое должно быть присвоено свойству объекта, определенному в NAME

Теперь посмотрим на исходный текст, приведенный в примере для элемента управления - Label.

<HTML>
<HEAD>
<TITLE>Пример, демонстрирующий объект ActiveX</TITLE>
</HEAD>
<BODY BGCOLOR="steelblue">
<OBJECT ID="mylabel" WIDTH="100" HEIGHT="51"
CLASSID="CLSID:99B42120-6EC7-11CF-A6C7-00AA00A47DD2">
<PARAM NAME="_ExtentX" VALUE="2646">
<PARAM NAME="_ExtentY" VALUE="1349">
<PARAM NAME="Caption" VALUE="This is label 1">
<PARAM NAME="Angle" VALUE="0">
<PARAM NAME="Alignment" VALUE="4">
<PARAM NAME="Mode" VALUE="1">
<PARAM NAME="FillStyle" VALUE="0">
<PARAM NAME="FillStyle" VALUE="0">
<PARAM NAME="ForeColor" VALUE="#000000">
<PARAM NAME="BackColor" VALUE="#C0C0C0">
<PARAM NAME="FontName" VALUE="Arial">
<PARAM NAME="FontSize" VALUE="12">
<PARAM NAME="FontItalic" VALUE="0">
<PARAM NAME="FontBold" VALUE="0">
<PARAM NAME="FontUnderline" VALUE="0">
<PARAM NAME="FontStrikeout" VALUE="0">
<PARAM NAME="TopPoints" VALUE="0">
<PARAM NAME="BotPoints" VALUE="0">
</OBJECT>
</BODY>
</HTML>

Для тега <OBJECT> определено несколько параметров, которые предоставляют полезную информацию относительно используемого объекта. Сначала определяется имя объекта "mylabel", затем свойства: ширина и высота. Далее элемент управления идентифицируется параметром "CLSID:99B42120-6EC7-11CF-A6C7-00AA00A47DD2" точно так, как он обозначается в системном реестре вашей операционной системы. Вложенные тэги <PARAM> содержат свойства объекта Label. Атрибут NAME используется, чтобы определить, какое свойство должно использоваться, а VALUE - для связи значения с этим свойством. Весь этот процесс автоматизирует программа ActiveX Control Pad. В ней есть окно, позволяющее корректировать свойства любого элемента управления (см. статью "Размещение ActiveX на страницах Web", "Мир ПК", # 2/97, с. 51).

Использование объектов

Теперь вы знаете, что для включения объектов в HTML-документ нужно использовать пару тегов <OBJECT></OBJECT>, а для установки значений его свойств - тег <PARAM>. Если вы имели дело с программированием на Visual Basic, то использование тегов <PARAM> будет напоминать вам размещение какого-либо компонента или элемента управления на форме. Например, следующие значения тегов <OBJECT> и <PARAM> добавляют на страничку (вместо формы в Visual Basic) элемент ActiveX Label:

<OBJECT
 classid="clsid:99B42120-6EC7-11CF-A6C7-00AA00A47DD2"
 id=lblActiveLbl
 width=250
 height=250
 align=left
 hspace=20
  vspace=0>
<PARAM NAME="Angle" VALUE="90">
<PARAM NAME="Alignment" VALUE="2">
<PARAM NAME="BackStyle" VALUE="0">
<PARAM NAME="Caption"
VALUE="A Simple Desultory Label">
<PARAM NAME="FontName" VALUE="Arial">
<PARAM NAME="FontSize" VALUE="20">
<PARAM NAME="FontBold" VALUE="1">
<PARAM NAME="FrColor" VALUE="0">
</OBJECT>

Вы можете присваивать или устанавливать свойства, а также вызывать методы точно так же, как с любым средством управления формой. Следующий текст, например, включает элементы <FORM>, которые могут использоваться для управления свойствами двух элементов Label:

<FORM NAME="LabelControls">
<INPUT TYPE="TEXT" NAME="txtNewText" SIZE=25>
<INPUT TYPE="BUTTON" NAME="cmdChangeIt"
VALUE="Change Text">
<INPUT TYPE="BUTTON" NAME="cmdRotate"
VALUE="Rotate Label">
</FORM>

Обработчик события нажатия кнопки cmdChangeIt изменяет текст объекта Label:

<SCRIPT LANGUAGE="VBScript">
<!-
Sub cmdChangeIt_onClick
   Dim TheForm
   Set TheForm = Document.LabelControls
   lblActiveLbl.Caption = TheForm.txtNewText.Value
End Sub
->
</SCRIPT>

Некоторые теги (прежде всего для управления вводом информации в форму) и элементы ActiveX могут обращаться к отдельным процедурам вашего сценария (скрипта), запускаемого событием. События могут вызываться из тегов <A>, <BODY>, <INPUT> и <OBJECT>. В примере, который приводится ниже, при нажатии кнопки изменяется текст объекта Label:

<HTML>
<HEAD>
<TITLE>Использование объектов с событиями</TITLE>
</HEAD>
<BODY BGCOLOR="slateblue"><BR>
<CENTER>
<OBJECT ID="MyLabel" WIDTH=215 HEIGHT=93
CLASSID="CLSID:99B42120-6EC7-11CF-A6C7-00AA00A47DD2">
<PARAM NAME="_ExtentX" VALUE="5689">
<PARAM NAME="_ExtentY" VALUE="2461">
<PARAM NAME="Caption" VALUE="Watch me change!!">
<PARAM NAME="Angle" VALUE="0">
<PARAM NAME="Alignment" VALUE="4">
<PARAM NAME="BackStyle" VALUE="1">
<PARAM NAME="Mode" VALUE="1">
<PARAM NAME="FillStyle" VALUE="0">
<PARAM NAME="FillStyle" VALUE="1">
<PARAM NAME="ForeColor" VALUE="#00FF00">
<PARAM NAME="BackColor" VALUE="#DDAADD">
<PARAM NAME="FontName" VALUE="Times">
<PARAM NAME="FontSize" VALUE="12">
<PARAM NAME="FontItalic" VALUE="0">
<PARAM NAME="FontBold" VALUE="0">
<PARAM NAME="FontUnderline" VALUE="0">
<PARAM NAME="FontStrikeout" VALUE="0">
<PARAM NAME="TopPoints" VALUE="0">
<PARAM NAME="BotPoints" VALUE="0">
</OBJECT></CENTER><BR><BR><CENTER>
<INPUT TYPE="button" value="click ME to change"
onclick="changes" Language="VBScript"></CENTER>
<SCRIPT>
sub changes
MyLabel.Caption="See. . . I TOLD YOU!"
MyLabel.ForeColor = "6316128"
MyLabel.BackColor = "167111680"
MyLabel.FontName = "Arial"
end sub
sub MyLabel_Click
MyLabel.Caption="Watch me change!!"
MyLabel.ForeColor = "65280"
MyLabel.BackColor = "14527197"
MyLabel.FontName = "Times"
end sub
</SCRIPT>
</BODY>
</HTML>

В этом примере, помимо объекта Label, добавлены некоторые возможности сценария и несколько событий. Во-первых, определяется элемент управления Label, затем с помощью тега <INPUT> добавляется элемент управления Button. Когда пользователь нажимает на кнопку, то текст Label изменяется. При повторном нажатии кнопки возвращается прежний текст. Оба эти действия являются событиями. Когда пользователь нажимает на кнопку, возникает событие, заставляющее метку изменяться. Атрибут OnClick в теге <INPUT> используется для того, чтобы вызвать событие. Сам по себе атрибут OnClick является событием, и его значение вызывает процедуру VBScript, которая заставляет метку изменяться. Прежний текст возвращается с помощью сценария, имеющего две процедуры. Первая используется элементом Button (кнопка), а вторая - Label (метка), если пришло время вернуть ее старое содержание. Как вы заметили, во второй процедуре имя объекта Label (MyLabel) сопровождается подчеркиванием, после которого стоит имя события (MyLabel_Click). Это означает, что когда происходит событие OnClick для названного объекта, то должны выполняться последовательности операций, перечисленные в этой процедуре.

При интенсивном программировании лучше всего использовать программу Microsoft ActiveX Control Pad, великолепно выполняющую связывание команд Visual Basic с разными событиями и элементами управления на вашей страничке. Таким образом, вы можете больше внимания обращать на сам процесс разработки приложения.

Установка свойств для элементов управления и объектов

Установка свойств для разных объектов на странице является одной из простейших задач при программировании на VBScript. Стандартная схема этого действия выглядит так:

<SCRIPT>
...
ObjectName.property = value
...
</SCRIPT>

Здесь ObjectName - имя вашего объекта, определенное с помощью параметров ID или NAME; property - свойство объекта, называемого ObjectName; value - присваиваемое свойству значение. Пример ниже иллюстрирует, как это делается:

...
MyLabel.Caption="Watch me change!"
MyLabel.ForeColor = "65280"
MyLabel.BackColor = "14527197"
MyLabel.FontName = "Times"
...

Свойству Caption (заголовок) присваивается строка "Watch me change!". Затем устанавливаются свойства ForeColor (цвет текста), BackColor (цвет фона) и FontName (стиль шрифта). Так можно устанавливать свойства для любого объекта на странице.

Обработка событий

VBScript - событийно-ориентированный язык. Некоторые события, перечисленные в табл. 3, работают со многими элементами управления. Существует несколько методов определения того, что должно происходить в сценарии при возникновении события. Можно определить событие как параметр тега для объекта на странице. Значением этого атрибута должно быть имя процедуры, которую нужно заранее определить внутри тега. Если надо использовать определенную процедуру, то параметр LANGUAGE тега <SCRIPT> (см. табл. 4) должен быть включен в этот тэг как дополнительный атрибут.

Таблица 3.
Некоторые события разных объектов в VBScript.

Событие | Назначение
OnClick | Пользователь щелкнул мышкой на элементе управления
MouseOver | Пользователь помещает курсор мыши над объектом
DblClick | Пользователь дважды щелкнул мышкой на соответствующем объекте
OnLoad | Происходит при загрузке документа или другого элемента управления

Таблица 4.
Дополнительные атрибуты тега <SCRIPT>.

Атрибут | Назначение
EVENT | Декларирует событие (для объекта или элемента управления, определенного с помощью FOR), используемое для вызова кода, вставленного между парой тэгов <SCRIPT>
FOR | Определяет, для какого объекта или элемента управления на странице должен использоваться скрипт, если происходит событие, указанное в атрибуте EVENT
LANGUAGE | Назначает используемый скриптовый язык. Значением может быть либо "VBScript" для VBScript, либо "JavaScript" для JavaScript
EventName | Псевдоним события. Любое событие для определенного элемента управления может использоваться как атрибут одного из четырех тегов: <BODY>, <INPUT>, <OBJECT> и <A>. Значением для этого события всегда является процедура (и в VBScript, и в JavaScript). Некоторые названия событий, которые могут использоваться как атрибуты, перечислены в табл. 3. Этот метод не может работать с некоторыми элементами ActiveX

Типовая схема описания обработчика события следующая: <...EventName="method" LANGUAGE="VBScript"...>, где EventName - любое правильное событие для соответствующего объекта, а method - это имя процедуры, которая вызывается при возникновении какого-то определенного события. Используя атрибуты, описанные в табл. 4, вы можете определить в теге <SCRIPT>, какое произошло событие и для какого объекта должен использоваться следующий код, что необходимо сделать, если в программе несколько процедур. Это выглядит примерно так:

<SCRIPT LANGUAGE="VBSCRIPT" FOR="ObjectName"
 EVENT="EventName">
...
</SCRIPT>

FOR определяет имя элемента управления или объекта, для которого используется последующий код; EVENT задает событие, при возникновении которого следующий код будет вызван.

Определение обработчика события через процедуру - самый простой способ выполнения некоторого кода, когда обрабатываемое событие имело место. Этот метод использует процедуру с таким же именем, как у элемента управления, который вы предполагаете использовать, плюс подчеркивание между именами объекта и события:

<SCRIPT LANGUAGE="VBScript">
...
sub ObjectName_ObjectEvent
...
end sub
...
</SCRIPT>

Следующий фрагмент исходного текста иллюстрирует оба рассмотренных метода. Обработчики события определяются как атрибуты, а также используются как процедуры внутри кода:

...
<INPUT TYPE="button" value="click ME to change"
onclick="changes" Language="VBScript"></CENTER>
<SCRIPT>
sub changes
MyLabel.Caption="See. . . I TOLD YOU!"
MyLabel.ForeColor = "6316128"
MyLabel.BackColor = "167111680"
MyLabel.FontName = "Arial"
end sub
...
...
sub MyLabel_Click
MyLabel.Caption="Watch me change!"
MyLabel.ForeColor = "65280"
MyLabel.BackColor = "14527197"
MyLabel.FontName = "Times"
end sub
...

Вызов методов

Чтобы вызвать подпрограмму из вашего сценария, используйте следующий синтаксис:

call ProcedureName(arguments)

ProcedureName - это имя подпрограммы, которую вы вызываете; arguments - значения, передающиеся в процедуру. Как было сказано ранее, подпрограммы используются, когда требуется обработать какое-то событие, например:

<SCRIPT>
...
sub MyObject_Click()
alert "This is a subroutine which is called _
when the Click event happens for the MyObject object."
end sub
...
</SCRIPT>

В нашем случае это Click, т. е. пользователь должен щелкнуть мышкой на этом объекте. Это не будет работать для функции, потому что функция - это процедура, возвращающая значение. Что касается функций, то они сами могут быть возвращены как значение, например:

<SCRIPT>
...
y = 2
x = GiveValue(y)
alert x
...
function GiveValue(a)
GiveValue = a + 4
end function
...
</SCRIPT>

Следующий пример иллюстрирует вызовы подпрограммы и функции из сценария:

<HTML>
<HEAD>
<TITLE>Документ HTML, использующий элементы
управления ActiveX и процедуры </TITLE>
</HEAD>
<BODY BGCOLOR="mistyrose">
<OBJECT ID="IeLabel1" WIDTH=170 HEIGHT=93
CLASSID="CLSID:99B42120-6EC7-11CF-A6C7-00AA00A47DD2">
<PARAM NAME="_ExtentX" VALUE="3625">
<PARAM NAME="_ExtentY" VALUE="2461">
<PARAM NAME="Caption" VALUE="Click Me">
<PARAM NAME="Angle" VALUE="0">
<PARAM NAME="Alignment" VALUE="4">
<PARAM NAME="Mode" VALUE="1">
<PARAM NAME="FillStyle" VALUE="0">
<PARAM NAME="FillStyle" VALUE="1">
<PARAM NAME="ForeColor" VALUE="#FF0000">
<PARAM NAME="BackColor" VALUE="#373737">
<PARAM NAME="BackStyle" VALUE="1">
<PARAM NAME="FontName" VALUE="Courier">
<PARAM NAME="FontSize" VALUE="24">
<PARAM NAME="FontItalic" VALUE="0">
<PARAM NAME="FontBold" VALUE="0">
<PARAM NAME="FontUnderline" VALUE="0">
<PARAM NAME="FontStrikeout" VALUE="0">
<PARAM NAME="TopPoints" VALUE="0">
<PARAM NAME="BotPoints" VALUE="0">
</OBJECT><BR CLEAR="ALL">
<INPUT TYPE="button" NAME="button1" VALUE="Get Value">
Enter Angle:
<INPUT TYPE="text" NAME="AngleInpt" VALUE="0"
 SIZE="10"
MAXLENGTH="10">
The Sine is:
<INPUT TYPE="text" NAME="SineInpt" VALUE="0"
 SIZE="10" MAXLENGTH="10">
<SCRIPT LANGUAGE="VBScript">
dim pi
pi = 4 * atn(1.0)
sub button1_OnClick
dim angle, sinangle
angle = AngleInpt.value
sinangle = getsin(angle)
SineInpt.value = sinangle
end sub
function getsin(angle)
getsin = sin((pi * angle)/180)
end function
sub ieLabel1_Click
call ieLabel1.AboutBox
end sub
</SCRIPT>
</BODY>
</HTML>

Когда пользователь щелкает мышью по метке Label, то вызывается метод About Box. Поля ввода используются для вычисления синуса заданного угла (в градусах). Если ввести некоторое значение в первое поле ввода, помеченное текстом "Enter Angle", и щелкнуть на кнопке мышью, то введенный аргумент будет переведен в радианы и затем вычислен синус, результат чего будет отображен. Первая процедура вызывается по нажатию мышью на единственный объект на странице - кнопку, называемую button1. Когда это событие обрабатывается, то вызывается сразу несколько методов. Сначала обновляются все переменные, которые вы собираетесь использовать для вашей программы. Затем отыскивается значение, называемое AngleInpt, которое пользователь набрал в поле ввода. Далее вызывается функция, в которую заносится введенный параметр из поля AngleInpt. Она преобразует угловые градусы в радианы, затем вычисляет его синус (радианы являются основной мерой измерения углов для тригонометрических функций в VBScript). Значение связывается с именем функции (getsin), которая возвращает его для определенной нами ранее переменной. В итоге это значение отображается в текстовом поле, называемом SineInpt.

В данном примере задействовано два метода: подпрограмма button1_OnClick, которая, если вы помните, вызывается, когда пользователь щелкает мышью по кнопке, и функция getsin(angle), где angle - параметр, используемый для преобразования градусов в радианы. У элемента ActiveX Label используются два включаемых метода (методами называются собственные подпрограммы и функции объекта): тот, который вызывается, когда по метке щелкнули мышкой (ieLabel1_Click), и метод отображения диалоговой панели ieLabel.AboutBox(). Когда обращаются к методам объектов, обычно используют следующий синтаксис:

call ObjectName.MethodName()

Ключевое слово call применяется для вызова определенного метода. ObjectName - это имя объекта, для которого вы хотели бы вызвать метод, а MethodName() - это имя метода, который будет использовать сам объект. Имя объекта и вызываемый метод отделяются точкой. Также для вызова метода не требуется никаких скобок, потому что у него нет никаких параметров.

Объектная модель Internet Explorer 3.0

Все объекты являются дочерними от объекта Window. Объект Window содержит несколько методов, свойств и событий. Некоторые из них перечислены в табл. 1 - 3. Объект Window является исходным объектом, так что вам не требуется добавлять его имя к свойствам или дочерним объектам. Например, вместо window.name вы могли бы использовать только name.

Таблица 1.
Свойства объекта Window.

Свойство | Назначение
Frames | Используется, когда в текущем документе существует набор кадров (фреймов). Это свойство является массивом фреймов на странице
Location | Определяет местоположение текущего окна
Name | Имя того окна, чьи свойства определяются
Parent | Указывает родительский фрейм или окно, чьи свойства определяются на данный момент

Таблица 2.
Несколько основных методов объекта Window.

Метод | Назначение
Open | Открывает или закрывает документ внутри текущего окна или другого определенного окна. У него есть два аргумента: имя файла, который вы хотите открыть, и имя окна, на котором вы хотите его разместить. Например, window.open(filename,windowname), где filename - имя файла, а windowname - имя окна. Для этого метода требуются оба параметра
Prompt | Определяет всплывающую подсказку. Имеет два параметра: текст подсказки и любой заданный по умолчанию текст, который входит в подсказку. Например: window.prompt(ExpString, DftString), где ExpString - текст подсказки, а DftString - исходный текст в подсказке. Оба параметра необязательны. Если этот метод связан с переменной (как x=prompt("stuff","more stuff")), то эта переменная получит любые данные, которые были введены в подсказку, если не была нажата "отмена"
Close | Используется для закрытия окна
Navigate | Переключает окно на другой адрес URL. Например: window.navigate(URL), где URL - имя того URL, на который нужно перейти

Таблица 3.
События загрузки объекта Window.

Событие | Назначение
OnLoad | Вызывается, когда загружается страница, содержащая данное событие. Используется в тэге <BODY> как атрибут при вызове процедуры
OnUnLoad | Вызывается, когда выгружается страница, содержащая данное событие. Используется в тэге <BODY> как атрибут при вызове процедуры

Объект Document

Объект Document имеет дело прежде всего с телом HTML-страницы. Он имеет три дочерних объекта: Link, Anchor и Form, каждый из которых является индексированным массивом объектов link, anchor и form. Кроме того, объект Form содержит подобъект Element, который является индексированным массивом всех объектов и элементов управления на странице. Некоторые важные свойства и методы перечислены в табл. 4. Для Document, заметим, не существует никаких событий.

Таблица 4.
Некоторые методы объекта Document.

Метод | Назначение
BgColor | Устанавливает цвет фона текущего документа. Этот цвет может иметь шестнадцатеричное представление #rrggbb или соответствующее название
FgColor | Устанавливает цвет текста документа. Аналогичен по функциям свойству BgColor
Referrer | Указывает URL документа, на который ссылается пользователь в настоящее время. Например, если кто-то обратился по адресу: http://www.nm.org/welcome.htm с сервера http://www.someplace.com, то свойством Referrer будет: http://www.someplace.com, если это свойство было в странице вышеупомянутого расположения; в противном случае оно обращается в Null
LastModified | Показывает дату последней модификации документа
Open | Открывает документ для записи дополнительных строк в формате HTML. Синтаксис: document.open()
Write | Записывает HTML-текст в текущий документ и должен вызываться, когда документ открывается для записи. Синтаксис: document.write(somestring), где somestring может быть одной строкой, переменной или же несколькими связанными строками в формате HTML, которые выводятся на экран
Close | Закрывает документ после того, как имели место следующие вызовы: document.write, document.close.

Объект Frame

Объект Frame - это индексированный массив из фреймов (кадров) на странице. Первый кадр из списка соответствует кадру в верхнем левом углу браузера. Вы можете использовать данный объект для установки или получения различных URL-адресов разных фреймов, которые находятся на экране браузера. Фреймовый массив очень похож на объект Window, поскольку он использует другие объекты похожим способом. Например, вы можете использовать расположение объекта для получения или установки расположения соответствующего фрейма.

Объект History

Основной целью объекта History является доступ к списку введенных адресов в браузере. Существуют три метода, используемых для навигации по папке History. Наиболее важные методы перечислены в табл. 5. Объект History не генерирует никаких событий.

Таблица 5.
Некоторые методы объекта History.

Метод | Назначение
Go | Используется для того, чтобы определить, сколько раз браузер должен пролистать вперед историю введенных адресов. Формат вызова: history.go(n), где n - номер файла истории, на который нужно перейти
Forward | Используется для определения того, сколько раз браузер должен пролистать вперед историю введенных адресов. Формат вызова: history.forward(n), где n - число раз, которые нужно "идти вперед"
Back | Используется, чтобы определить, сколько раз браузер должен "идти назад" по текущей истории вашего браузера. Формат вызова: history.back(n), где n - число раз, которое браузер должен "идти назад"

Объект Location

Этот объект необходим для получения информации относительно текущего расположения Internet Explorer. Наиболее важные свойства перечислены в табл. 6. Этот объект не имеет никаких событий или методов.

Таблица 6.
Наиболее важные свойства объекта Location.

Свойство | Назначение
Href | Используется для получения URL текущего окна или для создания нового URL
Protocol | Устанавливает тип протокола (FTP или HTTP) текущего документа
Host | Используется для установки главной части URL текущего документа вместе с номером используемого порта
Hostname | Используется для установки имени хоста в URL

Теперь вы имеете представление о объектах, их функционировании, а также их событиях, методах и свойствах. Вы познакомились с тем, как использовать VBScript для вызова процедур, которые основаны на различных событиях, происходящих с объектом. Надеемся, что это поможет вам в дальнейшей работе.


#VRML V1.0 ascii
Rotation {rotation 1 1 0 0.5}
Cylinder {
   parts   ALL      #видны все части конуса
   radius  1        #радиус основания
   height  2        #высота цилиндра
         }


#VRML V1.0 ascii
Rotation {rotation 1 1 0 0.5}
Cone {
    parts           ALL      #видны и основание, и боковая поверхность конуса
    bottomRadius    1        #радиус основания
    height          2        #высота
     }


#VRML V1.0 ascii
Rotation {rotation 1 1 0 0.5}
Cube {
    width   2    # ширина
    height  3    # высота
    depth   1    # глубина
     }


#VRML V1.0 ascii
Sphere {
    radius  1    # радиус
       }


#VRML V1.0 ascii
   Material {
     diffuseColor    0 0.3 0.3
     transparency    0.5
             }
   Cylinder {
     height  1
     radius  1
             }

   Material {
       emissiveColor    1 0 0
       transparency    0
             }
   Cylinder {
       height  0.8
       radius  0.1
            }


#VRML V1.0 ascii
  Cube {
     width 1
     height 1
    depth 1
   }
# Этот куб по умолчанию располагается в центре
Translation {
         translation 2 0 0
            }
#Второй куб сдвинут вправо на 2
 Cube {
     width 1
     height 1
    depth 1
   }
Translation {
         translation 2 0 0
            }
#Третий куб сдвинут вправо на два относительно 2-го !!!!
 Cube {
     width 1
     height 1
    depth 1
   }


#VRML V1.0 ascii
Separator {
 Rotation { rotation 1 1 0 1}

# all length = 1.5
DEF blue
         Material {            emissiveColor    0.2 0.2  0.8        }
DEF red
         Material {            emissiveColor    0.8 0.2  0.2        }
DEF yellow
         Material {            emissiveColor    1 1  0        }
DEF Dark
         Material { ambientColor    0 0 1  }

DEF Steklo
Separator { #steklo
Separator{ # конус,
        Material {            emissiveColor    0.2 0.8  0.2
                                      transparency    0.7}
          Scale { scaleFactor 1 1 0.03}
         Cone  {
             height  0.2
             bottomRadius 0.1
                     }
                     } # end of konus

Separator { # cube
    Translation {translation 0.05 0 0 }
          USE blue
          Cube  {
          height  0.2
          width  0.1
          depth 0.01
                    }
                  } # end of cube
}# end of steklo
Separator {
                 Translation {  translation 0 0 0.3  }
                  USE Steklo
                  }
#######-krisha
       Separator {
         USE blue
           Translation {  translation 0.05 0.1 0.15  }
               Cube  {
                       height  0.01
                      width  0.1
                     depth 0.31
                         }
                  }
#######-ZAD KABini
      Separator {
     USE blue
      Translation {  translation 0.1 -0.045 0.15  }
               Cube  {
                       height  0.3
                      width  0.01
                     depth 0.31
                         }
                  }
#######-steklo pereda
      Separator {
              Material {            emissiveColor    0.2 0.8  0.2
                                      transparency    0.7}
           Rotation {rotation  0 0 1 2.7 }
               Translation {  translation 0.04 0.02 0.15  }
               Cube  {
                       height  0.215
                      width  0.01
                     depth 0.3
                         }
                  }
#######-низ кабины
        Separator {
           USE blue
            Translation {  translation -0.02 -0.12 0.15  }
               Cube  {
                      height  0.15
                      width  0.25
                     depth 0.31
                         }
                  }
###########
       DEF DVER

       Separator {
          USE blue
          Translation {  translation 0.05 -0.07  0.305  }
          Rotation  {rotation 0 0 1 1.57}
               Cylinder  {
                     height  0.03
                     radius 0.005
                         }
}
          DEF TGD
            Separator {
            USE blue
              Translation {  translation 0 -0.07  0.31  }
              Cube {width  0.03 height 0.03 depth 0.005}
               }

        DEF BBB
            Separator {
            USE blue
              Translation {  translation -0.13 -0.15  0.31  }
              Cube {width  0.06 height 0.08 depth 0.005}
               }

########### -влепим фару
          DEF FARA
          Separator {
              Translation {  translation -0.15 -0.075 0.02  }
              Scale {scaleFactor 0.2 1 1}
            USE yellow

              Sphere {radius 0.025}
             }
########### -влепим фару номер 2
          Separator {
               Translation {  translation 0 0 0.26  }
                USE FARA
                            }
########### - решетка радиатора
          DEF  RESE
          Separator {
                USE Dark
                Translation {  translation -0.17 -0.12 0.15  }
                  Rotation {rotation 1 0 0 1.57}
                Cylinder { height 0.25 radius 0.005 }
                  }
         Separator {
         Translation {  translation 0 -0.02 0  }
         USE RESE
         Translation {  translation 0 -0.02 0  }
         USE RESE
                     } # конец горизонатльных решеток
        DEF  VERT
          Separator {
                USE Dark
                Translation {  translation -0.175 -0.14 0.03  }
                 Cylinder { height 0.05 radius 0.005 }
                  }
          Separator {
                Translation {  translation 0 0  0.05  }
                USE VERT
          Translation {  translation 0 0  0.05  }
                USE VERT
          Translation {  translation 0 0  0.05  }
                USE VERT
          Translation {  translation 0 0  0.05  }
                USE VERT
         Translation {  translation 0 0  0.05  }
                USE VERT

                  }

#######-бампер
        Separator {
           USE blue
            Translation {  translation -0.15 -0.12 0.15  }
          Rotation {rotation  1 0 0 1.57 }
                 Cylinder  {
                      height  0.31
                      radius  0.02
                                 }
                  }

  Separator {
           USE blue
            Translation {  translation -0.15 -0.18 0.15  }
             Rotation {rotation  1 0 0 1.57 }
                 Cylinder  {
                      height  0.31
                      radius  0.02
                                 }
                  }
Separator {
           USE blue
            Translation {  translation -0.15 -0.15 0.15  }
            Cube {
                      height  0.06
                      width  0.04
                      depth 0.31
                                 }
                  }
##########Включим колеса
DEF OS
Separator {
Separator {
        Translation {translation  0 -0.25 0.015 }
DEF KOLESO #переднее дальнее
        Separator {
                DEF PROT
                 Separator{
                               Material {            emissiveColor    0.2 0.2  0.2        }
        Rotation {rotation 1 0 0 1.57}
        Translation {translation  0.075 0 0 }
                                Cylinder {height 0.058 radius 0.01}
                                   }
     Rotation {rotation 0 0 1 2.4}
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT
    Rotation {rotation 0 0 1 0.4}
    USE PROT

      Rotation {rotation 1 0 0 1.57 }
    Separator{
        Material {            emissiveColor    0.2 0.2  0.2        }
        Cylinder  {
                    height  0.06
                    radius  0.08}

                    Material {            emissiveColor    1 0 0             }
                   Cylinder  {
                   height  0.065
                   radius  0.04}
                           }
                 }# конец def колесо

}
########## переднее ближнее
Separator {
Translation {translation  0 -0.25 0.285 }
USE KOLESO
               }
########## и между ними ось!!
Separator {
  Translation {translation  0 -0.25 0.15 }
  Cylinder    {
             height 0.03
             radius 0.01
                  }
  Rotation {rotation  1 0 0 1.57 }
   Cylinder   {
             height 0.3
             radius 0.01
                  }
            }
} #end def OS
########## и теперь кузов!!!!
########## низ
Separator {
           USE blue
            Translation {  translation 0.3 -0.18 0.15  }
               Cube  {
                      height  0.03
                      width  0.4
                     depth 0.32
                         }
                  }
########## бока
Separator {
           USE red
            Translation {  translation 0.3 -0.12 0  }
             DEF BOK
             Separator {
               Cube  {
                      height  0.15
                      width  0.4
                     depth 0.01
                         }
           DEF POLOSA
                 Separator {
                  Rotation {rotation 0 0 1 1.57}
                 Cylinder {radius 0.01 height 0.4}
                          }
          Translation {translation 0 -0.03 0}
          USE POLOSA
         Translation {translation 0 0.06 0}
          USE POLOSA
         Translation {translation 0 0.03 0}
          USE POLOSA

                                  }
                  }
Separator {
           USE red
            Translation {  translation 0.3 -0.12 0.3  }
            USE  BOK
                    }
########## зад кузова
Separator {
           USE red
            Translation {  translation 0.49 -0.11 0.155  }
                  Cube  {
                      height  0.12
                      width  0.01
                     depth 0.31
                         }
             Translation {translation 0 0.05 0}
              DEF POLOSA
                 Separator {
                  Rotation {rotation 1 0 0 1.57}
                 Cylinder {radius 0.01 height 0.31}
                          }
               Translation {translation 0 -0.03 0}
                USE POLOSA
    Translation {translation 0 -0.03 0}
                USE POLOSA
     Translation {translation 0 -0.03 0}
                USE POLOSA

                  }
Separator {
Translation {  translation 0.4 0 0  }
USE OS
                  }
}

}


#VRML V1.0 ascii
    Material {
diffuseColor 0 0 1
}
    Cube {}


#VRML V1.0 ascii
Texture2 {
            filename    "krp.gif"
            image       0 0 0
            wrapS REPEAT
            wrapT REPEAT
        }
Cube {
             width 1
             height 1
             depth 1
 }


#VRML V1.0 ascii
         Material {
                emissiveColor   1 1 0
            }
        Scale {
          scaleFactor 0.7 1 1
                  }
            Sphere { radius 1}
        }


#VRML V1.0 ascii
         Material {
                emissiveColor   1 1 0
            }
       Rotation {
               rotation 0 1 0 1
}
       Scale {
          scaleFactor 0.7 1 1
               }
            Cube {
               }
        }


#VRML V1.0 ascii
         Material {
                emissiveColor   1 1 0
            }
         Scale {
          scaleFactor 0.7 1 1
               }
          Rotation {
               rotation 0 1 0 1                }

            Cube {
               }
        }


#VRML V1.0 ascii
Separator {
  Cube {
     width 1
     height 1
    depth 1
   }
}# конец области действия узла Separator
Separator {
 Translation {
         translation 2 0 0
            }
#Второй куб сдвинут вправо на 2
 Cube {
     width 1
     height 1
    depth 1
   }
}# конец области действия узла Separator
Separator {
Translation {
         translation 2 0 0
            }
#Третий куб сдвинут вправо на два относительно 1-го.
 Cube {
     width 1
     height 1
    depth 1
   }
}# конец области действия узла Separator


#VRML V1.0 ascii
Material {
         emissiveColor 1 0.5 0.5
         }
Separator {
    Translation {
                translation 1 1 1
               }
    DEF LEG  #Определяем объект - ножку стула
    Separator { # leg
      Cylinder {
          height  0.8
          radius  0.1
                }
             } # определили ножку
        }
Separator {
       Translation {
          translation 0 1 1
                    }
       USE LEG # используем определенный объект
          }
Separator { # еще одна ножка
       Translation {
          translation 1 1 0
                   }
      USE LEG
           }
Separator { # последняя ножка
        Translation {
             translation 0 1 0
                    }
      USE LEG
           }

 Separator { # сиденье
        Translation {
             translation 0.49 1.5 0.5
                    }
        Cube {
            height 0.2
            width 1.2
            depth 1.2
              }
            }

Separator { # спинка
     Translation {
          translation 0.49 2 0
              }
     Cube {
        height 0.8
        width 1.2
        depth 0.2
          }
        }
Separator { # закругление спинки
    Translation {
       translation 0.49 2.1 0
                }
    Rotation {
         rotation    1 0 0 1.57
             }
    Cylinder {
        radius 0.6
        height 0.2
              }
          }


#VRML V1.0 ascii
 Separator { #
           Material {
                emissiveColor   1 0.6 0.6
            }
            Cylinder {
                height    1
                radius    0.3
            }
        }

        Separator { #
       Translation {
                   translation 0 0.5 0
                   }
       Rotation {
                rotation    0 0 1  1.57
            }
            Material {
                emissiveColor   0.5 0.5 1
            }
           Cylinder{
                height          1
                radius    0.3
            }
        }


VRML в примерах

  • Что такое VRML
  • Единицы измерения
  • Заголовок VRML-файла
  • Примитивы VRML

    • Куб

    • Сфера

    • Конус

    • Цилиндр

  • Цвет и текстура
  • Положение объектов в пространстве
    • Изменение координат
    • Вращение
    • Масштабирование
  • Определение собственных объектов

Что такое VRML

Язык VRML (Virtual Realty Modelling Languagy) предназначен для описания трехмерных изображений и оперирует объектами, описывающими геометрические фигуры и их расположение в пространстве.

Vrml-файл представляет собой обычный текстовый файл, интерпретируемый браузером. Поскольку большинство браузеров не имеет встроенных средств поддержки vrml, для просмотра Vrml-документов необходимо подключить вспомогательную программу - Vrml-браузер, например, Live3D или Cosmo Player.

Как и в случае с HTML, один и тот же vrml-документ может выглядеть по-разному в разных VRML-браузерах. Кроме того, многие разработчики VRML-браузеров добавляют нестандартные расширения VRML в свой браузер.

Существует немало VRML-редакторов, делающих удобней и быстрее процесс создания Vrml-документов, однако несложные модели, рассматриваемые в данной статье, можно создать при помощи самого простого текстового редактора.

Единицы измерения

В VRML приняты следующие единицы измерения:

  • Расстояние и размер: метры
  • Углы: радианы
  • Остальные значения: выражаются, как часть от 1.
  • Координаты берутся в трехмерной декартовой системе координат (см. рис.)

Заголовок VRML-файла

Как уже говорилось, Vrml-документ представляет собой обычный тестовый файл.

Для того, чтобы VRML-браузер распознал файл с VRML-кодом, в начале файла ставится специальный заголовок - file header:

    #VRML V1.0 ascii

Такой заголовок обязательно должен находиться в первой строке файла, кроме того, перед знаком диеза не должно быть пробелов.

Примитивы VRML

В VRML определены четыре базовые фигуры: куб (верней не куб, а прямоугольный параллепипед), сфера, цилиндр и конус.

Эти фигуры называются примитивами (primitives). Набор примитивов невелик, однако комбинируя их, можно строить достаточно сложные трехмерные изображения. Например, вот такие:

Рассмотрим поподробней каждый из примитивов.

Куб

Возможные параметры: width - ширина, height - высота, depth - глубина.

     Cube {

        width   2    # ширина

        height  3    # высота

        depth   1    # глубина

          }

Сфера

Параметр у сферы только один, это radius.

    Sphere {

        radius  1    # радиус

           }

Конус

Возможные параметры: bottomRadius - радиус основания, height - высота, parts - определяет, какие части конуса будут видны. Параметр parts может принимать значения ALL, SIDES или BOTTOM.

     Cone {

        parts           ALL      #видны и основание,
                                 и боковая поверхность
                                  конуса

        bottomRadius    1        #радиус основания

        height          2        #высота

          }

Цилиндр

Для цилиндра можно задать параметры radius и height. Кроме того, с помощью параметра parts для цилиндра можно определить будут ли отображаться основания цилиндра и его боковая поверхность. Параметр parts может принимать значения ALL, SIDES, BOTTOM или TOP.

      Cylinder {

          parts   ALL      #видны все части цилиндра

          radius  1        #радиус основания

          height  2        #высота цилиндра

              }

Цвет и текстура

Цвет фигуры, определяется с помощью объекта Material.

    Material {

         ambientColor    0.2 0.2 0.2

         diffuseColor    0.8 0.8 0.8

         specularColor   0 0 0

         emissiveColor   0 0 0

         transparency    0

             }

Параметры ambientColor, diffuseColor, specularColor и emissiveColor управляют цветами и указываются в палитре RGB (красный, зеленый и голубой), причем первая цифра определяет интенсивность красного цвета, вторая - зеленого, а третья - синего.

К примеру, синий кубик, может быть описан следующим образом:

    #VRML V1.0 ascii

        Material {

               diffuseColor 0 0 1

                 }

        Cube {}

Параметр transparency может принимать значения от 0 до1 и определяет степень прозрачности, причем максимальная прозрачность достигается при transparency равном единице. В приведенном примере описано два цилиндра разных размеров, меньший из которых просвечивает сквозь другой.

    #VRML V1.0 ascii

       Material {

          diffuseColor 0 0 1

          transparency    0.7

                }

       Cylinder {

          height  1

          radius  1

                }

       Material {

          emissiveColor    1 0 0

          transparency    0

                }

       Cylinder {

          height  0.8

          radius  0.1

                }

Для имитирования различных поверхностей в VRML существует объект Texture2.

В качестве текстуры легче всего использовать обычный графический файл, например, в GIF-формате. В таком случае для "натягивания" текстуры на трехмерное изображение нужно только указать путь к файлу в параметре filename объекта Texture2.

.     #VRML V1.0 ascii

     Texture2 {

                 filename    "krp.gif"

                 image       0 0 0

                 wrapS REPEAT

                 wrapT REPEAT

                    }

     Cube {

                 width 1

                 height 1

                 depth 1

              }

Параметры wrapS и wrapT могут принимать значения REPEAT или CLAMP, и управляют натягиванием текстуры по соответственно горизонтальной и вертикальной осям.

Положение объектов в пространстве

Изменение координат

По умолчанию любой описанный нами объект будет располагаться точно по центру окна браузера. По этой причине, если мы опишем к примеру два одинаковых цилиндра, они сольются друг с другом. Для того, чтобы изменить положение второго цилиндра, применим узел Translation.

Узел Translation определяет координаты объекта:

    Translation {

         translation 1 2 3 #т.е. соответственно
                                x=1 y=2 z=3

                }

Вообще говоря, координаты указываемые в Translation не являются абсолютными. Фактически это координаты относительно предыдущего узла Translation. Чтобы прояснить это вопрос, рассмотрим пример:

    #VRML V1.0 ascii

      Cube {

        width 1

        height 1

        depth 1

           }

     # Этот куб по умолчанию располагается в центре

      Translation {

          translation 2 0 0

                  }

     #Второй куб сдвинут вправо на 2

      Cube {

         width 1

         height 1

         depth 1

            }

      Translation {

         translation 2 0 0

                  }

     #Третий куб сдвинут вправо на два
     относительно 2-го !!!!

      Cube {

         width 1

         height 1

         depth 1

           }

Как видите, третий кубик вовсе не совпадает с первым, хотя в в узле Translation указаны те же координаты.

В VRML 1.0 принято следующее правило: узлы, модифицирующие свойства фигур (Translation, Material и т.п.), действуют на все далее описанные фигуры.

Чтобы ограничить область действия модифицирующих узлов, фигуры необходимо сгруппировать с помощью узла Separator.

     Separator

                     {

                 другие узлы

        }

Узел Separator работает как контейнер, он может содержать любые другие узлы, и основным его предназначением является именно ограничение области действия узлов типа Translation и Material.

Сравните следующий пример с предыдущим:

     #VRML V1.0 ascii

      Separator {

           Cube {

              width 1

              height 1

              depth 1

                 }

         }# конец области действия узла Separator

      Separator {

           Translation {

                translation 2 0 0

                       }

           #Второй куб сдвинут вправо на 2

            Cube {

                 width 1

                 height 1

                 depth 1

                 }

         }# конец области действия узла Separator

       Separator {

             Translation {

                 translation 2 0 0

                         }

            #Третий куб сдвинут вправо на два
                     относительно 1-го.

              Cube {

                 width 1

                 height 1

                 depth 1

                    }

        }# конец области действия узла Separator

Хотя в примере описано три кубика, мы видим только два, так как второй и третий совпадают.

Вообще говоря рекомендуется всегда и везде использовать узел Separator. Он не только избавит от ошибок, связанных с относительностью координат, но и сделает VRML-код более простым и понятным.

Вращение

Для вращения фигур вокруг осей координат применяется узел Rotation.

   Rotation {

         rotation 0 1 0  1.57

            }

Первые три цифры определяет будет ли осуществлен поворот вокруг соответственно осей x, y и z, а четвертая задает угол вращения в радианах. В приведенном выше листинге поворот осуществляется вокруг оси y на 90 градусов.

Углы в градусах | Радианы
30 | 0.52
45 | 0.78
60 | 1.04
90 | 1.57
180 | 3.14
270 | 4.71

Составим букву T из двух цилиндров. По умолчанию цилиндр ориентирован вертикально (см. рисунок). Поэтому для успешного выполнения задачи повернем его вокруг оси z на 90 градусов.

     #VRML V1.0 ascii

      Separator { #Красный цилиндр

               Material { emissiveColor   1 0.6 0.6 }

               Cylinder {

                      height    1

                      radius    0.3

                        }

                }

      Separator { # Синий цилиндр, повернутый
       на 90 градусов вокруг оси z

              Translation {

                    translation 0 0.5 0

                          }

              Rotation {

                     rotation    0 0 1  1.57

                       }

              Material { emissiveColor   0.5 0.5 1 }

              Cylinder {

                     height    1

                     radius    0.3

                       }

                }

Масштабирование

Узел Scale масштабирует фигуры по одному или нескольким измерениям. Три цифры, стоящие после параметра scaleFactor определяют коэффициенты масштабирования относительно осей x,y и z.

      Scale {

         scaleFactor 1 1 1

            }

В следующем примере, узел Scale сжимает сферу по оси x, и из сферы получается эллипсоид.

    #VRML V1.0 ascii

       Material { emissiveColor   1 1 0 }

       Scale {

          scaleFactor 0.7 1 1 #сжимаем сферу по оси x

             }

       Sphere { radius 1}

             }

Определение собственных объектов

VRML предоставляет прекрасную возможность сократить и сделать более понятным исходный код VRML-файла путем описания собственных объектов. Это значит, что если в изображении несколько раз повторяется одна и та же фигура, то ее можно описать всего лишь один раз и в дальнейшем только ссылаться на нее.

Объект описывается одним из способов:

    DEF name

       Cube {}

или

    DEF name

       Material {}

или

    DEF name

    Separator {

        Сгруппированные узлы, описывающие фигуру
        и свойства материала

    }

Для того, чтобы вставить в VRML-файл ранее определенную фигуру, используется команда USE

    Separator {

        USE name

    }

Создадим VRML-файл, описывающий стул, при этом ножку стула опишем как объект LEG:

    #VRML V1.0 ascii

    Material { emissiveColor 1 0.5 0.5 }

    Separator {

         Translation { translation 1 1 1 }

         DEF LEG  #Определяем объект - ножку стула

         Separator { # leg

            Cylinder {

               height  0.8

               radius  0.1

                     }

                   } # определили ножку

               }

    Separator {

         Translation { translation 0 1 1 }

         USE LEG # используем определенный объект

              }

    Separator { # еще одна ножка

         Translation { translation 1 1 0 }

         USE LEG

              }

    Separator { # последняя ножка

         Translation { translation 0 1 0 }

         USE LEG

              }

    Separator { # сиденье

         Translation { translation 0.49 1.5 0.5 }

         Cube {

            height 0.2

            width 1.2

             depth 1.2

              }

              }

    Separator { # спинка

        Translation { translation 0.49 2 0 }

        Cube {

           height 0.8

           width 1.2

           depth 0.2

             }

              }

    Separator { # закругление спинки

        Translation { translation 0.49 2.1 0 }

        Rotation {

           rotation    1 0 0 1.57

                 }

        Cylinder {

           radius 0.6

           height 0.2

                 }

          }

Как видите, нам не понадобилось описывать каждую ножку в отдельности - в результате объем VRML-кода стал меньше, а сам код более читабельным.

Еще один способ уменьшить размер VRML-файла - вставлять фигуры из другого файла.

Это позволяет делать узел WWWInline:

    #VRML V1.0 ascii

    Separator {

          WWWInline {

              name        ""

              bboxSize    0 0 0

              bboxCenter  0 0 0

                    }

              }

Параметр name - это путь к файлу, параметры bboxSize и bboxCenter не обязательны и показывают пользователю размеры и положение вставляемого объекта, пока объект подгружается.

Вместо заключения, хочется обратить Ваше внимание на две особенности VRML, незнание которых сильно затруднит создание VRML-документов вручную.

  1. Все описания узлов и параметров в VRML регистрозависимы. Если Вы используете буквы неправильного регистра - то VRML-браузер просто проигнорирует такое описание.
  2. В VRML имеет огромное значение порядок описания узлов. Так к примеру, описание
           ...

       Rotation {...}

       Scale {...}

       ...
    и описание
         ...

     Scale {...}

     Rotation {...}

     ...
дают совершенно разный результат.


Язык XML. Пример XML-документа

Приложение 1

Пример XML-документа

<?xml version="1.0"?>
<journal>
<title>Very Useful Journal</title>
<contacts>
<address>sdsds</address>
<tel>8-3232-121212</tel>
<tel>8-3232-121212</tel>
<email>j@j.ru</email>
<url>www.j.ru</url>
   </contacts>
<issues-list>

<issue index="2">
<title>XML today</title>
<date>12.09.98</date>
<about>XML</about>
<home-url>www.j.ru/issues/</home-url>
<articles>

<article ID="3">
<title>Issue overview</title>
<url>/article1</url>

<hotkeys>
<hotkey>language</hotkey>
<hotkey>marckup</hotkey>
<hotkey>hypertext</hotkey>
               </hotkeys>
<article-finished/>
            </article>

<article>
<title>Latest reviews</title>
<url>/article2</url>
<author ID="3"/>
<hotkeys>
<hotkey/>
               </hotkeys>
            </article>

<article ID="4">
<title/>
<url/>

<hotkeys/>
            </article>
         </articles>
      </issue>
   </issues-list>
<authors-list>

<author ID="1">
<firstname>Ivan</firstname>
<lastname>Petrov</lastname>
<email>vanya@r.ru</email>
      </author>

<author ID="3">
<firstname>Petr</firstname>
<lastname>Ivanov</lastname>
<email>petr@r.ru</email>
      </author>

<author ID="4">
<firstname>Sidor</firstname>
<lastname>Sidorov</lastname>
<email>sidor@r.ru</email>
      </author>
   </authors-list>
</journal>


Язык XML. JavaScript сценарий, обрабатывающий XML-документ

Приложение 2

JavaScript сценарий, обрабатывающий XML-документ, приведенный в приложении 1

<HTML>
<head>
<title></title>
<script language="javascript">
<!--
var xmldoc = new ActiveXObject("msxml");
var xmlsrc = "http://localhost/xml/journal.xml";
function viewTitle(elem){  // Отображение заголовка документа, определяемого элементом <title>
this.document.writeln('<center><table width="100%" border=0><tr><td width="100%" align="center" bgcolor="silver"><b><font color="black">'+elem.text+'</font></b></td></tr></table></center><br>');
}
function viewContactsList(elem){ // Отображение содержимого дочерних элементов <author-list>
this.document.writeln('<tr><td align="right" colspan="2" bgcolor="gray"><b><font color="white">Наши реквизиты</font></b></td></tr>');
this.document.writeln('<tr><td bgcolor="silver" colspan="2"><center><table width="80%" border=0>');
 if(elem.type==0){
  if(elem.children!=null){
    this.document.writeln('<tr><td colspan=2 width="100%"> </td></tr>');
     var cur_item=elem.children.item("address");
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">Адрес</font></td><td align="right" ><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
     var cur_item=elem.children.item("tel",0);
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">Телефон</font></td><td align="right" ><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
     var cur_item=elem.children.item("email");
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">E-Mail</font></td><td align="right"><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
     var cur_item=elem.children.item("url");
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">URL</font></td><td align="right"><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
    }
 }
this.document.writeln('<tr><td colspan=2 width="100%"> </td></tr>');
this.document.writeln('</table></center></td></tr>');
}
function viewAuthorsList(elem){	// Отображение содержимого дочерних элементов <author-list>
this.document.writeln('<tr><td align="right" colspan="2" bgcolor="gray"><b><font color="white">Наши авторы</font></b></td></tr>');
this.document.writeln('<tr><td bgcolor="silver" colspan="2"><center><table width="80%" border=0>');
 if(elem.type==0){
  if(elem.children!=null){
   for(i=0;i<elem.children.length;i++){
    var cur_author = elem.children.item("author",i);
    this.document.writeln('<tr><td colspan=2 width="100%"> </td></tr>');
    if(cur_author.children!=null){
     var cur_item=cur_author.children.item("firstname");
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">Имя</font></td><td align="right" ><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
     var cur_item=cur_author.children.item("lastname");
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">Фамилия</font></td><td align="right" ><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
     var cur_item=cur_author.children.item("email");
     if(cur_item!=null){
      this.document.writeln('<tr><td><font color="blue">E-Mail</font></td><td align="right"><b><font color="red">'+cur_item.text+'</font></b></td></tr>');
     }
    }
   }
  }
 }
this.document.writeln('</table></center></td></tr>');
}
function viewError(){
this.document.writeln('<center><hr>Error was detected');
}
function parse(root){
if(root==null) return;
 var i=0;
 var elem;
if(root.children!=null){	// Если вложенные элементы не были определены, то свойство children будет установленно в null
this.document.writeln('<center><table width="80%" border=0><tr><td>');
                               // Перебор дочерних элементов
 for(i=0;i<root.children.length;i++){
  elem=root.children.item(i);
  if(root.children.item(i).tagName=="TITLE"){
    viewTitle(elem);            // Разбор подэлементов <title>
  }
  if(elem.tagName=="CONTACTS"){
    viewContactsList(elem);     // Разбор подэлементов <contacts>
  }
  if(elem.tagName=="AUTHORS-LIST"){
    viewAuthorsList(elem);      // Разбор подэлементов <authors-list>
  }
 }
this.document.writeln('</td></tr></table>');
  }
}
function viewDocument(){
xmldoc.URL = xmlsrc;     // Загрузка XML документа
this.document.writeln('<body bgcolor="white">');
parse(xmldoc.root);      // Начало разбора документа
this.document.writeln('</body>');
}
// Генерирование страницы
viewDocument();
//-->
</script>
</head>


Язык XML. Пример XSL-документа

Приложение 3

Пример XSL-документа

<xsl>
<!-- Корневое  правило -->
<rule>
    <root/>
    <HTML><HEAD>    <!-- Область сценария-->
       <SCRIPT LANGUAGE="JSCRIPT"><![CDATA[
var ie4=((navigator.appName=="Microsoft Internet Explorer")&&(parseInt(navigator.appVersion)  >= 4 ));
       function msover(){
if (ie4){
          event.srcElement.style.color="red";
          event.srcElement.style.cursor = "hand";
      }
}
       function msout(){
    if (ie4){
          event.srcElement.style.color="black";
          event.srcElement.style.cursor = "auto";
      }
}        ]]></SCRIPT>
    </HEAD>
    <BODY bgcolor="white">
    <center>
    <table width="80%" border="1">
    <children/>
    </table></center>
    </BODY>
    </HTML>
</rule>
<!--   Использование элемента select-elements -->
  <rule>
    <target-element type="journal"/>
      <select-elements> <target-element type="title"/> </select-elements>,
      <select-elements> <target-element type="contacts"/> </select-elements>,
      <select-elements> <target-element type="issues-list"/> </select-elements>,
      <select-elements> <target-element type="authors-list"/> </select-elements>.
  </rule>
<!-- Formatting title element -->
<rule>
      <element type="journal">
        <target-element type="title"/>
        <!--  Элемент title должен определяться внутри элемента journal   -->
         </element>
        <tr><td align="center"><center>
    <table width="80%" border="1"><tr><td width="100%">
    <b><font color="blue">
    <children/>
    </font></b></td></tr>
    </table></center>
        </td></tr>
</rule>
<!-- Issues list -->
<rule>
<element type="journal">
        <target-element type="issues-list"/>
</element>
        <tr><td align="center">
    <children/>
    </td></tr>
</rule>
<rule>
    <element type="issues-list">
        <target-element type="issue"/>
        </element>
        <tr><td><center>
        <table width="100%" border="0">
    <tr><td colspan="2" bgcolor="gray">
    <font color="white">Issues list</font></td></tr>
    <children/>
    <tr><td> </td></tr></table></center></td></tr>
</rule>
<rule>
    <target-element type="issue"/>
        <tr><td>
        <table width="100%" border="0">
    <tr><td colspan="2">Issue number
    <b><eval>childNumber(this);</eval></b></td></tr>
    <children/>
    <tr><td> </td></tr></table></td></tr>
</rule>
<rule>
    <element type="issue">
        <target-element type="title"/>
        <target-element type="date"/>
        <target-element type="about"/>
        <target-element type="home-url"/>
        </element>
        <tr>
    <td width="40%"><font color="blue"><eval>tagName</eval></font></td>
    <td width="60%" align="right"><div align="right"><b><font color="red">
    <children/></font></b>
    </div></td></tr>
</rule>
<rule>
    <element type="issue">
        <target-element type="articles"/>
        </element>
        <tr><td colspan="2" align="right" bgcolor="silver">
    <center>Articles list</center></td></tr>
        <children/>
</rule>
<rule>
    <element type="articles">
        <target-element type="article"/>
        </element>
        <tr><td colspan="2" align="right">Article number
    <b><eval>childNumber(this);</eval></b></td></tr>
        <children/>
</rule>
<rule>
    <element type="article">
        <target-element type="title"/>
        <target-element type="url"/>
        <target-element type="author"/>
        </element>
        <tr>
    <td width="40%"><font color="maroon"><eval>tagName</eval></font></td>
    <td width="60%" align="right"><div align="right"><b><font color="red">
    <children/></font></b>
    </div></td></tr>
</rule>
<rule>
    <target-element type="article" position="last-of-type"/>
    <children/>
        <tr><td colspan="2" bgcolor="silver" width="100%"> </td></tr>
</rule>
<rule>
    <element type="hotkeys">
        <target-element type="hotkey"/>
        </element>
        <tr>
    <td width="40%"><font color="maroon"><eval>tagName</eval></font></td>
    <td width="60%" align="right"><div align="right"><b><font color="red">
    <children/></font></b>
    </div></td></tr>
</rule>
<!-- Contacts -->
<rule>
    <element type="journal">
    <target-element type="contacts"/>
    <select-elements> <target-element type="address"/> </select-elements>,
    <select-elements> <target-element type="tel"/> </select-elements>,
    <select-elements> <target-element type="email"/> </select-elements>,
    <select-elements> <target-element type="url"/> </select-elements>.
        </element>
        <tr><td><center><table width="100%" border="1">
    <tr><td colspan="2" bgcolor="gray"><font color="white">Contact us:</font></td></tr>
    <children/>
    <tr><td> </td></tr></table></center></td></tr>
</rule>
<rule>
    <element type="contacts">
        <target-element type="address"/>
        <target-element type="tel"/>
        <target-element type="email"/>
        <target-element type="url"/>
        </element>
        <tr>
    <td width="40%"><font color="blue"><eval>tagName</eval></font></td>
    <td width="60%" align="right"><div align="right"><b><font color="red">
    <children/></font></b>
    </div></td></tr>
</rule>
<!-- Authors -->
<rule>
    <element type="journal">
        <target-element type="authors-list"/>
        </element>
           <tr><td bgcolor="gray"><font color="white">Authors list</font></td></tr>
    <tr><td>
        <children/>
    </td></tr>
</rule>
<rule>
    <element type="authors-list">
        <target-element type="author"/>
    <select-elements> <target-element type="firstname"/> </select-elements>,
    <select-elements> <target-element type="lastname"/> </select-elements>,
    <select-elements> <target-element type="email"/> </select-elements>.
        </element>
        <table width="100%" border="1">
    <tr><td colspan="2">Author index  <b><eval>getAttribute("ID");</eval></b></td></tr>
    <children/>
    <tr><td> </td></tr></table>
</rule>
<rule>
    <element type="author">
      <attribute name="ID" has-value="yes"/>
        <target-element type="firstname"/>
        <target-element type="lastname"/>
        <target-element type="email"/>
        </element>
        <tr>
    <td width="40%"><font color="blue"><eval>tagName</eval></font></td>
    <td width="60%" align="right"><b><font color="black">
<!--  Подсветка элементов   -->
            <DIV id='=tagName + formatNumber(childNumber(this),"1")' background-color="marron"
      onmouseover='="msover("+ tagName + formatNumber(childNumber(this),1)+")"'
      onmouseout='="msout("+ tagName + formatNumber(childNumber(this),1)+")"'>
    <children/>
    </DIV>
    </font></b>
    </td></tr>
</rule>
<!-- Определение стиля. Изменение стиля комнется всех элементов title и url,
     вне зависимости от их месторасположения -->
  <style-rule>
    <target-element type="title"/>
    <target-element type="url"/>
    <apply font-style="italic" color="maroon"/>
  </style-rule>
</xsl>


Язык XML. DTD-определения для XML-документа

Приложение 4

DTD-определения для XML-документа приложения 1

<!ENTITY % idattr 'id ID #IMPLIED'gt;
<!ENTITY % opt 'title?,date,about'gt;
<!ENTITY % cont 'tel*,url*,email*'gt;
<!element title (PCDATA)?gt;
<!element firstname (PCDATA)?gt;
<!element lastname (PCDATA)?gt;
<!element email (PCDATA)?gt;
<!element url (PCDATA)?gt;
<!element tel (PCDATA)?gt;
<!element address (PCDATA)?gt;
<!element fax (PCDATA)?gt;
<!element date (PCDATA)?gt;
<!element home-url (PCDATA)?gt;
<!element article-url (PCDATA)?gt;
<!element hotkey (PCDATA)? gt;
<!element article-finished EMPTYgt;
<!element contents ANYgt;
<!element hotkeys (PCDATA|hotkey)? gt;
<!element author
(PCDATA|firstname?,lastname?,%cont;) gt;
<!attlist author %idattr;gt;
<!element authors (PCDATA|author*)? gt;
<!element article (%opt;,author,article-url,hotkeys*,
contents*,article-finished) gt;
<!attlist article %idattr;gt;
<!element articles (article)? gt;
<!element issue (%opt;,home-url?,articles*) gt;
<!attlist issue %idattr; index CDATA #REQUIRED gt;
<!element issues-list (PCDATA|issue*)* gt;
<!element contacts (address,%cont;)gt;
<!element journal (title?,contacts?,isues-list,
authors-list)gt;
<!attlist journal %idattr; src CDATA #IMPLIED gt;


Язык XML. Практическое введение (Содержание)

Язык XML - практическое введение

  • Введение
    • Для чего нужен новый язык разметки?
    • Структура документа
    • Правила создания XML- документа
    • Конструкции языка
  • Просмотр XML-документов
    • Использование msxml в IE 4
    • Использование ASP
  • Стилевые таблицы XSL
  • DTD - определения
  • Схемы данных

  • Описание технологии
    • Что такое XML
    • Как создать XML документ?
    • XML-генераторы
    • Что такое DTD?
    • Что такое Namespaces?
    • Инструментарий
    • Обработка XML-документов
    • Объектная модель документа DOM
  • XML в Microsoft Internet Explorer 5.0
    • Internet Explorer DOM
    • Описание объектной модели
    • Примеры использования объектов
  • Использование Java XML-обработчиков
    • Что такое XML Parser?
    • Что такое SAX
  • DOM совместимые анализаторы

  • Приложение 1
  • Приложение 2
  • Приложение 3
  • Приложение 4
  • Список ресурсов


XML в Интернет

Официальная документация

  1. XML - http://www.w3.org/TR/1998/REC-xml-19980210
  2. XML v. 1.0 DTD - http://www.w3.org/XML/1998/06/xmlspec-19980910.dtd
  3. XSL - http://www.w3.org/TR/1998/WD-xsl-19980818
  4. Document Object Model (DOM) Level 1 Specification - http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/
  5. SGML-XML - http://www.w3.org/TR/NOTE-sgml-xml
  6. Document Content Description for XML - http://w3c.org/TR/NOTE-dcd
  7. CSS2 - http://www.w3.org/TR/REC-CSS2
  8. HTML - http://www.w3.org/TR/1998/REC-xml-19980210.html
  9. Channel Definition Format (CDF) - http://www.microsoft.com/standards/cdf.htm
  10. ISO 8879. Information Processing -- Text and Office Systems - Standard Generalized Markup Language (SGML), 1986. - http://www.iso.ch/cate/d16387.html
  11. Namespaces in XML World Wide Web Consortium Working Draft 16-September-1998 - http://www.w3.org/TR/WD-xml-names
  12. XML-Data W3C Note 05 Jan 1998 - http://www.w3.org/TR/1998/NOTE-XML-data-0105/

Статьи, спецификации, ссылки на ресурсы

  13. The SGML/XML Web Page - http://www.oasis-open.org/cover/sgml-xml.html
  14. The SGML/XML Web Page - Extensible Markup Language (XML) - http://www.oasis-open.org/cover/xml.html
  15. The SGML/XML Web Page - Extensible Style Language (XSL - http://www.oasis-open.org/cover/xsl.html
  16. The SGML/XML Web Page - Public SGML/XML Software - http://www.oasis-open.org/cover/publicSW.html
  17. he SGML/XML Web Page - SGML/XML Bibliograph - http://www.oasis-open.org/cover/biblio.html

  18. Чего мы ждем от XMLШелли Пауэрс(Мир ПК 3/1998 г.) - http://www.osp.ru/pcworld/1998/03/180.htm

  19. Overview of SGML Resources - http://www.w3.org/MarkUp/SGML/
  20. XML/XSL Resources - from Bryan Van Hook - http://capita.wustl.edu/XMLRes/
  21. XML News and Links - http://www.arbortext.com/News_and_Events/XML_News_and_Links/xml_news_and_links.html
  22. XML Resources - http://www.microsoft.com/xml/xmllinks.asp
  23. SGML Syntax Summary Table of Contents - http://www.sil.org/htbin/sgml-redirect/sgml/sgmlsyn/contents.htm
  24. XML Community - http://www.inso.com/xml/index.htm

Microsoft XML

  25. Inside the XML 1.0 Specification - http://www.xml.com/xml/pub/axml/axmlintro.html
  26. XML Overview - http://www.microsoft.com/xml/OVERVIEW.asp
  27. XML Data types - http://www.microsoft.com/workshop/xml/schema/reference/datatypes.asp
  28. Microsoft XML Tutorial - http://www.microsoft.com/xml/tutorial/default.asp
  29. Building XML Parsers for Microsoft's IE4 - http://w3j.com/xml/excerpt.html
  30. XML 1.0 and Namespace Support - http://www.microsoft.com/xml/xml10andNameSpaceSupport.asp

XSL

  31. Using XSL and CSS together
    W3C Note, 11 September 1998     - http://www.w3.org/TR/1998/NOTE-XSL-and-CSS-19980911
  32. An Introduction to XSL by Henry S. Thompson HCRC Language Technology Group University of Edinburgh, Oct 27 1997 - http://www.ltg.ed.ac.uk/~ht/swindon.html
  33. Understanding XSL
    by Jay Greenspan 28 October 1998     - http://www.hotwired.com/webmonkey/98/43/index2a.html
  34. XSL Tutorials by Frank Boumphrey. - http://www.hypermedic.com/style/xsl/
  35. "The MSXSL XSL Processor." Presentation made by Sean McGrath to SGML UK (SGML Users' Group) Tuesday, March 31, 1998. - http://www.sgml.org.uk/slides/xsl95/

Схемы

  36. XML Schema and Data Types Preview - http://www.microsoft.com/xml/schema/reference/start.asp
  37. Resource Description Framework
    (RDF) Schema Specification     - http://www.w3.org/TR/WD-rdf-schema/
  38. Schema for Object-Oriented XML (SOX) - http://www.w3.org/TR/NOTE-SOX/
  39. XML Parsers/Processors - http://www.xmlsoftware.com/parsers/
  40. A Lexical Analyzer for HTML and Basic SGML - http://www.w3.org/MarkUp/SGML/sgml-lex/sgml-lex
  41. UNC Sunsite WWW [and FTP] Server, maintained by W3C XML Chair, Jon Bosak - http://sunsite.unc.edu/pub/sun-info/standards/xml/
  42. XML Editors - http://www.xmlsoftware.com/editors/
  43. Applications development models - http://www.xml.com/xml/pub/Applications
  44. XML.com Guide to Authoring Tools - http://www.xml.com/xml/pub/authortools
  45. Tools - XML and SGML tools including parsers - http://www.xml.com/xml/pub/Tools

FAQ

  46. Frequently Asked Questions about the Extensible Markup Language(The XML FAQ ) - http://www.ucc.ie/xml/
  47. Extensible Markup Language Frequently Asked Questions( XML FAQ) - http://www.textuality.com/xml/faq.html
  48. "20 Questions About XML." By Trisha Gorman. - http://builder.cnet.com/Authoring/Xml20/index.html

Информация по некоторым языкам разметки, производным от XML

  49. Meta Content Framework Using XML - http://www.textuality.com/mcf/NOTE-MCF-XML.html;
  50. Resource Description Framework (RDF) - http://www.w3.org/RDF/
  51. Synchronized Multimedia Integration Language (SMIL) - http://www.w3.org/AudioVideo/
  52. Mathematical Markup Language (MathML) - http://www.w3.org/Math/
  53. CML - Chemical Markup Language - http://www.oasis-open.org/cover/cml1.html
  54. TML(Tutorial Markup Language) Specification - http://www.ilrt.bris.ac.uk
  55. XLF (Extensible Log Format) Initiative - http://www.docuverse.com/xlf/
  56. Metafile for Interactive Documents (MID) - http://www.oasis-open.org/cover/gen-apps.html#mid

XML Web

  57. XML.COM - http://www.xml.com
  58. XMLINFO.COM - http://www.xmlinfo.com/
  59. SCHEMA.NET - http://www.schema.net/

  60. Robin Coverґs SGML/XML Pages - http://www.oasis-open.org/cover/

  61. Microsoft's XML Web Site - http://www.microsoft.com/xml/
  62. Microsoft's XSL Web Site - http://www.microsoft.com/xml/xsl
  63. ArborText's XML resources page - http://www.arbortext.com/xmlresrc.html
  64. Charles F. Goldfarb's SGML SOURCE HOME PAGE - http://www.sgmlsource.com/
  65. DataChannel XML Resource Center - http://www.datachannel.com/xml_resources/
  66. DEVELOPER.COM - XML Directory - http://www.developer.com/directories/pages/dir.xml.html
  67. POET XML resource library - http://www.poet.com/xml/


Язык XML. Практическое введение

Язык XML - практическое введение

Зa неполный год своего официального существования язык XML привлек к себе уже достаточно много внимания со стороны разработчиков и пользователей Интернет. Сегодня количество приверженцев этой новой технологии возрастает также стремительно, как и число сообщений об очередных взятых ею преградах на пути к всеобщему признанию. Несмотря на то, что XML очень молод (международная организация W3C утвердила спецификацию "Extensible Markup Language(XML) 1.0" чуть меньше года назад - в начале февраля 1998 г) и отдельные компоненты этого языка находятся еще в стадии доработки, уже сегодня появляются новые языки, созданные на основе XML, возникают многочисленные Web-сервера, использующие эту технологию для организации хранящейся на них информации. Мир Интернет вокруг нас в очередной раз преображается, и мы можем стать участниками этого процесса уже сегодня

Целью данной статьи является попытка на конкретных примерах показать некоторые из возможностей XML, ответить на ряд часто возникающих при знакомстве с новым языком вопросов. Что же такое XML? В чем заключаются его преимущества перед привычным уже нам языком HTML? Можно ли использовать XML на своих Web-страничках уже сегодня? А если можно, то как?

В конце статьи приведены ссылки на другие ресурсы Интернет, с помощью которых Вы сможете также получить более полную информацию по конкретным интересующим Вас вопросам, связанным с применением XML и незатронутых нами в этой статье. Полные спецификации XML и связанных с ним языков доступны на официальной странице W3C - www.w3.org

Для чего нужен новый язык разметки?

Когда осенью 1991 года Интернет впервые услышал позывные новой технологии, название которой легко уместилось в три буквы, почти никто не мог представить себе, что завоевания ее окажутся настолько глобальными. Сегодня для многих неискушенных пользователей слово Интернет прочно ассоциируется с WWW и с уст специалистов не сходит тема будущего информационных систем и влияния на это будущее всемирной сетевой паутины.

Популярность World Wide Web и неотъемлемой ее части, HTML, безусловно, стала причиной повышенного внимания к системам гипертекстовой разметки документов. Хотя понятие гипертекста было введено В.Бушем еще в 1945 году и, начиная с 60-х годов стали появляться первые приложения, использующие гипертекстовые данные, всплеск активности вокруг этой технологии начался лишь тогда, когда возникла реальная необходимость в механизме объединения множества информационных ресурсов, обеспечения возможности создания, просмотра нелинейного текста. И примером реализации этого механизма послужила паутина WWW.

Язык разметки документов - это набор специальных инструкций, называемых тэгами, предназначенных для формирования в документах какой-либо структуры и определения отношений между различными элементами этой структуры. Тэги языка, или, как их иногда называют, управляющие дескрипторы, в таких документах каким-то образом кодируются, выделяются относительно основного содержимого документа и служат в качестве инструкций для программы, производящей показ содержимого документа на стороне клиента. В самых первых системах для обозначения этих команд использовались символы “<” и “>”, внутри которых помещались названия инструкций и их параметры. Сейчас такой способ обозначения тэгов является стандартным.

Использование гипертекстовой разбивки текстового документа в современных информационных системах во многом связано с тем, что гипертекст позволяет создавать механизм нелинейного просмотра информации. В таких системах данные представляются не в виде непрерывного потока текстовой информации, а набором взаимосвязанных компонентов, переход по которым осуществляется при помощи гиперссылок.

Самый популярный на сегодняшний день язык гипертекстовой разметки – HTML, был создан специально для организации информации, распределенной в сети Интернет, и является одной из ключевых составляющих технологии WWW. С использованием гипертекстовой модели документа способ представления разнообразных информационных ресурсов в сети стал более упорядочен, а пользователи получили удобный механизм поиска и просмотра нужной информации.

HTML [8]является упрощенной версией стандартного общего языка разметки - SGML (Standart Generalised Markup Language[10]), который был утвержден ISO в качестве стандарта еще в 80-х годах. Этот язык предназначен для создания других языков разметки, он определяет допустимый набор тэгов, их атрибуты и внутреннюю структуру документа. Контроль за правильностью использования дескрипторов осуществляется при помощи специального набора правил, называемых DTD- описаниями(более подробно о DTD мы поговорим чуть позже), которые используются программой клиента при разборе документа. Для каждого класса документов определяется свой набор правил, описывающих грамматику соответствующего языка разметки. С помощью SGML можно описывать структурированные данные, организовывать информацию, содержащуюся в документах, представлять эту информацию в некотором стандартизованном формате. Но в виду некоторой своей сложности, SGML использовался, в основном, для описания синтаксиса других языков(наиболее известным из которых является HTML), и немногие приложения работали с SGML- документами напрямую.

Гораздо более простой и удобный, чем SGML, язык HTML позволяет определять оформление элементов документа и имеет некий ограниченный набор инструкций - тэгов, при помощи которых осуществляется процесс разметки. Инструкции HTML, в первую очередь, предназначены для управления процессом вывода содержимого документа на экране программы-клиента и определяют этим самым способ представления документа, но не его структуру. В качестве элемента гипертекстовой базы данных, описываемой HTML, используется текстовый файл, который может легко передаваться по сети с использованием протокола HTTP. Эта особенность, а также то, что HTML является открытым стандартом и огромное количество пользователей имеет возможность применять возможности этого языка для оформления своих документов, безусловно, повлияли на рост популярности HTML и сделали его сегодня главным механизмом представления информации в Web

Однако современные приложения нуждаются не только в языке представления данных на экране клиента, но и в механизме, позволяющем определять структуру документа, описывать содержащиеся в нем элементы. HTML обладает несложным набором команд и вполне успешно справляется с задачей описания текстовой информации и отображением ее на экране программы просмотра- броузера. Однако сами отображаемые данные никак не связаны с теми тэгами, которые используются для форматирования, поэтому у программ-анализаторов нет возможности использовать тэги HTML для поиска нужных нам фрагментов документа. Т.е. встретив, например, такое описание

<font color="red">rose</font>,

программа просмотра будет знать, каким цветом отобразить текст, содержащийся внутри тэгов <font></font> и, вероятно, отобразит его правильно, но ей абсолютно безразлично, в каком месте документа встретился этот тэг, в какие другие тэги заключен текущий фрагмент, существуют ли вложенные в него фрагменты, правильно ли построены отношения между объектами. Такое "безразличие" к структуре документа приводит к тому, что поиск или анализ информации внутри него ничем не будет отличаться от работы со сплошным, не разбитым на элементы текстовым файлом. А это, как известно, не самый эффективный способ работы с информацией.

Другим существенным недостатком HTML можно назвать ограниченность набора его тэгов. DTD- правила для HTML определяют фиксированный набор дескрипторов и поэтому у разработчика нет возможности вводить собственные, специальные тэги. Хотя время от времени появляются новые расширения языка(на сегодняшний день последней версией HTML является HTML 4.0), но долгий путь их стандартизации, сопровождаемый постоянными разногласиями между основными производителями броузеров делают практически невозможной быструю адаптацию языка, его использование для отображения специализированной информации(например, мультимедийной, математических, химических формул и т.д.).

Подводя итог всему сказанному, можно утверждать, что HTML уже сегодня не удовлетворяет в полной мере требованиям, предъявляемым современными разработчиками к языкам подобного рода. И ему на смену был предложен новый язык гипертекстовой разметки, мощный, гибкий, и, одновременно с этим, удобный язык XML. В чем же заключается его достоинства?

XML (Extensible Markup Language[1]) - это язык разметки, описывающий целый класс объектов данных, называемых XML- документами. Этот язык используется в качестве средства для описания грамматики других языков и контроля за правильностью составления документов. Т.е. сам по себе XML не содержит никаких тэгов, предназначенных для разметки, он просто определяет порядок их создания. Таким образом, если, например, мы считаем, что для обозначения элемента rose в документе необходимо использовать тэг <flower>;, то XML позволяет свободно использовать определяемый нами тэг и мы можем включать в документ фрагменты, подобные следующему:

<flower>rose</flower>

Набор тэгов может быть легко расширен. Если, предположим, мы хотим также указать, что описание цветка должно по смыслу идти внутри описания оранжереи, в которой он цветет, то просто задаем новые тэги и выбираем порядок их следования:

<conservatory>
<flower>rose</flower>
</conservatory>

Если мы хотим посадить туда еще несколько цветочков, то должны внести следующие изменения:

<conservatory>
<flower>rose</flower>
<flower>tulip</flower>
<flower>cactus</flower>
</conservatory>

Как видно, сам процесс создания XML документа очень прост и требует от нас лишь базовых знаний HTML и понимания тех задач, которые мы хотим выполнить, используя XML в качестве языка разметки. Таким образом, у разработчиков появляется уникальная возможность определять собственные команды, позволяющие им наиболее эффективно определять данные, содержащиеся в документе. Автор документа создает его структуру, строит необходимые связи между элементами, используя те команды, которые удовлетворяют его требованиям и добивается такого типа разметки, которое необходимо ему для выполнения операций просмотра, поиска, анализа документа.

Еще одним из очевидных достоинств XML является возможность использования его в качестве универсального языка запросов к хранилищам информации. Сегодня в глубинах W3C находится на рассмотрении рабочий вариант стандарта XML-QL(или XQL), который, возможно, в будущем составит серьезную конкуренцию SQL. Кроме того, XML-документы могут выступать в качестве уникального способа хранения данных, который включает в себя одновременно средства для разбора информации и представления ее на стороне клиента. В этой области одним из перспективных направлений является интеграция Java и XML - технологий, позволяющая использовать мощь обеих технологий при построении машинно-независимых приложений, использующих, кроме того, универсальный формат данных при обмене информации.

XML позволяет также осуществлять контроль за корректностью данных, хранящихся в документах, производить проверки иерархических соотношений внутри документа и устанавливать единый стандарт на структуру документов, содержимым которых могут быть самые различные данные. Это означает, что его можно использовать при построении сложных информационных систем, в которых очень важным является вопрос обмена информацией между различными приложениями, работающими в одной системе. Создавая структуру механизма обмена информации в самом начале работы над проектом, менеджер может избавить себя в будущем от многих проблем, связанных с несовместимостью используемых различными компонентами системы форматов данных.

Также одним из достоинств XML является то, что программы-обработчики XML- документов не сложны и уже сегодня появились и свободно распространяются всевозможные программные продукты, предназначенные для работы с XML-документами. XML поддерживается сегодня в Microsoft Internet Explorer 4/0 и в бэта-версиях IE5. Было заявлено о его поддержке в последующих версиях Netscape Communicator, СУБД Oracle, DB-2, в приложениях MS-Office . Все это дает основания предполагать, что, скорее всего, в ближайшем будущем XML станет основным языком обмена информации для информационных систем, заменив собой, тем самым, HTML. На основе XML уже сегодня созданы такие известные специализированные языки разметки, как SMIL, CDF, MathML, XSL, и список рабочих проектов новых языков, находящихся на рассмотрении W3C, постоянно пополняется.

Как выглядит XML-документ?

Если Вы знакомы с HTML, изучение XML не потребует от вас особых усилий. Хотя XML, безусловно, сильно отличается по своим возможностям и предназначению от языка гипертекстовой разметки, оба эти языка являются подмножествами SGML, и, следовательно, наследуют его базовые принципы.

Структура документа

Простейший XML- документ может выглядеть так, как это показано в Примере 1

<?xml version="1.0"?>
<list_of_items>
<item id="1"><first/>Первый</item>
<item id="2">Второй <sub_item>подпункт 1
</sub_item></item>
<item id="3">Третий</item>
<item id="4"><last/>Последний</item>
</list_of_items>

Обратите внимание на то, что этот документ очень похож на обычную HTML-страницу. Также, как и в HTML, инструкции, заключенные в угловые скобки называются тэгами и служат для разметки основного текста документа. В XML существуют открывающие, закрывающие и пустые тэги (в HTML понятие пустого тэга тоже существует, но специального его обозначения не требуется).

Тело документа XML состоит из элементов разметки (markup) и непосредственно содержимого документа - данных (content). XML - тэги предназначены для определения элементов документа, их атрибутов и других конструкций языка. Более подробно о типах применяемой в документах разметки мы поговорим чуть позже.

Любой XML- документ должен всегда начинаться с инструкции <?xml?>, внутри которой также можно задавать номер версии языка, номер кодовой страницы и другие параметры, необходимые программе-анализатору в процессе разбора документа

Правила создания XML- документа

В общем случае XML- документы должны удовлетворять следующим требованиям:

  • В заголовке документа помещается объявление XML, в котором указывается язык разметки документа, номер его версии и дополнительная информация
  • Каждый открывающий тэг, определяющий некоторую область данных в документе обязательно должен иметь своего закрывающего "напарника", т.е., в отличие от HTML, нельзя опускать закрывающие тэги
  • В XML учитывается регистр символов
  • Все значения атрибутов, используемых в определении тэгов, должны быть заключены в кавычки
  • Вложенность тэгов в XML строго контролируется, поэтому необходимо следить за порядком следования открывающих и закрывающих тэгов
  • Вся информация, располагающаяся между начальным и конечными тэгами, рассматривается в XML как данные и поэтому учитываются все символы форматирования ( т.е. пробелы, переводы строк, табуляции не игнорируются, как в HTML)

Если XML- документ не нарушает приведенные правила, то он называется формально-правильным и все анализаторы, предназначенные для разбора XML- документов, смогут работать с ним корректно.

Однако кроме проверки на формальное соответствие грамматике языка, в документе могут присутствовать средства контроля над содержанием документа, за соблюдением правил, определяющих необходимые соотношений между элементами и формирующих структуру документа. Например, следующий текст, являясь вполне правильным XML- документом, будет абсолютно бессмысленным:

<country><title>Russia</title><city><title>
Novosibirsk</country></title></city>

Для того, чтобы обеспечить проверку корректности XML- документов, необходимо использовать анализаторы, производящие такую проверку и называемые верифицирующими.

На сегодняшний день существует два способа контроля правильности XML- документа: DTD - определения(Document Type Definition) и схемы данных(Semantic Schema). Более подробно об использовании DTD и схемах мы поговорим в следующих разделах. В отличии от SGML, определение DTD- правил в XML не является необходимостью, и это обстоятельство позволяет нам создавать любые XML- документы, не ломая пока голову над весьма непростым синтаксисом DTD.

Конструкции языка

Содержимое XML- документа представляет собой набор элементов, секций CDATA, директив анализатора, комментариев, спецсимволов, текстовых данных. Рассмотрим каждый из них подробней.

Элементы данных

Элемент - это структурная единица XML- документа. Заключая слово rose в в тэги <flower> </flower> , мы определяем непустой элемент, называемый <flower>, содержимым которого является rose. В общем случае в качестве содержимого элементов могут выступать как просто какой-то текст, так и другие, вложенные, элементы документа, секции CDATA, инструкции по обработке, комментарии, - т.е. практически любые части XML- документа.

Любой непустой элемент должен состоять из начального, конечного тэгов и данных, между ними заключенных. Например, следующие фрагменты будут являться элементами:

<flower>rose</flower>
<city>Novosibirsk</city>
,а эти - нет:
<rose>
<flower>
rose

Набором всех элементов, содержащихся в документе, задается его структура и определяются все иерархическое соотношения. Плоская модель данных превращается с использованием элементов в сложную иерархическую систему со множеством возможных связей между элементами. Например, в следующем примере мы описываем месторасположение Новосибирских университетов (указываем, что Новосибирский Университет расположен в городе Новосибирске, который, в свою очередь, находится в России), используя для этого вложенность элементов XML :

<country id="Russia">
 <cities-list>
<city>
<title>Новосибирск</title>
<state>Siberia</state>
<universities-list>
<university id="2">
<title>Новосибирский Государственный
Технический Университет</title>
<noprivate/>
<address URL="www.nstu.ru"/>
<description>очень хороший институт</description>
</university>
<university id="2">
<title>Новосибирский Государственный
Университет</title>
<noprivate/>
<address URL="www.nsu.ru"/>
<description>тоже не плохой</description>
</university>
</universities-list>
</city>
</cities-list>
</country>

Производя в последствии поиск в этом документе, программа клиента будет опираться на информацию, заложенную в его структуру - используя элементы документа. Т.е. если, например, требуется найти нужный университет в нужном городе, используя приведенный фрагмент документа, то необходимо будет просмотреть содержимое конкретного элемента <university>, находящегося внутри конкретного элемента <city>. Поиск при этом, естественно, будет гораздо более эффективен, чем нахождение нужной последовательности по всему документу.

В XML документе, как правило, определяется хотя бы один элемент, называемый корневым и с него программы-анализаторы начинают просмотр документа. В приведенном примере этим элементом является <country>

В некоторых случаях тэги могут изменять и уточнять семантику тех или иных фрагментов документа, по разному определяя одну и ту же информацию и тем самым предоставляя приложению-анализатору этого документа сведения о контексте использования описываемых данных. Например, прочитав фрагмент <city>Holliwood</city> мы можем догадаться, что речь в этой части документа идет о городе, а вот во фрагменте <restaurant>Holliwood</restaurant> - о забегаловке.

В случае, если элемент не имеет содержимого, т.е. нет данных, которые он должен определять, он называется пустым. Примером пустых элементов в HTML могут служить такие тэги HTML, как <br>, <hr>, <img>;. Необходимо только помнить, что начальный и конечные тэги пустого элемента как бы объединяется в один, и надо обязательно ставить косую черту перед закрывающей угловой скобкой (например, <empty/>;)

Комментарии

Комментариями является любая область данных, заключенная между последовательностями символов <!-- и --> Комментарии пропускаются анализатором и поэтому при разборе структуры документа в качестве значащей информации не рассматриваются.

Атрибуты

Если при определении элементов необходимо задать какие-либо параметры, уточняющие его характеристики, то имеется возможность использовать атрибуты эдлемента. Атрибут - это пара "название" = "значение", которую надо задавать при определении элемента в начальном тэге. Пример:

<color RGB="true">#ff08ff</color>
<color RGB="false">white</color>
или
<author id=0>Ivan Petrov</author>

Примером использования атрибутов в HTML является описание элемента <font>:

<font color=”white” name=”Arial”>Black</font>

Cпециальные символы

Для того, чтобы включить в документ символ, используемый для определения каких-либо конструкций языка (например, символ угловой скобки) и не вызвать при этом ошибок в процессе разбора такого документа, нужно использовать его специальный символьный либо числовой идентификатор. Например, &lt; , &gt; &quot; или &#036;(десятичная форма записи), &#x1a (шестнадцатеричная) и т.д. Строковые обозначения спецсиволов могут определяться в XML документе при помощи компонентов (entity), о чем мы еще поговорим немного позже.

Директивы анализатора

Инструкции, предназначенные для анализаторов языка, описываются в XML документе при помощи специальных тэгов - <? и ?>;. Программа клиента использует эти инструкции для управления процессом разбора документа. Наиболее часто инструкции используются при определении типа документа (например, <? Xml version=”1.0”?>) или создании пространства имен[11].

CDATA

Чтобы задать область документа, которую при разборе анализатор будет рассматривать как простой текст, игнорируя любые инструкции и специальные символы, но, в отличии от комментариев, иметь возможность использовать их в приложении, необходимо использовать тэги <![CDATA] и ]]>. Внутри этого блока можно помещать любую информацию, которая может понадобится программе- клиенту для выполнения каких-либо действий (в область CDATA, можно помещать, например, инструкции JavaScript). Естественно, надо следить за тем, чтобы в области, ограниченной этими тэгами не было последовательности символов ]].

Что дальше

Мы рассмотрели в общих чертах основные структурные части XML- документа, правила описания элементов XML и теперь можем создать синтаксически правильный XML- документ, содержащий каким-то образом структурированные данные( более подробный пример приведен в Приложении 1.) Однако при этом почти не коснулись вопросов практического применения XML. Как отображать содержимое XML- документов на Web- страницах, осуществлять контроль над правильностью их составления; существуют ли уже сегодня какие-либо удобные средства для создания, анализа и просмотра таких документов? В следующих разделах мы попробуем ответить на эти вопросы.


Язык XML. Просмотр XML - документов

2. Просмотр XML - документов

Как уже отмечалось, в отличии от HTML, XML никак не определяет способ отображения и использования описываемых с его помощью элементов документа, т.е. программе-анализатору предоставляется возможность самой выбирать нужное оформление. Этого требует принцип независимости определения внутренней структуры документа от способов представления этой информации. Например, задавая в документе элемент <flower>роза</flower>, мы лишь указываем, что rose в данном случае является цветком, но информации о том, как должен выглядеть данный элемент документа на экране клиента и должен ли он отображаться вообще, в таком определении нет.

Для того, чтобы использовать данные, определяемые элементами XML, например, отображать их на экране пользователя, необходимо написать программу-анализатор, которая бы выполняла эти действия. Уже сегодня таких программ появилось достаточное количество и у разработчиков существует возможность выбора наиболее подходящей из них для решения конкретных проблем

Как уже отмечалось ранее, в общем случае, программы- анализаторы можно разделить на две группы: верифицирующие(т.е. использующие DTD- описания для определения корректности документа) и не верифицирующие. Если Вы создаете свой язык и описываете его грамматику на основе DTD, то для анализа документов, написанных на этом языке, безусловно, потребуется программа, проверяющая корректность составления документа. Но так как использование DTD в XML не является обязательным, то любой правильно оформленный документ может быть распознан и разобран программой, предназначенной для анализа XML- документов. В любом случае, используя универсальные XML- анализаторы, Вы можете быть уверенным в том, что если заданные в документе конструкции языка являются синтаксически правильными, то программа-анализатор сможет правильно извлечь определяемые ими элементы документа и передать их прикладной программе, выполняющей необходимые действия по отображению. Т.е. после разбора документа в большинстве случаев, Вам предоставляется объектная модель, отображающая содержимое Вашего документа, и средства, необходимые для работы с ней (прохода по дереву элементов). При этом в некоторых анализаторах способ представления структуры документа основывается на спецификации DOM, описанной в[4]. Поэтому у Вас появляется также возможность использовать строгую иерархическую модель DOM для построения собственных документов.

Если речь идет о способах отображения информации, хранящейся в XML, то необходимо упомянуть разрабатываемый в настоящее время W3C стандарт стилевых таблиц для XML, которые предназначены для описания правил отображения элементов XML. Более подробно мы поговорим об XSL чуть позже.

Использование msxml в IE 4

Если на Вашем компьютере установлен броузер Internet Explorer 4 (или более поздняя версия), то Вы можете использовать встроенный в этот броузер XML- анализатор msxml в своих сценариях, написанных на Java Script ил VBScript,. В настоящий момент существуют две его реализации, - одна предназначена для использования в виде написанного на C++ ActiveX- объекта(реализация на базе COM- технологии) другая, написанная на Java, не зависит от платформы. Оба анализатора не сложны, имеют сравнительно небольшой размер - msxml на C++ занимает около 100k, версия на Java - 127k. Анализатор, написанный на C++, в текущей реализации не поддерживает DTD- правил, более компактный и быстрый, чем его Java-версия. Оба они имеют поддержку иностранных языков, т.е. в составе Internet Explorer C++- анализатор работает со всеми языками, "понимаемыми" броузерами, а анализатор на Java - с теми языками, с которыми может работать виртуальная Java-машина.

Т.к. обе версии разрабатывались параллельно, объектная модель, заложенная в основу каждой из них, внешне схожа, поэтому больших сложностей при переходе от одной версии к другой обычно не возникает.

Рассмотрим основные свойства и методы, доступные JavaScript- сценарию в процессе его выполнения на стороне броузера. В наших примерах мы будем использовать XML- анализатор в сценариях Java Script, т.к. этот способ более понятен и быстрее работает. Полное описание C++ интерфейсов анализатора доступны в документации по Internet Client SDK

Объектная модель XML в Internet Explorer 4.0

Перед тем, как использовать свойства и методы анализатора, его необходимо создать. Делается это при помощи стандартного метода, предназначенного для создания ActiveX- объектов:

var mydoc = new ActiveXObject("msxml");

Если ActiveX- компонент был зарегистрирован на Вашей машине(или у Вас установлен броузер Internet Explorer 4), то в результате выполнения этой функции переменной mydoc будет присвоен объект, имеющий тип msxml, свойства и методы которого используются в дальнейшем для получения доступа к структуре XML- документа.

В Приложении 2 приведен полный текст сценария JavaScrtipt, выводящего на экран броузера Internet Explorer 4.0 XML- документ, созданный в Приложении 1. Вы можете использовать этот пример и комментарии к нему в качестве еще одного средства для более быстрого понимания принципов использования свойств и методов объектов Microsoft XML и создания собственных сценариев.

Объектная модель XML- анализатора Microsoft может быть представлена в виде следующего набора внутренних объектов: XML Document, XML Element и Element Collection. Объект XML Document содержит свойства и методы, необходимые нам для работы с XML- документом в целом. XML Element отвечает за работу с каждым из элементов XML- документа. Element Collection представляет из себя набор элементов, доступ к которым доступен при помощи имени или порядкового номера. В следующих примерах мы рассмотрим каждый из этих объектов подробнее.

Свойства и методы документа(объект XML Document)

URL | Свойство, доступное для записи и чтения. Задает или возвращает URL обрабатываемого документа. В случае изменения этого свойства текущий документ уничтожается и начинается загрузка нового по указанному URL
root | Возвращает корневой элемент XML- документа
charset | Свойство, доступное для записи и чтения.Возвращает или устанавливает название текущее кодировочной таблицы согласно требованиям ISO.
version | Возвращает номер версии XML
doctype | Возвращает содержимое элемента !DOCTYPE
createElement() | Метод, позволяющий создать новый элемент, который будет добавлен в качестве дочернего для текущего элемента дерева. В качестве первого параметра задается тип элемента, в качестве второго - название элемента xml.createElement(0,"new_element")
fileSize | Возвращает размер XML- документа. Это свойство в C++- версии анализатора еще не реализовано
fileModifiedDate | Возвращает дату последнего изменения XML- документа. Это свойство в C++- версии анализатора еще не реализовано
fileUpdatedDate | Возвращает дату последнего обновления XML- документа. Это свойство в C++- версии анализатора еще не реализовано
mimeType | Возвращает MIME-тип(MIME- Multipurpose Internet Mail Extension, RFC 1341).Это свойство в C++- версии анализатора еще не реализовано

Ниже приведен фрагмент JavaScript- сценария, использующего эти методы и свойства для вывода информации о текущем документе:

var xmldoc = new ActiveXObject("msxml");
var xmlsrc = "http://localhost/xml/journal.xml";
xmldoc.URL = xmlsrc;
function viewProperties(){
this.document.writeln('<center><table width=90% >');
this.document.writeln('<tr>');
this.document.writeln('<td align="center"
bgcolor="silver">Document URL</td>
<td align="center">'+xmldoc.URL+'</td></tr>');
this.document.writeln('<tr>');
this.document.writeln('<td align="center"
bgcolor="silver">Document root</td>
<td align="center">'+xmldoc.root+'</td></tr>');
this.document.writeln('<tr>');
this.document.writeln('<td align="center"
bgcolor="silver">Document doctype</td>
<td align="center">'+xmldoc.doctype+'</td></tr>');
this.document.writeln('<tr>');
this.document.writeln('<td align="center"
bgcolor="silver">Document version</td>
<td align="center">'+xmldoc.version+'</td></tr>');
this.document.writeln('<tr>');
this.document.writeln('<td align="center"
bgcolor="silver">Document charset</td>
<td align="center">'+xmldoc.charset+'</td></tr>');
this.document.writeln('</table></center>');
}

Свойства и методы элементов документа

type Возвращает тип элемента. Это свойство может быть использовано для того, чтобы разделить имена тэгов и данные, содержащиеся внутри них. В данной версии анализатора определены следующие типы элементов:
0 - элемент
1 - текст
2 - комментарий
3 - Document
4 - DTD
tagName | Возвращает или устанавливает название тэга(в виде строки с символами, приведенными к верхнему регистру). Названия метатэгов(например, &lt;?xml?&gt;) начинаются с символа ?. Названия тэгов комментариев начинаются с символа !.
text | Возвращает текстовое содержимое элементов и комментариев.
AddChild() Добавление нового дочернего элемента и всех его потомков в текущую ветвь дерева. В качестве первого параметра этой функции необходимо передать объект типа Element, который затем будет помещен в список дочерних элементов. Также необходимо задать индекс нового элемента в списке и в качестве последнего параметра обязательно передать значение -1. Т.к. в данной модели любой элемент в документе может иметь ссылку только на один родительский элемент, при выполнении данной процедуры у добавляемого объекта старая ссылка на родительский элемент теряется. Используя это свойство, можно перемещать элементы из одного XML- документа в другое, но том случае, если у дочерних ссылок перемещаемого элемента существуют внешние ссылки или сами дочерние элементы ссылаются на внешние возможно возникновение ошибки
elem.addChild(elem.children.item(). children.item(0),0,-1)
removeChild() Удаляет дочерний элемент и всех его потомков. Элементы остаются в памяти и могут быть вновь добавлены к дереву при помощи метода addChild().
elem.removeChild(elem.children.item(1))
parent | Возвращает указатель на текущий родительский элемент. Ссылки на родительский элемент имеют все элементы, за исключением корневого.
GetAttribute() Возвращает значение указанного атрибута в виде текстовой строки.
elem.getAttribute("color")
SetAttribute() Устанавливает указанный атрибут и его значение. Прежнее значение атрибута теряется
elem.setAttribute("color","red")
removeAttribute() Уничтожает указанный атрибут
elem.removeAttribute("color")
children | Возвращает ассоциированный список дочерних элементов(коллекцию). Такой список позволяет приложению получать нужные элементы как по названию, так и по порядковому номеру при помощи метода item(). В том случае, если потомков у текущего элемента нет, функция возвратит null

Пример использования

Вот пример использования описанных функций:

<script language="javascript">
<!--
var xmldoc = new ActiveXObject("msxml");
var xmlsrc = "http://localhost/xml/sample.xml";
function parse(root){
 var i=0;
 if(root.type==0){
  this.document.writeln('<UL>Current tag is
  '+root.tagName+'
  (parent is '+root.parent+'). ');
 }else if(root.type==1){
  this.document.writeln('<LI>It is a text of
  '+root.parent.tagName+'
  element: <i>'+root.text+'</i></LI>');
 }else{
  this.document.writeln('<br><br>Error');
 }
  if(root.children!=null){
  this.document.writeln('It consist of
  '+root.children.length+' elements:');
   for(i=0;i<root.children.length;i++){
     parse(root.children.item(i));
   }
  }
else{
     this.document.writeln('</UL>');
  }
}
function viewDocument(){
xmldoc.URL = xmlsrc;
this.document.writeln('<body bgcolor="white">');
this.document.writeln('<p><center><hr width=80%>
XML sample page
<hr width=80%></center><p>');
parse(xmldoc.root);
this.document.writeln('</body>');
}
viewDocument();
//-->
</script>

Как видно из примера, в процессе обработки XML- документа необходимо рекурсивно обходить все ветви создаваемого анализатором дерева, причем, на каждом шаге возможны следующие ситуации:

  • Встретился новый элемент. В этом случае его название(задаваемое тэгом) доступно при помощи свойства tagName, а содержимое - свойством text. У любого непустого элемента существует хотя бы один потомок, представляющий собой содержимое этого элемента(в этом отличие представленной объектной модели msxml от реальной структуры документа - в XML под элементом понимается как название тэга, так и текстовое содержимое его)
  • Встретилось текстовое поле. Это поле может быть либо комментарием, либо просто текстом, содержащемся в текущем элементе

Для обработки потомков текущего элемента используется метод item(), который вызывается в цикле столько раз, сколько потомков у текущего элемента. Обработка каждого дочернего элемента осуществляется вызовом этой же функции, в чем и заключается рекурсия.

Использование ASP

Доступ к свойствам XML- анализатора возможен также из сценариев ASP(Active Server Pages), выполняющихся на стороне сервера. Если при написании ASP-модуля используется язык VBscript, то для создания объекта, представляющего XML- документ, необходимо включить следующее выражение:

Set myxml=Server.CreateObject("msxml")

Однако необходимо учитывать, что в качестве сервера в этом случае надо использовать Web- сервер, поддерживающий ISAPI, и так же на компьютере должны быть установлены или броузер Internet Explorer версии 4 и выше, или зарегистрированный в реестре ActiveX- компонент msxml.

Вот пример использования свойств XML-документа в ASP- программе:

<%
Set myxml=Server.CreateObject("msxml")
myxml.url = "http://localhost/xml/sample1.xml"
url=myxml.url
Set root=myxml.root
version=myxml.version
charset=myxml.charset
%>
<html>
<body bgcolor="white">
<center>
<table width=80%>
<tr>
 <td align="center" bgcolor="silver">URL</td>
 <td align="center"><%=url%></td>
</tr>
<tr>
 <td align="center" bgcolor="silver">Version</td>
 <td align="center"><%=version%></td>
</tr>
<tr>
 <td align="center" bgcolor="silver">Root element</td>
 <td align="center"><%=root.tagName%></td>
</tr>
<tr>
 <td align="center" bgcolor="silver">Charset</td>
 <td align="center"><%=charset%></td>
</tr>
</table>
</body>
</html>

Создавая msxml- объект при помощи CreateObject, мы в дальнейшем вызываем его методы и свойства привычным нам способом. Отличается лишь способ вставки полученной информации в HTML- страницу - она генерируется не на стороне клиента, а приходит к нему в уже готовом виде.

В заключение хотелось бы отметить, что рассмотренные способы работы с XML- документами могут применяться для отображения их элементов на экране броузера. Не всегда они являются наиболее эффективными для форматирования текста - для каждого нового документа с измененной структурой требуются частично или полностью переписывать обработчик(в следующем разделе мы попробуем использовать для этих же целей стилевые таблицы XSL). Однако использование Java Script позволяет уже сегодня разрабатывать реальные Интернет- приложения, использующие встроенный в броузер клиента анализатор в качестве средства для доступа к структурированной информации XML.


Язык XML. Стилевые таблицы XSL

3. Стилевые таблицы XSL

В предыдущем разделе для вывода элементов XML- документа на экран броузера мы применяли Java Script-сценарии Однако, как уже отмечалось, для этих целей предпочтительней использование специально предназначенного для этого средства - стилевых таблиц XSL(Extensible Stylesheet Language).

Стилевыми таблицами (стилевыми листами) принято называть специальные инструкции, управляющие процессом отображения элемента в окне программы-клиента(например, в окне броузера). Предложенные в качестве рекомендация W3C, каскадные стилевые таблицы(CSS- Cascading Style Sheets [7]) уже больше года используются Web- разработчиками для оформления Web- страниц. Поддержка CSS наиболее известными на сегодняшний день броузерами Netscape Navigator(начиная с версии 4.0) и Microsoft Explorer(начиная с версии 3.0), позволила использовать стилевые таблицы для решения самого широкого спектра задач - от оформления домашней странички до создания крупного корпоративного Web-узла. Слово каскадные в определении CSS означает возможность объединения отдельных элементов форматирования путем вложенных описаний стиля. Например, атрибуты текста, заданные в тэге <body>, будут распространяться на вложенные тэги до тех пор, пока в них не встретятся стилевые описания, отменяющие или дополняющие текущие параметры. Таким образом, использование таблиц CSS в HTML было весьма эффективно - отпадала необходимость явного задания тэгов форматирования для каждого из элементов документа.

Являясь очень мощным средством оформления HTML- страниц, CSS- таблицы, тем не менее, не могут применяться в XML-документах, т.к. набор тэгов в этом языке не ограничен и использование статических ссылок на форматируемые объекты документа в этом случае невозможно.

Поэтому для форматирования XML- элементов был разработан новый язык разметки, являющийся подмножеством XML, и специально был предназначен для форматирования XML- элементов.

Некоторые его отличия от CSS:
Во-первых, стилевые таблицы XSL позволяют определять оформление элемента в зависимости от его месторасположения внутри документа, т.е. к двум элементам с одинаковым названием могут применяться различные правила форматирования.
Во-вторых, языком, лежащем в основе XSL, является XML, а это означает, что XSL более гибок, универсален и у разработчиков появляется возможность использования средства для контроля за корректностью составления таких стилевых списков(используя DTD или схемы данных)
В-третьих, таблицы XSL не являются каскадными, подобно CSS, т.к. чрезвычайно сложно обеспечить "каскадируемость" стилевых описаний, или, другими словами, возможность объединения отдельных элементов форматирования путем вложенных описаний стиля, в ситуации, когда структура выходного документа заранее неизвестна и он создается в процессе самого разбора. Однако в XSL существует возможность задавать правила для стилей, при помощи которых можно изменять свойства стилевого оформления, что позволяет использовать довольно сложные приемы форматирования

В настоящий момент язык XSL находится на стадии разработки в W3C[3] и в будущем, видимо, станет частью стандарта XML. Это означает, что использование этого механизма является наиболее перспективным способом оформления XML- документов. В текущем рабочем варианте W3C, XSL рассматривается не только как язык разметки, определяющий стилевые таблицы - в него заложены средства, необходимые для выполнения действий по фильтрации информации, выводимой в окно клиента, поиска элементов, сложного поиска, основанного на зависимостях между элементами и т.д. На сегодняшний день единственным броузером, поддерживающим некоторые из этих возможностей, является бэта-версия Internet Explorer 5.0, однако в самом ближайшем будущем, безусловно, XSL будет использоваться также широко, как сегодня стандартные тэги HTML

В этом разделе мы рассмотрим упрощенную объектную модель XSL- документа, используемую в текущей версии XSL-конвертора Microsoft msxsl, и поэтому информацию, изложенную далее, нельзя считать описанием стандарта языка. Полный рабочий вариант спецификации XSL в последней его редакции доступен на сервере [3].

Все примеры, приводимые далее, могут быть проверены при помощи XSL- конвертора, свободно доступного на странице Mcrosoft [ www.microsoft.com/xml/xsl/ ]

С чего начать

Принцип обработки XML- документов стилевыми таблицами заключается в следующем: при разборе XSL-документа программа-анализатор обрабатывает инструкции этого языка и каждому элементу, найденному в XML- дереве ставит в соответствие набор тэгов, определяющих форматирование этого элемента. Другими словами, мы задаем шаблон форматирования для XML- элементов, причем сам этот шаблон может иметь структуру соответствующего фрагмента XML-документа. Инструкции XSL определяют точное месторасположение элемента XML в дереве, поэтому существует возможность применять различные стили оформления к одинаковым элементам, в зависимости от контекста их использования. На рисунке 3.1 представлена схема, иллюстрирующая этот процесс.

Схема обработки XSL-документа

Рис 1

В общем случае, XSL позволяет автору задавать параметры отображения элемента XML, используя любые языки, предназначенные для форматирования - HTML, RTF и т.д. В этом разделе мы будем использовать в качестве такого языка HTML, т.к. документы, созданные при помощи этого языка разметки могут просматриваться любой подходящей программой просмотра Web-страниц

Структура XSL- таблиц

Рассмотрим основные структурные элементы XSL, используемые, в частности, в конверторе msxsl, для создания оформления XML-документов.

Правила XSL

XSL- документ представляет собой совокупность правил построения, каждое из которых выделено в отдельный блок, ограниченный тэгами <rule> и </rule>;. Правила определяют шаблоны, по которым каждому элементу XML ставится в соответствие последовательность HTML- тэгов, т.е. внутри них содержатся инструкции, определяющие элементы XML- документа и тэги форматирования, применяемые к ним.

Элементы XML, к которым будет применяться форматирование, обозначаются в XSL дескриптором <target-element/>;. Для указания элемента с конкретным названием (название элемента определяется тэгами, его обозначающими), т.е. определения класса элемента, можно использовать атрибут type="<имя_элемента>"

Вот пример простейшего XSL-документа, определяющего форматирование для фрагмента <flower>rose</flower> :

<xsl>
<rule>
<target-element type="flower"/>
<p color="red" font-size="12">
<children/>
</p>
</rule>
</xsl>

Уже на этом примере можно проследить особенность использования стилевых таблиц: в любых правилах при помощи соответствующих элементов декларативно задается область, которая определяет фрагмент XML-документа, как мы чуть позже увидим, программа-анализатор заново проходит все элементы, начиная с текущего, всякий раз, когда в структуре XML- документа обнаруживаются новые вложенные элементы.

Инструкция <target-element> указывает на то, что данное правило определяет элемент. Параметром type="flower" задается название XML-элемента, для которого будет использоваться это правило. Программа-конвертор будет использовать HTML-тэги, помещенные внутри блока <rule></rule> для форматирования XML-элемента, которому "предназначался" текущий блок. В том случае, если для какого-то элемента XML шаблон не определяется, в выходной документ будут добавлены тэги форматирования по умолчанию (например, <DIV></DIV> )

Процесс разбора XSL-правил является рекурсивным, т.е. если у элемента есть дочерние элементы, то программа будет искать определения этих элементов, расположенных "глубже" в дереве документа. Указанием на то, что необходимо повторить процесс разбора XML документа, теперь уже для дочерних элементов, является инструкция <children/>. Дойдя до нее, анализатор выберет из иерархического дерева XML- элементов нужную ветвь и найдет в XSL-шаблонах правила, определяющие форматирование этих нижележащих элементов. В том случае, если вместо <children> мы укажем инструкцию <empty/>;, программа закончит движение по данной ветви и возвратится назад, в родительское правило. При этом текущее правило никакой информации в выходном HTML-документе изменять не будет, т.к. <empty/> в данном случае означает, что содержимое элемента отсутствует.

Если в одном правиле <target-element> используется несколько раз, то инструкции по форматированию будут распространены на все описываемые внутри него XML-элементы, т.е. можно задавать единый шаблон форматирования для нескольких элементов одновременно:

<xsl>
<rule>
<target-element type="item1"/>
<target-element type="item2"/>
<target-element type="item3"/>
<hr>
<children/>
<hr>
</rule>
</xsl>

Ниже приведен пример более сложного XSL- описания, некоторые фрагменты которого будут пояснены позже.

XML-документ:

<?XML Version="1.0"?>
<documents>
<books>
<book id="Book1">
<title>Макроэномические показатели экономики
Римской Империи в период ее расцвета</title>
<author>Иван Петров</author>
<date>21.08.98</date>
</book>
<book id="Book2">
<title>Цветоводство и животноводство.
 Практические советы</title>
<author>Петр Сидоров</author>
<date>10.10.98</date>
</book>
</books>
<articles>
<article id="Article1">
<author>Петр Иванов</author>
<title>Влияние повышения тарифов оплаты за телефон
на продолжительность жизни населения</title>
<date>12.09.98</date>
</article>
</articles>
</documents>

Содержимое XSL-документа:

<xsl>
  <rule>
    <root/>
    <HTML>
    <BODY bgcolor="white">
    <center><hr width="80%"/><b>Library</b>
    <hr width="80%"/><br/>
    <table width="80%" border="2">
    <children/>
    </table></center>
    </BODY>
    </HTML>
  </rule>
    <rule>
     <element type="book">
        <target-element type="author"/>
             </element>
            <td align="center">
        <p color="red" font-size="14"> <b>
<children/>
</b></p></td>
    </rule>
    <rule>
     <element type="article">
        <target-element type="author"/>
         </element>
            <td align="center">
        <p color="red" font-size="14"
 font-style="italic"><children/></p></td>
    </rule>
    <rule>
        <target-element type="book"/>
            <tr><children/></tr>
    </rule>
    <rule>
        <target-element type="article"/>
            <tr><children/></tr>
    </rule>
    <rule>
        <target-element/>
            <td align="center"><p><children/></p></td>
    </rule>
    <rule>
        <target-element type="books"/>
            <tr><td colspan="3" bgcolor="silver"
             >Books</td></tr>
            <children/>
    </rule>
    <rule>
        <target-element type="articles"/>
            <tr><td colspan="3" bgcolor="silver"
             >Articles</td></tr>
            <children/>
    </rule>
</xsl>

Корневое правило

Разбор любого XSL- документа всегда начинается с правила для корневого элемента, в котором определяется область всего разбираемого XML документа и поэтому тэги форматирования, помещенные сюда, будут действовать на весь документ в целом. Для обозначения корневого правила необходимо включить в него элемент <root/>;. Например, для того, чтобы задать тэг <body> для формируемой нами выходной HTML- страницы, мы можем использовать следующий фрагмент:

<xsl>
<rule>
<root/>
<html><head><title> Flowers</title></head>
<body bgcolor="white" link="blue" alink="darkblue"
vlink="darckblue">
<children/>
</body>
</html>
</rule>
</xsl>

В этом примере при помощи инструкций <root/> и <children/> мы определили ряд начальных и конечных HTML-тэгов для нашей страницы, между которыми затем в процессе рекурсивного обхода XSL- анализатора будут помещены остальные элемента документа.

В том случае, если мы не определяем правило для корневого элемента, разбор документа начнется с первого правила с инструкцией <target-element>(для оформления же заголовка документа будет использован образец форматирования, применяющийся по умолчанию).

Отношения между элементами

Дочерние элементы в XML-документе всегда находятся внутри области, определяемой тэгами родительского по отношению к ним элемента. Для того, чтобы точно указать месторасположение обрабатываемого элемента в дереве XML, в XSL используется дополнительный тэг <element>;. При помощи него можно указать, какие элементы должны предшествовать текущему, а какие - следовать после него. Например, в следующем фрагменте определяется, что форматирование элемента <title> будет зависеть от его месторасположения внутри XML-документа:

<xsl>
<rule>
<element type="journal">
        <target-element type="title"/>
             </element>
<center>
<hr width=80%>
<children/>
<hr width=80%>
</center>
</rule>
<rule>
<element type="article">
        <target-element type="title"/>
             </element>
<td align="center"><p color="blue" font-size="14"
 font-style="italic"><children/>
</td>
</rule>
</xsl>

Как видно из примера, если в XML- документе будет найден элемент <title>, являющийся дочерним по отношению к элементу <article> (название статьи), то его форматирование будет несколько отличаться от элемента <title>, расположенного внутри тэгов <journal>

Приоритеты правил

В том случае, если внутри XSL- документа встречается несколько правил для одного и того же элемента, то msxsl будет использовать то из них, которое более точно определяет позицию данного элемента. Т.е. если XSL- документ содержит следующие правила:

<rule>
<element type="journal">
        <target-element type="title"/>
             </element>
<center>
<hr width=80%>
<children/>
<hr width=80%>
</center>
</rule>
<rule>
    <target-element type="title"/>
<b>
<children/>
</b>
</rule>

, то при использовании этой стилевой таблицы в случае, когда элемент <title> является потомком <journal>, к нему будет применено первое правило. Для любых же других элементов будет действовать правило без тэга <element>

В общем случае приоритет правил определяется следующим образом (в порядке убывания приоритета):

  • правила, помеченные специальным тэгом <importance>
  • правила с наибольшим значением атрибута id, если он определен
  • правила с наибольшим значением атрибута class, если он определен
  • правила, имеющие наибольшую вложенность, определяемую тэгом <element>
  • правила, использующие атрибут type совместно с <target-element>
  • правила, в которых отсутствует атрибут type в <target-element> или <element>
  • правила с более высоким приоритетом, задаваемым атрибутом priority тэга <rule>
  • правила с наибольшим значением квалификаторов <only>, <position>, <attribute>

Использование атрибутов элементов

Применительно к <target-element> и <element> в правилах также могут использоваться специальные элементы <attribute>;, при помощи которых можно уточнять характеристики обрабатываемых элементов, задавая различные инструкции форматирования для одинаковых элементов с различными атрибутами. Указываемые в <attribute> параметры name и value определяют атрибут XML, который должен иметь текущий обрабатываемый элемент. Например, в следующем фрагменте все элементы с атрибутом free_lance ="true" будут выделены в выходном HTML- документе серым цветом

<rule>
<target-element type="author">
 <attribute name="free_lance" value="true">
    </target-element>
<p color="gray">
<children/>
</p>
 </rule>

Фильтрация элементов

Одним из самых мощных средств XSL является возможность сортировки и выборки элементов, выделяемых из общего дерева элементов документа. Для этого используется элемент <select-elements>;, который заменяет <children/> в правилах, определяя те элементы, которые следует обработать в процессе рекурсивного обхода. Например, в следующем примере будут обработаны только элементы <author>:

<rule>
<target-element type=" staff"/>
<div>
<select-elements>
<target-element type = "author"/>
</select-elements>
</div>
</rule>

Элемент <select-elements> сам по себе не определяет шаблон форматирования, он лишь управляет работой анализатора, обозначая, подобно <children/>, "нижележащие" элементы. В приведенном примере элемент <author> должен быть расположен внутри элемента <staff>

Для того, чтобы в шаблоне выделить не только собственные дочерние элементы, но и дочерние элементы потомков, т.е. использовать несколько уровней вложенности, необходимо задать параметр from = "descendants". Если параметр имеет значение "children", что указывает на то, что выбор должен производится из списка собственных дочерних элементов, то атрибут from может опускаться, т.к. "children" является значением по умолчанию.

Правила стилей

В отличие от CSS, в XSL невозможно использование каскадных стилевых определений(т.е. нельзя использовать несколько правил для определения стиля одного того же элемента), т.к. такие ограничения вводит рекурсивный алгоритм работы программы - анализатора. Однако использование правил определения стиля(Style Rules) элемента позволяет каким-то образом скомпенсировать этот недостаток.

Для определения правила стилевого оформления необходимо воспользоваться элементом <style-rule>;, который используется точно также, как и <rule>, но инструкции, содержащиеся в нем, никак не влияют на структуру выходного документа. Поэтому все команды внутри этого правила должны описываться в рамках элемента <apply>. Вот как будет выглядеть, например, определение стиля для элемента <flower>rose</flower>;:

<style-rule>
<target-element type ="flower"/>
<apply color ="red"/>
</style-rule>
<rule>
<target-element type="flower"/>
<div font-style="italic";>
<children/>
</div>
</rule>

Если бы мы не определили правила <rule>, то в выходной документ не было бы помещено никакой информации, т.к. элемент <style-rule> только определяет параметры стилевого оформления, не предпринимая никаких других действий.

Также надо учитывать, что XSL- анализатор использует CSS для определения задаваемого правилами <style-rule> стиля в выходном HTML-документе, тем самым предоставляя нам возможность использования этого мощного средства при оформлении HTML-страниц После обработки приведенного в примере фрагмента в выходной документ будут помещены следующие элементы:

<div style= "font-style: italic; color : red;"
>rose</div>
Еще один пример:

Стили в формате CSS:

issue {font-weight=bold; color=blue;}
.new {font-weight=bold; color=red;}

Фрагмент XSL- документа, позволяющего использовать подобные стилевые определения:

<style-rule>
<target-element type ="issue"/>
<apply color ="blue"/>
</style-rule>
<style-rule>
<target-element type ="issue">
<attribute name ="class" value ="new" />
</target-element>
<apply color ="red"/>
</style-rule>
<rule>
<target-element type="issue"/>
<div>
<children/>
</div>
</rule>

Сценарии

Сценарии могут использоваться в документах XSL точно также, как и в HTML. Кроме того, сценарии, содержащиеся внутри XSL-документа и запускаемые броузером в процессе обработки документа могут динамически создавать HTML-документы, извлекая необходимую для этого информацию непосредственно из элементов XSL-документа.

Для написания сценариев XSL использует специальный скриптовый язык - ECMAScript. Однако в msxsl для этих целей можно применять Microsoft JavaScript,- язык, который объединил в себе некоторые элементы стандартного JavaScript и ECMAScript.

Вычисление выражений

Наиболее простым примером использования сценариев в XSL -документе является вычисление значений параметров описываемых элементов. Для этого надо просто поставить знак равенства в качестве первого символа параметра, что заставит XSL-процессор вычислить значение выражения(синтаксис этого выражения должен отвечать требованиям JavaScript). Например, после разбора этого правила:

<rule>
<target-element type="header">
<hr width="=100-20+'%'">
<children/>
<hr width="80%">
</rule>

, в выходном документе окажутся следующие инструкции:

<hr width=80%>
...
<hr width=80%>

Очень часто в правилах XSL необходимо использовать атрибуты описываемого в них элемента. Для этого мы должны воспользоваться методом getAttribute(), описанным в объектной модели XML (мы рассматриваем объектную модель XML-документов, предложенную Microsoft, список этих функций приведен в конце раздела). Т.к. каждому элементу XSL доступен указатель на соответствующий ему объект, сценарий может обращаться к внутренним функциям и свойствам этих элементов, при помощи которых и осуществляются необходимые действия.

В следующем фрагменте XML- документа определяется элемент <article>, в котором атрибут src используется для задания адреса файла, содержащего текст статьи.

<articles>
<article src="http://server/pages/article.html">
Bugs report</article>
</articles>

Для того, чтобы использовать этот атрибут в выходном HTML-документе, необходимо определить следующее правило:

<rule>
<target-element type="article">
<a href='=getAttribute("src")'>
<children/>
</a>
</rule>

После обработки этого фрагмента в выходной документ будет помещен элемент:

<a href="http://server/pages/article.html"
>Bugs report</a>

Выполнение инструкций

Другим способом помещения в выходной HTML- документ информации, являющейся результатом выполнения каких-либо операций JavaScript – сценариев является использовнаие инструкции <eval>;:

<rule>
<element type="articles">
<target-element type="article">
</element>
<tr><td><eval>childNumber(this)</eval></td><td>
<children/>
</td><tr>
</rule>

Метод childNumber в данном случае возвращает текущий номер дочернего элемента.

Определение функций и глобальных переменных

Аналогично тэгу <SCRIPT> в HTML, элемент <define-script> содержит функции и определения глобальных переменных. Обычно в XSL-документе определяется один элемент <define-script>, расположенный в самом начале.

<xsl>
<define-script>
<![CDATA[
var fontSize=12;
function getColor(elem){
return elem.children.item("color",0).text;
        // Возвращает содержимое дочернего элемента <color>
}]]>
</define-script>
<rule>
<target-element type = "flower">
<div background-color="=getColor(this)";
font-size="=fontSize">
<children/>
</div>
</rule>
</xsl>

Если применить эти правила к такому фрагменту XML- документу:

<xml>
<flower>
rose
<color>red</color>
</flower>
, то на выходе HTML -документ будет содержать следующие элементы:
<div background-color="red"; font-size="12">

Необходимо отметить, что использование глобальных переменных в некоторых случаях может приводить к серьезным ошибкам, вызванным попытками одновременного к ним доступа. Поэтому рекомендуется использовать такие переменные только в качестве констант.

Использование Java Script для HTML

Создавая шаблон HTML-документа, Вы можете указывать в нем практически любые элементы HTML, в том числе и блоки <SCRIPT>, внутри которых можно задавать любые конструкции Java Script, используя для этого область CDATA:

<xsl>
  <rule>
    <root/>
    <HTML>
      <HEAD>
        <SCRIPT LANGUAGE="JSCRIPT"><![CDATA[
var ie4=((navigator.appName=="Microsoft Internet
Explorer")&&(parseInt(navigator.appVersion)  >= 4 ));
       function msover(){
if (ie4){
          event.srcElement.style.color="red";
          event.srcElement.style.cursor = "hand";
      }
}
       function msout(){
    if (ie4){
          event.srcElement.style.color="black";
          event.srcElement.style.cursor = "auto";
      }
}        ]]></SCRIPT>
      </HEAD>
      <BODY>
        <children/>
      </BODY>
    </HTML>
  </rule>
  <rule>
    <target-element type="chapter"/>
    <DIV id='=tagName + formatNumber
    (childNumber(this),"1")'
         background-color="marron"
    onmouseover='="msover("+ tagName +
     formatNumber(childNumber(this),"1")+")"'
    onmouseout='="msout("+ tagName +
     formatNumber(childNumber(this),"1")+")"'
      <children/>
    </DIV>
  </rule>
</xsl>
Если использовать эти правила для следующего XML- документа:
<contents>
  <chapter>Part1</chapter>
  <chapter>Part2</chapter>
  <chapter>Part3</chapter>
</contents>
то в результате мы получим такой HTML-файл:
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JSCRIPT">
var ie4=((navigator.appName=="Microsoft Internet
Explorer")&&(parseInt(navigator.appVersion)  >= 4 ));
function msover(){
if (ie4){
          event.srcElement.style.color="red";
          event.srcElement.style.cursor = "hand";
     }
}
function msout(){
    if (ie4){
          event.srcElement.style.color="black";
          event.srcElement.style.cursor = "auto";
      }
  }
</SCRIPT>
</HEAD>
<BODY>
<DIV id=''chapter1" onmouseover="msover("chapter1")"'
              onmouseout="msout("chapter1")"'
      Part 1
       </DIV>
<DIV id=''chapter2" onmouseover="msover("chapter2")"'
              onmouseout="msout("chapter2")"'
      Part 2
       </DIV>
<DIV id=''chapter3" onmouseover="msover("chapter3")"'
              onmouseout="msout("chapter3")"'
      Part 3
       </DIV>
</BODY>
</HTML>

Встроенные функции XSL

В завершении приведем список внутренних функций, которые можно использовать в JavaScript –сценариях, предназначенных для анализатора msxsl:
Ancestor(elementType, elem) | Возвращает для текущего элемента ссылку на ближайший родительский элемент заданного типа. Если такого элемента нет или текущий элемент пустой, то возвращает null
ChildNumber(elem) | Возвращает индекс текущего элемента в списке других дочерних элементов данного типа.
AncestorChildNumber() | Возвращает номер ближайшего предка текущего элемента или null, если такового не существует
path(xsl) | Возвращает массив, содержащий "путь" к текущему элементу - в каждую ячейку этого массива помещается цифровое значение, указывающее на количество элементов одинакового типа, находящихся на текущем уровне вложенности. Первым значением этого массива будет представлен корневой элемент, последним - текущий. Размер массива определяет глубину вложенности текущего элемента.
HierarchicalNumberRecursive (elementType,elem) | Метод, похожий на метод path, но возвращает только дочерние элементы
FormatNumber(n,format) | Возвращает строку - символьное представление номера(т.е. "один", "два" и т.д.). Возможно определение следующих форматов: "1" - 0,1,2,.. "01" - 01,02,03,... "a" - a,b,c,..z, aa, ab,..zz "A" - A,..,Z,AA, .. ZZ
FormatNumberList(list,format,separator) | Возвращает строку, представляющую список, элементами которого являются символьные представления чисел


Documents Type Definitions (DTD)

4. Documents Type Definitions (DTD)

В XML- документах DTD определяет набор действительных элементов, идентифицирует элементы, которые могут находиться в других элементах, и определяет действительные атрибуты для каждого из них. Синтаксис DTD весьма своеобразен и от автора-разработчика требуются дополнительные усилия при создании таких документов(сложность DTD является одной из причин того, что использование SGML, требующего определение DTD для любого документа, не получило столь широкого распространения как, например, HTML). Как уже отмечалось, в XML использовать DTD не обязательно - документы, созданные без этих правил, будут правильно обрабатываться программой-анализатором, если они удовлетворяют основным требованиям синтаксиса XML. Однако контроль за типами элементов и корректностью отношений между ними в этом случае будет полностью возлагаться на автора документа. До тех пор, пока грамматика нашего нового языка не описана, его сможем использовать только мы, и для этого мы будем вынуждены применять специально разработанное программное обеспечение, а не универсальные программы-анализаторы..

В DTD для XML используются следующие типы правил: правила для элементов и их атрибутов, описания категорий(макроопределений), описание форматов бинарных данных. Все они описывают основные конструкции языка - элементы, атрибуты, символьные константы внешние файлы бинарных данных.

Для того, чтобы использовать DTD в нашем документе, мы можем или описать его во внешнем файле и при описании DTD просто указать ссылку на этот файл или же непосредственно внутри самого документа выделить область, в которой определить нужные правила. В первом случае в документе указывается имя файла, содержащего DTD- описания:

<?xml version="1.0" standalone="yes" ?>
<! DOCTYPE journal SYSTEM "journal.dtd">
...

Внутри же документа DTD- декларации включаются следующим образом:

...
<! DOCTYPE journal [
<!ELEMENT journal (contacts, issues, authors)>
...
]>
...

В том случае, если используются одновременно внутренние и внешние описания, то программой-анализатором будут сначала рассматриваться внутренние, т.е. их приоритет выше. При проверке документа XML- процессор в первую очередь ищет DTD внутри документа. Если правила внутри документа не определены и не задан атрибут standalone ="yes" , то программа загрузит указанный внешний файл и правила, находящиеся в нем, будут считаны оттуда. Если же атрибут standalone имеет значение "yes", то использование внешних DTD описаний будет запрещено.

Определение элемента

Элемент в DTD определяется с помощью дескриптора !ELEMENT, в котором указывается название элемента и структура его содержимого.

Например, для элемента <flower> можно определить следующее правило:

<!ELEMENT flower  PCDATA>

Ключевое слово ELEMENT указывает, что данной инструкцией будет описываться элемент XML. Внутри этой инструкции задается название элемента(flower) и тип его содержимого.

В определении элемента мы указываем сначала название элемента(flower), а затем его модель содержимого - определяем, какие другие элементы или типы данных могут встречаться внутри него. В данном случае содержимое элемента flower будет определяться при помощи специального маркера PCDATA( что означает parseable character data - любая информация, с которой может работать программа-анализатор). Существует еще две инструкции, определяющие тип содержимого: EMPTY,ANY. Первая указывает на то, что элемент должен быть пустым(например, <red/>), вторая - на то, что содержимое элемента специально не описывается.

Последовательность дочерних для текущего элемента объектов задается в виде списка разделенных запятыми названий элементов. При этом для того, чтобы указать количество повторений включений этих элементов могут использоваться символы +,*, ? :

<!ELEMENT issue (title, author+, table-of-contents?)>

В этом примере указывается, что внутри элемента <issue> должны быть определены элементы title, author и table-of-contents, причем элемент title является обязательным элементом и может встречаться лишь однажды, элемент author может встречаться несколько раз, а элемент table-of-contents является опциональным, т.е. может отсутствовать. В том случае, если существует несколько возможных вариантов содержимого определяемого элемента, их следует разделять при помощи символа "|" :

<!ELEMENT flower (PCDATA | title )*>

Символ * в этом примере указывает на то, что определяемая последовательность внутренних элементов может быть повторена несколько раз или же совсем не использоваться.

Если в определении элемента указывается "смешанное" содержимое, т.е. текстовые данные или набор элементов, то необходимо сначала указать PCDATA, а затем разделенный символом "|" список элементов.

Пример корректного XML- документа:

<?xml version="1.0"?>
<! DOCTYPE journal [
<!ELEMENT contacts (address, tel+, email?)>
<!ELEMENT address (street, appt)>
<!ELEMENT street PCDATA>
<!ELEMENT appt (PCDATA | EMPTY)*>
<!ELEMENT tel PCDATA>
<!ELEMENT email PCDATA>
]>
...
<contacts>
<address>
<street>Marks avenue</street>
<appt id="4">
</address>
<tel>12-12-12</tel>
<tel>46-23-62</tel>
<email>info@j.com</email>
</contacts>

Определение атрибутов

Списки атрибутов элемента определяются с помощью ключевого слова !ATTLIST. Внутри него задаются названия атрибутов, типы их значений и дополнительные параметры. Например, для элемента <article> могут быть определены следующие атрибуты:

<!ATTLIST article
id ID #REQUIRED
about CDATA #IMPLIED
type (actual | review | teach )  'actual' ''
>

В данном примере для элемента article определяются три атрибута: id, about и type, которые имеют типы ID(идентификатор), CDATA и список возможных значений соответственно. Всего существует шесть возможных типов значений атрибута:

  • CDATA - содержимым документа могут быть любые символьные данные
  • ID - определяет уникальный идентификатор элемента в документе
  • IDREF( IDREFS )- указывает, что значением атрибута должно выступать название(или несколько таких названий, разделенных пробелами во втором случае) уникального идентификатора определенного в этом документе элемента
  • ENTITY( ENTITIES) - значение атрибута должно быть названием(или списком названий, если используется ENTITIES) компонента (макроопределения), определенного в документе
  • NMTOKEN (NMTOKENS) - содержимым элемента может быть только одно отдельное слово(т.е. этот параметр является ограниченным вариантом CDATA)
  • Список допустимых значений - определяется список значений, которые может иметь данный атрибут.

Также в определении атрибута можно использовать следующие параметры:

  • #REQUIRED - определяет обязательный атрибут, который должен быть задан во всех элементах данного типа
  • #IMPLIED - атрибут не является обязательным
  • #FIXED "значение" - указывает, что атрибут должен иметь только указанное значение, однако само определение атрибута не является обязательным, но в процессе разбора его значение в любом случае будет передано программе-анализатору
  • Значение - задает значение атрибута по умолчанию

Определение компонентов(макроопределений)

Компонент (entity) представляет собой определения, содержимое которых может быть повтор.но использовано в документе . В других языках программирования подобные элементы называются макроопределениями. Создаются DTD- компоненты при помощи инструкции !ENTITY:

<!ENTITY hello ' Мы рады приветствовать Вас!' >

Программа-анализатор, просматривая в первую очередь содержимое области DTD- определений, обработает эту инструкцию и при дальнейшем разборе документа будет использовать содержимое DTD- компонента в том месте, где будет встречаться его название. Т.е. теперь в документе мы можем использовать выражение &hello; , которое будет заменено на строчку "Мы рады приветствовать Вас"

В общем случае, внутри DTD можно задать три типа макроопределений:

Внутренние макроопределения - предназначены для определения строковой константы, с их помощью можно организовывать ссылки на часто изменяемую информацию, делая документ более читабельным. Внутренние компоненты включаются в документ при помощи амперсанта &

В XML существует пять предустановленных внутренних символьных констант:

  • &lt; - символ "<"
  • &gt; - символ ">"
  • &amp; - символ "&"
  • &apos; - символ апострофа "'"
  • &quot; - символ двойной кавычки """

Внешние макроопределения - указывают на содержимое внешнего файла, причем этим содержимым могут быть как текстовые, так и двоичные данные. В первом случае в месте использования макроса будут вставлены текстовые строки, во втором - бинарные данные, которые анализатором не рассматриваются и используются внешними программами

<!ENTITY logotype SYSTEM "/image.gif" NDATA GIF87A>

Макроопределения правил - макроопределения параметров могут использоваться только внутри области DTD и обозначаются специальным символом %, вставляемым перед названием макроса. При этом содержимое компонента будет помещено непосредственно в текст DTD- правила

Например, для следующего фрагмента документа:

<!ELEMENT name (PCDATA)>
<!ELEMENT title (PCDATA | name)*>
<!ELEMENT author (PCDATA | name)*>
<!ELEMENT article (title, author)*>
<!ELEMENT book (title, author)*>
<!ELEMENT bookstore (book | article)*>
<!ELEMENT bookshelf (book | article)*>

можно использовать более короткую форму записи:

<!ELEMENT name (PCDATA)>
<! ENTITY %names 'PCDATA | name'>
<!ELEMENT article (%names;)*>
<!ELEMENT book (%names;)*>
<!ENTITY %content 'book | article'>
<!ELEMENT bookstore (%content;)*>
<!ELEMENT bookshelf (%content;)*>

Макроопределения часто используются для описания параметров в правилах атрибутов. В этом случае появляется возможность использовать одинаковые определения атрибутов для различных элементов:

<!ENTITY %itemattr 'id ID #IMPLIED src CDATA'>
<!ENTITY %bookattr "ISDN ID #IMPLIED type CDATA'>
<!ENTITY %artattr " size CDATA'>
<!ELEMENT book (title, author,content)*>
<!ATTLIST book %itemattr  %bookattr;>
<!ELEMENT article (title, author, content)*>
<!ATTLIST article %itemattr  %artattr;>

Типизация данных

Довольно часто при создании XML- элемента разработчику требуется определить, данные какого типа могут использоваться в качестве его содержимого. Т.е. если мы определяем элемент <last-modified>10.10.98</last-modified>, то хотим быть уверенными, что в документе в этом месте будет находиться строка, представляющая собой дату, а не число или произвольную последовательность символов. Используя типизацию данных, можно создавать элементы, значения которых могут использоваться, например, в качестве параметров SQL- запросов. Программа клиент в этом случае должна знать, к какому типу данных относится текущее значение элемента и в случае соответствия формирует SQL-запрос.

Если в качестве программы на стороне клиента используется верифицирующий XML-процессор, то информацию о типе можно передавать при помощи специально созданного для этого атрибута элемента, имеющего соответствующее DTD- определение. В процессе разбора программа-анализатор передаст значение этого атрибута клиентскому приложению, которое сможет использовать эту информацию должным образом. Например, чтобы указать, что содержимое элемента должно быть длинным целым, можно использовать следующее DTD- определение:

<!ELEMENT counter (PCDATA)>
<!ATTLIST counter data_long CDATA #FIXED "LONG">

Задав атрибуту значение по умолчанию LONG и определив его как FIXED, мы позволили тем самым программе-клиенту получить необходимую информацию о типе содержимого данного элемента, и теперь она может самостоятельно определить соответствие типа этого содержимого указанному в DTD- определении .

Вот пример XML- документа, в котором определяются и используются несколько элементов с различными типами данных:

<!ELEMENT price (PCDATA)>
<!ATTLIST price data_currency CDATA #FIXED "CURRENCY">
<!ELEMENT rooms_num (PCDATA)>
<!ATTLIST rooms_num data_byte CDATA #FIXED "BYTE">
<!ELEMENT floor (PCDATA)>
<!ATTLIST floor data_byte CDATA #FIXED "INTEGER">
<!ELEMENT living_space (PCDATA)>
<!ATTLIST living_space data_float CDATA #FIXED "FLOAT">
<!ELEMENT counter (PCDATA)>
<!ATTLIST counter data_long CDATA #FIXED "LONG">
<!ELEMENT is_tel (PCDATA)>
<!ATTLIST is_tel data_bool CDATA #FIXED "BOOL">
<!ELEMENT house (rooms_num, floor,living_space,
is_tel, counter, price)>
<!ATTLIST house id ID  #REQUIED>
...
<house id="0">
<rooms_num>5</rooms_num>
<floor>2</floor>
<living_space>32.5</living_space>
<is_tel>true</is_tel>
<counter>18346</counter>
<price>34 р. 28 к.</price>
</house>
...

Как видно из примера, механизм создания элементов документа при этом нисколько не изменился. Все необходимая для проверки типов данных информация заложена в определения элементов внутри блока DTD.

В заключении хотелось бы отметить, что DTD предоставляет нам весьма удобный механизм осуществления контроля за содержимым документа. На сегодняшний день, практически все программы просмотра документов Интернет используют DTD-правила. Однако это далеко не единственный способ проверки корректности документа. В настоящий момент в W3 консорциуме находится на рассмотрении новый стандарт языка описания структуры документов, называемый схемами данных. Следующий раздел посвящен работе с ними.


Язык XML. Схемы данных

5. Схемы данных

Схемы данных (Schemas) являются альтернативным способом создания правил построения XML-документов. По сравнению с DTD, схемы обладают более мощными средствами для определения сложных структур данных, обеспечивают более понятный способ описания грамматики языка, способны легко модернизироваться и расширяться. Безусловным достоинством схем является также то, что они позволяют описывать правила для XML- документа средствами самого же XML.

Однако это не означает, что схемы могут полностью заменить DTD- описания - этот способ определения грамматики языка используется сейчас практическими всеми верифицирующими анализаторами XML и, более того, сами схемы, как обычные XML- элементы, тоже описываются DTD. Но серьезные возможности нового языка и его относительная простота, безусловно, дают основания утверждать, что будущий стандарт найдет широкое применение в качестве удобного и эффективного средства проверки корректности составления документов.

В настоящее время в W3 консорциуме идет работа над первой спецификацией схем данных, рабочий ее вариант сейчас доступен на сервере [36]. В этом разделе мы рассмотрим основные возможности схем данных, попытаемся использовать их для проверки корректности ранее описываемых XML- документов.

Как это выглядит

Внешне документы схем очень похожи на те документы XML, с которыми мы уже встречались в предыдущих разделах. Мы размечаем документ при помощи специальных элементов, выполняющих в схемах роль инструкций. Эти инструкции составляют набор правил, используя которые, программа-клиент будет делать вывод о том, корректен документ или нет. Схема данных, например, может выглядеть следующем образом:

<schema id="OurSchema">
<elementType id="#title">
<string/>
</elementType>
<elementType id="photo">
<element type="#title">
<attribute name="src"/>
</elementType>
<elementType id="gallery">
<element type="#photo">
</elementType>
</schema>

Если мы включим приведенные правила внутрь XML- документа, программа-клиент сможет использовать их для проверки. Т.е. она теперь сможет определить, что правильным будет являться следующий фрагмент:

<gallery>
<photo id="1"><title>My computer</title></photo>
<photo id="2"><title>My family</title></photo>
<photo id="3"><title>My dog</title></photo>
</gallery>

, а некорректным этот:

<gallery>
<photo id="1"/>
<photo index="2"><title>My family</title></photo>
<photo index="3"><title> My dog </title><dogname>
Sharik</dogname></photo>
</gallery>

Все конструкции языка схем описываются правилами "XML DTD for XML-Data-Schema". Этот документ вы можете найти среди другой официальной документации, доступной на сервере W3 - консорциума. В этой статье мы коснемся лишь основных приемов для работы со схемами данных. Ссылки на более подробные источники приведены в конце.

Область схемы данных

Создавая схемы данных, мы определяем в документе специальный элемент, <schema>;, внутри которого содержатся описания правил:

<schema id="OurSchema">
<!-- последовательность инструкций -->
</schema>

Если использовать отдельное пространство имен, то полный XML-документ, содержащий в себе схему данных, будет выглядеть следующим образом:

<?XML version='1.0' ?>
<?xml:namespace
href="http://www.mrcpk.nstu.ru/schemas/" as="s"/?>
<s:schema id="OurSchema">
<!-- последовательность инструкций -->
</s:schema>

Описание элементов

Для определения класса элемента, к которому в дальнейшем будут применяться инструкции, описывающие его содержимое и структуру, предназначен специальный элемент схемы elementType,

<elementType id="issue">
<descript>Элемент содержит информацию об очередном
 выпуске журнала</descript>
</elementType>

Название элемента задается атрибутом id . Все дальнейшие инструкции, которые относятся к описываемому классу, определяют его внутреннюю структуру и набор допустимых данных, содержатся внутри блока, заданного тэгами <elementType> и </elementType>. Мы рассмотрим эти инструкции чуть позже.

Как видно из примера, при определении класса элемента, можно также использовать комментарии к нему, которые заключаются в тэги <descript></descript>

Атрибуты элемента

Для того, чтобы в описании элемента определить его атрибуты и описать свойства этих атрибутов мы должны использовать элемент attribute:

<elementType id="photo">
<attribute name="src"/>
<empty/>
</elementType>

В данном примере элементу <photo> определяется атрибут src, значением которого может быть любая последовательность разрешенных символов:

<photo src="0"/>
<photo src="some text">

Подобно DTD, схемы данных позволяют устанавливать ограничения на значения и способ использования атрибутов. Для этого в дескрипторе <attribute> необходимо использовать параметр atttype.

Например, если мы хотим указать, что значение атрибута должно использоваться программой-анализатором как уникальный идентификатор, то нам необходимо создать следующее правило:

<elementType id="bouquet">
<attribute name="id" atttype="ID">
</elementType>

Если же требуется задать список возможных значений атрибута, то пример будет выглядеть следующим образом:

<attribute name="flower" atttype="ENUMERATION"
values="red green blue" default="red">

Для приведенных примеров корректным будет являться следующий фрагмент XML-документа:

<bouquet id="0">
<flower color="red">rose</flower>
<flower color="green">leaf</flower>
<flower color="blue">bluet</flower>
</bouquet>

Модель содержимого элемента

Под моделью содержимого в схеме данных понимают описание всех допустимых объектов XML- документа, использование которых внутри данного элемента является корректным. Модель содержимого определяется инструкциями, расположенными внутри блока <elementType>.

<elementType id="article">
<attribute name="id" atttype="ID">
<element type="#title">
<string/>
</elementType>

Для этого правила корректным будет являться следующий фрагмент документа:

<article id="0">
<title>Психи и маньяки в Интернет</title>
</article>

Вложенные элементы описываются при помощи инструкции element, в которой параметром type указывается класс объекта - ссылка на его определение:

<elementType id="article">
<element type="#title"/>
<element type="#author"/>
</elementType>

Если требуется указать режим использования вложенного элемента, то надо определить параметр occurs:

<elementType id="article">
<element type="#title" occurs="REQUIRED"/>
<element type="#author" occurs="OPTIONAL"/>
<element type="#subject" occurs="ONEORMORE"/>
</elementType>
Возможные значения этого параметра таковы:
  • REQUIRED - элемент должен быть обязательно определен
  • OPTIONAL - использование элемента не является обязательным
  • ZEROORMORE - вложенный элемент может встречаться несколько раз или ни разу
  • ONEORMORE - элемент должен встречаться хотя бы один раз

Примеры правильных XML-документа, использующих приведенную выше схему:

<article>
<title>Зачем он нужен, XML?</title>
<author>Иван Петров</author>
<subject>Что такое XML</subject>
<subject>нужен ли он нам</subject>
</article>
или
<article>
<title>Зачем он нужен, XML?</title>
<subject>Что такое XML</subject>
</article>

Кроме элементов, содержимым XML-документа могут также является обычный текст и области CDATA. Для обозначения типов содержимого текущего элемента в схемах используются следующие инструкции:

  • <string/> - указывает на то, что содержимым элемента является только свободная текстовая информация(секция PCDATA) :
    <elementType id="flower">
<string/>
</elementType>
  • <any/> - указывает на то, что содержимым элемента должны являться только элементы, без текста, незаключенного ни в один элемент:
    <elementType id="issue">
<any/>
</elementType>
  • <mixed> - любое сочетание элементов и текста
    <elementType id="contacts">
<mixed/>
</elementType>
  • <empty> - пустой элемент
Пример:
<elementType id="title">
<string/>
</elementType>
<elementType id="chapter">
<string/>
</elementType>
<elementType id="chapters-list">
<any/>
</elementType>
<elementType id="content">
<element type="#chapters-list" occurs="OPTIONAL">
</elementType>
<elementType id="article">
<mixed><element type="#title"></mixed>
<element type="#content" occurs="OPTIONAL">
</elementType>

О типах данных, которые можно определять с помощью схем, мы поговорим чуть позже

Группировка элементов

Элемент group используется для того, чтобы задать некоторую последовательность вложенных объектов:

<elementType id="contacts">
<element type="#tel" occurs="ONEORMORE">
<group occurs="OPTIONAL">
<element type="#email">
<element type="#url">
</group>
</elementType>

Группировка объектов позволяет определять сразу группу объектов различных типов, которые могут находится внутри данного объекта. В приведенном примере мы указали, что внутри объекта типа contacts могут быть включены элементы tel, email, и url, причем атрибутом occurs мы указали, что элементы в группе являются необязательными. Корректным для таких схем будут являться следующие фрагменты документов:

<contacts>
<tel>12-12-12</tel>
<email>info@j.com</email>
<url>http://www.j.com</url>
</contacts>
...
<contacts>
<tel>12-12-12</tel>
</contacts>
...
<contacts>
<tel>12-12-12</tel>
<email>info@j.com</email>
</contacts>

При помощи атрибута groupOrder можно также задавать режим использования группированных элементов При установленном значении OR возможно использование не всех элементов группы, а лишь некоторых из них. Если задано значение AND, то оба элемента должны быть включены в обязательном порядке. Например, для следующей группы правил:

<elementType id="contacts">
<element type="#tel" occurs="ONEORMORE">
<group groupOrder="AND" occurs="OPTIONAL">
<element type="#email">
<element type="#url">
</group>
</elementType>
будут считаться правильными только следующие варианты:
<contacts>
<tel>12-12-12</tel>
<email>info@j.com</email>
<url>http://www.j.com</url>
</contacts>
или
<contacts>
<tel>12-12-12</tel>
</contacts>

Закрытая и открытая модели описания содержимого элемента

Когда мы определяем модель содержимого текущего элемента, список дополнительных допустимых элементов правилами не ограничивается - он может свободно расширяться. Например, для приведенного выше правила, кроме обозначенных элементов <tel>,<url> и <email> вполне могут использоваться дополнительные элементы, неописанные правилами, например, <fax>:

<contacts>
<tel>12-12-12</tel>
<fax>21-21-21</fax>
<email>info@j.com</email>
<url>http://www.j.com</url>
</contacts>

Однако в том случае, если мы хотим ограничить создаваемые нами правила от включения дополнительных элементов, мы должны использовать атрибут content и установить для него специальное значение CLOSED:

<elementType id="contacts" content="CLOSED">
<element type="#tel">
<element type="#email">
<element type="#url">
</elementType>

Теперь приведенный фрагмент XML-документа будет считаться некорректным, т.к. параметром content запрещено использование внутри элемента contacts других объектов, кроме указанных в правиле.

Иерархия классов

Для того, чтобы при описании класса ограничить список объектов, которые могут являться родительскими для данного элемента, необходимо использовать элемент схемы domain.

Инструкция <domain> указывает, что текущий объект должен определяться строго внутри элемента, заданного этим тэгом. Например, в следующем фрагменте указывается, что элемент <author> может быть определен строго внутри тэга <article>:

<elementType id="author">
<element type="#lastname">
<element type="#firstname">
<domain type="#article"/>
</elementType>

Ограничения на значения

Значения элементов могут быть ограничены при помощи тэгов <min> и <max>;:

<elementType id="room">
<element type="#floor"><min>0</min><max>100</max>
</elementType>

Внутри этих элементов могут указываться и символьные ограничения:

<elementType id="line">
<element type="#character"><min>A</min><max>Z</max>
</elementType>

Использование правил из внешних схем

Схема может использовать элементы и атрибуты из других схем. Для этого надо использовать атрибут href, в котором указывается название внешней схемы. Например:

<?XML version='1.0' ?>
<?xml:namespace name="urn:uuid:BDC6E3F0-6DA3-11d1-
A2A3-00AA00C14882/" as="s"/?>
<s:schema>
<elementType id="author">
 <string/>
</elementType>
<elementType id="title">
<string/>
</elementType>
<elementType id="Book">
<element type="#title" occurs="OPTIONAL"/>
<element type="#author" occurs="ONEORMORE"/>
<element href="http://mrcpk.org/" />
 </elementType></s:schema>
</elementType>
</s:schema>

Компоненты схем

Компоненты, или макроопределении, используются в схемах точно также, как и в DTD. Для их определения предназначены тэги <intEntityDcl/> и <extEntityDcl/>;:

<intEntityDcl name="MRCPK">
Новосибирский Межотраслевой Региональный Центр
Переподготовки Кадров
</intEntityDcl>
<extEntityDcl name="logo" notation="#gif"
 systemId="http://www.mrcpk.nstu.ru/logo.gif"/>

Типы данных

В разделе, посвященном DTD, мы уже выяснили, для чего программе-клиенту необходима информация о формате данных содержимого элемента. В схемах существует возможность задавать тот или иной тип данных, используя при определении элемента директиву <datatype> с указанием конкретного типа:

<elementType id="counter">
<datatype dt="int">
 </elementType>

В DTD мы должны были создать атрибут с конкретным названием, определяющим операцию назначения формата данных, и значением, определенным как fixed .Использование элемента <datatype> позволяет указывать это автоматически, но для обеспечения программной независимости необходимо сначала договориться об обозначениях типов данных(значения, которые должны передаваться параметру dt элемента dataype), для чего могут использоваться, например, универсальные идентификаторы ресурсов URI. В любом случае, как и прежде, все необходимые действия, связанные с конкретной интерпретацией данных, содержащихся в документе, осуществляются программой-клиентом и определяются логикой его работы. В разделе, посвященном DTD, мы уже рассматривали пример XML- документа, реализующего описанные нами возможности. Вот как выглядел бы этот пример при использовании схем данных:

<schema id="someschema">
<elementType id="#rooms_num">
<string/>
<datatype dt="int">
</schema>
<elementType id="#floor">
<string/>
<datatype dt="int">
</schema>
<elementType id="#living_space">
<string/>
<datatype dt="float">
</schema>
<elementType id="#is_tel">
<string/>
<datatype dt="boolean">
</schema>
<elementType id="#counter">
<string/>
<datatype dt="float">
</schema>
<elementType id="#price">
<string/>
<datatype dt="float">
</schema>
<elementType id="#comments">
<string/>
<datatype dt="string">
</schema>
<elementType id="#house">
<element type="#rooms_num" occurs="ONEORMORE"/>
<element type="#floor" occurs="ONEORMORE"/>
<element type="#living_space" occurs="ONEORMORE"/>
<element type="#is_tel" occurs="OPTIONAL"/>
<element type="#counter" occurs="ONEORMORE"/>
<element type="#price" occurs="ONEORMORE"/>
<element type="#comments" occurs="OPTIONAL"/>
</elementType>
</schema>
 ...
<house id="0">
<rooms_num>5</rooms_num>
<floor>2</floor>
<living_space>32.5</living_space>
<is_tel>true</is_tel>
<counter>18346</counter>
<price>34.28</price>
<comments>С видом на cеверный полюс</comments>
</house>
...

Подводя итог всему сказанному, необходимо отметить, что процесс развития современных информационных систем настолько динамичен, что временной промежуток между появлением новой технологии и ее практическим использованием в реально действующих приложениях сегодня слишком мал. На смену устаревающему стандарту HTML в самое ближайшее время должен будет прийти новый, более гибкий и универсальный язык описания данных. И тот факт, что XML как язык еще не стандартизирован и некоторые его составляющие до сих пор находятся в стадии разработки, видимо, не является причиной невозможности его использования уже сегодня, для решения конкретных задач в реальных системах. Примером этому может служить возникновение огромного количества языков описания документов, некоторые из которых приведены в Приложении

В этой статье были рассмотрены лишь самые основные аспекты, касающиеся новой XML- технологии. В будущем, мы, возможно, остановимся несколько подробнее на производных от XML языках описания данных - SMIL, RDF, MathML, механизмах описания пространства имен и рассмотрим некоторые вопросы, касающиеся создания программ-анализаторов для этих языков.


Язык XML - практическое введение. Часть 2

XML (Extensible Markup Language) - это новый SGML-производный язык разметки документов, позволяющий структурировать информацию разного типа, используя для этого произвольный набор инструкций. Разработчиков Интернет приложений, пытающихся на практике использовать новую технологию, могут заинтересовать конкретные вопросы, связанные с проблемами создания, обработки XML-документов, их отображения на стороне клиента. В этой статье дается краткое представление о том, что представляет собой и для чего нужен XML-документ и на практических примерах иллюстрируются некоторые простые, но пока, к сожалению, мало описанные механизмы его обработки.

Что такое XML

Сегодня XML может использоваться в любых приложениях, которым нужна структурированная информация - от сложных геоинформационных систем, с гигантскими объемами передаваемой информации до обычных "однокомпьютерных" программ, использующих этот язык для описания служебной информации. При внимательном взгляде на окружающий нас информационный мир можно выделить множество задач, связанных с созданием и обработкой структурированной информации, для решения которых может использоваться XML:

  • В первую очередь, эта технология может оказаться полезной для разработчиков сложных информационных систем, с большим количеством приложений, связанных потоками информации самой различной структурой. В этом случае XML - документы выполняют роль универсального формата для обмена информацией между отдельными компонентами большой программы.
  • XML является базовым стандартом для нового языка описания ресурсов, RDF, позволяющего упростить многие проблемы в Web, связанные с поиском нужной информации, обеспечением контроля за содержимым сетевых ресурсов, создания электронных библиотек и т.д.
  • Язык XML позволяет описывать данные произвольного типа и используется для представления специализированной информации, например химических, математических, физических формул, медицинских рецептов, нотных записей, и т.д. Это означает, что XML может служить мощным дополнением к HTML для распространения в Web "нестандартной" информации. Возможно, в самом ближайшем будущем XML полностью заменит собой HTML, по крайней мере, первые попытки интеграции этих двух языков уже делаются (спецификация XHTML).
  • XML-документы могут использоваться в качестве промежуточного формата данных в трехзвенных системах. Обычно схема взаимодействия между серверами приложений и баз данных зависит от конкретной СУБД и диалекта SQL, используемого для доступа к данным. Если же результаты запроса будут представлены в некотором универсальном текстовом формате, то звено СУБД, как таковое, станет "прозрачным" для приложения. Кроме того, сегодня на рассмотрение W3C предложена спецификация нового языка запросов к базам данных XQL, который в будущем может стать альтернативой SQL.
  • Информация, содержащаяся в XML-документах, может изменяться, передаваться на машину клиента и обновляться по частям. Разрабатываемые спецификации XLink и Xpointer поволят ссылаться на отдельные элементы документа, c учетом их вложенности и значений атрибутов.
  • Использование стилевых таблиц (XSL) позволяет обеспечить независимое от конкретного устройства вывода отображение XML- документов.
  • XML может использоваться в обычных приложениях для хранения и обработки структурированных данных в едином формате.

Поверхностное описание языка можно найти в первых двух главах. Напомню лишь в общих словах, что XML-документ представляет собой обычный текстовый файл, в котором при помощи специальных маркеров создаются элементы данных, последовательность и вложенность которых определяет структуру документа и его содержание. Основным достоинством XML документов является то, что при относительно простом способе создания и обработки (обычный текст может редактироваться любым тестовым процессором и обрабатываться стандартными XML анализаторами), они позволяют создавать структурированную информацию, которую хорошо "понимают" компьютеры.

Как создать XML документ?

Для создания XML документа в простейшем случае вам не понадобится ничего кроме обычного текстового редактора (по мнению многих Web-дизайнеров, лучший инструмент для создания Web-страниц). Вот пример небольшого XML-документа, используемого вместо обычной записной книжки:

<?xml version="1.0" encoding="koi-8"?>
<notepad>
 <note id="1" date="12/04/99" time="13:40">
 <subject>Важная деловая встреча</subject>
 <importance/>
 <text>
 Надо встретиться с <person id="1625">
 Иваном Ивановичем</person>, предварительно
 позвонив ему по телефону <tel>123-12-12</tel>
 </text>
 </note>
 ...
 <note id="2" date="12/04/99" time="13:58">
 <subject>Позвонить домой</subject>
 <text>
 <tel>124-13-13</tel>
 </text>
 </note>
</notepad>

При создании собственного языка разметки вы можете придумывать любые названия элементов, (почти любые, т.к. список допустимых символов ограничен и приведен в спецификации XML), соответствующих контексту их использования. В нашем примере приведен лишь один из многочисленных способ создания структуры дневника. В этом и заключается гибкость и расширяемость XML-производных языков - они создаются разработчиком "на лету", согласно его представлениям о структуре документа, и могут затем использоваться универсальными программами просмотра наравне с любыми другими XML-производными языками, т.к. вся необходимая для синтаксического анализа информация заключена внутри документа.

Создавая новый формат, необходимо учитывать тот факт, что документов, "написанных на XML", не может быть в принципе - в любом случае авторы документа для его разметки используют основанный на стандарте XML (т.н. XML-производный) язык, но не сам XML. Поэтому при сохранении созданного файла можно выбрать для него какое-то подходящее названию расширение (например, noteML).

XML может использоваться вами для создания документов какого-то определенного типа и структурой, необходимой для конкретного приложения. Однако если сфера применения языка оказывается достаточно широкой и он начинает представлять интерес для большого числа разработчиков, то его спецификация вполне может быть представлена на рассмотрение в W3C и после согласования всеми заинтересованными сторонами, утверждена консорциумом в качестве официальной рекомендации.

Надо заметить, что процесс появления новой спецификации очень длителен и сложен. Любой документ, предлагаемый W3C, прежде чем стать стандартом проходит несколько этапов. Сначала пожелания и рекомендации, поступающие от различных компаний, участвующих в его разработке, оформляются в виде принятого к сведению замечания (Note), своеобразного протокола о намерениях. Информация, изложенная в таких документах предназначена только для обсуждения членами консорциума и никто не дает гарантии того, что эти замечания потом станут рекомендацией.

Следующей этапом продвижения документа является рабочий вариант спецификации, который составляет и изменяет в дальнейшем специально созданная рабочая группа (Working Group), в состав которой входят представители заинтересовавшихся идеей компаний. Все изменения, вносимые в этот документ обязательно публикуются на сервере консорциума www.w3.org и до тех пор, пока рабочий вариант не станет рекомендацией, он может служить для разработчиков лишь "путеводной звездой", с которой компания может сверять свои планы, но не должна использовать при разработке ПО.

В том случае, если стороны договорились по всем основным вопросам и существенных изменений в документ больше вносится, рабочий вариант становится Предложенной Рекомендацией и после голосования членами рабочей группы может стать уже Официальной Рекомендаций W3C, что по статусу соответствует стандарту в WWW.

XML-генераторы

XML документы могут служить промежуточным форматом для передачи информации от одного приложения к другому (например, как результат запроса к базе данных), поэтому их содержимое иногда генерируется и обрабатывается программами автоматически. Далеко не всегда XML документ нужно создавать вручную.

Пусть, например, нашей задачей является создание формата хранения данных регистрации каких-то происходящих в системе событий (log-файла). В простейшем случае можно ограничиться фиксированием успешных и ошибочных запросов к нашим ресурсам - в таком документе должна присутствовать информация о времени произошедшего события, его результате (удача/ошибка), IP адресе источника запроса, URI ресурса и коде результата.

Наш XML документ может выглядеть следующим образом:

<?xml version="1.0" encoding="koi-8"?>
 <log>
 <event date=" 27/May/1999:02:32:46 " result="success">
 <ip-from> 195.151.62.18 </ip-from>
 <method>GET</method>
 <url-to> /misc/</url-to>
 <response>200</response>
 </event>
 <event date=" 27/May/1999:02:41:47 "
  result="success">
 <ip-from> 195.209.248.12 </ip-from>
 <method>GET</method>
 <url-to> /soft.htm</url-to>
 <response>200</response>
 </event>
 </log>

Структура документа довольно проста - корневым в данном случае является элемент log, каждое произошедшее событие фиксируется в элементе event и описывается при помощи его атрибутов(date - время и result - тип события ) и внутренних элементов (method - метод доступа, ip-from - адрес источника, url-to - запрашиваемый ресурс, response - код ответа). Генерацией этого документа может заниматься, например, модуль аутентификации запросов в систему, а использованием - программа обработки регистрационных данных (log viewer).

Что такое DTD?

Итак, мы создали XML документ и убедились, что набор используемых при этом тэгов позволяет осуществлять любые манипуляции с нашей информацией. В таком случае, для того, чтобы утвердить правила нашего нового языка, т.е. список допустимых элементов, их возможное содержимое и атрибуты, мы должны создать DTD - определения (на момент написания статьи спецификация схем данных для XMLдокументов еще не утверждена и пока DTD являются единственным стандартным способом описания грамматики).

Более подробно о DTD можно прочитать в этой главе, здесь приведy лишь небольшой пример для нашего XML-документа:

<?xml encoding="koi8-r"?>
 <!ELEMENT log (event)+>
<!ELEMENT event (ip-from,method,uri-to,result)>
<!ELEMENT method (#PCDATA)>
<!ELEMENT ip-from (#PCDATA)>
<!ELEMENT url-to (#PCDATA)>
<!ELEMENT response (#PCDATA)>
<!ATTLIST event
  result CDATA #IMPLIED
  date CDATA #IMPLIED>

Сохраните этот файл под именем log.dtd и включите в XML-документ новую строчку:

<!--DOCTYPE log SYSTEM "log.dtd"-->

Теперь верифицирующий XML-анализатор при обработке документа будет сверять порядок определения элементов и их атрибутов с тем, как это указано у нас в DTD-нотациях и в случае нарушения внутренней структуры (которая определяет "семантику" документа) выдавать сообщение об ошибке.

Что такое Namespaces?

Как уже упоминалось ранее, вся прелесть использования XML заключается в возможности придумывания собственных тэгов, названия которых наиболее полно соответствовали бы предназначению. Но фантазия и словарный запас людей не безграничны, поэтому нет абсолютно никакой гарантии того, что данные вами имена элементов не будут использованы кем-то еще. До тех пор, пока в вашем приложении обрабатываются только собственные XML-документы, никаких проблем не возникнет. Но вполне возможна ситуация, когда один и тот же документ будет содержать информацию для нескольких обработчиков одновременно. В этом случае названия некоторых элементов или их атрибутов могут совпасть, что вызовет либо ошибку в XML- анализаторе, либо неправильное представление документа. Например, в нашем случае, элемент event вполне мог бы быть использован для записи других событий и обрабатываться другим приложением.

Чтобы исправить эту ситуацию, мы должны определить уникальные названия элементов и их атрибутов, "дописывая" к их обычным именам некоторый универсальный неповторяющийся префикс. Для этого применяется механизм Namespaces (спецификация Namespaces была официально утверждена W3C в январе 1999 года и сегодня является частью стандарта XML). Согласно этой спецификации, для определения "области действия" тэга ( на самом деле этот термин, широко используемый в обычных языках программирования, неприменим в XML, потому что как такового множества, на котором могла бы быть построена "область", внутри структурированного XML документа нет) необходимо определить уникальный атрибут, описывающий название элемента, по которому анализатор документа сможет определить, к какой группе имен оно относится (Namespace идентификаторы могут применяться для описания уникальных названий как элементов, так и их атрибутов). В нашем последнем примере это может быть сделано так:

   <?xml version="1.0" encoding="koi8-r"?>

   <!--DOCTYPE log SYSTEM "log.dtd"-->

   <log xmlns:xlg="www.mrcpk.nstu.ru/xml/ar/4/">

    <xlg:event xlg:date=" 27/May/1999:02:32:46 "
xlg:result="success">

    <ip-from> 195.151.62.18 </ip-from>

    <method>GET</method>

    <url-to> /misc/</url-to>

    <response>200</response>

   </event>

    <xlg:event date=" 27/May/1999:02:41:47 "
 result="success">

    <ip-from> 195.209.248.12 </ip-from>

    <method>GET</method>

    <url-to> /soft.htm</url-to>

    <response>200</response>

   </event>

    </log>

Уникальность атрибуту имени обеспечивает использование в качестве его значения некоторых универсальных идентификаторов ресурсов (например, URI или ISBN) .

Полную информацию по использованию Namespace вы можете найти в официальной спецификации этого стандарта. В дальнейшем, для упрощения примеров, мы будем Namespace - описания пропускать.

Инструментарий

Очевидно, что ручной способ создания структурированной информации не может применяться для наполнения больших информационных узлов. Для этого существуют специальные средства разработки, список которых сегодня постоянно пополняется (их обзор будет приведен в одной из следующих статей). Одним их самых простых и удобных, на мой взгляд, является редактор XML Notepad, получить который можно здесь -msdn.microsoft.com/xml/notepad/intro.asp).

Обработка XML-документов

Основным сдерживающим фактором в продвижении XML технологии в Web на сегодняшний день является отсутствие полной поддержки этого формата всеми производителями броузеров - программ, наиболее часто используемых на стороне клиента. Выходом из создавшейся ситуации может стать вариант, при котором обработкой XML документов занимается серверная сторона Используя любой существующий XML-анализатор, можно формировать необходимую информацию уже на сервере и посылать клиенту нормальный HTML-документ. Однако такой способ, конечно, менее гибок, и позволяет использовать XML технологию лишь для хранения структурированной информации, но не для ее динамического изменения на стороне клиента.

В августе 1997 RFC 2376 были утверждены MIME типы для XML-ресурсов: text/xml и application/xml. Поэтому XML документы могут передаваться по HTTP и отображаться программой просмотра также, как и обычные HTML- страницы. Для этого нужно немного изменить конфигурацию Web-сервера (в Apache - добавить в файл mime.types строчку "text/xml xml ddt"), а на стороне клиента иметь броузер, поддерживающий стилевые таблицы или JavaScript. Сегодня такими броузерами являются Microsoft Internet Explorer 5, первый броузер, поддерживающий спецификацию XML 1.0 и стилевые таблицы XSL; броузер Amaya, предлагаемый консорциумом специально для тестовых целей (http://www.w3.org/Amaya/User/BinDist.html) и поддерживающий практически все разрабатываемые стандарты W3C. Поддержка XML также планируется в будущих версиях Netscape Navigator.

Объектная модель документа DOM

Одним из самых мощных интерфейсов доступа к содержимому XML документов является Document Object Model - DOM.

Объектная модель XML документов является представлением его внутренней структуры в виде совокупности определенных объектов. Для удобства эти объекты организуются в некоторую древообразную структуру данных - каждый элемент документа может быть отнесен к отдельной ветви, а все его содержимое, в виде набора вложенных элементов, комментариев, секций CDATA и т.д. представляется в этой структуре поддеревьями. Т.к. в любом правильно составленном XML-документе обязательно определен главный элемент, то все содержимое можно рассматривать как поддеревья этого основного элемента, называемого в таком случае корнем дерева документа. Для следующего фрагмента XML документа:

<tree-node>
  <node-level1>
    <node-level2/>
    <node-level2>text</node-level2>
    <node-level2/>
  </node-level1>
<node-level1>
   <node-level2>text</node-level2>
<node-level1>
    <node-level2/>
    <node-level2><node-level3/></node-level2>
</node-level1>
</tree-node>

дерево элементов выглядит так:

Рис.1Дерево элементов

Объектное представление структуры документа не является чем-то новым для разработчиков. Для доступа к содержимому HTML страницы в сценариях давно используется объектно-ориентированный подход, - доступные для Java Script или VBScript элементы HTML документа могли создаваться, модифицироваться и просматриваться при помощи соответствующих объектов. Но их список и набор методов постоянно изменяется и зависит от типа броузера и версии языка. Для того, чтобы обеспечить независимый от конкретного языка программирования и типа документа интерфейс доступа к содержимому структурированного документа в рамках W3 консорциума была разработана и официально утверждена спецификация объектной модели DOM Level 1.

DOM - это спецификация универсального платформо- и программно-независимого доступа к содержимому документов и является просто своеобразным API для их обработчиков. DOM является стандартным способом построения объектной модели любого HTML или XML документа, при помощи которой можно производить поиск нужных фрагментов, создавать, удалять и модифицировать его элементы.

Для описания интерфейсов доступа к содержимому XML документов в спецификации DOM применяется платформонезависимый язык IDL и для использования их необходимо "перевести" на какой-то конкретный язык программирования. Однако этим занимаются создатели самих анализаторов, нам можно ничего не знать о способе реализации интерфейсов - с точки зрения разработчиков прикладных программ DOM выглядит как набор объектов с определенными методами и свойствами. В следующем разделе мы вкратце рассмотрим объектную модель Microsoft Internet Explorer 5, доступную из Java Script и VBScript сценариев.


XML в Microsoft Internet Explorer 5.0

Разработчики, использующие Dynamic HTML для создания динамических HTML страниц, видимо, оценят новые возможности по управлению информацией, появившейся с включением в Internet Explorer 5 поддержки DOM Level1 и стилевых таблиц XSL.

HTML-документы давно уже стали привычным форматом для представления информации в Web. Но, к сожалению, их содержимое практически не описывается тэгами и единственное, что может делать с данными броузер, это форматировать их и выводить на экран. Передаваемая клиенту информация в виде HTML страницы доступна для пользователя лишь в том виде, в котором она была сформирована на стороне сервера и практически невозможно динамическое изменение данных в зависимости от текущих потребностей пользователя. Впервые попытка хранения информации независимо от форматирующих ее тэгов была сделана в спецификации Dynamic HTML для IE 4 - в объектную модель броузера были добавлены т.н. объекты источников данных (Data Source Object - DSO). Эти объекты позволяли динамически "назначать" информацию для тех или иных фрагментов HTML документа(например, таблицам), которую затем отображал броузер, и являлись, по сути, "островками данных" для моря остальных форматирующих тэгов документа. Следующим шагом в этом направлении стало появление нового тэга <xml> и включение в объектную модель Internet Explorer новых объектов доступа к XML-данным.

Говоря об объектной модели броузера необходимо уточнить, что в данном случае имеется в виду интерфейс доступа к содержимому документа для сценариев, а вовсе не часть программного обеспечения броузера. Некоторые интерфейсы доступны для других приложений и являются, по сути, COM-интерфейсами, некоторые могут использоваться только внутри. Для нас не важно, как представлены объекты в программной модели броузера - мы будем рассматривать объектную модель как набор объектов, их методов и событий, доступных для сценария внутри страницы.

Из внутренних сценариев HTML страницы обращение к методам объекта данных выглядит также, как и для любого другого элемента документа - при помощи его идентификатора или по индексу в коллекции классов страницы. Вот например, как можно это сделать через JScript (версия JavaScript от Microsoft):

 <xml ID="xmlNotes" src="notes.xml"></xml>
 <script language=JAVASCRIPT>
  var node_value = xmlNotes.document.all
("xmlNotes").XMLDocument.nodeValue;
  var document_text = xmlNotes.documentElement.text;
 </script>

При этом ссылка на элемент данных HTML страницы в сценарии автоматически предоставляет нам доступ к объектной модели XML документа, создаваемой обработчиком в памяти компьютера сразу после загрузки документа.

В настоящий момент поддержка XML в IE5 реализована, практически, в на трех вариантах. Во-первых, это возможность загрузки XML документа, так же, как загружается обычная HTML страница. Он будет нормально обработан броузером и отображен в виде дерева содержащихся в нем элементов.

Во-вторых, XML документы можно форматировать при помощи стилевых таблиц XSL. Стилевые таблицы позволяют управлять процессом отображения элемента на экране броузера, меняя в зависимости от его типа и месторасположения в документе используемые для этого форматирующие тэги. Кроме того, стилевые таблицы могут использоваться также и для поиска нужных фрагментов внутри документа, выводя по желанию пользователя отдельные его части независимо от остального содержимого. XSL инструкции в Internet Explorer 5.0 позволяют "фильтровать" и обрабатывать сложные XML документы, со множеством рекурсивно вложенных, сложноподчиненных элементов с нефиксированным набором атрибутов и строгими требованиями к порядку их определения внутри документа.

В-третьих, доступ к XML-данным возможен из сценариев внутри страницы, имеющих доступ практически к любому элементу документа через соответствующие объекты. Использование Dynamic HTML представляет собой наиболее гибкий и мощный способ формирования динамически изменяемой информации на стороне клиента. Структурированные данные XML-документа могут загружаться в страницу при помощи тэгов <xml>, <object> или <script>, в которых указывается либо адрес документа, либо непосредственно сами XML-элементы:

<xml ID="xmlNotes1" src="notes.xml"></xml>
...
<xml ID="xmlNotes2">
<notepad>
<note id="5">
<text>Очень важная информация</text>
</note>
</notepad>
</xml>
...
<script language="xml" ID="xmlNotes3">
<notepad id="6">
<note>
<text>Очень важная информация</text>
</note>
</notepad>
</script>
...
<script language=JAVASCRIPT>
var root_node = document.all.("SCRIPT").XMLDocument;
</script>
Возможны комбинации всех этих подходов - загрузка и обработка XML документа в сценариях через методы объектов DOM; включение стилевых таблиц в страницу при помощи тэга <xml>, их модификация в соответствии с запросами пользователя, назначение XSL-стилей для XML-документов прямо из сценариев, включение фрагментов скриптов непосредственно в XML документы при помощи блока CDATA.

Internet Explorer DOM

В настоящий момент Microsoft Internet Explorer является первым броузером, поддерживающим спецификацию DOM Level 1. Для сценариев на стороне клиента доступно множество объектов для работы с XML-документом. Полное их описание является темой отдельной статьи, здесь же рассмотрим лишь самые важные из них, объекты XMLDOMDocument, XMLDOMNode, XMLDOMNodeList, представляющие интерфейс для доступа ко всему документу, отдельным его узлам и поддеревьям соответственно. Также рассмотрим объект XMLDOMParseError, предоставляющий необходимую для отладки информацию о произошедших ошибках анализатора (т.к. его методы, к сожалению, на первых шагах используются очень часто). Описание дается по материалам официального руководства, расположенного на сервере Microsoft: msdn.microsoft.com/xml/, и является упрощенным и сокращенным его вариантом, поэтому если приведенных в таблице сведений будет недостаточно, нужно обратиться к первоисточнику.

Объект XMLDOMNode
Объект XMLDOMNode, реализующий базовый DOM интерфейс Node, предназначен для манипулирования с отдельным узлом дерева документа. Его свойства и методы позволяют получать и изменять полную информацию о текущем узле - его тип (является ли текущий узел элементом, комментарием, текстом и т.д.), название, полное название (вместе с Namespace префиксом), его содержимое, список дочерних элементов и т.д.
Свойства
Общая информация о текущем элементе дерева
nodeName | Возвращает полное название(вместе с Namaspace атрибутом) текущего узла в виде строки. Доступно только для чтения.
baseName | Возвращает название элемента без префикса Namespace. Только для чтения.
prefix | Возвращает Namespace префикс. Только для чтения.
namespaceURI | Возвращает URI Namespace префикса текущего элемента
dataType | Определяет тип содержимого текущего узла(описываемое схемами данных). Доступно для записи и чтения
nodeType | Возвращает тип текущего узла: NODE_ELEMENT (1) - элемент NODE_ATTRIBUTE (2) - атрибут NODE_TEXT (3) - текст NODE_CDATA_SECTION (4) - область CDATA NODE_ENTITY_REFERENCE (5) - объект ссылки на "макроподстановки" NODE_ENTITY (6) - объект ссылки на т.н. "подстановочые символы" - entity" NODE_PROCESSING_INSTRUCTION (7) - область инструкций XML процессору NODE_COMMENT (8) - комментарий NODE_DOCUMENT (9) - корневой элемент документа NODE_DOCUMENT_TYPE (10) - описание типа документа, задаваемое тэгом <!DOCTYPE> NODE_DOCUMENT_FRAGMENT (11) - фрагмент XML-документа - несвязанное поддерево NODE_NOTATION (12) - DTD нотация. Свойство доступно только для чтения.
nodeTypeString | Возвращает тип узла в виде текста. Только для чтения.
attributes | Возвращает список атрибутов текущего узла в виде коллекции XMLDOMNamedNodeMap. Если атрибутов нет, то свойство length будет содержать нулевое значение. Для тех узлов, у которых не может быть атрибутов (в XML документе они могут быть назначены лишь объектам элементов, макроподстановок и нотаций) возвращается null. Для объектов макроподстановок и нотаций содержимым коллекции будут являться атрибуты SYSTEMID, PUBLICID и NDATA. Доступно только для чтения.
definition | Возвращает DTD определение для текущего узла дерева.
Содержимое текущего узла
text | Возвращает содержимое текущего поддерева(узла и всех его дочерних элементов). Доступно для записи и чтения
xml | Возвращает XML-представление текущего поддерева. Доступно только для чтения
nodeValue | Возвращает содержимое текущего узла. Доступно для чтения и записи.
Работа со списком дочерних элементов
childNodes | Для тех узлов, которые имеют дочерние элементы возвращает их список в виде XMLDOMNodeList. В том случае, если дочерних элементов нет, значение свойства length списка равно нулю . Только для чтения.
lastChild | Возвращает последний дочерний элемент или null, если таковых не имеется. Свойство доступно только для чтения.
firstChild | Возвращает последний дочерний элемент или null. Только для чтения.
nextSibling | Возвращает следующий дочерний элемент. Только для чтения.
previousSibling | Возвращает предыдущий дочерний элемент. Доступно только для чтения.
parentNode | Содержит ссылку на родительский элемент. В том случае, когда такого элемента нет, возвращает null. Доступно только для чтения.
ownerDocument | Возвращает указатель на документ, в котором находится текущий узел. Если в процессе модификации дерева узел будет перенесен в другой документ, то значение этого свойства автоматически изменится. Только для чтения.
Методы
Добавление новых элементов в объектную модель документа
appendChild(newChild) | Добавляет текущему узлу новый дочерний элемент. Возвращает ссылку на объект этого нового элемента. То же самое можно сделать и при помощи insertBefore (newChild, null)
insertBefore(newChild, refChild) | Вставляет дочерний узел, располагая его в текущем поддереве "левее" узла, указанного параметром refChild. Если последний параметр не задан, то новый узел будет добавлен в конец списка.
Модификация и удаление узлов
cloneNode (deep) | Создание копии текущего элемента. Параметр deep определяет, будет ли эта процедура рекурсивно выполняться для всех дочерних элементов. Возвращаемое значение - ссылка на новый элемент
replaceChild(newChild, oldChild) | Замена объекта oldChild текущего списка дочерних объектов на newChild. Если newChild=null, то старый объект будет просто удален.
removeChild(oldChild) | Удаление объекта oldChild из списка дочерних элементов
Поиск узлов (выделение поддеревьев)
selectNodes (patternString) | Возвращает объект XMLDOMNodeList, содержащий поддерево, выбранное по шаблону поиска pattertnString
selectSingleNode (patternString) | Аналогичен методу selectNodes, только возвращает первый узел из найденного поддерева
Обработка поддеревьев стилевыми таблицами
transformNode (stylesheet) | Назначает стилевую таблицу для поддерева текущего узла и возвращает строку - результат обработки. В качестве параметра передается ссылка на объект DOMDocument, в котором находятся XSL инструкции.
transformNodeToObject (stylesheet, outputObject) | То же, что и transformNode, только результат - обработанное дерево передается в объект XMLDocument(другое дерево), задаваемый параметром outputObject

Объект XMLDOMDocument
Представляет верхний уровень объектной иерархии и содержит методы для работы с документом: его загрузки, анализа, создания в нем элементов, атрибутов, комментариев и т.д. . Многие свойства и методы этого объекта реализованы также в рассмотренном выше класса Node, т.к. документ может быть рассмотрен как корневой узел с вложенными в него поддеревьями.
Свойства

Получение и информации о текущем состоянии процесса загрузки и анализа документа.

async

Свойство, доступное для записи и чтения, идентифицирующее текущий режим обработки (синхронный или асинхронный)

parseError

Возвращает ссылку на объект XMLDOMParseError, при помощи которого можно получить всю необходимую информацию о последней ошибке анализатора. Только для чтения.

readyState

Содержит информацию о текущем состоянии анализатора:

  • LOADING (1) - находится в процессе загрузки документа
  • LOADED (2) - загрузка завершена, но объектная модель документа еще не создана
  • INTERACTIVE (3) - объектная модель создана(все элементы документа разобраны, установлены их связи и атрибуты) но доступна пока только для чтения
  • COMPLETED (4) - с ошибками или без, но документ разобран

Для получения своевременной информации о текущем состоянии анализатора можно воспользоваться обработчиком событий onreadystatechange

Только для чтения.

ondataavailable

Свойство, доступное только для записи, которое содержит ссылку на обработчик события ondataavailable (вызывается, когда обработчик обрабатывает очередную порцию данных документа)

onreadystatechange

Ссылка на обработчик события onreadystatechange (вызывается каждый раз, когда меняется состояние обработчика - свойство readyState)

ontransformnode

Ссылка на обработчик события ontransformnode (вызывается перед каждой трансформацией узла стилевыми таблицами)

Изменение параметров обработчика.

preserveWhiteSpace

Определяет, должны ли при разборе документа игнорироваться символы разделителей. Если значение свойства ложно, то будут, если истина - то разделители будут сохранены. По умолчанию установлено в false. Доступно для чтения и записи.

resolveExternals

Свойство определяет, будут ли в процессе анализа разбираться внешние определения (DTD-описания, макроподстановки и т.д.) - значение true или нет(false). Доступно для чтения и записи.

validateOnParse

Включение - выключение верификации документа. Значения true или false. Доступно для чтения и записи.

Получение информации о загруженном документе

doctype

Возвращает тип документа, определяемый при его создании тэгом <!DOCTYPE>, включающим DTD. Если в документе нет DTD описаний, возвращается null. Только для чтения.

url

Возвращает URL документа(в случае успешной его загрузки, в противном случае возвращает null). Доступно только для чтения.

implementation

Возвращет объект XMLDOMImplementation для данного документа. Только для чтения.

documentElement

Содержит ссылку на корневой элемент документа в виде объекта XMLDOMElement. Если корневого элемента нет, то возвращается null. Доступно для записи

Методы

Загрузка и сохранение документов

load(url)

Загружает документ, адрес которого задан параметром url. В случае успеха возвращает логическое значение true. Необходимо иметь в виду, что вызов этого метода сразу же обнуляет содержимое текущего документа

loadXML(xmlString)

Загружает XML - фрагмент, определенный в передаваемой строке

save(objTarget)

Сохраняет документ в файле (objTarget - строка, содержащая URL файла) или внутри другого документа (objTarget - объект XMLDOMDoument).

abort()

Прерывание процесса загрузки и обработки документа. Обработчик ошибок XMLDOMParseError будет содержать в коде ошибки соответствующее значение.

Создание новых объектов. Необходимо отметить, что все методы лишь создают указанные объекты и для включения их в объектную модель документа надо дополнительно использовать методы insertBefore, insertAfter или appendChild.

createAttribute (name)

Создает для текущего элемента новый атрибут с указанным именем. Новый атрибут добавляется в объектную модель документа только после определения его значения методом setAttribute.

createNode(Type, name, nameSpaceURI)

Создает узел указанного типа и названия. Namespace префикс задается параметром nameSpaceURI. Возвращаемым значением будет созданный объект указанного типа.

createCDATASection(data)

Создает область CDATA - возвращает объект XMLDOMCDATASection

createDocumentFragment()

Создает новый пустой фрагмента документа - объект XMLDOMDocumentFragment

createComment(data)

Создает комментарий.

createElement(tagName)

Создает элемент документа с указанным названием.

createEntityReference(name)

Создает ссылку на подстановочные символы

createProcessingInstruction(target, data)

Создает новую директиву XML-процессора

createTextNode(data)

Создает текст внутри документа

Поиск узлов дерева документа

getElementsByTagName(tagname)

Возвращает ссылку на коллекцию элементов документа с заданным именем (или всех элементов, если значение tagname равно "*")

nodeFromID(idString)

Поиск элемента по идентификатору

hasChildNodes()

Возвращает истину, если текущий узел содержит поддерево.

Объект XMLDOMNodeList
Представляет собой список узлов - поддеревья и содержит методы, при помощи которых можно организовать процедуру обхода дерева.
Свойства
length | число элементов списка узлов
Методы
item(i) | Выбор i-того элемента из списка. Возвращает объект XMLDOMNode
nextNode() | Выбор следующего элемента в списке. Если такого элемента нет, то возвращает null. первый вызов этого метода(после сброса итератора) возвратит ссылку на первый элемент списка.
reset() | Сброс внутреннего указателя текущего элемента

Объект XMLDOMParserError
Объект позволяет получить всю необходимую информацию об ошибке, произошедшей в ходе разбора документа. Все свойства этого объекта доступны только для чтения.
Свойства
errorCode | Содержит код возникшей ошибки либо 0, если таковой не случилось.
url | Возвращает URL обрабатываемого документа
filepos | Возвращает смещение относительно начала файла фрагмента, в котором обнаружена ошибка
line | Содержит номер строки, содержащей ошибку
linepos | Позицию ошибки в строкев которой была обнаружена ошибка
reason | Описание ошибки
srcText | Содержит полный текст строки, в которой произошла ошибка

Приведем некоторые примеры использования объектной модели.

Создание объекта документа

Работа с содержимым XML документа в DOM начинается с создания объекта, реализующего методы класса Document. В IE5 этим объектом является XMLDOMDocument. Создание объекта из сценариев осуществляется при помощи стандартных методов new ActiveXObject(JScript) и CreateObject:

<script language="JScript">
 var docobj = new ActiveXObject("Microsoft.XMLDOM");
...
и
<script language="VBScript">
Dim docobj
Set docobj = CreateObject("Microsoft.XMLDOM").
...

Если данные включаются в документ в виде DSO-объектов, то для доступа к документу можно также использовать объектную модель HTMLстраницы, получая ссылку на XML-документ по идентификаторам соответствующих тэгов:

<XML id="source" src="source-file.xml"></XML>
<XML id="style" src="style-file.xsl"></XML>
<SCRIPT FOR="window" EVENT="onload">
   xslArea.innerHTML =
   source.transformNode(style.XMLDocument);
</SCRIPT>
...
<DIV id="xslArea"></DIV>

Первым способом создаются объекты при "ручной"загрузке нового документа. Если же мы хотим получить доступ к данным, встроенным в страницу при помощи тэгов xml или object, то используется второй способ.

Объектной переменной XMLDOMDocument также можно присвоить ссылку на другой объект созданного раннее документа:

docobj.documentElement = otherobj;

Загрузка XML-документа

Все необходимые манипуляции с XML документом осуществляются после его загрузки и создания дерева элементов. Загрузка может осуществляться либо при помощи указателя на соответствующий ресурс: docobj.load("http://myserver/xml/notes.xml"), либо "на лету", при помощи метода loadXML, которому в качестве параметра передается строка- отрывок XML документа:

docobj.loadXML("<recipe><step id='1'>Насыпать чай</step><step id='2'>Залить кипятком </step> <step id='3'>Вылить</step></recipe>");

Анализ документа

Для управления процессом анализа документа можно изменять следующие рассмотренные ранее свойства XMLDOMObject : async, validateOnParse, resolveExternals, preserveWhiteSpaces.

Необходимо запомнить, что анализ XML документа производится непосредственно после загрузки его содержимого - остановить этот процесс можно только используя метод abort. Поэтому свойства обработчика нужно изменять перед его загрузкой.

В процессе анализа документа обработчиком могут вызываться некоторые события, перехватывая которые можно отслеживать все шаги обработки. Для назначения классов обработчиков используются свойства , описанные в таблице. Вот пример программы-обработчика события , возникающего при изменении текущего состояния анализатора.

<script>
 var xmldoc;
 var messages = new Array(5);
 var result_str = " Демонстрация
  обработки событий<hr/>";
 messages[0]="Загрузка документа.";
 messages[1]="Загрузка завершена.
  Начинаю анализ документа";
 messages[2]="Начинаю создание объектной модели";
 messages[3]="Обработку завершил";

 function startParse(url){
  xmldoc = new ActiveXObject("Microsoft.XMLDOM");
  xmldoc.onreadystatechange = onChangeState;
  xmldoc.load(url);
  xmlMessages.innerHTML = result_str;
 }

 function onChangeState(){
  var state = xmldoc.readyState;
  result_str += messages[state-1] + "<BR>";
}
</script>

<BODY onLoad="startParse('notepad.xml')">
    <DIV id="xmlMessages"></DIV>
</BODY>

Другой способ назначить обработчик событий для элемента - это использование атрибута event тэга <script>:

<XML id="xmlID" src="notes.xml"></XML>
 <script for="xmlID" event="onreadystatechange">
 alert(xmlID.readyState);
 </script>

Обработка ошибок

Информация об обнаруженных в результате разбора XML-документа ошибках передается сценарию через объект XMLDOMParseError. Ссылку на него возвращает метод parseError объекта XMLDOMDocument. Узнать о типе ошибки можно по ее коду, содержащемся в свойстве errorCode (если разбор закончился успешно, то значение errorCode равно 0). При помощи свойств filepos, line, linepos, reason, srcText и url можно получить полную информацию о причине появления ошибки и ее местонахождении.

<SCRIPT language="JavaScript">
var docobj;
var result_str = "<hr/>";

function view(){
docobj = new ActiveXObject("Microsoft.XMLDOM");
docobj.load("music.xml");

    if (docobj.parseError.errorCode != 0){
     xmlTree.innerHTML=reportParseError
     (docobj.parseError);
     return;
}
xmlTree.innerHTML = docobj.xml;
}

function reportParseError(error){
 error_str  = "<H4>Ошибка при загрузке документа '"
  +      error.url + "'</H4>" +
      "<p><font color='red'>" + error.reason +
          "</font></p>";
  if (error.line > 0)
    error_str += "<H5>" +    "Строка "
     + error.line + ", символ " + error.linepos +
    "\n" + error.srcText +   "</H4>";
  return error_str;

}

</script>
  <BODY onLoad="startParse()">
    <DIV id="xmlTree"></DIV>
  </BODY>
  </HTML>
...

Сохранение XML документа.

Созданное в памяти компьютера объектное дерево можно сохранить в текстовый файл, используя метод save:

xmlobj.save ("menu.xml");

Кроме этого, XML-документ можно сохранить в другом XMLDOMDOcument объекте, передав в качестве аргумента функции ссылку на него.

Обход дерева элементов

Для работы со списком элементов в объектной модели XML-анализатора Microsoft предназначены специальные объекты: XMLDOMNode - представляющий узел дерева и XMLDOMNodeList - список узлов, поддерево. Их описание приведено в таблице.

Просмотр списка элементов документа всегда начинается с получения нужного поддерева. Для этого у объекта XMLDOMNode используется методы childNodes, selectNodes или getElementsByTagName, возвращающие объект XMLDOMNodeList. Количество элементов этом поддереве можно узнать при помощи свойства length.

Вот, например, как будет выглядеть процедура рекурсивного просмотра документа произвольной структуры:

<SCRIPT language="JavaScript">
var result_str = "<hr/>";
var docobj = new ActiveXObject("Microsoft.XMLDOM");

 function printElements(){
  docobj.load("music.xml");
  viewNode(docobj.documentElement);
  xmlTree.innerHTML = result_str;
 }

 function viewNode(node){
  var childnodes = curNode.childNodes.length;
  result_str+=" "+curNode.nodeName+"<br/>";
  for(var i=0;i<childnodes;i++){
  viewNode(curNode.childNodes.item(i));
 }
}
</SCRIPT>

 <BODY onLoad="printElements()">
   <DIV id="xmlTree"></DIV>
 </BODY>
</HTML>

Навигация по документу осуществляется обычным перебором массива элементов в цикле for или при помощи методf nextNode. И в том и в другом случае мы сначала выбираем нужное поддерево, а затем обрабатываем его элементы. Необходимо заметить также, что в XMLDOMNodeList отражаются все изменения, вносимые в структуру XML-документа, и информация в результате будет всегда актуальной.

Поиск элемента

Поиск нужного элемента или поддерева осуществляется при помощи методов selectNode и selectSingleNode (то же что и selectNode, только возвращает первый найденный элемент). В качестве параметров им необходимо указать строку XSL запроса (образец поиска - XSL pattern).

Синтаксис языка запросов очень гибок и является одним из самых мощных механизмов в XSL - при помощи них можно осуществлять поиск элемента по названию, значению атрибутов, содержанию, учитывая вложенность и положение в дереве элементов. Наиболее ярко все эти возможности демонстрируются при обработке XML-документов стилевыми таблицами XSL , когда мы можем выделять из общего дерева необходимые нам элементы и применять к ним специальные форматирующие инструкции.

Внешне язык XSL запросов немного напоминает обычный способ определения пути к ресурсу в файловой системе - список узлов дерева, разделенных символом /. Для указания на текущий элемент используется символ "." , на родительский - "..", для выделения всех дочерних элементов - символ "*", для выделения элемента, расположенного просто "ниже" по дереву(не важно на каком уровне вложенности) - "//".

Вот примеры простых XSL шаблонов:

"/music-collection" - корневой элемент

"bards/" - возвращает дочерние элементы для элемента bards

"authors-list//" - список всех элементов, вложенных в authors-list

"author[@id]" - список элементов author, в котором определен атрибут id

"author[@id=2]" - элемент author, в котором значение атрибута id равно двум

"author[address]" - список элементов author, которые содержат хотя бы один элемент address

"author[address or city]" - список элементов author, содержащих элементы address или city

Условие на значение в запросе должно заключаться в символы "[" и "]". Для выбора значения атрибута в условии указывается символ @.

Применяя к XML- документу различные шаблоны поиска, можно осуществлять сложные манипуляции с его содержимым, динамически изменяя объем отображаемой пользователю информации в зависимости от производимых им действий (например, динамическая сортировка, отображение подчиненных таблиц и т.д.) Более подробное описание XSL-таблиц будет приведено в одной из следующих статей.


Использование Java XML-обработчиков

Internet Explorer, несмотря на мощную встроенную поддержку XML, сегодня далеко не единственное средство, которое можно использовать для работы с новым языком. Обработкой XML документа на стороне клиента или сервера может также заниматься любой другой анализатор XML-документов, который конвертирует их в обычную HTML страницу или извлекает из нее информацию для других приложений.

Что такое XML Parser?

Любой XML-процессор, являясь, по сути, транслятором языка разметки, может быть разбит на несколько модулей, отвечающих за лексический, синтаксический и семантический анализ содержимого документа. Понятно, что если бы мы были вынуждены каждый раз писать все эти блоки самостоятельно, необходимость в XML как в таковом бы отпала - основное его преимущество, как уже упоминалось ранее, заключается в стандартном способе извлечения информации из документа. Синтаксически правильно составленный XML-документ может быть разобран любым универсальным XML анализатором, и нашему XML-обработчику остается лишь использовать полученные на его выходе "чистые" данные (прошедшие синтаксический анализ) - интерпретировать содержимое документа, в соответствии с его DTD-описанием или схемами данных.

Рис. 2 Иллюстрация механизма доступа к содержимому XML-документа при помощи интерфейсов анализатора

Конечно, синтаксический анализатор может быть довольно легко реализован и самостоятельно, например, в Perl, с его мощными возможностями обработки регулярных выражений. Но в общем случае такой "ручной" способ является довольно нетривиальной задачей, требующей некоторых усилий и является дополнительным источником ошибок. Поэтому применение универсальных XML-анализаторов может существенно облегчить жизнь разработчикам, тем более, что уже сегодня количество свободно доступных программ такого рода довольно велико.

В функции современного XML-процессора обычно входит получение общих сведений о документе, извлечение информации о его структуре и построения некоторой абстрактной объектной модели данных, представляющей эту структуру. По способу проверки разбираемых документов универсальные программы-анализаторы делятся на два типа: верифицирующие, способные обнаружить DTD-описания грамматики языка и использовать их для проверки документа на семантическую корректность; и неверифицирующие, не осуществляющие такой проверки.

Описывая разобранный XML-документ, универсальная программа-анализатор должна представить его структуру в виде упорядоченной модели данных, для доступа к которой используется какая-то станадртная, описанная в соответствующей спецификации библиотека классов - интерфейсов XML документа. На сегодняшний день существует два подхода к их построению: собыйтийный - Simple API for XML, SAX и объектно-ориентированный - DOM(Document Object Model). Рассмотрим их использование на конкретных примерах.

Что такое SAX

Сегодня стандартным интерфейсом для большинства универсальных XML-анализаторов является событийно-ориентированное API SAX - Simple API for XML.

Термин событийно-ориентированный является ключевым в этом определении и объясняет способ использования SAX. Каждый раз, когда при разборе XML документа анализатор оказывается в каком-то новом состоянии - обнаруживает какую-либо синтаксическую конструкцию XML-документа (элемент, символ, шаблон, и т.д.), фиксирует начало, конец объявлений элементов документа, просматривает DTD-правила или находит ошибку, он воспринимает его как произошедшее событие и вызывает внешнюю процедуру - обработчик этого события. Информация о содержимом текущей конструкции документа передается ему в качестве параметров функции. Обработчик события - это какой-то объект приложения, который выполняет необходимые для обработки полученной из XML информации действия и осуществляет таким образом непосредственный разбор содержимого. После завершения этой функции управление опять передается XML-анализатору и процесс разбора продолжается.

Реализацией этого механизма в Java SAX 1.0 является библиотека классов org.xml.sax (их можно получить, например, с узла: http://www.megginson.com/, но обычно эти классы включаются в состав XML -анализатора). Наследуя клссы SAX-совместимого анализатора, мы получаем универсальный доступ к XML документу при помощи классов, содержимое и механизм использование которых приведено в соответствующем описании.

Последовательный разбор XML-документа SAX-обработчиком обычно производится по следующей схеме (более подробное описание приведено ниже):

  • загрузить документ, установить обработчики событий, начать просмотр его содержимого (если есть DTD-описания, то - их разбор);
  • найдено начало документа (его корневой, самый первый элемент) - вызвать виртуальную функцию- обработчик события startDocument;
  • каждый раз, когда при разборе будет найден открывающий тэг элемента вызывается обработчик-функция startElement. В качестве параметров ей передаются название элемента и список его атрибутов;
  • найдено содержимое элемента - передать его соответствующему обработчику - characters, ignorableWhitespace,processingInstruction и т.д.;
  • если внутри текущего элемента есть подэлементы, то эта процедура повторяется;
  • найден закрывающий тэг элемента - обработать событие endElement();
  • найден закрывающий тэг корневого элемента -обработать событие endDocument;
  • если в процессе обработки были обнаружены ошибки, то анализатором вызываются обработчики предупреждений (warning), ошибок (error) и критических ошибок обработчика (fatalError).

Ссылка на объект класса обработчика событий может передаваться объекту XML-анализатора при помощи следующих функций:

parser.setDocumentHandler(event_class); // - обработчик событий документа

parser.setEntityResolver(event_class); // - обработчик событий загрузки DTD-описаний

parser.setDTDHandler(event_class); // - обработчик событий при анализе DTD-описаний

parser.setErrorHandler(event_class); // - обработчик чрезвычайных ситуаций

Здесь event_class - объект созданного нами ранее класса.

Краткое описание некоторых из объектов-обработчиков событий приведено в следующей таблице:

Объект DocumentHandler

startDocument() | Начало документа
endDocument() | Конец документа
startElement (String name, AttributeList atts) | Начало элемента. Функции передается название элемента(открывающий тэг) и список его атрибутов.
endElement (String name) | Конец элемента
characters (char[] cbuf, int start, int len) | Обработка массива текстовых символов
ignorableWhitespace (char[] cbuf, int start, int len) | Необрабатываемые символы
processingInstruction (String target, String data) | Обработка инструкций XML-анализатора)

Объект ErrorHandler

warning (SAXParseException e) | Получение сообщения о "несерьезной" ошибке. Пдробная информация содержится в передаваемом объекте класса SAXParseException
error (SAXParseException e) | Сообщение об ошибке
fatalError (SAXParseException e) | Сообщение о критической ошибке

Для демонстрции использования этих методов рассмотрим небольшой пример обработчика регистрационного XML-документа (его структура описана в примере 2 первого раздела статьи). Java-приложение выводит содержимое документа и информацию о его структуре, путь к документу задается в командной строке. Для компилирования потребуется JDK 1.1.4 и классы SAX, находящиеся либо в текущем пакете, либо вместе с другими классами в classes.zip.

import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import com.ibm.xml.parsers.DOMParser;

import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.AttributeList;
import org.xml.sax.HandlerBase;
import org.xml.sax.helpers.ParserFactory;

class saxParser extends HandlerBase{

    private PrintWriter out;

    private int elements;
    private int attributes;
    private int characters;
    private int ignorableWhitespace;
    private String url;

    public saxParser(String url_str) {

        url = url_str;

        try {
            out = new PrintWriter
            (new OutputStreamWriter(System.out, "koi8-r"));
        }
        catch (UnsupportedEncodingException e) {
        }

    }

//===============================================
// Обработчики событий. Методы интерфейса
DocumentHandler
//========================

    // Начало документа
    public void startDocument() {

    // Статистика
        elements            = 0;
        attributes          = 0;
        characters          = 0;
        ignorableWhitespace = 0;

    // Процессорные инструкции

    out.println("<?xml version=\"1.0\"
    encoding=\"UTF-8\"?>");

    }

    // Конец документа
    public void endDocument() {

        out.flush();

    }

// Встретился открывающий тэг элемента //

    public void startElement(String name,
     AttributeList attrs) {

        elements++;
        if (attrs != null) {
            attributes += attrs.getLength();
        }

      // Печать тэга элемента вместе со списком его
       атрибутов, например, <elem id="48">
        out.print('<');
        out.print(name);
        if (attrs != null) {
            int len = attrs.getLength();
            for (int i = 0; i < len; i++) {
                out.print(' ');
                out.print(attrs.getName(i));
                out.print("=\"");
                out.print(attrs.getValue(i));
                out.print('"');
            }
        }
        out.println('>');

    }

// Встретился закрывающий тэг элемента

    public void endElement(String name) {

        out.println("</"+name+">");

    }

// Текстовые символы

    public void characters(char ch[], int start,
    int length) {

        characters += length;

        out.println(new String(ch, start, length));

    }

// Необрабатываемые символы(например, содержимое
 секции CDATA)

    public void ignorableWhitespace(char ch[],
    int start, int length) {

        characters(ch, start, length);

    }

// Инструкции XML-процессору

    public void processingInstruction (String target,
     String data) {

        out.print("<?");
        out.print(target);
        if (data != null && data.length() > 0) {
            out.print(' ');
            out.print(data);
        }
        out.print("?>");

    }

//===================================================
// Методы интерфейса ErrorHandler
//===============================

    // Последнее предупреждение
    public void warning(SAXParseException ex) {
        System.err.println("Warning at "+
                ex.getLineNumber()+" . "+
                ex.getColumnNumber()+"  -  "+
                ex.getMessage());
    }

    // Произошла ошибка
    public void error(SAXParseException ex) {
        System.err.println("Error at {"+
                ex.getLineNumber()+" . "+
                ex.getColumnNumber()+"  -  "+
                ex.getMessage());
    }

    // Такие ошибки исправить уже нельзя
    public void fatalError(SAXParseException ex)
     throws SAXException {
        System.err.println("Fatal error at {"+
                ex.getLineNumber()+" . "+
                ex.getColumnNumber()+"  -  "+
                ex.getMessage());
        throw ex;
    }

//============================================
// Вывести информацию о документе
//===============================

    public void printInfo() {

        System.out.println();

      System.out.println("Документ "+url+"
    был успешно обработан");

      System.out.println("Элементов : "+elements);
      System.out.println("Атрибутов : "+attributes);
      System.out.println("Символов  : "+characters);

    }

}

//================================================
// Обработка XML документа
//========================

public class saxSample{

    public static void main(String argv[]) {

        try {
            saxParser sample = new saxParser(argv[0]);

            Parser parser = ParserFactory.makeParser
("com.ibm.xml.parsers.SAXParser");
            parser.setDocumentHandler(sample);
            parser.setErrorHandler(sample);

            parser.parse(argv[0]);
            sample.printInfo();
        }
        catch (Exception e) {
            e.printStackTrace(System.err);
        }

   }

}

Комментарии

Первым шагом в процессе построения XML-обработчика является создание объекта из класса анализатора (в нашем случае это классы из паекета com.ibm.xml.parsers). Для этого можно использовать класс ParserFactory, входящий в org.xml.sax.helpers:

import org.xml.sax.*;
...
Parser parser = ParseFactory.createParser();
...

Затем следует определить обработчики возникающих в процессе разбора XML-документа событий. Приложению необязательно устанавливать все обработчики сразу - в классе HandlerBase все события могут обрабатываться "по умолчанию". Более подробную информацию по использованию SAX-анализаторов можно найти в примерах приложений в пакетах анализатора или на сервере www.megginson.com. Комментарии, файлы приложений и результатов их работы можно найти по адресу http://www.mrcpk.nstu.ru/


DOM совместимые анализаторы

Другим способом представления внутренней структуры документа являются DOM - интерфейсы. Как уже упоминалось, их реализацией занимаются разработчики XML-анализатора, используя для этого возможности конкретного языка программирования. Программисты на Java могут найти эти классы в библиотеке org.w3.dom. Наследуя виртуальные методы DOM интерфейсов, классы анализатора предоставляют приложению стандартный способ манипулирования структурой документа. В свою очередь, приложение, использующее XML-анализатор, может не знать о способе реализации интерфейсов, ему доступна готовая библиотека методов, при помощи которой он может производить поиск нужных фрагментов документа, создавать, удалять и модифицировать его элементы.

Одним из доступных на сегодня DOM-совместимых наборов классов для работы с документами является библиотека com.ibm.dom, входящая в состав XML анализатора xml4j от IBM. Получить ее можно по адресу www.alphaworks.ibm.com. Принцип использования DOM интерфесов по сравнению с IE5 практически не изменился - поменялись только названия объектов и методов. Их краткий обзор представлен в следующей таблице.

Node | Базовый интерфейс для остальных элементов объектной модели XML, представляющий узел дерева структуры документа.
Document | Используется для получения информации о документе и изменения его структуры. Это интерфейс представляет собой корневой элемент XML документа и содержит методы доступа ко всему содержимому документа. При помощи методов объекта Document в программе можно создавать дочерние объекты, представляющие различные конструкции документа (например, createElement - создание элемента, createComment - создание комментария, createTextNode - текстового фрагмента), удалять, перемещать, добавлять объекты (removeChild, replaceChild, insertBefore, ...), перемещаться по дереву элементов(getFirstChild, getLastChild, getNextSibling, getParentNode, getPreviousSibling, ...), получать элементы по их названию (getElementsByTagName, :) и т.д. В объектной модели IE5 этот интерфейс доступен для сценариев на JScript, VB через объект XMLDOMDocument
Element | Представляет элемент документа, определяя методы доступа к его названию(getTagName, getElementsByTagName), атрибутам (getAttribute, getAttributeNode, setAttribute, removeAttribute, : ) и дочерним элементам(appendChild, getChildNodes, getFirstChild, ...).
Attr | Интерфейс, представляющий атрибут элемента. Имеет методы для получения(getValue) и установления(setValue) значения атрибута. Хотя согласно синтаксису XML атрибуты должны назначаться только элементам, в DOM возможно их создание любым объектом, наследующим интерфейс Node. Поэтому можно создать атрибут для документа, который будет находится в списке атрибутов, но не принадлежать ни одному из его элементов.
CharacterData | Интерфейс, предоставляющий доступ к текстовым данным документа. В XML документе к этому типу данных относятся комментарии, текстовое содержимое элементов, секции CDATA. При помощи методов этого интерфейса можно добавлять, удалять, редактировать данные(appendData, deleteData, replaceData, setData), получать размер области текста (getLength) и извлекать текстовое содержимое(getData, substringData, ...)
Comments | Интерфейс для доступа к тексту комментариев
Text | Представляет текстовое содержимое элемента
CDATASection | Интерфейс, представляющий секции CDATA - фрагментов документа, заключенные в символы "[[" и "]]>", которые не обрабатываются XML-анализатором и поэтому могут содержать символы, "запрешенные" в спецификации XML. В эту область можно, к примеру, помещать стилевые таблицы или JavaScript сценарии, используемые при отображении HTML страницы.
ProcessingInstruction | Предоставляет доступ к т.н. области "инструкций процессора", данные из которой используются XML-анализатором при разборе документа. Доступ к этим данным возможен при помощи методо getData, setData и getTarget
Notation | Определяет инструкцию DTD описания. Для получения ее идентификаторов используются методы getPublicId и getSystemId . DOM Level 1 не поддерживает прямого доступа к DTD декларациям по записи и сейчас они доступны лишь для чтения (при помощи параметра nodeName интерфейса Node)

В следующем примере демонстрируется использование DOM-объектов для вывода содержимого XML документа в двух форматах - в виде дерева элементов и обычной HTML страницы. Немного изменив пример, можно заставить программу сохранять выходной формат в файле и мы получим таким образом обычный XML-HTML конвертор.

/*
Пример использования DOM анализатора.
Демонстрируется возможность рекурсивного обхода дерева элементов,
создание новых элементов, фильтрация элементов (поиска по параметрам)
*/

import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.*;

import org.w3c.dom.*;

import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.ParserFactory;

import com.ibm.xml.parsers.DOMParser;

public class logParser {

    static String defaultParser =
     "com.ibm.xml.parsers.DOMParser";
    static String urlLog;
    static  Document xmldoc = null;
    static  PrintWriter out;

/*
Конструктор нашего класса- обработчика.
В нем создается выходной поток для печати
*/

    public logParser(String url){
     urlLog = url;

       try {
            out = new PrintWriter
            (new OutputStreamWriter(System.out,
             "koi8-r"));
        }
        catch (UnsupportedEncodingException e) {
         System.err.println(e.toString());
        }

    }

    public void parseDoc(){
     parseDoc(defaultParser);
    }

/*
Создание класса анализатора, обрабтка им XML-документа
и создание объектной модели документа
*/

    public void parseDoc(String parserName){

        try {
            Parser parser =
        ParserFactory.makeParser(parserName);
        parser.parse(urlLog);

// Получение указателя на корневой элемент документа
            xmldoc = ((DOMParser)parser).getDocument();

        }
        catch (Exception e) {
         System.err.println(e.toString());
        }
    }

//=================================================
// Вывод содержимого документа в виде форматированного
списка XML- элементов
//========================

    public void viewLogAsXML(){

        try {

         viewLogAsXML(xmldoc,"");

        }
        catch (Exception e) {
         System.out.println(e.toString());
        }
      out.flush();

    }

/*
Рекурсивный обход элементов документа, начиная с указанного
элемента node.
*/

    public void viewLogAsXML(Node node,String offs){

        if (node == null) {
            return;
        }
        int type = node.getNodeType(); //
         Получение информации о типе текущего узла
        switch (type) {
/* Если текщий узел - корневой элемент документа */

            case Node.DOCUMENT_NODE: {
                out.println("<?xml
                 version=\"1.0\"
                 encoding=\"koi-8\"?>");
                viewLogAsXML(((Document)node).
                getDocumentElement(),offs);
                out.flush();
                break;
            }

/* Если текщий узел - элемент */

            case Node.ELEMENT_NODE: {
                out.print(offs+"<");
// Печать названия элемента
                out.print(node.getNodeName());
// Получение списка атрибутов текущего элемента

                NamedNodeMap attrs = node.getAttributes();
        Node attr;
                for (int i = 0; i <
                 attrs.getLength(); i++) {
                    attr = attrs.item(i);
                    out.print(' ');

out.print(attr.getNodeName()+"=\""
+attr.getNodeValue()+"\"");
                }
                out.println('>');

// Получение списка дочерних элементов
                NodeList children = node.getChildNodes();

// Если у текщего элемента есть дочерние, то выводим и их

                if (children != null) {
                    int len = children.getLength();
                    for (int i = 0; i < len; i++) {
                        viewLogAsXML(children.item(i),
                        offs+" ");
                    }
                }
                break;
            }

/* Если текщий узел - текстовый */
            case Node.TEXT_NODE: {
                out.println(offs+node.getNodeValue());
                break;
            }

        }
// Печать закрывающего тэга элемента
        if (type == Node.ELEMENT_NODE) {
            out.print(offs+"</");
            out.print(node.getNodeName());
            out.println('>');
        }

    }

//===============================================
// Вывод в формате HTML
//=====================

/* Вызов рекурсивного обходчика */

    public void viewLog(){

// Header
        viewAsHTML("All log records:");

        try {

// Вывод содержимого
         viewLog(null);

        }
        catch (Exception e) {
         System.out.println(e.toString());
        }

// Header
        viewAsHTML();

    }

/* Печать только сообщений об ошибках */

    public void viewErrors(){

// Header
        viewAsHTML("Log errors:");

        try {
// Вывод содержимого
         viewLog("error");
        }
        catch (Exception e) {
         System.out.println(e.toString());
        }
// Footer
        viewAsHTML();

    }

/*
Рекурсивный обход элементов, у которых
атрибут type равен заданному.
*/

    public int viewLog(String type){

        int i=0;
        int elemNum=0;
        int messageCount=0;
    Element elem;
    NodeList elements;

    elements = xmldoc.getElementsByTagName
    ("event");
        if(elements==null) System.out.println
        ("Empty element collection");

        elemNum = elements.getLength();

        if (type == null) {

            for (i = 0; i < elemNum; i++) {
            if(elements.item(i)==null)
            System.out.println
            ("Empty element");
              viewLogMessage((Element)elements.item(i));
            }
            messageCount=elemNum;

        }
        else {
            for (i = 0; i < elemNum; i++) {
                elem = (Element)elements.item(i);

                if(elem.getAttribute
                ("type")==type){
                 messageCount++;
                 viewLogMessage(elem);
                }

            }
        }
       return messageCount;
    }

/* Печать заголовка таблицы */

    public void viewAsHTML(String title){
        out.println("<html>");
        out.println("<head><title>
        Log parser
         sample</title></head>");

out.println("<body><br><b>"
+title+"</b><hr>");
        out.println("<table cellspacing=\"2\"
cellpadding=\"2\" border=\"1\"
width=\"600\">");
        out.println("<tr
bgcolor=\"silver\"><th>IP</th>
<th>Date</th><th>Method</th>
<th>Request</th><th>
Response</th></tr>");
    }

/* Печать комментариев к таблице */

    public void viewAsHTML(){
        Date d = new Date();
        String date = new
String(""+d.getHours()+":"
+d.getMinutes()+":"+d.getSeconds());
        out.println("</table><hr>
        generated by logParser at
<i>"+date+"</i><br>
</body></html>");
        out.flush();
    }

/* Форматированный вывод содержимого элемента event */

    public void viewLogMessage(Element elem){

      /*
     Получение текста внутри элемента - обращаемся
 к первому	 дочернему узлу (им должен оказаться текст)
 и получаем его	 значение, используя метод
 getNodeValue() интерфейса Node
      */

      String
str_from=(elem.getElementsByTagName
("ip-from")).item(0)
    .getFirstChild().getNodeValue();
      String
str_method=(elem.getElementsByTagName
("method")).item(0).
     getFirstChild().getNodeValue();
      String
str_to=(elem.getElementsByTagName
("url-to")).item(0).
getFirstChild().getNodeValue();
      String
str_result=(elem.getElementsByTagName
("response")).item(0).
getFirstChild().getNodeValue();

out.println("<tr><td>"+str_from
+"</td><td>"+elem.getAttribute
("date")+"</td><td>"
+str_method+"</td><td>"+str_to+"</td>
<td>"+str_result+"</td></tr>");

    }

//=======================================================
// Модификация дерева элементов
//=============================

public void logMessage(String result, String datetime,
     String method, String ipfrom, String urlto,
     String response){

      if(xmldoc==null) return;

      Element root = xmldoc.getDocumentElement();
      Element log_elem = xmldoc.createElement
      ("event");
      log_elem.setAttribute("result",result);
      log_elem.setAttribute("date",datetime);

      Element elem;
      Text elem_value;

      elem = xmldoc.createElement("method");
      elem_value = xmldoc.createTextNode(method);
      elem.appendChild(elem_value);
      log_elem.appendChild(elem);

      elem = xmldoc.createElement("ip-from");
      elem_value = xmldoc.createTextNode(ipfrom);
      elem.appendChild(elem_value);
      log_elem.appendChild(elem);

      elem = xmldoc.createElement("url-to");
      elem_value = xmldoc.createTextNode(urlto);
      elem.appendChild(elem_value);
      log_elem.appendChild(elem);

      elem = xmldoc.createElement("response");
      elem_value = xmldoc.createTextNode(response);
      elem.appendChild(elem_value);
      log_elem.appendChild(elem);

      root.appendChild(log_elem);
    }

//================================================
// Пример использования методов класса logParser
//==============================================

    public static void main(String argv[]) {

/*
 Создание объекта анализатора. В качестве параметра ему
 передается название документа(можно и через
 командную строку, конечно...)
*/

 logParser log_file = new logParser("log.xml");
  log_file.parseDoc();              // Анализ документа

   if (argv.length == 0) {         // Что с ним делать
      log_file.viewLogAsXML();
            System.exit(0);
        }

        for (int i = 0; i < argv.length; i++) {
            String arg = argv[i];

            if (arg.startsWith("-")) {
                if (arg.equals("-vx")) {
                    log_file.viewLogAsXML();
                    break;
                }
                if (arg.equals("-va")) {
                    log_file.viewLog();
                    break;
                }
                if (arg.equals("-ve")) {
                    log_file.viewErrors();
                    break;
                }

            if (arg.equals("-h")) {
               usage();
                }
            }
        }

log_file.logMessage("success","12",
"GET","127.0.0.1","./index.html",
"200");
      log_file.viewLogAsXML();

    }
    private static void usage() {

        System.err.println("usage:
        java logParser (options)");
        System.err.println();
        System.err.println("options:");
        System.err.println("
    -vx View result as XML tree (default)");
        System.err.println("
    -va View all messages as HTML page");
        System.err.println("
     -ve View only errors as HTML page");
        System.err.println("
     -h  View help ");

    }

}

Комментарии

Более подробные комментарии, файлы приложений и результатов их работы можно найти по адресу www.mrcpk.nstu.ru/xml/


Рекурсия -> АЛДАН - IV

Рекурсия
Рекурсия - это вызов функции (процедуры) из самой себя. Я воспринимаю рекурсию как трюк, который позволяет программисту облегчить себе жизнь. Как у каждого трюка у рекурсии есть свои достоинства и недостатки. К достоинствам относится способность создавать простой код. К недостаткам: медленность работы.

Несмотря на все недостатки, присущие рекурсии, она мне нравится. Как говорится любят ни за что, не любят за все.

Где хоть как то объясняется слово рекурсия в качестве примера приводится программа вычисления факториала. Если захотите, сами найдете.

А первая программа будет печатать полный текст стихотворения "У попа была собака".

Вторая программа: то, что я условно назвал квадраты. Но если угодно это могут быть ромбы, треугольники и другие фигуры. Как захотите, так и сделаете.

Подсчет частей, на которые развалится доска. При помощи рекурсии решается просто. Искать ее здесь.


Квадраты -> АЛДАН - IV

Квадраты

Строится прямоугольник. Каждая его сторона делится в заданном отношении, задавая таким образом новую вершину. Затем на вновь полученных вершинах строится новый четырехугольник.

program kvadr;
uses crt,graph;
const n=10;
var l:word;
procedure new(x1,y1,x2,y2:integer;var xl,yl:integer);
{ Вычисление  новой координаты вершины }
begin
 xl:=(x1+(n-1)*x2) div n;
 yl:=(y1+(n-1)*y2) div n;
end;
{ Процедура построения четырехугольника }
procedure postroit(x1,y1,x2,y2,x3,y3,x4,y4:integer);
var l1,k1,l2,k2,l3,k3,l4,k4:integer;
label qv;
begin
  setcolor(l);
  line(x1,y1,x2,y2);
  line(x2,y2,x3,y3);
  line(x3,y3,x4,y4);
  line(x4,y4,x1,y1);
 if abs(x2-x1)<0.001 then goto qv; { Проверка окончания
                                          построения }
  new(x1,y1,x2,y2,l1,k1);
  new(x2,y2,x3,y3,l2,k2);
  new(x3,y3,x4,y4,l3,k3);
  new(x4,y4,x1,y1,l4,k4);
  delay(70);
  postroit(l1,k1,l2,k2,l3,k3,l4,k4);
qv:
end;
var k,dr,md:integer;
    x1,x2,x3,x4,y1,y2,y3,y4:integer;
begin
  l:=10;
  x1:=120;y1:=40;
  x2:=520;y2:=40;
  x3:=520;y3:=440;
  x4:=120;y4:=440;
  dr:=9;md:=2;
  initgraph(dr,md,'');
  setbkcolor(1);
  postroit(x1,y1,x2,y2,x3,y3,x4,y4);
  readln;
  closegraph;
end.
Вот и все.


У попа была собака -> АЛДАН - IV

У попа была собака

Для тех, кто не знает, привожу полный текст стихотворения:
     У попа была собака
     Он ее любил
     Она съела кусок мяса
     Он ее убил и на камне написал:
А дальше по кругу то же самое.

Конечно его можно было бы проще напечатать с помощью цикла но это не интересно. Рекурсия больше отвечает духу стихотворения. Конечный цикл рано или поздно закончится, т.е. не не выполнит задание до конца, а бесконечный подразумевает бессмысленность труда автора (попа), поскольку какой смысл трудиться, точно зная, что стихотворение никогда не будет написано до конца? С рекурсией дело хитрее. Вызывая в который раз саму себя, функция считает, что скоро будет конец, что в ближайшем вызове дело закончится полным успехом. А чтобы во время печати не пришлось ждать слишком долго, пусть программа продолжается до тех пор, пока не будет нажата какая-нибудь клавиша.

   program pop;
   uses crt;
   procedure absaz(c:word);{ Процедура,
    печатающая один абзац }
   { В качестве параметра цвет абзаца }
   begin
     textcolor(c);
     writeln(' У попа была собака');
     writeln(' Он ее любил');
     writeln(' Она съела кусок мяса');
     writeln(' Он ее убил');
     writeln(' И на камне написал:');
     delay(500);{ Задержка перед выводом
      следующего абзаца }
     if not keypressed then absaz((c+1)mod 15+1);
     { Продолжать, пока не нажата клавиша }
   end;
   begin
     clrscr;
     absaz(1);
     readkey;
   end.


Разбиение Доски -> АЛДАН - IV

Разбиение доски

Сама проблема стоит следующим образом: Дана клетчатая доска прямоугольной формы. Некоторые клетки на ней выколоты. Требуется определить на сколько частей распадется доска.

Суть алгоритма проста: сама доска (массив) заполняется единицами, выколотые клетки нулями. затем начинается просмотр всех клеток подряд. Примерно следующим образом:

  for i:=1 to m do for j:=1 to n do
   begin
     if doska.d[i,j]=1 then begin s:=s+1;
                           proverka(i,j);end;
   end;
Если находится клетка содержание которой равно единице, то запускается процедура proverka(i,j) главная цель которой - выявить кусок доски которому принадлежит клетка, заполнить, его значением s>1 (для каждого куска свое). Все это происходит по рекурсии следующим образом:
procedure traschet.proverka(a,b:byte);
begin
   doska.d[a,b]:=s;
   if doska.d[a-1,b]=1 then proverka(a-1,b);
   if doska.d[a+1,b]=1 then proverka(a+1,b);
   if doska.d[a,b-1]=1 then proverka(a,b-1);
   if doska.d[a,b+1]=1 then proverka(a,b+1);
end;
Для красоты сделал некоторое подобие интерфейса (чтоб было проще доску на части раскалывать).

Работа программы строится на трех объектах: доски - tdoska, расчета - traschet и курсора - tkyrsor.

Архив программы находится здесь: RAR-архив.


Часть I. Язык Borland Pascal
Что такое программа Borland Pascal?

Следующие несколько глав посвящены формальному определению

языка Borland Pascal. В каждой главе обсуждается один из элемен-

тов Borland Pascal. Совместно эти элементы образуют программу

Borland Pascal.

Однако, изучая части, трудно понять целое. В данной главе

дается общий обзор программы Borland Pascal, опуская детали.

Здесь приводится краткое описание каждого элемента программы, а

затем показывается, как все это компонуется вместе. Подробности

элементов языка освещаются главах 2 - 11.

Программа Borland Pascal

В своей простейшей форме программа Borland Pascal состоит из

заголовка программы, который именует программу, и основного прог-

раммного блока, выполняющего назначение программы. В основном

программном блоке находится секция кода, заключенная между ключе-

выми словами begin и end. Приведем простейшую программу, иллюст-

рирующую эти принципы:

program Privet;

begin

Writeln('Добро пожаловать в Borland Pascal');

end.

Первая строка - это заголовок программы, который именует

данную программу. Остальная часть программы - это исходный код,

который начинается ключевым словом begin и заканчивается end. Хо-

тя данная конкретная программа содержит только одну строку, их

может быть много. В любой программе Borland Pascal все действия

выполняются между begin и end.

Процедуры и функции

Код между последними операторами begin и end программы уп-

равляет логикой программы. В очень простой программе в этой сек-

ции кода может содержаться все, что вам нужно. В более крупных и

сложных программах размещение в этой секции всего программного

кода может затруднить чтение и понимание программы. К тому же ее

будет труднее разрабатывать.

Процедуры и функции позволяют разделить логику программы на

более мелкие и управляемые фрагменты и аналогичны подпрограммам в

других языках. Как и в основном блоке программы, все действия в

процедурах и функциях заключаются в begin и end. Каждый из этих

сегментов кода выполняет конкретную задачу.

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура или функция і

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Заголовок процедуры или функции іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Блок процедуры или функциями іі

іі begin іі

ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

ііі Логика ііі

ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

іі end; іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 1.1 Диаграмма процедуры или функции.

Если вы обнаружите, что в вашей диаграмме одни и те же дейс-

твия выполняются многократно, такую логику желательно выделить в

процедуру или функцию. Вы можете один раз записать этот код в

процедуре или функции, а затем многократно вызывать его в прог-

рамме.

Приведем пример функции. Следующая функция GetNumber получа-

ет число от пользователя:

function GetNumber: Real;

var

Responce: Real;

begin

Write('Введите число: ');

Readln(Response);

GetNumber := Response;

end;

Процедура или функция должна содержаться в программе перед

секцией основного кода. В основном коде она может затем использо-

ваться (вызываться).

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура или функция і

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Заголовок процедуры или функции іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Блок процедуры или функциями іі

ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

ііі Процедуры или функции (0 или более) ііі

ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

іі begin іі

ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

ііі Логика ііі

ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

іі end; іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 1.2 Простая программа на Паскале.

В следующем примере дается набросок программы, в которой ис-

пользуется функция GetNumber. Программист разделил логику прог-

раммы на три задачи:

1. Получение числа от пользователя.

2. Выполнение с этим числом необходимых вычислений.

3. Печать отчета.

Основная логика программы заключена в последнем блоке

begin..end.

Program Report;

var

A: Real;

{ другие описания }

.

.

.

function GetNumber: Real;

var

Responce: Real;

begin

Write('Введите число: ');

Readln(Response);

GetNumber := Response;

end;

procedure Calculate(X: Real);

.

.

.

procedure PrintReport;

.

.

.

begin

A: = GetNumber;

Calculate(A);

PrintReport;

end.

Основная логика программы достаточно проста для понимания.

Все детали убраны в тела процедур и функций. Использование проце-

дур и функций позволяет вам рассматривать программу более удобным

и модульным способом.

Операторы

Исходный код между begin и end содержит операторы, которые

описывают выполняемые программой действия. Это называются опера-

торной частью программы. Приведем примеры операторов:

A := B + C; { присвоить значение }

Calculate(Length, Height); { активизировать процедуру }

if X < 2 then { оператор условия }

Answer := X * Y;

begin { составной оператор }

X := 3;

Y := 4;

Z := 5;

end;

while not EOF(InFile) do { оператор цикла }

begin

ReadLn(InFile, Line);

Process(Line);

end;

В простых операторах можно присваивать значение, активизиро-

вать процедуру или функцию или передавать управление на другую

часть кода. Структурные операторы могут быть составными и содер-

жать несколько операторов, оператор цикла или оператор условия,

управляющий логикой программы, а также операторы with, упрощающие

доступ к данным в записи.

Выражения

Оператор Паскаля состоит из выражений. Выражения оператора

могут состоять из операндов и операций. Обычно в выражениях вы-

полняется сравнение либо арифметические, логические или булевские

операции.

Выражения Паскаля могут состоять из более простых выражений.

О комбинации операндов и операций вы можете прочитать в Главе 6.

Они могут быть достаточно сложными. Приведем некоторые примеры

выражений:

X + Y

Done <> Error

I <= Length

-X

Лексемы

Лексемы - это наименьшие значащие элементы в программе Пас-

каля. Они образуются операндами и операциями выражений. Лексемы -

это специальные символы, зарезервированные слова, идентификаторы,

метки и строковые константы. Приведем примеры лексем Паскаля:

function { зарезервированное слово }

( { специальный символ }

:= { специальный символ }

Calculate { идентификатор процедуры }

9 { число }

Приведем пример, из которого вы можете видеть, что операторы

состоят из выражений, которые в свою очередь состоят из лексем.

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Операторы (1 или более) і

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Выражения (1 или более) іі

ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

ііі Лексемы (1 или более) ііі

ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 1.3 Диаграмма оператора.

Типы, переменные, константы и типизированные константы

Переменная может содержать изменяемое значение. Каждая пере-

менная должна иметь тип. Тип переменной определяет множество зна-

чений, которые может иметь переменная.

Например, в следующей программе описываются переменные X и

Y, имеющие тип Integer. Таким образом, X и Y могут содержать

только целые значения (числа). Если в вашей программе предприни-

мается попытка присвоить этим переменным значения другого типа,

Borland Pascal сообщает об ошибке.

program Example;

const

A = 12; { константа A не изменяет значения }

B: Integer = 23; { типизированная константа B получает

начальное значение }

var

X, Y: Integer; { переменные X и Y имеют тип Integer }

J: Real; { переменная J имеет тип Real }

begin

X := 7; { переменной X присваивается значение }

Y := 7; { переменной Y присваивается значение }

X := Y + Y; { значение переменной X изменяется }

J := 0.075; { переменной J присваивается значение

с плавающей точкой }

end.

В этой простой и не очень полезной программе X первоначально

присваивается значение 7; двумя операторами ниже ей присваивается

новое значение: Y + Y. Как можно видеть, значение переменной мо-

жет изменяться.

A - это константа. Программа назначает ей значение 12, и это

значение изменяться не может - в ходе выполнения программы оно

остается постоянным.

B представляет собой типизированную константу. Ей присваива-

ется значение при описании, но дается также тип Integer. Типизи-

рованую константу можно рассматривать как переменную с начальным

значением. Позднее программа может изменить первоначальное значе-

ние B на какое-то другое значение.

Если вы вернетесь обратно к приведенному в начале главы при-

меру кода, то увидите, что функция GetNumber имеет раздел описа-

ний, в котором описывается переменная. Процедуры и функции могут

содержать разделы описаний также как программы и модули.

Компоновка частей

Теперь, когда вы познакомились с основными компонентами

программы Borland Pascal, давайте посмотрим, как все это работает

вместе. Приведем диаграмму программы Borland Pascal:

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Программа на Паскале і

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Заголовок программы іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Необязательные операторы uses іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїі

іі Основной блок программы іі

ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

ііі Описания ііі

ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

ііі Процедуры или функции (0 или более) ііі

іііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїііі

іііі Описания іііі

іііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩііі

ііі begin ііі

ііі ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїііі

ііі і Операторы (1 или более) іііі

ііі АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩііі

ііі end; ііі

ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

іі begin іі

іі ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіі

іі і Операторы (1 или более) ііі

іі іЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїііі

іі іі Выражения (1 или более) іііі

іі ііЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДїіііі

іі ііі Лексемы (1 или более) ііііі

іі ііАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіііі

іі іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩііі

іі АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩіі

іі end. іі

іАДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩі

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 1.4 Расширенная диаграмма программы на Паскале.

Программу на Паскале составляют заголовок программы, необя-

зательный оператор uses (о нем будет рассказано позднее) и основ-

ной блок программы. В основном блоке могут присутствовать более

мелкие блоки процедур и функций. Хотя на диаграмме это не пока-

зано, процедуры им функции могут быть вложенными в другие проце-

дуры или функции. Другими словами, блоки могут содержать другие

блоки.

В сочетании с другими лексемами и пробелами лексемы могут

образовывать выражения, формирующие оператор. Операторы, в свою

очередь, в сочетании с разделом описаний образуют блоки основной

программы или блок в процедуре или функции.

Модули

Программа Borland Pascal может использовать блоки кода в

программных модулях. Модуль (unit) можно рассматривать как ми-

ни-программу, которую может использовать ваша прикладная програм-

ма. Как и программа, он имеет заголовок (который называется заго-

ловком модуля) и основной блок, ограниченный begin и end.

Основной блок любой программы Borland Pascal может включать

в себя строку, позволяющую программе использовать один или более

модулей. Например, если вы пишете программу DOS с именем Colors и

хотите изменять цвета выводимого на экран текста, то ваша прог-

рамма может использовать стандартный модуль Crt, являющийся

частью библиотеки исполняющей системы Borland Pascal:

program Colors;

uses Crt;

begin

.

.

.

end.

Строка uses Crt сообщает Borland Pascal, что нужно включить

модуль Crt в выполняемую программу. Кроме всего прочего, модуль

Crt содержит весь необходимый код для изменения цвета в вашей

программе. Путем простого включения uses Crt ваша программа может

использовать весь код, содержащийся в модуле Crt. Поэтому опера-

тор uses называют также оператором использования. Если бы вы по-

местили весь код, необходимый для реализации функциональных воз-

можностей Crt, в свою программу, это потребовало бы огромных уси-

лий и отвлекло бы вас от основной цели программы.

Библиотеки исполняющей системы Borland Pascal включают в се-

бя несколько модулей, которые вы найдете весьма полезными. Напри-

мер, благодаря использованию модулей Dos или WinDos, ваша прог-

рамма может получить доступ к нескольким подпрограммам операцион-

ной системы и подпрограммам работы с файлами.

Вы можете также писать свои собственные модули. Применяйте

их для разделения больших программ на логические связанные фраг-

менты. Программный код, который вы помещаете в модуль, может ис-

пользоваться любой программой. Вам нужно написать исходный код

только один раз, а затем вы сможете много раз его использовать.

Синтаксические диаграммы

При изучении глав 2 - 11, где определяется язык Borland

Pascal, вы встретите синтаксические диаграммы, например:

ЪДДДї ЪДДДДДДДДДДДДДДї ЪДДДї

константа-массив ДДД>і ( ГДДДД>ітипизированнаяГДДВД>і ) ГДД>

АДДДЩ ^ і константа і і АДДДЩ

і АДДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДґ , і<ДДДДДДДДЩ

АДДДЩ

Чтобы прочитать диаграмму, следуйте по стрелкам. Часто

встречаются альтернативные пути: путь, начинающийся слева и за-

канчивающийся стрелкой справа, является допустимым. Путь пересе-

кает блоки, содержащие имена элементов, используемых для построе-

ния этой части синтаксиса.

Имена в прямоугольных рамках с текстом должны быть заменены

действительными конструкциями. Некоторые рамки содержат зарезер-

вированные слова, знаки операций и знаки пунктуации, то есть фак-

тические термы, используемые в программе. Имена в блоках - это

конструкции языка. Имена, написанные по-английски (например,

procedure), представляю собой зарезервированные слова и операции

Borland Pascal.


Глава 2. Лексемы

Лексемы - это минимальные значимые единицы текста в програм-

ме, написанной на Паскале. Они представлены такими категориями

как специальные символы, идентификаторы, метки, числа и строковые

константы.

Программа, написанная на Паскале, состоит из лексем и разде-

лителей, причем разделитель представляет собой пробел или коммен-

тарий. Две соседние лексемы, если они представляют собой зарезер-

вированное слово, идентификатор, метку или число, должны быть

отделены друг от друга одним и несколькими разделителями.

Примечание: Разделители не могут быть частью лексем,

за исключением строковых констант.

Специальные символы

Borland Pascal использует следующие подмножества набора сим-

волов кода ASCII:

* Буквы - буквы английского алфавита от A до Z и от a до z.

* Цифры - арабские цифры от 0 до 9.

* Шестнадцатиричные цифры - арабские цифры от 0 до 9, буквы

от A до F и буквы от a до f.

* Разделители - символ пробела (ASCII 32) и все управляющие

символы кода ASCII (ASCII 0-31), включая символ конца

строки или символ возврата (ASCII 13).

буква

і

АДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДї

і і і і

v v v v

ЪДДДї ЪДДДї ЪДДДї ЪДДДї

і A і ... і Z і і a і ... і z і

АДВДЩ АДВДЩ АДВДЩ АДВДЩ

і і і і

АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДД цифра

і

АДДДДДДВДДДДДДДДДДДї

і і

v v

ЪДДДї ЪДДДї

і 0 і ... і 9 і

АДВДЩ АДВДЩ

і і

АДДДДДДДДДДДБДДДДДДД>

шестнадцатиричная

цифра

і ЪДДДДДДДДДДДї

АДДДДДДДД>і цифра іДДДДДДДДДДДДДДДДДДДДДДДДДї

і АДДДДДДДДДДДЩ і

і і

АДДДВДДДДДДДДДВДДДДДДДДДВДДДДДДДДДї і

і і і і і

v v v v і

ЪДДДї ЪДДДї ЪДДДї ЪДДДї і

і A і .. і F і і a і ... і f і і

АДВДЩ АДВДЩ АДВДЩ АДВДЩ і

і і і і і

АДДДДДДДДДБДДДДДДДДДБДДДДДДДДДБДДДДДДДДДБДДДДДДДДД>

Специальные символы и зарезервированные слова представляют

собой символы, имеющие одно или несколько фиксированных значений.

Специальными символами являются следующие одиночные символы:

+ - * / = < > [ ] . , ( ) : ; ^ @ { } $ #

Следующие пары символов также представляют собой специальные

символы:

<= >= := .. (* *) (. .)

Кроме того, некоторые специальные символы являются знаками

операций. Левая квадратная скобка ([) эквивалентна паре символов,

состоящей из левой круглой скобки и точки ((.). Аналогично правая

квадратная скобка (]) эквивалентна паре символов, состоящей из

точки и правой круглой скобки (.)).

Зарезервированные слова

и стандартные директивы Borland Pascal

Следующие слова являются зарезервированными в Borland Pascal:

Зарезервированные слова Borland Pascal Таблица 1.1

and exports mod shr

array file nil string

asm for not then

begin function object to

case goto of type

const if or unit

consatructor implementation packed until

destructor in procedure uses

div inherited program var

do inline record while

downto interface repeat with

else label set xor

end library shl

В настоящем руководстве зарезервированные слова записаны

строчными буквами. Однако, для Borland Pascal безразличен регистр

клавиатуры, поэтому вы можете использовать в своей программе бук-

вы как нижнего, так и верхнего регистра.

Далее приведены стандартные директивы Borland Pascal. В от-

личие от зарезервированных слов пользователь может их переопреде-

лить. Однако делать это не рекомендуется.

Стандартные директивы Borland Pascal Таблица 1.2

ДДДДДДД

absolute far name resident

assembler forward near virtual

export index private

external interrupt public

Идентификаторы

Идентификаторы выступают в качестве имен констант, типов,

переменных, процедур, модулей, программ и полей в записях.

Идентификатор может иметь любую длину, однако только первые

его 63 символа являются значимыми. Идентификатор должен начинать-

ся с буквы и не может содержать пробелов. После первого символа

идентификатора можно использовать буквы, цифры и символы подчер-

кивания (значение ASCII $5F). Как и в зарезервированных словах, в

идентификаторах можно использовать как строчные, так и прописные

буквы (компилятор их не различает).

Идентификатор должен начинаться с буквы и не должен содер-

жать пробелов. После первого символа допускаются буквы, цифры и

символ подчеркивания (ASCII $5F). Как и зарезервированные слова,

идентификаторы безразличны к регистру клавиатуры.

Когда имеется несколько мест с указанием одного и того же

идентификатора, для задания нужного идентификатора необходимо

уточнить этот идентификатор с помощью идентификатора модуля. Нап-

ример, для уточнения идентификатора Ident с помощью идентификато-

ра модуля UnitName следует записать UnitNamt.Ident. Такой комби-

нированный идентификатор называется уточненным идентификатором.

Примечание: Модули описываются в Главе 7 "Руководства

пользователя" и в Главе 10 данного руководства.

ЪДДДДДДДДДДДї

Идентификатор ДДВДДД>і буква іДДДДДДДДДДДДДДДДДДДДДДДДДВД>

і АДДДДДДДДДДДЩ ^ ^ і

і ЪДДДДДДДДДДДДДї і і і

АД>і символ ГДЩ і ЪДДДДДДДДДДДДДї і

іподчеркиванияі ГДДДґ буква і<ДДґ

АДДДДДДДДДДДДДЩ і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

ГДДДґ цифра і<ДДґ

і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

АДДДґ символ і<ДДЩ

іподчеркиванияі

АДДДДДДДДДДДДДЩ

ЪДДДї

символ подчеркиванияДДДДД>і _ іДДДДД>

АДДДЩ

идентификатор программы ЪДДДДДДДДДДДДДї

идентификатор модуля ДДДДД>іидентификаторіДДДД>

идентификатор поля АДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДї

уточненный ДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДґ идентификатор ГДД>

идентификатор і ^ АДДДДДДДДДДДДДДДЩ

і ЪДДДДДДДДДДДДДї ЪДДДї і

АД>іидентификаторГД>і . ГДЩ

і модуля і АДДДЩ

АДДДДДДДДДДДДДЩ

Приведем несколько примеров идентификаторов:

Writeln

Exit

Real2String

System.MemAvail

Dos.Exec

WinCrt.Windows

Числа

Для чисел, представляющих собой константы целого и вещест-

венного типа, используется обычная десятичная запись. Целая конс-

танта в шестнадцатиричном формате имеет в качестве префикса знак

доллара ($). Техническое обозначение (E или е с показателем сте-

пени) в вещественных типах читается, как "на десять в степени".

Например, 7E-2 означает 7х10^-2, а 12.25E+6 или 12.25E6 оба

обозначают 12.25х10^+6. Синтаксические диаграммы для записи чисел

приведены ниже.

ЪДДДДДДДДДДДДДДДДДї

последовательность ДДДДДДДД>ішестнадцатиричнаяГДДДДДДДВДД>

шестнадцатиричных ^ і цифра і і

цифр і АДДДДДДДДДДДДДДДДДЩ і

АДДЩ

ЪДДДДДДДДДДДДДДДДДї

последовательность ДДДДДДДД>і цифра ГДДДДДДДВДДД>

цифр ^ АДДДДДДДДДДДДДДДДДЩ і

і і

АДДЩ

ЪДДДДДДДДДДДДДДДДДДї

целые без знака ДДДДВДДДД>іпоследовательностьГДДДДДДДДДДД>

і і цифр і ^

і АДДДДДДДДДДДДДДДДДДЩ і

і і

і ЪДДДї ЪДДДДДДДДДДДДДДБДДДї

АДДДД>і $ іДДДД>іпоследовательностьі

АДДДЩ ішестнадцатиричных і

і цифр і

АДДДДДДДДДДДДДДДДДДЩ

ЪДДДї

знак ДВДДД>і + ГДДДДДДД>

і АДДДЩ ^

і ЪДДДї і

АДДД>і - ГДДДЩ

АДДДЩ

вещественное без знака

і ЪДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДї

АДД>іПоследова-ГДВД>і . ГД>іпоследова-ГДДВДДДДДДДДДДДДДДДДДД>

ітельность і і АДДДЩ ітельность і і ^

і цифр і і і цифр і і і

АДДДДДДДДДДЩ і АДДДДДДДДДДЩ і і

і v ЪДДДДДДДДДДї і

АДДДДДДДДДДДДДДДДДДДДДДДДД>імасштабныйГДЩ

і множительі

АДДДДДДДДДДЩ

масштабный множитель

і ЪДДДї ЪДДДДДДДДДДДДДДДДДДї

АДДДДДДДВД>і E ГДДДДДВДДДДДДДДДДДД>іпоследовательностьГДД>

і АДДДЩ ^ і ^ і цифр і

і ЪДДДї і і ЪДДДДї і АДДДДДДДДДДДДДДДДДДЩ

АД>і е ГДЩ АД>ізнакГДДЩ

АДДДЩ АДДДДЩ

число без знака

і ЪДДДДДДДДДДДДДДДї

АДДДДДДДДДДДВД>іцелое без знакаГДДДДДДДД>

і АДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДї і

АД>івещественноеГДДДДДДДЩ

ібез знака і

АДДДДДДДДДДДДЩ

число со знаком

і ЪДДДДДДДДДДДДДДДї

АДДДДДДДДВДДДДДДДДДДДДДДДД>ічисло без знакаГДДДД>

і ^ АДДДДДДДДДДДДДДДЩ

і ЪДДДДї і

АД>ізнакГДДДДДЩ

АДДДДЩ

Числа с десятичными точками или показателями степени предс-

тавляют собой константы вещественного типа. Остальные десятичные

числа обозначают константы целого типа. Они должны принимать зна-

чения в диапазоне от -2147483648 до 2147483647.

Шестнадцатиричные числа обозначают константы целочисленного

типа. Они должны находиться в диапазоне от $00000000 до

$FFFFFFFF. Окончательный знак значения определяется шестнадцати-

ричной записью.

Метки

Меткой является последовательность цифр в диапазоне от 0 до

9999. Начальные нули не являются значащими. Метки используются с

операторами перехода goto.

ЪДДДДДДДДДДДДДДДДДДДДДДї

Метка ДДДДДДДВДДДДДДДД>і последовательность ГДДДДДДДДДД>

і і цифр і ^

і АДДДДДДДДДДДДДДДДДДДДДДЩ і

і і

і ЪДДДДДДДДДДДДДї і

АДДДДДДДДДДД>іидентификаторГДДДДДДДДДДДДЩ

АДДДДДДДДДДДДДЩ

Как расширение стандартного Паскаля, Borland Pascal позволя-

ет использовать в качестве меток идентификаторы функций.

Строки символов

Строка символов представляет собой последовательность, со-

держащую ноль и более символов из расширенного набора символов

кода ASCII, записанную в одной строке программы и заключенную в

одиночные кавычки (апострофы). Строка символов, ничего не содер-

жащая между апострофами, называется нулевой строкой. Два последо-

вательных апострофа в строке символов обозначают один символ -

апостроф. Атрибут длины строки символов выражается действительным

количеством символов между апострофами, например:

'Borland'

'You'll see'

''''

';'

' '

'' { пустая строка }

' ' { пробел }

В качестве расширения стандартного Паскаля, Borland Pascal

разрешает вставлять в строку символов управляющие символы. Символ

# с целой константой без знака в диапазоне от 0 до 255 обозначает

соответствующий этому значению символ в коде ASCII. Между симво-

лом # и целой константой не должно быть никаких разделителей.

Аналогично, если несколько управляющих символов входит строку

символов, то между ними не должно быть разделителей.

Приведем несколько примеров строк символов:

#13#10

'Line 1'#13'Line2'

#7#7'Make up!'#7#7

ЪДДДДДДДДДДДДДДДДДДДДДДї

строка символов ДДДДВДД>і строка в кавычках ГДДДВДВ>

^ і АДДДДДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДДДДДї і і

і АДД>і управляющая строка ГДДДЩ і

і АДДДДДДДДДДДДДДДДДДДДДДЩ і

АДДДДДЩ

ЪДДДї ЪДДДї

строка ДДДДДД>і ' ГДДДДДДДДДДДДДДВДДДД>і ' ГДДДД>

в кавычках АДДДЩ ^ ЪДДДДДДї і АДДДЩ

АДДґсимволі<ДЩ

істрокиі

АДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДї

символ строки ДДДВДД>ілюбой символ, кроме ' ГДДДДДДД>

і і или CR і ^

і АДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДї ЪДДДї і

АДДДДДДД>і ' ГДДДДДД>і ' ГДДДДДЩ

АДДДЩ АДДДЩ

ЪДДДї ЪДДДДДДДДДДДДДДДДДДДї

символ строки ДДДДДД>і # ГД>і беззнаковое целое ГДВДД>

^ АДДДЩ АДДДДДДДДДДДДДДДДДДДЩ і

і і

АДДДДДЩ

Примечание: CR - символ возврата каретки.

Длина символьной строки - это фактическое число символов в

строке. Строка символов любой длины совместима с любым строковым

типом и, при разрешении директивой {$X+} расширенного синтаксиса,

с типом PChar.. Кроме того, строка символов с длиной, равной 1,

совместима с любым типом Char. Строка символов длиной n, где n

больше или равен 1, допустима для любого строкового типа и упако-

ванных массивов из n символов.

Комментарии

Следующие конструкции представляют собой комментарии и поэ-

тому игнорируются компилятором:

{ любой текст, не содержащий правую фигурную скобку }

(* любой текст, не содержащий звездочку/правую круглую

скобку *)

Комментарий, содержащий знак доллара ($) сразу после откры-

вающей скобки { или (*, является директивой компилятора. За сим-

волом $ следует мнемоника команды компилятора.

Примечание: Общее описание директив компилятора дано в

Главе 2 "Справочного руководства программиста".

Строки программы

В Borland Pascal строки программы имеют максимальную длину в

126 символов.


Глава 3. Константы

Константа - это идентификатор, отмечающий значение, которое

не может изменяться. Идентификатор константы не может быть вклю-

чен в свое собственное описание.

описание константы

і ЪДДДДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДї ЪДДДї

АДДДДД>іидентификаторГДДД>і = ГДД>іконстантаГДДД>і ; ГДВДД>

^ АДДДДДДДДДДДДДЩ АДДДЩ АДДДДДДДДДЩ АДДДЩ і

і і

АДДДДДДДДДДДДДДДДДДДДДДДЩ

Идентификатор константы с предшествующим ему знаком обозна-

чает значение целого или вещественного типа.

Являясь расширением стандартного Паскаля, Borland Pascal

позволяет использовать выражения-константы. Выражение-константа

представляет собой выражение, которое может вычисляться компиля-

тором без необходимости выполнения программы. Приведем примеры

выражений-констант:

100

'A'

256 - 1

(2.5 + 1) / (2.5 - 1)

'Borland' + '' + 'Pascal'

Chr(32)

Ord('Z') - Ord('A') + 1

Простейший случай выражения-константы представляет собой

простая константа, например 100 или 'A'. В стандартном Паскале

допускается использовать только простые константы. В Borland

Pascal разрешено использование выражений-констант.

ЪДДДДДДДДДї

константа ДДДД>івыражениеГДДД>

АДДДДДДДДДЩ

Поскольку компилятор должен иметь возможность полностью вы-

числить выражение-константу во время компиляции, в качестве выра-

жений-констант не допускается использовать следующие конструкции:

- ссылки на переменные и типизированные константы (кроме

констант в адресных выражениях, описываемых в Главе 5);

- вызовы функций (кроме тех, которые отмечены далее);

- оператор получения адреса @ (кроме констант в адресных вы-

ражениях, описываемых в Главе 5).

За исключением этих ограничений для выражений-констант соб-

B.Pascal 7 & Objects/LR - 33 -

людаются те же синтаксические правила, что и для обычных выраже-

ний (описанных в Главе 6 "Выражения").

В выражениях-константах допускается использовать следующие

стандартные функции:

Abs, Chr, Hi, High, Length, Lo, Low, Odd, Ord, Pred, Ptr,

Round, SizeOf, Succ, Swap, Trunc.

Приведем некоторые примеры использования выражений-констант

в описаниях констант:

const

Min = 0;

Max = 100;

Center = (Max - Min) div 2;

Beta = Chr(255);

NumChars = Ord('Z') - Ord('A') + 1;

Message = 'Out of memory';

ErrStr = 'Error:' + Message + '.';

ErrPos = 80 - Length(Error) div 2;

ErrAttr = Blink + Red * 16 + White;

Ln10 = 2.302585092994095684;

Ln10R = 1 / Ln10;

Numeric = ['0'..'9'];

Alpha = ['A'..'Z','a'..'z'];

AlphaNum = Alpha + Numeric;


Глава 4. Типы

При описании переменной необходимо указать ее тип. Тип пере-

менной описывает набор значений, которые она может принимать, и

действия, которые могут быть над ней выполнены. Описание типа оп-

ределяет идентификатор, который обозначает этот тип.

ЪДДДДДДДДДДДДДДДї ЪДДДї ЪДДДДДї ЪДДДї

описание ДД>і идентификатор ГДД>і = ГДД>і тип ГДД>і ; ГДД>

типа АДДДДДДДДДДДДДДДЩ АДДДЩ АДДДДДЩ АДДДЩ

Указание идентификатора в левой части описания типа означа-

ет, что он определен как идентификатор типа для блока, в котором

указано это описание типа. Область действия идентификатора типа

не включает его самого, исключение составляют типы "указатель"

(которые называют также ссылочными типами).

ЪДДДДДДДДДДДДДДДДДДДДї

тип ДДДДДДДДВДДДД>і простой тип іДДДДДДДДД>

і АДДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і строковый тип ГДДДДґ

і АДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і ссылочный тип ГДДДДґ

і АДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і структурный тип ГДДДДґ

і АДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і процедурный тип ГДДДДґ

і АДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДї і

АДДДД>і идентификатор типа ГДДДДЩ

АДДДДДДДДДДДДДДДДДДДДЩ

Имеется пять следующих основных классов типов. Они описыва-

ются в следующем разделе.

Простые типы

Простые типы определяют упорядоченные множества значений.

ЪДДДДДДДДДДДДДДДДДДДї

простой тип ДДДДДВДДДД>і порядковый тип ГДДДДДДДДД>

і АДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДї і

АДДДД>і вещественный тип ГДДДДДЩ

АДДДДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДї

вещественный тип ДДДДД>і идентификатор ГДДДДД>

і вещественного типа і

АДДДДДДДДДДДДДДДДДДДДЩ

Идентификатор вещественного типа относится к числу стандарт-

ных идентификаторов, которые могут быть вещественными, с одинар-

ной точностью, с двойной точностью, с повышенной точностью и

сложными.

Примечание: В разделах "Числа" и "Строковые константы"

Главы 2 вы можете найти описание того, как обозначать конс-

танты целого и вещественного типов.

Порядковые типы

Порядковые типы представляют собой подмножество простых ти-

пов. Все простые типы, отличные от вещественных типов, являются

порядковыми и выделяются по следующим четырем характеристикам.

- Все возможные значения данного порядкового типа представ-

ляют собой упорядоченное множество, и каждое возможное

значение связано с порядковым номером, который представ-

ляет собой целочисленное значение. За исключением значе-

ний целочисленного типа, первое значение любого порядко-

вого типа имеет порядковый номер 0, следующее значение

имеет порядковый номер 1 и так далее для каждого значения

в этом порядковом типе. Порядковым номером значения цело-

численного типа является само это значение. В любом по-

рядковом типе каждому значению, кроме первого, предшест-

вует другое значение, и после каждого значения, кроме

последнего, следует другое значение в соответствии с упо-

рядоченностью типа.

- К любому значению порядкового типа можно применить стан-

дартную функцию Ord, возвращающую порядковый номер этого

значения.

- К любому значению порядкового типа можно применить стан-

дартную функцию Pred, возвращающую предшествующее этому

значению значение. Если эта функция применяется к первому

значению в этом порядковом типе, то выдается сообщение об

ошибке.

- К любому значению порядкового типа можно применить стан-

дартную функцию Succ, возвращающую следующее за этим зна-

чением значение. Если эта функция применяется к последне-

му значению в этом порядковом типе, то выдается сообщение

об ошибке.

- К любому значению порядкового типа и к ссылке на перемен-

ную порядкового типа можно применить стандартную функцию

Low, возвращающую наименьшее значение в диапазоне данного

порядкового типа.

- К любому значению порядкового типа и к ссылке на перемен-

ную порядкового типа можно применить стандартную функцию

High, возвращающую наибольшее значение в диапазоне данно-

го порядкового типа.

Синтаксис порядкового типа имеет следующий вид:

ЪДДДДДДДДДДДДДДДДДДДДї

порядковый ДДДДДВДДДД>і отрезок типа ГДДДДДДДДД>

тип і АДДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і перечислимый тип ГДДДДДґ

і АДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДї і

АДДДД>і идентификатор ГДДДДДЩ

і порядкового типа і

АДДДДДДДДДДДДДДДДДДДДЩ

Borland Pascal имеет 10 встроенных порядковых типов: Integer

(целое), Shortint (короткое целое), Longint (длинное целое), Byte

(длиной в байт), Word (длиной в слово), Boolean (булевское),

ByteBool (булевское размером в байт), WordBool (булевское разме-

ром в слово), LongBool (длинный булевский тип) и Char (символьный

тип). Кроме того, имеется два других класса определяемых пользо-

вателем порядковых типов: перечислимые типы и отрезки типов (под-

диапазоны).

Целочисленные типы

В Borland Pascal имеется пять предопределенных целочисленных

типов: Shortint (короткое целое), Integer (целое), Longint (длин-

ное целое), Byte (длиной в байт) и Word (длиной в слово). Каждый

тип обозначает определенное подмножество целых чисел, как это по-

казано в следующей таблице.

Предопределенные целочисленные типы Таблица 4.1

ЪДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДї

і Тип і Диапазон і Формат і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

і короткое целое і -128 .. 127 і 8 бит со знаком і

і (Shortint) і і і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

і целое і -32768 .. 32767 і 16 бит со знаком і

і (Integer) і і і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

і длинное целое і -2147483648 .. і 32 бита со знаком і

і (Longint) і ..2147483647 і і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

і длиной в байт і 0 .. 255 і 8 бит без знака і

і (Byte) і і і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

і длиной в слово і 0 .. 65535 і 16 бит без знака і

і (Word) і і і

АДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДЩ

Арифметические действия над операндами целочисленного типа

предполагают 8-битовую, 16-битовую и 32-битовую точность в соот-

ветствии со следующими правилами:

- Тип целой константы представляет собой встроенный целочис-

ленный тип с наименьшим диапазоном, включающим значение

этой целой константы.

- В случае бинарной операции (операции, использующей два

операнда), оба операнда преобразуются к их общему типу пе-

ред тем, как над ними совершается действие. Общим типом

является встроенный целочисленный тип с наименьшим диапа-

зоном, включающим все возможные значения обоих типов. Нап-

ример, общим типом для целого и целого длиной в байт явля-

ется целое, а общим типом для целого и целого длиной в

слово является длинное целое. Действие выполняется в соот-

ветствии с точностью общего типа и типом результата явля-

ется общий тип.

- Выражение справа в операторе присваивания вычисляется не-

зависимо от размера или типа переменной слева.

- Любые операнды размером в байт преобразуются к промежуточ-

ному операнду размером в слово, который совместим перед

выполнением арифметической операции с типами Integer и

Word.

Значение одного целочисленного типа может быть явным образом

преобразовано к другому целочисленному типу с помощью приведения

типов.

Примечание: Приведение типов описывается в Главах 5 и 6.

Булевские типы

Существует 4 предопределенных булевских типа: Boolean,

ByteBool, WordBool и LongBool. Значения булевского типа обознача-

ются встроенными идентификаторами констант False и True. Посколь-

ку булевский тип является перечислимым, между этими значениями

имеют место следующие отношения:

- False < True

- Ord(False) = 0

- Ord(True) = 1

- Succ(False) = True

- Pred(True) = False

Переменные типа Boolean и ByteBool занимают 1 байт, пере-

менная WordBool занимает два байта (слово), а переменная LongBool

занимает четыре байта (два слова). Boolean - это наиболее предпо-

чтительный тип, использующей меньше памяти; типа ByteBool,

WordBool и LongBool обеспечивают совместимость с другими языками

и средой Windows.

Предполагается, что переменная типа Boolean имеет порядковые

значения 0 и 1, но переменные типа ByteBool, WordBool и LongBool

могут иметь другие порядковые значения. Когда выражение типа

ByteBool, WordBool или LongBool равна 1, то подразумевается, что

она имеет значение True, а если оно равно 0 - то False. Когда

значение типа ByteBool, WordBool или LongBool используется в кон-

тексте, где ожидается значение Boolean, компилятор будет автома-

тически генерировать код, преобразующий любое ненулевое значение

в значение True.

Символьный тип (char)

Множеством значений этого типа являются символы, упорядочен-

ные в соответствии с расширенным набором символов кода ASCII. При

вызове функции Ord(Ch), где Ch - значение символьного типа, возв-

ращается порядковый номер Ch.

Строковая константа с длиной 1 может обозначать значение

константы символьного типа. Любое значение символьного типа может

быть получено с помощью стандартной функции Chr.

Перечислимые типы

Перечислимые типы определяют упорядоченные множества значе-

ний через перечисление идентификаторов, которые обозначают эти

значения. Упорядочение множеств выполняется в соответствии с пос-

ледовательностью, в которой перечисляются идентификаторы.

ЪДДДї ЪДДДДДДДДДДДДДДДї ЪДДДї

перечислимый ДД>і ( ГДДД>і список ГДДД>і ) ГДДД>

тип АДДДЩ іидентификаторові АДДДЩ

АДДДДДДДДДДДДДДДЩ

список ЪДДДДДДДДДДДДДї

идентификаторов ДДДДДДДД>іидентификаторГДДДВДДДД>

^ АДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДґ , і<ДДДДДДДДДДДЩ

АДДДЩ

При указании идентификатора в списке идентификаторов пере-

числимого типа он описывается как константа для блока, в котором

указано описание перечислимого типа. Типом этой константы являет-

ся описанный перечислимый тип.

Порядковый номер перечислимой константы определяется ее по-

зицией в списке идентификаторов при описании. Перечислимый тип, в

котором описывается константа, становится ее типом. Первая пере-

числимая константа в списке имеет порядковый номер 0.

Приведем пример перечислимого типа:

type

suit = (club, diamond, heart, spade);

Согласно этим описаниям diamond является константой типа

suit.

При применении функции Ord к значению перечислимого типа Ord

возвращает целое число, которое показывает, какое положение зани-

мает это значение в отношении других значений этого перечислимого

типа. Согласно предшествующим описаниям, Ord(club) возвращает 0,

Ord(diamond) возвращает 1 и так далее.

Отрезки типа

Отрезок типа представляет собой диапазон значений из поряд-

кового типа, называемого главным типом. Определение отрезка типа

включает наименьшее и наибольшее значение в поддиапазоне. Оно

имеет следующий синтаксис:

отрезок ЪДДДДДДДДДДДї ЪДДДДї ЪДДДДДДДДДДДї

типа ДДДДДДДДДДД>і константа ГДДД>і .. ГДДД>і константа ГДДД>

АДДДДДДДДДДДЩ АДДДДЩ АДДДДДДДДДДДЩ

Обе константы должны иметь один и тот же порядковый тип. От-

резки типов, имеющие вид a..b, предполагают, что a меньше или

равно b.

Приведем примеры отрезков типов:

0..99

-128..127

club..heart

Переменная отрезка типа имеет все свойства переменных глав-

ного типа, однако ее значение на этапе выполнения должно принад-

лежать указанному интервалу.

Разрешение использования выражений-констант там, где стан-

дартный Паскаль допускает только простые константы, приводит к

некоторой синтаксической неоднозначности. Рассмотрим следующие

описания:

const

X = 50;

Y = 10;

type

Color = (Red, Green, Blue);

Scale = (X - Y) * 2..(X + Y) * 2;

Согласно синтаксису стандартного Паскаля, если определение

типа начинается с круглой скобки, то это перечислимый тип (такой

как Color в данном примере). Однако Scale предназначен для опре-

деления отрезка типа. Решение состоит в том, чтобы переупорядо-

чить первое выражение поддиапазона или задать другую константу,

равную значению данного выражения, и использовать эту константу в

определении типа:

type

Scale = 2 * (X - Y)..(X + Y);

Вещественные типы

К вещественному типу относится подмножество вещественных чи-

сел, которые могут быть представлены в формате с плавающей точкой

с фиксированным числом цифр. Запись значения в формате с плаваю-

щей запятой обычно включает три значения - m, b и e - таким обра-

зом, что m x b^e=n, где b всегда равен 2, а m и e являются цело-

численными значениями в диапазоне вещественного типа. Эти

значения m и e далее определяют диапазон представления и точность

вещественного типа.

Имеется пять видов вещественных типов: вещественное (Real),

с одинарной точностью (Single), с двойной точностью (Double), с

повышенной точностью (Extended) и сложное (Comp). Действия над

типами с одинарной точностью, с двойной точностью и с повышенной

точностью и над сложным типом могут выполняться только при нали-

чии числового сопроцессора 8087 (который был описан ранее).

Вещественные типы различаются диапазоном и точностью связан-

ных с ними значений (см. Таблицу 4.2).

Диапазон представления

и десятичные цифры для вещественных типов Таблица 4.2

ЪДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДї

і Тип і Диапазон і Цифры і

ГДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і вещественное і2.9x10^-39 .. 1.7x10^38 іот 11 до 12і

і (Real) і і і

ГДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і с одинарной точностью і1.5x10^-45 .. 3.4x10^38 іот 7 до 8 і

і (Single) і і і

ГДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і с двойной точностью і5.0x10^-324 .. 1.7x10^308 іот 15 до 16і

і (Double) і і і

ГДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і с повышенной точностьюі1.9x10^-4951 .. 1.1x10^4932іот 19 до 20і

і (Extended) і і і

ГДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і сложный тип і -2^63 + 1 .. 2^63 - 1 і і

і (Comp) і і і

АДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДЩ

Примечание: Сложный тип содержит только целочисленные

значения в диапазоне от -2^63+1 до 2^63-1, что приблизи-

тельно равно -9.2x10^18 и 9.2x10^18.

Borland Pascal поддерживает две модели генерации кода для

выполнения действий над вещественными типами: программную для чи-

сел с плавающей точкой и аппаратную для чисел с плавающей точкой.

Выбор соответствующей модели осуществляется с помощью директивы

компилятора $N.

Программная поддержка чисел с плавающей точкой

В состоянии {$N-}, которое устанавливается по умолчанию, ге-

нерируемый код выполняет все вычисления с вещественными типами

программно, через вызов подпрограмм библиотеки исполняющей систе-

мы. Из-за соображений скорости и размера кода в этом состоянии

допускаются только действия над переменными типа real (веществен-

ное). Любая попытка оттранслировать операторы, выполняющие дейс-

твия над типами с одинарной точностью, с двойной точностью, с по-

вышенной точностью и над сложными типами, вызовет сообщение об

ошибке.

Аппаратная поддержка чисел с плавающей точкой

В состоянии {$N+} генерируемый код выполняет все вычисления

над вещественными типами с помощью числового сопроцессора 8087.

Это состояние позволяет использовать все пять вещественных типов,

однако оно требует наличия сопроцессора 8087 на этапе компиляции

и выполнения.

Borland Pascal включает в себя библиотеки исполняющей систе-

мы, которые автоматически эмулируют программным путем сопроцессор

80х87, если при выполнении прикладной программы DOS реального или

защищенного режима он отсутствует. Для определения того, следует

ли в программу DOS включить эмулятор сопроцессора 80x87, исполь-

зуется директива компилятора $E. Если вы создает прикладную прог-

рамму для реального или защищенного режима DOS, и сопроцессор

80х87 отсутствует, разрешение директивы компилятора $E обеспечи-

вает полную программную эмуляцию сопроцессора 80x87. Для программ

Windows директива $E не действует, так как Windows обеспечивает

собственные подпрограммы эмуляции.

Примечание: Более детальное описание генерации кода

при аппаратной поддержке чисел с плавающей запятой вы може-

те найти в Главе 15 "Использование сопроцессора 8087 в

Borland Pascal".

Строковые типы

Значением строкового типа является последовательность симво-

лов с динамическим атрибутом длины (в зависимости от действитель-

ного числа символов при выполнении программы) и постоянным атри-

бутом размера в диапазоне от 1 до 255. Текущее значение атрибута

длины можно получить с помощью стандартной функции Length.

ЪДДДДДДї

строковый тип ДДД>іstringГДДВД>

АДДДДДДЩ і ^

і ЪДДДї ЪДДДДДї ЪДДДї і

АД>і [ ГДД>іцелоеГДД>і ] ГДЩ

АДДДЩ і без і АДДДЩ

ізнакаі

АДДДДДЩ

Примечание: Операторы работы со строковыми типами опи-

сываются разделах "Строковые операторы" и "Операторы отно-

шений" Главы 6.

Отношение между любыми двумя строковыми значениями устанав-

ливается согласно отношению порядка между значениями символов в

соответствующих позициях. В двух строках разной длины каждый сим-

вол более длинной строки без соответствующего символа в более ко-

роткой строке принимает значение "больше"; например, 'Xs' больше,

чем 'X'. Нулевые строки могут быть равны только другим нулевым

строкам, и они являются наименьшими строковыми значениями.

Примечание: Стандартные процедуры и функции для работы

со строковыми типами описаны в разделе "Строковые процедуры

и функции".

К символам в строках можно обращаться как к элементам масси-

ва. См. раздел "Массивы, строки и индексы" в Главе 5.

К идентификатору строкового типа и к ссылке на переменную

строкового типа можно применять стандартные функции Low и High. В

этом случае функция Low возвращает 0, а High возвращает атрибут

размера (максимальную длину) данной строки.

Параметр-переменная, описанная с помощью идентификатора

OpenString и ключевого слова string в состоянии {$P+}, является

открытым строковым параметром. Открытые строковые параметры поз-

воляют передавать одной и той же процедуре или функции строковые

переменные изменяющегося размера.

Примечание: Открытые строковые параметры описываются в

Главе 9.

Структурные типы

Структурный тип, характеризуемый методом структурирования и

типами своих компонентов, имеет более одного значения. Если тип

компонента является структурным, то получаемый в результате

структурный тип имеет более одного уровня структурирования.

Структурный тип может иметь неограниченные уровни структурирова-

ния.

ЪДДДДДДДДДДДДДДДї

структурный ДДВДДДДДДДДДДДДДДДДВДД>і тип массив ГДДДДД>

тип і ЪДДДДДДДДї ^ і АДДДДДДДДДДДДДДДЩ ^

АД>і packed ГДЩ і ЪДДДДДДДДДДДДДДДї і

АДДДДДДДДЩ ГДД>і множественный ГДДґ

і і тип і і

і АДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДї і

ГДД>і файловый тип ГДДґ

і АДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДї і

ГДД>і тип "запись" ГДДґ

і АДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДї і

АДД>і объектный тип ГДДЩ

АДДДДДДДДДДДДДДДЩ

Слово packed (упакованный) в описании структурного типа тре-

бует от компилятора уплотнить хранимые данные, даже за счет

уменьшения скорости доступа к компоненту в переменной этого типа.

Слово packed не имеет никакого действия в Borland Pascal, пос-

кольку упаковка выполняется здесь автоматически всюду, где это

возможно.

Типы массив

Массивы содержат фиксированное число элементов одного типа,

так называемого типа элемента. На приводимой ниже синтаксической

диаграмме тип элемента следует за словом of.

ЪДДДДДДДї ЪДДДї ЪДДДДДДДї ЪДДДї ЪДДДДї ЪДДДДДї

тип ДД>і array ГД>і [ ГДДД>і тип ГДВД>і ] ГД>і of ГД>і тип Г>

массив АДДДДДДДЩ АДДДЩ ^ іиндексаі і АДДДЩ АДДДДЩ АДДДДДЩ

і АДДДДДДДЩ і

і ЪДДДї і

АДДДДґ , і<ДДЩ

АДДДЩ

тип ЪДДДДДДДДДДДДДДДДї

индекса ДДД>і порядковый тип ГДДД>

АДДДДДДДДДДДДДДДДЩ

В индексных типах, по одному для каждой размерности массива,

указывается число элементов. Допустимыми индексными типами явля-

ются все порядковые типы, за исключением длинного целого и подди-

апазонов длинного целого. Массив может быть проиндексирован по

каждой размерности всеми значениями соответствующего индексного

типа; число элементов поэтому равно числу значений в каждом ин-

дексном типе. Число размерностей не ограничено.

Приведем пример типа массив:

array[1..100] of Real

Если тип элемента в типе массив также является массивом, то

результат можно рассматривать как массив массивов или как один

многомерный массив. Например,

array[boolean] of array[1..100] of array[Size] of Real

интерпретируется компилятором точно так же, как массив:

array[boolean,1..10,Size] of Real

Кроме того, можно записать выражение:

packed array[1..10] of packed array[1..8] of Boolean

как

packed array[1..10,1..8] of Boolean

Для доступа к элементам массива необходимо указать идентифи-

катор массива с одним или несколькими индексами в скобках (см.

раздел "Массивы, строки и индексы").

Тип массив, имеющий вид:

packed array[M..N] of Char

где M меньше N, называется упакованным строковым типом (слово

packed можно опустить, поскольку оно не оказывает действия в

Borland Pascal). Упакованный строковый тип имеет некоторые свойс-

тва, не характерные для других типов массив (см. раздел "Тождест-

венные и совместимые типы" далее в этой главе).

Массив вида:

array[0..X] of Char

где X - положительное целое число, называется массивом с нулевой

базой. Массивы с нулевой базой используются для хранения строк с

завершающим нулем, и, когда разрешен расширенный синтаксис (с по-

мощью директивы компилятора {$X+}), символьный массив с нулевой

базой совместим со значением типа PChar. Полностью эта тема об-

суждается в Главе 18 "Использование строк с завершающим нулем".

Параметр, описанный с помощью синтаксиса array of T, называ-

ется открытым строковым параметром. Открытые строковые параметры

позволяют передавать одной и той же процедуре или функции строко-

вые переменные изменяющегося размера.

Примечание: Открытые строковые параметры описываются в

Главе 9.

Типы запись

Тип запись содержит установленное число элементов или полей,

которые могут быть различных типов. Описание типа запись указыва-

ет тип каждого поля и идентификатор, который именует поле.

ЪДДДДДДДДї ЪДДДДДї

тип запись ДДД>і record ГДДВДДДДДДДДДДДДДДДД>і end ГДД>

АДДДДДДДДЩ і ЪДДДДДДДДї ^ АДДДДДЩ

АД>і список ГДЩ

і полей і

АДДДДДДДДЩ

список ЪДДДДДДДДДДДДї

полейВД>і фиксирован-ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДД>

і і ная часть і і ЪДДДї ЪДДДДДДДДДДДДї ^ і ЪДДДї ^

і АДДДДДДДДДДДДЩ АД>і ; ГДДД>і вариантная ГДЩ АД>і ; ГДЩ

і АДДДЩ ^ і часть і АДДДЩ

АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ АДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДї ЪДДДї ЪДДДДДї

фиксированная ДДДД>і список ГДД>і : ГДДД>і тип ГДДВДД>

часть ^ і идентификаторов і АДДДЩ АДДДДДЩ і

і АДДДДДДДДДДДДДДДДДЩ і

і і

і ЪДДДї і

АДДДДДДДДДДДДґ ; і<ДДДДДДДДДДДДДДДДДДДДДДДДЩ

АДДДЩ

Фиксированная часть типа запись содержит список фиксирован-

ных полей вместе с идентификатором и типом для каждого поля. Каж-

дое поле содержит информацию, которая всегда отыскивается одним и

тем же способом.

Приведем пример типа запись:

record

year: integer; { год }

month: 1..12; { месяц }

day: 1..31; { число }

end

В вариантной части, изображенной на синтаксической диаграмме

описания типа запись, память распределяется более чем для одного

списка полей, поэтому доступ к информации может быть осуществлен

более чем одним способом. Каждый список полей является вариантом.

Варианты налагаются друг на друга в памяти, поэтому в любое время

возможен доступ ко всем полям во всех вариантах.

вариантная часть

і ЪДДДДї ЪДДДДДДДДї ЪДДї ЪДДДДДДДї

АД>іcaseГДВДДДДДДДДДДДДДДДДДДД>ітип поляГД>іofГДДДД>івариантГДВ>

АДДДДЩ і ^ іпризнакаі АДДЩ ^ АДДДДДДДЩ і

і ЪДДДДДДДї ЪДДДї і АДДДДДДДДЩ і ЪДДДї і

А>іиденти-Г>і : ГДЩ АДДДДґ ; і<ДДДЩ

іфикаторі АДДДЩ АДДДЩ

АДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДї

тип поля ДДДД>і идентификатор ГДДДД>

признака іпорядкового типаі

АДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДї ЪДДДї ЪДДДї ЪДДДї

вариант ДДДД>іконстантаГДВД>і : ГД>і ( ГДВДДДДДДДДДДДДД>і ) ГДД>

^ АДДДДДДДДДЩ і АДДДЩ АДДДЩ і ^ АДДДЩ

і ЪДДДї і і і

АДДДДґ , і<ДДДДЩ і ЪДДДДДДї і

АДДДЩ АД>ісписокГДЩ

іполей і

АДДДДДДЩ

Вы можете видеть на диаграмме, что каждый вариант идентифи-

цирован по крайней мере одной константой. Все константы должны

быть отличными друг от друга и иметь порядковый тип, совместимый

с типом поля признака. Доступ к вариантным и фиксированным полям

один и тот же.

В вариантной части можно указать необязательный идентифика-

тор - идентификатор признака поля. При наличии идентификатора

признака поля он становится идентификатором дополнительного фик-

сированного поля записи - поля признака. Программа может исполь-

зовать значение поля признака для указания, какой вариант являет-

ся активным в настоящий момент. Без указания поля признака

программа выбирает вариант по другому критерию.

Ниже приводятся несколько примеров типов запись:

record

firstName,lastName : string[40];

birthDate : Date;

case citizen : boolean of

True : (birthPlace: string[40]);

False : (country : string[20];

entryPort : string[20];

entryDate : Date;

exitDate : Date);

end

record

x,y : real;

case kind : Figure of

rectangle : (height,wigth: real); { прямоугольник }

triangle : (size1,side2,angle: real); { треугольник }

circle : (radius: real); { круг }

end

Объектные типы

Объектный тип является структурой, состоящей из фиксирован-

ного числа компонентов. Каждый компонент является либо полем, со-

держащим данные строго определенного типа, либо методом, выполня-

ющим операции над объектом. По аналогии с описанием переменных,

описание поля указывает тип данного этого поля и идентификатор,

именующий поле: по аналогии с описанием процедуры или функции,

описание метода указывает заголовок процедуры, функции, конструк-

тора или деструктора.

Объектный тип может наследовать компоненты другого объектно-

го типа. Если T2 наследует от T1, то T2 является потомком T1, а

T1 является родителем T2.

Наследование является транзитивным, то есть если T3 наследу-

ет от T2, а T2 наследует от T1, то T3 наследует от T1. Область

(домен) объектного типа состоит из него самого и из всех его нас-

ледников.

ЪДДДДДДї ЪДДДДДДДДДДДДДДДДї

тип объектаДД>іobjectГДВДДДДДДДДДДДДДДДДДДД>ісписок компонентГДї

АДДДДДДЩ і ЪДДДДДДДДДДДДї ^ АДДДДДДДДДДДДДДДДЩ і

АД>іHаследованиеГДЩ і

АДДДДДДДДДДДДЩ і

ЪДДДДДДДДДДДДДДЩ

і ЪДДДї

АДВДДДДДВДґendГ>

і ЪДДДДДДДї ЪДДДДДДДДДДДДДДДДї і АДДДЩ

АДД>іprivateГДД>ісписок компонентГДЩ

АДДДДДДДЩ АДДДДДДДДДДДДДДДДЩ

ЪДДДї Ъї ЪДДДї

наследование ДД>і ( ГД>іидентификатор объектного типаГД>і ) ГДД>

АДДДЩ АЩ АДДДЩ

список компонент ДДВДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДД>

і ЪДДДДДДДДї ^ і ЪДДДДДДДДДї ^

АД>і список ГДДЩ АД>і список ГДДЩ

і полей і і методов і

АДДДДДДДДЩ АДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДї ЪДДДї ЪДДДДї ЪДДДї

список полей ДДД>іcписок идентификаторовГД>і : ГД>іtypeГ>і ; ГВ>

^ АДДДДДДДДДДДДДДДДДДДДДДЩ АДДДЩ АДДДДЩ АДДДЩі

і і

АДДДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДї ЪДДДї

список методов ДД>ізаголовокГДВДДДДДДДДДДДДДДДДДДДДДДДДДДґ ; ГВД>

^ і метода і і ЪДДДї ЪДДДДДДДї ^АДДДЩі

і АДДДДДДДДДЩ А>і ; ГД>іvirtualГВДДДДДДДЩ і

і АДДДЩ АДДДДДДДЩі ^ і

і і АДДДДДДДДїі

і і ЪДДДДДДДДДїіі

і А>і целая ГЩі

і іконстантаі і

і АДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДДДї

заголовок метода ДДДДВДДД>і заголовок процедуры ГДДДДДД>

і АДДДДДДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДДДДДДї і

ГДДД>і заголовок функции ГДДґ

і АДДДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДДДДї і

ГДДД>і заголовок конструктора ГДДґ

і АДДДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДДДДї і

АДДД>і заголовок деструктора ГДДЩ

АДДДДДДДДДДДДДДДДДДДДДДДДЩ

Следующий исходный код приводит пример описания объектного

типа. Далее во всей этой главе на данное описание будут делаться

ссылки.

type

Point = object

X, Y: integer;

end;

Rect = object

A, B: TPoint;

procedure Init(XA, YA, XB, YB: Integer);

procedure Copy(var R: TRectangle);

procedure Move(DX, DY: Integer);

procedure Grow(DX, DY: Integer);

procedure Intersect(var R: TRectangle);

procedure Union(var R: TRectangle);

function Contains(P: Point): Boolean;

end;

StringPtr = ^String;

FieldPtr = ^TField;

TField = object

X, Y, Len: Integer;

Name: StringPtr;

constructor Copy(var F: TField);

constructor Init(FX, FY, FLen: Integer; FName: String);

destructor Done; virtual;

procedure Display; virtual;

procedure Edit; virtual;

function GetStr: String; virtual;

function PutStr(S: String): Boolean; virtual;

end;

StrFieldPtr = ^TStrField;

StrField = object(TField)

Value: PString;

constructor Init(FX, FY, FLen: Integer; FName: String);

destructor Done; virtual;

function GetStr: String; virtual;

function PutStr(S: String): Boolean;

virtual;

function Get: string;

procedure Put(S: String);

end;

NumFieldPtr = ^TNumField;

TNumField = object(TField)

private

Value, Min, Max: Longint;

public

constructor Init(FX, FY, FLen: Integer; FName: String;

FMin, FMax: Longint);

function GetStr: String; virtual;

function PutStr(S: String): Boolean; virtual;

function Get: Longint;

function Put(N: Longint);

end;

ZipFieldPtr = ^TZipField;

ZipField = object(TNumField)

function GetStr: String; virtual;

function PutStr(S: String): Boolean;

virtual;

end;

В отличие от других типов, объектные типы могут описываться

только в разделе описаний типов, находящемся на самом внешнем

уровне области действия программы или модуля. Таким образом, объ-

ектные типы не могут описываться в разделе описаний переменных

или внутри блока процедуры, функции или метода.

Тип компоненты файлового типа не может иметь объектный тип

или любой структурный тип, содержащий компоненты объектного типа.

Компоненты и область действия

Область действия идентификатора компоненты простирается за

пределы объектного типа. Более того, область действия идентифика-

тора компонента простирается сквозь блоки процедур, функций,

конструкторов и деструкторов, которые реализуют методы объектного

типа и его наследников. Исходя из этих соображений, написание

идентификатора компоненты должно быть уникальным внутри объектно-

го типа и внутри всех его наследников, а также внутри всех его

методов.

Область действия идентификатора компонента, описанного в

части private описания типа, ограничивается модулем (программой),

которая содержит описание объектного типа. Другими словами, част-

ные (private) компоненты-идентификаторы действуют, как обычные

общедоступные идентификаторы в рамках модуля, который содержит

описание объектного типа, а вне модуля любые частные компоненты и

идентификаторы неизвестны и недоступны. Поместив в один модуль

связанные типы объектов, можно сделать так, что эти объекты смо-

гут обращаться к частным компонентам друг друга, и эти частные

компоненты будут неизвестны другим модулям.

В описании объектного типа заголовок метода может задавать

параметры описываемого объектного типа, даже если описание еще не

полное. Это иллюстрируется методами Copy, Intersect и Union типа

TRectange в предыдущем примере.

Методы

Описание метода внутри объектного типа соответствует опере-

жающему описанию метода (forward). Таким образом, где-нибудь пос-

ле описания объектного типа, но внутри той же самой области дейс-

твия, что и область действия описания объектного типа, метод дол-

жен реализоваться путем определения его описания.

Если требуется уникальный идентификатор метода, то использу-

ется уточненный идентификатор метода. Он состоит из идентификато-

ра типа объекта, за которым следуют точка и идентификатор метода.

Как и любому другому идентификатору, идентификатору уточненного

метода, если требуется, могут предшествовать идентификатор пакета

и точка.

уточненный идентификатор метода

і Ъї ЪДДДї ЪДДДДДДДДДДДДДДДДДДДДї

АД>іидентификатор объектного типаГ>і . Г>іидентификатор методаГ>

АЩ АДДДЩ АДДДДДДДДДДДДДДДДДДДДЩ

Виртуальные методы

По умолчанию, методы являются статическими, однако они мо-

гут, за исключением конструкторов, быть виртуальными (посредством

включения директивы virtual в описание метода). Компилятор разре-

шает ссылки на вызовы статических методов во время процесса ком-

пиляции, тогда как вызовы виртуальных методов разрешаются во вре-

мя выполнения. Это иногда называют поздним связыванием.

Если объектный тип объявляет или наследует какой-либо вирту-

альный метод, то переменные этого типа должны быть инициализиро-

ваны посредством вызова конструктора перед вызовом любого вирту-

ального метода. Таким образом, объектный тип, который описывает

или наследует виртуальный метод, должен также описывать или нас-

ледовать по крайней мере один метод-конструктор.

Объектный тип может переопределять любой из методов, которые

он наследует от своих родителей. Если описание метода в потомке

указывает тот же идентификатор метода, что и описание метода в

родителе, то описание в потомке переопределяет описание в родите-

ле. Область действия переопределяющего метода расширяется до сфе-

ры действия потомка, в котором этот метод был введен, и будет ос-

таваться таковой, пока идентификатор метода не будет переопреде-

лен снова.

Переопределение статического метода не зависит от изменения

заголовка метода. В противоположность этому, переопределение вир-

туального метода должно сохранять порядок, типы и имена парамет-

ров, а также типы результатов функций, если таковые имеются. Бо-

лее того, переопределение опять же должно включать директиву

virtual.

Динамические методы

Borland Pascal поддерживает дополнительные методы с поздним

связыванием, которые называются динамическими методами. Динами-

ческие методы отличаются от виртуальных только характером их дис-

петчеризации на этапе выполнения. Во всех других отношениях дина-

мические методы считаются эквивалентными виртуальным.

Описание динамического метода эквивалентно описанию вирту-

ального метода, но описание динамического метода должно включать

в себя индекс динамического метода, который указывается непос-

редственно за ключевым словом virtual. Индекс динамического мето-

да должен быть целочисленной константой в диапазоне от 1 до

656535 и должен быть уникальным среди индексов других динамичес-

ких методов, содержащихся в объектном типе или его предках. Нап-

ример:

procedure FileOpen(var Msg: TMessage); virtual 100;

Переопределение динамического метода должно соответствовать

порядку, типа и именам параметров и точно соответствовать типу

результата функции порождающего метода. Переопределение также

должно включать в себя директиву virtual, за которой следует тот

же индекс динамического метода, который был задан в объектном ти-

пе предка.

Примечание: Подробнее о динамических методах и о раз-

нице в диспетчеризации динамических и виртуальных методов

рассказывается в Главе 22.

Создание экземпляров объектов

Экземпляр объекта создается посредством описание переменной

или константы объектного типа или путем применения стандартной

процедуры New к переменной типа указатель на объектный тип. Ре-

зультирующий объект называется экземпляром объектного типа.

var

F: TField;

Z: TZipField;

FP: PField;

ZP: PZipField;

С учетом этих описание переменных F является экземпляром

TField, а Z - экземпляром TZipField. Аналогично, после применения

New к FP и ZP, FP будет указывать на экземпляр TField, а ZP - на

экземпляр TZipField.

Если объектный тип содержит виртуальные методы, то экземпля-

ры этого объектного типа должны инициализироваться посредством

вызова конструктора перед вызовом любого виртуального метода. Ни-

же приведен пример:

var

S: StrField;

begin

S.Init (1, 1, 25, 'Первое имя');

S.Put ('Френк');

S.Display;

...

S.Done;

end;

Если S.Init не вызывался, то вызов S.Display приведет к неу-

дачному завершению данного примера.

Присваивание экземпляра объектного типа не подразумевает

инициализации экземпляра.

Объект инициализируется кодом, генерируемым компилятором,

который выполняется между вызовом конструктора, и когда выполне-

ние фактически достигает первого оператора в блоке кода конструк-

тора.

Если экземпляр объекта не инициализируется, и проверка диа-

пазона включена (директивой {$R+}), то первый вызов виртуального

метода экземпляра объекта дает ошибку этапа выполнения. Если про-

верка диапазона выключена (директивой {$R-}), то первый виртуаль-

ного метода неинициализированного объекта может привести к неп-

редсказуемому поведению.

Правило обязательной инициализации применимо также к экземп-

лярам, которые являются компонентами структурных типов. Например:

var

Comment: array [1..5] of TStrField;

I: integer;

begin

for I := 1 to 5 do

Comment [I].Init (1, I + 10, 40, 'первое_имя');

.

.

.

for I := 1 to 5 do Comment [I].Done;

end;

Для динамических экземпляров инициализация, как правило,

связана с размещением, а очистка - с удалением, что достигается

благодаря расширенному синтаксису стандартных процедур New и

Dispose. Например:

var

SP: StrFieldPtr;

begin

New (SP, Init (1, 1, 25, 'первое_имя');

SP^.Put ('Френк');

SP^.Display;

.

.

.

Dispose (SP, Done);

end;

Указатель на объектный тип является совместимым по присваи-

ванию с указателем на любой родительский объектный тип, поэтому

во время выполнения программы указатель на объектный тип может

указывать на экземпляр этого типа или на экземпляр любого дочер-

него типа.

Например, указатель типа ZipFieldPtr может присваиваться

указателям типа PZipField, PNumField и PField, а во время выпол-

нения программы указатель типа PField может либо иметь значение

nil, либо указывать на экземпляр TField, TNumField или TZipField,

или на любой экземпляр дочернего по отношению к TField типа.

Эти правила совместимости указателей по присваиванию приме-

нимы также к параметрам-переменным объектного типа. Например, ме-

тоду TField.Copy могут быть переданы экземпляры типов TField,

TStrField, TNumField, TZipField или любые другие экземпляры до-

чернего от TField типа.

Активизация методов

Метод активизируется посредством оператора вызова процедуры

или функции, состоящего из десигнатора метода, за которым следует

список параметров. Такой тип вызова называется активизацией мето-

да.

десигнатор метода

і ЪДДДДДДДДДДДДДДДДДДДДї

АДВДДДДДДДДД>іидентификатор методаГ>

і ^ АДДДДДДДДДДДДДДДДДДДДЩ

і ЪДДДДДДДДДДДДДДДДДДДДДДї ЪДДДї і

А>і ссылка на переменную Г>і . ГДДДДЩ

АДДДДДДДДДДДДДДДДДДДДДДЩ АДДДЩ

Ссылка на переменную задается, если десигнатор метода должен

описывать экземпляр объектного типа, а идентификатор метода дол-

жен обозначать метод этого объектного типа.

Экземпляр, обозначенный десигнатором метода, становится не-

явным фактическим параметром метода; он соответствует формальному

параметру-переменной с именем Self, который владеет объектным ти-

пом, соответствующим активизированному методу.

Для статических методов описанный тип (на этапе компиляции)

определяет, какой из методов активизируется. Например, десигнато-

ры F.Init и FP^.Init всегда активизируют TField.Init, так как

описанным типом F и FP^ является TField.

Для виртуальных методов выбором экземпляра управляет факти-

ческий тип (этапа выполнения). Например, десигнатор FP^.Display

может активизировать методы TField.Display, TStrField.Display,

TNumField.Display или TZipField.Display (в зависимости от факти-

ческого типа экземпляра, указываемого FP).

В операторе with, ссылающемся на экземпляр объектного типа,

ссылка на переменную в десигнаторе метода может опускаться. В

этом случае экземпляром, на который ссылается оператор with, ста-

новится неявный параметр Self активизации метода. Аналогично,

ссылка не переменную может опускаться в методе. В этом случае

параметром Self метода, содержащего вызов, становится неявный па-

раметр Self активизации метода.

Активизация уточненных методов

В методе, операторе вызова функции или процедуры для обозна-

чения активизации конкретного метода допускается использование

десигнатора уточненного метода. Такой тип вызова называется акти-

визацией уточненного метода.

десигнатор уточненного метода

і ЪДДДДДДДДДДДДДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДДДДДДДДДДДї

АДВ>і идентификатор Г>і . ГДДДДДДД>іидентификатор методаГ>

і і объектного типа і АДДДЩ ^ АДДДДДДДДДДДДДДДДДДДДЩ

і АДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДДї і

А>і inherited ГДДДДДДДДДДЩ

АДДДДДДДДДДДДДДДДДДДДДДЩ

Объектный тип, заданный в десигнаторе уточненного метода,

должен быть таким же, как и включающий метод объектный тип, или

соответствовать родительскому типу.

Для обозначения родительского объектного типа или объектного

типа, включающего метод, можно использовать ключевое слово

inherited; в методах объектного типа, не имеющего предка, ключе-

вое слово inherited использоваться не может.

Неявный параметр Self активизации уточненного метода стано-

вится параметром Self метода, содержащего вызов. Активизация

уточненных методов не предусматривает механизма диспетчеризации

виртуальных методов - вызов будет всегда статическим и всегда вы-

зывает заданный метод.

Активизация уточненного метода используется обычно в переоп-

ределяющем методе для активизации переопределяющего метода. С

учетом описанных выше типов приведем некоторые примеры активиза-

ции уточненных методов:

constructor TNumField.Init(Fx, FY, Flen: Integer;

FName: String; FMin, FMax: Longint);

begin

inherited Init(FX, FY, FLen, FName);

Value := 0;

Min := FMin;

Max := FMax;

end;

function TZipField.PutStr(S: String): Boolean;

begin

PutStr := (Length(S) = 5) and TNumField.PutStr(S);

end;

Как показывают эти примеры, активизация уточненных методов

позволяет переопределяющему методу "вновь использовать" код мето-

да, который он переопределяет.

Множественные типы

Диапазон значений множественного типа представляет собой

мощность множества для определенного порядкового типа (базового

типа). Каждое возможное значение множественного типа является

подмножеством возможных значений базового типа.

Переменная множественного типа может принимать как все зна-

чения множества, так и ни одного.

ЪДДДДДї ЪДДДДї ЪДДДДДДДДДДДДДДДДї

тип множество ДДД>і set ГДДД>і of ГДДД>і порядковый тип ГДДД>

АДДДДДЩ АДДДДЩ АДДДДДДДДДДДДДДДДЩ

Базовый тип не должен иметь более 256 возможных значений, и

порядковые значения верхней и нижней границы базового типа должны

не превышать диапазона от 0 до 255. В силу этого базовый тип мно-

жества не может быть коротким целым (Shortint), целым (Integer),

длинным целым (Longint) или словом (Word).

Примечание: Операции над множественными типами описыва-

ются в разделе "Операции над множествами" в Главе 6. В раз-

деле "Описатели множеств" показано, как определять значения

множества.

Любой множественный тип может принимать значение [], которое

называется пустым множеством.

Файловые типы

Файловый тип состоит из линейной последовательности компо-

нентов, которые могут иметь любой тип за исключением файлового

типа или структурного типа, содержащего компонент с файловым ти-

пом. Число компонентов описанием файлового типа не устанавливает-

ся.

ЪДДДДДДї ЪДДДДї ЪДДДДДї

файловый тип ДДД>і file ГДДВД>і of ГДДД>і тип ГДДДДД>

АДДДДДДЩ і АДДДДЩ АДДДДДЩ ^

АДДДДДДДДДДДДДДДДДДДДДЩ

Если слово of и тип компонента опущены, то тип обозначает

нетипизированный файл. Нетипизированные файлы представляют собой

каналы ввода-вывода нижнего уровня, в основном используемые для

прямого доступа к любому файлу на диске, независимо от его внут-

реннего формата.

Стандартный файловый тип Text определяет файл, содержащий

символы, упорядоченные в строки. Текстовые файлы используют спе-

циальные процедуры ввода-вывода, которые описываются в Главе 14

"Ввод и вывод".

Ссылочные типы

Cсылочный тип (указатель) определяет множество значений, ко-

торые указывают на динамические переменные определенного типа,

называемого базовым типом. Переменная ссылочного типа содержит

адрес динамической переменной в памяти.

ЪДДДї ЪДДДДДДДДДДДДДї

ссылочный тип ДДДДДД>і ^ ГДДД>і базовый тип ГДД>

АДДДЩ АДДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДї

базовый тип ДДДД>і идентификатор типа ГДДД>

АДДДДДДДДДДДДДДДДДДДДДЩ

Если базовый тип является еще не описанным идентификатором,

то он должен быть описан в той же самой части описания типов, что

и тип указатель.

Переменной-указателю можно присвоить значение с помощью про-

цедуры New, операции @ или функции Ptr. Процедура New отводит но-

вую область памяти в динамически распределяемой области для дина-

мических переменных и сохраняет адрес этой области в переменной

указателя. Операция @ ориентирует переменную-указатель на область

памяти, содержащую существующую переменную, включая и те перемен-

ные, которые имеют идентификаторы. Функция Ptr ориентирует пере-

менную-указатель на определенный адрес в памяти.

Зарезервированное слово nil обозначает константу со значени-

ем указателя, которая ни на что не указывает.

Тип Pointer

Встроенный тип Pointer обозначает нетипизированный указа-

тель, то есть указатель, который не указывает ни на какой опреде-

ленный тип. Переменные типа Pointer могут быть разыменованы: ука-

зание символа ^ после такой переменной вызывает появление ошибки.

Как и значение, обозначаемое словом nil, значения типа Pointer

совместимы со всеми другими типами указателей.

Примечание: В разделе "Указатели и динамические пере-

менные" в Главе 5 вы можете найти синтаксис ссылки на дина-

мические переменные, которые указываются с помощью указате-

ля-переменной.

Тип PChar

Для представления указателя на строку с завершающим нулем в

Borland Pascal имеется предопределенный тип PChar. В блоке System

данный тип описывается следующим образом:

type PChar = ^Char;

Borland Pascal поддерживает набор расширенных правил, позво-

ляющих работать со строками с завершающим нулем, используя тип

PChar. Полностью эта тема обсуждается в Главе 18 "Использование

строк с завершающим нулем".

Процедурные типы

В стандартном Паскале процедуры и функции рассматриваются

только как части программы, которые можно выполнять с помощью вы-

зова процедуры или функции. В Borland Pascal процедуры и функции

трактуются гораздо шире: здесь допускается интерпретация процедур

и функций, как объектов, которые можно присваивать переменным и

передавать в качестве параметров. Такие действия можно выполнять

с помощью процедурных типов.

В описании процедурного типа задаются параметры, а для функ-

ции - результат функции.

процедурный тип

і

і ЪДДДДДДДДДї

АВ>іprocedureГДВДДДДДДДДДДДДДДДДДД>

і АДДДДДДДДДЩ і ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДї ^ ^

і АД>ісписок формальных параметровГДЩ і

ЪЩ АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ АДї

і ЪДДДДДДДДї ЪДДДї ЪДДДДДДДДДїі

А>іfunctionГВДДД>і : Г>ірезультатГЩ

АДДДДДДДДЩі ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДї^ АДДДЩ АДДДДДДДДДЩ

А>ісписок формальных параметровГЩ

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Характерно, что синтаксис записи процедурного типа в точнос-

ти совпадает с записью заголовка процедуры или функции, только

опускается идентификатор после ключевого слова procedure или

function. Приведем некоторые примеры описаний процедурного типа:

type

Proc = procedure;

SwapProc = procedure(var X, Y: Integer);

StrProc = procedure(S: String);

MathFunc = function(X: Real): Real;

DeviceFunc = function(var F: text): Integer;

MaxFunc = function(A, B: Real; F: MathFunc): Real;

Имена параметров в описании процедурного типа играют чисто

декоративную роль - на смысл описание они не влияют.

Borland Pascal не позволяет описывать функции, которые возв-

ращают значения процедурного типа. Результат функции должен быть

строкового, вещественного, целого, символьного, булевского типа,

указателем или иметь перечислимый тип, определенный пользовате-

лем.

Процедурные значения

Переменной процедурного типа можно присвоить процедурное

значение. Процедурные значения могут быть следующими:

* значениями nil;

* ссылкой на переменную процедурного типа;

* идентификатором процедуры или функции.

В контексте процедурных значений описание процедуры или

функции можно рассматривать как специальный вид описаний конс-

тант, когда значением константы является процедура или функция.

Рассмотрим, например, следующее описание:

var

P: SwapProc;

F: MathFunc;

procedure Swap(var A, B: Integer); far;

var

Temp: Integer;

begin

Temp := A;

A := B;

B := Temp;

end;

function Tan(Angle: Real); far;

begin

Tan := Sin(Angle) / Cos(Angle);

end;

Переменным P и F можно присвоить значения следующим образом:

P := Swap;

F := Tan;

а вызовы с помощью P и F можно выполнить так:

P(I, J); { эквивалентно Swap(I, J) }

X := F(X); { эквивалентно X := Tan(X) }

Использование процедурных переменных, которым в операторе

вызова процедуры или функции присваивается значение nil, приводит

к ошибке. Значение nil предназначено для указания того, что про-

цедурная переменная не присвоена, и, так где процедурная перемен-

ная может получить значение nil, участвующие в этой процедурной

переменной вызовы процедур и функций следует подвергать проверке:

if @P <> nil then P(I, J);

Обратите внимание на использование операции @ для указания

того, что P проверяется, а не вызывается.

Совместимость типов

Чтобы они считались совместимыми, процедурные типы должны

иметь одно и то же число параметров, а параметры в соответствую-

щих позициях должны иметь тождественные типы. При определении

совместимости процедурных типов имена параметров значения не име-

ют. Значение nil совместимо с любым процедурным типом.

Чтобы использоваться в качестве процедурных значений, проце-

дуры и функции должны описываться с директивой far и компилиро-

ваться в состоянии с {$F+}. Кроме того, в качестве процедурных

значений не могут указываться стандартные процедуры и функции,

вложенные процедуры и функции, методы, процедуры и функции, опи-

санные с ключевым словом inline или interrupt.

Стандартные процедуры и функции - это подпрограммы, описан-

ные в модуле Unit, например, WriteLn, ReadLn, Chr или Ord. Чтобы

использовать в качестве процедурного значения стандартную проце-

дуру и функцию, напишите для нее "оболочку". Например, следующая

функция DSin совместима по присваиванию с описанным выше типом

MathFunc:

function FSin(X: Real): Real; far;

begin

FSin := Sin(X);

end;

Процедура или функция является вложенной, когда она описыва-

ется внутри другой процедуры или функции. Такие вложенные проце-

дуры и функции не могут использоваться в качестве процедурных

значений.

Тождественные и совместимые типы

Два типа могут быть тождественными, и эта тождественность

(идентичность) является обязательной в некоторых контекстах. В

других случаях два типа должны быть только совместимы или совмес-

тимы по присваиванию. Два типа являются тождественными, если они

описаны с одним идентификатором типа, или если их определения ис-

пользуют один и тот же идентификатор типа.

Тождественность типов

Тождественность типов требуется только для переменных факти-

ческих и формальных параметров при вызове процедур и функций.

Два типа, скажем T1 и T2, являются тождественными, если яв-

ляется истинным одно из следующих утверждений: T1 и T2 представ-

ляю собой один и тот же идентификатор типа; T1 описан как эквива-

лентный типу, тождественному T2.

Второе условие означает, что T1 не обязательно должен быть

описан как непосредственно эквивалентный T2. Следующие описания

типов:

T1 = integer;

T2 = T1;

T3 = integer;

T4 = T2;

означают, что T1, T2, T3, T4 и integer являются тождественными

типами. Следующие описания типов:

T5 = set of integer;

T6 = set of integer;

не определяют T5 и T6 как тождественные, поскольку set of integer

не является идентификатором типа. Две переменные, описанные в од-

ном и том же описании, например:

V1, V2: set of integer;

имеют тождественные типы, поскольку их описания не раздельны.

Описания:

V1: set of integer;

V2: set of integer;

V3: integer;

V4: integer;

означают, что V3 и V4 имеют тождественный тип, а V1 и V2 - нет.

Совместимость типов

Иногда, например, в выражениях и операциях сравнения, требу-

ется совместимость типов. Совместимость типов, кроме того, явля-

ется важной предпосылкой для совместимости по присваиванию.

Совместимость типов имеет место, если выполняется по крайней

мере одно из следующих условий:

* Оба типа являются одинаковыми.

* Оба типа являются вещественными типами.

* Оба типа являются целочисленными.

* Один тип является поддиапазоном другого.

* Оба типа являются отрезками одного и того же основного типа.

* Оба типа являются множественными типами с совместимыми ба-

зовыми типами.

* Один тип является строковым типом, а другой - строковым

типом, упакованным строковым типом или типом PChar;

* Один тип - это тип Pointer, а другой - любой ссылочный

тип.

* Один тип является типом PChar, а другой - символьным мас-

сивом с нулевой базой вида array[0..X] of Char (это дейс-

твует только при разрешении директивой {$X+} расширенного

синтаксиса).

* Оба типа являются указателями идентичных типов (это дейс-

твует только при разрешении указателя с проверкой типа ди-

рективой {$X+}).

* Оба типа являются процедурными с идентичными типами ре-

зультатов, одинаковым числом параметров и соответствием

между параметрами.

Совместимость по присваиванию

Совместимость по присваиванию необходима, если имеет место

присваивание значения, например, в операторе присваивания или при

передаче значений параметров.

Значение типа T1 является совместимым по присваиванию с ти-

пом T2 (то есть допустим оператор T1:=T2), если выполняется одно

из следующих условий:

* T1 и T2 имеют тождественные типы, и ни один из них не яв-

ляется файловым типом или структурным типом, содержащим

компонент с файловым типом на одном из своих уровней.

* T1 и T2 являются совместимыми порядковыми типами, и значе-

ния типа T2 попадают в диапазон возможных значений T1.

* T1 и T2 являются вещественными типами, и значения типа T2

попадают в диапазон возможных значений T1.

* T1 является вещественным типом, а T2 является целочислен-

ным типом.

* T1 и T2 являются строковыми типами.

* T1 является строковым типом, а T2 является символьным ти-

пом (Char).

* T1 является строковым типом, а T2 является упакованным

строковым типом.

* T1 и T2 являются совместимыми упакованными строковыми ти-

пами.

* T1 и T2 являются совместимыми множественными типами, и все

члены значения типа T2 попадают в диапазон возможных зна-

чений T1.

* T1 и T2 являются совместимыми типами указателей.

* T1 - это тип PChar, а T2 - это строковая константа (это

действует только при разрешении директивой {$X+} расширен-

ного синтаксиса).

* T1 является типом PChar, а T2 - символьным массивом с ну-

левой базой вида array[0..X] of Char (это действует только

при разрешении директивой {$X+} расширенного синтаксиса).

* T1 и T2 являются совместимыми процедурными типами.

* T1 представляет собой процедурный тип, а T2 - процедура

или функция с идентичным типом результата, идентичным чис-

лом параметров и соответствием между типами параметров.

* Объектный тип T2 совместим по присваиванию с объектным ти-

пом T1, если T2 является доменом T1.

* Тип указателя Р2, указывающий на объект типа Т3, совместим

по присваиванию с типом указателя P1, указывающим на объ-

ект T1, если T2 является доменом T1.

На этапе компиляции и выполнения выдается сообщение об ошиб-

ке, если совместимость по присваиванию необходима, а ни одно из

условий предыдущего списка не выполнено.

Раздел описания типов

Программы, процедуры и функции имеют для описания типов спе-

циальный раздел описания типов. Например:

type

TRange = integer;

TNumber = integer;

TColor = (red,green,blue);

TTextIndex = 1..100;

TTestValue = -99..99;

TTestList = array[TestIndex] of TestValue;

PestList = ^TTestList;

TDate = object

year: integer;

month: 1..12;

day: 1.. 31;

procedure SetDate(D, M, Y: Integer);

function ShowDate: String;

end;

MeasureData = record

when: Date;

count: TTestIndex;

data: TestListPtr;

end;

TMeasureList = array[1..50] of MeasureData;

TName = string[80];

TSex = (male,female);

TPersonDate = ^TPersonData;

TPersonData = record

name,firstName: TName;

age: integer;

married: boolean;

father,child,sibling: Person;

case s: Sex of

male: (bearded: boolean);

female: (pregnant: boolean);

end;

TPersonDate = array[0..SizeOf(TPersonDate)-1] of Byte;

TPeople = file of TPersonData;

В этом примере Range, Number и Integer являются тождествен-

ными типами. TTestIndex является просто совместимым и совместимым

по присваиванию, но не тождественным, с типами Number, Range и

Integer. Обратите внимание на использование в описаниях TCharVal

и TPersonBuf выражений-констант.


Глава 5. Переменные и типизированные константы

Описания переменных

Описание переменной представляет собой список идентификато-

ров, которые обозначают новые переменные и их типы.

описание ЪДДДДДДДДДДДДї ЪДДДї ЪДДДї ЪДДДї

переменной Д>ісписок иден-ГД>і : ГД>ітипГДВДДДДДДДДДДДВД>і ; Г>

ітификаторов і АДДДЩ АДДДЩ і і АДДДЩ

АДДДДДДДДДДДДЩ і ЪДДДДДДДДїі

А>іabsoluteГЩ

АДДДДДДДДЩ

Тип, задаваемый для переменных, может быть идентификатором

типа, который был ранее описан в разделе описания типов того же

самого блока, или блока, в который входит данный блок, или моду-

ля, или же этот тип может быть новым определением типа.

При указании идентификатора в списке идентификаторов описа-

ния переменной этот идентификатор имеет силу идентификатора пере-

менной в том блоке, где это описание было указано. К этой пере-

менной можно обращаться из любого места этого блока, если ее

идентификатор не переопределен в блоке, входящем в первый. Пере-

определение означает, что для новой переменной используется тот

же самый идентификатор, но это использование не оказывает влияния

на значение первоначальной переменной.

Приведем пример раздела описания переменной:

var

X,Y,Z: real;

I,J,K: integer;

Digit: 0..9;

C: Color;

Done,Error: boolean;

Operator: (plus, minus, times);

Hue1,Hue2: set of Color;

Today: Date;

Results: MeasureList;

P1,P2: Person;

Matrix: array[1..10,1..10] of Real;

Переменные, описанные вне процедуры и функции, называются

глобальными переменными и располагаются в сегменте данных. Пере-

менные, описанные в самой процедуре или функции, называются ло-

кальными переменными и располагаются в сегменте стека.

Сегмент данных

Максимальный размер сегмента данных равен 65520 байт. При

компоновке программы (что автоматически осуществляется в конце

компиляции программы) глобальные переменные всех модулей, исполь-

зуемых программой, а также собственные глобальные переменные

программы, размещаются в сегменте данных.

Если для глобальных переменных требуется более 65520 байт,

то следует распределить большие структуры в виде динамических пе-

ременных. Дальнейшее описание этой темы можно найти в разделе

"Указатели и динамические переменные" настоящей главы.

Сегмент стека

Размер сегмента стека устанавливается с помощью директивы

компилятора $M и лежит в пределах от 1024 до 65520 байт. По умол-

чанию размер стека равен 16384 байт.

При каждой активизации (вызове) процедуры или функции в стек

помещается множество локальных переменных. При завершении работы

память, занимаемая локальными переменными, освобождается. В любой

момент выполнения программы общий размер локальных переменных в

активных процедурах и функциях не должен превышать размера сег-

мента стека.

Примечание: Если вы пишете приложение для Windows, то

Windows налагает на сегменты данных и стека специальные

требования, так что рабочий максимум стека и область сег-

мента данных могут быть меньше, чем упомянутые максимальные

области сегмента данных и стека.

Директива компилятора $S используется для проверок перепол-

нения стека в программе. В состоянии {$S+}, принятом по умолча-

нию, генерируется код, осуществляющий проверку переполнения стека

в начале каждой процедуры или функции. В состоянии {$S-} такие

проверки не проводятся. Переполнение стека может вызвать аварий-

ное завершение работы системы, поэтому не следует отменять про-

верки стека, если нет абсолютной уверенности в том, что перепол-

нения не произойдет.

Абсолютные переменные

Переменные можно описать так, что они будут располагаться по

определенному адресу в памяти, и в этом случае они называются аб-

солютными переменными. Описание таких переменных должно содержать

после типа оператор absolute:

описание ЪДДДДДДДДї ЪДДДДДДДДДї ЪДДДї ЪДДДДДДДДДї

абсолютной ДДД>іabsoluteГДВД>іцелое безГД>і : ГД>іцелое безГДВД>

переменной АДДДДДДДДЩ і і знака і АДДДЩ і знака і і

і АДДДДДДДДДЩ АДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

АДДДДДД>іидентификаторГДДДДДДДДДДДДЩ

і переменной і

АДДДДДДДДДДДДДЩ

Отметим, что список идентификаторов в описании переменной

при указании оператора absolute может содержать только один иден-

тификатор.

Первая часть оператора absolute содержит сегмент и смещение,

то есть адрес, по которому переменная должна быть размещена.

CrtMode : byte absolute $0040:$0049;

Первая константа обозначает базу сегмента, а вторая опреде-

ляет смещение внутри этого сегмента. Обе константы не должны вы-

ходить за пределы диапазона от $0000 до $FFFF (от 0 до 65535).

В программах защищенного режима DOS и в Windows первую форму

оператор absolute нужно использовать очень аккуратно, если вообще

стоит это делать. Во время выполнения прикладной программы

Windows или DOS защищенного режима она может не иметь полномочий

доступа к областям памяти вне вашей программы. Попытка доступа к

этим областям памяти может привести к сбою программы.

Вторая форма оператора absolute используется для описания

переменной, которая помещается "поверх" другой переменной, то

есть по тому же самому адресу, что и другая переменная.

var

Str: string[32];

StrLen: byte absolute Str;

Это описание указывает, что переменная StrLen должна разме-

щаться с того же адреса, что и переменная Str, а поскольку первый

байт строковой переменной содержит динамическую длину строки, то

StrLen будет содержать длину Str.

Эту вторую форму оператора absolute можно без опасения ис-

пользовать при программировании в Windows или в защищенном режиме

DOS. Память, к которой вы обращаетесь, находится в области прог-

раммы.

Ссылки на переменные

Ссылка на переменную может обозначать следующее:

- переменную;

- компонент в переменной структурного или строкового типа;

- динамическую переменную, на которую указывает переменная

типa указатель.

Синтаксис ссылки на переменную имеет вид:

ЪДДДДДДДДДДДДДї

ссылка на ДВДД>іидентификаторГДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДД>

переменную і і переменной і ^^ ЪДДДДДДДДДДДДї і

і АДДДДДДДДДДДДДЩ іАДДґквалификаторі<ДЩ

і ЪДДДДДДДДДДДДДДДї і АДДДДДДДДДДДДЩ

ГДД>іприведение типаГДДДДДДґ

і і переменной і і

і АДДДДДДДДДДДДДДДЩ АДДДДДї

і ЪДДДДДДДДДї ЪДДДДДДДДДДДДї і

АДД>івыражениеГД>іквалификаторГДДЩ

АДДДДДДДДДЩ АДДДДДДДДДДДДЩ

Отметим, что синтаксис ссылки на переменную допускает ис-

пользование выражения, вычисляющего значение ссылочного типа. Вы-

ражение должно следовать за квалификатором, разыменовывающим ссы-

лочное значение (или индексирующим значением указателя, если с

помощью директивы {$X+} разрешен расширенный синтаксис), что дает

фактическую ссылку на переменную.

Квалификаторы

Обращение к функции представляет собой идентификатор пере-

менной с несколькими квалификаторами или без них, которые изменя-

ют значение обращения к функции.

ЪДДДДДДї

квалификатор ДДВДД>іиндексГДДДДДДДДДД>

і АДДДДДДЩ ^

і ЪДДДДДДДДДДДДї і

ГДД>і десигнатор ГДДґ

і і поля і і

і АДДДДДДДДДДДДЩ і

і ЪДДДї і

АДД>і ^ ГДДДДДДДДДДДЩ

АДДДЩ

Идентификатор массива без квалификатора является ссылкой на

весь массив, например:

Results

Идентификатор массива с указанным индексом обозначает конк-

ретный элемент массива, в данном случае структурную переменную:

Results[Current+1]

В случае, если элементом является запись, за индексом можно

указать обозначение поля. В этом случае ссылка на переменную оз-

начает конкретное поле конкретного элемента массива:

Results[Current+1].Data

Десигнатор поля в указателе-поле может сопровождаться сим-

волом указателя (^) с тем, чтобы указать различие между указате-

лем-полем и динамической переменной, на которую он указывает.

Results[Current+1].Data^

Если переменная, на которую указывается, является массивом,

то можно добавить индексы для обозначения компонентов этого мас-

сива.

Results[Current+1].Data^[J]

Массивы, строки и индексы

Конкретный элемент массива обозначается с помощью ссылки на

переменную массива, за которой указывается индекс, определяющий

данный элемент.

Конкретный символ в строковой переменной обозначается с по-

мощью ссылки на строковую переменную, за которой указывается ин-

декс, определяющий позицию символа.

ЪДДДї ЪДДДДДДДДДї ЪДДДї

индекс ДД>і [ ГДДДДДДД>івыражениеГДДДДВДД>і ] ГДД>

АДДДЩ ^ АДДДДДДДДДЩ і АДДДЩ

і ЪДДДї і

АДДДДДДДґ , і<ДДДДДДДЩ

АДДДЩ

Индексные выражения обозначают компоненты в соответствующей

размерности массива. Число выражений не должно превышать числа

индексных типов в описании массива. Более того, тип каждого выра-

жения должен быть совместимым по присваиванию с соответствующим

индексным типом.

В случае многомерного массива можно использовать несколько

индексов или несколько выражений в индексе. Например:

Matrix[I][J]

что тождественно записи:

Matrix[I,J]

Строковую переменную можно проиндексировать с помощью оди-

ночного индексного выражения, значение которого должно быть в ди-

апазоне 0...n, где n - указанный в описании размер строки. Это

дает доступ к каждому символу в строковом значении, если значение

символа имеет тип Char.

Первый символ строковой переменной (индекс 0) содержит дина-

мическую длину строки, то есть Length(S) тождественно Ord(S[0]).

Если атрибуту длины присваивается значение, то компилятор не про-

веряет, является ли это значение меньшим описанного размера стро-

ки. Вы можете указать индекс строки и вне ее текущей динамической

длины. В этом случае считываемые символы будут случайными, а

присваивания вне текущей длины не повлияют на действительное зна-

чение строковой переменной.

Когда с помощью директивы компилятора {$X+} разрешен расши-

ренный синтаксис, значение PChar может индексироваться одиночным

индексным выражением типа Word. Индексное выражение задает смеще-

ние, которое нужно добавить к символу перед его разыменованием

для получения ссылки на переменную типа Char.

Записи и десигнаторы полей

Конкретное поле переменной-записи обозначается с помощью

ссылки на переменную-запись, после которой указывается обозначе-

ние поля, специфицирующее это поле.

ЪДДДї ЪДДДДДДДДДДДДДї

обозначение поля ДДД>і . іДДД>іидентификаторіДДД>

АДДДЩ і поля і

АДДДДДДДДДДДДДЩ

Приведем несколько примеров десигнаторов полей:

Today.Year

Results[1].Count

Result[1].When.Month

В операторе, входящем в оператор with, обозначению поля не

должна предшествовать ссылка на переменную, содержащую запись.

Десигнаторы компонентов объекта

Формат десигнатора компонента объекта совпадает с форматом

десигнатора поля записи. То есть, он состоит из экземпляра (ссыл-

ки на переменную), за которым следует точка и идентификатор ком-

понента. Десигнатор компонента, который обозначает метод, называ-

ется десигнатором метода. К экземпляру объектного типа можно

применить оператор with. В этом случае при ссылке на компоненты

объектного типа экземпляр и точку можно опустить.

Экземпляр и точку можно опустить также в любом блоке метода.

При этом эффект будет тот же, что и при записи перед ссылкой на

компонент Self и точки.

Переменные-указатели и динамические переменные

Значением переменной-указателя является или nil (то есть

пустое значение), или адрес значения, указывающий на динамическую

переменную.

Ссылка на динамическую переменную, на которую указывает пе-

ременная-указатель, записывается в виде переменной-указателя,

после которой ставится символ указателя (^).

Динамические переменные и значения их указателей создаются с

помощью стандартных процедур New и GetMem. Вы можете использовать

операцию @ и стандартную функцию Ptr для создания значений указа-

теля, которые рассматриваются как указатели динамических перемен-

ных.

Значение nil не указывает ни на какую переменную. Если вы

попытаетесь получить доступ к динамической переменной при неопре-

деленном значении указателя или указателе, равном nil, результат

будет неопределенным.

Приведем несколько примеров ссылок (указателей) на динами-

ческие переменные:

P1^

P1.Sibling^

Results[1].Data^

Приведение типов переменных

Ссылка на переменную одного типа может быть преобразована в

ссылку на переменную другого типа с помощью приведения типов пе-

ременных.

ЪДДДДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДї ЪДДДї

приведение ДДД>іидентификаторГДД>і ( ГДД>іссылка на ГДД>і ) ГД>

типов і типа і АДДДЩ іпеременнуюі АДДДЩ

АДДДДДДДДДДДДДЩ АДДДДДДДДДДЩ

Когда приведение типов применяется к ссылке на переменную,

ссылка на переменную рассматривается как экземпляр типа, предс-

тавленного идентификатором типа. Размер переменной (число байт,

занимаемых переменной) должен быть равен размеру типа, представ-

ленного идентификатором типа. После приведения типа переменной

можно указать один или несколько квалификаторов, если это допус-

кается указанным типом.

Примечание: Определять допустимость приведения типа

должен программист.

Приведем несколько примеров приведения типов переменных:

type

TByteRec = record

lo, hi: byte;

end;

TWordRec = record

low, high: word;

end;

TPtrRec = record

ofs, seg: word;

end;

PByte = ^Byte;

var

B: byte;

W: word;

L: longint;

P: pointer;

begin

W := $1234;

B := TByteRec(W).lo;

TByteRec(W).hi := 0;

L := $1234567;

W := TWordRec(L).lo;

B := PByte(L)^;

P := Ptr($40,$49);

W := TPtrRec(P).seg;

Inc(TPtrRec(P).Ofs,4);

end.

Обратите внимание на использование для доступа к младшим и

старшим байтам слова типа TByteRec: это соответствует встроенным

функциям Lo и Hi, только над левой частью в операции присваивание

может выполняться приведение типа. Отметим также, что для доступа

к младшим и старшим словам длинного целого, а также к смещению и

адресу сегмента указателя используются типы TWordRec и TPtrRec.

Borland Pascal также полностью поддерживает приведение типов

для процедурных типов. Например, имея следующие описания:

type

Func = function(X: Integer): Integer;

var

F: Func;

P: Pointer;

N: Integer;

вы можете построить следующие присваивания:

F := Func(P); { присвоить F значение процедурного типа в P }

Func(P) := F; { присвоить P значение процедурного типа в F }

@F := P; { присвоить F значение-указатель в P }

P := @F; { присвоить P значение-указатель в F }

N := F(N); { вызвать функцию через F }

N := Func(P)(N); { вызвать функцию через P }

Обратите в частности внимание на операцию получения адреса

@, которая применяется к переменной процедурного типа. Ее можно

использовать в левой части присваивания. Кроме того, отметьте

приведение типа на последней строке при вызове функцию через пе-

ременную-указатель.

Типизированные константы

Типизированные константы можно сравнить с инициализированны-

ми переменными - переменными, значения которых определяются на

входе в их блок. В отличие от нетипизированных констант в описа-

нии типизированной константы указывается как тип, так и значение

константы.

описание типизированной константы

і ЪДДДДДДДДДДДДДї ЪДДДї ЪДДДї ЪДДДї ЪДДДДДДДДДДДДДДї

АД>іидентификаторГД>і : ГД>ітипГД>і = ГД>ітипизированнаяГДД>

АДДДДДДДДДДДДДЩ АДДДЩ АДДДЩ АДДДЩ і константа і

АДДДДДДДДДДДДДДЩ

типизированная ЪДДДДДДДДДДДДДДДДДДДї

константа ДДДДДДВДДД>і константа ГДДДДДДД>

і АДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДї і

ГДДД>і адресная константаГДДДґ

і АДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДї і

ГДДД>і константа-массив ГДДДґ

і АДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДї і

ГДДД>і константа-запись ГДДДДґ

і АДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДї і

ГДДД>і константа-объект ГДДДґ

і АДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДї і

АДДД>іконстанта-множествоГДДДЩ

АДДДДДДДДДДДДДДДДДДДЩ

Типизированные константы можно использовать точно так же,

как переменные того же самого типа, и они указываются в левой

части оператора присваивания. Отметим, что типизированные конс-

танты инициализируются только один раз - в начале выполнения

программы. Таким образом, при каждом новом входе в процедуру или

функцию локально описанные типизированные константы заново не

инициализируются.

Кроме обычных выражений-констант значение типизированной

константы может задаваться с помощью адресного выражения-констан-

ты. Адресное выражение-константа - это выражение, предусматриваю-

щее получение адреса, смещения или сегмента глобальной перемен-

ной, типизированной константы, процедуры или функции. Адресные

выражения-константы не могут ссылаться на локальные переменные

(расположенные в стеке) или динамические переменные (размещенные

в динамически распределяемой области памяти), поскольку их адреса

нельзя вычислить на этапе компиляции.

Константы простого типа

Описание типизированной константы с простым типом означает

указание значения константы:

const

Maximum : integer = 9999;

Factor : real = -0.1;

Breakchar : char = #3;

Как уже упоминалось ранее, значение типизированной константы

можно задать с помощью адресного выражение-константы, то есть вы-

ражения, в котором используются адрес, смещение или сегмент гло-

бальной переменной, типизированной константы, процедуры или функ-

ции. Например:

var

Buffer: array[0..1023] of Byte;

const

BufferOfs: Word = Ofs(Buffer);

BufferSeg: Word = Seg(Buffer);

Поскольку типизированная константа фактически представляет

собой переменную со значением константы, она не является взаимо-

заменяемой для обычных констант. Например, она не может использо-

ваться в описании других констант или типов.

const

Min : integer = 0;

Max : integer = 99;

type

Vector = array[Min..Max] of integer;

Описание Vector является недопустимым, поскольку Min и Max

являются типизированными константами.

Константы строкового типа

Описание типизированной константы строкового типа содержит

максимальную длину строки и ее начальное значение:

const

Heading : string[7] = 'Section';

NewLine : string[2] = #13#10;

TrueStr : string[5] = 'Yes';

FalseStr : string[5] = 'No';

Константы структурного типа

Описание константы структурного типа определяет значение

каждого компонента структуры. Borland Pascal поддерживает описа-

ния констант типа массив, запись, множество и указатель. Констан-

ты файлового типа и константы типа массив или запись, содержащие

компоненты файлового типа, не допускаются.

Константы типа массив

Описание константы типа массив содержит значения элементов,

заключенные в скобки и разделенные запятыми.

ЪДДДї ЪДДДДДДДДДДДДДДї ЪДДДї

константа-массив ДДД>і ( ГДДДД>ітипизированнаяГДДВД>і ) ГДД>

АДДДЩ ^ і константа і і АДДДЩ

і АДДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДґ , і<ДДДДДДДДЩ

АДДДЩ

Приведем пример константы типа массив:

type

Status = (Active,Passive,Waiting);

StatusMap = array[Status] of string[7];

const

StatStr: StatusMap = ('Active','Passive','Waiting');

В этом примере определяется константа-массив StarStr, кото-

рая может использоваться для преобразования значений типа Status

в соответствующие им строковые представления. Элементами массива

StarStr являются:

StatStr[Active] = 'Active'

StatStr[Passive] = 'Passive'

StatStr[Waiting] = 'Waiting'

Тип элемента константы-массива может быть любым, кроме фай-

лового типа. Упакованные константы строкового типа (символьные

массивы) могут быть определены и как одиночные символы, и как

строки. Определение:

const

Digits:array[0..9] of

char=('0','1','2','3','4','5','6','7','8','9');

можно представить в более удобном виде:

const

Digits: array[0..9] of char = '0123456789';

При разрешении расширенного синтаксиса (с помощью директивы

компилятора {$X+}) массивы с нулевой базой могут инициализирова-

ться строкой, которая короче, чем описанная длина массива, напри-

мер:

const

FileName = array[0..79] of Char = 'TEXT.PAS';

В таких случаях оставшиеся символы устанавливаются в NULL

(#0), и массив содержит строку с завершающим нулем.

Примечание: Подробнее о строках с завершающим нулем

рассказывается в Главе 18.

При описании константы типа "многомерный массив" константы

каждой размерности заключаются в отдельные скобки и разделяются

запятыми. Расположенные в середине константы соответствуют самым

правым размерностям. Описание:

type

Cube = array[0..1,0..1,0..1] of integer;

const

Maze: Cube = (((0,1),(2,3)),((4,5),(6,7)));

задает следующие начальные значения массива Maze:

Maze[0, 0, 0] = 0

Maze[0, 0, 1] = 1

Maze[0, 1, 0] = 2

Maze[0, 1, 1] = 3

Maze[1, 0, 0] = 4

Maze[1, 0, 1] = 5

Maze[1, 1, 0] = 6

Maze[1, 1, 1] = 7

Константы типа запись

Описание константы типа запись содержит идентификатор и зна-

чение каждого поля, заключенные в скобки и разделенные точками с

запятой.

константа-запись

і ЪДДДї ЪДДДДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДДДДДї ЪДДДї

АД>і ( ГДДД>іидентификаторГД>і : ГД>ітипизированнаяГДВД>і ) ГД>

АДДДЩ ^ і поля і АДДДЩ і константа і і АДДДЩ

і АДДДДДДДДДДДДДЩ АДДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДДДДДДДДДДДДДДґ ; і<ДДДДДДДДДДДДДДДДДДЩ

АДДДЩ

Приведем несколько примеров констант-записей:

type

Point = record

x,y: real;

end;

Vector = array[0..1] of Point;

Month =

(Jan,Feb,Mar,Apr,May,Jun,Jly,Aug,Sep,Oct,Nov,Dec);

Date = record

d: 1..31; m: Month; y: 1900..1999;

end;

const

Origin : Point = (x: 0.0; y: 0.0);

Line : Vector = ((x: -3.1; y: 1.5),(x: 5.8; y: 3.0));

SomeDay : Date = (d: 2; m: Dec; y: 1960);

Поля должны указываться в том же порядке, как они следуют в

описании типа запись. Если запись содержит поля файлового типа,

то для этого типа запись нельзя описать константу. Если запись

содержит вариант, то можно указывать только поля выбранного вари-

анта. Если вариант содержит поле признака, то его значение должно

быть определено.

Константы объектного типа

При описании константы объектного типа используется тот же

синтаксис, что и при описании константы типа запись. Значения для

элементов (компонентов) метода задаваться не могут. С учетом при-

водимых ранее описаний объектных типов, приведем некоторые приме-

ры констант объектного типа:

const

ZeroPoint: Point = (X: 0; Y: 0)

ScreenRect: Rect = (A: (X: 0; Y: 0); B: (X: 80; Y: 25);

CountField: NumField = (X: 5; Y: 20; Len: 4; Name: nil;

Value: 0; Min: -999; Max: 999);

Константы объектного типа, которые содержат виртуальные ме-

тоды, не требуется инициализировать с помощью вызова конструкто-

ра. Эта инициализация автоматически выполняется компилятором.

Константы множественного типа

Описание константы множественного типа может содержать нес-

колько элементов, заключенных в квадратные скобки и разделенных

запятыми. Каждый элемент такой константы представляет собой конс-

танту или отрезок типа, состоящий из двух констант, разделенных

двумя точками.

ЪДДДї ЪДДДї

константа-множество Д>і [ ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДД>і ] Г>

АДДДЩ і ЪДДДДДДДДДДДДДДДДДї ^ АДДДЩ

АДДД>іконстанта-элементГДВДЩ

^ АДДДДДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДДДґ , і<ДДДДДДДДЩ

АДДДЩ

ЪДДДДДДДДДї

константа-элемент ДДДД>іконстантаГДДВДДДДДДДДДДДДДДДДДДДДДДДДД>

АДДДДДДДДДЩ і ЪДДї ЪДДДДДДДДДї ^

АД>і..ГДД>іконстантаГДДЩ

АДДЩ АДДДДДДДДДЩ

Приведем несколько примеров констант-множеств:

type

Digits = set of 0..9;

Letters = set of 'A'..'Z';

const

EvenDigits: Digits = [0,2,4,6,8];

Vowels : Letters = ['A','E','I','O','U','Y'];

HexDigits : set of '0'..'z' =

['0'..'9','A'..'F','a'..'f'];

Константы ссылочного типа

Описание константы ссылочного типа может содержать только

значение nil (пусто). Приведем несколько примеров:

type

TDirection = (Left, Right, Up, Down);

TStringPtr = ^String;

TNodePtr = ^Node;

TNode = record

Next: NodePtr;

Symbol: StringPtr;

Value: Direction;

end;

const

S1: string[4] = 'DOWN';

S2: string[2] = 'UP';

S3: string[5] = 'RIGHT';

S4: string[4] = 'LEFT';

N1: Node = (Next: nil; Symbol: @S1; Value: Down);

N2: Node = (Next: @N1; Symbol: @S2; Value: Up);

N3: Node = (Next: @N2; Symbol: @S3; Value: Right);

N2: Node = (Next: @N3; Symbol: @S4; Value: Left);

DirectionTable: NodePtr = @N4;

Если разрешен расширенный синтаксис (указана директива ком-

пилятора {$X+}), типизированная константа типа PChar может иници-

ализироваться строковой константой, например:

const

Message: PChar = 'Программа завершена';

Prompt: PChar = 'Введите значения: ';

Digits: array[0..9] of PChar = (

'Ноль', 'Один', 'Два', 'Три', 'Четыре',

'Пять', 'Шесть', 'Семь', 'Восемь', 'Девять');

Результатом будет то, что указатель теперь указывает на об-

ласть памяти, содержащую копию строкового литерала с завершающим

нулем. Подробности вы можете найти в Главе 18 "Строки с завершаю-

щим нулем".

Константы процедурного типа

Константы процедурного типа должны определять идентификатор

процедуры или функции, совместимый по присваиванию с типом конс-

танты.

ЪДДДДДДДДДДДДДДДДДї

процедурная константа ДДДДДДВДДД>іконстанта-элементГДДДДДДДДДДД>

і АДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДї і

ГДДД>іконстанта-элементГДДДґ

і АДДДДДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДДДД>іnilГДДДДДДДДДДДЩ

АДДДЩ

Приведем следующий пример:

type

ErrorProc = procedure(ErrorCode: Integer);

procedure DefaultError(ErrorCode: Integer); far;

begin

WriteLn('Error ', ErrorCode, '.');

end;

const

ErrorHandler: ErrorProc = DefaultError;


Глава 6. Выражения

Выражения состоят из операций и операндов. Большинство опе-

раций в языке Паскаль являются бинарными, то есть содержат два

операнда. Остальные операции являются унарными и содержат только

один операнд. В бинарных операциях используется обычное алгебраи-

ческое представление, например: a+b. В унарных операциях операция

всегда предшествует операнду, например: -b.

В более сложных выражениях порядок, в котором выполняются

операции, соответствует приоритету операций (см. Таблицу 6.1).

Старшинство операций Таблица 6.1

ЪДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДї

і Операция і Приоритет і Вид операции і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДґ

і @, not і первый (высший) і унарная операция і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДґ

і *, /, div, mod, і второй і операция умножения,і

і and, shl, shr і і деления, сдвига... і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДґ

і +, -, or, xor і третий і операция сложения і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДґ

і =, <>, <, >, і четвертый (низший) і операция отношения і

і <=, >=, in і і і

АДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДЩ

Для определении старшинства операций имеется три основных

правила:

1. Во-первых, операнд, находящийся между двумя операциями с

различными приоритетами, связывается с операцией, имею-

щей более высокий приоритет.

2. Во-вторых, операция, находящаяся между двумя операциями

с равными приоритетами, связывается с той операцией, ко-

торая находится слева от него.

3. В-третьих, выражение, заключенное в скобки, перед выпол-

нением вычисляется, как отдельный операнд.

Операции с равным приоритетом обычно выполняются слева-нап-

раво, хотя иногда компилятор при генерации оптимального кода мо-

жет переупорядочить операнды.

Синтаксис выражений

Правила, определяющие порядок выполнения операций, вытекают

из синтаксиса выражений, которые строятся из множителей, термов и

простых выражений.

Множитель имеет следующий синтаксис:

ЪДДДДДДДДДДДДДДДї

множитель ДДДВДДДДДДДДДДДДДД>і ссылка на ГДДДДДДДДДДД>

і і переменную і ^

і АДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДї і

ГДДДД>іконстантаГДДДДДДДДДДДДДДДДДДДДДДґ

і ібез знакаі і

і АДДДДДДДДДЩ і

і ЪДДДї ЪДДДДДДДДДї ЪДДДї і

ГДДДД>і ( ГДДДД>івыражениеіДДД>і ) ГДДДґ

і АДДДЩ АДДДДДДДДДЩ АДДДЩ і

і ЪДДДї ЪДДДДДДДДДї і

ГДДДД>іnotГДДДД>імножительГДДДДДДДДДДДДґ

і АДДДЩ АДДДДДДДДДЩ і

і ЪДДДДї ЪДДДДДДДДДї і

ГДДДД>ізнакГДДД>імножительГДДДДДДДДДДДДґ

і АДДДДЩ АДДДДДДДДДЩ і

і ЪДДДДДДДДДї і

ГДДДД>і вызов ГДДДДДДДДДДДДДДДДДДДДДДґ

і і функции і і

і АДДДДДДДДДЩ і

і ЪДДДДДДДДДДДї і

ГДДДД>іконструкторГДДДДДДДДДДДДДДДДДДДДґ

і і множества і і

і АДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДї і

ГДДДД>і адресный ГДДДДДДДДДДДДДДДДДДДДґ

і і множитель і і

і АДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДї і

АДДДД>і приведение ГДДДДДДДДДДДДДДДДДЩ

ітипа значения і

АДДДДДДДДДДДДДДЩ

Вызов функции активизирует функцию и представляет собой зна-

чения, возвращаемые функцией (см. далее в этой главе раздел "Вы-

зовы функций"). Описатель множества представляет собой значение

множественного типа (см. раздел, озаглавленный, как "Описание

множеств"). Приведение типа изменяет тип значения (см. "Приведе-

ние типа").

Адресный множитель вычисляет адрес переменной, процедуры,

функции или метода. См. раздел "Операция @".

Беззнаковая константа имеет следующий синтаксис:

ЪДДДДДДДДДї

константа без знака ДДДВДДДД>і число ГДДДДДДДДДДДДД>

і ібез знакаі ^

і АДДДДДДДДДЩ і

і ЪДДДДДДДДДДї і

ГДДДД>ісимвольнаяГДДДДДДДДґ

і і строка і і

і АДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

ГДДДД>іидентификаторГДДДДДґ

і і константы і і

і АДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДД>іnilГДДДДДДДДДДДДДДДЩ

АДДДЩ

Некоторые примеры множителей могут включать в себя:

Х { ссылка на переменную }

@Х { указатель на переменную }

15 { константа без знака }

(Х+Y+Z) { подвыражение }

SIN(Х/2) { вызов функции }

['0..''9','А'..'Z'] { описатель множества }

not Done { отрицание булевской переменной }

сhar(Digit+48) { назначение типа }

Термы используются в операциях умножения на множитель:

ЪДДДДДДДДДї

терм ДДДДДДДД>імножительГДДДВДДДДДД>

^ АДДДДДДДДДЩ і

і ЪДДДї і

ГДДДДДґ * і<ДДДДДДДДґ

і АДДДЩ і

і ЪДДДї і

ГДДДДДґ / і<ДДДДДДДДґ

і АДДДЩ і

і ЪДДДї і

ГДДДДДґdivі<ДДДДДДДДі

і АДДДЩ і

і ЪДДДї і

ГДДДДДґmodі<ДДДДДДДДі

і АДДДЩ і

і ЪДДДї і

ГДДДДДґandі<ДДДДДДДДі

і АДДДЩ і

і ЪДДДї і

ГДДДДДґshlі<ДДДДДДДДі

і АДДДЩ і

і ЪДДДї і

АДДДДДґshrі<ДДДДДДДДЩ

АДДДЩ

Приведем несколько примеров термов:

Х * Y

Z / (1 - Z)

Done or Error

(Х <= Y) and (Y < Z)

В простых выражениях к термам применяются операции сложения

и присваивания знака:

ЪДДДДДДДї

простое выражение ДДДДДДДД>і терм ГДДДВДДДД>

^ АДДДДДДДЩ і

і ЪДДДї і

ГДДДДДґ + і<ДДДДДДґ

і АДДДЩ і

і ЪДДДї і

ГДДДДДґ - і<ДДДДДДґ

і АДДДЩ і

і ЪДДДї і

ГДДДДДґ orі<ДДДДДДі

і АДДДЩ і

і ЪДДДї і

АДДДДДґxorі<ДДДДДДЩ

АДДДЩ

Приведем несколько примеров простых выражений:

Х + Y

-Х

Hue1 + Hue2

I * J + 1

В выражениях к простым выражениям применяются операции отно-

шения.

ЪДДДДДДДДДї

выражение ДДДД>і простое ГДДВДД>

івыражениеі і ^

АДДДДДДДДДЩ і ЪДДДї ЪДДДДДДДДДї і

ГД>і < ГДДДДДД>і простое ГДДЩ

і АДДДЩ ^ івыражениеі

і ЪДДДї і АДДДДДДДДДЩ

ГД>і<= ГДДґ

і АДДДЩ і

і ЪДДДї і

ГД>і > ГДДґ

і АДДДЩ і

і ЪДДДї і

ГД>і>= ГДДґ

і АДДДЩ і

і ЪДДДї і

ГД>і = ГДДґ

і АДДДЩ і

і ЪДДДї і

ГД>і<> ГДДґ

і АДДДЩ і

і ЪДДДї і

АД>іin ГДДЩ

АДДДЩ

Приведем некоторые примеры выражений:

Х = 1.5

Done <> Error

(I < J) = (J < К)

C in Huel

Операции

Операции подразделяются на арифметические операции, логичес-

кие операции, операции со строками, операции над множествами,

операции отношения и операцию @ (операция получения адреса).

Арифметические операции

В следующей таблице приведены типы операндов и результаты

для бинарных и унарных арифметических операций:

Бинарные арифметические операции Таблица 6.2

ЪДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДї

і Операция і Действие і Типы операндов і Тип результата і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і + і Сложение і Целый і Целый і

і і і Вещественный і Вещественный і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і - і Вычитание і Целый і Целый і

і і і Вещественный і Вещественный і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і * і Умножение і Целый і Целый і

і і і Вещественный і Вещественный і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і / і Деление і Целый і Вещественный і

і і і Вещественный і Вещественный і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і div і Целочисленноеі і і

і і деление і Целый і Целый і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і mod і Остаток і Целый і Целый і

АДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДЩ

Примечание: Операция + используется также, как опера-

ция для работы со строками и множествами. Операции +, - и *

используются также для операций над множествами.

Унарные арифметические операции

Таблица 6.3

ЪДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДї

і Операция і Действие і Тип операнда і Тип результата і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і + і Сохранение і Целый і Целый і

і і знака і Вещественный і Вещественный і

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і - і Отрицание і Целый і Целый і

і і знака і Вещественный і Вещественный і

АДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДЩ

Любая операция, включающая операнд, тип которого является

подмножеством порядкового типа, обрабатывается также, как если бы

он был порядкового типа.

Если оба операнда в операциях +, -, *, div или моd являются

операндами целого типа, то тип результата будет таким же, как об-

щий тип обоих операндов. (Определение общего типа см. в разделе

"Целый тип" в Главе 3).

Если один или более операндов в операциях +, -, или * имеют

вещественный тип, то тип результата будет вещественным, если ис-

пользована директива компилятора {$N-}, или типом с повышенной

точностью при использовании директивы компилятора {$N+}.

Если при использовании операции сохранения знака или опера-

ции отрицания знака операнд имеет целый тип, то результат будет

тоже целого типа. Если операнд вещественного типа, то тип резуль-

тата будет вещественным или типом с повышенной точностью

(extended).

Значение выражения х/у всегда будет вещественного типа

(real) или с повышенной точностью (extended), независимо от типов

операндов. Если у равно 0, то результат будет ошибочным.

Значение выражение i div j представляет собой математическое

частное от i/j, округленное в меньшую сторону до значения целого

типа. Если j равно 0, результат будет ошибочным.

Операция mod возвращает остаток, полученный путем деления

двух ее операндов, то есть:

i mod j = i - (i div j) * j

Знак результата операции mod будет тем же, что и знак i. Ес-

ли j равно нулю, то результатом будет ошибка.

Логические операции

Типы логических операций показаны в Таблице 6.4.

Логические операции Таблица 6.4

ЪДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДї

і Операция і Действие іТипы операндові Тип результатаі

ГДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

і not і Отрицание (битовое) і Целый і Целый і

і and і И (битовое) і Целый і Целый і

і or і ИЛИ (битовое) і Целый і Целый і

і xor і Исключающее ИЛИ і Целый і Целый і

і і (битовое) і і і

і shl і Сдвиг влево і Целый і Целый і

і shr і Сдвиг вправо і Целый і Целый і

АДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДЩ

Примечание: Операция not является унарной операцией.

Если операндом операции not является операнд целого типа, то

результат будет также целого типа.

Если оба операнда в операциях or, and или xor целого типа,

то тип результата будет таким же, как тип обоих операндов.

Операции i shl j и i shr j сдвигают значение i влево или

вправо на j битов. Тип результата будет таким же, как тип i.

Булевские операции

Типы операндов и результат для булевских операций показаны в

Таблице 6.5.

Таблица 6.5 Булевские операции

ЪДДДДДДДДДДВДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДї

і Операция і Действие і Типы операндов і Тип результатаі

ГДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

і not і Отрицание і Булевский і Булевский і

і and і Логическое И і Булевский і Булевский і

і or і Логическое ИЛИ і Булевский і Булевский і

і xor і Логическое і і і

і і исключающее ИЛИ і Булевский і Булевский і

АДДДДДДДДДДБДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДЩ

Примечание: Операция not является унарной операцией.

Результаты этих операций соответствуют обычной булевой логи-

ке. Например, выражение a and b является истинным (принимает зна-

чение Тruе) только в том случае, если оба операнда a и b имеют

истинное значение (Тruе).

В Borland Pascal поддерживаются две различные модели генера-

ции кода для операций or и and - полное вычисление и вычисление

по короткой схеме (частичное вычисление).

При полном вычислении подразумевается, что каждый операнд

булевского выражения, построенный с помощью операций or и and,

всегда будет вычисляться, даже если результат всего выражения уже

известен. Эта модель полезна в том случае, когда один или более

операндов в выражении представляют собой функции с побочными эф-

фектами, которые изменяют смысл программы.

Вычисление по короткой схеме обеспечивает строгое вычисление

слева направо. Это вычисление прекращается, как только результат

всего выражения становится очевиден. Во многих случаях эта модель

удобна, поскольку она обеспечивает минимальное время выполнения

и, как правило, минимальный объем кода. Вычисление по короткой

схеме делает также возможными такие конструкции, которые в про-

тивном случае были бы недопустимы, например:

while (I<=Lenght(S)) and (S[I]<>' ') do

Inc(I);

while (P<>nil) and (P^.Value<>5) do

P:=P^.Next;

В обоих случаях, если результатом первого вычисления будет

значение False, вычисление второго выражения не выполняется.

Схему вычисления можно задавать с помощью директивы компиля-

тора $B. Значением по умолчанию является состояние {$B-} (пока

оно не будет изменено с помощью "меню" возможностей компилятора).

В этом случае генерируется код с вычислением по короткой схеме. В

случае директивы {$B+} генерируется код с полным вычислением.

Поскольку в стандартном Паскале не определяется, какую схему

следует использовать для вычисления булевских выражений, то прог-

раммы, зависящие от действия какой-либо конкретной схемы, в дейс-

твительности не являются переносимыми. Однако, если пожертвовать

переносимостью, то очень часто можно получить значительный выиг-

рыш во времени выполнения и простоте, которую позволяет получить

вычисление по короткой схеме.

Операция со строками

Типы операндов и результаты для операции со строками показа-

ны в Таблице 6.6.

Операции со строками Таблица 6.6

ЪДДДДДДДДДДДВДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДї

і Операция і Действие і Типы операндов іТип результатаі

ГДДДДДДДДДДДЕДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і + і Конкатенация і Строковый, і Строковый і

і і і символьный или і і

і і іупакованный строковыйі і

АДДДДДДДДДДДБДДДДДДДДДДДДДДБДДДДДДДДДДДДДДЩ

Borland Pascal позволяет использовать операцию + для объеди-

нения двух строковых операндов. Результатом операции s + t, где s

и t имеют строковый тип, символьный тип (Char) или упакованный

строковый тип, будет конкатенация s и t. Результат будет совмес-

тим с любым строковым типом (но не с символьным Char и не с упа-

кованным строковым типом). Если длина результирующей строки пре-

вышает 255 символов, то она усекается до 255 символов.

Операции над символьными указателями

Расширенный синтаксис (разрешенный по директиве компилятора

{$X+}) поддерживает несколько операций с указателями на PChar.

Для увеличения и уменьшения смещения указателя можно использовать

операции + и -. Минус можно также использовать для вычисления

расстояния (разности) между двумя символьными указателями. Если P

и Q - это значения типа PChar, а I - значение типа Word, то до-

пустимы следующие конструкции:

Допустимые конструкции PChar Таблица 6.7

ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДї

і Операция і Результат і

ГДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і P + I і Сложение I со смещением P. і

і I + P і Сложение I со смещением P. і

і P - I і Вычитание I из смещения P. і

і P - Q і Вычитает смещение Q из смещения P. і

АДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДЩ

Операции P + I и I + P складывает I c адресом, заданным P,

создавая указатель, ссылающийся на I символов после P. Операция P

- I вычитает I из адреса, заданного P, создавая указатель, ссыла-

ющийся на I символов перед P.

Операция P - Q вычитает расстояние между Q (младший адрес) и

P (старший адрес), создавая в результате значение типа Word, по-

казывающее число символов между Q и P. Эта операция подразумева-

ет, что P и Q ссылаются на один символьный массив. Если два сим-

вольный указателя ссылаются на разные массивы, то результат будет

не определен.

Операции над множествами

Типы операндов для операций над множествами показаны в Таб-

лице 6.7.

Операции над множествами Таблица 6.7

ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДДДВДДДДї

і Операция і Действие і Типы операндов і

ГДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДґ

і + і Объединение і Множества с совместимыми типами і

і - і Разность і Множества с совместимыми типами і

і * і Пересечение і Множества с совместимыми типами і

АДДДДДДДДДДДДДДДБДДДДДДДДДДДДДБДДДДЩ

Результаты операций соответствуют правилам логики работы с

множествами:

1. Порядковое значение c содержится в a+b только тогда,

когда оно содержится в a или в b.

2. Порядковое значение c содержится в a-b только тогда,

когда оно содержится в a и не содержится в b.

3. Порядковое значение c содержится в a*b только тогда,

когда он содержится в обоих множествах a и b.

Если наименьшим порядковым значением, которое является чле-

ном результата операций над множествами, является a, а наибольшим

- b, то типом результата будет множество a..b.

Операции отношения

Типы операндов и результаты операций отношения приведены в

Таблице 6.8.

Таблица 6.8 Операции отношения

ЪДДДДДДДДДВДДДДДДДДДДДДВВДДДДДДДДДДДДДДДї

і Операцияі Действие і Типы операндов і Тип результатаі

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

і = і Равно і Совместимый простой, і Булевский і

і і і указатель, множествен- і і

і і і ный строковый или упа- і і

і і і кованный строковый і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

і <> і Не равно і Совместимый простой, і Булевский і

і і і указатель, множествен- і і

і і і ный, строковый или упа-і і

і і і кованный строковый і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДЕДДДДДДДДДДДДДДДґ

і < і Меньше чем і Совместимый простой, і Булевский і

і і і указатель, множествен- і і

і і і ный, строковый или упа-і і

і і і кованный строковый і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і > і Больше чем і Совместимый простой, і Булевский і

і і і указатель, множествен- і і

і і і ный строковый или упа- і і

і і і кованный строковый і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

і <= і Меньше і Совместимый простой, і Булевский і

і і или равно і указатель, множествен- і і

і і і ный строковый или упа- і і

і і і кованный строковый і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і >= і Больше і Совместимый простой, і Булевский і

і і или равно і указатель, множествен- і і

і і і ный строковый или упа- і і

і і і кованный строковый і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДґ

і <= іПодмножествоі Множества совместимых і Булевский і

і і і типов і і

ГДДДДДДДДДЕДДДґ

і >= іНадмножествоі Множества совместимых і Булевский і

і і і типов і і

ГДДДДДДДДДЕДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДґ

і in і Элемент і Левый операнд: любой і Булевский і

і і множества і перечислимый тип t; і і

і і і правый: множество, і і

і і і совместимое с t. і і

АДДДДДДДДДБДДДДДДДДДДДДББДДДДДДДДДДДДДДДЩ

Сравнение простых типов

Когда операции =, <>, <, >, >= или <= применяются для опе-

рандов простых типов, то это должны быть совместимые типы. Одна-

ко, если один операнд имеет вещественный тип, то другой может

быть целого типа.

Сравнение строк

Операции отношения =, <>, <, >, >= или <= могут применятся

для сравнения строк согласно порядку расширенного набора символов

кода ASСII. Любые два значения строковых данных можно сравнить,

поскольку все значения строковых данных совместимы.

Значения символьного типа совместимы со значениями строково-

го типа, и при их сравнении символьное значение обрабатывается

как строковое значение с длиной 1. Когда со значением строкового

типа сравнивается упакованное строковое значение из N элементов,

то оно обрабатывается, как значение строкового типа длиной N.

Сравнение упакованных строк

Операции отношения =, <>, <, >, >= или <= могут применятся

также для двух упакованных значений строкового типа, если они со-

держат одинаковое число элементов. Если число элементов равно n,

то операция соответствует сравнению двух строк, каждая из которых

имеет длину n.

Сравнение указателей

Операции = и <> могут использоваться для сравнения операндов

типа указатель. Два указателя равны только в том случае, если они

ссылаются на один и тот же объект.

Сравнение символьных указателей

При разрешении по директиве компилятора {$X+} расширенного

синтаксиса операции =, <>, <, >, >= или <= могут применятся к

значениям PChar. Заметим, однако, что эти операции отношения

предполагают, что два сравниваемые указателя ссылаются на один и

тот же символьный массив.. По этой причине в сравнении участвуют

только смещения двух значений-указателей. Если указатели ссылают-

ся на разные символьные массивы, результат будет не определен.

Сравнение множеств

Если операндами являются множества a и b, то при их сравне-

нии получаются следующие результаты:

1. Выражение a=b истинно (= True) только когда a и b содер-

жат одни и те же элементы, в противном случае a<>b.

2. Выражение a = b истинно, когда каждый элемент множества

а является также элементом множества b.

3. Выражение a = b истинно, когда каждый элемент множества

b является также элементом множества a.

Проверка на принадлежность к множеству

Операция in возвращает истинное значение (True), когда зна-

чение элемента порядкового типа является элементом операнда мно-

жественного типа, в противном случае он возвращает значение

False.

Операция @

Операция @ используется в адресном коэффициенте для вычисле-

ния адреса переменной, процедуры, функции или метода. В Таблице

6.9 показан операнд и типы результата.

адресный коэффициент

і ЪДДДї ЪДДДДДДДДДДДДДДДДДДДДДДї

АДДі @ ГДДВДДДДДі ссылка не переменную ГДДДДДДДДДДДДДДДДДДДДДДД>

АДДДЩ і АДДДДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДї і

ГДДДД>і идентификатор процедуры ГДДДДДДДДДДДґ

і АДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і идентификатор функции ГДДДДДДДДДДДДДґ

і АДДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДї і

АДДДД>і уточненный идентификатор метода ГДДДЩ

АДДДДЩ

Операция создания указателя Таблица 6.9

ЪДДДДДДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДї

і Операция і Действие і Типы операндов іТип результатаі

ГДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і @ і Получение і Ссылка на переменную, і Указатель і

і і указателя і процедуру или иденти- і (совмести- і

і і і фикатор функции. і мый с nil) і

АДДДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДЩ

Операция @ возвращает адрес операнда, то есть строит значе-

ние-указатель, ссылающееся на этот операнд.

Использование операции @ для переменной

Использование операции @ для обычной переменной (не парамет-

ра) не вызывает никаких сложностей. Применение @ к ссылке на пе-

ременную возвращает указатель на переменную. Введем описания:

type

TwoChar = array[0..1] of char;

var

Int: integer;

TwoCharPtr: ^TwoChar;

тогда оператор:

TwoCharPtr := @Int;

приводит к тому, что TwoCharPtr для получения ссылки на

TwoCharPtr^ становится повторной интерпретацией значения Int, как

если бы оно было символьным массивом array[0..1].

Тип получаемого в результате указатель управляется директи-

вой компилятора $T: в состоянии {$T-} (по умолчанию) типом ре-

зультата будет Pointer. Другими словами, результат ом является

нетипизированный указатель, совместимый со всеми другими типами

указателей. В состоянии {$T+} типом результата будет ^T, где T -

тип ссылки на переменную. То есть тип результата будет совместим

со всеми другими указателями на тип этой переменной.

Примечание: К использованию операции @ с процедурным

типом применяются специальные правила. См. ниже раздел

"Процедурный типы в выражениях".

Использование операции @ для процедуры или функции или метода

Вы можете применять операцию @ к процедуре, функции или ме-

тоду. При этом вы получите указатель на точку входа подпрограммы.

Независимо от состояния $T, типом полученного в результате указа-

теля всегда будет Pointer. Другими словами, результатом всегда

является нетипизированный указатель, совместимый со всеми другими

ссылочными типами.

При применении операции @ к методу метод должен задаваться с

помощью уточненного идентификатора (идентификатора объектного ти-

па, за которым следует точка и идентификатор метода).

Вызовы функции

Вызовы функции приводят к активизации функции, заданной с

помощью идентификатора функции. Идентификатором функции является

любой идентификатор, использованный для обозначения функции.

Если в соответствующем описании функции содержится список

формальных параметров то в вызове функции должен содержаться спи-

сок фактических параметров. Каждый параметр подставляется вместо

соответствующего формального параметра в соответствии с набором

правил, который вводится в Главе 9 ("Процедуры и функции").

Примечание: См. выше разделы "Активизация методов",

"Активизация уточненных методов" и "Процедурные типы".

ЪДДДДДДДДДДДДДї

вызов функции ДВД>іидентификаторГДВВДДД>

і і функции і іі ^

і АДДДДДДДДДДДДДЩ іі ЪДДДДДДДДДДДДДДДДДДї і

і ЪДДДДДДДДДДДДДї іАДД>ісписок фактическихГДДЩ

ГД>і десигнатор ГДґ і параметров і

і і метода і і АДДДДДДДДДДДДДДДДДДЩ

і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

і і уточненный і і

ГД>і десигнатор ГДґ

і і метода і і

і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

АД>і ссылка на ГДЩ

і переменную і

АДДДДДДДДДДДДДЩ

ЪДДДї ЪДДДДДДДДДДДї ЪДДДї

список фактических ДДДД>і ( ГДДДДД>іфактическийГДДВД>і ) ГДДД>

параметров АДДДЩ ^ і параметр і і АДДДЩ

і АДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДґ , і<ДДДДДДДДЩ

АДДДЩ

ЪДДДДДДДДДДДДї

фактический параметр ДДВДД>і выражение ГДДДДДДДД>

і АДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДї і

АДД>і ссылка на ГДДДЩ

і переменную і

АДДДДДДДДДДДДЩ

Приведем некоторые примеры вызовов функций:

Sum(A,63)

Maximum(147,J)

Sin(X+Y)

Eof(F)

Volume(Radius, Height)

В режиме расширенного синтаксиса ($X+) вызовы функций можно

использовать в качестве операторов, то есть результат вызова

функции может отбрасываться.

Описатели множества

Описатель множества определяет значения множественного типа

и получается путем записи выражений, заключенных в квадратные

скобки ([]). Каждое выражение определяет значение множества.

ЪДДДї ЪДДДї

описатель ДДД>і [ ГДДВ>і ] ГДДД>

множества АДДДЩ і ЪДДДДДДДДДДДДї ^ АДДДЩ

АДДД>і группа ГДДВДЩ

^ і элементов і і

і АДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДґ , і<ДДДДДДДДЩ

АДДДЩ

ЪДДДДДДДДДДДї

группа элементов ДД>і выражение ГДДВДДД>

АДДДДДДДДДДДЩ і ^

і ЪДДї ЪДДДДДДДДДДДї і

АД>і..ГДД>і выражение ГДЩ

АДДЩ АДДДДДДДДДДДЩ

Обозначение [ ] означает пустое множество, тип которого сов-

местим по присваиванию с типом любого множества. Любая группа

элементов, описанная, как х..у, объявляет элементами множества

все значения в диапазоне х..у. Если х больше, чем у, то х..у не

описывает никаких элементов и [x..y] обозначает пустое множество.

В конкретном описателе множества все значения выражения в

группах элементов должны быть одного порядкового типа.

Приведем некоторые примеры описателей множеств:

[red, C, green]

[1,5,10..K mod 12, 13, 23]

['A'..'Z', 'a'..'z', Chr(Digit+48)]

Приведение типа значений

Тип выражения можно изменить на другой тип с помощью приве-

дения типа значений.

ЪДДДДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДї ЪДДДї

приведение ДДД>іидентификаторГДД>і ( ГДД>івыражениеГДД>і ) ГД>

типа значения і типа і АДДДЩ АДДДДДДДДДЩ АДДДЩ

АДДДДДДДДДДДДДЩ

Тип выражения и задаваемый тип должны оба иметь перечислимый

тип или тип указателей. Для перечислимых типов результирующее

значение получается путем преобразования выражения (и возможной

проверки на нахождение в допустимых границах). Преобразование мо-

жет привести к усечению или увеличению размера исходного значения

в том случае, если вновь определяемый тип отличается от типа вы-

ражения. В том случае, когда значение расширяется, его знак всег-

да сохраняется. Таким образом, значение является расширяемым по

знаку.

Синтаксис приведения типа значений почти совпадает с синтак-

сисом приведения типа переменных (см. раздел "Приведение типа пе-

ременных" в Главе 5). Однако при приведении типа значений опера-

ции производятся со значениями, а не с переменными и, таким

образом, могут не участвовать в ссылках на переменные. То есть за

приведением типа значения не обязательно следуют квалификаторы. В

частности, приведение типа значений не должно встречаться в левой

части оператора присваивания.

Некоторые примеры приведения типа значений включают в себя:

Intereg('A')

Char(48)

Boolean(0)

Color(2)

IntPtr(@Buffer)

BytePtr(Ptr($40,$49))

Процедурные типы в выражениях

В общем случае использование процедурной переменной в опера-

торе или выражении означает вызов процедуры или функции, храня-

щейся в этой переменной. Однако, имеется исключение. Когда компи-

лятор видит, что процедурная переменная находится в левой части

оператора присваивания, он знает, что правая часть должна предс-

тавлять собой процедурное значение. Рассмотрим в качестве примера

следующую программу:

type

IntFunc = function: Integer;

var

F: IntFunc;

N: Integer;

function ReadInt: Integer; far;

var

I: Integer;

begin

Read(I);

ReadInt := I;

end;

begin

F := ReadInt; { присваивание процедурного значения }

N := ReadInt; { присваивание результата функции }

end.

Первый оператор основной программы присваивает процедурное

значение (адрес процедуры) ReadInt процедурной переменной F, вто-

рой оператор вызывает ReadInt и присваивает N возвращаемое значе-

ние. Различие между получением процедурного значения или вызовом

функции осуществляется по типу переменной, которой присваивается

значение (F или N).

К сожалению, есть ситуации, когда компилятор не может опре-

делить из контекста желаемое действие. Например, в следующем опе-

раторе для компилятора не очевидно, что нужно сделать: сравнить

процедурное значение в F с процедурным значением ReadInt, чтобы

определить, что F указывает в данный момент на ReadInt, или выз-

вать F и ReadInt, а затем сравнить возвращаемые значения:

if F = ReadInt then

WriteLn('Equal');

Однако, стандартный синтаксис Паскаля определяет, что вхож-

дение в выражение идентификатора функции означает вызов этой

функции, поэтому в результате предыдущего оператора будет выпол-

нен вызов F и ReadInt, а затем будут сравниваться возвращаемые

значения. Чтобы сравнить процедурное значение в F с процедурным

значением в ReadInt, нужно использовать следующую конструкцию:

if @F = @ReadInt then

WriteLn('Equal');

При применении к процедурной переменной, идентификатору про-

цедуры или функции операции получения адреса @, эта операция пре-

дотвращает вызов компилятором процедуры и в то же время преобра-

зует аргумент в указатель. Таким образом, @F преобразует F в не-

типизованный указатель-переменную, которая содержит адрес

ReadInt. Для определения того, что F ссылается на ReadInt можно

сравнить два значения-указателя.

Операция @ часто используется при присваивании процедурной

переменной нетипизированного значения-указателя. Например, опре-

деленная в Windows (в модуле WinProcs) функция GetProcAddress

возвращает адрес экспортируемой функции в DLL как нетипизирован-

ной значение-указатель. С помощью операции @ вызов GetProcAddress

можно присвоить процедурной переменной:

type

TStrComp = function(Str1, Str2: PChar): Integer;

var

StrComp: TStrComp:

.

.

.

begin

.

.

.

@StrComp := GetProcAddress(KernelHandle, 'Lstrcmpi');

.

.

.

end.

Чтобы получить адрес в памяти процедурной переменной, а не

адрес, в ней записанный, используйте двойную операцию @ (@@).

Например, @P означает преобразование P в нетипизированный указа-

тель-переменную, в @@P означает возвращение физического адреса

переменной P.


Глава 7. Операторы

Операторы описывают те алгоритмические действия, которые

должны выполняться. Операторам могут предшествовать метки, кото-

рые можно использовать для ссылок в операторах перехода goto.

оператор ДДВДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДД>

і ЪДДДДДї ЪДДДї ^ і ЪДДДДДДДДДДДДДДДДї ^

АД>іметкаГДД>і : ГДЩ ГДД>іпростой операторГДДґ

АДДДДДЩ АДДДЩ і АДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДї і

АДД>і структурный ГДДЩ

і оператор і

АДДДДДДДДДДДДДДДДЩ

Метка - это последовательность цифр в диапазоне от 0 до 9999

или идентификатор.

Существует два основных вида операторов: простые операторы и

структурные операторы.

Простые операторы

Простым оператором является такой оператор, который не со-

держит в себе других операторов.

ЪДДДДДДДДДДДДДДДДДДДДДї

простой оператор ДДДДВДДД>іоператор присваиванияГДДДДДДД>

і АДДДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДДДї і

ГДДД>і оператор процедуры ГДДДґ

і АДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДї і

АДДД>і оператор перехода ГДДДЩ

АДДДДДДДДДДДДДДДДДДДДДЩ

Оператор присваивания

Оператор присваивания заменяет текущее значение переменной

новым значением, которое определяется выражением, или определяет

выражение, значение которого должно возвращаться функцией.

ЪДДДДДДДДДДДї ЪДДї ЪДДДДДДДДДї

оператор ДДДДДВДД>іссылка на ГДДДДДД>і:=ГДД>івыражениеГДД>

присваивания і іпеременную і ^ АДДЩ АДДДДДДДДДЩ

і АДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

АДД>іидентификаторГДЩ

і функции і

АДДДДДДДДДДДДДЩ

Выражение должно быть совместимо по присваиванию с типом пе-

ременной или типом значения, возвращаемого функцией в качестве

результата (см. раздел "Совместимость типов" в Главе 4).

Приведем некоторые примеры операторов присваивания:

X := Y + Z

Done := (I >= 1) and (I < 100);

Huel := [blue, Succ(C)];

I := Sqr(J) - I * K;

Присваивания объектного типа

Правила совместимости по присваиванию объектных типов позво-

ляют присваивать экземпляру объекта экземпляр любого из его до-

черних типов. Такое присваивание представляет собой проекцию

потомка на пространство его предка. В примере исходного кода в

Главе 4 с учетом экземпляра F типа TField и экземпляра Z типа

TZipField присваивание F := Z копирует только поля X, Y, Len и

Name.

Присваивание экземпляру объектного типа не инициализирует

экземпляр. Например, в предыдущем примере присваивание F := Z оз-

начает, что вызов конструктора для F можно опустить.

Операторы процедуры

Оператор процедуры определяет активизацию процедуры, обозна-

ченную с помощью идентификатора процедуры. Если соответствующее

описание процедуры содержит список формальных параметров, то опе-

ратор процедуры должен содержать в себе соответствующий ему спи-

сок фактических параметров (параметры, список которых приводится

в определении, являются формальными параметрами, а в операторе

вызова процедуры они являются фактическими параметрами). При вы-

зове происходит передача фактических параметров формальным пара-

метрам.

ЪДДДДДДДДДДДДДї

оператор ДДВД>іидентификаторГДВВДДДДДДДДДДДДДДДДДДДДДДДДДД>

процедуры і і процедуры і іі ЪДДДДДДДДДДДДДДДДДДї ^

і АДДДДДДДДДДДДДЩ іАД>ісписок фактическихГДЩ

і ЪДДДДДДДДДДДДДї і і параметров і

ГД>і десигнатор ГДґ АДДДДДДДДДДДДДДДДДДЩ

і і метода і і

і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

ГД>і уточненный ГДґ

і і десигнатор і і

і і метода і і

і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДї і

АД>і ссылка на ГДЩ

і переменную і

АДДДДДДДДДДДДДЩ

Приведем некоторые примеры операторов процедур:

PrintHeaing;

Transpose(A,N,M);

Fin(Name,Address);

Операторы перехода

Оператор перехода goto вызывает передачу управления операто-

ру, которому предшествует метка, указанная в данном операторе пе-

рехода. Синтаксическая схема оператора перехода имеет следующий

вид:

ЪДДДДї ЪДДДДДї

оператор перехода ДДД>іgotoГДДД>іметкаГДДД>

АДДДДЩ АДДДДДЩ

При использовании оператора перехода должны соблюдаться сле-

дующие правила:

1. Метка, которая указывается в операторе перехода, должна

находиться в том же блоке или модуле, что и сам оператор

перехода. Другими словами, не допускаются переходы из

процедуры или функции или внутрь нее.

2. Переход извне внутрь структурного оператора (то есть пе-

реход на более глубокий уровень вложенности) может выз-

вать непредсказуемые эффекты, хотя компилятор не выдает

сообщения об ошибке. Например, вы не должны переходить в

тело цикла for.

Примечание: Хорошая практика программирования требует

минимального использования переходов.

Структурные операторы

Структурные операторы строятся из других операторов, порядок

выполнения которых должен быть последовательным (составные опера-

торы и операторы над записями), определяемым условной передачей

управления (условные операторы) или повторяющимся (операторы цик-

ла).

ЪДДДДДДДДДДДДДДДДДДДДДДДї

структурный ДДДДВДДДД>і составной оператор ГДДДДДДД>

оператор і АДДДДДДДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і условный оператор ГДДДґ

і АДДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДДДї і

ГДДДД>і оператор цикла ГДДДґ

і АДДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДДДДДДДї і

АДДДД>і оператор над записями ГДДДЩ

АДДДДДДДДДДДДДДДДДДДДДДДЩ

Составные операторы

Составные операторы задают порядок выполнения операторов,

являющихся их элементами. Они должны выполняться в том порядке, в

котором они записаны. Составные операторы обрабатываются, как

один оператор, что имеет решающее значение там, где синтаксис

Паскаля допускает использование только одного оператора. Операто-

ры заключаются в ограничители begin и end, и отделяются друг от

друга точкой с запятой.

ЪДДДДДї ЪДДДДДДДДї ЪДДДї

составной ДДДД>іbeginГДДДДДД>іоператорГДДДДВДД>іendГДД>

оператор АДДДДДЩ ^ АДДДДДДДДЩ і АДДДЩ

і ЪДДДї і

АДДДДДґ ; і<ДДДДДДЩ

АДДДЩ

Приведем пример составного оператора:

begin

Z := X;

X := Y;

Y := Z;

end;

Условные операторы

Условные операторы позволяют выбрать для выполнения один из

составных операторов (или не выбрать ни одного).

ЪДДДДДДДДДДДДДДДї

условный оператор ДДВДД>і оператор if ГДДДДДДД>

і АДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДї і

АДД>і оператор case ГДДДЩ

АДДДДДДДДДДДДДДДЩ

Оператор условия (if)

Синтаксис оператора if можно представить следующим образом:

ЪДДї ЪДДДДДДДДДї ЪДДДДї ЪДДДДДДДДї

оператор if Д>іifГДД>івыражениеГДД>іthenГДД>іоператорГДДВДДї

АДДЩ АДДДДДДДДДЩ АДДДДЩ АДДДДДДДДЩ і і

ЪДДДДДДДДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДї ЪДДДДДДДДї v

АДД>іelseГДД>іоператорГДДДДДДДДД>

АДДДДЩ АДДДДДДДДЩ

В выражении должен получаться результат, имеющий стандартный

булевский тип. Если результатом выражения является истинное зна-

чение (True), то выполняется оператор, следующий за ключевым сло-

вом then.

Если результатом выражения является значение False и при-

сутствует ключевое слово else, то выполнятся оператор, следующий

за ключевым словом else. Если ключевое слово else отсутствует, то

никакой оператор не выполняется.

Синтаксическая неоднозначность, возникающая в конструкции:

if e1 then e2 else e3

разрешается путем следующей интерпретации этой конструкции:

if e1 then

begin

if e2 then

s1

else

s2

end

Примечание: В предшествующем операторе else двоеточие

не указывается.

В общем случае ключевое слово else связывается с ближайшим

ключевым словом if, которое еще не связано с ключевым словом

else.

Приведем два примера оператора if:

if X < 1.5 then

Z := X+Y

else

Z := 1.5;

if P1 <> nil then

P1 := P1^.father;

Оператор варианта (case)

Оператор варианта (casе) состоит из выражения (переключате-

ля) и списка операторов, каждому из которых предшествует одна или

более констант (они называются константами выбора) или ключевое

слово else. Переключатель (селектор) должен иметь порядковый тип

(размером в байт или слово). Таким образом, строковый тип и длин-

ный целый тип являются недопустимыми типами переключателя. Все

константы выбора должны быть уникальными и иметь порядковый тип,

совместимый с типом переключателя.

ЪДДДДї ЪДДДДДДДДДї ЪДДї ЪДДДДї

оператор case Д>іcaseГДД>івыражениеГДД>іofГДДДДД>іcaseГДДВДДї

АДДДДЩ АДДДДДДДДДЩ АДДЩ ^ АДДДДЩ і і

і ЪДДДДї і і

АДДДґ ; і<ДЩ і

АДДДДЩ і

ЪДДДДДДДДДДЩ

і ЪДДДї

АДВДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДД>іendГДД>

і ЪДДДДДДДДДДї ^ і ЪДДДї ^ АДДДЩ

АДД>іветвь elseГДДЩ АД>і ; ГДДЩ

АДДДДДДДДДДЩ АДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДї

ЪДДДДДДДДДї і ЪДДї ЪДДДДДДДДДї v ЪДДДї ЪДДДДДДДДї

case ДД>іконстантаГДБД>і..ГД>іконстантаГДДВД>і : ГД>іоператорГД>

^ АДДДДДДДДДЩ АДДЩ АДДДДДДДДДЩ і АДДДЩ АДДДДДДДДЩ

і ЪДДДї і

АДДДДДДДДДДДДДДДДДґ , і<ДДДДДДДДДДДДДЩ

АДДДЩ

ЪДДДДї ЪДДДДДДДДї

ветвь else ДДДД>іelseГДДД>іоператорГДДД>

АДДДДЩ АДДДДДДДДЩ

Оператор варианта case приводит к выполнению оператора, ко-

торому предшествует константа выбора, равная значению переключа-

теля или диапазону выбора, в котором находится значение переклю-

чателя. Если такой константы выбора или такого диапазона выбора

не существует и присутствует ветвь else, то выполнятся оператор,

следующий за ключевым словом else. Если же ветвь else отсутству-

ет, то никакой оператор не выполняется.

Приведем некоторые примеры оператора варианта:

case Operator of

plus: X := X+Y;

minus: X := X-Y;

times: X := X*Y;

end;

case I of

0, 2, 4, 6, 8: Writeln('Четная цифра');

1, 3, 5, 7, 9: Writeln('Нечетная цифра');

10..100: Writeln('Между 10 и 100');

end;

Оператор цикла

Оператор цикла задает повторное выполнение определенных опе-

раторов.

ЪДДДДДДДДДДДДДДДДДї

оператор цикла ДДДВДД>і оператор repeat ГДДДДДД>

і АДДДДДДДДДДДДДДДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДї і

ГДД>і оператор while ГДДґ

і АДДДДДДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДДДДДДї і

АДД>і оператор for ГДДЩ

АДДДДДДДДДДДДДДДДДЩ

Если число повторений заранее известно, то подходящей конс-

трукций является оператор for. В противном случае следует исполь-

зовать операторы while или repeat.

Для управления повторением операторов можно использовать

стандартные процедуры Break и Continue. Break завершает оператор

цикла, а Continue продолжает со следующей итерации этого операто-

ра. Подробности вы можете найти в Главе 1 "Справочного руководс-

тва программиста".

Оператор цикла с постусловием (repeat)

В операторе цикла с постусловием (начинающимся со слова

repeat) выражение, которое управляет повторным выполнением после-

довательности операторов содержится внутри оператора repeat.

ЪДДДДДДї ЪДДДДДДДДї ЪДДДДДї ЪДДДДДДДДДї

оператор Д>іrepeatГДДДД>іоператорГДДВД>іuntilГДД>івыражениеГДД>

repeat АДДДДДДЩ ^ АДДДДДДДДЩ і АДДДДДЩ АДДДДДДДДДЩ

і ЪДДДї і

АДДДДґ ; і<ДДДДЩ

АДДДЩ

Результат выражения должен быть булевского типа. Операторы,

заключенные между ключевыми словами repeat и until, выполняются

последовательно до тех пор, пока результат выражения не примет

значение True. Последовательность операторов выполнится по край-

ней мере один раз, поскольку вычисление выражения производится

после каждого выполнения последовательности операторов.

Приведем примеры оператора цикла с постусловием:

repeat

K := I mod J;

I := J;

J := K;

until J = 0;

repeat

Write('Введите значение (0..9):');

Readln(I);

until (I >= 0) and (I <= 9);

Операторы цикла с предусловием (while)

Оператор цикла с предусловием (начинающийся с ключевого сло-

ва while) содержит в себе выражение, которое управляет повторным

выполнением оператора (который может быть составным оператором).

ЪДДДДДї ЪДДДДДДДДДї ЪДДї ЪДДДДДДДДї

оператор ДДД>іwhileГДД>івыражениеГДД>іdoГДД>іоператорГДД>

while АДДДДДЩ АДДДДДДДДДЩ АДДЩ АДДДДДДДДЩ

Выражение, с помощью которого осуществляется управление пов-

торением оператора, должно иметь булевский тип. Вычисление его

производится до того, как внутренний оператор будет выполнен.

Внутренний оператор выполнятся повторно до тех пор, пока выраже-

ние принимает значение Тruе. Если выражение с самого начала при-

нимает значение False, то оператор, содержащийся внутри оператора

цикла с предусловием, не выполняется.

Примерами операторов цикла с предусловием могут служить сле-

дующие операторы:

while Data[I] <> X do I := I + 1;

While I > 0 do

begin

if Odd(I) then Z := Z * X;

I := I div 2;

X := Sqr(X);

end;

while not Eof(InFile) do

begin

Readln(InFile,Line);

Process(Line);

end;

Операторы цикла с параметром (for)

Операторы цикла с параметром (которые начинаются со слова

for) вызывает повторяющееся выполнение оператора (который может

быть составным оператором) пока управляющей переменной присваива-

ется возрастающая последовательность значений.

ЪДДДї ЪДДДДДДДДДДДї ЪДДї ЪДДДДДДДДї

оператор ДДД>іforГДД>іуправляющаяГДД>і:=ГДД>іисходноеГДДДї

for АДДДЩ іпеременная і АДДЩ ізначениеі і

АДДДДДДДДДДДЩ АДДДДДДДДЩ і

ЪДДДДДДДДДДДДДДДДДДДЩ

і ЪДДї

і ЪД>іtoГДДДДДї ЪДДДДДДДДї ЪДДї ЪДДДДДДДДї

АДДДґ АДДЩ ГДД>іконечноеГДД>іdoГДД>іоператорГДДД>

і ЪДДДДДДї і ізначениеі АДДЩ АДДДДДДДДЩ

АД>іdowntoГДЩ АДДДДДДДДЩ

АДДДДДДЩ

ЪДДДДДДДДДДДДДДДДДДДДДДДДї

управляющая переменная ДДД>іидентификатор переменнойГДДД>

АДДДДДДДДДДДДДДДДДДДДДДДДЩ

ЪДДДДДДДДДї

исходное значение ДДДД>івыражениеГДДД>

АДДДДДДДДДЩ

ЪДДДДДДДДДї

конечное значение ДДДД>івыражениеГДДД>

АДДДДДДДДДЩ

В качестве управляющей переменной должен использоваться

идентификатор переменой (без какого-либо квалификатора), который

обозначает переменную, объявленную локальной в блоке, в котором

содержится оператор for. Управляющая переменная должна иметь пе-

речислимый тип. Начальное и конечное значения должны иметь тип,

совместимый по присваиванию с перечислимым типом.

Примечание: О локальности и области действия рассказы-

вается в Главе 8.

Когда начинает выполняться оператор for, начальное и конеч-

ное значения определяются один раз, и эти значения сохраняются на

протяжении всего выполнения оператора for.

Оператор, который содержится в теле оператора for, выполня-

ется один раз для каждого значения в диапазоне между начальным и

конечным значением. Управляющая переменная всегда инициализирует-

ся начальным значением. Когда работает оператор for, значение уп-

равляющей переменной (счетчика циклов) увеличивается при каждом

повторении на единицу. Если начальное значение превышает конечное

значение, то содержащийся в теле оператора for оператор не выпол-

нятся. Когда в операторе цикла используется ключевое слово

downto, значение управляющей переменной уменьшается при каждом

повторении на единицу. Если начальное значение в таком операторе

меньше, чем конечное значение, то содержащийся в теле оператора

цикла оператор не выполнятся.

Если оператор, содержащийся в теле оператора for, изменяет

значение управляющей переменной, то это является ошибкой. После

выполнения оператора for значение управляющей переменной стано-

вится неопределенным, если только выполнение оператора for не бы-

ло прервано с помощью оператора перехода.

Если принять во внимание эти ограничения, то оператор

for V := Expr1 to Expr2 do Body;

эквивалентен оператору:

begin

Temp1 := Expr1;

Temp2 := Expr2;

if Temp1 <= Temp2 then

begin

V := Temp1;

Body;

while V <> Temp2 do

begin

V := Succ(V);

Body;

end;

end;

end;

и оператор цикла:

for V := Expr1 downto Exp2 do Body;

эквивалентен операторам:

begin

Temp1 := Expr1;

Temp2 := Expr2;

if Temp1 >= Temp2 then

begin

V := Temp1;

Body;

while V <> Temp2 o

begin

V := Pred(V);

Body;

end;

end;

end;

где Temp1 и Temp2 - вспомогательные переменные, тип которых сов-

падает с основным типом переменной V и которые не встречаются в

другом месте программы.

Приведем примеры оператора цикла с параметром:

for I := 2 to 63 do

if Data[I] > Max then Max := Data[I]

for I := 1 to 10 do

for J := 1 to 10 do

begin

X := 0;

for K := 1 to 10 do

X := X + Mat1[I,K]*Mat2[K,J];

Mat[I,J] := X;

end;

for C := red to blue do Check(C);

Оператор with

В операциях над записями оператор with удобно использовать

для краткого обращения к полям записи. В операторе with к полям

одной или более конкретных переменных типа запись можно обращать-

ся, используя только идентификаторы полей. Оперaтор with имеет

следующий синтаксис:

ЪДДДДї ЪДДДДДДДДДДДДДДДї ЪДДї ЪДДДДДДДДї

оператор ДД>іwithГДДДДД>і ссылка на ГДДВД>іdoГДД>іоператорГ>

with АДДДДЩ ^ іпеременную типаі і АДДЩ АДДДДДДДДЩ

і і запись і і

і і или объект і і

і АДДДДДДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДДДґ , і<ДДДДДДДДЩ

АДДДЩ

ссылка на переменную ЪДДДДДДДДДДДДДДДДДДДДї

типа запись или объект ДДД>іссылка на переменнуюГДД>

АДДДДДДДДДДДДДДДДДДДДЩ

Возьмем следующее описание:

type

TDate = record

Day : Integer:

Month : Integer;

Year : Integer:

end;

var OrderDate: TDate;

С учетом данного описания приведем пример оператора with:

with OrderDate do

if Month = 12 then

begin

Month := 1;

Year := Year + 1

end else

Month := Month + 1;

Это эквивалентно следующему:

if OrderDate.Month = 12 then

begin

OrderDate.Month := 1;

OrderDate.Year := TDate.Year + 1

end

else

Date.month := TDate.Month + 1;

B.Pascal 7 & Objects/LR - 132 -

В операторе with сначала производится проверка каждой ссылки

на переменную, а именно: можно ли ее интерпретировать, как поле

записи. Если это так, то она всегда интерпретируется именно таким

образом, даже если имеется доступ к переменной с тем же именем.

Допустим описаны следующие переменные:

type

TPoint = record

x,y: Integer;

end;

var

x: Point;

y: Integer;

В этом случае и к x, и к y можно обращаться, как к перемен-

ной или как к полю записи. В операторе:

with x do

begin

x := 10;

y := 25;

end;

x между ключевыми словами with и dо относится к переменной типа

указатель, а в составном операторе x и y ссылаются на x.x и y.y.

Оператор:

with V1,V2,...Vn do s;

эквивалентен операторам:

with V1 do

with V2 do

...

with Vn do

S;

В обоих случаях, если Vn является полем и v1, и v2, то она

интерпретируется как v2.Vn, а не как v1.Vn.

Если выборка переменной типа запись связана с индексировани-

ем массива или разыменованием указателя, то эти действия произво-

дятся до того, как будет выполняться составной оператор.


Глава 8. Блоки, локальность и область действия

Блоки состоят из описаний, которые записаны и скомбинированы

в любом порядке, и операторов. Каждый блок является частью описа-

ния процедуры или функции, или частью программы или модуля. Все

идентификаторы и метки, объявленные в разделе описаний, являются

для блока локальными.

Синтаксис

В общем виде любой блок имеет следующий формат:

ЪДДДДДДДДДДї ЪДДДДДДДДДДї

блок ДДДД>і раздел ГДДДД>і раздел ГДДДД>

і описания і іоператорові

АДДДДДДДДДДЩ АДДДДДДДДДДЩ

раздел ДДДДДДДДДДВДДДДДДДВДДД>

объявления ^ і ^ і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГДДД>і раздел описания ГДДДДДДґ і

і і і меток і і і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГДДД>і раздел описания ГДДДДДДґ і

і і і констант і і і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГДДД>і раздел описания ГДДДДДДґ і

і і і типов і і і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГДДД>і раздел описания ГДДДДДДґ і

і і і переменных і і і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГДДД>і оператор exports ГДДДДДДґ і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і АДДД>і раздел описания ГДДДДДДЩ і

і іпроцедур и функцийі і

і АДДДДДДДДДДДДДДДДДДЩ і

АДДДДДДДДДДДДДЩ

Раздел описания меток - это та часть блока, где описываются

метки, присваиваемые операторам в соответствующем разделе опера-

торов. Каждая метка должна помечать только один оператор.

раздел ЪДДДДДДДї ЪДДДДДДДї ЪДДДї

описания ДДДДДДД>і label ГДДДДДД>і метка ГДДВДД>і ; ГДДД>

меток АДДДДДДДЩ ^ АДДДДДДДЩ і АДДДЩ

і ЪДДДї і

АДДДґ , ГДДДДДДЩ

АДДДЩ

Меткой может быть идентификатор или последовательность цифр.

Используемая в качестве метки последовательность цифр должна на-

ходиться в диапазоне от 0 до 9999.

Раздел описания констант содержит описания констант, локаль-

ных для этого блока.

раздел ЪДДДДДДДї ЪДДДДДДДДДДДДї

описания ДДДДД>і const ГДДДДВД>і описание ГДДДДДДДДДДДВДДД>

констант АДДДДДДДЩ ^ і і константы і ^ і

і і АДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДДї і і

і і і описание і і і

і АД>і типизированной ГДЩ і

і і константы і і

і АДДДДДДДДДДДДДДДДДДДЩ і

АДЩ

Раздел описания типов включает описания всех типов в блоке.

раздел ЪДДДДДДДї ЪДДДДДДДДДДДДї

описания ДДДДД>і type ГДДДДДД>і описание ГДДДДВДДД>

типов АДДДДДДДЩ ^ і типа і і

і АДДДДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДДДДДДЩ

Раздел описания переменных состоит из описания переменных,

локальных для этого блока.

раздел ЪДДДДДї ЪДДДДДДДДДДДДї

описания ДДДДД>і var ГДДДДДД>і описание ГДДДДВДДД>

переменных АДДДДДЩ ^ і переменной і і

і АДДДДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДДДДДДЩ

Раздел описания процедур и функций состоит из описания про-

цедур и функций, локальных для этого блока.

раздел ЪДДДДДДДДДДДДї

описания ДДДДДДДДДДДВД>і описание ГДДДДДВДДДД>

процедур и ^ і і процедуры і ^ і

функций і і АДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДї і і

і АД>і описание ГДДЩ і

і і функции і і

і АДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДї і

ГДДДД>і описание ГДДДДДґ

і іконструктораі і

і АДДДДДДДДДДДДЩ і

і ЪДДДДДДДДДДДДї і

ГДДДД>і описание ГДДДДДґ

і ідеструктора і і

і АДДДДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДДДДДДДДЩ

В операторе exports перечисляются все процедуры и функции,

которые экспортируются данной программой или динамически компону-

емой библиотекой. Оператор exports допускается только во внешнем

разделе описаний программы или динамически компонуемой библиотеки

- в разделе описаний процедуры, функции или модуля его использо-

вать нельзя.

Раздел операторов определяет операторы или алгоритмические

действия, которые выполняются в блоке.

раздел ЪДДДДДДДДДДДї

операторов ДДДДД>і составной ГДДДДД>

і оператор і

АДДДДДДДДДДДЩ

Правила для области действия

Наличие идентификатора или метки в описании означает опреде-

ление идентификатора или метки. Каждый раз, когда идентификатор

или метка встречаются в программе, они должны находиться в облас-

ти действия этого описания.

Область действия для блока

Область действия идентификатора или метки в описании метки,

константе, типа, переменной, процедуры или функции распространя-

ется от их описания до конца текущего блока, включая все блоки,

входящие в текущий блок. Ниже приводится несколько исключений.

Идентификатор или метка, описанные во внешнем блоке, могут

заново описываться во внутреннем блоке, входящем во внешний блок.

До точки описания во внутреннем блоке или после конца вложенного

блока идентификатор или метка представляют элемент, описанный во

внешнем охватывающем блоке.

program Outer; { начало внешней области действия ъ

type

I = Integer; { определяет I как Integer }

var

T: I; { определяет T как целочисленную

переменную }

procedure Inner; { начало внутреннего блока }

type

T = I; { переопределяет T с типом Integer }

var

I: T; { переопределяет I как целочисленную

переменную }

begin

I := 1; { конец вложенного блока }

end;

begin

T := 1; { конец внешнего блока }

end.

Область действия записи

Область действия идентификатора поля, описанного в определе-

нии записи, простирается от точки описания до конца определения

типа запись. Кроме того, область действия идентификаторов включа-

ет десигнаторы поля и операторы with над ссылками на переменную

данного типа записи.

Примечание: О типе запись рассказывается в Главе 4.

Область действия объекта

Область действия идентификатора элемента, описанного в объ-

ектном типе, простирается от точки описания до конца определения

объектного типа и распространяется на все дочерние объектные типы

и блоки всех описаний методов объектного типа. Область действия

идентификаторов элемента включает десигнаторы поля и операторы

with над ссылками на переменную данного объектного типа.

Примечание: о типе запись рассказывается в Главе 4.

Область действия модуля

Область действия идентификаторов, описанных в интерфейсной

секции модуля, подчиняется правилам области действия блока и

распространяется на всех клиентов модуля. Другими словами, прог-

раммы и модули, содержащие операторы uses (операторы использова-

ния) имеют доступ к идентификаторам, которые описаны в интерфейс-

ной части модулей, указанных в этих операторах uses.

Каждый модуль в операторе uses определяет свою область дейс-

твия, которая охватывает остальные модули и программу в целом.

Первый модуль в операторе uses представляет самую внешнюю область

действия, а последний модуль представляет самую внутреннюю об-

ласть действия. Это означает, что если два или более модулей со-

держат описание одного и того же идентификатора, то при неуточ-

ненном обращении к этому идентификатору будет выбран тот вариант,

который был описан в последнем модуле в операторе uses. Однако,

вы можете выбрать любой вариант этого идентификатора, указав

уточненный идентификатор.

Идентификаторы встроенных констант, типов, переменных, про-

цедур и функций Borland Pascal действуют, как если бы они были

описаны в блоке, охватывающем все используемые модули и программу

в целом. В действительности эти стандартные объекты описаны в мо-

дуле System, который используется любой программой или модулем

прежде любого модуля, указанного в операторе uses. Это означает,

что любой модуль или программа могут переопределить стандартные

идентификаторы, а обращение к ним может быть выполнено с помощью

уточненного (составного) идентификатора, например, System.Integer


Глава 9. Процедуры и функции

Процедуры и функции позволяют включать в основной программ-

ный блок дополнительные блоки. Каждое описание процедуры или

функции содержит заголовок, за которым следует программный блок.

Процедура активизируется с помощью оператора процедуры. Функция

активизируется при вычислении выражения, содержащего вызов функ-

ции, и возвращаемое функцией значение подставляется в это выраже-

ние.

Примечание: Определение блока вы можете найти в Главе

8 "Блоки, локальность и область действия".

В данной главе обсуждаются различные способы описания проце-

дуры или функции и их параметры.

Описания процедур

Описание процедуры позволяет связать идентификатор с проце-

дурным блоком. Процедуру можно затем активизировать с помощью

оператора процедуры.

ЪДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДДДї ЪДДДї

описание ДДД>ізаголовокГДД>і ; ГДД>і тело ГДД>і ; ГДД>

процедуры іпроцедурыі АДДДЩ іподпрограммыі АДДДЩ

АДДДДДДДДДЩ АДДДДДДДДДДДДЩ

ЪДДДДДДДДДї ЪДДДДДДДДДДДДДї

заголовок ДД>іprocedureГДВ>іидентификаторГДДї

процедуры АДДДДДДДДДЩ і АДДДДДДДДДДДДДЩ ^ГДДДДДДДДДДДДДДДДДД>

і ЪДДДДДДДДДДДДДї іі ЪДДДДДДДДДДї ^

і і уточненный і іі і список і і

А>іидентификаторГДЩАД>іформальныхГДЩ

і метода і іпараметрові

АДДДДДДДДДДДДДЩ АДДДДДДДДДДЩ

ЪДДДДДДї

блок ДДДВДВДД>імодульГДДДДДДДД>

подпрограммыі ЪДДДДДДДДДї ЪДДДї ^ і АДДДДДДЩ ^

ГДД>і near ГДДДДД>і ; ГДДДЩ і ЪДДДДДДДї і

і АДДДДДДДДДЩ ^ АДДДЩ іДД>іforwardГДДДґ

і ЪДДДДДДДДДї і і АДДДДДДДЩ і

ГДД>і far ГДДґ і ЪДДДДДДДДДї і

і АДДДДДДДДДЩ і іДД>ідирективаГДґ

і ЪДДДДДДДДДї і і і externalі і

ГДД>і export ГДДґ і АДДДДДДДДДЩ і

і АДДДДДДДДДЩ і і ЪДДДДДДДДї і

і ЪДДДДДДДДДї і АДД>іблок asmГДДґ

ГДД>іinterruptГДДЩ АДДДДДДДДЩ і

і АДДДДДДДДДЩ ЪДДДДДДДДДї і

АДДДД>ідирективаГДЩ

і inline і

АДДДДДДДДДЩ

Заголовки процедур именуют идентификаторы процедур и задают

формальные параметры (если они имеются).

Примечание: Синтаксис списка формальных параметров по-

казан далее в этой главе в разделе "Параметры".

Процедура активизируется с помощью оператора процедуры, в

котором содержатся имя процедуры и необходимые параметры. Опера-

торы, которые должны выполняться при запуске процедуры, содержат-

ся в операторной части модуля процедуры. Если в содержащемся в

процедуре операторе внутри модуля процедуры используется иденти-

фикатор процедуры, то процедура будет выполняться рекурсивно (бу-

дет при выполнении обращаться сама к себе).

Приведем пример описания процедуры:

procedure NumString(N: integer; var S: string);

var

V: integer;

begin

V := Abs(N);

S := '';

repeat

S := Chr(N mod 10 + Ord('0')) + S;

N := N div 10;

until N = 0;

if N < 0 then S := '-' + S;

end;

Описания near и far

Borland Pascal поддерживает две модели вызова процедур -

ближнюю (near) и дальнюю (far). С точки зрения объема программы и

скорости выполнения ближняя модель вызова более эффективна, но с

ней связаны ограничения: процедуры типа near могут вызываться

только в том модуле, где они описаны. Процедуры же с дальним ти-

пом вызова можно вызывать из любого модуля, но они несколько ме-

нее эффективны.

Примечание: О вызовах ближнего и дальнего типа расска-

зывается в Главе 22 "Вопросы управления".

На основе описания процедуры компилятор будет автоматически

выбирать правильную модель вызова. Для процедур, описанных в ин-

терфейсной части модуля (interface), используется дальняя модель

вызова - их можно вызывать из других модулей. Процедуры, описан-

ные в секции реализации модуля (implementation), имеют ближний

тип вызова. Вызываться они могут только из программ данного моду-

ля.

Для некоторых специальных целей может потребоваться исполь-

зовать модель с дальним типом вызова. Например, в оверлейных за-

дачах обычно требуется, чтобы все процедуры и функции имели даль-

ний тип вызова. Аналогично, если процедура или функция

присваивается процедурной переменной, то она также должна исполь-

зовать дальний тип вызова. Чтобы переопределить автоматический

выбор модели вызова компилятором, можно использовать директиву

компилятора {$F+}. Процедуры и функции, компилируемые в состоянии

{$F+}, всегда будут иметь дальний тип вызова (far), а в состоянии

{$F-} компилятор автоматически выбирает корректную модель. По

умолчанию используется директива {$F-}.

Чтобы задать конкретную модель вызова, в описании процедуры

перед ее блоком можно указать директиву near или far. При наличии

такой директивы она переопределяет директиву $F компилятора и ав-

томатический выбор модели вызова.

Описания export

Описание export делает процедуру или функцию экспортируемой,

вынуждая компилятор использовать для нее дальний тип вызова и ге-

нерировать специальный код входы и выхода из процедуры.

Процедуры и функции должны быть экспортируемыми в следующих

случаях:

* Процедуры и функции экспортируются DLL (динамически компо-

нуемой библиотекой).

* Процедуры и функции системного вызова в программе Windows.

О том, как экспортировать процедуры и функции в DLL, расска-

зывается в Главе 11 "Динамически компонуемые библиотеки". Хотя

процедура и функция компилируется с директивой export, фактичес-

кий экспорт процедуры или функции не происходит, пока подпрограм-

ма не перечисляется в операторе exports библиотеки.

Процедуры и функции системного вызова - это те процедуры и

функции вашей прикладной программы, которые вызываются самой

Windows, а не вашей прикладной программой. Подпрограммы системно-

го вызова должны компилироваться с директивой export, но в опера-

торе exports их перечислять не нужно. Приведем некоторые примеры

процедур и функций системного вызова:

* процедуры Windows;

* диалоговые процедуры;

* процедуры системного вызова для перечисления;

* процедуры уведомления об обращении к памяти;

* специализированные процедуры Windows (фильтры).

Borland Pascal автоматически генерирует для процедур и функ-

ций, экспортируемых программой Windows, эффективные системные вы-

зовы. Эффективные вызовы ослабляют необходимость использования

при создании подпрограмм системного вызова подпрограмм API

Windows MakeProcInstance и FreeProcInstance.

Примечание: См. раздел "Код входа и выхода" в Главе 22.

Описания interrupt

В описании процедуры перед блоком операторов может указыва-

ется директива interrupt. Процедура в этом случае рассматривает-

ся, как процедура прерывания. Отметим пока, что процедура

interrupt не может вызываться из операторов процедуры, и что каж-

дая процедура interrupt должна определять список параметров, нап-

ример, следующим образом:

procedure MyInt(Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS,

ES, BP: Word);

interrupt;

Примечание: Не используйте директиву interrupt при

разработке программ для Windows - это приведет к сбою.

Список параметров не обязательно должен совпадать с указан-

ным синтаксисом - он может быть короче и использовать другие име-

на, но регистры должны передаваться в указанном порядке.

Описание forward

Описание процедуры, содержащее вместо блока операторов ди-

рективу forward, называется опережающим описанием. В каком-либо

месте после этого описания с помощью определяющего описания про-

цедура должна определяться. Определяющее описание - это описание,

в котором используется тот же идентификатор процедуры, но опущен

список формальных параметров и в которое включен блок операторов.

Описание forward и определяющее описание должны присутствовать в

одной и той же части описания процедуры и функции. Между ними мо-

гут описываться другие процедуры и функции, которые могут обра-

щаться к процедуре с опережающим описанием. Таким образом возмож-

на взаимная рекурсия.

Опережающее описание и определяющее описание представляют

собой полное описание процедуры. Процедура считается описанной с

помощью опережающего описания.

Примечание: В интерфейсной части модуля описания

forward не допускаются.

Приведем следующий пример опережающего описания:

procedure Walter(m,n : integer); forward;

procedure Clara(x,y : real);

begin

.

.

.

end;

procedure Walter;

begin

.

.

Clara(8.3, 2.4);

.

.

end;

Определяющее описание процедуры может быть внешним описани-

ем. Однако, оно не может быть внутренним описанием или другим

опережающим описанием. Определяющее описание также не может со-

держать директиву interrupt, описания assembler, near, far,

export, inline или другое описание forward.

Описания external

Описания external позволяют связывать отдельно скомпилиро-

ванные процедуры и функции, написанные на языке ассемблера. Опи-

сания external позволяют также импортировать процедуры и функции

из DLL.

Примечание: Более детальное описания компоновки с

программой на языке ассемблера содержится в Главе 25.

директива external

і ЪДДДДДДДДДДї

АД>і external ГВДДДДДДДДДДДДДДДДДД>

АДДДДДДДДДДЩі ЪДДДДДДДДДДДДДДДДДДДї ^

А>істроковая константаГВДДДДДДДДДДДДДДДДДДДДДДДЩ

АДДДДДДДДДДДДДДДДДДДЩі ЪДДДДДДї ЪДДДДДДДДДї^

Г>і name ГД>істроковаяГґ

і АДДДДДДЩ іконстантаіі

і АДДДДДДДДДЩі

і ЪДДДДДДДї ЪДДДДДДДДДїі

А>і index Г>і целая ГЩ

АДДДДДДДЩ іконстантаі

АДДДДДДДДДЩ

Директива external, состоящая только из зарезервированного

слова external, используется в сочетании с директивами {$L

имя_файла} для компоновки с процедурами и функциями, реализован-

ными в файлах .OBJ.

Приведем следующие примеры описаний внешних процедур:

procedure MoveWord(var source,dest; count: longint);

external;

procedure MoveLong(var source,dest; count: longint);

external;

procedure FillWord(var dest,data: integer; count: longint);

external;

procedure FillLong(var dest,data: integer; count: longint);

external;

{$L BLOCK.OBJ}

Внешними процедурами следует пользоваться, когда вы хотите

объединить большое количество объектных модулей. Если ваши прог-

раммы имеют небольшой объем, лучше вместо этого использовать

внутренние процедуры.

Директивы external, специфицирующие имя динамически компону-

емой библиотеки (и, возможно, импортируемое имя или порядковый

номер импорта), используются для импорта процедур и функций из

динамически компонуемых библиотек. Например, следующая директива

external импортирует из DLL с именем KERNEL (ядро Windows) функ-

цию с именем GlobalAlloc:

function GlobalAlloc(Flags: Word; Bytes: Longint): THandle;

far; external 'KERNEL' index 15;

В импортируемой процедуре или функции директива external за-

нимает место описания и операторной части. В импортируемых проце-

дурах или функциях должен использоваться дальний тип вызова, за-

даваемый с помощью директивы far в описании процедуры или дирек-

тивы компилятора {$F+}. В остальном импортируемые процедуры и

функции аналогичны обычным процедурам и функциям.

Примечание: Подробнее об импорте функций из DLL расс-

казывается в Главе 11.

Описания assembler

Описания assembler позволяют вам написать всю процедуру или

функцию на ассемблере.

Примечание: Более подробно о процедурах и функциях на

Ассемблере рассказывается в Главе 24 "Встроенный ассемблер".

ЪДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДї ЪДДДДДДДДДДДДї

блок asm Д>іassemblerГДД>і ; ГДД>і раздел ГДД>іasm операторГД>

АДДДДДДДДДЩ АДДДЩ і описания і АДДДДДДДДДДДДЩ

АДДДДДДДДДДЩ

Описания inline

Директивы inline позволяют записывать вместо блока операто-

ров инструкции в машинном коде. При вызове обычной процедуры ком-

пилятор создает код, в котором параметры процедуры помещаются в

стек, а затем для вызова процедуры генерируется инструкция CАLL.

ЪДДДДДДДДДДДДДДДДДї

директива inline ДД>і оператор inline ГДДДДДДДДДД>

АДДДДДДДДДДДДДДДДДЩ

Когда вы вызываете подставляемую процедуру (inline), компи-

лятор генерирует код с помощью директивы inline, а не с помощью

инструкции CALL. Таким образом, поставляемая процедура "расширя-

ется" при каждом обращении к ней, аналогично макроинструкции на

языке ассемблера. Приведем два небольших примера подставляемых

процедур:

procedure DisableInterrupts: inline($FA); { CLI }

procedure EnableInterrupts; inline($FB); { STI }

Примечание: Синтаксические диаграммы оператора inline

описаны подробно в Главе 25.

Описания функций

Описание функции определяет часть программы, в которой вы-

числяются и возвращается значение.

ЪДДДДДДДДДї ЪДДДї ЪДДДДДДДї ЪДДДї

описание ДДД>ізаголовокГДД>і ; ГДД>і тело ГДД>і ; ГДД>

функции і функции і АДДДЩ іфункцииі АДДДЩ

АДДДДДДДДДЩ АДДДДДДДЩ

ЪДДДДДДДДї ЪДДДДДДДДДДДДДї

заголовок ДДД>іfunctionГВ>іидентификаторГДДВДДДДДДДДДДДДДДДДДДДї

функции АДДДДДДДДЩі АДДДДДДДДДДДДДЩ^ і ЪДДДДДДДДДДї ^ і

і ЪДДДДДДДДДДДДДїі і ісписок і і і

А>і уточненный ГЩ АД>іформальныхГДДЩ і

іидентификаторі іпараметрові і

і метода і АДДДДДДДДДДЩ і

АДДДДДДДДДДДДДЩЪДДДДДДДДДДДДДДДДДДДДДЩ

і ЪДДДї ЪДДДДДДДДї

АД>і : ГДД>ітип ре- ГДД>

АДДДЩ ізультатаі

АДДДДДДДДЩ

ЪДДДДДДДДДДДДДї

тип результата ДДВДД>іидентификаторГДДДДДДДДД>

і і типа і ^

і АДДДДДДДДДДДДДЩ і

і ЪДДДДДДї і

АДДДДД>іstringГДДДДДДДДДЩ

АДДДДДДЩ

Примечание: Функция не может возвращать процедурный

тип или структурный тип.

В заголовке функции определяется идентификатор функции, фор-

мальные параметры (если они имеются) и тип результата функции.

Функция активизируется при вызове функции. При вызове функ-

ции указывается идентификатор функции и какие-либо параметры, не-

обходимые для вычисления функции. Вызов функции может включаться

в выражения в качестве операнда. Когда выражение вычисляется,

функция выполняется и значением операнда становится значение,

возвращаемое функцией.

В операторной части блока функции задаются операторы, кото-

рые должны выполняться при активизации функции. В модуле должен

содержаться по крайней мере один оператор присваивания, в котором

идентификатору функции присваивается значение. Результатом функ-

ции является последнее присвоенное значение. Если такой оператор

присваивания отсутствует или он не был выполнен, то значение,

возвращаемое функцией, не определено.

Если идентификатор функции используется при вызове функции

внутри модуля-функции, то функция выполняется рекурсивно.

Приведем далее примеры описаний функции:

function Max(a: Vector; n: integer): extended;

var

x: extended;

i: integer;

begin

x := a(1);

for i := 2 to n do if x < a[i] then x := a[i];

Max := x;

end;

function Power(x: extended; y: integer): extended;

var

z: extended;

i: integer;

begin

z := 1.0; i := y;

while i > 0 do

begin

if Odd(i) then z := z*x;

x := Sqr(x);

end;

Power := z;

end;

Аналогично процедурам функции могут описываться, как с ближ-

ним типом вызова (near), с дальним типом вызова (far), опережаю-

щие (forward), внешние (external), ассемблерные (assembler) или

подставляемые (inline). Однако функции прерываний (interrupt) не

допускаются.

Описания методов

Описание метода внутри объектного типа соответствует опере-

жающему описанию (forward) этого метода. Таким образом, метод

должен быть реализован где-нибудь после описания объектного типа

и внутри той же самой области действия метода путем определяющего

описания.

Для процедурных и функциональных методов определяющее описа-

ния имеет форму обычного описания процедуры или функции, за тем

исключением, что в этом случае идентификатор процедуры или функ-

ции рассматривается как идентификатор метода.

Для методов конструкторов и деструкторов определяющее описа-

ний принимает форму описания процедурного метода, за тем исключе-

нием, что зарезервированное слово procedure заменяется на заре-

зервированное слово constructor или destructor. Определяющее опи-

сание метода может повторять (но не обязательно) список формаль-

ных параметров заголовка метода в объектном типе. В этом случае

заголовок метода должен в точности повторять заголовок в объект-

ном типе в порядке, типах и именах параметров и в типе возвращае-

мого функцией результата, если метод является функцией.

В определяющем описании метода всегда присутствует неявный

параметр с идентификатором Self, соответствующий формальному па-

раметру-переменной, обладающему объектным типом. Внутри блока ме-

тода Self представляет экземпляр, компонент метода которого был

указан для активизации метода. Таким образом, любые изменения

значений полей Self отражаются на экземпляре.

Область действия идентификатора компонента объектного типа

распространяется на блоки процедур, функций, конструкторов и

деструктора, которые реализуют методы данного объектного типа.

Эффект получается тот же, как если бы в начало блока метода был

вставлен оператор with в следующей форме:

with Self do

begin

...

end;

Исходя из этих соображений, написание идентификаторов компо-

нентов, формальных параметров метода, Self и любого идентификато-

ра, введенного в исполняемую часть метода, должно быть уникаль-

ным.

Ниже приводятся несколько примеров реализаций методов:

procedure Rect.Intersect(var R: Rect);

begin

if A.X < R.A.X then A.X := R.A.X;

if A.X < R.A.Y then A.Y := R.A.Y;

if B.X > R.B.X then B.X := R.B.X;

if B.Y < R.B.Y then B.Y := R.B.Y;

if (A.X >= B.X) or (A.Y >= B.Y) then Init (0, 0, 0, 0);

end;

procedure Field.Display;

begin

GotoXY(X, Y);

Write(Name^, ' ', GetStr);

end;

function NumField.PutStr(S: string): boolean;

var

E: integer;

begin

Val(S, Value, E);

PutStr := (E = 0) and (Value >= Min) and (Value <= Max);

end;

Конструкторы и деструкторы

Конструкторы и деструкторы являются специализированными фор-

мами методов. Используемые в связи с расширенным синтаксисом

стандартных процедур New и Dispose, конструкторы и деструкторы

обладают способностью размещения и удаления динамических объек-

тов. Кроме того, конструкторы имеют возможность выполнить требуе-

мую инициализацию объектов, содержащих виртуальные методы. Как и

все другие методы, конструкторы и деструкторы могут наследовать-

ся, а объекты могут содержать любое число конструкторов и дест-

рукторов.

Конструкторы используются для инициализации вновь созданных

объектов. Обычно инициализация основывается на значениях, переда-

ваемых конструктору в качестве параметров. Конструктор не может

быть виртуальным, так как механизм диспетчеризации виртуального

метода зависит от конструктора, который первым совершил инициали-

зацию объекта.

ЪДДДДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДДДї ЪДДДї

описание ДДД>і заголовок ГДД>і ; ГДД>і блок ГДД>і ; ГД>

конструктора іконструктораі АДДДЩ іподпрограммыі АДДДЩ

АДДДДДДДДДДДДЩ АДДДДДДДДДДДДЩ

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДДДї

заголовок ДДДД>іconstructorГВ>іидентификаторГДВДДДДДДДДДДДДДДДД>

конструктора АДДДДДДДДДДДЩі АДДДДДДДДДДДДДЩ^і ЪДДДДДДДДДДї ^

і ЪДДДДДДДДДДДДДїіі і список і і

А>і уточненный ГЩАД>іформальныхГДЩ

іидентификаторі іпараметрові

і метода і АДДДДДДДДДДЩ

АДДДДДДДДДДДДДЩ

Приведем несколько примеров конструкторов:

constructor Field.Copy(var F: Field);

begin

Self := F;

end;

constructor Field.Init(FX, FY, FLen: integer; FName: string);

begin

X := FX;

Y := FY;

GetMem(Name, Length (FName) + 1);

Name^ := FName;

end;

constructor TStrField.Init(FX, FY, FLen: integer; FName:

string);

begin

inherited Init(FX, FY, FLen, FName);

Field.Init(FX, FY, FLen, FName);

GetMem(Value, Len);

Value^ := '';

end;

Главным действием конструктора порожденного (дочернего) ти-

па, такого как указанный выше TStrField.Init, почти всегда явля-

ется вызов соответствующего конструктора его непосредственного

родителя для инициализации наследуемых полей объекта. После вы-

полнения этой процедуры, конструктор инициализирует поля объекта,

которые принадлежат только порожденному типу.

Деструкторы ("сборщики мусора") являются противоположностями

конструкторов и используются для очистки объектов после их ис-

пользования. Обычно очистка состоит из удаления всех полей-указа-

телей в объекте.

Примечание: Деструктор может быть виртуальным и часто

является таковым. Деструктор редко имеет параметры.

Приведем несколько примеров деструкторов:

destructor Field.Done;

begin

FreeMem(Name, Length (Name^) + 1);

end;

destructor StrField.Done;

begin

FreeMem(Value, Len);

Field.Done;

end;

Деструктор дочернего типа, такой как указанный выше

TStrField.Done, обычно сначала удаляет введенные в порожденном

типе поля указателей, а затем в качестве последнего действия вы-

зывает соответствующий сборщик деструктор непосредственного роди-

теля для удаления унаследованных полей-указателей объекта.

Восстановление ошибок конструктора

Borland Pascal позволяет вам с помощью переменной HeapError

модуля System (см. Главу 21) установить функцию обработки ошибки

динамически распределяемой области. Эта функциональная возмож-

ность влияет на способ работы конструкторов объектного типа.

По умолчанию, когда для динамического экземпляра объекта не

хватает памяти, вызов конструктора, использующий расширенный син-

таксис стандартной процедуры New, генерирует ошибку этапа выпол-

нения 203. Если вы установили функцию обработки ошибки динамичес-

ки распределяемой области, которая вместо стандартного результата

функции 0 возвращает 1, когда выполнить запрос невозможно, вызов

конструктора через New возвращает nil (вместо прерывания програм-

мы).

Код, выполняющий распределение памяти и инициализацию поля

таблицы виртуальных методов (VMT) динамического экземпляра объек-

та является частью последовательности вызова конструктора. Когда

управление передается на оператор begin операторной части конс-

труктора, память для экземпляра уже выделена, и он инициализиро-

ван. Если выделения памяти завершается неудачно, и если функция

обработки ошибки динамически распределяемой области памяти возв-

ращает 1, конструктор пропускает выполнение операторной части и

возвращает значение nil. Таким образом, указатель, заданный в вы-

полняемом конструктором вызове New, устанавливается в nil.

Когда управление передается на оператор begin операторной

части конструктора, для экземпляра объектного типа обеспечивается

успешное выполнение памяти и инициализация. Сам конструктор может

попытаться распределить динамические переменные для инициализации

полей-указателей в экземпляре, однако, такое распределение может

завершиться неудачно. Если это происходит, правильно построенный

конструктор должен отменять все успешные распределения и, нако-

нец, освобождать выделенную для экземпляра объекта память, так

что результатом может стать указатель nil. Для выполнения такой

"отмены" Borland Pascal реализует стандартную процедуру Fail, ко-

торая не требует параметров и может вызываться только из конс-

труктора. Вызов Fail приводит к тому, что конструктор будет осво-

бождать выделенную для динамического экземпляра память, которая

была выделена перед входом в конструктор, и для указания неудачи

возвращает указатель nil.

Когда память для динамических экземпляров выделяется с по-

мощью расширенного синтаксиса New, результирующее значение nil

в заданном указателе-переменной указывает, что операция заверши-

лась неудачно. К сожалению, не существует такого указателя-пере-

менной, которую можно проверить после построения статического эк-

земпляра или при вызове наследуемого конструктора. Вместо этого

Borland Pascal позволяет использовать конструктор в виде булевс-

кой функции в выражении: возвращаемое значение True указывает на

успешное выполнение, а значение False - не неуспешное выполнение

из-за вызова в конструкторе Fail.

На диске вы можете найти две программы - NORECVER.PAS и

RECOVER.PAS. Оба программы реализуют два простых объектных типа,

содержащих указатели. Первая программа не содержит восстановления

ошибок конструктора.

Программа RECOVER.PAS демонстрирует, как можно переписать

исходный код для реализации восстановления ошибки. Заметим, что

для отмены успешного выделения памяти перед вызовом Fail для ито-

гового неуспешного выполнения используются соответствующие дест-

рукторы в Base.Init и Derived.Init. Заметим также, что в

Derived.Init вызов Base.Init содержится внутри выражения, так что

можно проверить успешность выполнения наследуемого конструктора.

Параметры

В описании процедуры или функции задается список формальных

параметров. Каждый параметр, описанный в списке формальных пара-

метров, является локальным по отношению к описываемой процедуре

или функции и в модуле, связанным с данной процедурой или функци-

ей на него можно ссылаться по его идентификатору.

ЪДДДї ЪДДДДДДДДДДї ЪДДДї

список формальных ДДД>і ( ГДДДДД>і описание ГДДВДД>і ) ГДД>

параметров АДДДЩ ^ іпараметра і і АДДДЩ

і АДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДґ ; і<ДДДДДЩ

АДДДЩ

ЪДДДДДДДДДДДДДї

описание ДДВДДДДДДДДДДДД>ісписок иден- ГВДДДДДДДДДДДДДДДДДДДДД>

параметра і ЪДДДї ^ ітификаторов іі ^

ГД>іvarГДДДДґ АДДДДДДДДДДДДДЩі ЪДДДї ЪДДДДДДДї і

і АДДДЩ і А>і : ГД>ітип па-ГДЩ

і ЪДДДДДї і АДДДЩ іраметраі

АД>іconstГДДЩ АДДДДДДДЩ

АДДДДДЩ

Существует три типа параметров: значение, переменная и нети-

пизированная переменная. Они характеризуются следующим:

1. Группа параметров без предшествующего ключевого слова

является списком параметров-значений.

2. Группа параметров, перед которыми следует ключевое слово

const и за которыми следует тип, является списком пара-

метров-констант.

3. Группа параметров, перед которыми стоит ключевое слово

var и за которыми следует тип, является списком нетипи-

зированных параметров-переменных.

4. Группа параметров, перед которыми стоит ключевое слово

var или const за которыми не следует тип, является спис-

ком нетипизированных параметров-переменных.

Параметры строкового типа и массивы могут быть открытыми па-

раметрами. Параметры-переменные, описанные с помощью идентифика-

тора OpenString или с использованием ключевого слова string в

состоянии {$P+}, являются открытыми строковыми параметрами. Зна-

чение, константа или параметр-переменная, описанные с помощью

синтаксиса array of T, являются открытым параметром-массивом.

Примечание: Подробнее об открытых параметрах рассказы-

вается ниже.

Параметры-значения

Формальный параметр-значение обрабатывается, как локальная

по отношению к процедуре или функции переменная, за исключением

того, что он получает свое начальное значение из соответствующего

фактического параметра при активизации процедуры или функции. Из-

менения, которые претерпевает формальный параметр-значение, не

влияют на значение фактического параметра.

Соответствующее фактическое значение параметра-значения

должно быть выражением и его значение не должно иметь файловый

тип или какой-либо структурный тип, содержащий в себе файловый

тип.

Фактический параметр должен иметь тип, совместимый по прис-

ваиванию с типом формального параметра-значения. Если параметр

имеет строковый тип, то формальный параметр будет иметь атрибут

размера, равный 255.

Параметры-константы

Формальные параметры-константы работают аналогично локальной

переменной, доступной только по чтению, которая получает свое

значение при активизации процедуры или функции от соответствующе-

го фактического параметра. Присваивания формальному парамет-

ру-константе не допускаются. Формальный параметр-константа также

не может передаваться в качестве фактического параметра другой

процедуре или функции.

Параметр-константа, соответствующий фактическому параметру в

операторе процедуры или функции, должен подчиняться тем же прави-

лам, что и фактическое значение параметра.

В тех случаях, когда формальный параметр не изменяет при вы-

полнении процедуры или функции своего значения, вместо парамет-

ра-значения следует использовать параметр-константу. Парамет-

ры-константы позволяют при реализации процедуры или функции защи-

титься от случайных присваиваний формальному параметру. Кроме то-

го, для параметров структурного и строкового типа компилятор при

использовании вместо параметров-значений параметров-констант мо-

жет генерировать более эффективный код.

Параметры-переменные

Параметр-переменная используется, когда значение должно пе-

редаваться из процедуры или функции вызывающей программе. Соот-

ветствующий фактический параметр в операторе вызова процедуры или

функции должен быть ссылкой на переменную. При активизации проце-

дуры или функции формальный параметр-переменная замещается факти-

ческой переменной, любые изменения в значении формального пара-

метра-переменной отражаются на фактическом параметре.

Внутри процедуры или функции любая ссылка на формальный па-

раметр-переменную приводит к доступу к самому фактическому пара-

метру. Тип фактического параметра должен совпадать с типом фор-

мального параметра-переменной (вы можете обойти это ограничение с

помощью нетипизированного параметра-переменной).

Примечание: Файловый тип может передаваться только,

как параметр-переменная.

Директива компилятора $P управляет смыслом параметра-пере-

менной, описываемого с ключевым словом string. В состоянии по

умолчанию ({$P-}) string соответствует строковому типу с атрибу-

том размера 255. В состоянии {$P+} string указывает, что параметр

является открытым строковым параметром (см. ниже).

При ссылке на фактический параметр-переменную, связанную с

индексированием массива или получением указателя на объект, эти

действия выполняются перед активизацией процедуры или функции.

Правила совместимости по присваиванию для объектного типа

применяются также к параметрам-переменным объектного типа. Для

формального параметра типа T1 фактический параметр должен быть

типа T2, если T2 находится в домене T1. Например, с учетом опи-

саний Главы 4, методу TField.Copy может передаваться экземпляр

TField, TStrField, TNumField, TZipField или любой другой экземп-

ляр потомка TField.

Нетипизированные параметры

Когда формальный параметр является нетипизированным парамет-

ром-переменной, то соответствующий фактический параметр может

представлять собой любую ссылку на переменную или константу, не-

зависимо от ее типа. Нетипизированный параметр, описанный с клю-

чевым словом var, может модифицироваться, а нетипизированный па-

раметр, описанный с ключевым словом const, доступен только по

чтению.

В процедуре или функции у нетипизированного параметра-пере-

менной тип отсутствует, то есть он несовместим с переменными всех

типов, пока ему не будет присвоен определенный тип с помощью

присваивания типа переменной.

Приведем пример нетипизированных параметров-переменных:

function Equal(var source,dest; size: word): boolean;

type

Bytes = array[0..MaxInt] of byte;

var

N: integer;

begin

N := 0;

while (N<size) and (Bytes(dest)[N] <> Bytes(source)[N]

do Inc(N);

Equal := N = size;

end;

Эта функция может использоваться для сравнения любых двух

переменных любого размера. Например, с помощью описаний:

type

Vector = array[1..10] of integer;

Point = record

x,y: integer;

end;

var

Vec1, Vec2: Vector;

N: integer;

P: Point;

и вызовов функций:

Equal(Vec1,Vec2,SizeOf(Vector))

Equal(Vec1,Vec2,SizeOf(integer)*N)

Equal(Vec[1],Vec1[6],SizeOf(integer)*5)

Equal(Vec1[1],P,4)

сравнивается Vес1 с Vес2, сравниваются первые N элементов Vес1 с

первыми N элементами Vес2, сравниваются первые 5 элементов Vес1 с

последними пятью элементами Vес2 и сравниваются Vес1[1] с Р.х и

Vес2[2] с P.Y.

Хотя нетипизированные параметры дают вам большую гибкость,

их использование сопряжено с некоторым риском. Компилятор не мо-

жет проверить допустимость операций с нетипизированными перемен-

ными.

Открытые параметры

Открытые параметры позволяют передавать одной и той же про-

цедуре или функции строки и массивы различных размеров.

Открытые строковые параметры

Открытые строковые параметры могут описываться двумя спосо-

бами:

- с помощью идентификатора OpenString;

- с помощью ключевого слова string в состоянии {$P+}.

Идентификатор OpenString описывается в модуле System. Он

обозначает специальный строковый тип, который может использовать-

ся только в описании строковых параметров. В целях обратной сов-

местимости OpenString не является зарезервированным словом и мо-

жет, таким образом, быть переопределен как идентификатор, задан-

ный пользователем.

Когда обратная совместимость значения не имеет, для измене-

ния смысла ключевого слова string можно использовать директиву

компилятора {$P+}. В состоянии {$P+} переменная, описанная с клю-

чевым словом string, является открытым строковым параметром.

Для открытого строкового параметра фактический параметр мо-

жет быть переменной любого строкового типа. В процедуре или функ-

ции атрибут размера (максимальная длина) формального параметра

будет тем же, что у фактического параметра.

Открытые строковые параметры ведут себя также как парамет-

ры-переменные строкового типа, только их нельзя передавать как

обычные переменные другим процедурам или функциям. Однако, их

можно снова передать как открытые строковые параметры.

В следующем примере параметр S процедуры AssignStr - это

открытый строковый параметр:

procedure AssignStr(var S: OpenString;

begin

S := '0123456789ABCDEF';

end;

Так как S - это открытый строковый параметр, AssignStr можно

передавать переменные любого строкового типа:

var

S1: string[10];

S1: string[20];

begin

AssignStr(S1); { S1 := '0123456789' }

AssignStr(S2); { S2 := '0123456789ABCDEF' }

end;

В AssingStr максимальная длина параметра S та же самая, что

у фактического параметра. Таким образом, в первом вызове

AssingStr при присваивании параметра S строка усекается, так как

максимальная длина S1 равна 10.

При применении к открытому строковому параметру стандартная

функция Low возвращает 0, стандартная функция High возвращает

описанную максимальную длину фактического параметра, а функция

SizeOf возвращает размер фактического параметра.

В следующем примере процедура FillString заполняет строку

заданным символом до ее максимальной длины. Обратите внимание на

использование функции High для получения максимальной длины отк-

рытого строкового параметра.

procedure FillStr(var S: OpenString; Ch: Char);

begin

S[0] := Chr(High(S)); { задает длину строки }

FillChar(S[1], High(S), Ch); { устанавливает число

символов }

emd;

Значения и параметры-константы, описанные с использованием

идентификатора OpenString или ключевого слова string в состоянии

{$P+}, не являются открытыми строковыми параметрами. Они ведут

себя также, как если бы были описаны с максимальной длиной стро-

кового типа 255, а функция Hingh для таких параметров всегда

возвращает 255.

Открытые параметры-массивы

Формальный параметр, описанный с помощью синтаксиса:

array of T

является открытым параметром-массивом. T должно быть идентифика-

тором типа, а фактический параметр должен быть переменной типа T,

или массивом, типом элементов которого является T. В процедуре

или функции формальный параметр ведет себя так, как если бы он

был описан следующим образом:

arra[0..N - 1] of T

где N - число элементов в фактическом параметре. По существу,

диапазон индекса фактического параметра отображается в диапазон

целых чисел от 0 до N - 1. Если фактический параметр - это прос-

тая переменная типа T, то он интерпретируется как массив с одним

элементом типа T.

К открытому формальному параметру-массиву можно обращаться

только по элементам. Присваивания всему открытому массиву не до-

пускаются, и открытый массив может передаваться другим процедурам

или функциям только как открытый параметр-массив или нетипизиро-

ванный параметр-переменная.

Открытый параметр-массив может быть параметром-значением,

параметром-константой и параметром-переменной и имеет тот же

смысл, что и обычные параметры-значения, параметры-константы и

параметры-переменные. В частности, присваивания элементам фор-

мального открытого массива-константы не допускаются, а присваива-

ния элементам формального открытого массива, являющегося парамет-

ром-значением, не влияют на фактический параметр.

Для открытых массивов-значений компилятор создает в кадре

стека процедуры или функции локальную копию фактического парамет-

ра. Таким образом, при передаче в качестве открытых парамет-

ров-значений больших массивов следует учитывать возможное пере-

полнение стека.

При применении к открытому параметру-массиву стандартная

функция Low возвращает 0, стандартная функция High возвращает ин-

декс последнего элемента в фактическом параметре-массиве, а функ-

ция SizeOf возвращает размер фактического параметра-массива.

Процедура Clear в следующем примере присваивает каждому эле-

менту массива вещественных значений ноль, а функция Sum вычисляет

сумму всех элементов массива вещественных чисел. Поскольку в обо-

их случаях параметр A является открытым параметром-массивом,

подпрограммы могут работать с любым массивом элементов типа Real:

procedure Clear(var A: array of Real);

var

I: Word;

begin

for I := 0 to High(A) do A[I] := 0;

end;

function Sum(const A: array of Real): Real;

var

I: Word;

S: Real;

begin

S := 0;

for I := 0 to High(A) do S := S + A[I];

Sum := S;

end;

Когда типом элементов открытого параметра-массива является

Char, фактический параметр может быть строковой константой. Нап-

ример, с учетом предыдущего описания:

procedure PringStr(const S: array of Char);

var

I: Integer;

begin

for I := 0 to High(S) do

if S[I] <> #0 then Write(S[I]) else Break;

end;

и допустимы следующие операторы процедур:

PrintStr('Hello word');

PrintStr('A');

При передаче в качестве открытого параметра-массива пустая

строка преобразуется в строку с одним элементом, содержащим сим-

вол NULL, поэтому оператор PrintStr('') идентичен оператору

PrintStr('#0').

Динамические переменные объектного типа

Стандартные процедуры New и Dispose допускают в качестве

второго параметра вызов конструктора или деструктора для выделе-

ния для памяти переменной объектного типа или ее освобождения.

При этом используется следующий синтаксис:

New(P, Construct)

и

Dispose(P, Destruct)

где P - это указатель на переменную, ссылающийся на объектный

тип, а Construct и Destruct - это вызовы конструкторов и деструк-

торов объектного типа. Для New эффект расширенного синтаксиса тот

же, что и от выполнения операторов:

New(P);

P^.Construct;

а для Dispose это эквивалентно операторам:

P^.Dispose;

Dispose(P);

Без расширенного синтаксиса вам пришлось бы часто вслед за

вызовом конструктора вызывать New, или после вызова деструктора

вызывать Dispose. Расширенный синтаксис улучшает читаемость ис-

ходного кода и генерирует более короткий и эффективный код.

Приведенный пример иллюстрирует использование расширенного

синтаксиса New и Dispose:

var

SP: PStrField

ZP: PZipField

begin

New(SP, Init(1, 1, 25, 'Имя'));

New(ZP, Init(1, 2, 5, 'Почтовый индекс'), 0, 99999));

SP^.Edit;

ZP^.Edit;

.

.

.

Dispose(ZP, Done);

Dispose(SP, Done);

end;

Вы можете также использовать New как функцию, распределяющую

и возвращающую динамическую переменную заданного размера:

New(T)

или

New(T, Construct)

В первой форме T может быть любым ссылочным типом. Во второй

форме T должен указывать на объектный тип, а Construct должен

быть вызовом конструктора этого объекта. В обоих случаях типом

результата функции будет T.

Приведем пример:

var

F1, F2: PField

begin

F1 := New(PStrField, Init(1, 1, 25, 'Имя'));

F1 := New(PZipField, Init(1, 2, 5, 'Почтовый индекс', 0,

99999));

.

.

.

WriteLn(F1^.GetStr); { вызывает TStrField.GetStr }

WriteLn(F2^.GetStr); { вызывает TZipField.GetStr }

.

.

.

Dispose(F2, Done); { вызывает TField.Done }

Dispose(F1, Done); { вызывает TStrField.Done }

end;

Заметим, что хотя F1 и F2 имеют тип PField, правила совмес-

тимости по присваиванию расширенного указателя позволяют присваи-

вать F1 и F2 указателю на любой потомок TField. Поскольку GetStr

и Done являются виртуальными методами, механизм диспетчеризации

виртуального метода корректно вызывает, соответственно,

TStrString.GetStr, TZipField.GetStr, TField.Done и

TStrField.Done.

Процедурные переменные

После определения процедурного типа появляется возможность

описывать переменные этого типа. Такие переменные называют проце-

дурными переменными. Например, с учетом описаний типа из предыду-

щего примера, можно объявить следующие переменные:

var

P: SwapProc;

F: MathFunc;

Как и целая переменная, которой можно присвоить значение це-

лого типа, процедурной переменной можно присвоить значение проце-

дурного типа. Таким значением может быть, конечно, другая проце-

дурная переменная, но оно может также представлять собой иденти-

фикатор процедуры или функции. В таком контексте описания проце-

дуры или функции можно рассматривать, как описание особого рода

константы, значением которой является процедура или функция. Нап-

ример, пусть мы имеем следующие описания процедуры и функции:

procedure Swap(var A,B: integer);

var

Temp: integer;

begin

Temp := A;

A := B;

B := Temp;

end.

function Tan(Angle: real): real;

begin

Tan := Sin(Angle) / Cos(Angle);

end.

Описанным ранее переменным P и F теперь можно присвоить зна-

чения:

P := Swap;

F := Tan;

После такого присваивания обращение P(i,j) эквивалентно Swap

(i,j) и F(X) эквивалентно Tan(X).

Как и при любом другом присваивании, значения переменной в

левой и в правой части должны быть совместимы по присваиванию.

Процедурные типы, чтобы они были совместимы по присваиванию,

должны иметь одно и то же число параметров, а параметры на соот-

ветствующих позициях должны быть одинакового типа. Как упомина-

лось ранее, имена параметров в описании процедурного типа никако-

го действия не вызывают.

Кроме того, для обеспечения совместимости по присваиванию

процедура и функция, если ее нужно присвоить процедурной перемен-

ной, должна удовлетворять следующим требованиям:

- Это не должна быть стандартная процедура или функция.

- Такая процедура или функция не может быть вложенной.

- Такая процедура не должна быть процедурой типа inline.

- Она не должна быть процедурой прерывания (interrupt).

Стандартными процедурами и функциями считаются процедуры и

функции, описанные в модуле System, такие, как Writeln, Readln,

Chr, Ord. Чтобы получить возможность использовать стандартную

процедуру или функцию с процедурной переменной, вы должны напи-

сать для нее специальную "оболочку". Например, пусть мы имеем

процедурный тип:

type

IntProc = procedure(N: integer);

Следующая процедура для записи целого числа будет совмести-

мой по присваиванию:

procedure WriteInt(Number: Integer); far;

begin

Write(Number);

end.

Вложенные процедуры и функции с процедурными переменными ис-

пользовать нельзя. Процедура или функция считается вложенной,

когда она описывается внутри другой процедуры или функции. В сле-

дующем примере процедура Inner вложена в процедуру Outer и поэто-

му ее нельзя присваивать процедурной переменной:

program Nested;

procedure Outer;

procedure Inner;

begin

Writeln('Процедура Inner является вложенной');

end;

begin

Inner;

end;

begin

Outer;

end.

Использование процедурных типов не ограничивается просто

процедурными переменными. Как и любой другой тип, процедурный тип

может участвовать в описании структурного типа, что видно из сле-

дующих описаний:

type

GotoProc = procedure(X,Y: integer);

ProcList = array[1..10] of GotoProc;

WindowPtr = ^WindowRec;

Window = record

Next: WindowPtr;

Header: string[31];

Top,Left,Bottom,Right: integer;

SetCursor: GotoProc;

end;

var

P: ProcList;

W: WindowPtr;

С учетом этих описаний допустимы следующие вызовы процедур:

P[3](1,1);

W.SetCursor(10,10);

Когда процедурной переменной присваивается значение процеду-

ры, то на физическом уровне происходит следующее: адрес процедуры

сохраняется в переменной. Фактически, процедурная переменная

весьма напоминает переменную-указатель, только вместо ссылки на

данные она указывает на процедуру или функцию. Как и указатель,

процедурная переменная занимает 4 байта (два слова), в которых

содержится адрес памяти. В первом слове хранится смещение, во

втором - сегмент.

Параметры процедурного типа

Поскольку процедурные типы допускается использовать в любом

контексте, то можно описывать процедуры или функции, которые

воспринимают процедуры и функции в качестве параметров. В следую-

щем примере показывается использование параметров процедурного

типа для вывода трех таблиц различных арифметических функций:

program Tables;

type

Func = function(X,Y: integer): integer;

function Add(X,Y: integer): integer; far;

begin

Add := X + Y;

end;

function Multiply(X,Y: integer): integer; far;

begin

Multiply := X*Y;

end;

function Funny(X,Y: integer): integer; far;

begin

Funny := (X+Y) * (X-Y);

end;

procedure PrintTable(W,H: integer; Operation: Func);

var

X,Y : integer;

begin

for Y := 1 to H do

begin

for X := 1 to W do Write(Operation(X,Y):5);

Writeln;

end;

Writeln;

end;

begin

PrintTable(10,10,Add);

PrintTable(10,10,Multiply);

PrintTable(10,10,Funny);

end.

При работе программа Table выводит три таблицы. Вторая из

них выглядит следующим образом:

1 2 3 4 5 6 7 8 9 10

2 4 6 8 10 12 14 16 18 20

3 6 9 12 15 18 21 24 27 30

4 8 12 16 20 24 28 32 36 40

5 10 15 20 25 30 35 40 45 50

6 12 18 24 30 36 42 48 54 60

7 14 21 28 35 42 49 56 63 70

8 16 24 32 40 48 56 64 72 80

9 18 27 36 45 54 63 72 81 90

10 20 30 40 50 60 70 80 90 100

Параметры процедурного типа особенно полезны в том случае,

когда над множеством процедур или функций нужно выполнить ка-

кие-то общие действия. В данном случае процедуры PrintTable

представляет собой общее действие, выполняемое над функциями Add,

Multiply и Funny.

Если процедура или функция должны передаваться в качестве

параметра, они должны удовлетворять тем же правилам совместимости

типа, что и при присваивании. То есть, такие процедуры или функ-

ции должны компилироваться с директивой far, они не могут быть

встроенными функциями, не могут быть вложенными и не могут описы-

ваться с атрибутами inline или interrupt.


Глава 10. Программы и модули

Синтаксис программ

Программа в Borland Pascal состоит из заголовка программы,

необязательного оператора uses и основного блока.

программа

і ЪДДДДДДДДДї ЪДДДї ЪДДДДї ЪДДДї

АДДДВД>ізаголовокГДД>і ; ГДДДВДДДДДДДДДДДДДДДДД>іблокГД>і . ГД>

і іпрограммыі АДДДЩ ^ і ЪДДДДДДДДДДДї ^ АДДДДЩ АДДДЩ

і АДДДДДДДДДЩ і АД>іпредложениеГДЩ

АДДДДДДДДДДДДДДДДДДДДДДЩ і uses і

АДДДДДДДДДДДЩ

Заголовок программы

Заголовок программы определяет имя программы и ее параметры.

заголовок программы

і

і ЪДДДДДДДї ЪДДДДДДДДДДДДДї

АДД>іprogramГДД>іидентификаторГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД>

АДДДДДДДЩ АДДДДДДДДДДДДДЩ і ЪДДДї ЪДДДДДДДДДї ЪДДДї ^

АД>і ( ГД>іпараметрыГД>і ) ГДЩ

АДДДЩ іпрограммыі АДДДЩ

АДДДДДДДДДЩ

ЪДДДДДДДДДДДДДДДї

параметры программы ДДДД>і список ГДДДД>

іидентификаторові

АДДДДДДДДДДДДДДДЩ

Если заголовок программы присутствует, он является чисто де-

коративной деталью и компилятор его игнорирует.

Оператор uses

Оператор uses идентифицирует все модули, используемые прог-

раммой, включая непосредственно используемые модули и модули, ис-

пользуемые этими модулями.

ЪДДДДї ЪДДДДДДДДДДДДДї ЪДДДї

предложение uses ДД>іusesГДДВДД>іидентификаторГДДДДД>і ; ГДДД>

АДДДДЩ і АДДДДДДДДДДДДДЩ ^ АДДДЩ

і ЪДДДї і

АДДДДД>і , ГДДДДДДДДДЩ

АДДДЩ

Модуль System всегда используется автоматически. Для под-

держки таких средств, как файловый ввод-вывод, обработка строк,

операции с плавающей запятой, динамическое распределение памяти и

других этот модуль реализует весь нижний уровень, а также обслу-

живающие фоновые программы.

Паскаль, в свою очередь, обслуживает многие стандартные мо-

дули, такие, как Dos и Crt. Это не происходит автоматически: вы

должны обязательно включить их в оператор uses. Например:

uses Dos,Crt; { теперь могут быть доступны средства модулей

Dos и Crt }

Чтобы найти файл, содержащий скомпилированный модуль, компи-

лятор усекает указанное в операторе uses имя модуля до первых

восьми файлов и добавляет расширение файла. Если целевой платфор-

мой является DOS, расширением будет .TPU. Если целевая платформа

- Windows, то расширением файла будет .TPW. Если целевой платфор-

мой является защищенный режим DOS, то расширением файла будет

.TPP. Хотя имена файлов усекаются, в операторе uses должен указы-

ваться полный идентификатор модуля.

Синтаксис модулей

Модули являются основой модульного программирования. Они ис-

пользуются для создания библиотек, которые могут включаться в

различные программы (при этом становится необязательным иметь в

наличии исходный код), а большие программы могут подразделяться

на логически связанные модули.

ЪДДДДДДДДДї ЪДДДї ЪДДДДДДДДДДї

модуль ДДДДД>ізаголовокГДД>і ; ГДД>іинтерфейс-ГДДДї

і модуля і АДДДЩ іный разделі і

АДДДДДДДДДЩ АДДДДДДДДДДЩ і

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

і ЪДДДДДДДДДДї ЪДДДДДДДДДДДДДї ЪДДДї

АД>і раздел ГДДД>і раздел ГДД>і . ГДД>

іреализацииі іинициализацииі АДДДЩ

АДДДДДДДДДДЩ АДДДДДДДДДДДДДЩ

Заголовок модуля

В заголовке модуля определяется имя модуля.

ЪДДДДї ЪДДДДДДДДДДДДДДДДДДДДї

заголовок модуля ДДД>іunitіДД>іидентификатор модуляіДДДД>

АДДДДЩ АДДДДДДДДДДДДДДДДДДДДЩ

Имя модуля используется при ссылке на модуль в предложении

использования. Это имя должно быть уникальным, так как два модуля

с одним именем не могут одновременно использоваться.

Имя исходного файла модуля и двоичного файла должны совпа-

дать с идентификатором модуля, усеченным до первых 8 символов.

Если это не так, то компилятор не сможет найти исходный и/или

двоичный файл при компиляции использующей этот модуль программы.

Интерфейсная секция

В интерфейсной секции описываются те константы, типы, пере-

менные, процедуры и функции, которые являются глобальными, то

есть доступными основной программе (программе или модулю, которые

используют данный модуль). Основная программа имеет доступ к этим

элементам, как если бы они были описаны в модуле, являющимся вло-

женным по отношению к данной программе.

интерфейсная секция

і

і ЪДДДДДДДДДї

АД>іinterfaсeГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВД>

і і і ЪДДДДДДДДДДДї^ ^ і ЪДДДДДДДДДДДДДДДДДДї ^ і

АДДДДДДДДДЩ АД>і оператор ГЩ і ГД>і раздел описания ГДґ і

і uses і і і і констант і і і

АДДДДДДДДДДДЩ і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГД>і раздел описания ГДґ і

і і і типов переменных і і і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і ГД>і раздел описания ГДґ і

і і і переменных і і і

і і АДДДДДДДДДДДДДДДДДДЩ і і

і і ЪДДДДДДДДДДДДДДДДДДї і і

і АД>іраздел заголовков ГДЩ і

і іпроцедур и функцийі і

і АДДДДДДДДДДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

раздел заголовков

процедур и функций

і ЪДДДДДДДДДї ЪДДДї

АДДДДВДД>ізаголовокГДДДДДДДДДД>і ; ГДВДДДДДДДДДДДДДДДДДДДДДДД>

і іпроцедурыі ^ АДДДЩ і ЪДДДДДДДДДї ЪДДДї ^

і АДДДДДДДДДЩ і АД>ідирективаГДД>і ; ГДЩ

і ЪДДДДДДДДДДДДДДДДДї і і inline і АДДДЩ

АД>ізаголовок функцииГДЩ АДДДДДДДДДЩ

АДДДДДДДДДДДДДДДДДЩ

В том случае, если процедура или функция является процедурой

или функцией типа inline, в интерфейсной секции содержится только

список заголовков процедур или функций. Модуль процедуры или

функции следует дальше в секции реализации. Заметим, что заголо-

вок процедуры или функции может дублироваться и быть здесь таким

же, как в интерфейсной секции. Вам не нужно задавать здесь список

формальных параметров, но если вы это сделали и если описание в

интерфейсной секции и секции реализации не совпадают, то компиля-

тор во время компиляции выдаст сообщение об ошибке.

Секция реализации

В секции реализации определяются модули всех глобальных про-

цедур или функций. В ней также описываются константы, переменные,

процедуры и функции, являющиеся локальными, то есть недоступными

основной программе.

Секция реализации

і

і ЪДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДї

АД>іimplementationГДВДДДДДДДДДДДДДДДДДД>і раздел описаний ГДД>

АДДДДДДДДДДДДДДЩ і ЪДДДДДДДДДДДї^ АДДДДДДДДДДДДДДДДДДЩ

АД>і оператор ГЩ

і uses і

АДДДДДДДДДДДЩ

По механизму действия описания процедур и функций в интер-

фейсная секция аналогична опережающему описанию, хотя директива

forward не указывается. Таким образом, эти процедуры и функции

могут быть определены (и к ним можно обращаться в любой последо-

вательности) в секции реализации.

Допускается дублирование заголовков процедур и функций из

интерфейсной части. Вам не нужно при этом задавать список фор-

мальных параметров, но если вы это делаете, компилятор на этапе

компиляции в случае несовпадения описаний в интерфейсной части и

секции реализации будет выдавать сообщение об ошибке.

Секция инициализации

Секция инициализации является последней секцией модуля. Она

может состоять либо из зарезервированного слова end (в этом слу-

чае модуль не содержит кода инициализации), либо из операторной

части, которая должна выполняться для инициализации модуля.

ЪДДДї

секция инициализации ДДДВДД>іendГДДДДДДДДДДДДДДДДДД>

і АДДДЩ ^

і ЪДДДДДДДДДДДДДДДДДї і

АД>іоператорная частьГДЩ

АДДДДДДДДДДДДДДДДДЩ

Секции инициализации модулей, которые используются програм-

мой, выполняются в том же порядке, в каком модули указаны в опе-

раторе uses.

Косвенные ссылки на модули

В операторе uses в основной программе должны содержаться

имена всех модулей, непосредственно или косвенно используемых ос-

новной программой. Рассмотрим следующий пример:

Program Prog;

uses Unit1, Unit2

const a = b;

begin

end.

end.

unit Unit2;

interface

uses Unit1;

const b = c;

implementation

end.

unit Unit1;

interface

const c = 1;

implementation

const d = 2;

end;

В данном примере Unit12 непосредственно зависит от Unit1, а

Prog непосредственно зависит от Unit2. Кроме того, Prog зависит

косвенно от Unit1 (через Unit1), хотя ни один из описанных в

Unit1 идентификаторов в Prog не доступен.

Для компиляции программы компилятор должен иметь возможность

находить все модули, от которых она прямо или косвенно зависит.

Поэтому, для компиляции Prog компилятор должен иметь возможность

найти и Unit1, и Unit2, иначе возникнет ошибка.

Когда в интерфейсную часть модуля вносятся изменения, другие

модули, использующие этот модуль, должны быть заново скомпилиро-

ваны. При использовании команд Make или Build компилятор делает

это автоматически. Однако, если изменения коснулись только секции

реализации или секции инициализации, то другие модули, в которых

используется этот модуль, перекомпилировать не нужно. В предыду-

щем примере, если интерфейсная часть модуля Unit1 изменилась

(например, с = 2), то модуль Unit2 нужно перекомпилировать. Изме-

нение же секции реализации (например, d = 1) не требует переком-

пиляции Unit2.

При компиляции модуля в Borland Pascal на основе контрольной

суммы интерфейсной секции вычисляется номер версии модуля. В пре-

дыдущем примере при компиляции модуля Unit2 в скомпилированной

версии модуля Unit2 сохраняется номер версии модуля Unit1. При

компиляции основной программы номер версии модуля Unit1 сравнива-

ется с номером версии, сохраненным в модуле Unit2. Если номера

версий не совпадают, что свидетельствует об изменении в интер-

фейсной части модуля Unit1 со времени последней компиляции модуля

Unit2, компилятор, в зависимости от режима компиляции, выдает со-

общение об ошибке или перекомпилирует модуль Unit2 (в зависимости

от режима компиляции).

Перекрестные ссылки на модули

Размещение в секции реализации оператора uses позволяет

"скрыть" внутренние детали модуля, поскольку используемые в сек-

ции реализации модули оказываются "невидимыми" для того, кто этот

модуль использует. Более важным, однако, является то, что это

позволяет вам строить взаимозависимые модули.

В следующей программе показаны два модуля, которые "исполь-

зуют" друг друга. Основная программа Circular использует модуль с

именем Display. Модуль Display содержит в своей интерфейсной сек-

ции одну программу WriteXY, которая имеет три параметра: пару ко-

ординат (x,y) и сообщение для вывода на экран. WriteXY перемещает

курсор в точку (x,y) и выводит там сообщение. В противном случае

она вызывает простую программу обработки ошибки.

Пока мы не видим здесь ничего интересного: процедура WriteXY

просто используется вместо процедуры Write. Однако далее, когда

программа обработки ошибки будет выводить сообщение на экран, на-

чинаются перекрестные ссылки (ведь при этом она снова использует

WriteXY). Таким образом, мы имеем процедуру WriteXY, вызывающую

процедуру обработки ошибки SwapError, которая в свою очередь вы-

зывает WriteXY для вывода сообщения на экран. Если у вас уже от

всего этого закружилась голова, не беда. Давайте рассмотрим ис-

ходный код в примере и увидим, что все это не столь уж запутано.

Основная программа Circular очищает экран и выполняет три

обращения к процедуре WriteXY:

program Circular;

{ выводит текст, используя WriteXY }

uses

WinCrt, Display;

begin

ClrScr;

WriteXY(1, 1, 'Левый верхний угол экрана');

WriteXY(100, 100, 'За пределами экрана');

WriteXY(81 - Lenght('Снова в экран..'), 15,

'Снова в экран..');

end.

Взгляните на координаты (x,y) при втором обращении к проце-

дуре WriteXY. В точке с координатами (100,100) на 80х25-символь-

ном экране вывести текст невозможно. Давайте теперь посмотрим,

как работает процедура WriteXY. Далее приведен текст исходного

кода модуля Display, в котором содержится процедура WriteXY. Если

координаты (x,y) являются допустимыми, она выводит на экран сооб-

щение. В противном случае она выводит сообщение об ошибке.

unit Display;

{ содержит простую программу вывода информации на экран }

interface

procedure WriteXY(X,Y : integer, Message : string);

implementation

uses

Crt, Error;

procedure WriteXY(X,Y : integer, Message : string);

begin

if (X in [1..80] and Y in [1..25] then

begin

Goto(X,Y);

Write(Message);

end;

else

ShowError('Неверные координаты в процедуре WriteXY');

end;

end.

Процедура ShowError, вызываемая в процедуре WriteXY, показа-

на в приведенном далее исходном коде модуля Error. Она всегда вы-

водит сообщение об ошибке на 25-й строке экрана.

unit Error;

{ содержит простую программу сообщения об ошибке }

interface

procedure ShowError(ErrMsg : string);

implementation

uses

Display;

procedure ShowError(ErrMsg :string);

begin

WriteXY(1,25, 'Ошибка: '+ ErrMsg);

end;

end.

Обратите внимание, что операторы uses в секции реализации

обоих модулей (Display и Error) ссылаются друг на друга. Эти два

модуля могут ссылаться друг на друга в секции реализации благода-

ря тому, что Borland Pascal может для обеих модулей выполнять

полную компиляцию интерфейсных секций. Другими словами, компиля-

тор воспринимает ссылку на частично скомпилированный модуль A в

секции реализации модуля В, если интерфейсные секции модуля A и

модуля В не зависят друг от друга (и, следовательно, строго соб-

людаются правила Паскаля, касающиеся порядка описания).

В случае взаимозависимости интерфейсных секций модулей вы

получите ошибку из-за перекрестных ссылок.

Совместное использование описаний

Можно модифицировать процедуру WriteXY таким образом, чтобы

она воспринимала дополнительный параметр, задающий прямоугольное

окно на экране:

procedure WriteXY(SomeWindow : WindRec;

X, Y : integer;

Message : string);

procedure ShowError(Somewindow : WindRec; ErrMsg : string);

Нужно учитывать, что две процедуры находятся в разных моду-

лях. Даже если вы описываете WindData в интерфейсной секции одно-

го модуля, то нет такого допустимого способа, с помощью которого

это описание могло бы быть доступно в другом модуле. Решение сос-

тоит в том, чтобы описать третий модуль, в котором содержится

только определение записи WindRec:

unit WindData;

interface

type

WindRec = record

X1, Y1, X2, Y2 : integer;

ForeColor,

BackColor : byte;

Active : boolean;

end;

implementation

end.

В добавление к тому, что модификация кода процедур WriteXY и

ShowError позволяет использовать новый параметр, в интерфейсной

секции модулей Display и Error теперь может использоваться

WindData. Это допустимо, так как модуль WindData не зависит от

своего оператора uses, а модули Display и Error ссылаются друг на

друга только в соответствующих секциях реализации.

Взаимозависимые модули могут быть полезны в отдельных ситуа-

циях, но использовать их надо аккуратно. Если вы будете применять

их так, где это не требуется, программу станет сложней обслужи-

вать, и она будет больше подвержена ошибкам.


Глава 11. Динамически компонуемые библиотеки.

Динамически компонуемые библиотеки (DLL) позволяют несколь-

ким прикладным программа Windows или DOS защищенного режима сов-

местно использовать код и ресурсы. В Borland Pascal вы можете как

использовать существующие DLL, так и написать свои собственные

DLL, которые можно применять в других программах.

Что такое DLL?

DLL - это выполняемый модуль, содержащий программный код или

ресурсы, используемые другими прикладными программами или DLL.

Концептуально динамически компонуемая библиотека аналогичная мо-

дулю - они обеспечивают для программ процедуры и функции. Однако

между DLL и модулями имеются существенные различия. В частности,

модули компонуются статически, а DLL - динамически.

Когда программа использует процедуру или функцию из модуля,

копия кода этой процедуры или функции статически компонуется с

выполняемым файлом программы. Если две программы выполняются од-

новременно и используют одну и ту же процедуру и функцию модуля,

то в системе будет присутствовать две копии этой подпрограммы.

Эффективнее было бы использовать одну копию. Такую возможность

предоставляет DLL.

В отличие от модуля DLL не компонуется с использующей DLL

программой. Вместо этого код и ресурсы DLL находятся в отдельном

выполняемом файле с расширением .DLL. Этот файл должен присутс-

твовать при выполнении программы-клиента. Вызываемые программой

процедуры и функции динамически компонуются со своими точками

входа в используемой программе DLL.

Другое отличие модулей от DLL состоит в том, что модули мо-

гут экспортировать типы, константы, данные и объекты, а DLL -

только процедуры и функции.

Чтобы ее можно было использовать в программе Borland Pascal,

DLL не обязательно должна быть написана на Borland Pascal. Кроме

того, программы, написанные на других языках, могут использовать

DLL, написанные на Borland Pascal. DLL, таким образом, идеально

подходит при программных проектах, реализуемых на нескольких язы-

ках.

Использование DLL

Чтобы модуль мог использовать процедуру или функцию в DLL,

он должен импортировать процедуру или функцию с помощью описания

external. Например, в следующем описании из DLL и именем KERNEL

(ядро Windows) импортируется функция с именем GlobalAlloc:

function GlobalAlloc(Glags: Word; Bytes: Longint): THandle;

far; external 'KERNEL' index 15;

В импортируемой процедуре или функции директива external за-

нимает место описательной и операторной части, которые нужно было

бы включить в противном случае. В импортируемых процедурах и

функциях должна использоваться дальняя модель вызова, выбранная

ключевым словом far или директивой компилятора {$F+}; во всем ос-

тальном их поведение не отличается от обычных процедур и функций.

Borland Pascal импортирует процедуры и функции тремя спосо-

бами:

- по имени;

- по новому имени;

- по порядковому номеру.

Формат директив external для каждого из трех методов показан

в приведенном ниже примере.

Когда оператор index или name не указан, процедура или функ-

ция экспортируются по имени. Это имя совпадает с идентификатором

процедуры или функции. В данном примере процедура ImportByName

импортируется из библиотеки 'TESTLIB' по имени 'IMPORTBYNAME':

procedure ImportByName; external 'TESTLIB';

Когда задан оператор name, процедура или функция импортиру-

ется под именем, отличным от имени идентификатора. В следующем

примере процедура ImportByName импортируется из библиотеки

'TESTLIB' по имени 'REALNAME':

procedure ImportByName; external 'TESTLIB'name 'REALNAME'

Наконец, при наличии оператор index процедура или функция

импортируется по порядковому значению. Такой вид импорта уменьша-

ет время загрузки модуля, так как отпадает необходимость поиска

имени в таблице имен DLL. В следующем примере процедура

ImportByOrd импортируется из библиотеки 'TESTLIB':

procedure ImportByOrd; external 'TESTLIB' index 5;

Имя DLL задается после ключевого слова external, а новое

имя, заданное в операторе name, не обязано представлять собой

строковые литералы. Допускается любое строковое выражение-конс-

танта. Аналогично, порядковый номер, задаваемый в операторе

index, может быть любым целочисленным выражением-константой.

const

TestLib = TestLib;

Ordinal = 5;

procedure ImportByName; external TestLib;

procedure ImportByName; external TestLibname 'REALNAME'

procedure ImportByOrd; external TestLib index Ordinal;

Хотя DLL может содержать переменные, импортировать их в дру-

гие модули невозможно. Любой доступ к переменным DLL должен осу-

ществляться через процедурный интерфейс.

Модули импорта

Описания импортируемых процедур и функций могут помещаться

непосредственно в программу, которая их импортирует. Однако обыч-

но они объединяются в модуль импорта, содержащий описания всех

процедур и функций в DLL, а также все типы и константы, необходи-

мые для интерфейса с DLL. Примерами таких модулей импорта являют-

ся поставляемые с Borland Pascal модули WinTypes, WinProcs и

WinAPI. Модули импорта не обязательны для интерфейса с DLL, но

они значительно упрощают обслуживание использующих множество DLL

проектов.

В качестве примера рассмотрим DLL с именем DATETIME.DLL, со-

держащую четыре подпрограммы для получения и установки даты и

времени с помощью типа записи, содержащей число, месяц, год и за-

писи, которая содержит секунду, минуту и час. Вместо спецификации

соответствующих описаний процедуры, функции и типа в каждой ис-

пользующей DLL программе вы можете построить наряду с DLL модуль

импорта. В следующем примере создается файл .TPW (в предположе-

нии, что целевой платформой является Windows), но отсутствуют код

и данные для использующей его программы.

unit DateTime;

interface

type

TTimeRec = record

Second: Integer;

Minute: Integer;

Hour: Integer;

end;

type

TDateRec

TDateRec = record

Day: Integer;

Month: Integer;

Year: Integer;

end;

procedure SetTime(var Time: TTimeRec);

procedure GetTime(var Time: TTimeRec);

procedure SetDate(var Date: TDateRec);

procedure GetDate(var Date: TDateRec);

inplementation

procedure SetTime; external 'DATETIME' index 1;

procedure GetTime; external 'DATETIME' index 2;

procedure SetDate; external 'DATETIME' index 3;

procedure GetTime; external 'DATETIME' index 4;

end.

Любая программа, использующая DATETIME.DLL может теперь

просто задать в своем операторе uses модуль DateTime. Приведем

пример программы Windows:

program ShowTime;

uses WinCrt, DateTime;

var

Time: TTimeRec;

begin

GetTime(Time);

with Time do

WriteLn('Текущее время: ', Hour, ':', Minute, ':',

Second);

end.

Другим преимуществом использования модуля импорта, такого

как DateTime, является то, что при модификации DATETIME.DLL обно-

вить требуется только модуль импорта DateTime.

Когда вы компилируете использующую DLL программу, компилятор

не ищет DLL, так что ее присутствие не требуется. Однако DLL

должна присутствовать в системе при выполнении программы.

Если вы пишете собственные DLL, они не компилируются автома-

тически при компиляции использующей ее программы с помощью коман-

ды CompileіMake. DLL следует компилировать отдельно.

Статический и динамический импорт

Директива external обеспечивает возможность статического им-

порта процедур и функций из DLL. Статически импортируемая проце-

дура и функция всегда ссылается на одну и ту же точку входа в

DLL. Расширения Windows и защищенного режима DOS Borland поддер-

живает также динамический импорт, при котором имя DLL и имя или

порядковый номер импортируемой процедуры или функции задается во

время выполнения. Приведенная ниже программа ShowTime использует

динамический импорт для вызова процедуры GetTime в DATETIME.DLL.

Обратите внимание на использование переменной процедурного типа

для представления адреса процедуры GetTime.

program ShowTime;

uses WinProcs, WinTypes, WinCrt;

type

TTimeRec = record

Second: Integer;

Minute: Integer;

Hour: Integer;

end;

TGetTime = procedure(var Time: TTimeRec);

var

Time: TTimeRec;

Handle: THAndle;

GetTime: TGetTime;

begin

Handle := LoadLibrary('DATETIME.DLL');

if Handle >= 32 then

begin

@GetTie := GetProcAddress(Handle, 'GETTIME');

if @GetTime <> nil then

begin

GetTime(Time);

with Time do

WriteLn('Текущее время: ', Hour, ':', Minute, ':',

Second);

end;

FreeLibrary(Handle);

end;

end;

Написание DLL

Структура DLL Borland Pascal идентичная структуре программы,

но DLL начинается вместо заголовка program с заголовка program.

Заголовок library указывает Borland Pascal, что нужно создать вы-

полняемый файл с расширением .DLL, а не с расширением .EXE, и вы-

полняемый файл помечается как DLL.

библиотека

і

і ЪДДДДДДДДДДДДДї ЪДДДї ЪДДДДДДї

АДД>і заголовок ГДД>і ; ГДВДДДДДДДДДДДДДДДДДДі блок ГДДДДДДД>

і библиотеки і АДДДЩ і ЪДДДДДДДДДДї ^ АДДДДДДЩ

АДДДДДДДДДДДДДЩ АДД>і оператор ГДЩ

і uses і

АДДДДДДДДДДЩ

ЪДДДДДДДДДї ЪДДДДДДДДДДДДДДДї

заголовок ДДДД>і library ГДД>і идентификатор ГДДДДД>

процедуры АДДДДДДДДДЩ АДДДДДДДДДДДДДДДЩ

В приведенном ниже примере приведена очень простую DLL с

двумя экспортируемыми функциями Min и Max, которые вычисляют наи-

меньшее и наибольшее из двух целочисленных значений.

library MinMax;

function Min(X, Y: Integer): Integer; export;

begin

if X < Y then Min := X else Min := Y;

end;

function Max(X, Y: Integer): Integer; export;

begin

if X > Y then Max := X else Max := Y;

end;

exports

Min index 1,

Max index 2;

begin

end.

Обратите внимание на использование для подготовки Min и Max,

для экспорта ключевого слова export, и на оператор exports, ис-

пользуемый для фактического экспорта двух подпрограмм, указываю-

щий, для каждой из них, необязательный порядковый номер.

Хотя предыдущий пример этого не показывает, библиотека может

состоять из нескольких модулей. В таких случаях исходный файл

библиотеки часто сводится к оператору uses, оператору exports и

коду инициализации библиотеки. Например:

library Eritors;

uses EdInit, EdInOut, EdFormat, EdPrint;

exports

InitEditors index 1,

DoneEditors index 2,

InsertText index 3,

DeleteSelection index 4,

FormatSelection index 5,

PrintSelection index 6,

.

.

.

SetErrorHandler index 53;

begin

InitLibrary;

end.

Директива процедуры export

Если процедуры и функции должны экспортироваться DLL, они

должны компилироваться с директивой компилятора export. Директива

export принадлежит к тому же семейству процедурных директив, что

и near, far, inline и interrupt. Это означает, что директива

export, если она присутствует, должна указываться перед первым

заданием процедуры или функции - она не может указываться в опре-

деляющем описании или в опережающем описании.

Директива export делает процедуру или функцию экспортируе-

мой. Она принудительно использует для подпрограммы дальний тип

вызова и подготавливает ее для экспорта, генерируя для процедуры

специальный код входа и выхода. Заметим, однако, что фактический

экспорт процедуры или функции не происходит, пока подпрограмма не

перечисляется в операторе exports библиотеки.

Оператор exports

Процедура или функция экспортируется DLL, когда она указыва-

ется в операторе exports библиотеки.

оператор exports

і ЪДДДДДДДДДї ЪДДДДДДДДДДДДДДДДї ЪДДДї

АДД>і exports ГДД>і список экспортаГДДДДДДДДДДД>і ; ГДДДДДДД>

АДДДДДДДДДЩ АДДДДДДДДДДДДДДДДЩ АДДДЩ

ЪДДДДДДДДДДДДДДДДї

список экспорта ДДВД>і запись экcпортаГДДДДДДДДДДД>

і АДДДДДДДДДДДДДДДДЩ ^

і ЪДДДї і

АДДДДДДД>і ; ГДДДДДДДДДЩ

АДДДЩ

оператор exports

і ЪДДДДДДДДДДДДДДДї

АДДД>і идентификатор ГДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

АДДДДДДДДДДДДДДДЩ і ЪДДДДДДДї ЪДДДДДДДДДДДДДДДДДї ^ і

АДД>і index ГД>і целая константа ГДЩ і

АДДДДДДДЩ АДДДДДДДДДДДДДДДДДЩ і

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

АДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДД>

і ЪДДДДДДї ЪДДДДДДДДДДДДДДДДДДДДДї ^і ЪДДДДДДДДДДї ^

А>і name ГДД>і строковая константа ГДЩАД>і resident ГДДЩ

АДДДДДДЩ АДДДДДДДДДДДДДДДДДДДДДЩ АДДДДДДДДДДЩ

Оператор exports может встречаться в любом месте описатель-

ной части программы или библиотеки и любое число раз. Каждая за-

пись в операторе exports задает идентификатор экспортируемой про-

цедуры или функции. Однако, эта процедура или функция должна опи-

сываться до оператора exports, и ее описание должно содержать ди-

рективу export. Перед идентификатором в операторе exports вы мо-

жете указать идентификатор модуля с точкой; это называется пол-

ностью уточненным идентификатором.

Запись экспорта может также включать в себя оператор index,

который состоит из ключевого слова index, за которым следует це-

лочисленное значение в диапазоне от 1 до 32767. Когда задается

оператор index, для экспортируемой процедуры или функции должно

использоваться специальное порядковое значение. Если в записи

экспорта оператор index отсутствует, то порядковое значение прис-

ваивается автоматически.

Запись может содержать оператор name, состоящий из ключевого

слова name, за которым следует строковая константа. При наличии

оператора name экспортируемая процедура или функция должна экс-

портироваться с помощью задаваемого строковой константой имени.

Если оператор name в записи экспорта отсутствует, то процедура

или функция экспортируется по ее идентификатору (символы которого

преобразуются в верхний регистр).

Наконец, запись экспорта может включать в себя ключевое сло-

во resident. При задании ключевого слова resident информация об

экспорте остается в памяти, пока DLL загружена. Параметр resident

существенно уменьшает время поиска подпрограммы в DLL по имени.

Программа может содержать оператор exports, но это встреча-

ется редко, так как Windows не позволяет прикладным программам

экспортировать функции, используемые другие прикладными програм-

мами.

Код инициализации библиотеки

Операторная часть библиотеки состоит из кода инициализации

библиотеки. Код инициализации выполняется только один раз при

первоначальной загрузке библиотеки. Когда другие прикладные прог-

раммы будут использовать уже загруженную библиотеку, код инициа-

лизации повторно не выполняется, но увеличивается счетчик исполь-

зования DLL.

DLL хранится в памяти, пока ее счетчик использования больше

нуля. Когда счетчик использования становится нулевым, указывая,

что все использующие DLL прикладные программы завершили работу,

она удаляется из памяти. При этом выполняется код процедуры выхо-

да. Процедуры выхода регистрируются с помощью переменной

ExitProc, которая описывается в Главе 22 "Вопросы управления".

Код инициализации DLL обычно выполняет такие задачи как ре-

гистрация класса окна для содержащихся в DLL оконных процедур и

установка начальных значений для глобальных переменных DLL. Уста-

новив в нулевое значение переменную ExitCode, код инициализации

библиотеки может указать состояние ошибки (ExitCode описывается в

модуле System). По умолчанию ExitCode равна 1, что указывает на

успешную инициализацию. Если код инициализации устанавливает зна-

чение этой переменной в 0, то DLL выгружается из системной памя-

ти, и вызывающая прикладная программа уведомляется о неудачной

загрузке DLL.

Когда выполняется библиотечная процедура выхода, переменная

ExitCode не содержит код завершения процесса. Вместо этого

ExitCode содержит одно из значений wep_System или wep_Free_DLL,

определенных в модуле WinTypes. wep_System указывает на заверше-

ние работы Windows, а wep_Free_DLL указывает на то, что выгружена

данная DLL.

Приведем пример библиотеки с кодом инициализации и процеду-

рой выхода:

library Test;

{$S-}

uses WinTypes, WinProcs;

var

SaveExit: Pointer;

procedure LibExit; far;

begin

if ExitCode = wep_System_Exit then

begin

{ выполняется завершение работы системы }

.

.

.

end else

begin

.

.

.

{ разгружается DLL }

.

.

.

end;

ExitProcess : SaveExit;

end;

begin

.

.

.

{ выполнить инициализацию DLL }

.

.

.

SaveExit := ExitProc; { сохранить старый указатель

процедуры выхода }

ExitProc := @LibExit; { установка процедуры выхода

LibExit }

end.

В защищенном режиме DOS передаваемое процедуре выхода DLL

значение ExitCode всегда равно 0 и соответствует wep_FREE_DLL.

После разгрузки DLL экспортируемая функция вызывает процеду-

ру WEP (процедура выхода Windows) DLL, если она присутствует.

Библиотека Borland Pascal автоматически экспортирует функцию WEP,

которая продолжает вызывать записанный в переменной ExitProc ад-

рес, пока ExitProc не примет значения nil. Поскольку этот меха-

низм процедур выхода соответствует работе с процедурами выхода в

программах Borland Pascal, и в программах, и в библиотеках вы мо-

жете использовать одну и ту же логику процедур выхода.

Поскольку операционная система при завершении DLL переключа-

ет внутренний стек, процедуры выхода в DLL должны компилироваться

с запрещением проверки стека (в состоянии {$S-}). Кроме того, ес-

ли в процедуре выхода DLL происходит ошибка этапа выполнения,

операционная система аварийно завершает работу, поэтому вы для

предотвращения ошибок этапа выполнения вы должны включить в свой

код достаточное количество проверок.

Замечания по программированию библиотек

В следующих разделах описаны некоторые важные моменты, кото-

рые следует иметь в виду при работе с DLL.

Глобальные переменные в DLL

DLL имеет свой собственный сегмент данных, и любая описанная

в DLL переменная является локальной для этой DLL. DLL не может

получить доступ к переменным, описанным в вызывающих DLL модулях,

и не может экспортировать переменные в другие модули. Такой дос-

туп должен реализовываться через процедурный интерфейс.

Глобальные переменные и файлы в DLL

Как правило, DLL не является "владельцем" каких-либо откры-

ваемых ей файлов или получаемых ей от системы глобальных блоков

памяти. Такими объектами владеет (прямо или косвенно) сама прик-

ладная программа, вызывающая DLL.

Когда прикладная программа завершает работу, любые открытые

файлы, владельцем которых она является, автоматически закрывают-

ся, а все принадлежащие ей глобальные блоки памяти автоматически

освобождаются. Это означает, что описатели данных файлов и блоков

памяти, записанные в DLL в глобальных переменных, могут в любое

время стать недопустимыми без уведомления DLL. По этой причине

DLL не следует полагаться на допустимость описателя файла и гло-

бальных описателей памяти, хранящихся между обращениями к DLL в

глобальных переменных. Такие описатели следует сделать параметра-

ми процедур и функций DLL, и вызывающая прикладная программа

должна отвечать за их поддержку.

В Windows глобальные блоки памяти, распределенные с атрибу-

том gmem_DDEShare (определенные в модуле WinTypes), принадлежат

DLL, а не вызывающим прикладным программам. Такие блоки памяти

остаются распределенными, пока они явно не освобождаются DLL, или

пока DLL не выгружается.

Администратор памяти защищенного режима DOS не поддерживает

совместно используемых блоков памяти и игнорирует флаг

gmem_DDEShare. В защищенном режиме DOS распределяемые DLL блоки

памяти всегда принадлежат вызывающей библиотеку DLL программе.

DLL и модуль System

В продолжении существования DLL переменная HInstance содер-

жит описатель экземпляра DLL. Переменные FPrevInst и CmdShow в

DLL всегда равны 0 (как и переменная PrefixSeg), поскольку DLL не

имеет префикса программного сегмента (PSP). В прикладной програм-

ме PrefixSeg никогда не равна 0, поэтому проверка PrefixSeg <> 0

возвращает True, если текущем модулем является прикладная прог-

рамма, и False, если текущим модулем является DLL.

Чтобы обеспечить правильную работу администратора динамичес-

ки распределяемой области, содержащегося в модуле System, код за-

пуска библиотеки устанавливает переменную HeapAllocFlags в значе-

ние gmem_Moveable + gmem_DDEShare. В Windows это приводит к тому,

что все блоки памяти, распределенные через процедуры New и

GetMem, будут принадлежать DLL, а не вызывающей ее прикладной

программе.

Примечание: Подробности об администраторе памяти вы

можете найти в Главе 21.

Ошибки этапа выполнения в DLL

Если в DLL происходит ошибка этапа выполнения, вызывающая

DLL прикладная программа завершает работу. При этом сама DLL не

обязательно удаляется из памяти, поскольку она может использо-

ваться другими прикладными программами.

Поскольку DLL не может знать, вызывается ли она из приклад-

ной программы Borland Pascal или из прикладной программы, напи-

санной на другом языке программирования, то DLL не может вызывать

процедуры выхода прикладной программы до завершения прикладной

программы. Прикладная программа просто прерывается и выгружается

из памяти. По этой причине, чтобы таких ошибок не происходило,

нужно обеспечить в DLL достаточное количество проверок.

Если в DLL под Windows происходит ошибка этапа выполнения,

то надежнее всего полностью выйти в Windows. Если вы просто пыта-

етесь модифицировать и перестроить сбойный код DLL, а затем снова

выполнить прикладную программу, Windows не будет загружать новую

версию, если ошибочная версия уже находится в память. Выйдите из

Windows и перезапустите ее, а Borland Pascal обеспечит загрузку

корректной версии DLL.

DLL и сегменты стека

В отличие от прикладной программы DLL не имеет своего собс-

твенного сегмента стека. Вместо этого она использует сегмент сте-

ка вызывающей DLL прикладной программы. Это может создать пробле-

мы в подпрограмме DLL, которые полагают, что регистры DS и SS

ссылаются на один и тот же сегмент, как это имеет место в модуле

прикладной программы Windows.

Borland Pascal никогда не генерирует код, подразумевающий

равенство DS = SS, и в библиотеке исполняющей системы Borland

Pascal таких предположений не делается. Если вы пишете код на

языке ассемблера, то не полагайтесь на то, что регистры DS и SS

содержат одно и то же значение.

Создание совместно используемых DLL

Borland Pascal поддерживает DLL, которые могут совместно ис-

пользоваться в защищенном режиме DOS и в Windows. Совместно ис-

пользуемые DLL совместимы на уровне двоичного кода. Это означает,

что один и тот же файл .DLL может использоваться в прикладной

программе защищенного режима DOS или в прикладной программе

Windows.

При компиляции совместно используемой DLL в качестве целевой

платформы нужно выбирать Windows:

* В IDE выберите команду CompileіTarget и в диалоговом окне

Target (Целевая платформа) укажите Windows.

* При использовании компилятора, работающего в режиме ко-

мандной строки, для выбора в качестве целевой платформы

Windows используйте переключатель /CW.

DLL, скомпилированная для защищенного режима DOS, под

Windows использоваться не может, так как библиотека исполняющей

системы защищенного режима DOS использует отдельные функциональ-

ные вызовы DOS и DPMI, которые следует избегать в Windows.

Совместно используемая DLL может взаимодействовать с опера-

ционной системой (DOS защищенного режиме или Windows) только че-

рез модуль WinAPI. Этот модуль представляет функции, общие для

защищенного режима DOS и Windows. Другие интерфейсные модули

Windows, такие как WinTypes и WinProcs, описывают большое число

подпрограмм API, не поддерживаемых в защищенном режиме DOS.

Примечание: О модуле WinAPI рассказывается в Главе 17

"Программирование в защищенном режиме DOS".

Важно отметить, что хотя совместно используемая DLL может

выполняться одновременно и под Windows, в окне защищенного режима

Windows DOS, связь через DLL между двумя операционными средами

невозможна. Реально в системе будет присутствовать две копии DLL,

каждая из которых защищена от другой и использует полностью изо-

лированную область памяти.


Часть II.
Глава 12. Библиотеки исполняющей системы

Borland Pascal включает в себя библиотеки исполняющей систе-

мы для защищенного режима DOS, реального режима DOS и Windows.

Наиболее часто используемые библиотеки исполняющей системы нахо-

дятся в файлах TURBO.TPL (реальный режим DOS), TPP.TPL (защищен-

ный режим DOS) и TPW.TPL (Windows). Дополнительные модули постав-

ляются в отдельных файлах .TPU, .TPP и .TPW.

* Для реального режима DOS библиотека TURBO.TPL содержит мо-

дули System, Overlay, Crt, Dos и Printer. Кроме того, в

отдельных файлах .TPU поставляются модули Graph, Strings,

WinDos, Turbo3 и Graph3.

* Для защищенного режима DOS библиотека TPP.TPL содержит мо-

дули System, Crt, Dos, Printer, Strings, WinDos и WinAPI.

Кроме того, в виде отдельного файла .TPP поставляется мо-

дуль Graph.

* Для Windows библиотека TPW.TPL содержит модули System,

Strings, WinTypes, WinProcs, Win31, WinAPI, WinDos, WinCrt

и WinPrn. В виде исходного кода поставляются некоторые до-

полнительные модули Windows.

Кроме библиотек исполняющей системы, Borland Pascal включает

в себя прикладную среду Turbo Vision для реального и защищенного

режима DOS и прикладную среду ObjectWindows для Windows. Эти биб-

лиотеки описаны в "Руководстве по программированию с Turbo

Vision" и в "Руководстве по программированию с использованием

ObjectWindows".

В данной главе кратко описывается каждый модуль библиотеки

исполняющей системы.

Модули Borland Pascal

Borland Pascal обеспечивает вам доступ к большому числу

встроенных констант, типов данных, переменных, процедур и функ-

ций. Некоторые из них специфичны для Borland Pascal, другие спе-

цифичны для программирования прикладных задач для Windows или

Dos. Их количество велико, однако, в своей программе вы редко ис-

пользуете их все сразу. Поэтому они разделены на связанные груп-

пы, называемые модулями. В этом случае можно использовать только

те модули, которые необходимы в программе.

Программный модуль (unit) представляет собой набор констант,

типов данных, переменных, процедур и функций. Каждый модуль ана-

логичен отдельной программе на Паскале: он может иметь основное

тело, которое вызывается перед запуском вашей программы и осу-

ществляет необходимую инициализацию. Короче говоря, модуль предс-

тавляет собой библиотеку описаний, которую можно вставить в прог-

рамму и которая позволит разбить программу на части, компилируе-

мые отдельно.

Модуль обеспечивает набор средств благодаря входящим в него

процедурам и функциям при поддержке констант, типов данных и пе-

ременных, однако действительная реализация этих средств скрыта в

силу того, что модуль разделен на две части: интерфейс и реализа-

цию. Если программа использует модуль, то все описания модуля

становятся доступными этой программе, как если бы они были опре-

делены в ней самой.

Структура модуля аналогична структуре программы. Все описа-

ния внутри модуля связаны друг с другом. Например, модуль Strings

содержит все описания, необходимые для подпрограмм обработки

строк, заканчивающихся нулевым символом.

Модуль System

Модуль System реализует поддерживающие подпрограммы нижнего

уровня для всех встроенных средств, таких как ввод-вывод, работа

со строками, операции с плавающей точкой и динамическое распреде-

ление памяти.

Модуль System содержит все стандартные и встроенные процеду-

ры и функции Borland Pascal. Любая подпрограмма Borland Pascal,

не являющаяся частью стандартного Паскаля и не находящаяся ни в

каком другом модуле, содержится в модуле System. Этот модуль ав-

томатически используется во всех программах, и его не требуется

указывать в операторе uses.

Модуль Dos и WinDos

Модули Dos и WinDos реализуют многочисленные процедуры и

функции Паскаля, которые эквивалентны наиболее часто используемым

вызовам DOS, как например, GetТime, SetТime, DiskSize и так да-

лее. Кроме того, WinDos определяет две программы низкого уровня

МsDos и Intr, которые позволяют активизировать любой вызов MS-DOS

или системное прерывание. Тип Registers представляет собой тип

данных для параметра в МsDos и Intr. Кроме того, определяются не-

которые другие константы и типы данных. Ни одна из этих подпрог-

рамм не определена в стандартном Паскале, поэтому они помещены в

свои собственные модули. Подробнее модули WinDos и Dos описывают-

ся в Главе 16 "Интерфейс с DOS".

Модуль Crt

Модуль Crt реализует ряд мощных программ, предоставляющих

вам полную возможность управления средствами компьютера РС, таки-

ми, как управление режимом экрана, расширенные коды клавиатуры,

цвета, окна, и звуковые сигналы. Модуль Crt может использоваться

только в программах, работающих на персональных компьютерах IBM

РС, РС AT, РS/2 фирмы IBM и полностью совместимых с ними.

Одним из основных преимуществ использования модуля Crt явля-

ется большая скорость и гибкость при выполнении операций работы с

экраном. Программы, не работающие с модулем Crt, выводят на экран

информацию с помощью средств операционной системы DOS, что связа-

но с дополнительными непроизводительными затратами. При использо-

вании модуля Crt выводимая информация посылается непосредственно

в базовую систему ввода-вывода (ВIОS), или, для еще более быстрых

операций, непосредственно в видеопамять.

О модуле Crt рассказывает в Главе 14 "Ввод и вывод".

Модуль WinCrt

Модуль WinCrt - это дpайвеp устpойства текстовых файлов, ко-

тоpый пеpеопpеделяет вывод в пpокpучиваемое окно. Хотя большая

часть ваших пpогpамм для Windows, как пpавило, будет создавать

свои собственные окна, модуль WinCrt можно использовать для

быстрых и простых программ, базирующихся на текстах, когда вам

нужно быстро получить результаты. Модуль WinCrt описывается в

главе 14 "Ввод и вывод".

Модуль Printer

Модуль Printer позволяет вам посылать стандартный вывод Пас-

каля на принтер, используя процедуры Write и WriteLn. Подробнее

он описывается в главе 14 "Ввод и вывод".

Модуль WinPrn

Модуль WinPrn позволяет вам посылать своей вашей программы

Windows на принтер по вашему выбору. Подробнее он описывается в

главе 14 "Ввод и вывод".

Модуль Overlay

Модуль Overlay позволяет вам уменьшить требования к памяти

программы DOS реального режима. Фактически, вы можете писать

программы, превышающие общий объем доступной памяти, поскольку в

каждый момент в памяти будет находиться только часть вашей прог-

раммы. Подробно данный модуль описан в Главе 20 "Использование

оверлеев".

Модуль Strings

Модуль Strings обеспечивает обработку новых строк, заканчи-

вающихся пустым символом. Строки, стандартные для Паскаля,

обрабатываются модулем System. Подробнее модуль Strings описыва-

ется в Главе 18 "Использование строк с завершающим нулем".

Модуль Graph

Модуль Graph обеспечивает ряд быстрых и мощных графических

подпрограмм. Он реализует независимый от устройств графический

драйвер Borland, поддерживающий графику CGA, EGA, VGA, Hercules,

AT&T 400, MCGA, 3270PC и 8514. Модуль Graph не встроен в

TURBO.TPL, он находится на том же диске, что и файлы .BGI (графи-

ческий интерфейс Borland) и .CHR (шрифты).

Подробнее о модуле Graph рассказывается в Главе 19 "Исполь-

зование графического интерфейса Borland".

Модули Turbo3 и Graph3

Модули Turbo3 и Graph3 предусмотрены только для обратной

совместимости. Turbo3 содержит две переменные и несколько проце-

дур, которые больше не поддерживаются Borland Pascal. Graph3 со-

держит полный набор графических подпрограмм версии 3.0 - основ-

ных, продвинутых, и использующих графику в относительных коман-

дах. Информацию об этих файлах вы можете найти в файле

TURBO3.INT.

Модули WinTypes и WinProcs

Модуль WinTypes содержит все константы, структуры данных и

стили, используемые в прикладном программном интерфейсе Windows.

Модуль WinTypes подробно описывается в справочной системе Borland

Pascal.

Модуль WinProcs содержит все функции и процедуры, составляю-

щие прикладной программный интерфейс Windows. Модуль WinProcs

также подробно описывается в справочной системе.

Совместно эти модули образуют прикладной программный интер-

фейс Windows (API).

Модуль Win31

Модуль Win31 обеспечивает интерфейс с дополнительными подп-

рограммами API, которые можно найти в Windows 3.1. Прикладные

программы, использующие Win31, не работают под Windows 3.0.

Модуль WinAPI

Модуль WinAPI определяет подмножество подпрограмм API

Windows, поддерживаемых и в Windows, и в защищенном режиме DOS.

Подробнее об этом модуле рассказывается в Главе 17 "Программиро-

вание в защищенном режиме DOS".

Модули, поддерживающие Windows 3.1

Borland Pascal поддерживает API Windows 3.1 в следующих мо-

дулях:

ColorDlg LZExpand ShellAPI

CommDlg MMSystem Stress

Cpl OLE ToolHelp

DDEML PenWin Ver

Dlgs Print WinMem32


Глава 13. Стандартные процедуры и функции

В данной главе кратко описываются стандартные (встроенные)

процедуры и функции Borland Pascal и предописанные переменные,

определенные в модуле System. Более подробную информацию о конк-

ретной процедуре, функции или предописанной переменной вы можете

найти в Главе 1 ("Справочник по библиотеке") в "Руководстве прог-

раммиста".

Стандартные процедуры и функции являются предописанными. Так

как записи с их предварительными описаниями действуют точно так-

же, как если бы они были описаны в окружающем программу модуле,

при описании, переопределяющем тот же идентификатор внутри прог-

раммы конфликта не возникает.

Примечание: О других процедурах и функциях вы можете

прочесть в Главе 14 "Ввод и вывод".

В данной главе освещаются следующие темы:

- Процедуры управления программой.

- Функции преобразования.

- Арифметические функции.

- Порядковые процедуры и функции.

- Строковые процедуры и функции.

- Процедуры и функции динамического распределения памяти.

- Прочие процедуры и функции.

- Предописанные переменные модуля System.

Процедуры управления работой программы

Процедуры управления работой программы - это процедуры, уп-

равляющие логикой выполнения программы.

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура і Описание і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Break і Завершает оператор for, while или repeat. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Continue і Продолжает итерацию оператора for, while,і

і і или repeat. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Eхit і Позволяет немедленно выйти из текущего мо-і

і і дуля. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Наlt і Останавливает выполнение программы и возв-і

і і ращает управление операционной системе. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і RunError і Останавливает выполнение программы и гене-і

і і рирует ошибку этапа выполнения. і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Функции преобразования

Ниже перечислены функции преобразования.

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Chr і Возвращает символ, заданный целым числом. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і High і Возвращает старшее значение в диапазоне ар-і

і і гумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Low і Возвращает младшее значение в диапазоне ар-і

і і гумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Оrd і Возвращает порядковое число по значению пе-і

і і речислимого типа. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Rоund і Округляет значение вещественного типа доі

і і значения, имеющего длинный целый тип. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Тrunс і Усекает значение вещественного типа до зна-і

і і чения, имеющего длинный целый тип. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Арифметические функции

Эти функции полезно использовать для выполнения арифметичес-

ких операций.

Примечание: Значения, возвращаемые процедурами опера-

ций с плавающей запятой модуля System, при компиляции в

режиме числовой обработки (директива {$N+}), имеют не ве-

щественный тип (real), а расширенный (extended).

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Abs і Возвращает абсолютное значение аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Аrctan і Возвращает арктангенс аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Cоs і Возвращает косинус аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Eхp і Возвращает экспоненту аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Frас і Возвращает дробную часть аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Int і Возвращает целую часть аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Ln і Возвращает натуральный логарифм аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Pi і Возвращает значение числа Pi і

і і (3.141592653897932385). і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Sin і Возвращает синус аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Sqr і Возвращает аргумент в квадрате. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Sqrt і Возвращает квадратный корень аргумента. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Порядковые процедуры и функции

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура/функцияі Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Dес і Уменьшает значение переменной. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Inс і Увеличивает значение переменной. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Оdd і Проверяет, является ли аргумент нечетнымі

і і числом. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Рred і Возвращает предшествующее значение аргумен-і

і і та. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Suсс і Возвращает его последующее значение. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Строковые процедуры и функции

Следующие процедуры и функции используются для работы со

строками Паскаля.

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Cоncat і Выполняет конкатенацию последовательностиі

і і строк. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Cору і Возвращает подстроку строки. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Delete і Удаляет из строки подстроку. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Insert і Добавляет в строку подстроку. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Length і Возвращает динамическую длину строки. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Pоs і Производит поиск подстроки в строке. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Str і Преобразует численное значение в его стро-і

і і ковое представление. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Val і Преобразует строковое значение в его чис-і

і і ленное представление. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Процедуры и функции динамического распределения памяти

Эти процедуры и функции используются для управления динами-

чески распределяемой областью - областью памяти, которая занимает

всю свободную память или ее часть, остающуюся при выполнении

программы. Полное описание методов, используемых для управления

динамически распределяемой областью памяти приводится в разделе

"Программа динамического распределения памяти" в Главе 21 ("Воп-

росы управления памятью").

Процедуры динамического распределения памяти

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура/функцияі Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Dispose і Уничтожает динамическую переменную. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FrееМем і Уничтожает динамическую переменную данногоі

і і размера. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetМем і Создает новую динамическую переменную за-і

і і данного размера и устанавливает на нее пе-і

і і ременную-указатель. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МахАvail і Возвращает размер наибольшего непрерывногоі

і і свободного модуля в динамически распределя-і

і і емой области памяти, соответствующий разме-і

і і ру наибольшей динамической переменной, ко-і

і і торая может быть выделена при обращении ві

і і МахAvail. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і МемАvail і Возвращает количество имеющихся в динами-і

і і чески распределяемой области свободныхі

і і байт. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і New і Создает новую динамическую переменную и ус-і

і і танавливает на нее переменную-указатель. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Функции для работы с указателями и адресами

Ниже перечислены функции для работы с указателями и адреса-

ми.

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функции і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Аddr і Возвращает адрес заданного объекта. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і CSeg і Возвращает текущее значение регистра CS. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і DSeg і Возвращает текущее значение регистра DS. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Оfs і Возвращает смещение для заданного объекта. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Ptr і Преобразует адрес базового сегмента и сме-і

і і щение в значение типа указатель. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Seg і Возвращает сегмент для заданного объекта. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SPtr і Возвращает текущее значение регистра SР. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SSeg і Возвращает текущее значение регистра SS. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Прочие процедуры и функции

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура/функцияі Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Exclude і Исключает элемент из множества. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FillChar і Заполняет заданное число следующих друг заі

і і другом бит указанным значением. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Hi і Возвращает старший байт аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Include і Включает элемент в множество. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Lo і Возвращает младший байт аргумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Моvе і Копирует заданное число непрерывных байт ві

і і указанных границах из одного места в дру-і

і і гое, границы которого также указываются. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і ParamCount і Возвращает число параметров, переданныхі

і і программе в командной строке. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і ParamStr і Возвращает параметр, заданный в команднойі

і і строке. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Random і Возвращает случайное число. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Rаndомizе і Инициализирует встроенный генератор случай-і

і і ных чисел случайным значением. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SizeOf і Возвращает число байт, занимаемых аргумен-і

і і том. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Swap і Меняет местами старший и младший байты ар-і

і і гумента. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і TypeOf і Указывает на таблицу виртуальных методові

і і объекта. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і UpCase і Преобразует символ в верхний регистр. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Предописанные переменные

Кроме процедур и функций в модуле System предусмотрен ряд

предописанных переменных. Их перечень зависит от библиотеки ис-

полняющей системы, к которой относится модуль System.

Следующие переменные описываются в модуле System библиотеки

TURBO.TPL - библиотеке исполняющей системы для приложений реаль-

ного режима DOS.

ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Переменная і Тип і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ErrorAddr і Pointer і адрес ошибки этапа выполне- і

і і і ния і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ExitProc і Pointer і процедура выхода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ExitCode і Integer і код выхода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileMode і Byte і режим открытия файла і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FreeList і Pointer і список свободных блоков ди- і

і і і намически распределяемой об- і

і і і ласти памяти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FreeZero і Pointer і должен быть равен 0 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapOrg і Pointer і начало динамически распреде- і

і і і ляемой области і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapPtr і Pointer і указатель динамически рас- і

і і і пределяемой области і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapError і Pointer і функция ошибки динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Input і Text і стандартный файл ввода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і InOutRes і Integer і буфер результата операции і

і і і ввода-вывода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Output і Text і стандартный файл вывода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrCodeList і Word і список сегментов оверлейного і

і і і кода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrDebugPtr і Pointer і используется при отладке і

і і і оверлеев і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrDosHandle і Word і описатель оверлея DOS і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrEmsHandle і Word і описатель оверлея EMS і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrHeapEnd і Word і конец оверлейного буфера і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrHeapOrg і Word і начало оверлейного буфера і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrHeapPtr і Word і указатель оверлейного буфера і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrHeapSize і Word і начальный размер оверлейного і

і і і буфера і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і OvrLoadList і Word і список загруженных оверлеев і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і PrefixSeg і Word і префикс программного сегмен- і

і і і та і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RandSeed і Longint і случайное число (генериру- і

і і і ется датчиком случайных чи- і

і і і сел) і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt00 і Pointer і сохраненное прерывание $00 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt02 і Pointer і сохраненное прерывание $02 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt1B і Pointer і сохраненное прерывание $1B і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt23 і Pointer і сохраненное прерывание $23 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt24 і Pointer і сохраненное прерывание $24 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt34 і Pointer і сохраненное прерывание $34 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt35 і Pointer і сохраненное прерывание $35 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt36 і Pointer і сохраненное прерывание $36 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt37 і Pointer і сохраненное прерывание $37 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt38 і Pointer і сохраненное прерывание $38 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt39 і Pointer і сохраненное прерывание $39 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3A і Pointer і сохраненное прерывание $3A і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3B і Pointer і сохраненное прерывание $3B і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3C і Pointer і сохраненное прерывание $3C і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3D і Pointer і сохраненное прерывание $3D і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3E і Pointer і сохраненное прерывание $3E і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3F і Pointer і сохраненное прерывание $3F і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt75 і Pointer і сохраненное прерывание $75 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Seg0040 і Word і селектор сегмента $0040 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SegA000 і Word і селектор сегмента $A000 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SegB000 і Word і селектор сегмента $B000 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SegC000 і Word і селектор сегмента $C000 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SelectorInc і Word і шаг увеличения селектора і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і StackLimit і Word і указатель на нижнюю границу і

і і і стека і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Test8086 і Byte і результат проверки процес- і

і і і сора 8086 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Test8087 і Byte і результат проверки сопроцес- і

і і і сора 8087 і

АДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

PrefixSeg представляет собой переменную длиной в слово, со-

держащую адрес префикса программного сегмента (PSP), создаваемого

при выполнении программы операционной системой DOS. Полное описа-

ние PSP приведено в руководстве по операционной системе DOS.

Переменная StackLimit содержит смещение начала стека относи-

тельно сегмента стека, что соответствует минимальному допустимому

значению регистра SP, после которого уже возникает ситуация пере-

полнения стека. По умолчанию значение этой переменной равно 0, но

если программа компилируется с директивами {$N+,$E+}, то эмулятор

сопроцессора 8087 при отсутствии в системе сопроцессора 8087 для

резервирования места в младших адресах сегмента стека будет уста-

навливать ее в значение 224.

Переменная InOutRes используется встроенными программами

ввода-вывода для сохранения значения, возвращаемого при следующем

обращении к функции IOResult.

В RandSeed сохраняется начальное значение для встроенного

генератора случайных чисел. Если присваивать этой переменной оп-

ределенное значение, то функция Random будет генерировать задан-

ную последовательность случайных чисел.

Переменная FileMode позволяет изменять режим доступа к отк-

рытым типизированным и нетипизированным файлам.

В переменной Test8087 сохраняется результат работы алгорит-

мов автоматического распознавания сопроцессора 8087, которые на-

чинают работать при запуске программы, скомпилированной с дирек-

тивой {$N+}.

Input и Оutput - это стандартные файлы ввода-вывода, необхо-

димые в каждой реализации Паскаля. По умолчанию они связываются

со стандартными входными и выходными файлами в Dos.

Следующие переменные описываются в модуле System библиотеки

TPW.TPL - библиотеке исполняющей системы для приложений Windows.

ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Переменная і Тип і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і CmdLine і PChar і указатель командной строки і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і CmdShow і Integer і параметр CmdShow для Create- і

і і і Window і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ErrorAddr і Pointer і адрес ошибки этапа выполне- і

і і і ния і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ExitProc і Pointer і процедура выхода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ExitCode і Integer і код выхода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileMode і Byte і режим открытия файла і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Input і Text і стандартный файл ввода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapAllocFlag і Word і флаги распределения блока і

і і і динамически распределяемой і

і і і области памяти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapBlock і Word і размер блока динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HearError і Pointer і функция ошибки динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapLimit і Word і размер наименьшего блока ди- і

і і і намически распределяемой об- і

і і і ласти памяти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapList і Word і список сегментов динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HInstance і Word і описатель данного экземпляра і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HPrevInst і Word і описатель предыдущего экзем- і

і і і пляра і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і InOutRes і Integer і буфер результата операции і

і і і ввода-вывода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Output і Text і стандартный файл вывода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і PrefixSeg і Word і префикс программного сегмен- і

і і і та і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RandSeed і Longint і случайное число (генериру- і

і і і ется датчиком случайных чи- і

і і і сел) і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SelectorInc і Word і шаг увеличения селектора і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і StackLimit і Word і указатель на нижнюю границу і

і і і стека і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Test8086 і Byte і результат проверки процес- і

і і і сора 8086 і

АДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

HInstance содержит описатель экземпляра прикладной программы

или библиотеки, как это предусматривается операционной средой

Windows. В программе HPrevInst содержит предыдущий экземпляр

прикладной программы, или 0, если предыдущего экземпляра нет. В

библиотеке HPrevInst всегда равно 0.

В программе CmdLine содержит указатель на завершающуюся ну-

лем строку, которая содержит аргументы командной строки, заданные

при запуске прикладной программы. В библиотеке эта переменная не

определена.

В программе CmdShow содержит значение параметра, передачу

которого в ShowWindow ожидает Windows, когда прикладная программа

создает основное окно. В библиотеке эта переменная всегда равна

0.

Подсистемой управления динамически распределяемой областью

памяти для реализации программ динамического распределения памяти

Borland Pascal используются переменные HeapList, HeapLimit,

HeapBlock и HeapError.

Для реализации процедур выхода используются переменные

ExitProc, ErrorCode и ErrorAdr.

Переменная PrefixSeg представляет собой переменную длиной в

слово, содержащую адрес префикса программного сегмента (PSP),

создаваемого при выполнении программы операционной системой DOS.

Полное описание PSP приведено в руководстве по операционной сис-

теме DOS.

Переменная InOutRes используется встроенными программами

ввода-вывода для сохранения значения, возвращаемого при следующем

обращении к функции IOResult.

Переменная FileMode позволяет изменять режим доступа к отк-

рытым типизованным и нетипизированным файлам. Более подробно это

описано в Главе 14 "Ввод и вывод".

Следующие переменные описываются в модуле System библиотеки

TPP.TPL - библиотеке исполняющей системы для приложений защищен-

ного режима DOS.

ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДї

і Переменная і Тип і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ErrorAddr і Pointer і адрес ошибки этапа выполне- і

і і і ния і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ExitProc і Pointer і процедура выхода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ExitCode і Integer і код выхода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileMode і Byte і режим открытия файла і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapAllocFlagsі Word і флаги распределения блока і

і і і динамически распределяемой і

і і і области памяти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapBlock і Word і размер блока динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HearError і Pointer і функция ошибки динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapLimit і Word і размер наименьшего блока ди- і

і і і намически распределяемой об- і

і і і ласти памяти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HeapList і Word і список сегментов динамически і

і і і распределяемой области памя- і

і і і ти і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HInstance і Word і описатель данного экземпляра і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і HPrevInst і Word і описатель предыдущего экзем- і

і і і пляра і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і InOutRes і Integer і буфер результата операции і

і і і ввода-вывода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Output і Text і стандартный файл вывода і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і PrefixSeg і Word і префикс программного сегмен- і

і і і та і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RandSeed і Longint і случайное число (генериру- і

і і і ется датчиком случайных чи- і

і і і сел) і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RealModeRegs і array[0..49] ofі регистры реального режима і

і і byte і і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt00 і Pointer і сохраненная исключительная і

і і і ситуация $00 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt02 і Pointer і сохраненное прерывание $02 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt0C і Pointer і сохраненное исключительная і

і і і ситуация $0С і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt0D і Pointer і сохраненное прерывание $0D і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt1B і Pointer і сохраненное прерывание $1B і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt21 і Pointer і сохраненное прерывание $21 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt23 і Pointer і сохраненное прерывание і

і і і реального режима $23 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt24 і Pointer і сохраненное прерывание і

і і і реального режима $24 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt34 і Pointer і сохраненное прерывание $34 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt35 і Pointer і сохраненное прерывание $35 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt36 і Pointer і сохраненное прерывание $36 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt37 і Pointer і сохраненное прерывание $37 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3B і Pointer і сохраненное прерывание $38 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt39 і Pointer і сохраненное прерывание $39 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3A і Pointer і сохраненное прерывание $3A і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3B і Pointer і сохраненное прерывание $3B і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3C і Pointer і сохраненное прерывание $3C і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3D і Pointer і сохраненное прерывание $3D і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3E і Pointer і сохраненное прерывание $3E і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt3F і Pointer і сохраненное прерывание $3F і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SaveInt75 і Pointer і сохраненное прерывание $75 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Seg0040 і Word і селектор сегмента $0040 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SegA000 і Word і селектор сегмента $A000 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SegB000 і Word і селектор сегмента $B000 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SegB800 і Word і селектор сегмента $B800 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Test8086 і Byte і результат проверки процес- і

і і і сора 8086 і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Test8087 і Byte і результат проверки сопроцес- і

і і і сора 8087 і

АДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Более подробную информацию об этих переменных вы можете най-

ти в Главе 1 ("Справочник по библиотеке") в "Справочном руководс-

тве программиста.


Глава 14. Ввод и вывод

В данной Главе кратко описываются стандартные (или встроен-

ные) функции и процедуры ввода-вывода Borland Pascal. Эти проце-

дуры и функции можно найти в модуле System.

Процедуры и функции ввода-вывода

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДґ

і Append і Открывает существующий файл для добавле-і

і і ния. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДґ

і Assign і Присваивает имя внешнего файла файловой пе-і

і і ременной. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДґ

і BlockRead і Считывает из нетипизированного файла однуі

і і или более записей. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ

і BlockWrite і Записывает в нетипизированный файл однуі

і і или более записей. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і ChDir і Выполняет смену текущего каталога. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Close і Закрывает открытый файл. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Erase і Стирает внешний файл. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Eоf і Возвращает для файла состояние end-of-fileі

і і (конец файла). і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FilePos і Возвращает текущую позицию в файле. Дляі

і і текстовых файлов не используется. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileSize і Возвращает текущий размер файла. Для текс-і

і і товых файлов не используется. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Flush і Сбрасывает буфер текстового файла вывода. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Getdir і Возвращает текущий каталог на заданном дис-і

і і ке. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і IОResult і Возвращает целое значение, являющееся сос-і

і і тоянием последней выполненной операции вво-і

і і да-вывода. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і MkDir і Создает подкаталог. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Read і Считывает одно или более значений из файлаі

і і в одну или более переменных. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Readln і Делает то же, что и Read, и выполняет про-і

і і пуск до начала следующей строки текстовогоі

і і файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Rеnаме і Переименовывает внешний файл. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Rеset і Открывает существующий файл. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Rewritе і Создает и открывает новый файл. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RмDir і Удаляет пустой подкаталог. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Seek і Перемещает текущую позицию в файле на за-і

і і данный элемент. Для текстовых файлов не ис-і

і і пользуется. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SeekEof і Возвращает для текстового файла состояниеі

і і "конец файла". і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SeekEoln і Возвращает для текстового файла состояниеі

і і "конец строки". і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetTextBuf і Назначает для текстового файла буфер ввода-і

і і вывода. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Truncate і Усекает размер файла до текущей позиции.і

і і Для текстовых файлов не используется. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Write і Записывает в файл одно или более значений. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Writeln і Делает то же, что Write, но затем записы-і

і і вает в текстовый файл символ конца строки. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Файловый ввод-вывод

Файловая переменная в Паскале - это любая переменная файло-

вого типа. В Паскале имеются три класса файлов: типизированный

файл, текстовый файл и нетипизированный файл.

Примечание: Синтаксис записи типов файлов представлен

в Главе 4, в разделе "Структурные типы".

Перед использованием файловой переменной она должна быть

связана с внешним файлом с помощью вызова процедуры Assign. Внеш-

ним файлом обычно является поименованный файл на диске, но он

также может представлять собой устройство, например, клавиатуру

или дисплей. Во внешних файлах сохраняется записанная в файл ин-

формация, или они служат источниками информации, которая считыва-

ется из файла.

Когда связь с внешним файлом установлена, для подготовки ее

к операции ввода или вывода файловая переменная должна быть "отк-

рыта". Существующий файл можно открыть с помощью процедуры Reset,

а новый файл можно создать и открыть с помощью процедуры Rewrite.

Текстовые файлы, открытые с помощью процедуры Reset доступны

только по чтению, а текстовые файлы, открытые с помощью процедуры

Rewrite, доступны только по записи. Типизированные и нетипизиро-

ванные файлы всегда допускают как чтение, так и запись, независи-

мо от того были они открыты с помощью процедуры Reset или с по-

мощью процедуры Rewrite.

Любой файл, представляет собой линейную последовательность

элементов, каждый из которых имеет тип элемента (или тип записи)

файла. Каждый элемент файла имеет номер. Первый элемент файла

считается нулевым элементом.

Обычно доступ к файлам организуется последовательно, то

есть, когда элемент считывается с помощью стандартной процедуры

Read или записывается с помощью стандартной процедуры Write, те-

кущая позиция файла перемещается к следующему по порядку элементу

файла. Однако к типизированным и нетипизированным файлам можно

организовать прямой доступ с помощью стандартной процедуры Sееk,

которая перемещает текущую позицию файла к заданному элементу.

Для определения текущей позиции в файле и текущего размера файла

можно использовать стандартные функции FilePоs и Filesize.

Когда программа завершает обработку файла, он должен закры-

ваться с помощью стандартной процедуры Close. После полного зак-

рытия файла связанный с ним внешний файл обновляется. Затем фай-

ловая переменная может быть связана с другим внешним файлом.

По умолчанию при всех обращениях к стандартным функциям и

процедурам ввода-вывода автоматически производится проверка на

наличие ошибок. При обнаружении ошибки программа прекращает рабо-

ту и выводит на экран сообщение об ошибке. С помощью директив

компилятора {$I+} и {$I-} эту автоматическую проверку можно вклю-

чить или выключить. Когда автоматическая проверка отключена, то

есть когда процедура или функция была скомпилирована с директивой

{$I-}, ошибки ввода-вывода, возникающие при работе программы, не

приводят к ее останову. При этом, чтобы проверить результат вы-

полнения операции ввода-вывода, нужно использовать стандартную

функцию IОResult.

Для очистки ошибки, которая может произойти, вы можете выз-

вать функцию IOResult. Если вы этого не сделаете, и текущим сос-

тоянием является {$I+}, то из-за оставшейся ошибки IOResult сле-

дующая операция ввода-вывода завершится с ошибкой.

Примечание: Если вы пишете программу дл Windows и не

хотите, чтобы Windows обрабатывала за вас ошибки ввода-вы-

вода на диск или другие ошибки ввода-вывода, вызовите

SetErrorMode(1).

Текстовые файлы

В данном разделе описываются операции ввода и вывода, ис-

пользующие файловую переменную стандартного текстового типа. За-

метим, что в Borland Pascal текстовый тип (тип Text) отличается

от символьного типа Char.

При открытии текстового файла внешний файл интерпретируется

особым образом: считается, что он представляет собой последова-

тельность символов, сгруппированных в строки, где каждая строка

заканчивается символом конца строки (end-of-line), который предс-

тавляет собой символ перевода каретки, за которым возможно следу-

ет символ перевода строки.

Для текстовых файлов существует специальный вид операций

чтения и записи (read и write), который позволяют вам считывать и

записывать значения, тип которых отличается от символьного типа

Char. Такие значения автоматически переводятся в символьное

представление и обратно. Например, Read(f,i), где i - переменная

целого типа, приведет к считыванию последовательности цифр, ин-

терпретации этой последовательности, как десятичного числа, и

сохранению его в i.

Как было отмечено ранее, имеются две стандартных переменных

текстового типа - это Input и Оutput. Стандартная файловая пере-

менная Input - это доступный только по чтению файл, связанный со

стандартным файлом ввода операционной системы (обычно это клавиа-

тура), а стандартная файловая переменная Оutput - это доступный

только по записи файл, связанный со стандартным файлом вывода

операционной системы (обычно это дисплей). Перед началом выполне-

ния программы DOS файлы Input и Оutput автоматически открываются,

как если бы были выполнены следующие операторы:

Assign(Input,'');

Reset(Input);

Assign(Output,'');

Rewrite(Output);

Так как Windows не поддерживает непосредственно ориентиро-

ванный на текст ввод и вывод, файлы Input и Output по умолчанию в

прикладной программе Windows не присваиваются, и любая попытка

чтения из этих файлов или записи в них приведет к ошибке вво-

да-вывода. Однако, если прикладная программа использует модуль

WinCrt, то Input и Output будут ссылаться на прокручиваемое текс-

товое окно. Модуль WinCrt содержит всю логику управления, необхо-

димую для эмуляции текстового экрана в операционной среде

Windows, поэтому в прикладной программе, использующей модуль

WinCrt, не требуется никаких приемов программирования, специфи-

ческих для Windows.

Для некоторых из стандартных процедур и функций, список ко-

торых приведен в данном разделе, не требуется явно указывать в

качестве параметра файловую переменную. Если этот параметр опу-

щен, то по умолчанию будут рассматриваться переменные Input или

Output, в зависимости от того, будет ли процедура или функция

ориентирована на ввод или на вывод. Например, Read(х) соответс-

твует Read(Input,х) и Write(х) соответствует Write(Output,х).

Если при вызове одной из процедур или функций из этого раз-

дела вы задаете файл, этот файл должен быть связан с внешним фай-

лов с помощью процедуры Assign и открыт с помощью процедуры

Reset, Rewritе или Append. Если для ориентированной на вывод про-

цедуры или функции вы указываете файл, который был открыт с по-

мощью процедуры Reset, то выведется сообщение об ошибке. Анало-

гично, будет ошибкой задавать для ориентированной на ввод проце-

дуры или функции файл, открытый с помощью процедур Rewrite или

Append.

Нетипизированные файлы

Нетипизированные файлы представляют собой каналы ввода-выво-

да нижнего уровня, используемые в основном для прямого доступа к

любому файлу на диске, независимо от его типа и структуры. Любой

нетипизированный файл описывается словом file без атрибутов. Нап-

ример:

var

DataFile: file;

Для нетипизированных файлов в процедурах Reset и Rewrite до-

пускается указывать дополнительный параметр, чтобы задать размер

записи, использующийся при передаче файла.

По историческим причинам принимаемая по умолчанию длина за-

писи равна 128 байтам. Предпочтительной длиной записи является

длина записи, равная 1, поскольку это единственное значение, ко-

торое позволяет точно отразить размер любого файла (когда длина

записи равна 1, то в файле не могут присутствовать неполные запи-

си, то есть записи с меньшей длиной).

За исключением процедур Read и Write для всех нетипизирован-

ных файлов допускается использование любой стандартной процедуры,

которые допускается использовать с типизированными файлами. Вмес-

то процедур Read и Write здесь используются соответственно проце-

дуры Blockrеаd и BlockWrite позволяющие пересылать данные с высо-

кой скоростью.

Переменная FileMode

Переменная FileMode, определенная в модуле System, задает

код доступа, передаваемый в DOS для типизированных и нетипизиро-

ванных файлов (не для текстовых файлов), когда они открываются с

помощью процедуры Reset.

По умолчанию значение FileMode = 2. При этом допускается

чтение и запись файла. Присваивание FileMode другого значения

приводит к использованию этого режима при всех последующих вызо-

вах Reset.

Примечание: Новые файлы, открываемые с помощью

Rewrite, всегда открываются в режиме чтения/записи, что со-

ответствует Filemode = 2.

Диапазон допустимых значений FileMode зависит от используе-

мой версии DOS. Однако во всех версиях определены следующие режи-

мы:

0: доступ только по чтению

1: Только запись

2: Чтение/запись

В DOS версии 3.х определены дополнительные режимы, которые

касаются в основном совместного использования файлов при работе в

сети (более подробно это описывается в "Руководстве программиста

по DOS").

Устройства в Borland Pascal

В Borland Pascal и в операционной системе DOS внешняя аппа-

ратура, как, например, клавиатура, устройство печати, дисплей,

рассматривается, как устройства. С точки зрения программиста уст-

ройство можно рассматривать, как файл, и с ним можно работать с

помощью того же набора стандартных процедур и функций, что и с

файлом. В Турбо Паскале поддерживается два типа устройств - уст-

ройства DOS и устройства для текстовых файлов.

Устройства DOS

Устройства DOS реализованы с помощью зарезервированных имен

устройств, которые имеют специальный смысл. Устройства DOS пол-

ностью "прозрачны": в Турбо Паскале неизвестно даже, когда файло-

вая переменная связана с устройством, а когда с файлом на диске.

Например, программа:

var

Lst: Text;

begin

Assign(Lst,'LPT1');

Rewrite(Lst);

Writeln(Lst,'Привет...');

Close(Lst);

end;

выведет строку "Привет..." на устройство печати, хотя синтаксис

точно такой же, как если бы она выводилась в файл.

Устройства, реализованные в операционной системе DOS, ис-

пользуются для однозначного ввода или вывода. Таким образом, уст-

ройства в DOS используются обычно для текстовых файлов. В редких

случаях для работы с устройствами DOS может оказаться полезным

использование также нетипизированного файла.

Устройство CОN

Устройство CОN означает консоль, посредством которой выводи-

мая информация пересылается на экран дисплея, а вводимая информа-

ция воспринимается с клавиатуры. Если не было изменено направле-

ние ввода или вывода, стандартные файлы Input и Оutput и все

файлы, которым присвоено пустое имя, ссылаются на устройство CОN.

Вводимая с устройства CОN информация является строчно-ориен-

тированной и используется средствами редактирования строки, кото-

рые описаны в руководстве по DOS. Символы считываются из буфера

строки, а когда буфер становится пустым, вводится новая строка.

При нажатии клавиш Ctrl+Z генерируется символ конца файла

(end-of-file), после которого функция Eоf возвращает значение

Truе.

Устройства LРT1, LРT2 и LРT3

В качестве возможного устройства построчной печати допуска-

ется использование до трех устройств печати. Если присоединено

только одно устройство печати, на него обычно ссылаются, как на

устройство LРT1. Для этого устройства можно также использовать

синоним РRN.

Построчное устройство печати - это устройство, предназначен-

ное только для вывода. При любой попытке использовать процедуру

Reset для открытия файла, связанного с одним из этих устройств,

немедленно генерируется признак конца файла.

Стандартный модуль Рrinter описывает текстовую файловую пе-

ременную с именем Lst и устанавливает ее связь с устройством

LРT1. Чтобы облегчить вывод какой-либо информации из вашей прог-

раммы на устройство печати, включите в оператор uses вашей прог-

раммы модуль Рrinter, а для вывода используйте процедуры

Writе(Lst,...) и Writеln(Lst,...).

Примечание: О печати из программы Windows рассказыва-

ется ниже.

Устройства CОМ1 и CОМ2

Коммуникационными портами (CОМ1 и CОМ2) являются устройства,

представляющие собой два последовательных коммуникационных порта.

Вместо CОМ1 можно использовать синоним AUХ.

Устройство NUL

Нулевое устройство (NUL) игнорирует любую попытку записи на

него и немедленно генерирует признак конца файла при попытки счи-

тывания с этого устройства. Его следует использовать, если вы не

хотите создавать отдельный файл, а в программе требуется указать

имя входного или выходного файла.

В общем случае следует избегать использования устройств DOS

под Windows и применять функции ввода-вывода API Windows. Некото-

рые устройства, такие как CON, не будут правильно работать. Дру-

гие устройства могут работать, но результаты могут оказаться не

теми, что вы ожидаете. Например, если вы используете LPT1, ваша

распечатка может выводиться, прерывая другое задание печати. Поэ-

тому надежнее использовать функции API Windows.

Устройства, предназначенные для текстовых файлов

Устройства, предназначенные для текстовых файлов, использу-

ются для реализации устройств, не поддерживаемых в DOS, или для

того, чтобы сделать доступным набор средств, отличающийся от то-

го, который предусмотрен для аналогичного устройства DOS. Хорошим

примером устройства, предназначенного для текстового файла, явля-

ется окно CRT, реализованное с помощью стандартного модуля Crt.

Оно обеспечивает аналогичный терминалу текстовый экран и позволя-

ет вам создавать прикладные программы со "стандартным вводом-вы-

водом" с минимальными усилиями, используя такие средства, как

цвета и окна.

В отличие от устройств DOS, устройства, предназначенные для

вывода текстовых файлов, не имеют зарезервированных имен. Факти-

чески, у них вообще отсутствуют имена. Вместо этого файл связыва-

ется в устройством с помощью обычной процедуры Assign. Например,

стандартный модуль Crt реализует процедуру AssignCrt, которая

связывает текстовые файлы с устройством CRT.

Устройства, предназначенные для текстовых файлов, использу-

ются для реализации устройств, не поддерживаемых в DOS, или для

того, чтобы сделать доступным набор средств, отличающийся от то-

го, который предусмотрен для аналогичного устройства DOS. Хорошим

примером устройства, предназначенного для текстового файла, явля-

ется устройство CRT, реализованное с помощью стандартного модуля

Crt. Его основной функцией является обеспечение интерфейса с

дисплеем и клавиатурой, аналогично устройству CОN в модуле Dos.

В отличие от устройств DOS, устройства, предназначенные для

вывода текстовых файлов, не имеют зарезервированных имен. Факти-

чески, у них вообще отсутствуют имена. Вместо этого файл связыва-

ется с устройством с помощью обычной процедуры Assign. Например,

стандартный модуль Crt реализует процедуру AssignCrt, которая

связывает текстовые файлы с устройством CRT.

Ввод и вывод с помощью модуля Crt

Примечание: Этот раздел относится только к программам

реального и защищенного режима DOS.

Модуль Crt позволяет использовать все возможности дисплея и

клавиатуры персонального компьютера РС, включая управление режи-

мом экрана, расширенные коды клавиатуры, цвет, окна и звуковые

сигналы.

Модуль Crt реализует ряд мощных программ, предоставляющих

вам полную возможность управления средствами компьютера РС, таки-

ми, как управление режимом экрана, расширенные коды клавиатуры,

цвета, окна, и звуковые сигналы. Модуль Crt может использоваться

только в программах, работающих на персональных компьютерах IBM

РС, РС AT, РS/2 фирмы IBM и полностью совместимых с ними.

Одним из основных преимуществ использования модуля Crt явля-

ется большая скорость и гибкость при выполнении операций работы с

экраном. Программы, не работающие с модулем Crt, выводят на экран

информацию с помощью средств операционной системы DOS, что связа-

но с дополнительными непроизводительными затратами. При использо-

вании модуля Crt выводимая информация посылается непосредственно

в базовую систему ввода-вывода (ВIОS), или, для еще более быстрых

операций, непосредственно в видеопамять.

Использование модуля CRT

Чтобы использовать модуль Crt, его нужно указать в операторе

uses вашей программы:

uses Crt;

При инициализации модуля Crt для того, чтобы можно было об-

ращаться к CRТ, вместо стандартных файлов ввода и вывода DOS наз-

начаются стандартные входные и выходные текстовые файлы. Это со-

ответствует выполнению в начале программы следующих операторов:

AssignCrt(Input); Reset(Input);

AssignCrt(Output); Rewrite(Output);

Это означает, что переопределение входных и выходных файлов

далее не допускается до тех пор, пока для данных файлов не будет

выполнено обратного переназначения и не произойдет переход к

стандартному вводу и выводу с помощью выполнения операторов:

Assing(Input,''); Reset(Input);

Assing(Output,''); RewriteOutput);

Окна CRT

Модуль Crt поддерживает простую, но, тем не менее, мощную

форму использования окон. Процедура Window позволяет вам опреде-

лить в каком-либо месте экрана окно. При записи в это окно оно

ведет себя точно также, как целый экран. При этом остальная часть

экрана остается нетронутой. Другими словами, доступ к экрану вне

окна отсутствует. Внутри окна можно добавлять и удалять строки,

при этом курсор возвращается к правому краю и при достижении кур-

сором нижней строки текст продвигается вверх.

Все координаты экрана, кроме тех, которые используются для

определения окна, относятся к текущему окну. Координата экрана

(1,1) соответствует левому верхнему углу экрана.

По умолчанию окном считается весь экран.

Специальные символы

При записи в выходной файл или в файл, который назначен для

модуля Crt, специальное значение имеют следующие управляющие сим-

волы:

ЪДДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

іСимвол і Название і Описание і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #7 і Звонок і Вызывает звуковой сигнал, издаваемый сі

і і BELL і помощью внутреннего динамика. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #8 іОбратный пробелі Возврат на одну позицию. Вызывает пе-і

і і BS і ремещение курсора влево на одну пози-і

і і і цию. Если курсор уже находится у лево-і

і і і го края текущего окна, то никакихі

і і і действий не производится. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #10 і Перевод строкиі Перемещает курсор на одну строку вниз.і

і і LF і Если курсор уже находится на нижнейі

і і і строке окна, то окно пролистываетсяі

і і і вверх на одну строку. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #13 іВозврат кареткиі Возвращает курсор с левому краю теку-і

і і BS і щего окна. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Ввод строк

При чтении из входного файла (Input) или из текстового фай-

ла, который назначен для модуля Crt, текст вводится по одной

строке. Строка запоминается во внутреннем буфере текстового файла

и когда переменные считываются, то в качестве источника использу-

ется этот буфер. Каждый раз когда буфер становится пустым, вво-

дится новая строка. При вводе строк можно использовать следующие

клавиши редактирования:

ЪДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

іКлавиша редактированияі Описание і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Backsрасе і Удаляет последний введенный символ. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Esс і Удаляет всю вводимую строку. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Enter і Прекращает ввод строки и записываеті

і і метку конца строки (возврат каретки/пе-і

і і ревод строки) в буфере. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Ctrl+S і Действует также, как Backspace. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Ctrl+D і Извлекает один символ из последней вво-і

і і димой строки и выводит его на экран. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Ctrl+F і Восстанавливает на экране последнююі

і і вводимую строку. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Ctrl+Z і Завершает ввод строки и генерирует сим-і

і і вол конца файла. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Сtrl-Z і Генерирует символ конца файла и завер-і

і і шает строку ввода. і

АДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Ctrl+Z будет генерировать конец файла в том случае, если пе-

ременная CheckEOF установлена в True (по умолчанию False).

Для проверки состояния клавиатуры и ввода отдельных символов

под управлением программы используйте функции KeyРressed и

RеаdKey.

Процедуры и функции модуля Crt

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

іФункция/процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і AssignCrt і Назначает текстовый файл для устройстваі

і і CRT. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ClrEоl і Очищает все символы, начиная от позицииі

і і курсора до конца строки, без перемещенияі

і і курсора. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ClrScr і Очищает экран и помещает курсор в верхнемі

і і левом углу. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Dеlау і Выполняет задержку на указанное число мил-і

і і лисекунд. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DelLine і Удаляет строку, на которой находится курсорі

і і и перемещает все следующие строки на однуі

і і строку вверх. Нижняя строка очищается. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GоtоХY і Выполняет позиционирование курсора. Х - этоі

і і горизонтальная позиция, Y - вертикальнаяі

і і позиция. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і НightVideo і Выбирает символы с подсветкой. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і InsLine і Вставляет пустую строку в месте расположе-і

і і ния курсора. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і KeyРrеssеd і Возвращает значение Truе, если клавиша наі

і і клавиатуре нажата и Falsе - в противномі

і і случае. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і LowVidе і Выбирает символы с пониженной яркостью. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і NormVideo і Выбирает символы с нормальной яркостью. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і NoSound і Выключает внутренний динамик. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Sound і Включает внутренний динамик. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TextВаckground і Выбирает фоновый цвет. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TextColor і Выбирает цвет самого символа. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TextМоdе і Выбирает конкретный текстовый режим. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Window і Определяет на экране текстовое окно. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Rеаdкеу і Считывает символ с клавиатуры. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WherеХ і Возвращает координату Х для текущей позицииі

і і курсора, относящуюся к текущему окну. Хі

і і представляет собой горизонтальную позицию.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WhereY і Возвращает координату Y для текущей позицииі

і і курсора, относящуюся к текущему окну. Yі

і і представляет собой вертикальную позицию. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Константы и переменные модуля Crt

В модуле Crt содержится рад констант, облегчающих программи-

рование. Подробно они описываются в Главе 1 "Справочного руко-

водства программиста". Опишем группы этих констант:

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Группа констант і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Константы режима Crt і Графические константы, используе-і

і і мые в качестве параметров проце-і

і і дуры TextMode. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Константы цветов і Константы, используемые для уста-і

і і новки цветов с помощью процедурі

і і TextColor и TextBackGround. і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Например, чтобы найти значение константы, которая позволит

вам выводить текст в программе красным цветом, просмотрите конс-

танты цветов текста и найдите константу Red со значением 4.

В модуле Crt содержатся следующие переменные:

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Переменная і Тип і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і CheckBreak і boolean і Разрешает или запрещает про-і

і і і верку на Ctrl+Break. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і CheckEof і boolean і Разрешает или запрещает сим-і

і і і вол конца файла. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і CheckSnow і boolean і Разрешает или запрещает про-і

і і і верку на помехи. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і DirectVideo і boolean і Разрешает или запрещает пря- і

і і і мой доступ к памяти для про- і

і і і цедур WriteLn и Write. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і LastMode і word і При каждом вызове TextMode і

і і і сохраняет текущий видеоре- і

і і і жим. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і TextAttr і byte і Содержит атрибуты текущего і

і і і выбранного текста. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і WindMin і word і Содержит координаты верхнего і

і і і левого угла текущего окна. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і WindMax і word і Содержит координаты нижнего і

і і і правого угла текущего окна. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Ввод и вывод с помощью модуля WinCrt

Примечание: Этот раздел относится только к программам

Windows.

Модуль WinCrt реализует аналогичный терминалу текстовый эк-

ран в окне. С помощью модуля WinCrt вы можете легко создавать

программы, использующие стандартные процедуры Read. ReadLn, Write

и WriteLn для выполнения операций ввода и вывода (так же, как в

обычной прикладной программе, работающей в текстовом режиме). Мо-

дуль WinCrt содержит все алгоритмы, управляющие эмуляцией тексто-

вого экрана в программной среде Windows. Если ваша программа ис-

пользует модуль WinCrt, вам не потребуется писать "специфический

для Windows" исходный код.

Использование модуля WinCrt

Чтобы использовать модуль WinCrt, нужно просто указать в ва-

шей программе оператор uses, как и при использовании любого дру-

гого модуля.

uses WinCrt;

По умолчанию стандартные текстовые файлы Input и Output, оп-

ределенные в модуле System, не присваиваются, и все обращения к

процедурам Read, Readln, Write или Writeln без указания файловой

переменной приводят к ошибке ввода-вывода. Однако, когда програм-

ма использует модуль WinCrt, код инициализации данного модуля

присваивает Input и Output стандартные текстовые файлы, чтобы

ссылаться на окно, эмулирующее текстовый экран. Это соответствует

выполнению в начале программы следующих операторов:

AssignWinCrt(Input); Reset(Input);

AssignWinCrt(Output); Rewrite(Output);

Когда в программе выполняются процедуры Readln, Read, Write

или Writeln, в оперативной области Windows открывается окно CRT.

По умолчанию заголовком окна CRT будет полное имя маршрута файла

.EXE программы. Когда программа завершает работу (управление дос-

тигает конечного зарезервированного слова end), заголовок окна

CRT изменяется на "(Inactive nnnnn)", где nnnnn - заголовок окна

в его активном состоянии.

Заметим, что хотя программа и завершила работу, окно остает-

ся на экране, благодаря чему пользователь может проверить вывод

программы. Аналогично другим прикладным программам Windows, прог-

рамма не завершается полностью, пока пользователь не закроет ок-

но.

Более полно управлять жизненным циклом окна CRT вам позволя-

ют подпрограммы InitWinCrt и DoneWinCrt. При обращении к первой

из них без ожидания первого вызова процедур Readln, Read, Write

или Writeln немедленно создается окно CRT. Аналогично, обращение

к DoneWinCrt немедленно уничтожает окно CRT, не ожидая, пока его

закроет пользователь.

Окно CRT представляет собой прокручиваемое "панорамное" окно

на виртуальном текстовом экране. По умолчанию виртуальный экран

имеет размеры 80 столбцов на 25 строк, но реальный размер окна

CRT может быть меньше. Если этот размер меньше, пользователь для

перемещения области окна по текстовому экрану большего размера

может использовать полосы прокрутки окна или клавиши управления

курсором. Это особенно полезно для "обратной прокрутки" и провер-

ки ранее написанного текста. По умолчанию панорамное окно отсле-

живает курсор текстового экрана. Другими словами, панорамное окно

автоматически прокручивается, чтобы обеспечить постоянную види-

мость курсора. Установив переменную AutoTracking в значение

False, вы можете запретить средство автоматической прокрутки.

Размеры виртуального экрана определяются переменной

ScreenSize. Присвоив этой переменной новые размерности перед тем,

как ваша программа создает окно CRT, вы можете изменить размеры

виртуального экрана. Когда окно создается, в динамически распре-

деляемой памяти выделяется буфер экрана. Размер этого буфера ра-

вен произведению ScreenSize.Y на ScreenSize.Y и не может превы-

шать 65520 байт. Ответственность за присваивания значений этим

переменным возлагается на вас (они не должны превышать указанную

границу). Если, например, вы присвоите ScreenSize.X значение 64,

то наибольшим допустимым значением для ScreenSize.Y будет 1023.

В любой момент в процессе выполнения программы, использующей

модуль WinCrt, пользователь может прервать выполнение, выбрав в

меню Control (Управление) окна CRT команду Close (Закрытие),

дважды щелкнув кнопкой "мыши" в рамке меню Control или нажав кла-

виши Alt+F4. Аналогично, в любой момент для прерывания прикладной

программы пользователь может нажать Ctrl+C или Ctrl+Break, при

этом окно переводится в неактивное состояние. Установив перемен-

ную CheckBreak в значение False, вы можете запретить эту возмож-

ность.

Специальные символы

При записи в выходной файл (Output) или в файл, который наз-

начен для окна CRT, специальное значение имеют следующие управля-

ющие символы:

ЪДДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

іСимвол і Название і Описание і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #7 і Звонок і Вызывает звуковой сигнал, издаваемый сі

і і BELL і помощью внутреннего динамика. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #8 іОбратный пробелі Возврат на одну позицию. Вызывает пе-і

і і BS і ремещение курсора влево на одну пози-і

і і і цию. Если курсор уже находится у лево-і

і і і го края текущего окна, то никакихі

і і і действий не производится. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #10 і Перевод строкиі Перемещает курсор на одну строку вниз.і

і і LF і Если курсор уже находится на нижнейі

і і і строке окна, то окно пролистываетсяі

і і і вверх на одну строку. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #13 іВозврат кареткиі Возвращает курсор с левому краю теку-і

і і CR і щего окна. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Ввод строк

При чтении из входного файла (Input) или из текстового фай-

ла, который назначен для окна CRT, текст вводится по одной стро-

ке. Строка запоминается во внутреннем буфере текстового файла и

когда переменные считываются, то в качестве источника использует-

ся этот буфер. Каждый раз когда буфер становится пустым, вводится

новая строка.

При вводе строк в окне CRT можно использовать следующие кла-

виши редактирования: Вacksрасе - удаляет последний введенный сим-

вол, Esс - удаляет всю вводимую строку, Enter - прекращает ввод

строки и записывает метку конца строки (возврат каретки/перевод

строки) в буфере. Кроме того, можно использовать клавиши Сtrl+Z,

которые генерируют символ конца файла только в том случае, если

переменная CheckEof установлена в значение Truе (по умолчанию ей

присвоено значение Falsе). Нажатие Ctrl+Z также завершает строку

ввода и генерирует маркер конца строки.

Для проверки состояния клавиатуры и ввода отдельных символов

под управлением программы используйте функции KeyРressed и

Rеаdkey.

Процедуры и функции

В следующих таблицах перечисляются процедуры и функции, ко-

торые можно найти в модуле WinCrt.

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура/функция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і AssignCrt і Назначает текстовый файл для окна CRT. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ClrEоl і Очищает все символы, начиная от позицииі

і і курсора до конца строки, без перемещенияі

і і курсора. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ClrScr і Очищает экран и помещает курсор в верхнемі

і і левом углу. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і CursorTo і Перемещает курсор в точку на виртуальномі

і і экране с заданными координатами. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DoneWinCrt і Уничтожает окна CRT. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GоtоХY і Выполняет позиционирование курсора. Х -і

і і это горизонтальная позиция, Y - вертикаль-і

і і ная позиция виртуального экрана. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і InitWinCrt і Инициализирует окно CRT. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і KeyРrеssеd і Возвращает значение Truе, если клавиша наі

і і клавиатуре нажата и Falsе - в противном і

і і случае. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ReadBuf і Считывает из окна CRT строку. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RеаdKеу і Считывает символ с клавиатуры. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ScrollTo і Прокручивает окно CRT, чтобы видна былаі

і і точка с заданными координатами. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TrackCursor і Прокручивает окно CRT, чтобы курсор былі

і і видимым. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WherеХ і Возвращает координату Х для текущей позициі

і і курсора, относящуюся к текущему окну. Х і

і і представляет собой горизонтальную і

і і позицию. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WhereY і Возвращает координату Y для текущей пози-і

і і ции курсора, относящуюся к текущему окну.і

і і Y представляет собой вертикальную позицию.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WriteBuf і Выводит в окно CRT блок символов. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WriteChar і Выводит в окно CRT отдельный символ. і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Переменные модуля WinCrt

В модуле WinCrt описывается несколько переменных:

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Переменная і Тип і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WindowOrg і Используемое по умолчанию размещение поз-і

і і воляет Windows выбирать подходящее распо-і

і і ложение окна CRT. Вы можете изменить на-і

і і чальное значение, присвоив перед созданиемі

і і окна CRT новые значения координатам X и Y.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WindowSize і Используемый по умолчанию размер позволяеті

і і Windows выбирать подходящий размер окнаі

і і CRT. Вы можете изменить начальный размер,і

і і присвоив перед созданием окна CRT новыеі

і і значения координатам X и Y. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і ScreenSize і По умолчанию экран имеет размер 80 столб-і

і і цов на 25 строк. Присвоив другие значенияі

і і координатам X и Y ScreenSize перед созда-і

і і нием окна CRT, вы можете изменить исполь-і

і і зуемый по умолчанию размер экрана CRT.і

і і Значение, получаемое при произведенииі

і і ScreenSize.X на ScreenSize.Y, не должноі

і і превышать 65520. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Cursor і Верхний левый угол соответствует координа-і

і і те (0,0). Cursor - это переменная, доступ-і

і і ная только по чтению. Присваивать ей зна-і

і і чения нельзя. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Origin і Содержит текущую позицию курсора на вирту-і

і і альном экране - координаты ячейки символа,і

і і выводимой в левом верхнем углу окна CRT.і

і і Отсчитывается с 0. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і InactiveTitle і Указывает на завершающуюся нулем строку,і

і і используемую для создания заголовка неак-і

і і тивного окна CRT. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і AutoTracking і Разрешает или запрещает автоматическуюі

і і прокрутку окна для отслеживания видимогоі

і і курсора. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і CheakBreak і Переменная ChеckEOF разрешает или запреща-і

і і ет символ конца файла. Если переменнаяі

і і ChеckEOF имеет значение Truе, то когдаі

і і чтение производится из файла, назначенногоі

і і окну CRT, при нажатии клавиш Ctrl+Z гене-і

і і рируется символ конца файла. Когда пере-і

і і менная ChеckEOF имеет значение False приі

і і нажатии клавиш Ctrl+Z никаких действий неі

і і выполняется. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і CheckEof і Переменная CheckВrеak разрешает или запре-і

і і щает проверки ситуации Ctrl+Break. Когдаі

і і переменная ChеckВrеak принимает значениеі

і і Truе, нажатие пользователем клавиш Alt+F4,і

і і выбор пользователем команды Close в менюі

і і Control окна CRT или двойное нажатие кноп-і

і і ки "мыши" в управляющей рамке меню Controlі

і і этого окна приведет к принудительному за-і

і і вершению работы прикладной программы приі

і і следующей операции вывода на экран дисп-і

і і лея, которую выполнит эта программа. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і WindowTitle і Определяет заголовок окна CRT. По умолча-і

і і нию используется значение, равное полномуі

і і имени маршрута файла .EXE программы. і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Печать из программы Windows

Модуль WinPrn позволяет вам печатать текст из программы

Windows. Чтобы использовать WinPrn, укажите этот модуль в опера-

торе uses вашей программы;

uses WinPrn;

Перед началом печати вам нужно присвоить принтеру переменную

типа текстового файла. Сделать это можно двумя путями - назначив

используемый по умолчанию принтер или выбрав конкретный принтер,

драйвер и порт. Для вывода на используемый по умолчанию принтер

вызовите функцию AssignPrn. Любая запись в присвоенную файловую

переменную типа текстового файла приведет к выводу на принтер.

Изменение заголовков

По умолчанию администратор печати Windows будет выводить все

задания печати через WinPrn без заголовков. С помощью процедуры

TitlePrn (вызвав ее вслед за Rewrite) вы можете задать заголовок,

например:

AssignDefPrn(Prn);

TitlePrn(Prn, 'Конец годового отчета');

Rewrite(Prn);

задает для вывода используемый по умолчанию принтер и изменяет

заголовок на "Конец годового отчета", выводя его на этот принтер.

Если TitlePrn вызывается после Rewrite, то никакого эффекта это

не вызывает.

Изменение шрифтов

WinPrn использует назначенный по умолчанию шрифт, который

возвращается драйвером устройства. Чтобы изменить шрифт, вызовите

функцию SetPrnFont, передав ей описатель используемого шрифта.

SetPrnFont возвращает текущий используемый шрифт. Возвращаемый

шрифт можно использовать для будущего вызова SetPrnFont или для

передачи его DeleteObject. Приведем пример программы, демонстри-

рующей изменение шрифта:;

program Test;

uses WinTypes, WinProcs, WinCrt, WinPrn;

var

Prn: Text;

OldFont: HFont;

begin

Writeln('Печать...');

AssingDefPrn(Prn);

Rewrite(Prn);

Rewrite(Prn, 'Некоторый текст');

OldFont := SetPrnFont(Prn, CreateFont(100,0,0,0,0,0,0,0,1,

Out_Default_Precis,Clip_Default_Precis,

Default_Quality,ff_Roman,nil);

Writeln(Prn,' Произвольный текст новым шрифтом');

DeleteObject(SetPrnFont(Prn, OldFont));

Writeln(Prn, ' Возврат к старому шрифту');

Close(Prn);

Writeln('Выполнено');

end.

Остановка задания печати

Чтобы остановить задание печати, запущенное с помощью

WinPrn, вызовите процедуру AbortPrn. Это приведет к прекращению

печати, сбросу устройства и подготовки его к выводу нового зада-

ния печати.

Специальные символы

Когда ваша программа использует модуль WinPrn, следующие

символы будут иметь специальный смысл:

ЪДДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

іСимвол і Название і Описание і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #9 і Табуляция і Начинает печать символов со следующейі

і і TAB і позиции табуляции, которая отстоит оті

і і і предыдущей позиции табуляции на 8-і

і і і кратную среднюю ширину шрифта. і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #10 і Перевод строкиі Начинает печать с новой строки. і

і і LF і і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #12 іПеревод форматаі Принудительный перевод страницы. і

і і FF і і

ГДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і #13 іВозврат кареткиі Начинает печать с начала новой строки.і

і і CR і і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры и функции модуля WinPrn

Процедуры и функции модуля WinPrn перечислены в следующей

таблице:

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Процедура/функция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і AbortPrn і Прекращает печать задания, отбрасы-і

і і вая все нераспечатанные данные. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і AssignPrn і Присваивает принтеру файл. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і AssingDefPrn і Присваивает файл используемому поі

і і умолчанию принтеру. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetPrnFont і Начинает печать файла с выбраннымі

і і шрифтом. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TitlePrn і Выводит заголовок печатаемого фай-і

і і ла. і

АДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Драйверы устройств для текстовых файлов

Borland Pascal позволяет вам определить ваши собственные

драйверы устройств для текстовых файлов. Драйвер устройства для

текстовых файлов представляет собой набор из четырех функций, ре-

ализующих полный интерфейс между файловой системой Borland Pascal

и каким-либо устройством.

Этими четырьмя функциями, с помощью которых определяется лю-

бой драйвер устройства, являются функции Open, InOut, Flush и

Close. Заголовок каждой функции имеет следующий вид:

function DeviceFunc(var F: TextRec) integer

где TехtRес (или TTextRec для Windows) - тип записи текстового

файла, который определяется в Главе 21. Чтобы в функции использо-

вался дальний тип вызова, каждая из них должна компилироваться с

директивой {$F+}. Значение, возвращаемое каждой функцией, предс-

тавляющей собой интерфейс с устройством, становится значением,

возвращаемым функцией IOResult. Возвращаемое значение 0 свиде-

тельствует об успешном завершении операции.

Для того, чтобы связать функцию, осуществляющую интерфейс с

устройством, с конкретным файлом, нужно написать специальную про-

цедуру Assign (аналогичную процедуре AssignCrt в модуле Crt или

WinCrt). Эта процедура должна присваивать адреса четырех функций,

осуществляющих интерфейс с устройствами, четырем указателям на

функции в переменной текстового файла. В придачу к этому вы долж-

ны сохранить системную константу fmClosed в поле Моdе, записать

размер буфера текстового файла в переменную BufSize, сохранить

указатель буфера текстового файла в переменной BufPtr и очистить

строку Nаме.

Предположим, например, что именами четырех функций, реализу-

ющих интерфейс с устройством, являются функции DevOpen, DevInOut,

DevFlush, DevClose и Assign.

Тогда процедура Assing может выглядеть следующим образом:

procedure AssignDev(var F: Text);

begin

with TextRec(F) do

begin

mode := fmClosed;

BufSize := SizeOf(Buffer);

BufPtr := @Buffer;

OpenFunc := @DevOpen;

InOutFunc := @DevInOut;

FlushFunc := @DevFlush;

CloseFunc := @DevClose;

Name[0] := #0;

end;

end;

Для хранения пользовательской информации в функции, реализу-

ющей интерфейс с устройством, может использоваться поле записи

UserData. Это поле не изменяется файловой системой Borland

Pascal.

Функция Open

Функция Open вызывается стандартными процедурами Rеset,

Rеwritе и Appеnd для открытия текстового файла, связанного с уст-

ройством. Чтобы отметить была ли функция Open вызвана из процеду-

ры Rеset, Rеwritе или Appеnd, на входе поле Моdе содержит значе-

ние fmInput, fmOutput или fmInOut.

В соответствии со значением Моdе функция Open подготавливает

файл для ввода или вывода. Если в Моdе указывается FmInOut (ука-

зывая, что функция Оpеn была вызвана из Appеnd), то перед возвра-

том управления функцией Оpеn это значение должно быть изменено на

fmOutput.

Функция Opеn всегда вызывается перед любой другой функцией,

реализующей интерфейс с устройством. По этой причине функция

Assign инициализирует только поле OpеnFunc, откладывая инициали-

зацию оставшихся векторов до завершения выполнения функции Opеn.

Основываясь на значении поля Моdе функция Opеn может установить

указатели как для функций, ориентированных на ввод, так и для

функций, ориентированных на вывод. Это позволяет избежать опреде-

ления текущего режима в функциях InOut, Flush и Close.

Функция InOut

Всякий раз, когда требуется ввод с устройства или вывод на

него, функциями Readln, Read, Write, Writeln, Page, Eof, SeekEof,

SeekEoln и Close вызывается функция InOut.

Когда в поле Моdе установлено значение fnInput, функция

InOut считывает символы (объем ввода задается переменной BufSize)

в BufPtr^ и возвращает число считанных символов в BufEnd, а также

записывает 0 в BufPos. Если функция InOut в результате запроса на

ввод возвращает в BufEnd значение 0, то переменная Eоf для файла

принимает значение Truе.

Когда в поле Моdе установлено значение fnOutput, функция

InOut записывает символы, количество которых определяется пере-

менной BufРоs, из BufPtr^ и возвращает в BufРоs значение 0.

Функция Flush

Функция Flush вызывается в конце выполнения каждой функции

Rеаd, Write, Rеаdln или Writeln. Она может также сбрасывать буфер

текстового файла.

Если в поле Моdе находится fmInput, функция Flush для того,

чтобы отбросить оставшиеся (несчитанные) символы в буфере, может

записать 0 в BufPos и BufEnd. Это средство используется редко.

Если в поле Моdе находится fnOutput, то функция Flush может

записать содержимое буфера, в точности таким же образом, как

функция InOut. Этим обеспечивается, что выведенный на устройство

текст появится на устройстве немедленно. Если функция Flush не

выполняет никаких действий, текст не будет выведен на устройство,

пока буфер не станет полным, или файл не будет закрыт.

Функция Clоsе

Функция Clоsе вызывается стандартной процедурой Clоsе для

закрытия связанного с устройством текстового файла. (Процедуры

Rеsеt, Rеwritе, Appеnd также вызывают функцию Clоsе, если файл,

который они открывают, уже был открыт.) Если в поле Моdе находит-

ся fmOut, то перед вызовом функции Clоsе файловая система Турбо

Паскаля обращается к функции InOut. Это гарантирует вывод на уст-

ройство всех символов.


Глава 15. Использование сопроцессора 80x87

В Borland Pascal вы можете работать с двумя типами чисел -

целыми (короткими целыми - Shortint, целыми - Integer, длинными

целыми - Longint, целыми длиной в байт - Byte, целыми длиной в

слово - Word) и вещественными (вещественными - Real, вещественны-

ми одинарной точности - Single, вещественными двойной точности -

Double, повышенной точности - Extended, сложными - Comp). Вещест-

венные числа называют также числами с плавающей точкой (плавающей

запятой). Для облегчения работы с целыми числами создан процессор

8086, но для работы с вещественными числами на этом процессоре

затрачивается гораздо больше времени и усилий. Для семейства про-

цессоров 8086 предназначено соответствующее семейство вспомога-

тельных специализированных процессоров для математических вычис-

лений (сопроцессоров) 80x87.

Процессор 80x87 - это специальный сопроцессор для обработки

чисел, который может входить в состав вашего компьютера РС. С по-

мощью него операции с плавающей точкой выполняются очень быстро.

Поэтому если вы собираетесь использовать большой объем вычислений

с плавающей точкой, то вам, вероятно, понадобится сопроцессор.

Borland Pascal построен таким образом, что он обеспечивает

оптимальное выполнение операций с плавающей точкой независимо от

наличия сопроцессора 80x87.

* Для программ, работающих на компьютере РС, независимо от

того, оснащен он сопроцессором 80x87 или нет, в Borland

Pascal предусмотрено использование вещественных чисел и

соответствующая библиотека программ, которые предназначены

для выполнения операций с плавающей точкой. Числа вещест-

венного типа занимают 6 байт памяти. При этом обеспечива-

ется представление чисел в диапазоне от 2.9х10^-39 до

1.7х10^38 с 11-12 значащими цифрами. Программы в библиоте-

ке программ для работы с плавающей точкой оптимизированы

по скорости и по размеру и используют самые новейшие

средства процессора 80x87.

* Если вы пишете программы, использующиеся только на компь-

ютерах, оснащенных сопроцессором 80x87, то вы можете ука-

зать Borland Pascal на необходимость получения выполняемо-

го кода, в котором используется плата процессора 80x87.

Это даст вам возможность использования четырех дополни-

тельных типов вещественных чисел (одинарной и двойной точ-

ности, повышенной точности, сложного типа) и расширенный

диапазон представления чисел с плавающей точкой - от

1.9х10^-4951 до 1,1х10^4943 с 19-20 значащими цифрами.

С помощью директивы компилятора $N или параметра меню

OptionsіCоmpiler (ПараметрыіКомпилятор) 80x87/80287 можно перек-

лючаться между различными моделями генерации кода с плавающей

точкой. По умолчанию используется состояние {$N-}. В этом состоя-

нии компилятор использует 6-байтовую библиотеку с плавающей точ-

кой, что позволяет вам работать только с переменными типа Real. В

состоянии {$N+} компилятор генерирует код для сопроцессора 80x87,

что дает вам дополнительную точность и доступ к 4 дополнительным

вещественным типам.

В Windows при компиляции с режимом числовой обработки, то

есть с директивой {$N+}, убедитесь, что в вашей системе можно

найти библиотеку эмуляции Windows 8087 - WIN87EM.DLL. Эта библио-

тека обеспечивает необходимый интерфейс между сопроцессором

80х87, Windows и вашей прикладной программой. Если сопроцессор

80х87 в вашей системе отсутствует, то библиотека WIN87EM.DLL бу-

дет эмулировать его программно. Эмуляция существенно замедляет

работу по сравнению с реальным сопроцессором 80х87, но обеспечи-

вает выполнение вашей прикладной программы на любой машине.

В реальном или защищенном режиме DOS, даже если у вас нет

сопроцессора 8087, вы можете указать Borland Pascal, что нужно

включить библиотеку исполняющей системы, которая эмулирует ариф-

метический сопроцессор 8087. В случае наличия сопроцессора 8087

он используется. Если сопроцессор отсутствует, его работа эмули-

руется библиотекой исполняющей системы (за счет некоторой потери

скорости работы программы).

Для разрешения и запрещения эмуляции сопроцессора 8087 ис-

пользуются директива компилятора $E и параметр Emulation (Эмуля-

ция) меню OptionsіCompiler (ПараметрыіКомпилятор). По умолчанию

используется состояние {$E+}. В этом состоянии в программу авто-

матически включается полная эмуляция сопроцессора 8087. В состоя-

нии {$E-} используется существенно меньшая часть библиотеки с

плавающей точкой, а полученный в результате файл .EXE будет рабо-

тать только на машинах с сопроцессором 8087.

В приложении Windows директива компилятора $E не действует.

Не действует она также в модуле. Более того, если программа ком-

пилировалась с директивой {$N-}, а все модули программы компили-

ровались с директивой {$N+}, то библиотека исполняющей системы

для сопроцессора 8087 не требуется, и директива компилятора $E

игнорируется.

Прикладной программе Windows не требуется библиотека испол-

няющей системы 80x87. Вместо этого ей нужно поддерживающая библи-

отека WIN87EM.DLL, поставляемая с Windows, которая обеспечивает

необходимый интерфейс между вашей прикладной программой, Windows

и сопроцессором. Таким образом, в Windows даже при наличии в ва-

шей системе сопроцессора 80х87 для выполнения программ, скомпили-

рованных в состоянии {$N+}, должна присутствовать библиотека эму-

ляции WIN87EM.DLL (данная библиотека - это часть Windows, а не

Borland Pascal). При отсутствии сопроцессора WIN87EM.DLL будет

эмулировать его операции программным путем, что замедляет выпол-

нение программы и не гарантирует, что использующая сопроцессор

80x87 программа сможет работать на любой машине.

Когда вы запускаете прикладную программу Windows, cкомпили-

рованную в состоянии {$N+}, убедитесь, что она может найти в сис-

теме файл WIN87EM.DLL.

Когда вы выполняете компиляцию в режиме кода 80х87 (директи-

ва {$N+}), то возвращаемые подпрограммы модуля Systем (Sqrt, Рi,

Sin и т.д.) значения представляют собой не вещественные числа, а

числа типа Extended (с повышенной точностью).

{$N+}

begin

Writeln(Pi); { 3.14159265358979E+0000 }

end.

{$N-}

begin

Writeln(Pi); { 3.1415926536E+00 }

end.

В оставшейся части данной главы обсуждаются специальные воп-

росы, касающиеся использования процессора 80x87 в программах

Borland Pascal.

Типы данных процессора 80x87

В дополнение к вещественному типу для программ, использующих

средства процессора 80x87, предусматривается четыре новых вещест-

венного типа:

1. Тип с одинарной точностью Single, представляющий собой

наименьший формат, который вы можете использовать для

чисел с плавающей точкой. Он занимает 4 байта памяти

обеспечивает диапазон представления чисел от 1.5х10^-45

до 3.4х10^48 с 7-8 значащими цифрами.

2. Тип с двойной точностью Double, занимающий 8 байт памяти

и обеспечивающий представление чисел в диапазоне от

5.0х10^-334 до 1.7х10^308 с 15-16 значащими цифрами.

3. Тип с повышенной точностью Extended представляет собой

наибольший формат представления чисел с плавающей запя-

той, обеспечиваемый процессором 8087. Он занимает 10

байт памяти и обеспечивает диапазон представления чисел

от 1.9х10^-4952 до 1.1х10^4932 с 19-20 значащими цифра-

ми. Любые арифметические операции, в которых участвуют

числа вещественного типа, выполняются с точностью и диа-

пазоном представления, соответствующими типу с повышен-

ной точностью.

4. Числа сложного типа Comp используются для предварительно

объединенных значений в 8 байтах памяти, обеспечивая при

этом диапазон представления от -2^63+1 до 2^63-1, что

составляет приблизительно от -9.2х10^18 до 9.2х10^18.

Сложный тип можно сравнить с длинным целым типом (двой-

ная точность), но он считается вещественным типом, пос-

кольку при операциях с числами этого типа используется

сопроцессор 8087. Сложный тип хорошо подходит для предс-

тавления значений денежных единиц, представляющих собой

сотни и тысячи, которые используются в прикладных ком-

мерческих программах.

Независимо от того, используете вы сопроцессор 80x87 или

нет, 6-битовый вещественный тип является допустимым. Таким обра-

зом, при переходе к использованию сопроцессора 80 x87 вам не пот-

ребуется изменять исходный текст программы, и вы можете использо-

вать файлы данных, созданные программами, которые работают с

программно обеспечиваемыми операциями с плавающей точкой.

Отметим, однако, что аппаратные вычисления с переменными ве-

щественного типа выполняются несколько медленнее, чем с перемен-

ными другого типа. Это связано с тем, что сопроцессор 80x87 не

может непосредственно обрабатывать вещественный формат. Вместо

этого, перед выполнением операций, для преобразования веществен-

ных значений в числа с повышенной точностью требуются обращения к

библиотечным программам. Если вы заинтересованы в максимальной

скорости выполнения и не собираетесь использовать свою программу

на системах без сопроцессора 80x87, то возможно вы захотите ис-

пользовать вещественный тип с одинарной точностью, вещественный

тип с двойной точностью, вещественный тип с повышенной точностью

и сложный типы явным образом.

Арифметические операции с повышенной точностью

При использовании сопроцессора 80x87 тип с повышенной точ-

ностью Extended является основой всех операций с плавающей точ-

кой. В Турбо Паскале тип с повышенной точностью используется для

представления всех нецелых числовых констант, а также при вычис-

лении всех выражений нецелого типа. Например, в следующих опера-

циях присваивания все правые части выражений будут вычисляться,

как выражения с повышенной точностью, а затем их тип будет преоб-

разован к типу соответствующей левой части:

{$N+}

var

X, AA, B, C : real;

begin

X := (B + Sqrt(B*B - A*C))/A;

end;

Borland Pascal выполняет вычисления с точностью и диапазоном

представления чисел, соответствующими типу с повышенной точ-

ностью, без дополнительных усилий программиста. Дополнительная

точность приводит к меньшим ошибкам округления, а дополнительный

диапазон означает, что ситуации переполнения и потери значимости

будут встречаться в программах реже.

Вы можете обойтись и без дополнительных автоматических воз-

можностей вычислений с повышенной точностью Borland Pascal. Нап-

ример, описать переменные, использующиеся для промежуточных вы-

числений, как переменные с повышенной точностью. В следующем при-

мере вычисляется сумма произведений:

var

Sm : single;

X,Y array[1..100] of single;

I : integer;

T : extended; { для промежуточных результатов }

begin

T := 0.0;

for I := 1 to 100 do T := T + X[I] * Y[I]

Sum := T;

end;

Если бы переменная T была описана, как переменная с одинар-

ной точностью, то при каждом цикле операции присваивания для пе-

ременной T были бы выполнены с ошибкой округления и ограничения-

ми, соответствующими одинарной точности. Но, поскольку переменная

T является переменной с повышенной точностью, то все ошибки ок-

ругления (кроме операции, при которой значение переменной T прис-

ваивается переменной Suм) имеют ограничения, соответствующие по-

вышенной точности. Меньшие ошибки округления означают более точ-

ный результат.

Для значений формальных параметров и результата функции вы

также можете задать повышенную точность. Это поможет избежать не-

нужных преобразований типов чисел, приводящих к потере точности.

Например:

function Area(Radius: extended): extended;

begin

Area := Pi * Radius * Radius;

end;

Сравнение вещественных чисел

Поскольку значения вещественного типа являются приблизитель-

ными, результат сравнения значений различного вещественного типа

не всегда можно предсказать. Например, если Х - переменная ве-

щественного типа с одинарной точностью, а Y - переменная вещест-

венного типа с двойной точностью, то результатом выполнения сле-

дующих операторов будет значение False:

X := 1/3;

Y := 1/3;

Writeln(X = Y);

Причина этого состоит в том, что Х имеет точность только до

7-8 цифр, а Y - точность до 15-16 цифр, и когда оба значения пре-

образуются к типу с повышенной точностью, то после первых 7-8

цифр остальные цифры будут различаться. Аналогично, результатом

выполнения операторов:

X := 1/3;

Writeln(X = 1/3);

будет значение False, результат 1/3 в операторе Writeln вычисля-

ется с точностью до 20 значащих цифр.

Стек вычислений сопроцессора 80x87

У сопроцессора 80x87 имеется внутренний стек вычислений, ко-

торый может быть глубиной до восьми уровней. Доступ к значению,

находящемуся в стеке сопроцессора 80x87 осуществляется намного

быстрее, чем доступ к переменной в памяти, поэтому для достижения

максимально возможной производительности в Borland Pascal внут-

ренний стек сопроцессора 80x87 используется для хранения времен-

ных результатов и для передачи параметров процедурам и функциям.

Теоретически, слишком сложные выражения вещественного типа

могут вызвать переполнение стека сопроцессора 80x87. Однако этого

не может случиться, поскольку для этого потребовалось бы, чтобы в

выражении получалось более восьми промежуточных результатов.

Более весомая опасность таится во вложенных вызовах функций.

Если такие конструкции составлены некорректно, то они, вполне ве-

роятно, могут привести к переполнению стека сопроцессора 80x87.

Рассмотрим, следующую функцию, в которой с помощью рекурсии

вычисляются числа Фибоначчи:

function Fib(N: integer): extended;

begin

if N = 0 then

Fib := 0.0

else

if N = 1 then

Fib := 1.0

else

Fib := Fib(N-1) + Fib(N-2);

end;

Обращение к данной версии процедуры Fib приведет к перепол-

нению стека сопроцессора 80x87, так как значений N больше, чем 8.

Причина заключается в том, что последний оператор присваивания

требует временного сохранения результата выполнения процедуры Fib

(N-1) в стеке сопроцессора 80x87. Каждое рекурсивное обращение

выделяется одна ячейка стека и на девятом обращении произойдет

переполнение стека. Корректной конструкцией в этом случае будет:

function Fib(N : integer) : extended;

var

F1,F2 : Extended;

begin

if N = 0 then

Fib := 0.0

else

if N = 1

then Fib := 1.0

else

begin

F1 := Fib(N-1); F2 := Fib(N-2);

Fib := F1 + F2;

end;

end;

Временные результаты теперь сохраняются в переменных, для

которых отводится стек процессора 8086. (Стек процессора 8086 ко-

нечно тоже может переполниться, но это обычно требует гораздо

большего числа рекурсивных вызовов).

Запись вещественных чисел при использовании сопроцессора 80x87

Если была указана директива {$N+}, то стандартные процедуры

Write и Writeln, чтобы обеспечить представление в расширенном ди-

апазоне, выводят в строке с десятичными числами с плавающей точ-

кой четыре цифры для показателя степени вместо двух. Аналогично,

стандартная процедура Str при выборе формата с плавающей точкой

возвращает значение показателя степени, состоящее из четырех

цифр.

Модули, в которых используется сопроцессор 80x87

Модули, в которых используется сопроцессор 80x87, могут вы-

зываться другими модулями или программами только в том случае,

если эти модули или программы были скомпилированы с директивой

{$N+}. То, что модуль использует сопроцессор 80x87, определяется

наличием в нем инструкций сопроцессора 80x87, а не директивой $N

во время компиляции. Это позволяет компилятору быть более "снис-

ходительным", когда вы случайно компилируете модуль (в котором

используется сопроцессор 80x87), не указав директиву {$N+}.

Когда вы выполняете компиляцию в режиме кода 80х87 (директи-

ва {$N+}), то возвращаемые подпрограммами модуля Systем (Sqrt,

Рi, Sin и т.д.) значения представляют собой не вещественные чис-

ла, а числа типа Extended (с повышенной точностью).

Распознавание сопроцессора 80х87 в программах DOS

Исполняющая библиотека Borland Pascal, встроенная в вашу

программу (скомпилированную с директивой {$N+}) включает в себя

код инициализации, который автоматически распознает наличие в

системе микросхемы сопроцессора 8087. Если сопроцессор 8087 име-

ется, то программа будет его автоматически использовать. В случае

же его отсутствия программа будет использовать эмулирующую библи-

отеку исполняющей системы. Если программа компилировалась с ди-

рективой {$E-} и по время начала ее работы сопроцессор не обнару-

живается, то программа завершает работу с сообщением Numeric

coprocessor required ("Требуется сопроцессор арифметических вы-

числений").

Есть несколько случаев, когда вы возможно захотите изменить

такую принятую по умолчанию логику автоматического обнаружения

сопроцессора. Например, в вашей системе может присутствовать соп-

роцессор 8087, но вы захотите проверить, как будет работать прог-

рамма, предназначенная для функционирования на системах без соп-

роцессора. Или же потребуется запустить вашу программу на

системе, совместимой с компьютером РС, но на этой системе при ра-

боте алгоритма автообнаружения будет выводиться некорректная ин-

формация (например, будет сообщаться о наличие сопроцессора, ког-

да на самом деле его нет, или наоборот).

В Borland Pascal предусмотрена возможность отмены принятой

по умолчанию логики автоматического распознавания. Эта возмож-

ность задается переменной операционной среды 87.

Вы можете установить переменную операционной среды 87 в от-

вет на подсказку DOS с помощью команды SET, например, следующим

образом:

SET 87=Y

или

SET 87=N

Установка для переменной операционной среды 87 значения N

(Нет) указывает коду инициализации, что вы не хотите использовать

сопроцессор 8087, хотя он может и присутствовать в системе. И на-

оборот: установка для переменной 87 значения Y (Да) означает, что

сопроцессор имеется, и вы хотите, чтобы ваша программа его ис-

пользовала. Однако при этом нужно помнить о том, что установка

для переменной 87 значения Y при отсутствии в системе сопроцессо-

ра 8087 приведет к тому, что ваша программа аварийно завершит ра-

боту или "зависнет".

Если переменная операционной среды 87 определена, а вы хоти-

те, чтобы она стала неопределенной, то можно ввести в ответ на

подсказку DOS:

SET 87=

и нажать клавишу Enter.

Если в операционной среде DOS присутствует запись 87=Y, или

если код инициализации успешно распознает сопроцессор, то далее

код инициализации выполняет последующие проверки, чтобы опреде-

лить, какой это сопроцессор (8087, 80287 или 80387). Это необхо-

димо для того, чтобы Турбо Паскаль мог корректно работать с от-

дельными несовместимостями, которые имеются между сопроцессорами

различных типов.

Результат автоматического распознавания наличия сопроцессора

и его модели сохраняется в переменной Test8087 (которая описыва-

ется в модуле System). Для нее определены следующие значения:

ЪДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Значение і Определение і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і 0 і сопроцессор не обнаружен і

і 1 і обнаружен сопроцессор 8087 і

і 2 і обнаружен сопроцессор 80287 і

і 3 і обнаружен сопроцессор 80387 і

АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Чтобы определить характеристики системы, на которой работает

ваша программа, вы можете в программе проверить содержимое пере-

менной Test8087. В частности, эту переменную можно проанализиро-

вать для того, чтобы определить, эмулируются инструкции работы с

плавающей точкой, или они действительно выполняются.

Распознавание сопроцессора 80x87 в программе Windows

Операционная среда Windows и библиотека эмуляции WIN87EM.DLL

автоматически распознает наличие в системе платы сопроцессора

80x87. Если сопроцессор 80x87 имеется, то программа будет его ав-

томатически использовать. В случае же его отсутствия программа

будет использовать эмуляцию с помощью WIn87EM.DLL. Чтобы опреде-

лить наличие в системе сопроцессора 80х87, вы можете использовать

функцию GetWinFlags (которая определена в модуле WinProcs) и би-

товую маску wf_80x87 (определенную в модуле WinTypes). Например:

if GetWinFlags and wf_80x87 <> 0 then

Writeln('80x87 присутствует') else

Writeln('80x87 отсутствует');

Использование эмуляции сопроцессора 80x87 на языке ассемблера

Когда компоновка объектных файлов выполняется с директивой

{$L имя_файла}, необходимо обеспечить, чтобы эти файлы компилиро-

вать с разрешением эмуляции сопроцессора 80x87. Например, если вы

используете инструкции сопроцессора 80x87 во внешних процедурах

на языке ассемблера, необходимо убедиться, что при ассемблирова-

нии файлов .ASM в файлы .OBJ эмуляция разрешена. В противном слу-

чае инструкции сопроцессора 80x87 не могут эмулироваться на маши-

нах без сопроцессора 80x87. Для разрешения эмуляции используйте

параметр командной строки Турбо Ассемблера /E.


Глава 16. Модуль Dоs

С помощью модулей Dos и WinDos реализуется целый ряд прог-

рамм операционной системы и программ обработки файлов. Ни одна из

программ модуля Dos не определена в стандартном Паскале, поэтому

они помещаются в отдельный модуль.

Более полное описание операций DOS приведено в руководствах

по DOS фирмы IBM.

Основное различие модулей Dos и WinDos состоит в том, что

процедуры и функции модуля Dos используют стандартные строки Пас-

каля, а процедуры и функции модуля WinDos - строки с завершающим

нулем. Стандартная строка Паскаля - это байт длины, за которым

следует последовательность символов. Строка с завершающим нулем -

это последовательность ненулевых символов с завершающим символом

NULL (#0).

Примечание: Подробнее о различии этих строк рассказы-

вается в Главе 18.

Если вы разрабатываете только программы Windows, используйте

модуль WinDos.

Если вы разрабатываете только программы DOS, то желательно

пользоваться в программах модулем Dos, так как большинство прог-

рамм Паскаля традиционно работают со строками Паскаля. Однако,

если вы разрабатываете приложения для среды Windows, то можете

написать программу, используемую в обеих платформах - DOS и

Windows, применяя для этого модули WinDos и Strings. Windows тре-

бует использования строк с завершающим нулем. Вы можете также

воспользоваться данными модулями, если у вас есть файл данных Си,

и вы хотите его конвертировать. В языке Си используются строки с

завершающим нулем.

Процедуры и функции модуля Dos

Ниже перечислены процедуры и функции модуля Dos. Чтобы ис-

пользовать их, вы должны ссылаться на модуль Dos с помощью опера-

тора программы uses. См. также Главу 1 ("Справочник по библиоте-

ке") в "Руководстве программиста".

Процедуры для работы с датой и временем

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetDate і Возвращает текущую дату, установленную ві

і і операционной системе. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetFTime і Возвращает дату и время последней записиі

і і файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetTiме і Возвращает текущее время, установленное ві

і і операционной системе. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і РackTiме і Преобразует запись DateTiме в четырехбайто-і

і і вое упакованное символьное представлениеі

і і даты и времени длинного целого типа, кото-і

і і рое используется в процедуре SetTiме. Поляі

і і записи DateTiме не проверяются на допусти-і

і і мость границ. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetDate і Устанавливает для операционной системы те-і

і і кущую дату. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetFTiме і Устанавливает время и дату последней записиі

і і файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetTiме і Устанавливает в операционной системе теку-і

і і щее время. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і UnpackTiме і Преобразует четырехбайтовое упакованнойі

і і символьное представление даты и времениі

і і длинного целого типа, возвращаемого проце-і

і і дурами GetFTiме, FindFirst, FindNext в рас-і

і і пакованную запись DateTiме. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры и функции обслуживания прерываний

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetIntVес і Возвращает адрес, сохраненный в заданномі

і і векторе прерываний. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Intr і Выполняет заданное программное прерывание.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і МsDos і Выполняет вызов функции DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetIntVес і Устанавливает по заданному адресу заданныйі

і і вектор прерывания. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Функции, проверяющие состояние диска

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Фуннкция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DiskFrее і Возвращает число свободных байт на диске ві

і і заданном дисководе. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DiskSize і Возвращает полный объем в байтах заданногоі

і і диска. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры обработки файлов

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FExpand і Воспринимает имя файла и возвращает полноеі

і і уточненное имя (диск, каталог, расширение).і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FSearch і Ищет файл в списке каталогов. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FindFirst і Производит поиск в заданном (или текущем)і

і і каталоге записи, содержимое которой совпа-і

і і дает с заданным именем файла и атрибутами.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FindNext і Возвращает следующую запись, имя файла иі

і і атрибуты в которой совпадают с теми, кото-і

і і рые были заданы при предыдущем обращении кі

і і процедуре FindFirst. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetFAttr і Возвращает атрибуты файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetFAttr і Устанавливает атрибуты файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Функции управления операционной средой

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Функция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і EnvCount і Возвращает число строк, содержащихся в опе-і

і і рационной среде DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і EnvStr і Возвращает заданную строку операционнойі

і і среды. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetEnv і Возвращает значение заданной переменнойі

і і операционной среды. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры управления процессами

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Eхесutе і Выполняет заданную программу с указаннойі

і і командной строкой. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Keep і Сохраняет (прекращает выполнение и сохраня-і

і і ет в памяти) прекратившую работу программу,і

і і оставляя ее резидентной в памяти. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SwapVectors і Меняет местами содержимое сохраненных век-і

і і торов прерываний и текущих векторов. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Прочие процедуры и функции

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

іПроцедура/функция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DosVersion і Возвращает номер версии операционной систе-і

і і мы DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetCBreak і Возвращает проверяемое DOS состояниеі

і і Ctrl+Break. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetCBreak і Устанавливает проверяемое DOS состояниеі

і і Ctrl+Break. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetVerify і Возвращает состояние флага проверки в DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetVerify і Устанавливает состояние флага проверки ві

і і DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Константы, типы и переменные модуля Dos

В данном разделе кратко обсуждаются константы, типы и пере-

менные, определяемые в модуле Dos. Более детальная информация со-

держится в разделе "Константы флагов" (значение FParity) в Главе

1 ("Справочник по библиотеке") "Справочного руководства програм-

миста".

Группы констант

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Группа констант і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Флаги і Используются для проверки отдель-і

і і ных флагов после вызова функцийі

і і Intr или MsDos. Это флаги:і

і і FParity, FAuxiliary, FZero,і

і і FSign, FOverflow, fCarry. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і fmXXXX і Определяет допустимые значенияі

і і поля Mode записи TextRec тексто-і

і і вого файла: fmClosed, fmInput,і

і і fmOutput, fmInOut. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Атрибуты файла і Используются для построения ат-і

і і рибутов, применяемых в FindFirst,і

і і GetFAttr и SetFAttr. Это флагиі

і і ReadOnly, Hidden, SysFile,і

і і VolumeID, Directory, Archive,і

і і AnyFile. і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Типы

В модуле Dos определяются следующие типы:

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Тип і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Тип записи файла і Определения записей, использующие-і

і і ся в Borland Pascal для внутреннихі

і і целей, описываются также в модулеі

і і Dos. Тип FilеRес используется какі

і і для типизованных, так и для нетипи-і

і і зованных файлов, в то время, какі

і і TехtRес представляет собой внутрен-і

і і ний формат переменной текстовогоі

і і типа. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Registers і Переменные регистрового типа приме-і

і і няются в процедурах Intr и МsDosі

і і для задания содержимого входногоі

і і регистра и проверки содержимого вы-і

і і ходного регистра при прерываниях,і

і і использующихся в программном обес-і

і і печении. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DateTime і Переменные типа DateTiме (даты иі

і і времени) используются в процедурахі

і і UnраскТiме и РаскТiме для анализа,і

і і упаковки и построения четырехбайто-і

і і вого значения, содержащего дату иі

і і время. Это четырехбайтовое значениеі

і і используется затем в процедурахі

і і GetFTiме, SetTiме, FindFirst иі

і і FindNехt. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SearchRec і Переменные типа SearchRес использу-і

і і ются в процедурах FindFirst иі

і і Findnext для просмотра каталогові

і і файлов. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Строковые типы і Эти строковые типы определены в мо-і

і работы с файлами і дуле Dos и используются для работыі

і і с именами файлов и маршрутов приі

і і вызове строковой процедуры FSplit.і

і і Это типы ComStr, PathStr, DirStr,і

і і NameStr, ExtStr. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Переменные модуля Dos

Многими подпрограммами модуля Dos для сообщения об ошибке

используется переменная DosError.

Процедуры и функции модуля WinDos

Ниже перечислены процедуры и функции модуля WinDos. Чтобы

использовать их, вы должны ссылаться на модуль WinDos с помощью

оператора программы uses.

Процедуры для работы с датой и временем модуля WinDos

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetDate і Возвращает текущую дату, установленную ві

і і операционной системе. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetFTime і Возвращает дату и время последней записиі

і і файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetTiме і Возвращает текущее время, установленное ві

і і операционной системе. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і РackTiме і Преобразует запись DateTiме в четырехбайто-і

і і вое упакованное символьное представлениеі

і і даты и времени длинного целого типа, кото-і

і і рое используется в процедуре SetTiме. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetDate і Устанавливает для операционной системы те-і

і і кущую дату. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetFTiме і Устанавливает время и дату последней записиі

і і файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetTiме і Устанавливает в операционной системе теку-і

і і щее время. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і UnpackTiме і Преобразует четырехбайтовое упакованнойі

і і символьное представление даты и времениі

і і длинного целого типа, возвращаемого проце-і

і і дурами GetFTiме, FindFirst, FindNext в рас-і

і і пакованную запись DateTiме. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры обслуживания прерываний модуля WinDos

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetIntVес і Возвращает адрес, сохраненный в заданномі

і і векторе прерываний. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Intr і Выполняет заданное программное прерывание.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і МsDos і Выполняет вызов функции DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetIntVес і Устанавливает по заданному адресу заданныйі

і і вектор прерывания. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Функции модуля WinDos, проверяющие состояние диска

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Фуннкция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DiskFrее і Возвращает число свободных байт на диске ві

і і заданном дисководе. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DiskSize і Возвращает полный объем в байтах заданногоі

і і диска. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры работы с файлами модуля WinDos

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileExpand і Воспринимает имя файла и возвращает полноеі

і і уточненное имя (диск, каталог, расширение).і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileSearch і Ищет файл в списке каталогов. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FileSplit і Разбивает полное имя файла на три компонен-і

і і та (диск, каталог, имя и расширение). і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FindFirst і Производит поиск в заданном (или текущем)і

і і каталоге записи, содержимое которой совпа-і

і і дает с заданным именем файла и атрибутами.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і FindNext і Возвращает следующую запись, имя файла иі

і і атрибуты в которой совпадают с теми, кото-і

і і рые были заданы при предыдущем обращении кі

і і процедуре FindFirst. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetFAttr і Возвращает атрибуты файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetFAttr і Устанавливает атрибуты файла. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры и функции для работы с каталогами

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура/функцияі Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і CreateDir і Создает новый подкаталог. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetCurDir і Возвращает текущий каталог на заданном дис-і

і і ке. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і RemoveDir і Удаляет подкаталог. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetCurDir і Изменяет текущий каталог. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Процедуры и функции обслуживания прерываний модуля WinDos

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Процедура і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetArgCount і Возвращает число параметров, переданныхі

і і программе в командной строке. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetArgStr і Возвращает заданный аргумент команднойі

і і строки. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetEnvVar і Возвращает указатель на значение заданнойі

і і переменной операционной среды. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Прочие процедуры и функции модуля WinDos

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

іПроцедура/функция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і DosVersion і Возвращает номер версии операционной систе-і

і і мы DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetCBreak і Возвращает проверяемое DOS состояниеі

і і Ctrl+Break. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetCBreak і Устанавливает проверяемое DOS состояниеі

і і Ctrl+Break. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і SetVerify і Устанавливает состояние флага проверки ві

і і DOS. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Константы, типы и переменные модуля WinDos

В данном разделе кратко обсуждаются константы, типы и пере-

менные, определяемые в модуле WinDos. Более детальная информация

содержится в разделе "Константы флагов" (значение FParity) в Гла-

ве 1 ("Справочник по библиотеке") "Справочного руководства прог-

раммиста".

Группы констант

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Группа констант і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Флаги і Используются для проверки отдель-і

і і ных флагов после вызова функцийі

і і Intr или MsDos. Это флаги:і

і і FParity, FAuxiliary, FZero,і

і і FSign, FOverflow, fCarry. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і fmXXXX і Определяет допустимые значенияі

і і поля Mode записи TextRec тексто-і

і і вого файла: fmClosed, fmInput,і

і і fmOutput, fmInOut. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і faXXXX і Используются для построения ат-і

і і рибутов, их проверки и измененияі

і і в процедурах и функциях работы сі

і і файлами. Это константы faHidden,і

і і faSysFile, faVolumeID, faDirecto-і

і і ry, faArchive, faAnyFile. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і fsXXXX і Максимальные длины компонентові

і і имени файла, используемых в под-і

і і программах FileSearch и File-і

і і Expand. Это константы: fsPathNa-і

і і me, fsDirectory, fsFileName,і

і і fsExtension. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і fcXXXX і Флаги, возвращаемые функциейі

і і FileSplit: fcExtension, fcFile-і

і і Name, fcDirectory, fcWildcards. і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Типы

В модуле WinDos определяются следующие типы:

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Тип і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і Тип записи файла і Определения записей, использующие-і

і і ся в Borland Pascal для внутреннихі

і і целей, описываются также в модулеі

і і Dos. Тип TFilеRес используется какі

і і для типизованных, так и для нетипи-і

і і зированных файлов, в то время, какі

і і TTехtRес представляет собой внут-і

і і ренний формат переменной текстовогоі

і і типа. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TRegisters і Переменные регистрового типа приме-і

і і няются в процедурах Intr и МsDosі

і і для задания содержимого входногоі

і і регистра и проверки содержимого вы-і

і і ходного регистра при прерываниях,і

і і использующихся в программном обес-і

і і печении. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TDateTime і Переменные типа TDateTiме (даты иі

і і времени) используются в процедурахі

і і UnраскТiме и PаскТiме для анализа,і

і і упаковки и построения четырехбайто-і

і і вого значения, содержащего дату иі

і і время. Это четырехбайтовое значениеі

і і используется затем в процедурахі

і і GetFTiме, SetTiме, FindFirst иі

і і FindNехt. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і TSearchRec і Переменные типа TSearchRес исполь-і

і і зуются в процедурах FindFirst иі

і і Findnext для просмотра каталогові

і і файлов. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Переменные модуля WinDos

Многими подпрограммами модуля WinDos для сообщения об ошибке

используется переменная DosError.


Глава 17. Программирование в защищенном режиме DOS

Микропроцессор 80286 дает новый способ адресации к памяти:

защищенный режим виртуальной адресации или просто защищенный ре-

жим. Этот новый режим адресации дает три основных преимущества:

* Адресация к памяти объемом до 16 мегабайт.

* Логическое адресное пространство, превышающее пространство

физических адресов.

* Способ изоляции программ друг от друга, так что одна прог-

рамма не может нарушать другой выполняющейся одновременно

с ней программы.

С помощью Borland Pascal вы легко можете писать работающие в

защищенном режиме прикладные программы DOS без необходимости при-

менения дополнительного "расширителя" DOS. Вы обнаружите, что

многие программы реального режима прекрасно работают в защищенном

режиме. Данная глава поможет вам модифицировать те программы, ко-

торые этого не делают, и прояснит некоторые основные моменты за-

щищенного режима и его отличия от реального режима.

Что такое защищенный режим?

Процессор 80286 и более поздние процессоры поддерживают два

режима операций: защищенный режим и реальный режим. Реальный ре-

жим совместим с работой процессора 8086 и позволяет прикладной

программе адресоваться к памяти объемом до одного мегабайта. За-

щищенный режим расширяет диапазон адресации до 16 мегабайт. Ос-

новное отличие между реальным и защищенным режимом заключается в

способе преобразования процессором логических адресов в физичес-

кие. Логические адреса - это адреса, используемые в прикладной

программе. Как в реальном, также и в защищенном режиме логический

адрес - это 32-разрядное значение, состоящее из 16-битового се-

лектора (адреса сегмента) и 16-битового смещения. Физические ад-

реса - это адреса, которые процессор использует для обмена данны-

ми с компонентами системной памяти. В реальном режиме физический

адрес представляет собой 20-битовое значение, а в защищенном ре-

жиме - 24-битовое.

Когда процессор обращается к памяти (для выборки инструкции

или записи переменной), он генерирует из логического адреса физи-

ческий адрес. В реальном режиме генерация физического адреса сос-

тоит из сдвига селектора (адреса сегмента) на 4 бита влево (это

означает умножение на 16) и прибавления смещения. Полученный в

результате 20-разрядный адрес используется затем для доступа к

памяти.

16МбЪДДДДДДДДДДДДДДДДї

і і

ЪДДДДДДДДї і і

іСмещениеГДї і і

АДДДДДДДДЩ і і і

і ГДДДДДДДДДДДДДДДДґї

АДДЕДДДДД>±±±±±±±±±±іГ сегмент 64К

ЪД>ГДДДДДДДДДДДДДДДДґЩ

і і і

і і Пространство і

ЪДДДДДДДДї ЪДДДДДДї і і адресов і

іСелекторГДґ x 16 ГДДДДДЩ і і

АДДДДДДДДЩ АДДДДДДЩ і і

0АДДДДДДДДДДДДДДДДЩ

Рис. 17.1 Генерация физического адреса в реальном режиме.

Чтобы получить физический адрес в защищенном режиме, селек-

торная часть логического адреса используется в качестве индекса

таблицы дескрипторов. Запись в таблице дескрипторов содержит

24-битовый базовый адрес, к которому затем для образования физи-

ческого адреса прибавляется смещение логического адреса.

16МбЪДДДДДДДДДДДДДДДДї

і і

ЪДДДДДДДДї і і

іСмещениеГДї і і

АДДДДДДДДЩ і і і

і ГДДДДДДДДДДДДДДДДґї

Таблица дескрипторов АДДЕДДДДД>±±±±±±±±±±іГ сегмент 64К

ЪДДДДДДї ЪД>ГДДДДДДДДДДДДДДДДґЩ

ГДДДДДДґ і і і

ГДДДДДДґ і і Пространство і

ГДДДДДДґ і і адресов і

ЪД>ГДДДДДДґДДДЩ і і

і ГДДДДДДґ і і

і ГДДДДДДґ 0АДДДДДДДДДДДДДДДДЩ

і ГДДДДДДґ

і ГДДДДДДґ

і ГДДДДДДґ

і ГДДДДДДґ

і ГДДДДДДґ

і ГДДДДДДґ

і ГДДДДДДґ

і ГДДДДДДґ

ЪДДДДДДДДї і ГДДДДДДґ

іСелекторГДЩ АДДДДДДЩ

АДДДДДДДДЩ

Рис. 17.2 Генерация физического адреса в защищенном режиме.

Каждая запись в таблице дескрипторов называется дескриптором

и определяет сегмент в памяти. Запись таблицы дескрипторов зани-

мает 8 байт, а записанная в дескрипторе информация включает в се-

бя базовый адрес, предельное значение и флаги полномочий доступа

к сегменту.

Записи предельного значения сегмента и полномочий доступа в

дескрипторе определяют размер и тип сегмента. Сегменты могут

иметь размер от 1 до 65536 байт и могут быть сегментами кода или

сегментами данных. Сегменты кода могут содержать выполняемые ма-

шинные инструкции и доступные только по чтению данные. Сегменты

данных могут содержать данные, доступные по чтению и записи. За-

писывать данные в сегменты кода или выполнять инструкции в сег-

ментах данных невозможно. Любая попытка сделать это или попытка

доступа к данным вне границ сегмента вызывает общий сбой по на-

рушению защиты (сокращенно сбой GP). Поэтому режим и называется

защищенным.

По данному адресу в реальном режиме прикладная программа мо-

жет определить физический адрес. В защищенном режиме это обычно

не так, поскольку селекторная часть логического адреса является

индексом в таблице дескрипторов, и сам селектор не имеет прямого

отношения к вычислению физического адреса. Это дает то преиму-

щество, что управление виртуальной памятью можно реализовать, не

влияя на прикладную программу. Например, путем простого обновле-

ния поля базового адреса дескриптора сегмента, операционная сис-

тема может перемещать сегмент в физической памяти без влияния на

использующую сегмент прикладную программу. Прикладная программа

ссылается только на селектор сегмента, и на селектор не влияют

изменения в дескрипторе.

Прикладная программа редко имеет дело с дескрипторами. При

необходимости дескрипторы создаются и уничтожаются операционной

системой и администратором памяти, а прикладная программа знает о

соответствующих селекторах. Селекторы аналогичны описателям фай-

лов - с точки зрения прикладной программы это то, что обслужива-

ется операционной системой, но в операционной системе они работа-

ют как индексы содержащих дополнительную информацию таблиц.

Расширения Borland защищенного режима DOS

Расширения защищенного режима Borland Pascal реализованы че-

рез два компонента: DPMI-сервер (файл DPMI16BI.OVL) и администра-

тор этапа выполнения (файл RTM.EXE).

DPMI-сервер

Интерфейс защищенного режима DOS (DPMI) - это отраслевой

стандарт, позволяющий программам DOS аппаратно-независимым путем

получить доступ к развитым средствам персональных компьютеров,

реализованных на процессорах 80286, 80386 и 80486. Определены

функции DPMI для обслуживания таблиц дескрипторов, переключения

режима, распределения расширенной памяти, выделения памяти DOS,

управления подсистемой прерываний и взаимодействия с программами

реального режима.

Расширения защищенного режима Borland Pascal основаны на

спецификации DPMI 0.9. Хотя спецификация DPMI не поддерживает вы-

зовы DOS из прикладных программ защищенного режима, DPMI-сервер

Borland и серверы многих других фирм, включая улучшенный режим

Windows 3.x, поддерживают прерывание INT 21H и другие стандартные

прерывания DOS и BIOS, используемые обычно в приложениях DOS за-

щищенного режима.

Администратор этапа выполнения

Администратор этапа выполнения (RTM.EXE) является надстрой-

кой DPMI-сервера и обеспечивать для прикладных программ защищен-

ного режима несколько служебных функций. Администратор этапа вы-

полнения содержит загрузчик защищенного режима и администратор

памяти защищенного режима и позволяет под DPMI сосуществовать

нескольким клиентам защищенного режима.

Приложения защищенного режима Borland используют те же фор-

маты выполняемых файлов, что и Windows 3.x и OS/2 1.x. Программ-

ный загрузчик администратора этапа выполнения может загружать как

выполняемые файлы (.EXE), так и динамически компонуемые библиоте-

ки (.DLL).

Администратор памяти защищенного режима позволяет прикладным

программам защищенного режима распределять блоки динамической па-

мяти. Администратор памяти поддерживает фиксированные, перемещае-

мые и выгружаемые блоки, а также обслуживает код и сегменты дан-

ных прикладной программы. Используя уникальные для защищенного

режима средства, администратор памяти функционирует также в ка-

честве администратора оверлеев, автоматически загружая и выгружая

сегменты кода (по этой причине прикладной программе защищенного

режима не требуется модуль Overlay).

Прикладные программы могут получить доступ к программам за-

щищенного режима через модуль WinAPI. Модуль WinAPI, описанный в

следующем разделе, реализует подмножество функций API (прикладно-

го программного интерфейса) Windows, обеспечивая управление па-

мятью, обслуживание программных модулей, управление ресурсами,

загрузку динамически компонуемых библиотек и доступ к селекторам

на нижнем уровне. Поскольку администратор этапа выполнения API

является подмножеством API Windows, вы можете написать совмести-

мые на уровне двоичного кода динамически компонуемые библиотеки,

которые можно использовать и в защищенном режиме DOS, и в

Windows.

Разработка прикладных программ DOS защищенного режима

Написание прикладной программы защищенного режима не предс-

тавляет собой сложной задачи. Вам не нужно беспокоиться о селек-

торах и адресах памяти. Операционная система с расширениями

Borland все делает за вас. Фактически, большинство ваших программ

реального режима может прекрасно работать в защищенном режиме. В

следующих разделах описывается некоторая разница между реальным и

защищенным режимом, о которых вы должны знать при разработке

прикладной программы защищенного режима.

Надежное программирование в защищенном режиме

Существует несколько приемов, используемых обычно в програм-

мах реального режима, которые в программах защищенного режима бу-

дут приводить к общему нарушению защиты (сбой GP). Borland Pascal

при сбое GP выводит ошибку этапа выполнения 216. Сбой GP происхо-

дит, когда вы пытаетесь получить доступ к памяти, к которой ваша

прикладная программа обращаться не может. Операционная система

останавливает прикладную программу, но сбоя системы не происхо-

дит. Хотя сбои GP и прекращают работу вашей программы, система

"защищена" от сбоя. К сбою GP приводит следующее:

* загрузка в сегментные регистры недопустимых значений;

* обращение к памяти вне границы сегмента;

* запись в сегмент кода;

* разыменование указателей nil.

Примечание: Сбои по нарушению защиты предохраняют вашу

систему от плохой практики программирования.

Загрузка в сегментные регистры недопустимых значений

Когда процессор работает в защищенном режиме, сегментные ре-

гистры (CS, DS, ES и SS) могут содержать только селекторы. Пос-

кольку селекторы являются индексами в таблице дескрипторов, они

не имеют физического отношения к памяти, на которую ссылается.

Если вы пытаетесь загрузить в сегментный регистр произвольное

значение, то возможно получите сбой GP, поскольку это значение

может не представлять допустимого дескриптора.

Функция Ptr и массивы Mem

При разыменовании указателей компилятор генерирует код для

загрузки сегментного регистра. Если вы строите указатели с по-

мощью стандартной функции Ptr, то нужно обеспечить, чтобы сег-

ментная часть указателя была допустимым селектором. Аналогично,

при работе с массивами Mem, MemW и MemL вы вместо физических ад-

ресов должны использоваться селекторы. Например, при доступе к

рабочей области ROM BIOS (сегмент $0040) или к областям видеопа-

мяти (сегменты $A000, $B000 и $B800) следует использовать вместо

абсолютных значений переменные SegXXXX. (Переменные SegXXXX опи-

сываются ниже.)

Абсолютные переменные

В защищенном режиме вы не можете задавать абсолютный адрес

переменной. Любой исходных код, где сегмент и смещение задаются в

операторе absolute, нужно переписать. Например, вам может потре-

боваться построить указатель, используя переменные SegXXXX.

Операции с сегментами

Добавление или вычитание значений из селекторной части ука-

зателя обычно не допускается. Например, добавление к селекторной

части указателя $1000 в реальном режиме увеличивает указатель на

64К, но в защищенном режиме результирующий указатель будет недо-

пустимым. Вместо этого для выделения и управления блоками памяти

следует использовать функцию GlobalXXXX модуля WinAPI.

В Borland Pascal существует способ выполнения арифметических

операций с селекторами с помощью переменной SelectorInc (см. ни-

же).

Использование сегментных регистров в качестве временных переменных

В реальном режиме некоторые старые программы на ассемблере

используют сегментные регистры для хранения временных переменных.

В защищенном режиме это работать не будет, так как обычно сохра-

няемые в сегментных регистрах временные значения не являются до-

пустимыми селекторами.

Доступ к памяти вне границ сегмента

В реальном режиме каждый сегмент имеет размер 64К. В защи-

щенном режиме дескриптор сегмента содержит поле, специфицирующее

предельный размер сегмента, и если вы пытаетесь обратиться к дан-

ным вне границ сегмента, по получите сбой GP. При загрузке прик-

ладной программы администратор этапа выполнения устанавливает со-

ответствующие предельные значения для сегментов кода, данных и

стека. Кроме того, блок памяти, распределяемый с помощью функции

GlobalAlloc модуля WinAPI, имеет предельное значение сегмента,

соответствующее размеру блока памяти.

Запись в сегмент кода

В реальном режиме можно записывать переменные в сегмент ко-

да, поскольку реальные режим не определяет, что может и что не

может существовать в сегменте. В защищенном режиме это не так.

Селектор защищенного режима имеет флаг чтения/записи или доступа

только по чтению, а селекторы кода всегда отмечены как доступные

только по чтению. Если вы пытаетесь записывать в селектор сегмен-

та кода, происходит сбой GP. Однако вы можете использовать псев-

доним и написать самомодифицирующийся код (см. ниже).

Разыменование указателей nil

При преобразовании прикладной программы реального режима в

защищенный режим, в программе, которая уже годы работала без оши-

бок, возможно внезапное появление определенных ошибок. Например,

вы можете случайно разыменовывать указатель nil, или обнаружите,

что ваша программа содержит "потерянные" указатели, которые разы-

меновываются после их освобождения. В реальном режиме такие ошиб-

ки не обязательно проявляются, но в защищенном режиме они обычно

приводят к сбою GP. Согласно своему названию, защищенный режим

значительно лучше предохраняет вас от ошибок, связанных с указа-

телями.

Сегменты кода и данных

Аналогично программе Borland Pascal реального режима, прог-

рамма защищенного режима содержит несколько сегментов кода, сег-

мент данных и сегмент стека. При загрузке программы защищенного

режима администратор этапа выполнения автоматически выделяет се-

лекторы для сегментов кода, данных и стека. Для сегментов кода

с помощью директивы компилятора $C можно управлять отдельными ат-

рибутами. В частности, сегменты кода можно сделать перемещаемыми

или фиксированными в физической памяти, они могут загружаться

предварительно или по запросу, а также могут быть выгружаемыми

или постоянными.

Примечание: Подробнее о директиве компилятора $C расс-

казывается в Главе 21 данного руководства и в Главе 2 ("Ди-

рективы компилятора") "Справочного руководства программис-

та".

Атрибуты сегмента кода позволяют вам обозначать сегмент как

статический (перемещаемый, предварительно загружаемый, постоян-

ный) или динамический (перемещаемый, загружаемый по запросу, выг-

ружаемый). Таким образом, в защищенном режиме вам не нужно ис-

пользовать модуль Overlay и директиву компилятора $O, и в версии

модуля System для защищенного режима переменные OvrXXXXXX отсутс-

твуют.

Управление динамически распределяемой памятью

Администратор динамически распределяемой области памяти

Borland Pascal защищенного режима довольно существенно отличается

от администратора динамически распределяемой памяти Borland

Pascal реального режима. В частности, переменные HeapOrg,

HeapEnd, HeapPtr и FreeList в версии модуля System для защищенно-

го режима не определены. Администратор этапа выполнения динами-

чески распределяемой области памяти Borland Pascal защищенного

режима (который идентичен администратору этапа выполнения динами-

чески распределяемой области памяти Borland Pascal для Windows)

для выполнения основных операций по выделению и освобождению па-

мяти использует администратор этапа выполнения, а для оптимизации

распределения небольших блоков памяти включает в себя подсистему

вторичного распределения сегмента. Подробнее об администраторе

динамически распределяемой области памяти этапа выполнения расс-

казывается в Главе 21.

Предопределенные селекторы

В модуле System для обычно используемых адресов реального

режима предусмотрено несколько предопределенных селекторов. Они

именуются по физическому сегменту, которому данные селекторы

присвоены, и используются для совместимости между реальным и за-

щищенным режимом DOS.

Предопределенные селекторы Таблица 17.1

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Селектор і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і Seg0040 і Используется для доступа к области данныхі

і і BIOS $40 в младших адресах. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SegA000 і Используется для доступа к графической па-і

і і мяти EGA и VGA по адресу сегмента $A000. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SegB000 і Используется для доступа к видеопамяти мо-і

і і нохромного адаптера по адресу сегментаі

і і $A000. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SegB800 і Используется для доступа к видеопамятиі

і і цветного графического адаптера по адресуі

і і сегмента $A000. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

В реальном режиме переменные SegXXXX всегда содержат значе-

ния $0040, $A000, $B000 и $B800 соответственно. В защищенном ре-

жиме код запуска библиотеки исполняющей системы создает четыре

селектора, ссылающихся на конкретные области памяти реального ре-

жима. При ссылке на эти области памяти вам следует использовать

переменные SegXXXX. Например, если у вас был код следующего вида:

CtrMode := Mem[$40: $49];

то вместо него следует записать:

CtrMode := Mem[Seg0040: $49];

Используя переменные SegXXXX, вы можете гарантировать, что

ваша программа без изменений будет работать в реальном и защищен-

ном режимах.

Переменная SelectorInc

Переменная SelectorInc модуля System содержит значение, ко-

торое должно прибавляться к селектору или вычитаться из него для

получения следующего или предыдущего селектора в таблице дескрип-

торов. SelectorInc полезно использовать при работе с большими

блоками памяти (превышающими 64К) и при доступе к псевдонимам

сегментов.

Для выделения блоков, превышающих 64К (такие блоки называют

также большими блоками памяти), можно использовать функции

GlobalAlloc и GlobalAllocPrt в модуле WinAPI. Большие блоки неп-

рерывны в физической памяти, но из-за 16-разрядной архитектуры

процессора прикладная программа не может получить к ним доступ

целиком. Для большого блока памяти администратор памяти выделяет

несколько непрерывных (следующих подряд) селекторов, каждый из

которых (кроме последнего) ссылается на часть большого блока па-

мяти размером 64К. Например, чтобы выделить блока памяти размером

в 220К, администратор памяти создает четыре селектора, при этом

первые три селектора ссылаются на блоки по 64К, а последний се-

лектор - на блок размером 28К. Прибавляя SelectorInc к селектору,

принадлежащему большому блоку, вы можете получить селектор для

следующего сегмента, а вычитая SelectorInc - для предыдущего.

При распределении большого блока функция GlobalAlloc всегда

возвращает описатель первого сегмента, а GlobalAllocPtr - указа-

тель на первый сегмент.

Приведенная ниже функция GetPtr воспринимает указатель боль-

шого блока (возвращаемый функцией GlobalAllocPtr) и 32-разрядное

смещение и возвращает указатель на заданное внутри блока смеще-

ние.

function GetPtr(P: Pointer; Offset: Longint): Pointer;

type

Long = record

Lo, Hi: Word;

end;

begin

GetPtr := Ptr(

Long(P).Hi + Long(Offset).Hi * SelectorInc,

Long(P).Lo + Long(Offset).Lo);

end;

Заметим, что старшее слово параметра Offset используется для

определения того, сколько раз нужно увеличить селекторную часть P

для получения корректного сегмента. Например, если Offset равно

$24000, то селекторная часть P будет увеличена на 2 *

SelectorInc, а смещение P - на $4000.

Следующая функция LoadFile загружает в блок памяти весь файл

и возвращает указатель на блок. Если файл превышает 64К, то выде-

ляется большой блок памяти.

function LoadFile(const FileName: string): Pointer;

var

Buffer: Pointer;

Size, Offset, Count: Longint;

F: file;

begin

Buffer := nil;

Assign(F, FileName);

Reset(F, 1);

Size := FileSize(F);

Buffer := GlobalAllocPtr(gmem_Moveable, Size);

if Buffer <> nil then

begin

Offset := 0;

while Offset < Size do

begin

Count := Size - Offset;

if Count > $8000 then Count := $8000;

BlockRead(F, GetPtr(Buffer, Offset)^, Count);

Inc(Offset, Count);

end;

end;

LoadFile := Buffer;

end;

Переменная SelectorInc определена также в версии модуля

System для реального режима. В реальном режиме она всегда содер-

жит значение $1000, которое при сложении его с сегментной частью

указателя реального режима увеличивает указатель на 64К.

Другим образом вы можете использовать переменную SelectorInс

только в программах DOS защищенного режима. Используйте перемен-

ную SelectorInc для доступа к псевдонимам сегментов, выделяемых

администратором этапа выполнения при загрузке прикладной програм-

мы. Для каждого сегмента кода прикладной программы администратор

этапа выполнения создает селектор-псевдоним, ссылающийся на тот

же сегмент, но имеющий полномочия селектора данных. Для сегментов

стека и данных селекторы-псевдонимы не создаются.

Чтобы получить доступ к селектору-псевдониму для конкретного

сегмента, добавьте к селектору сегмента SelectorInc. Предположим,

например, что P - это переменная типа Pointer, а Foo - процедура

или функция. Тогда присваивание вида:

P := Addr(Foo)

приводит к тому, что P будет указывать на выполняемую доступную

только по чтению точку входа Foo, а после оператора:

P := Ptr(Seg(Foo) + SelectorInc, Ofs(Foo));

P будет ссылаться на тот же адрес, но с полномочиями на чте-

ние/запись.

Модуль WinAPI

Модуль WinAPI дает вам непосредственный доступ к расширениям

Borland защищенного режима DOS. Чтобы облегчить написание перено-

симых прикладных программ и совместимых на уровне двоичного кода

DLL, разработан интерфейс WinAPI, являющийся подмножеством интер-

фейса API Windows.

Модуль WinAPI позволяет вам использовать функции управления

памятью, управления ресурсами, модулями, селекторами и многие

другие функции API. Ниже приведено их краткое описание. Полное

описание констант, типов, процедур и функций модуля WinAPI вы мо-

жете найти в "Справочном руководстве программиста".

При работе под Windows подпрограммы API, поддерживаемые с

помощью модуля WinAPI, находятся в динамически компонуемых библи-

отеках KERNEL.DLL и USER.DLL. В защищенном режиме DOS эти DLL не

требуются, так как администратор этапа выполнения защищенного ре-

жима содержит реализацию подпрограмм KERNEL и USER, автоматичес-

ки перенаправляя их вызовы администратору.

Управление памятью

При разработке программ, работающих с динамической памятью,

обычно используются стандартные процедуры New, Dispose, GetMem и

FreeMem. Однако получить доступ к администратору памяти защищен-

ного режима Borland вы можете с помощью функций GlobalXXXX в мо-

дуле WinAPI.

Заметим, что функции GlobalXXXXPtr комбинируют в одной подп-

рограмме общие последовательности вызовов функций, такие как

GlobalAlloc, за которыми следуют вызовы GlobalLock, GlobalUnlock

или GlobalFree.

Подпрограммы управления памятью API

Таблица 17.2

ЪДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GetFreeSpace і Определяет объем свободной памяти в ди-і

і і намически распределяемой области. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalAlloc і Выделяет блок памяти в динамически расп-і

і і ределяемой области. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalAllocPtr і Выделяет и блокирует блок памяти (с по-і

і і мощью вызовов GlobalAlloc и GlobalLock).і

і і і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalCompact і Переупорядочивает память, распределен-і

і і ную в динамической области, так что ос-і

і і вобождается заданный объем памяти. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalDiscard і Выгружает заданный объект памяти. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalDosAlloc і Распределяет память, к которой можно по-і

і і лучить доступ в реальном режиме DOS. Этаі

і і память будет существовать в первом мега-і

і і байте линейного адресного пространства. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalDosFree і Освобождает память, выделенную ранее сі

і і помощью GlobalDosAlloc. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalFlags і Получает информацию о блоке памяти. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalFree і Освобождает разблокированный блок памятиі

і і и делает его описатель недействительным.і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalFreePtr і Разблокирует и освобождает блок памятиі

і і с помощью GlobalUnlock и GlobalFree. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalHandle і Получает описатель объекта в памяти поі

і і заданному адресу сегмента. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalLock і Увеличивает счетчик ссылки блока памятиі

і і и возвращает указатель на него. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalLockPtr і То же, что и GlobalLock, но вместо опи-і

і і сателя воспринимает указатель. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalLRUNewest і Перемещает объект в памяти на новую не-і

і і давно используемую позицию, минимизируя,і

і і таким образом, вероятность выгрузкиі

і і объекта. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalLRUOldest і Перемещает объект в памяти на самуюі

і і "старую" недавно используемую позицию,і

і і максимизирую вероятность выгрузки объ-і

і і екта. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalNorify і Вызывает адрес экземпляра процедуры уве-і

і і домления, передавая описатель блока, ко-і

і і торый нужно выгрузить. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalPageLock і Увеличивает значение счетчика блокиров-і

і і ки для памяти, связанной с данным селек-і

і і тором. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalPageUnlock і Уменьшает значение счетчика блокировкиі

і і для памяти, связанной с данным селекто-і

і і ром. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalPtrHandle і По заданному указателю на блок памятиі

і і возвращает описатель этого блока. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalReAlloc і Перераспределяет блок памяти. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalReAllocPtr і Разблокирует, перераспределяет и блоки-і

і і рует блок памяти (используя функцииі

і і GlobalUnlock, GlobalReAlloc иі

і і GlobalLock). і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalSize і Определяет текущий размер блока памяти. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalUnfix і Разблокирует блок памяти, блокированныйі

і і ранее с помощью GlobalLock. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і GlobalUnockPtr і То же, что и GlobalUnlock, но вместоі

і і описателя воспринимает указатель. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і LockSegment і Блокирует заданный выгружаемый сегмент. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

і UnlockSegment і Разблокирует сегмент. і

АДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Функция GlobalAlloc используется для распределения блоков

памяти. Для их освобождения применяется функция GlobalFree. Адми-

нистратор памяти поддерживает три типа блоков памяти: фиксирован-

ный, перемещаемый и выгружаемый. Фиксированный блок остается в

одних и тех же адресах физической памяти. Перемещаемый блок может

перемещаться в физической памяти и освобождать место для других

запросов на выделение памяти, а выгружаемые блоки могут выгру-

жаться из памяти, освобождая место для других блоков. С помощью

передаваемых GlobalAlloc флагов вы можете выбрать один из этих

трех типов:

* gmem_Fixed (фиксированный)

* gmem_Moveable (перемещаемый)

* gmem_Moveable + gmem_Discardable (выгружаемый)

Прикладная программа обычно выделяет только перемещаемые

блоки памяти, которые представляются типом THandle в модуле

WinAPI. Описатель памяти - это значение размером в слово, которое

идентифицирует блок памяти аналогично тому, как описатель файла -

это значение размером в слово, идентифицирующее открытый файл.

Перед тем как вы сможете получить доступ к памяти, его нужно

заблокировать с помощью функции GlobalAlloc, а когда вы закончите

к нему обращаться, его нужно разблокировать с помощью функции

GlobalUnlock. GlobalLock возвращает полный 32-разрядный указатель

на первый байт блока. Смещение указателя всегда равно 0. В защи-

щенном режиме DOS селектор указателя - это тоже самое, что описа-

тель блока, но в Windows это не всегда так.

Правильная последовательность вызовов для выделения, блоки-

ровки, разблокировки или освобождения блока показана в приведен-

ном ниже примере. В данном примере H - это переменная типа

THandle, а P - указатель:

H := GlobalAlloc(gmem_Moveable, 8192); { выделение блока }

if H <> then { если память выделена }

begin

P := GlobalLock(H); { блокировка блока }

.

. { доступ к блоку через P }

.

GlobalUnlock(H); { разблокировать блок }

GlobalFree(H); { освободить блок }

end;

Блокировка и разблокировка блока при каждом обращении к нему

достаточно утомительна и ведет к ошибкам, и реально она необходи-

ма только для выгружаемых блоков и в прикладных программах

Windows, работающих в реальном режиме. Во всех других ситуациях

лучшим решением является блокировка блока сразу после его выделе-

ния и сохранение этого состояния до освобождения блока. С этой

целью модуль WinAPI включает в себя семейство подпрограмм-"оболо-

чек" GlobalXXXXPtr. Особый интерес представляет функция

GlobalAllocPtr, которая выделяет и блокирует блок памяти, и функ-

ция GlobalFreePtr, разблокирующая и освобождающая блок памяти. С

помощью этих подпрограмм приведенный выше пример можно упростить:

H := GlobalAlloc(gmem_Moveable, 8192); { выделение блока }

if H <> then { если память выделена }

begin

.

. { доступ к блоку }

.

GlobalFreePtr(P); { освободить блок }

end;

Вызвав функцию GlobalReAlloc, вы можете изменить размер или

атрибуты блока памяти, сохранив его содержимое. Функция

GlobalReAlloc возвращает новый описатель блока, который может от-

личаться от передаваемого функции описателя, если старый размер

или новый размер блок превышает 64К. Заметим, что в тех случаях,

когда старый размер блока и новый его размер меньше 64К,

GlobalReAlloc всегда может изменить размер блока, не изменяя его

описателя.

Функция GlobalReAlloc можно также использоваться для измене-

ния атрибутов блока. Это можно сделать, задав наряду с

gmem_Moveable или gmem_Discardable флаг gmem_Modify.

Функция GlobalReAlloc выполняет те же действия, что и

GlobalReAlloc, но обе они вместо описателей использует указатели.

Имеется также ряд других, менее часто используемых

GlobalXXXX. Все они подробно описаны в Главе 1 ("Справочник по

библиотеке") "Справочного руководства программиста".

Управление модулем

Администратор этапа выполнения поддерживает следующие подп-

рограммы обслуживания модулей:

Подпрограммы API обслуживания модулей Таблица 17.3

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Подпрограмма і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FreeLibrary і Делает недействительным загружен-і

і і ный модуль библиотеки, и освобож-і

і і дает соответствующую память, еслиі

і і ссылок на модуль больше нет. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetModuleFileName і Дает полный маршрут и имя выполня-і

і і емого файла, задающий, откуда заг-і

і і ружен модуль. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetModuleHandle і Определяет описатель заданного мо-і

і і дуля. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetModuleUsage і Определяет счетчик ссылок на мо-і

і і дуль. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetProcAddress і Определяет адрес экспортируемойі

і і библиотечной функции. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і LoadLibrary і Загружает указанный библиотечныйі

і і модуль. і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Некоторые из этих подпрограмм воспринимают в качестве пара-

метра описатель модуля. Описатель модуля самой прикладной прог-

раммы хранится в переменной HInstance, описанной в модуле System.

Управление ресурсами

Администратор этапа выполнения поддерживает следующие подп-

рограммы управления ресурсами:

Функции API управления ресурсами Таблица 17.4

ЪДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і AccessResource і Открывает заданный выполняемый файл иі

і і перемещает указатель файла на началоі

і і заданного ресурса. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FindResource і Определяет адрес ресурса в заданномі

і і файле ресурса. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FreeResource і Уменьшает счетчик ссылок для загружен-і

і і ного ресурса. Когда значение этогоі

і і счетчика становится равным нулю, то ис-і

і і пользуемая ресурсом память освобождает-і

і і ся. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і LoadResource і Загружает заданный ресурс в память. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і LoadString і Загружает заданную строку ресурса. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і LockResource і Блокирует заданный ресурс в памяти иі

і і увеличивает его счетчик ссылок. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SizeOfResource і Возвращает размер (в байтах) заданногоі

і і ресурса. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і UnlockResource і Разблокирует заданный ресурс и уменьша-і

і і ет на 1 счетчик ссылок на ресурс. і

ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

Ресурсы могут компоноваться с прикладной программой с по-

мощью директив компилятора {$R имя_файла}. Указанные файлы должны

быть файлами ресурсов Windows (.RES). Обычно с прикладными прог-

раммами защищенного режима DOS компонуются только строковые ре-

сурсы и ресурсы, определенные пользователем. Другие типы ресурсов

Windows к прикладной программе DOS обычно неприменимы.

Примечание: Ресурсы Turbo Vision не следуют тем же

соглашениям, что ресурсы Windows, и к ним нельзя обращаться

с помощью подпрограмм API.

Некоторые подпрограммы API управления ресурсами требуют ука-

зания описателя экземпляра, которым обычно является указатель эк-

земпляра прикладной программы (который содержится в переменной

HInstance модуля System).

Управление селектором

Прикладной программе обычно не требуется манипулировать се-

лекторами, но в отдельных ситуациях полезно использовать следую-

щие подпрограммы обслуживания селектора:

Подпрограммы API управления селектором Таблица 17.5

ЪДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і AllocDStoCSAlias і Отображает селектор сегмента данных наі

і і селектор сегмента кода. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і AllocSelector і Выделяет новый селектор. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і ChangeSelector і Генерирует селектор кода, соответству-і

і і щий заданному селектору данных, илиі

і і генерирует заданный селектор, соот-і

і і ветствующий селектору кода. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FreeSelector і Освобождает селектор, первоначальноі

і і выделенный функциями AllocDStoCSAliasі

і і или AllocSelector. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetSelectorBase і Дает базовый адрес селектора. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetSelectorLimit і Возвращает предельное значение для за-і

і і данного селектора. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і PrestoChangoSelectorі Генерирует селектор кода, соответству-і

і і ющий заданному селектору данных, либоі

і і генерирует селектор данных, соответс-і

і і твующий селектору кода. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SetSelectorBase і Устанавливает базовый адрес селектора.і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SetSelectorLomit і Устанавливает предельное значение се-і

і і лектора. і

АДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Другие подпрограммы API

Администратор этапа выполнения поддерживает следующие допол-

нительные подпрограммы API:

Прочие подпрограммы API Таблица 17.6

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Функция і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і DOS3Call і Вызывает функцию прерывания DOS 21h; вызы-і

і і вается только из подпрограмм ассемблера. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і FatalExit і Передает отладчику текущее состояние опе-і

і і рационной среды защищенного режима и вы-і

і і выводит подсказку для ввода инструкций оі

і і продолжении работы. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetDOSEnviromentі Определяет текущую строку операционнойі

і і среды задачи. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetVersion і Дает текущую версию операционной средыі

і і Windows или операционной системы DOS. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і GetWinFlags і Дает используемые Windows флаги конфигура-і

і і ции памяти. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і MessageBox і Создает, выводит на экран и обслуживаеті

і і окно сообщений. і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Совместно используемая DLL, чтобы определить, выполняется ли

она в защищенном режиме DOS или под Windows, может использовать

функцию GetWinFlags, например:

if GetWinFlags and wf_DPMI <> 0 then

Message('Работа в защищенном режиме DOS')

else

Message('Работа в среде Windows');

Прямой доступ к DPMI-серверу

Прямой доступ к DPMI-серверу вы можете получить через преры-

вание $31, которое непосредственно вызывает DPMI-сервер в обход

администратора этапа выполнения. Однако это опасный прием. DPMI

не поддерживает очистку ресурсов, таких как векторы прерываний

памяти; корректно с этими проблемами работает администратор этапа

выполнения. Вы должны глубоко понимать концепции защищенного ре-

жима и знать о существенном риске, с которым связано использова-

ние данного метода доступа защищенного режима.

Компиляция прикладной программы защищенного режима

В большинстве случаев для получения прикладной программы за-

щищенного режима вам не нужно делать ничего особенного. Просто

скомпилируйте свою программу, задав в качестве целевой работы за-

щищенный режим одним из следующих способов:

* В IDE выберите команду CompileіTarget и в диалоговом окне

Target Platform (Целевая платформа) выберите

Protected-mode Application.

* При использовании компилятора, работающего в режиме ко-

мандной строки, укажите для выбора в качестве целевой

платформы защищенного режима параметр /CP.

Выполнение программы защищенного режима DOS

Когда вы выполняете программу DOS защищенного режима, нужно

обеспечить наличие в текущем каталоге или по маршруту DOS файлов

DPMI16BI.OVL (сервер DPMI), RTM.EXE (администратор этапа выполне-

ния) и всех DLL, с которыми работает ваша программа.

Примечание: Лицензионное соглашение позволяет вам

распространять файлы DPMI16BI.OVL и RTM.EXE вместе с вашей

программой.

В выполняемом файле .EXE защищенного режима DOS используется

тот же формат файла, что и в Windows 3.x и OS/2 1.x. Этот формат

файла является надмножеством обычного формата .EXE DOS и состоит

из обычного образа файла .EXE, называемого фиктивным модулем, за

которым следует расширенный заголовок и код, данные и ресурсы за-

щищенного режима. Ниже показана последовательность событий при

выполнении программы защищенного режима DOS.

1. DOS загружает фиктивный модуль реального режима и переда-

ет ему управление.

2. Если средства DPMI отсутствуют, то фиктивный модуль заг-

ружает DPMI-сервер из файла DPMI16BI.OVL. Некоторые новые

администраторы памяти поддерживают средства DPMI (как,

например, это делается в окне DOS улучшенного режима

Windows 3.х). В таких конфигурациях фиктивный модуль не

загружает DPMI-сервер, но использует уже имеющийся.

3. Далее, если администратор этапа выполнения еще не загру-

жен в память, фиктивный модуль загружает его из файла

RTM.EXE. Если прикладная программа защищенного режима вы-

полняет другую программу защищенного режима, обе исполь-

зуют одну копию администратора этапа выполнения.

4. Если средства DPMI и администратор этапа выполнения при-

сутствуют, фиктивный модуль переключается из реального в

защищенный режим и передает управление расширенному заг-

рузчику .EXE в администратора этапа выполнения.

5. Загрузчик сначала загружает используемую прикладной прог-

раммой DLL (если она имеется), затем загружает сегменты

кода и данных прикладной программы. Наконец, загрузчик

передает управление на точку входа прикладной программы.

При выполнении вашей прикладной программы защищенного режима

DOS всегда возможно ситуация, когда уже присутствует DMPI-сервер,

отличный от сервера Borland. Поскольку между серверами могут быть

небольшие различия, особенно в плане обработки прерываний DOS, вы

должны проверить программу и убедиться, что она работает со всеми

возможными серверами, которые могут ей встретиться.

Когда прикладная программа защищенного режима DOS выполняет-

ся в окне DOS улучшенного режима Windows, вы можете управлять

объемом расширенной памяти, которую выделяет администратор этапа

выполнения, задав в файле .PIF прикладной программы предельное

значение памяти XMS.

Управление объемом используемой RTM памяти

По умолчанию администратор этапа выполнения использует при

загрузке всю доступную память. Затем по запросам он выделяет па-

мять своим клиентам (через подпрограммы API администратора памя-

ти).

В защищенном режиме нет разницы между обычной памятью (ниже

1 мегабайта) и расширенной памятью (с адресами выше 1 мегабайта);

для программ защищенного режима доступны оба типа памяти. Однако

администратор этапа выполнение отдает предпочтение расширенной

памяти. Только после того как вся расширенная память будет выде-

лена, или когда прикладная программа специально запрашивает

обычную память (например, с помощью функции GlobalDosAlloc), ад-

министратор этапа выполнения выделяет обычную память.

Причина, по которой администратор этапа выполнения предпочи-

тает расширенную память, заключается в том, что прикладная прог-

рамма может с помощью вызова подпрограммы Exec в модуле Dos по-

рождать другие прикладные программы. Порожденные прикладные прог-

раммы не обязательно являются программами защищенного режима; та-

ким образом, им может потребоваться обычная память. Фактически,

порожденные программы защищенного режима запускаются как програм-

мы реального режима и переключаются в защищенный режим только

после успешной загрузки фиктивным модулем средств DPMI и адми-

нистратора этапа выполнения.

Администратор этапа выполнения перед порождением прикладной

программы пытается освободить максимальный объем обычной памяти

(например, перенеся перемещаемые блоки в расширенную память). Од-

нако попытки освобождения расширенной памяти не предпринимаются.

Таким образом, если должны порождаться прикладные программы защи-

щенного режима, не использующие администратор этапа выполнения,

то необходим споcоб управления распределением памяти администра-

тором этапа выполнения.

Чтобы управлять тем, сколько памяти может использовать адми-

нистратор этапа выполнения, в командной строке DOS добавьте к

строке операционной среды DOS переменную среды RTM:

SET RTM={параметр nnnn}

Возможные параметры перечислены в следующей таблице. Значе-

ние nnnn может быть десятичным или шестнадцатиричным числом в ви-

де xAB54 или xab54.

Параметры переменной операционной

среды RTM, используемые для управления памятью Таблица 17.7

ЪДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і Параметр і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і EXTLEAVE nnnn і Всегда оставляет не менее nnnn килобайті

і і доступной расширенной памяти. По умолча-і

і і нию это значение равно 640К. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і EXTMAX nnnn і Не выделяет более nnnn килобайт расширен-і

і і ной памяти. По умолчанию используетсяі

і і значение 4 гигабайта. В Windows использу-і

і і емое по умолчанию значение равно половинеі

і і доступной памяти. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і EXTMIN nnnn і Если после применения EXTMAX или EXTLEAVEі

і і доступно менее nnnn килобайт, то програм-і

і і ма завершается с сообщением о нехваткеі

і і памяти (Out of memory). По умолчанию этоі

і і значение равно 0. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і REALLEAVE nnnn і Всегда оставляет не менее nnnn параграфові

і і доступной реальной памяти. По умолчаниюі

і і это значение равно 64К или 4096 парагра-і

і і фов. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і REALMAX nnnn і Не выделяет более nnnn параграфов реаль-і

і і ной памяти. По умолчанию это значениеі

і і равно 1 мегабайту или 65535 параграфов. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і REALMIN nnnn і Если после применения REALMAX и REALLEAVEі

і і доступно менее nnnn параграфов, то прог-і

і і рамма завершается с сообщением о нехваткеі

і і памяти (Out of memory). По умолчанию этоі

і і значение равно 0. і

АДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Следующая команда DOS ограничивает RTM 2 мегабайтами расши-

ренной памяти и обеспечивает, что нераспределенными останутся

128К реальной памяти.


Глава 18. Строки с завершающим нулем

В Borland Pascal поддерживается класс символьных строк, ко-

торые называются строками, завершающимися нулем. Благодаря расши-

ренному синтаксису Borland Pascal и модулю Strings ваши программы

(как для DOS, так и для Windows) могут использовать строки с за-

вершающим нулем путем задания в операторе uses модуля Strings.

Что такое строка с завершающим нулем?

В Borland Pascal строки обычного типа (String) хранятся как

байт длины, за которым следует последовательность символов. Мак-

симальная длина строки в Паскале равна 255 символам. Таким обра-

зом, строка Паскаля занимает от 1 до 256 байт памяти.

Строки с завершающим нулем не содержат байта длины. Вместо

этого они состоят из последовательности ненулевых символов, за

которыми следует символ NULL (#0). Никаких ограничений на длину

строк с завершающим нулем не накладывается, но 16-разрядная архи-

тектура DOS и Windows ограничивает их размер 65535 символами.

Функции модуля Strings

Borland Pascal не имеет встроенных подпрограмм, предназна-

ченных специально для работы со строками с завершающим нулем. Эти

функции вы можете найти в модуле Strings. Среди них вы найдете

функцию StrPCopy, которую можно использовать для копирования

строки Паскаля в строку с завершающим нулем, и StrPos, используе-

мую для преобразования строки с завершающим нулем в строку Паска-

ля. Приведем краткое описание каждой функции:

Функции модуля Strings

і Функция і Описание і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrCat і Добавляет исходную строку к концу целевой стро-і

і і ки и возвращает указатель на целевую строку. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrComp і Сравнивает две строки S1 и S2. Возвращаеті

і і значение < 0, если S1 < S2, равное 0, если S1 =і

і і S2 и > 0, если S1 > S2. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrCopy і Копирует исходную строку в целевую строку иі

і і возвращает указатель на целевую строку. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrECopy і Копирует исходную строку в целевую строку иі

і і возвращает указатель на конец целевой строки. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrIComp і Сравнивает две строки без различия регистраі

і і символов. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrLCat і Присоединяет исходную строку к концу целевойі

і і строки. При этом обеспечивается, что длина ре-і

і і зультирующей строки не превышает заданного мак-і

і і симума. Возвращается указатель на строку-ре-і

і і зультат. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrLComp і Сравнивает две строки с заданной максимальнойі

і і длиной. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrLCopy і Копирует заданное число символов из исходнойі

і і строки в целевую строку и возвращает указательі

і і на целевую строку. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrEnd і Возвращает указатель на конец строки, то естьі

і і указатель на завершающий строку нулевой символ.і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrDispose і Уничтожает ранее выделенную строку. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrLen і Возвращает длину строки. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrLIComp і Сравнивает две строки с заданной максимальнойі

і і длиной без различия регистра символов. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrLower і Преобразует строку в нижний регистр и возвраща-і

і і ет указатель на нее. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrMove і Перемещает блок символов из исходной строки ві

і і целевую строку и возвращает указатель на целе-і

і і вую строку. Два блока могут перекрываться. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrNew і Выделяет для строки память в динамически рас-і

і і пределяемой области. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrPas і Преобразует строку с завершающим нулем в строкуі

і і Паскаля. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrPCopy і Копирует строку Паскаля в строку с завершающимі

і і нулем и возвращает указатель на строку с завер-і

і і шающим нулем. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrPos і Возвращает указатель на первое вхождение задан-і

і і ной подстроки в строке, или nil, если подстрокаі

і і в строке не содержится. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrRScan і Возвращает указатель на последнее вхождениеі

і і указанного символа в строку, или nil, если сим-і

і і вол в строке отсутствует. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrScan і Возвращает указатель на первое вхождение ука-і

і і занного символа в строку, или nil, если символі

і і в строке отсутствует. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і StrUpper і Преобразует строку в верхний регистр и возвра-і

і і щает указатель на нее. і

АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ

Использование строк с завершающим нулем

Строки с завершающим нулем хранятся в виде символьных масси-

вов с нулевой базой (начинающихся с 0) с индексом целого типа, то

есть в виде массива:

array[0..X] of Char;

где X - положительное ненулевое целое число. Такие массивы назы-

ваются символьными массивами с нулевой базой. Приведем некоторые

примеры описаний символьных массивов с нулевой базой, которые мо-

гут использоваться для хранения завершающихся нулем строк.

type

TIdentifier = array[0..15] of Char;

TFileName = array[0..79] of Char;

TMemoText = array[0..1023] of Char;

Более всего строки Паскаля и строки с завершающим нулем от-

личаются интенсивностью использования указателей. Borland Pascal

выполняет операции с этими указателями, используя набор правил

расширенного синтаксиса. Кроме того, в Borland Pascal имеется

встроенный тип PChar, который представляет собой указатель на

строку с завершающим нулем. В модуле System тип PChar определяет-

ся следующим образом:

type PChar = ^Char;

Правилами расширенного синтаксиса управляет директива компи-

лятора $X. В состоянии {$X+} (по умолчанию) расширенный синтаксис

разрешен. Правила расширенного синтаксиса описываются в следующих

разделах.

Символьные указатели и строковые литералы

При разрешении расширенного синтаксиса строковый литерал

совместим по присваиванию с типом PChar. Это означает, что пере-

менной типа PChar можно присвоить строковый литерал. Например:

var

P: PChar;

.

.

begin

P := 'Привет...';

end;

В результате такого присваивания указатель указывает на об-

ласть памяти, содержащую строку с завершающим нулем, являющуюся

копией строкового литерала. Компилятор записывает строковые лите-

ралы в сегмент данных, аналогично описанию "скрытых" типизирован-

ных констант:

const

TempString: array[0..14] of Char = 'Привет...'#0;

var

P: PChar;

.

.

begin

P := @TempString;

end;

Когда соответствующие формальные параметры имеют тип Char,

строковые литералы вы можете использовать как фактические пара-

метры при вызовах процедур и функций. Например, если имеется про-

цедура с описанием:

procedure PrintStr(Str: PChar);

то допустимы следующие вызовы процедуры:

procedure PrintStr('Строка для проверки');

PrintStr(#10#13);

Аналогично тому, как это происходит при присваивании, компи-

лятор генерирует строку с завершающим нулем, представляющую собой

копию литеральной строки в сегменте данных, и передает указатель

на эту область памяти в параметре Str процедуры PrintStr.

Наконец, типизированная константа типа PChar может инициали-

зироваться строковой константой. Это справедливо также для струк-

турных типов, таких как массивы PChar и записи, а также объекты

PChar.

const

Message: PChar = 'Program terminated';

Prompt: PChar = 'Enter values: ';

Digits; array [0..9] of PChar = {

'Zero', 'One', 'Two', 'Three', 'Four', 'Five',

'Six', 'Seven', Eight', 'Nine'};

Строковая выражение-константа всегда вычисляется как строка

Паскаля, даже если она инициализируется как типизированная конс-

танта типа PChar. Таким образом, строковое выражение-константа

всегда ограничено длиной в 255 символов.

Символьные указатели и символьные массивы

Если вы с помощью директивы $X разрешаете расширенный син-

таксис, то символьный массив с нулевой базой совместим с типом

PChar. Это означает, что там, где предполагается использование

типа PChar, может использоваться символьный массив с нулевой ба-

зой. Когда символьный массив используется вместо значения PChar,

компилятор преобразует символьный массив в указатель-константу,

значение которой соответствует адресу первого элемента массива.

Например:

var

A: array[0..63] of Char;

P: PChar;

.

.

.

begin

P := A;

PrintStr(A);

PrintStr(P);

end;

Благодаря оператору присваивания P теперь указывает на пер-

вый элемент массива A, поэтому PrintStr вызывается дважды с одним

и тем же значением.

Вы можете инициализировать типизованную константу, имеющую

тип символьного массива с нулевой базой, с помощью строкового ли-

терала, имеющего меньшую длину, чем размер массива. Оставшиеся

символы устанавливаются в значение NULL (#0), и массив будет со-

держать строку с завершающим нулем.

type

TFileName = array[0..79] of Char;

const

FileNameBuf: TfileName = 'TEST.PAS';

FileNamePtr: PCahr = FileNameBuf;

Индексирование символьного указателя

Так как символьный массив с нулевой базой совместим с сим-

вольным указателем, символьный указатель можно индексировать ана-

логично символьному массиву с нулевой базой.

var

A: array[0..63] of Char;

P: PChar;

Ch: Char;

.

.

.

begin

P := A;

Ch := A[5];

Ch := P[5];

end;

Оба последних присваивания присваивают Ch значение, содержа-

щееся в шестом символе-элементе A.

При индексировании символьного указателя индекс задает безз-

наковое смещение, которое добавляется к указателю перед его разы-

менованием. Таким образом, P[0] эквивалентно P^ и задает символ,

на который указывает P. P[1] задает символ справа от того, на ко-

торый указывает P, P[2] задает следующий символ и т.д. Для целей

индексирования PChar ведет себя таким образом, как если бы он

описывался:

type

TCharArray = array[0..65535] of Char;

Pchar = ^TCharArray;

Компилятор при индексировании символьного указателя не вы-

полняет проверку диапазона, так как у него нет информации о типе,

по которой можно определить максимальную длину строки с завершаю-

щим нулем, на которую указывает символьный указатель.

Показанная ниже функция StrUpper иллюстрирует использование

символьного указателя для преобразования строки с завершающим ну-

лем в верхний регистр.

function StrUpper(Srt: Pchar): Pchar;

var

I: Word;

begin

I := 0;

while Str[I] <> #0 do

begin

Str[I] := UpCase(Str[I]);

Inc(I);

end;

StrUpper := Str;

end;

Обратите внимание, что StrUppper - это функция, а не проце-

дура, и что она всегда возвращает значение, которое передавалось

ей в качестве параметра. Так как расширенный синтаксис допускает

игнорирование результата функции, StrUpper может интерпретиро-

ваться, как процедура:

StrUpper(A);

PrintStr(A);

Однако, StrUpper всегда возвращает передаваемое ей значение,

приведенные выше операторы можно скомбинировать в один:

PrintStr(StrUpper(A));

Вложенные вызовы функций работы со строками с завершающим

нулем могут оказаться очень удобными, когда вы хотите указать оп-

ределенную взаимосвязь между последовательными операциями со

строками.

Операции с символьными указателями

Расширенный синтаксис Borland Pascal позволяет использовать

для работы с символьными указателями отдельные операции. Для уве-

личения или уменьшения смещения в значении указателя можно ис-

пользовать операции плюс (+) и минус (-). Операцию минус (-) мож-

но использовать для вычисления расстояния (разности смещений)

между двумя символьными указателями. Предположим, что P и Q

представляют собой значения тип PChar, а I - значение типа Word.

Тогда допустимы следующие конструкции:

P + I I прибавляется к смещению P

I + P I прибавляется к смещению P

P - I I вычитается из смещения P

P - Q Смещение Q вычитается из смещения P

В операциях P + I и I + P I прибавляется к адресу, задавае-

мому P. При этом получается указатель, который указывает на I

символов после P. В операции P - I I вычитается из адреса, зада-

ваемого P, и получается указатель, указывающий на I символов до

P.

Операция P - Q вычисляет расстояние между Q (младший адрес)

и P (старший адрес). При этом возвращается результат типа Word,

показывающий число символов между Q и P. Эта операция предполага-

ет, что P и Q указывают на один и тот же массив символов. Если

эти два указателя указывают на разные символьные массивы, то ре-

зультат непредсказуем.

Стандартный синтаксис Borland Pascal позволяет при сравнении

указателей определять только их равенство или неравенство. Расши-

ренный синтаксис (разрешенный по директиве компилятора {$X+})

позволяет применять операции <, >, <= и <= к значениям PChar. За-

метим, однако, что при таких проверках предполагается, что два

сравниваемых указателя указывают на один и тот же массив симво-

лов. По этой причине сравниваются только смещения указателей. Ес-

ли два указателя указывают на различные символьные массивы, то

результат не определен.

var

A, B: array[0..79] of Char;

P, Q: PChar;

begin

P := A; { P указывает на A[0] }

Q := A + 5; { Q указывает на A[5] }

if P < Q then ...; { допустимая проверка,

результат - True }

Q := B; { Q указывает на B[0] }

if P < Q then ...; { результат не определен }

end;

Подробнее об операциях с PChar рассказывается в Главе 6.

Строки с завершающим нулем и стандартные процедуры

Расширенный синтаксис Borland Pascal позволяет применять к

символьным массивам с нулевой базой стандартные процедуры Read,

ReadLn и Val, а к символьным массива с нулевой базой и символьным

указателям - стандартные процедуры Write, WriteLn, Val, Assign и

Rename. Более подробные описания этих процедур можно найти в Гла-

ве 1 ("Справочник по библиотеке") "Справочного руководства прог-

раммиста".

Пример использования функций с завершающим нулем

Приведем пример исходного кода, показывающий, как можно ис-

пользовать некоторые функции обработки строк. Этот пример исполь-

зован при разработке функции FileSplit в модуле WinDos.

{ максимальные размеры компонентов имени файла }

const

fsPathName = 79; { имя маршрута }

fsDirectory = 67; { имя каталога }

fsFileName = 8; { имя файла }

fsExtension = 4; { расширение имени файла }

{ флаги, возвращаемые FileSplit }

const

fcWildcards = $0008 { трафаретные символы }

fcDirectory = $0004 { имя каталога }

fcFileName = $0002 { имя файла }

fcExtension = $0001 { расширение имени файла }

{ FileSplit разбивает имя файла, заданное маршрутом, на три }

{ компонента. Dir принимает значение диска и каталога с }

{ предшествующей и завершающей обратной косой чертой, Name }

{ принимает значение имени файла, а Ext - расширения с }

{ предшествующей точкой. Если компонент строки-параметра }

{ равен NIL, то соответствующая часть маршрута не }

{ записывается. Если маршрут не содержит данного компонента, }

{ то возвращаемая строка компонента будет пустой. }

{ Максимальные длины строк, возвращаемых в Dir, Name и Ext, }

{ определяются битовыми масками fsDirectory, fsFileName, }

{ fsExtension. Возвращаемое значение представляет собой }

{ комбинацию битовых масок fсDirectory, fсFileName и }

{ fсExtension, показывающую, какие компоненты присутствуют в }

{ маршруте. Если имя и расширение содержат трафаретные }

{ символы (* и ?), то в возвращаемом значении устанавливается }

{ флаг fcWildcards. }

function FileSplit(Path, Dir, Name, Ext: PChar): Word;

var

DirLen, NameLEn, Flags: Word;

NamePtr, ExtPtr: PChar;

begin

NamePtr := StrRScan(Path, '/');

if NamePtr = nil then NamePtr := StrRScan(Path, ':');

if NamePtr = nil then NamePtr := Path else Inc(NamePtr);

ExtPtr := StrScan(NamePtr, '.');

if ExtPtr = nil then ExtPtr := StrEnd(NamePtr);

DirLen := NamePtr - Path;

if DirLen > fsDirectory then DirLen := fsDirectory;

NameLen := ExtPtr - NamePtr;

if NameLen > fsFilename then NameLen := fsFileName;

Flags := 0;

if (StrScan(NamePtr, '?') <> nil) or

(StrScan(NamePtr, '*') <> nil) then

Falgs := fcWildcards;

if DirLen <> 0 then Flags := Flags or fcDirectory;

if NameLen <> 0 then Flags := Flags or fcFilename;

if ExtPtr[0] <> #0 then Flags := Flags or fcExtension;

if Dir <> nil then StrLCopy(Dir, Path, DirLen);

if Name <> nil then StrLCopy(Name, NamePtr, NameLen);

if Ext <> nil then StrLCopy(Ext, ExtPtr, fsExtension);

FileSplit := Flags:

end;


Глава 19. Использование графического интерфейса Borland

Модуль Graph реализует полную библиотеку из более чем 50

графических программ - от вызовов процедур и функций высокого

уровня, как, например, SetViewPort, Bаr3D, DrаwPoly, до программ,

ориентированных на работу с битами, таких, как GetImage или

РutImage. Поддерживается несколько видов закрашивания и типов ли-

ний, и имеется несколько шрифтов, которые можно изменять по вели-

чине, выравнивать и ориентировать горизонтально или вертикально.

Для компиляции программы, использующей модуль Grаph, вам не

потребуется никаких внешних файлов (кроме, конечно, исходного

текста вашей программы, компилятора и доступа к стандартным моду-

лям в библиотеке исполняющей системы).

Имена библиотек и модуля Graph

Таблица 19.1

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДї

і Тип программы і Библиотека і Имя модуля Graph і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і Реальный режим і TURBO.TPL і GRAPH.TPU і

і Защищенный режим і TPP.TPL і GRAPH.TPP і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДЩ

Для запуска программы, использующей модуль Grарh, кроме ва-

шей программы с расширением .EXE вам потребуются один или более

графических драйверов (см. далее файлы .BGI). Кроме того вам пот-

ребуется также один или более файлов шрифтов (.CНR), если в вашей

программе используются какие-либо шрифты.

Примечание: В соответствии с лицензионными условиями

вы можете распространять файлы .CHR и .BGI наряду со своими

программами.

Драйверы

ДДДДДДД

Для перечисленных ниже графических адаптеров и полностью

совместимых с ними предусмотрены следующие графические драйверы:

CGA Неrcules

МСGA AT&T 400

EGA 3270 PC

VGA IBM 8514

Каждый драйвер содержит выполняемый код и данные и хранится

в отдельном файле на диске. Во время работы процедура InitGraph

идентифицирует графическую аппаратуру и производит загрузку и

инициализацию соответствующего графического драйвера, переводит

систему в графический режим, а затем возвращает управление вызы-

вающей программе. Процедура CloseGraph выгружает драйвер из памя-

ти и восстанавливает предыдущий видеорежим. С помощью программ

RеstoreCrtMode и SetGraphMode вы можете переключаться между текс-

товым и графическим режимом. См. Главу 1 в "Справочном руководс-

тве программиста".

Модуль Grаph может также работать на компьютерах с двумя мо-

ниторами. При инициализации модуля Graph с помощью процедуры

InitGraph для графического драйвера и требуемого режима будет

выбран нужный монитор. При завершении работы графической програм-

мы предыдущий видеорежим будет восстановлен. Если для графической

аппаратуры с двумя мониторами требуется автоматическое распозна-

вание, то процедура InitGraph выберет монитор и графическую пла-

ту, при которой будет получаться наилучшее качество выводимой

графической информации.

ЪДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДї

і Драйвер і Аппаратура і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДґ

і CGA.BGI і Драйвер для адаптеров CGA, MCGA фирмы IBM. і

і EGAVGA.BGI і Драйвер для адаптеров EGA, VGA фирмы IBM. і

і HERC.BGI і Драйвер для монохромного адаптера Hercules фир-і

і і мы IBM. і

і ATT.BGI і Драйвер для AT&T 6300 (400 строк). і

і PC3270.BGI і Драйвер для IBM 3270 РС. і

і IBM8514.BGI і Драйвер для IBM 8514. і

АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ

Поддержка устройства IBM 8514

Borland Pascal поддерживает графическую плату IBM 8514, ко-

торая представляет собой новую графическую плату с высоким разре-

шением, позволяющую получить разрешающую способность до 1024х768

точек и палитру, содержащую 256 оттенков из 256 цветов. Файл

драйвера для этой графической платы называется IBM8514.BGI.

Графическая плата IBM 8514 не может правильно распознаваться

Borland Pascal при автоматическом обнаружении (она будет распоз-

наваться алгоритмами автообнаружения, как плата VGA). Таким обра-

зом, чтобы использовать плату IBM 8514, переменной GraphDriver

при вызове InitGraph нужно присвоить значение IBM8514 (которое

определено в модуле Graph). При работе с платой IBM 8514 не сле-

дует использовать с InitGraph DetectGraph или DETECT (если только

вы не хотите эмулировать режим VGA).

Для платы IBM 8514 поддерживаются следующие режимы: IBM8514LO

(640х480 элементов изображения) и IBM8514HI (1024х768 элементов

изображения). Обе константы режима определены в интерфейсной час-

ти GRAPH.TPU.

Для определения цветов в плате IBM 8514 используются три

6-битовых значения. Для каждого определяемого цвета имеются 6-би-

товые компоненты Red (красный), Green (зеленый) и Blue (голубой).

Для того, чтобы при работе с графической платой IBM 8514 пользо-

ватель мог задавать цвета, в библиотеку BGI добавлена новая прог-

рамма. Эта программа определяется в модуле GRAPH.TPU следующим

образом:

procedure SetRGBPalette(ColorNum, Red, Green, Blue: Word);

Аргумент ColorNum задает запись палитры, которую нужно загру-

зить. Этот аргумент представляет собой целое значение в диапазоне

от 0 до 255 (дес.). Аргументы Red, Green и Blue определяют компо-

ненты цветов в записи палитры. Используется только младший байт

этих значений и только 6 старших битов этого байта загружаются в

палитру.

Другие программы, модифицирующие палитру (SetAllPalette,

SetPalette, GetPalette), при работе с графической платой IBM 8514

использовать не следует.

Для совместимости с графическими адаптерами фирмы IBM драйве-

ры формата BGI определяют для первых 16 цветов палитры IBM 8514

значения цветов, принятые по умолчанию для адаптеров EGA/VGA. Эти

значения могут использоваться в неизмененном виде или модифициро-

ваться с помощью процедуры SetGRBPalette.

Система координат

По соглашению верхний левый угол экрана имеет координату

(0,0). У более правого столбца координата х больше, у более ниж-

ней строки больше координата y. То есть координата х увеличивает-

ся при перемещении вправо, а координата y - при перемещении вниз.

Таким образом координаты каждого из четырех углов и конкретной

точки (середины экрана) будут выглядеть следующим образом:

(0,0) (319,0)

ЪДДДДДДДДДДДДДДДДДДДДДДДї

і і

і (159,99) і

і . і

і і

і і

і і

АДДДДДДДДДДДДДДДДДДДДДДДЩ

(0,199) (319,199)

Рис. 19.1 Экран с координатами xy.

Текущий указатель

Понятие текущего указателя используется во многих графичес-

ких системах. Понятие текущего указателя аналогично понятию кур-

сора для текстового режима, за исключением того, что текущий ука-

затель невидим.

Write('ABC');

В текстовом режиме предшествующий оператор Write оставит

курсор в колонке, непосредственно следующим за буквой C. Если

буква C была введена в колонке 80, то курсор перейдет на колонку

1 следующей строки. Если буква c была введена в позиции 80 строки

25, то произойдет пролистывание (прокрутка) экрана вверх на 1

строку и курсор будет находится в 1 позиции 25 строки.

MoveTo(0,0);

LineTo(20,20)

В графическом режиме данный оператор LinеТо оставит текущий

указатель в последней заданной точке (20,20). Если действует ре-

жим отсечения, то реально выводимая прямая будет отсечена до те-

кущей точки. Заметим, что текущий указатель никогда не отсекает-

ся.

Команда МоvеТо является эквивалентом команды GotoXY. Единс-

твенное ее назначение - это перемещение текущего указателя. Пере-

мещение текущего указателя может использоваться только в следую-

щих командах, использующих текущий указатель: MoveTo, InitGraph,

MoveRel, LineTo, LineRel, OutText, SetGraphMode, ClearDevice,

SetViewPort и ClearViewPort. Последние 5 из них перемещают теку-

щий указатель в точку (0,0).

Текст

В графическом режиме для вывода текста используется шрифт с

растром 8х8 и несколько векторных шрифтов. Растровый символ зада-

ется с помощью матрицы элементов изображения. Векторный шрифт за-

дается рядом векторов, которые указывают графической системе, как

рисовать шрифт.

Преимущество использования векторных шрифтов становится оче-

видным, когда вы начинаете рисовать большие символы. Поскольку

штриховой шрифт задается векторами, то при увеличении шрифта ка-

чество и разрешение остаются, тем не менее, хорошими.

Когда увеличивается растровый шрифт, то матрица умножается

на масштабный коэффициент, а когда этот масштабный коэффициент

увеличивается, разрешение у символов становится более грубым. Для

маленьких шрифтов растровый шрифт должен быть достаточно приемле-

мым, но для больших шрифтов вы, вероятно, захотите выбрать век-

торный шрифт.

Выравнивание графического текста управляется процедурой

SetTextJustify. Масштабирование и выбор шрифта осуществляется с

помощью процедуры SetTextStyle. Графический текст выводится с по-

мощью процедур ОutText или ОutTextХY. Запрос о текущих установ-

ленных для текста параметрах выполняется с помощью обращения к

процедуре GetTextSettings. Векторные шрифты хранятся каждый в от-

дельном файле на диске и должны присутствовать там во время рабо-

ты (при вызове процедуры SetTextStyle). Размер векторного шрифта

можно настроить с помощью процедуры SetUserCharSize. Файлы шриф-

тов (которые имеют расширение .CHR) могут загружаться с диска ав-

томатически модулем Graph, или их можно компоновать с программой

пользователя или загружать и "регистрировать" с помощью модуля

Graph.

Для преобразования файла шрифта (или любого другого предназ-

наченного для этой цели двоичного файла данных) в файл .OBJ, ко-

торый можно компоновать с модулем или программой с помощью дирек-

тивы компилятора $L в Borland Pascal предусмотрена специальная

утилита BINOBJ.EXE. При этом становится возможным поместить все

файлы шрифтов в выполняемый файл .EXE (см. комментарии в начале

примера программы BGILINK.PAS на дистрибутивном диске).

Графические изображения и их виды

Для вычерчивания и закрашивания графических изображения,

включая точки, прямые, окружности, дуги, эллипсы, прямоугольники,

многоугольники, штриховку, трехмерную штриховку и секторы, имеет-

ся целый ряд обеспечивающих программ. Для управления видом линии

- будет она тонкой или толстой, непрерывной или состоящей из то-

чек, или же построенной по вашему собственному образцу - можно

использовать процедуру SetLineStyle.

Для закрашивания области или многоугольника пересекающейся

штриховкой или чем-либо более сложным можно использовать процеду-

ры SetFillStyle, SetFloodPattern, FillPoly и FloodFill.

Области просмотра и двоичные образы

Процедура ViewPoint позволяет всем командам вывода работать

в прямоугольной области экрана. Графики, прямые и другие графи-

ческие изображения (весь графический вывод) связывается с об-

ластью просмотра, пока эта область не изменяется. Предусмотрены

программы для очистки области просмотра и считывания ее текущих

определений. Если задан режим отсечения, то весь графический вы-

вод отсечется до текущей точки. Заметим, что текущий указатель

никогда не отсекается.

Для считывания и вывода элементов изображения предусмотрены

процедуры GetPixel и Putpixel. Чтобы сохранить и восстановить на

экране прямоугольную область, можно использовать процедуры

GetImage и PutImage. Они обеспечивают полное выполнение операций

процедуры ВitВlt (нормальное, хоr, оr, аnd, nоt).

Поддержка страниц и цветов

Имеется много других поддерживающих программ, включая под-

держку для нескольких графических страниц (только для адаптеров

EGA, VGA и Неrcules; это особенно полезно при использовании в

мультипликации), палитры, цвета и так далее.

Обработка ошибок

Внутренние ошибки модуля Graph возвращаются функцией

GraphResult. Эта функция возвращает код ошибки, показывающий сос-

тояние последней графической операции. Коды возврата приведены

в разделе по GraphResult в Главе 1 ("Справочник по библиотеки")

"Справочного руководства программиста".

Значение кода возврата для функции GraphResult устанавлива-

ется следующими процедурами:

DetectGraph SetTextStile SetAllPalette

InitGraph SetGraphMode SetFillPattern

FloodFill CloseGraph SetFillStyle

FillPoly GetGraphMode SetGraphBufSize

DrawPoly ImageSize SetGraphMode

Bar InstallUserDriver SetLineStyle

Bаr3D InstallUserFont SetPalette

PieSlice RegisterBGIDriver SetTextJustify

ClearViewPort RegisterGBIFont

Заметим, что функция GraphResult после обращения к ней сбра-

сывает код ошибки в 0. Таким образом, пользователь должен сохра-

нить значение кода ошибки во временной переменной и затем прове-

рить его.

Начало работы

Приведем пример простой графической программы:

1 program GraphTest;

2 uses

3 Graph;

4 var

5 GraphDriver : integer;

6 GraphMode : integer;

7 ErrorCode : integer;

8 begin

9 GraphDriver := Detect; { Установить флаг: выполнить

распознавание }

10 InitGraph(GraphDriver, GraphMode, 'C:\DRIVERS');

11 ErrorCode := GraphResult;

12 if ErrorCode <> grOk then { ошибка? }

13 begin

14 Writeln('Ошибка графики: ',GraphErrorMsg(ErrorCode);

15 Writeln('Программа аварийно завершила работу...');

16 Halt(1);

17 end;

18 Rectangle(0, 0, GetMaxX, GetMaxY); { нарисовать рамку

размером в экран }

19 SetTextJustify(CenterText, CenterText); { центрирова-

ние текста }

20 SetTextStyle(DefaultFont, HorizDir, 3);

21 OutTextXY(GetMaxX div 2, GetMaxY div 2, { центр экрана }

22 'Графический интерфейс фирмы Borland (BGI)');

23 Readln;

24 CloseGraph;

25 end. { GraphTest }

Программа начинается с обращения к процедуре InitGraph, ко-

торая автоматически проверяет наличие аппаратуры и загружает со-

ответствующий графический драйвер (находящийся в каталоге C:

DRIVERS). Если графическая аппаратура не распознана или в процес-

се инициализации произошла ошибка, то на экран выводится сообще-

ние об ошибке и программа прекращает работу. В противном случае

вдоль краев экрана рисуется прямоугольник и в центре экрана выво-

дится текст.

Плата AT&T 400 или IBM 8514 не распознается автоматически.

Тем не менее, вы можете пользоваться драйвером графики AT&T путем

отмены автоматической проверки, пересылки исполняемого кода драй-

вера AT&T процедуре InitGraph и установки допустимого графическо-

го режима. Замените 8 и 9 строку в предыдущем примере следующими

тремя строками:

GraphDriver := ATT400;

GraphMode := ATT400Hi;

InitGraph(GraphDriver, GraphMode, 'C:\BP\BGI');

Это укажет графической системе на необходимость загрузки

драйвера устройства AT&T400, расположенного в каталоге C:\BP\BGI,

и установит графический режим 640 на 400.

Приведем еще один пример, который показывает, как можно пе-

реключаться между графическим и текстовым режимами:

1 program GraphTest;

2 uses

3 Graph;

4 var

5 GraphDriver : integer;

6 GraphMode : integer;

7 ErrorCode : integer;

8 begin

9 GraphDriver := Detect; { Установить флаг: выполнить

распознавание }

10 InitGraph(GraphDriver, GraphMode, 'C:\DRIVERS');

11 ErrorCode := GraphResult;

12 if ErrorCode <> grOk then { ошибка? }

13 begin

14 Writeln('Ошибка графики: ',GraphErrorMsg(ErrorCode);

15 Writeln('Программа аварийно завершила работу...');

16 Helt(1);

17 end;

18 OutText('Графический режим. Нажмите <RETURN>');

19 Readln;

20 RestoreCrtMode;

21 Write('Текстовый режим. Нажмите <RETURN>');

22 Readln;

23 SetGraphMode(GraphMode);

24 OutText('Снова графический режим. Нажмите <RETURN>');

25 Readln;

26 CloseGraph;

27 end. { GraphTest }

Заметим, что вызов процедуры SetGraphMode на строке 23 сбра-

сывает все графические параметры (палитра, текущий указатель, ос-

новной и фоновый цвета и т.д.) и им присваиваются принятые по

умолчанию значения.

Вызов CloseGraph восстанавливает первоначально обнаруженный

видеорежим (InitGraph) и освобождает память, используемую графи-

ческим драйвером.

Пользовательские программы управления динамически распределяемой памятью

<

Для модуля Graph предусмотрены две программы управления ди-

намически распределяемой областью GraphFrееМем и GraphGetМем.

Первая из них освобождает память, распределенную для драйверов, а

вторая - распределяет память для драйверов графических устройств.

Стандартные программы имеют следующий вид:

procedure GraphGetMem(var P : Pointer; Size : word);

{ выделить память для драйверов графических устройств }

procedure GraphFreeMem(var P : Pointer; Size : word);

{ освободить память для драйверов графических устройств }

В модуле Graph имеются два указателя, которые по умолчанию

указывают на две описанные здесь стандартные подпрограммы. Эти

указатель определяются следующим образом:

var

GraphGetMemPtr : pointer;

{ указатель на программу распределения памяти }

GraphFreeMemPtr : pointer;

{ указатель на программу освобождения памяти }

Во время инициализации модуля Graph эти указатели ссылаются

на стандартные графические программы распределения/освобождения,

которые определяются в секции реализации модуля Graph. Память

распределятся в трех различных целях:

* для многоцелевых графических буферов, размер которых уста-

навливается вызовов SegGraphBufSize (по умолчанию это 4К);

* для драйвера устройства, загружаемого InitGraph (файлы

*.BGI);

* для файла векторного шрифта, загруженного SetTextStyle

(файлы *.CHR).

Графический буфер всегда выделяется в динамически распреде-

ляемой области памяти. Память для драйвера устройства выделяется

в динамической памяти, если программа не загружает его или не

компонуется с ним вызовом RegisterBGIdriver. При выборе векторно-

го шрифта с помощью SetTextStyle также выделяется память в дина-

мически распределяемой области (если ваша программа не компонует-

ся со шрифтом и не использует RegisterBGIfont).

Чтобы задать ваше собственное управление памятью, в модуле

Graph вы можете изменить значения этих указателей так, чтобы они

ссылались на ваши собственные программы. Программы, заданные

пользователем, должны иметь тот же список параметров, что и стан-

дартные программы, и должны иметь дальний тип вызова. Приведем

далее пример заданных пользователем программ распределения и ос-

вобождения памяти. Заметим, что при использовании процедуры Eхit

автоматически вызывается процедура CloseGraph.

program UserHeapManegement;

{ программа показывает, как пользователь может работать с

подпрограммами управления динамически распределяемой об-

ластью памяти, используемыми в модуле Graph }

uses

Graph;

var

GraphDriver, GraphMode : Integer;

ErrorCode : Integer; { используется для

сохранения кода возврата функции GraphResult }

PreGraphExitProc : Pointer { используется для сох-

ранения исходной процедуры выхода }

{ процедуры пользователя должны использовать дальний тип

обращения }

procedure MyGetMem(var P : Pointer; Size : word); far;

{ выделить память для драйверов графических устройств }

begin

Write('Была вызвана процедура ',

'MyGetMem, нажмите <Enter>:');

GetMem(P, Size);

end; { MyGetMem }

procedure MyFreeMem(ver P : Pointer; Size : word); far;

{ освободить память, занятую драйверами графических

устройств }

begin

RestoreCRTMode;

Write('Была вызвана процедура MyFreeMem, нажмите ',

'<Enter>:'); Readln;

if P <> Nil Then { не освобождать пустые указатели }

begin

FreeMem(P, Size);

P := Nil;

end; { MyFreeMem }

procedure MyExitProc; far;

{ процедура всегда получает вызов при прекращении работы

программы }

begin

ExitProc := PreGraphExitProc; { восстановить исходную

процедуру выхода }

CloseGraph; { очистить динамически распределяемую

область }

end; { MyExitProc }

begin

{ инициализировать программу очистки памяти }

PreGraphExitProc := ExitProc;

ExitProc := @MyExitProc;

GraphGetMemPtr := @MyGetMem ; { заменить распределение

памяти }

GraphFreeMemPtr := @MyFreeMem ; { заменить освобождение

памяти }

GraphDriver := Detect;

InitGraph(GraphDriver, GraphMode, '');

ErrorCode := GraphResult;

if ErrorCode <> grOk then

begin

Writeln('Графическая ошибка: ' GraphErrorMsg(ErrorCode);

Readln;

Halt(1);

end;

Line(0, 0, GetMaxX, GetMaxY);

OutText(1, 1, 'Нажмите клавишу <Enter>:');

Readln;

end. { UserHeapManegement }

Если целевой платформой является защищенный режим DOS, то

при использовании подобных программ следует иметь в виду следую-

щее: вы должны обеспечить, что любой возвращаемый GetMem указа-

тель должен иметь нулевое смещение. Сделать это можно с помощью

функции GlobalAllocPtr:

procedure MyGetMem(var P: Pointer; Size: Word); far;

var

P: Pointer;

bagin

P:= GlobalAllocPtr(HeapAllocFlags, Size);

GetMem(P, 4096);

end; { MyGetMem }

Процедуры модуля Graph

Таблица 19.3

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДї

і Подпрограмма і Описание і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Arс і Рисует дугу окружности от начального углаі

і і до конечного угла; точка (x,y) берется ві

і і качестве центра окружности. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Bаr і Рисует столбец, используя текущий тип зак-і

і і раски. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Bаr3D і Рисует трехмерный столбец, используя те-і

і і кущий тип закраски. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Circlе і Рисует окружность с центром в точке (x,y).і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і ClearDeviсе і Сбрасывает текущие параметры, установлен-і

і і ные для устройства вывода, и подготавлива-і

і і ет его для вывода. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і ClearViewPort і Очищает текущую область просмотра (окноі

і і экрана). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і CloseGraph і Выполняет останов графической системы. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і DetectGraph і Распознает аппаратуру и определяет, какойі

і і графический драйвер и режим нужно исполь-і

і і зовать. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і DrawPoly і Рисует многоугольник, используя текущийі

і і тип линии и цвет. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Ellipse і Рисует эллиптическую дугу от начальногоі

і і угла до конечного угла, использую (Х,Y),і

і і как точку центра. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і FillPoly і Закрашивает многоугольник, используя пре-і

і і образователь развертки. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і FloodFill і Закрашивает ограниченную область, исполь-і

і і зуя текущий образец закраски. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetArcCoords і Позволяет пользователю запрашивать коор-і

і і динаты последней команды Arс. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetAspectRatio і Возвращает действующее разрешение графи- і

і і ческого экрана, на основе которого можеті

і і быть вычислен коэффициент относительногоі

і і удлинения (Хаsр,Yаsр). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetBkСоlor і Возвращает текущий фоновый цвет. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetCоlor і Возвращает текущий цвет рисунка. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetDefaultPaletteі В записи типа PaletteType возвращает ис-і

і і пользуемую по умолчанию палитру. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetDriverName і Возвращает строку, содержащую имя те-і

і і кущего драйвера. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetFillPattern і Возвращает последний образец заполнителя,і

і і установленный с помощью обращения к проце-і

і і дуре SetFillPattern. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetFillSetting і Позволяет пользователю выполнить запрос оі

і і текущем образце и цвете закраски, установ-і

і і ленными с помощью процедур SetFillStyle иі

і і SetFillPattern. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetImage і Сохраняет двоичный образ заданной областиі

і і в буфере. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetGraphMode і Возвращает текущий графический режим. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetLineSettings і Возвращает текущий тип линии, образец ли-і

і і нии и толщину линии, заданные процедуройі

і і SetLineStyle. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetMaxColor і Возвращает максимальное значение цвета,і

і і которое можно передать процедуре SetColor.і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetMAxMode і Возвращает максимальный номер режима дляі

і і текущего загруженного драйвера. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetМахХ і Возвращает для текущего графического драй-і

і і вера и режима самую правую колонку (разре-і

і і шение по х). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetМахY і Возвращает для текущего графического драй-і

і і вера и режима самую нижнюю строку (разре-і

і і шение по у). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetPaletteSize і Возвращает размер таблицы просмотра палит-і

і і ры. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetPixel і Возвращает значение элемента изображения ві

і і точке Х,Y. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetPalette і Возвращает текущую палитру и ее размер. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetTextSettings і Возвращает текущий текстовый шрифт, нап-і

і і равление, размер и выравнивание для него,і

і і установленные с помощью процедурі

і і SetTextStyle и SetTextJustify. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetViewSettings і Позволяет пользователю выдать запрос о те-і

і і кущей области изображения и параметрах от-і

і і сечения изображения. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetХ і Возвращает координату Х текущей позицииі

і і (текущего указателя). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GetY і Возвращает координату Y текущей позицииі

і і (текущего указателя). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GraphErrorMsg і Для заданного кода ошибки возвращает стро-і

і і ку сообщения об ошибке. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і GraphResult і Возвращает код ошибки для последней гра-і

і і фической операции. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і InitGraph і Инициализирует графическую систему и пе-і

і і реводит аппаратуру в графический режим. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і ImageSize і Возвращает число байт, которые требуют-і

і і ся для сохранения прямоугольной областиі

і і экрана. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і InstallUserDriverі Устанавливает добавленный пользователемі

і і драйвер в таблице драйверов устройств BGI.і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і InstallUserFont і Устанавливает новый файл шрифта, не встро-і

і і енный в графическую систему. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і InitGraph і Инициализирует графическую систему и пере-і

і і водит аппаратные средства в графическийі

і і режим. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Line і Рисует прямую линию из точки (x1,y1) ві

і і (x2,y2). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і LineRel і Рисует прямую линию до точки, представ-і

і і ляющей собой относительное расстояние оті

і і текущего указателя. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і LinеTо і Рисует линию из текущего положения ві

і і точку (x,y). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і МоveRеl і Перемещает текущий указатель на расстоя-і

і і ние, являющееся относительным расстояниемі

і і от текущей позиции. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і МоvеТо і Перемещает текущий указатель в точкуі

і і (x,y). і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і ОutText і Посылает строку на устройство вывода, на-і

і і чиная с текущего указателя. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і ОutTextХY і Посылает строку на устройство вывода. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і PieSlice і Рисует сектор. Точка (Х,Y) используется ві

і і качестве центра, а сектор рисуется от на-і

і і чального до конечного угла. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і РutImagе і Выводит на экран двоичный образ. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і РutРiхеl і Строит элемент изображения в точке x,y. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і Rесtanglе і Рисует прямоугольник, используя текущийі

і і тип линии и цвет. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і RegisterBGIDriverі Регистрирует допустимый драйвер (форматаі

і і BGI) в графической системе. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і RegisterBGIFont і Регистрирует в графической системе допус-і

і і тимый (формата BGI) шрифт. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і RеstoreCRTМоdе і Восстанавливает исходный режим экрана,і

і і который был установлен при инициализацииі

і і графики. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetActivePage і Устанавливает для графического вывода ак-і

і і тивную страницу. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetAllPalette і Изменяет все цвета палитры, как было ука-і

і і зано. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetAspectRatio і Изменяет принятый по умолчанию коэффициенті

і і относительного удлинения. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetBkСоlor і Используя палитру, устанавливает текущийі

і і фоновый цвет. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetColor і Используя палитру, устанавливает текущийі

і і цвет рисунка. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetFillPattern і Выбирает образец закраски, заданный поль-і

і і зователем. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetFillStyle і Устанавливает образец закраски и ее цвет. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetGraphBufSize і Позволяет изменить размер буфера, исполь-і

і і зуемого для опроса и закраски. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetGraphMode і Переключает систему в графический режимі

і і и очищает экран. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetLineStyle і Устанавливает текущий тип линии и ее ши-і

і і рину. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetPalette і Изменяет один цвет палитры, заданный пе-і

і і ременными Colornum и Color. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetGRBPalette і Позволяет модифицировать записи палит-і

і і ры для драйверов IBM 8514 и VGA. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetTextJustify і С помощью ОutTеxt и ОutTехtХY уста-і

і і навливает значения для выравнивания текс-і

і і та. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetTextStyle і Задает текущий текстовый шрифт, его тип иі

і і коэффициент размера символа. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetUserCharSize і Позволяет вам для векторных шрифтов из-і

і і менить высоту и ширину символа. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetViewPort і Для графического вывода устанавливаеті

і і текущую область вывода или окно. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetVisualPage і Задает визуальный номер графической стра-і

і і ницы. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і SetWriteMode і Устанавливает режим вывода на экран (ко-і

і і пирование или с помощью операции XOR) дляі

і і линий, вычерчиваемых процедурами DrawPoly,і

і і Line, LineRel, LineTo, Rectangle. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і TехtНеight і Возвращает высоту страниц в элементахі

і і изображения. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДґ

і TехtWidth і Возвращает ширину строки в элементахі

і і изображения. і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДЩ

Подробное описание каждой процедуры и функции дано в Главе 1

("Справочник по библиотеке") "Справочного руководства программис-

та".

Константы, типы и переменные модуля Graph

В модуле Graph имеется много полезных описаний типов и конс-

тант, на которое вы можете ссылаться.

Константы

Константы модуля Graph можно сгруппировать по их назначению.

Подробное описание каждой константы дано в Главе 1 ("Справочник

по библиотеке") "Справочного руководства программиста".

Группы констант модуля Graph Таблица 19.4

ЪДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДї

і Группа констант і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы драйверов і Константы, определяющие видеодрайве-і

і и режимов і ры и режимы; используются в подпрог-і

і і раммах InitGraph, DetectGraph иі

і і GetModeRange. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і grXXXX і Константы, идентифицирующие типі

і і ошибки, возвращаемой GraphResult. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы цветов і Константы, определяющие цвета. Ис-і

і і пользуются в подпрограммахі

і і SetPalette и SetAllPalette. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы цветов і Константы, используемые в подпрог-і

і для SetRGBPalette і рамме SetGRBPalette для выбора наі

і і IBM 8514 стандартных цветов EGA. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы стиля і Константы, используемые для опреде-і

і линии і ления стиля и толщины линии; исполь-і

і і зуются с GetLineSettings иі

і і SetLineStyle. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы шрифта і Используются для идентификации шриф-і

і і тов в подпрограммах GetTextSettingsі

і і и SetTextSetting. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы выравниванияі Константы, управляющие горизонталь-і

і і ным и вертикальным выравниванием.і

і і Используются в SetTextJustify. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы отсечений і Константы, управляющие отсечением.і

і і Используются в SetViewPort. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Константы столбцов і Управляют изображением "вершины"і

і і трехмерного столбца; используются ві

і і Bar3D. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Образцы закраски і Определяют образец закраски области.і

і і Используются в GetFillSettings иі

і і SetFillStyle. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і Операции BitBlt і Операции (копирование, xor, or, and,і

і і not), которые используются ві

і і PutImage и SetWriteMode. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і MaxColors і Константы, определяющие максимальноеі

і і число цветов в GetPalette,і

і і GetDefaultPalette и SetAllPalette. і

АДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДЩ

Типы

В модуле Graph определены следующие типы:

Типы модуля Graph Таблица 19.5

ЪДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДї

і Тип і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і PaletteType і Запись, определяющая размер и цветаі

і і палитры; используется в GetPalette,і

і і GetDefaultPalette и SetAllPalette. і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і LineSettingsType і Запись, определяющая стиль, образец иі

і і толщину линии; используетcя і

і і GetLineSettings. і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FillSettingsType і Запись, определяющая текст. Использу-і

і і ется в GetTextSettings. і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FillPatternType і Запись, определяющая заданный поль-і

і і зователем образец закраски. Даннаяі

і і запись используется процедурамиі

і і GetFillPattern и SetFillPattern. і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і PointType і Тип, определенный для вашего удобст-і

і і ва. і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і ViewPortType і Запись, сообщающая о состоянии теку-і

і і щей области просмотра; используетсяі

і і GetViewSettings. і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і ArcCoordsType і Запись для получения информации оі

і і последнем вызове Arc или Ellipse; ис-і

і і пользуется GetArcCoords. і

АДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДЩ

Переменные

Модуль Graph содержит две переменные, которые вы можете ис-

пользовать: GraphGetMemPtr и GraphFreeMemPtr. Они применяются в

подпрограммах управления динамически распределяемой областью па-

мяти. Прочитать о них можно в Главе 1 ("Справочник по библиоте-

ке") "Справочного руководства программиста".


Глава 20. Использование оверлеев

Оверлеи представляют собой части программы, которые совмест-

но используют общую область памяти. В один и тот же момент време-

ни резидентно размещаться в памяти может та или иная часть прог-

раммы, необходимая для выполнения заданной функции. В процессе

выполнения эти части программы могут замещать друг друга.

Оверлеи полезны только в программах DOS реального режима.

Поскольку для программ Windows памятью управляет сама Windows, а

для программ защищенного режима - администратор этапа выполнения

(RTM.EXE), эти средства включают в себя полный механизм обслужи-

вания оверлеев, и в программах Windows и программах защищенного

режима необходимость использования оверлеев отпадает.

Оверлеи могут значительно сократить объем памяти, необходи-

мый для выполнения программы. Фактически, так как в любой момент

времени в памяти резидентно размещаются только части программы, с

помощью оверлеев вы можете выполнять программы, значительно пре-

восходящие по объему доступную память.

Borland Pascal управляет оверлеями на уровне модулей, кото-

рые являются наименьшей частью программы, образующей оверлей. При

компиляции программы, имеющей оверлейную структуру, Borland

Pascal генерирует наряду с выполняемым файлом (который имеет рас-

ширение .EXE) оверлейный файл (имеющий расширение .OVR). Файл с

расширением .EXE содержит статические (не оверлейные) части прог-

раммы, а файл с расширением .OVR содержит все оверлейные модули,

которые при выполнении программы будут подкачиваться в память или

выводиться из нее на диск.

За исключением нескольких правил, касающихся его программи-

рования, оверлейный модуль полностью идентичен неоверлейному мо-

дулю. Фактически, если вы соблюдаете эти правила, у вас нет необ-

ходимости перекомпилировать модуль, чтобы образовать из него

оверлей. Решение о том, будет модуль оверлейным или нет, принима-

ется программой, которая использует данный модуль.

При загрузке оверлеев в память они помещаются в оверлейный

буфер, который размещается в памяти между сегментом стека и дина-

мически распределяемой областью памяти. По умолчанию для оверлей-

ного буфера выбирается минимальный возможный размер, но во время

выполнения программы его размер может быть легко увеличен путем

выделения дополнительной области памяти из динамически распреде-

ляемой области. Аналогично сегменту данных и минимальному размеру

динамически распределяемой области, оверлейный буфер принятого по

умолчанию размера выделяется при загрузке файла .EXE. При отсутс-

твии памяти необходимого объема модулем Dos или интегрированной

программной средой IDE будет выводиться сообщение об ошибке

(Program too big to fit in memory - "Программа слишком велика,

чтобы разместиться в памяти") или (Not enough memory to run

program - "Для запуска программы не хватает памяти").

Одной из очень важных возможностей подсистемы управления

оверлеями является возможность при наличии достаточного прост-

ранства загружать оверлейный файл в дополнительную память. Для

этой цели в Borland Pascal поддерживается средство расширения па-

мяти EMS (Lotus/Intel/Microsoft Expanded Memory Specification).

При размещении оверлейного файла в памяти EMS все последующие

загрузки оверлеев сводятся к быстрой передаче информации из памя-

ти в память.

Администратор оверлеев

Администратор оверлеев (или подсистема управления оверлеями)

Borland Pascal реализуется с помощью стандартного модуля Overlay.

В модуле Overlay используются усовершенствованные методы управле-

ния буферами, что обеспечивает оптимальное выполнение программы в

имеющейся области памяти. Например, подсистема управления оверле-

ями сохраняет в оверлейном буфере столько оверлеев, сколько воз-

можно. Это позволяет уменьшить частоту считывания оверлеев с дис-

ка. После загрузки оверлея вызов одной из его подпрограмм выпол-

няется также быстро, как обращение к неоверлейной программе. Кро-

ме того, когда у администратора оверлеев возникает необходимость

вывести один оверлей, чтобы освободить место для другого, он сна-

чала пытается вывести те оверлеи, которые не являются активными

(то есть те, которые в данный момент времени не содержат активных

программ).

Для реализации улучшенных методов управления оверлеями

Borland Pascal требует от вас при написании программы, в которой

используются оверлеи, соблюдать два важных правила:

1. Все оверлейные модули должны содержать директиву {$O+},

приводящую к тому, что компилятор обеспечивает генериро-

вание оверлейного кода.

2. При каждом обращении к оверлейной процедуре или функции

вы должны обеспечить использование всеми активными про-

цедурами и функциями вызовов типа FAR (дальний тип вызо-

ва).

Оба правила будут поясняться далее в разделе под заголовком

"Разработка оверлейных программ". Сейчас мы просто отметим, что

вы можете легко удовлетворить эти правила, поместив в начале

оверлейных модулей директиву компилятора {$O+,F+}, а в начале

всех других модулей и основной программы - директиву {$F+}.

Примечание: Несоблюдение требования обеспечения даль-

него типа вызова в оверлейной программе вызовет непредска-

зуемые и возможно катастрофические результаты при выполне-

нии программы.

Директива компилятора {$O имя_модуля} используется в прог-

рамме для указания того, какой из модулей будет оверлейным. Эта

директива должна размещаться за оператором uses программы, в кото-

ром перед именами всех других оверлейных модулей должно указы-

ваться имя стандартного модуля Overlay. Приведем следующий при-

мер:

program Editor;

{$F+} { Все процедуры и функции будут использовать

дальний тип вызова }

uses

Overlay, Crt, Dos, EdInOut, EdFormat, EdPrint, EdFind,

EdMain;

{$O EdInOut }

{$O EdFormat }

{$O EdPrint }

{$O EdFind }

{$O EdMain }

Если вы пытаетесь использовать в качестве оверлейного мо-

дуль, при компиляции которого не была указана директива {$O+}, то

компилятор выведет сообщение об ошибке. Что касается стандартных

модулей, то оверлейным может быть только модуль Dos. Другие стан-

дартные модули не могут использоваться в качестве оверлейных. К

тому же программы, содержащие оверлейные модули, при использова-

нии IDE реального режима должны компилироваться на диск. Если вы

пытаетесь выполнить компиляцию таких программ в память, то компи-

лятор выводит сообщение об ошибке.

Управление оверлейным буфером

Оверлейный буфер Borland Pascal лучше всего описывается в

виде кольцевого буфера, в котором имеется указатель начала и ука-

затель конца. Оверлеи всегда загружаются в начало буфера. При

этом более "старые" оверлеи смещаются к его концу. Когда буфер

заполняется (то есть между его началом и концом не будет доста-

точно свободного пространства), то оверлеи в конце буфера выгру-

жаются (освобождаются), и освобождается место для новых оверлеев.

Поскольку обычная память по своей природе не имеет характера

кольцевого буфера, действительная реализация кольцевого буфера

предусматривает несколько шагов, обеспечивающих, чтобы буфер

действительно стал кольцевым. Этот процесс показан на Рис. 20.1.

Здесь изображен процесс загрузки оверлеев в первоначально пустой

оверлейный буфер. Сначала загружается оверлей A, затем - оверлей

B, потом C, и, наконец, D. Заштрихованные области показывают сво-

бодное пространство в буфере.

Шаг 1 Шаг 2

ЪДДДДДДДДДДДДї ЪДДДДДДДДДДДДї

і °°°°°°°°°° і і °°°°°°°°°° і

і °°°°°°°°°° і і °°°°°°°°°° і

і °°°°°°°°°° і і °°°°°°°°°° і

і °°°°°°°°°° і Начало ДДД> ГДДДДДДДДДДДДґ

і °°°°°°°°°° і і Оверлей B і

Начало ДДДД> ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ

і Оверлей А і і Оверлей А і

Конец ДДДД> АДДДДДДДДДДДДЩ Конец ДДД> АДДДДДДДДДДДДЩ

Шаг 3 Шаг 4

ЪДДДДДДДДДДДДї ЪДДДДДДДДДДДДї

і °°°°°°°°°° і і Оверлей С і

і °°°°°°°°°° і ГДДДДДДДДДДДДґ

Начало ДДДД> ГДДДДДДДДДДДДґ і Оверлей В і

і Оверлей С і Конец ДДД> ГДДДДДДДДДДДДґ

ГДДДДДДДДДДДДґ і °°°°°°°°°° і

і Оверлей В і і °°°°°°°°°° і

ГДДДДДДДДДДДДґ Начало ДДД> ГДДДДДДДДДДДДґ

і Оверлей А і і Оверлей D і

Конец ДДДД> АДДДДДДДДДДДДЩ АДДДДДДДДДДДДЩ

Рис. 20.1 Загрузка и освобождение оверлеев.

Как можно заметить, при переходе от шага 3 к шагу 4 происхо-

дит несколько интересных моментов. Во-первых, заголовок начала

перемещается к концу оверлейного буфера, приводя к тому, что под-

система управления оверлеями смещает все загруженные оверлеи (и

указатель конца) вверх. Это смещение необходимо, чтобы свободная

область всегда находилась между указателем начала и указателем

конца. Во-вторых, чтобы загрузить оверлей D, подсистеме управле-

ния оверлеями приходится выгрузить из конца буфера оверлей A. В

этом случае оверлей A является оверлеем, которых был загружен

раньше всех, поэтому прежде чем продолжить работу, лучше всего

выгрузить именно его. Администратор оверлеев продолжает освобож-

дать оверлеи в конце буфера, освобождая место в его начале для

новых оверлеев. При этом каждый раз повторяется операция смещения

и переноса указателя начала.

Этот режим операция используется администратором оверлеев

Borland Pascal 0 по умолчанию. Однако, Borland Pascal также поз-

воляет вам использовать возможность оптимизации алгоритма управ-

ления оверлеями.

Предположим, что оверлей A содержит некоторые часто исполь-

зуемые подпрограммы. Хотя некоторые из этих подпрограмм использу-

ются все время, существует вероятность, что оверлей A будет

выгружен из буфера и вскоре загружен в него снова. Проблема здесь

состоит в том, что подсистема управления оверлеями ничего не зна-

ет о частоте вызовов подпрограмм в модуле A. Она знает только,

что если при обращении к подпрограмме оверлея A его нет в памяти,

то нужно загрузить этот оверлей. Одно из решений здесь может сос-

тоять в том, чтобы перехватывать каждое обращение к подпрограммам

оверлея A и затем при каждом вызове перемещать оверлей A в начало

оверлейного буфера, чтобы было отражено его новое состояние, как

последнего использованного оверлея. Такой перехват вызовов к со-

жалению будет слишком непроизводительным в смысле скорости выпол-

нения, и в некоторых случаях может даже более замедлить работу

прикладной программы, чем дополнительная операция загрузки овер-

лея.

В Borland Pascal предусматривается компромиссное решение,

которое практически не увеличивает непроизводительные расходы и

обеспечивает высокую степень успеха в идентификации последних ис-

пользованных оверлеев, которые не следуют разгружать. Когда овер-

лей приближается к концу оверлейного буфера, то начинается его

"тестирование". Если в ходе этого "тестирования" выполняется вы-

зов подпрограммы данного оверлея, "приговор" ему будет отменен, и

он не будет разгружен, когда достигнет конца оверлейного буфера.

Вместо этого он просто перемещается в начало буфера, и начинается

новые цикл его перемещения по кольцевому оверлейному буферу. Ес-

ли, с другой стороны, в процессе "тестирования" обращений к овер-

лею не будет, то оверлей при достижении конца буфера выгружается.

Схема тестирования (проб/отказов) приводит к тому, что часто

используемые оверлеи будут сохраняться в оверлейном буфере за

счет того, что будет перехватываться почти каждый вызов, когда

оверлей приближается к концу оверлейного буфера.

Механизмом тестирования управляют две новые подпрограммы

подсистемы управления оверлеями - OvrSetRetry и OvrGetRetry.

Подпрограмма OvrGetRetry устанавливает размер области в оверлей-

ном буфере, которую нужно тестировать, а OvrGetRetry возвращает

текущее состояние. Если оверлей смещается в последние OvrGetRetry

байт перед концом оверлейного буфера, то он будет автоматически

подвергаться тестированию. Все свободное пространство в оверлей-

ном буфере рассматривается, как часть пробной области (области

тестирования).

Процедуры и функции модуля Overlay

В модуле Overlay определяются несколько процедур и функций.

Полные их описания вы можете найти в Главе 1 ("Справочник по биб-

лиотеке") "Справочного руководства программиста".

Процедуры и функции модуля Overlay Таблица 20.1

ЪДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДї

і Подпрограмма і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvrClearBuf і Очищает оверлейный буфер. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvrGetBuf і Возвращает текущий размер оверлейно-і

і і го буфера. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvrGetRetry і Возвращает текущий размер пробнойі

і і области (последнее значение, уста-і

і і новленное OvrSetRetry). і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvtInit і Эта процедура инициализирует подсис-і

і і тему управления оверлеями и открыва-і

і і ет оверлейный файл. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvrInitEMS і Данная процедура, если это возможно,і

і і загружает оверлейный файл в памятьі

і і EMS. При этом все последующие заг-і

і і рузки оверлеев сводятся к быстройі

і і передаче информации из памяти в па-і

і і мять. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvrSetBuf і Устанавливает размер оверлейного бу-і

і і фера. і

ГДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДґ

і OvrSetRetry і Задает размер пробной области ві

і і оверлейном буфере. і

АДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДЩ

Константы и переменные модуля Overlay

ДДДДДДД

В модуле Overlay определены пять переменных:

Переменные модуля Overlay Таблица 20.2

ЪДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДї

і Переменная і Описание і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і OvrFileMode і Определяет передаваемый DOS при откры-і

і і тии файла код доступа. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і OvrLoadCount і Данная переменная увеличивается приі

і і каждой загрузке оверлея. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і OvrReadBuf і Эта процедурная переменная позволяеті

і і вам интерпретировать операции загрузкиі

і і оверлея. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і OvrResult і Перед возвратом управления каждая про-і

і і цедура в модуле Overlay сохраняет свойі

і і код результата в переменной OvrResult. і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і OvrTrapCount і Каждый раз, когда обращение к подпрог-і

і і рамме оверлея перехватывается подсисте-і

і і мой управления оверлеями (когда оверлеяі

і і нет в памяти или он находится на тести-і

і і ровании) значение переменнойі

і і OvrTrapCount увеличивается. Начальноеі

і і ее значение равно 0. і

АДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДЩ

Значения этих переменных вы можете найти в Главе 1 ("Спра-

вочник по библиотеке") "Справочного руководства программиста".

Коды результата

Об ошибках модуль Overlay сообщает через переменную

OvrResult. См. константы ovrXXXX в Главе 1 ("Справочник по биб-

лиотеке") "Справочного руководства программиста".

Разработка программ с оверлеями

В этом разделе дается наиболее важная информация по разра-

ботке программ с оверлеями. Просмотрите ее внимательно: для хоро-

шей работы прикладных программ, в которых используются оверлеи,

многие обсуждаемые вопросы являются жизненно важными.

Генерация оверлейного кода

Borland Pascal допускает использование модуля в качестве

оверлейного только в том случае, если он генерировался с директи-

вой {$O+}. Когда задана эта директива, генератор выполняемого ко-

да, при передаче строки из одной оверлейной процедуры в другую и

задании постоянных параметров, предпринимает особые меры предос-

торожности. Например, если модуль UnitA содержит процедуру со

следующим заголовком:

procedure WriteStr(s: string);

и модуль UnitB содержит оператор:

WriteStr('Hello word...');

то Borland Pascal помещает строковую константу 'Hello word...' в

сегмент кода модуля UnitB и передает указатель на него процедуре

WriteStr. Однако, если оба модуля являются оверлейными, то это

работать не будет, поскольку при обращении в WriteStr сегмент ко-

да модуля UnitB может быть перекрыт модулем UnitA, и ссылка на

строку окажется недопустимой. Для того, чтобы избежать эти проб-

лемы, используется директива {$O+}. Каждый раз, когда Турбо Пас-

каль встречает обращение из одного модуля, скомпилированного с

директивой {$O+}, к другому модулю, скомпилированному с директи-

вой {$O+}, компилятор перед передачей ссылок на них обеспечивает

временное копирование всех размещенных в сегменте кода констант в

стек.

Указание в модуле директивы {$O+} не обязывает вас использо-

вать этот модуль как оверлейный. Она просто указывает Borland

Pascal на необходимость обеспечения, если это нужно, использова-

ния данного модуля в качестве оверлейного. Если вы разрабатываете

модули, которые планируете использовать как в оверлейных, так и в

неоверлейных прикладных программах, то компиляция их с директивой

{$O+} обеспечивает использование одной версии модуля для обоих

случаев.

Требование использования дальнего типа вызовов

Как уже упоминалось ранее, при любом обращении к оверлейной

процедуре или функции из другого модуля вы должны обеспечить для

всех активных процедур и функций вызовы типа FAR (дальний тип вы-

зова).

Это можно хорошо проиллюстрировать на следующем примере.

Предположим, что OvrA представляет собой процедуру в оверлейном

модуле, а процедуры MainC и MainD - процедуры в основной програм-

ме. Если основная программа вызывает MainC, которая вызывает про-

цедуру MainB, которая в свою очередь обращается к процедуре OvrA,

то во время обращения к процедуре OvrA процедуры MainC и MainB

являются активными (они еще не выполнили возврат управления), по-

этому необходимо использовать для них дальний тип вызова. Описан-

ные в основной программе, процедуры MainC и MainB в обычной ситу-

ации используют ближний тип вызовов (NEAR). С помощью директивы

компилятора {$F+} необходимо задать дальний тип вызовов.

Самый легкий способ удовлетворения требования использования

дальнего типа вызовов состоит в размещении в начале основной

программы и в начале каждого модуля директивы {$F+}. Альтернатив-

ный способ состоит в изменении принятой по умолчанию установки $F

на {$F+} с помощью директивы командной строки /$F+ или с помощью

параметра Force Far Calls (Использовать дальний тип вызова) в ди-

алоговом меню OptionsіCompiler (ПараметрыіКомпилятор) среды IDE

интерактивного компилятора. По сравнению со смешанным использова-

нием вызовов ближнего и дальнего типа использование вызовов толь-

ко типа FAR не приводит к особенно большим дополнительным затра-

там памяти: для этого требуется одно дополнительное слово прост-

ранства стека на активную процедуру и один дополнительный байт на

каждый вызов.

Инициализация администратора оверлеев

Здесь мы рассмотрим некоторые примеры того, как инициализи-

руется администратор оверлеев (подсистема управления оверлеями).

Код инициализации должен быть помещен перед первым обращением к

оверлейной программе. Инициализацию обычно следует делать в опе-

раторной части программы.

Следующая часть программы показывает, как немного требуется

для того, чтобы инициализировать администратор оверлеев.

begin

OvrInit('EDITOR.OVR');

end;

Проверка на ошибки не делается. Поэтому если для оверлейного

буфера не хватает памяти или оверлейный файл не найден, то при

попытке вызова оверлейной программы произойдет ошибка 208

(Overlay manager not installed - "Администратор оверлеев не уста-

новлен").

Приведем другой небольшой пример, являющийся расширением

предыдущего.

begin

OvrInit('EDITOR.OVR');

OvrInitEMS;

end;

В этом случае, если предположить, что для оверлейного буфера

имеется достаточно памяти и что можно найти оверлейный файл, ад-

министратор оверлеев проверяет, имеется ли память EMS, и если это

так, загружает оверлейный файл в расширенную память.

Как уже упоминалось ранее, начальный размер оверлейного бу-

фера выбирается минимально возможным или, иначе говоря, настолько

большим, чтобы вместить оверлей наибольшего размера. Для некото-

рых прикладных задач этого может быть достаточным, однако предс-

тавим ситуацию, при которой одна из функций программы реализуется

с помощью двух или более модулей, каждый из которых является

оверлейным. Если общий размер таких модулей больше, чем размер

наибольшего оверлея, то частое обращение модулей друг к другу

приведет к интенсивному свопингу.

Очевидно, решение заключается в том, чтобы увеличить размер

оверлейного буфера таким образом, чтобы в любой заданный момент

времени имелось достаточно памяти для того, чтобы содержать в се-

бе все оверлеи, часто обращающиеся друг к другу. Следующая часть

программы показывает использование для увеличения размера овер-

лейного буфера процедуры OvrSetBuf:

OvrMaxSize = 80000;

begin

OvrInit('EDITOR.OVR');

OvrInitEMS;

OvrSetBuf(OvrMaxSize);

end;

Для определения идеального размера оверлейного буфера общих

рекомендаций нет. Подходящее значение можно определить только

имея некоторую информацию о прикладной задачи и экспериментальным

путем.

Использование процедуры OvrInitEMS для размещения оверлейно-

го файла в расширенной памяти не устраняет необходимости работы с

оверлейным буфером. Ведь оверлеи перед выполнением тем не менее

должны копироваться из расширенной памяти в обычную (то есть в

оверлейный буфер). Однако, поскольку такие передачи из памяти в

память выполняются значительно быстрее, чем чтение с диска, то

необходимость увеличения размера оверлейного буфера становится

менее очевидной.

Нужно также помнить о том, что процедура OvrSetBuf увеличи-

вает размер оверлейного буфера за счет уменьшения размера динами-

чески распределяемой области памяти. Таким образом, динамически

распределяемая область должна быть пустой, иначе процедура

OvrSetBuf не окажет никакого действия. Если вы используете модуль

Graph, убедитесь в том, что вы обращаетесь к процедуре OvrSetBuf

перед вызовом процедуры InitGraph, которая выделяет память в ди-

намически распределяемой области.

Приведем теперь более исчерпывающий пример инициализации

подсистемы управления оверлеями, включающей в себя полную провер-

ку на возможное возникновение ошибок.

const

OvrMaxSize = 80000;

var

OvrName: string[79];

Size: Longint;

begin

OvrName:='EDITOR.OVR';

repeat

OvrInit(OvrName);

if OvrResult=ovrNotFound then

begin

WriteLn('Оверлейный файл не найден');

WriteLn('Введите правильное имя оверлейного файла:');

ReadLn(OvrName);

end;

until OvrResult<>ovrNotFound;

if OvrResult<>ovrOk then

begin

WriteLn('Ошибка администратора оверлеев.')

Halt(1);

end;

OvrInEMS;

if OvrResult<>OvrOk then

begin

case OvrResult of

ovrIOError: Write('Ошибка ввода-вывода',

' оверлейного файла');

ovrNoEMSDriver: Write('Драйвер EMS не',

' установлен');

ovrNoEMSMemory: Write('Не хватает расширенной',

' памяти');

end;

Write('. Нажмите клавишу Enter...');

ReadLn;

end;

OvrSetBuf(OvrMaxSize);

end;

Сначала, если принятое по умолчанию имя оверлейного файла

было неверным, пользователю будет выводиться подсказка на введе-

ние правильного имени файла.

Далее проверяются другие ошибки, которые могут произойти при

инициализации. В случае обнаружения ошибки программа останавлива-

ется, так как ошибки в OvrInit являются фатальными. (Если они иг-

норируются, то при первом обращении к оверлейной программе во

время выполнения произойдет ошибка.)

Если предположить, что инициализация проходит успешно, далее

для загрузки оверлейного файла в расширенную память (если это

возможно) выполняется обращение к процедуре OvrInitEMS. В случае

ошибки на экран выводится диагностическое сообщение, однако прог-

рамма не останавливается. Вместо этого она просто продолжает счи-

тывать оверлеи с диска.

Наконец, для задания значения размера оверлейного буфера,

определенного с помощью анализа или эксперимента с конкретной

прикладной программой, вызывается процедура OvrSetBuf. Ошибки,

которые могут возникнуть при выполнении данной процедуры, игнори-

руются, хотя OvrResult может возвращать код возврата по ошибке -3

(OvrNoMemory). Если памяти недостаточно, подсистема управления

оверлеями будет просто продолжать использовать буфер минимального

размера, выделенный при запуске программы.

Разделы инициализации в оверлейных модулях

Аналогично статическим модулям оверлейные модули могут со-

держать секцию инициализации. Хотя оверлейный код инициализации

не отличается от обычного кода инициализации, администратор овер-

леев должен первоначально инициализироваться таким образом, чтобы

он мог загружать и выполнять оверлейные модули.

Взяв в качестве примера ранее рассмотренную программу

Editor, предположим, что модули EdInOut и EdMain содержат код

инициализации. При этом требуется, чтобы процедура OvrInit вызы-

валась перед кодом инициализации модуля EdInOut, и единственный

способ осуществить это состоит во введении дополнительного нео-

верлейного модуля, который следует перед EdInOut и вызывает в

своем разделе инициализации процедуру OvrInit.

unit EdInit;

interface

implementation

uses Overlay;

const

OvrMaxSize = 80000;

begin

OvrInit('EDITOR.OVR');

OvrInitEMS;

OvrSetBuf(OvrMaxSize);

end.

В операторе uses программы модуль EdInit должен следовать

перед всеми оверлейными модулями:

program Editor;

{$F}

uses

Overlay,Crt,Dos,EdInit,EdInOut,EdFormat,EdPrint,EdMain;

{$O EdInOut }

{$O EdFormat }

{$O EdPrint }

{$O EdFind }

{$O EdMain }

В общем случае, хотя использование кода инициализации в

оверлейных модулях и допускается, по ряду причин его следует из-

бегать.

Во-первых, код инициализации, даже если он выполняется толь-

ко один раз, является частью оверлея и будет занимать пространс-

тво в оверлейном буфере при каждой загрузке оверлея. Во-вторых,

если большое число оверлейных модулей содержат код инициализации,

каждый из них придется считывать в память при загрузке программы.

Намного более привлекательный подход состоит в том, чтобы

собрать весь код инициализации в оверлейный модуль инициализации,

который вызывается только один раз при загрузке программы и к ко-

торому затем программа не обращается.

Что не должно использоваться в качестве оверлеев

Отдельные модули не могут использоваться, как оверлейные. В

частности, не пытайтесь использовать в качестве оверлейных моду-

лей следующие:

1. Модули, скомпилированные с директивой {$O-}. Если вы пы-

таетесь использовать как оверлейный модуль, который не

был скомпилирован с директивой {$O+}, то компилятор вы-

дает сообщение об ошибке. Такими неоверлейными модулями

являются модули System, Overlay, Crt, Graph, Turbo3 и

Graph3.

2. Модули, которые содержат драйверы прерываний. Из-за то-

го, что сама операционная система DOS имеет неоверлейную

структуру, модули, реализующие процедуры прерываний

(interrupt), не должны быть оверлейными. В качестве при-

мера такого модуля можно привести стандартный модуль

Crt, реализующий драйвер обработки прерывания, возникаю-

щего при нажатии клавиш Ctrl+Break.

3. Драйверы BGI или шрифты, зарегистрированные с помощью

вызова подпрограмм RegisterBGIdriver или

RegisterBGIfont.

Администратором оверлеев Borland Pascal полностью поддержи-

вается вызов оверлейных процедур с помощью указателей процедур. В

качестве примеров использования указателей процедур можно привес-

ти процедуры завершения и драйверы устройств для текстовых фай-

лов.

Аналогично, полностью поддерживается передача оверлейных

процедур и функций в качестве параметров процедурного типа и

присваивание оверлейных процедур и функций переменным процедурно-

го типа.

Отладка оверлеев

Большинство отладчиков обладают весьма ограниченными возмож-

ностями отладки оверлеев, если они вообще обладают такими средс-

твами. Этого нельзя сказать о Borland Pascal и Турбо отладчике

(Turbo Debugger). Встроенный отладчик полностью поддерживает при

работе с оверлеями пошаговый режим и точки останова, используя

при этом метод, полностью прозрачный для пользователя. С помощью

оверлеев вы легко можете конструировать и отлаживать прикладные

пакеты большого объема. Все это можно делать как с помощью Турбо

отладчика, так и из интерактивной среды компилятора IDE.

Внешние программы в оверлеях

Аналогично обычным процедурам и функциям Borland Pascal при

использовании внешних программ на языке ассемблера для обеспече-

ния корректной работы подсистемы управления оверлеями должны соб-

людаться определенные правила программирования.

Если в программе на языке ассемблера осуществляется обраще-

ние к любой оверлейной процедуре или функции, то в программе ас-

семблера должен использоваться дальний тип вызова, и с помощью

регистра BP должны быть установлены границы стека. Например,

предположим, что OtherProc является оверлейной процедурой в дру-

гом модуле и ее вызывает программа ExternProc на языке ассембле-

ра. Тогда программа ExternProc должна иметь дальний тип вызова и

устанавливать границы стека следующим образом:

ExternProc PROC FAR

PUSH bp ; сохранить регистр ВР

mov bp,sp ; установить границы стека

SUB sp,LocalSize ; выделить локальные

; переменные

...

CALL OtherProc ; вызвать другой оверлейный

; модуль

...

mov sp,bp ; отменить локальные переменные

pop bp ; восстановить регистр ВР

RET ParamSize ; возврат управления

ExternProc ENDP

где LocalSize представляет собой размер локальных переменных, а

ParamSize - размер параметров. Если значение LocalSize равно 0,

то две строки, в которых выделяются и уничтожаются локальные пе-

ременные, можно опустить.

Если в программе ExternProc имеются косвенные ссылки на

оверлейные процедуры и функции, то эти требования остаются теми

же. Например, если процедура OtherProc вызывает оверлейные проце-

дуры или функции, но сама не является оверлейной, то программа

ExternProc должна, тем не менее, иметь дальний тип вызова и уста-

навливать границы стека.

В том случае, если в программе на языке ассемблера отсутс-

твуют прямые или косвенные ссылки на оверлейные процедуры или

функции, то никаких специальных требований соблюдаться не должно:

программа ассемблере может использовать ближний тип вызова и не

устанавливать границ стека.

Оверлейные программы на языке ассемблера не должны создавать

переменных в сегменте кода, поскольку при освобождении оверлея

любые изменения, внесенные в оверлейный сегмент кода, теряются.

Аналогично, не следует считать, что указатели на размещенные в

оверлейном сегменте кода объекты останутся действительными при

вызове других оверлеев, поскольку подсистема управления оверлеями

может свободно перемещать и освобождать оверлейные сегменты кода.

Задание функции чтения оверлея

Переменная OvrReadBuf позволяет вам перехватывать операции

загрузки оверлеев. Например, вы можете реализовать обработку оши-

бок или проверку наличия сменного диска. Когда администратору

оверлеев требуется считать оверлей, он вызывает функцию, адрес

которой записан в OverReadBuf. Если функция возвращает нулевое

значение, то администратор оверлеев предполагает, что операция

была успешной. Если функция возвращает ненулевой результат, то

компилятор генерирует ошибку этапа выполнения 209. Параметр

OvrSeg указывает, какой именно оверлей требуется загрузить, но,

как вы далее увидите, вам эта информация не потребуется.

Чтобы установить свою собственную функцию чтения оверлея,

вам нужно сначала сохранить предыдущее значение OvrReadBuf в пе-

ременной типа OvrReadFunc, а затем присвоить OvrReadBuf вашу

функцию чтения оверлея. В своей функции чтения вам следует для

выполнения фактической операции загрузки вызвать сохраненную

функцию чтения оверлея. Любые нужные вам проверки допустимости

(такие как проверка наличия сменного диска) следует выполнять пе-

ред вызовом сохраненной функции чтения, а все проверки на ошибки

следует выполнять после вызова.

Примечание: Не пытайтесь вызывать из своей функции

чтения оверлея какие-либо оверлейные подпрограммы - это

приведет к сбою системы.

Код для установки функции чтения оверлея должен следовать

непосредственно после вызова OvrInit; в этот момент OvrReadBuf

будет содержать адрес используемой по умолчанию функции чтения с

диска.

Если вы также вызываете OvrInitEMS, она использует вашу

функцию чтения для чтения оверлеев с диска в память EMS, и в слу-

чае отсутствия ошибок сохраняет адрес используемой по умолчанию

функции чтения в EMS в OvrReadBuf. Если вы хотите также переопре-

делить функцию чтения в EMS, просто повторите после вызова

OvrInitEMS процесс установки.

Используемая по умолчанию функция чтения с диска в случае

успешного выполнения возвращает 0. В противном случае возвращает-

ся код ошибки DOS. Аналогично, используемая по умолчанию функция

чтения из EMS в случае успешного выполнения возвращает 0. В про-

тивном случае возвращается код ошибки EMS (от $80 до $FF). Под-

робно коды ошибок DOS описываются в "Справочном руководстве прог-

раммиста". Коды ошибок EMS можно найти в документации по EMS.

Следующий фрагмент программы показывает, как написать и ус-

тановить функцию чтения оверлея. Новая функция чтения оверлея

повторно вызывает сохраненные функции чтения оверлея, пока не

возникает ошибка.

Все ошибки передаются процедурам DOSError или EMSError (ко-

торые здесь не показаны), которые могут вывести ошибку пользова-

телю. Заметим, что параметр OvrSeg просто передается сохраненной

функции чтения оверлея и не обрабатывается непосредственно новой

функцией чтения оверлея.

uses Overlay;

var

SaveOvrRead: OvrReadFunc;

UsingEMS: Boolean;

function MyOvrRead(OvrSeg: Word): Integer: far;

var

E: Integer;

begin

repeat

E := SaveOvrRead(OvrSeg);

if E <> 0 then

if UsingEMS then

EMSError(E) else DOSError(E);

until E = 0;

MyOvrRead := 0;

end;

begin

OvrInit('MYPROG.OVR');

SaveOvrRead := OvrReadBuf; { сохранить }

OvrReadBuf := MyOvrRead; { установить свою }

UsingEMS := False;

OvrInitEMS;

if OvrResult = OvrOK then

begin

SaveOvrRead := OvrReadBuf { сохранение }

OvrReadBuf := MyOvrRead; { установить свою }

UsingEMS := True;

end;

.

.

.

end.

Оверлеи в файлах .EXE

Borland Pascal также позволяет вам записывать оверлеи в ко-

нец выполняемого файла .EXE прикладной программы, а не в отдель-

ный файл .OVR. Чтобы присоединить файл .OVR к концу файла .EXE,

используйте команду DOS COPY с параметром командной строки /B,

например:

COPY/B MYPROG.EXE + MYPROG.OVR

Вы должны убедиться, что файл .EXE компилировался без вклю-

чения в него информации для отладки. Таким образом, в интегриро-

ванной интерактивной среде IDE в меню OptionsіCompiler (Парамет-

рыіКомпилятор) проверьте параметр Standalone (Автономная отлад-

ка). При использовании компилятора, работающего с командной стро-

кой, укажите параметр /V.

Для чтения оверлея не из отдельного файла .OVR, а из конца

файла .EXE просто задайте при вызове OvrInit имя файла .EXE. Если

вы работаете под управлением DOS версии 3.х, то можете использо-

вать для получения имени файла .EXE стандартную функцию ParamStr,

например:

OvrInit(ParamStr(0));


Часть III. В среде Borland Pascal
Глава 21. Использование памяти

В данной главе описывается, как программы Borland Pascal ис-

пользуют память. Borland Pascal может создавать прикладные прог-

раммы для реального режима DOS, защищенного режима DOS, и

Windows; в каждом типе прикладной программы память используется

по-разному. В данной главе поясняется, как использует память каж-

дый из этих типов программ. Мы рассмотрим также внутренние форма-

ты данных, подсистему управления динамически распределяемой об-

ластью памяти и прямой доступ к памяти.

Использование памяти программами реального режима DOS

На Рис. 21.1 приведена схема распределения памяти программы

Borland Pascal, для реального режима DOS.

Префикс программного сегмента (PSP) - это область длиной 256

байт, которая строится операционной системой DOS при загрузке

файла .EXE. Адрес PSP сохраняется в предописанной переменной

Borland Pascal длиной в слово с именем PrefixSeg.

Каждой программе (которая включает в себя основную программу

и каждый модуль) соответствует сегмент ее кода. Основная програм-

ма занимает первый сегмент кода. Следующие сегменты кода заняты

модулями (в порядке, обратном тому, в котором они указаны в опе-

раторе uses). Последний сегмент кода занят библиотекой исполняю-

щей системы (модуль System). Размер отдельного сегмента не может

превышать 64К, однако общий размер кода ограничен только объемом

имеющейся памяти.

Верхняя граница памяти DOS

HeapEnd ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

і і

і свободная память і

і і

HeapPtr ДД>і............................і

і динамически распределяемая і

і область памяти і

і (растет вверх) ^ і

HeapOrg ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДД OvrHeapEnd

і оверлейный буфер і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДД OvrHeapOrg

і стек (растет вниз) v і

SSeg:SPtr ДД>і............................і

і свободный стек і

SSeg:0000 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і глобальные переменные і

і............................і<ДДДДДДДї

і типизированные константы і і

DSeg:0000 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і кодовый сегмент і і

і модуля System і і

і............................і і

і кодовый сегмент і і

і первого модуля і і

і............................і і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ содержимое

. кодовый сегмент . образа

. других модулей . файла .EXE

ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і

і............................і і

і кодовый сегмент і і

і последнего модуля і і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

і кодовый сегмент і і

і главной программы і і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДДДДДДДЩ

і префикс сегмента программы і

і (PSP) і

PrefixSeg ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 21.1 Схема памяти для программы реального режима DOS.

Сегмент данных (адресуемый через регистр DS) содержит все

типизированные константы, за которыми следуют все глобальные пе-

ременные. В процессе выполнения программы регистр DS никогда не

изменяется. Размер сегмента данных не может превышать 64К.

При входе в программу регистр сегмента стека (SS) и указа-

тель стека (SР) загружаются так, что пара регистров SS:SР указы-

вает на первый байт, следующий за сегментом стека. Регистр SS в

процессе выполнения программы никогда не изменяется, а SP может

B.Pascal 7 & Objects/LR - 350 -

перемещаться вниз, пока не достигнет нижней границы сегмента.

Размер сегмента стека не может превышать 64К. По умолчанию ему

назначается размер, равный 16К, но с помощью директивы компилято-

ра $М это значение можно изменить.

Оверлейный буфер используется стандартным модулем Overlay

для хранения оверлейного кода. По умолчанию размер оверлейного

буфера соответствует размеру наибольшего оверлея в программе. Ес-

ли программа не имеет оверлеев, то размер оверлейного буфера бу-

дет нулевым. Размер оверлейного буфера можно увеличить с помощью

вызова подпрограммы OvrSetBuf модуля Overlay. В этом случае раз-

мер динамически распределяемой области памяти соответственно

уменьшается, а HeapOrg перемещается вверх.

В динамически распределяемой области сохраняются динамичес-

кие переменные, то есть переменные, выделенные при обращениях к

стандартным процедурам New и GetMem. Она занимает всю свободную

память или часть свободной памяти, оставшуюся при выполнении

программы. Действительный размер динамически распределяемой об-

ласти зависит от максимального и минимального значений, которые

можно установить для динамически распределяемой области с помощью

директивы компилятора $М. Гарантированный минимальный размер ди-

намически распределяемой области не может быть меньше минимально-

го значения, установленного для этой области. По умолчанию мини-

мальные размер динамически распределяемой области равен 0 байт, а

максимальный - 640К; это означает, что по умолчанию динамически

распределяемая область занимает всю доступную память.

Подсистема динамического распределения памяти (являющаяся

частью библиотеки исполняющей системы), как можно догадаться, уп-

равляет динамически распределяемой областью. Детально она описы-

вается в следующем разделе.

Администратор динамически распределяемой области памяти DOS

Динамически распределяемая область - это похожая на стек

структура, которая увеличивается, начиная от младших адресов па-

мяти. При этом используется сегмент динамически распределяемой

области. Нижняя граница динамически распределяемой области запо-

минается в переменной HеаpOrg, а верхняя граница динамически

распределяемой области соответствует нижней границе свободной па-

мяти и сохраняется в переменной НеаpPtr. При каждом выделении ди-

намической переменной в динамически распределяемой области под-

система динамического распределения памяти (администратор динами-

чески распределяемой области) перемещает переменную HeapPtr вверх

на размер переменной, как бы организуя при этом стек динамических

переменных, в котором одна переменная размещается над другой.

Переменная НеаpPtr после каждой операции как правило норма-

лизуется, и смещение, таким образом, принимает значения в диапа-

зоне от $0000 до $000F. Так как каждая переменная должна целиком

содержаться в одном сегменте, максимальный размер отдельной пере-

менной, которая может быть размещена в динамически распределяемой

области, составляет 65521 байт (что соответствует $10000 минус

$000F).

Методы освобождения областей динамически распределяемой памяти

Динамические переменные, сохраняемые в динамически распреде-

ляемой области, освобождаются одним из двух следующих способов:

1. С помощью процедур Dispose или FrееМем.

2. С помощью процедур Маrk и Rеlеаsе.

Простейшей схемой использования процедур Маrk и Rеlеаsе,

например, является выполнение следующих операторов:

New(Ptr1);

New(Ptr2);

Mark(P);

New(Ptr3);

New(Ptr4);

New(Ptr5);

Схема динамически распределяемой области при этом будет выг-

лядеть, как показано на Рис. 21.2.

HeapEnd ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

і і памяти

і і

HeapPtr ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr5^ і

Ptr5 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr4^ і

Ptr4 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr3^ і

Ptr3 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr2^ і

Ptr2 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr1^ і

Ptr1 ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.2 Метод освобождения областей динамически распреде-

ляемой области помощью процедур Маrk и Rеlеаsе.

Оператор Маrk(P) отмечает состояние динамически распределяе-

мой области непосредственно перед выделением памяти для перемен-

ной Ptr3 (путем сохранения текущего значения переменной НеаpPtr в

P). Если выполняется оператор Rеleаsе(P), то схема динамически

распределяемой области становится такой, как показано на Рис.

21.3. При этом, поскольку производится обращение к процедуре

Маrk, освобождается память, выделенная под все указатели.

Примечание: Выполнение процедуры Rеleаsе(НеаpОrg) пол-

ностью освобождает динамически распределяемую область памя-

ти, поскольку переменная НеаpOrg указывает на нижнюю грани-

цу динамически распределяемой области.

HeapEnd ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

і і памяти

і і

і і

і і

і і

і і

HeapPtr ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr2^ і

Ptr2 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr1^ і

Ptr1 ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.3 Схема динамически распределяемой области при вы-

полнении процедуры Rеleаsе(P).

Применение процедур Маrk и Rеlеаsе для освобождения памяти,

выделенной для динамических переменных, на которые ссылаются ука-

затели, выполняемое в порядке, в точности обратном порядку выде-

ления памяти, весьма эффективно. Однако в большинстве программ

имеется тенденция в более случайному выделению и освобождению па-

мяти, отведенной для динамических переменных, на которые ссылают-

ся указатели, что влечет за собой необходимость использования бо-

лее тонких методов управления памятью, которые реализованы с по-

мощью процедур Dispose и FrееMem. Эти процедуры позволяют в любой

момент освободить память, выделенную для любой динамической пере-

менной, на которую ссылается указатель.

Когда с помощью процедур Dispose и FrееМем освобождается па-

мять, отведенная для динамической переменной, не являющаяся "са-

мой верхней" переменной в динамически распределяемой области, то

динамически распределяемая область становится фрагментированной.

Предположим, что выполнялась та же последовательности операторов,

что и в предыдущем примере. Тогда после выполнения процедуры

Dispose(Ptr3) в центре динамически распределяемой области памяти

образуется незанятое пространство ("дыра"). Это показано на Рис.

21.4.

HeapEnd ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

і і памяти

і і

HeapPtr ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr5^ і

Ptr5 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr4^ і

Ptr4 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і±±±±±±±±±±±±±±±±±±±±±±±±±±і

і±±±±±±±±±±±±±±±±±±±±±±±±±±і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr2^ і

Ptr2 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr1^ і

Ptr1 ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.4 Создание незанятой области ("дыры") в динамически

распределяемой области памяти.

Если в данный момент выполняется процедура New(Ptr3), то это

опять приведет к выделению той же области памяти. С другой сторо-

ны, выполнение процедуры Dispose(Ptr4) увеличит размер свободного

блока, так как Ptr3 и Ptr4 были соседними блоками (см. Рис.

21.5).

HeapEnd ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

і і памяти

і і

HeapPtr ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr5^ і

Ptr5 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і±±±±±±±±±±±±±±±±±±±±±±±±±±і

і±±±±±±±±±±±±±±±±±±±±±±±±±±і

і±±±±±±±±±±±±±±±±±±±±±±±±±±і

і±±±±±±±±±±±±±±±±±±±±±±±±±±і

ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr2^ і

Ptr2 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr1^ і

Ptr1 ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.5 Увеличение размера незанятого блока памяти.

В конечном итоге выполнение процедуры Dispose(Ptr5) приведет

сначала к созданию незанятого блока большего размера, а затем

НеаpPtr переместится в более младшие адреса памяти. Поскольку

последним допустимым указателем теперь будет Ptr2 (см. Рис.

21 6), то это приведет к действительному освобождению незанятого

блока.

HeapEnd ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

і і памяти

і і

і і

і і

і і

і і

і і

і і

HeapPtr ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr2^ і

Ptr2 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і содержимое Ptr1^ і

Ptr1 ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.7 Освобождение незанятого блока памяти.

Как показано на Рис. 21.7, динамически распределяемая об-

ласть памяти теперь находится в том же самом состоянии, в каком

она находилась бы после выполнения процедуры Rеlеаsе(P). Однако

создаваемые и освобождаемые при таком процессе незанятые блоки

отслеживаются для их возможного повторного использования.

Список свободных блоков

Адреса и размеры свободных блоков, созданных при операциях

Dispose и FrееМем, хранятся в списке свободных блоков, который

увеличивается вниз, начиная со старших адресов памяти, в сегменте

динамически распределяемой области. Каждый раз перед выделением

памяти для динамической переменной, перед тем, как динамически

распределяемая область будет расширена, проверяется список сво-

бодных блоков. Если имеется блок соответствующего размера (то

есть размер которого больше или равен требуемому размеру), то он

используется.

Процедура Rеlеаsе всегда очищает список свободных блоков.

Таким образом, программа динамического распределения памяти "за-

бывает" о незанятых блоках, которые могут существовать ниже ука-

зателя динамически распределяемой области. Если вы чередуете об-

ращения к процедурам Маrk и Rеlеаsе с обращениями к процедурам

Dispose и FrееМем, то нужно обеспечить отсутствие таких свободных

блоков.

Переменная FreeList модуля System указывает на первый сво-

бодный блок динамически распределяемой области памяти. Данный

блок содержит указатель на следующий свободный блок и т.д. Пос-

ледний свободный блок содержит указатель на вершину динамически

распределяемой области (то есть адрес, заданный HeapPtr). Если

свободных блоков в списке свободных блоков нет, то FreeList будет

равно HeapPtr.

Формат первых 8 байт свободного блока задается типом

TFreeRec:

type

PFreeRec = ^TFreeRec;

TFreeRec = record

Next: PFreeRec;

Size: Pointer;

end;

Поле Next указывает на следующий свободный блок, или на ту

же ячейку, что и HeapPtr, если блок является последним свободным

блоком. В поле Size записан размер свободного блока. Значение в

поле Size представляет собой не обычное 32-битовое значение, а

"нормализованное" значение-указатель с числом свободных парагра-

фов (16-байтовых блоков) в старшем слове и счетчиком свободных

байт (от 0 до 15) в младшем слове. Следующая функция BlockSize

преобразует значение поля Size в обычное значение типа Longint:

function BlockSize(Size: Pointer): Longint;

type

PtrRec = record Lo, Hi: Word end;

begin

BlockSize := Longint(PtrRec(Size)).Hi)*16+PtrRec(Size).Lo

end;

Чтобы обеспечить, что в начале свободного блока всегда имеет-

ся место для TFreePtr, подсистема управления динамически распре-

деляемой областью памяти округляет размер каждого блока, выделен-

ного подпрограммами New и GetMem до 8-байтовой границы. Таким

образом, 8 байт выделяется для блоков размером 1..8, 16 байт -

для блоков размером 9..16 и т.д. Сначала это кажется непроизводи-

тельной тратой памяти. Это в самом деле так, если бы каждый блок

был размером 1 байт. Но обычно блоки имеют больший размер, поэто-

му относительный размер неиспользуемого пространства меньше.

8-байтовый коэффициент раздробленности обеспечивает, что

при большом числе случайного выделения и освобождения блоков от-

носительно небольшого размера (что типично для записей переменной

длины в программах обработки текста) не приведет к сильной фраг-

ментации динамически распределяемой области. В качестве примера

предположим, что занимается и освобождается блок размером 50

байт. После его освобождения запись о нем включается в список

свободных блоков. Этот блок округляется до 56 (7*8) байт. Если в

дальнейшем потребуется блок размером от 49 до 56 байт, то данный

блок будет полностью повторно использован, а не останется от 1 до

7 байт памяти (использование который маловероятно), которые будут

только фрагментировать динамически распределяемую область.

Переменная HeapError

Переменная HeapError позволяет вам реализовать функцию обра-

ботки ошибки динамически распределяемой области памяти. Эта функ-

ция вызывается каждый раз, когда программа динамического распре-

деления памяти не может выполнить запрос на выделение памяти.

НеаpЕrror является указателем, который ссылается на функцию со

следующим заголовком:

function HeapFunc(Size: Word): Integer; far;

Заметим, что директива far указывает функции обработки ошиб-

ки динамически распределяемой области не необходимость использо-

вать дальнюю модель вызова.

Функция обработки ошибки динамически распределяемой области

реализуется путем присваивания ее адреса переменной НеаpEror:

HeapError := @HeapFunc;

Функция обработки ошибки динамически распределяемой области

памяти получает управление, когда при обращении к процедурам New

или GetМем запрос не может быть выполнен. Параметр Size содержит

размер блока, для которого не оказалось области памяти соответс-

твующего размера, и функция обработки ошибки динамически распре-

деляемой области попытается освободить блок, размер которого не

меньше данного размера.

В зависимости от успеха выполнения этой попытки функция об-

работки ошибки динамически распределяемой области возвращает зна-

чения 0, 1 или 2. Возвращаемое значение 0 свидетельствует о неу-

дачной попытке, что немедленно приводит к возникновению ошибки во

время выполнения программы. Возвращаемое значение 1 также свиде-

тельствует о неудачной попытке, но вместо ошибки этапа выполнения

оно приводит к тому, что процедуры GetМем или FrееМем возвращают

указатель nil. Наконец, возвращаемое значение 2 свидетельствует

об удачной попытке и вызывает повторную попытку выделить память

(которая также может привести к вызову функции обработки ошибки

динамически распределяемой области).

Стандартная обработки функция ошибки динамически распределя-

емой области всегда возвращает значение 0, приводя, таким обра-

зом, к ошибке всякий раз, когда не могут быть выполнены процедуры

New или GetМем. Однако для многих прикладных программ более под-

ходящей является простая функция обработки ошибки динамически

распределяемой области, пример которой приведен ниже:

function HeapFunc(Size: Word): Integer; far;

begin

HeapFunc := 1;

end;

Если такая функция реализована, то вместо принудительного

завершения работы программы в ситуации, когда процедуры New или

GetМем не могут выполнить запрос, она будет возвращать пустой

указатель (указатель nil).

Вызов функции ошибки динамически распределяемой области па-

мяти со значением параметра Size, равным 0, показывает, что удов-

летворение запроса на выделение памяти привело к расширению дина-

мически распределяемой области памяти путем перемещения HeapPtr

вверх. Это происходит, когда в списке свободных блоков нет сво-

бодных блоков, или когда все свободные блоки слишком малы для

удовлетворения данного запроса. Вызов со значением Size, равным

0, не указывает на состояние ошибки, поскольку между HeapPtr и

HeapEnd достаточно пространства для расширения, однако такой вы-

зов служит предупреждением, что неиспользуемая область выше

HeapPtr сократилась, и подсистема управления динамически распре-

деляемой областью памяти игнорирует значение, возвращаемое при

вызове такого типа.

Использование памяти в программах DOS защищенного режима

В данном разделе поясняется использование память в програм-

мах Borland Pascal для защищенного режима.

Сегменты кода

Прикладная программа и каждая библиотека в прикладной прог-

рамме или DLL имеет свой собственный сегмент кода. По умолчанию

модули с аналогичными атрибутами группируются в сегментах кода.

Вы можете управлять таким группированием с помощью директив $S и

$G имя_модуля. Размер одного сегмента кода не может превышать

64К, но общий размер кода ограничен только объемом доступной па-

мяти.

Атрибуты сегмента

Каждый сегмент кода имеет набор атрибутов, определяющих по-

ведение сегмента кода при загрузке в память.

Атрибуты MOVEABLE или FIXED

Когда сегмент кода имеет атрибут MOVEABLE (перемещаемый),

администратор памяти может перемещать сегмент в физической памя-

ти, чтобы удовлетворить другие запросы распределения памяти. Ког-

да сегмент кода имеет атрибут FIXED (фиксированный), он ни при

каких обстоятельствах не перемещается в физической памяти. Пред-

почтительным атрибутом является MOVEABLE, и пока не будет абсо-

лютно необходимо хранить сегмента в одних и тех же адресах памяти

(это имеет место, например, для обработчика прерываний), следует

использовать этот атрибут. Когда вам потребуется фиксированный

сегмент кода, такой сегмент кода следует сделать по возможности

маленьким.

Атрибуты PRELOAD или DEMANDLOAD

Сегмент кода, имеющий атрибут PRELOAD (предварительно загру-

жаемый), автоматически загружается при активизации прикладной

программы или библиотеки. Атрибут DEMANDLOAD (загружаемый по зап-

росу) откладывает загрузку сегмента или программы до фактического

вызова сегмента. Хотя это требует больше времени, но позволяет

прикладной программе экономить память.

Атрибуты DISCARDABLE или PERMAMENT

Когда сегмент имеет атрибут DIASCARDABLE (выгружаемый), ад-

министратор памяти защищенного режима может освободить занимаемую

сегментом память, когда требуется дополнительная память. Когда

сегмент имеет атрибут PERMANENT (постоянный), он все время хра-

нится в памяти. Когда прикладная программа вызывает сегмент

DISCARDABLE, отсутствующий в памяти, администратор защищенного

режима сначала загружает его из файла .EXE. Это требует больше

времени, чем если бы сегмент имел атрибут PERMANENT, но позволяет

выполнять прикладную программу в меньшем объеме памяти.

Сегмент DISCARDABLE в программе DOS защищенного режима ана-

логичен оверлейному сегменту в программе DOS, в то время как сег-

мент PERMANENT в защищенном режиме DOS аналогичен сегменту прог-

раммы DOS, не являющемуся оверлейным.

Сегменты данных и стека

Каждая прикладная программа защищенного режима DOS или биб-

лиотека содержит сегмент данных, которые может иметь размер до

64К. На сегмент всегда указывает регистр сегмента данных (DS).

Этот сегмент содержит типизированные константы и глобальные пере-

менные.

Кроме сегмента данных, прикладная программа защищенного ре-

жима DOS имеет сегмент стека, который используется для хранения

локальных переменных, распределенных процедурами и функциями. На

входе в прикладную программу регистр сегмента стека (SS) и указа-

тель стека (SP) загружены таким образом, что пара регистров SS:SP

указывает на первый байт после сегмента стека. Когда вызываются

процедуры и функции, SP для выделения пространства для парамет-

ров, адреса возврата и локальных переменных перемещается вниз.

Когда подпрограмма возвращает управление, процесс изменяется на

обратный: указатель стека увеличивается до значения, которое он

имел перед вызовом. По умолчанию размер сегмента стека равен 16К,

но с помощью директивы компилятора $M его можно изменить.

В отличие от прикладной программы, DDL DOS защищенного режи-

ма не имеет сегмента стека. Когда в DLL вызывается процедура или

функция, регистр DS изменяется, чтобы указывать на сегмент данных

DLL, но пара регистров SS:SP не модифицируется. Таким образом,

DLL всегда использует сегмент стека вызывающей прикладной прог-

раммы.

Изменение атрибутов

Используемые по умолчанию атрибуты сегмента кода - это атри-

буты MOVEABLE, DEMANDLOAD и DISCARDABLE. Но с помощью директивы

компилятора $C вы можете задать другие используемые по умолчанию

атрибуты, например:

{$C MOVEABLE PRELOAD PERMANENT}

В прикладной программе защищенного режима DOS нет необходи-

мости в администраторе оверлеев. Администратор памяти DOS защи-

щенного режима включает в себя полный набор средств управления

оверлеями, управлять которыми можно через атрибуты сегмента кода.

Описываемые ниже средства доступны для любой программы защищенно-

го режима DOS.

Примечание: Подробности о директиве компилятора $C

можно найти в Главе 2 ("Директивы компилятора") в "Справоч-

ном руководстве программиста".

Администратор динамически распределяемой области памяти DOS

Расширения Borland защищенного режима DOS включают в себя

полный администратор памяти защищенного режима. При выполнении

программы защищенного режима DOS вся доступная память превращает-

ся в глобальную динамически распределяемую область памяти, кото-

рая управляется администратором памяти (подсистемой управления

памятью) защищенного режима. Прикладная программа может получить

доступ к глобальной динамически распределяемой области памяти че-

рез подпрограммы GlobalXXXX модуля WinAPI. Хотя можно распреде-

лять блоки глобальной памяти любого размера, глобальная динами-

чески распределяемая область памяти предназначена только для

больших блоков (1024 байт или более). Для каждого блока глобаль-

ной памяти требуется дополнительно 32 байта (это непроизводитель-

ные затраты), а общее число блоков глобальной памяти не может

превышать 8192.

Примечание: Подробнее расширения Borland защищенного

режима DOS описываются в Главе 17 "Программирование в защи-

щенном режиме DOS".

Borland Pascal включает в себя администратор памяти (который

называют также подсистемой управления памятью), реализующий стан-

дартные процедуры New, Dispose, GetMem и FreeMem. Администратор

памяти использует для всех распределений памяти глобальную дина-

мически распределяемую область. Поскольку глобальная динамически

распределяемая область памяти ограничена 8192 блоками (что оче-

видно меньше, чем может потребоваться для некоторых прикладных

программ), администратор памяти Borland Pascal реализует алгоритм

вторичного распределения сегментов, который улучшает производи-

тельность и допускает распределение существенно большего коли-

чества блоков.

Примечание: Borland Pascal для расширенного режима DOS

не поддерживает схему распределения с помощью процедур MArk

и Release, предусмотренную для реального режима DOS.

Алгоритм вторичного распределения блоков работает следующим

образом. При выделении большого блока администратор памяти просто

выделяет глобальную память с помощью подпрограммы GlobalAlloc.

При выделении маленького блока администратор выделяет более круп-

ный блок памяти, а затем разбивает его (вторично распределяет) по

требованию на более мелкие блоки. Перед тем, как администратор

памяти выделяет новый блок глобальной памяти, который, в свою

очередь, будет распределяться повторно, при распределении малень-

ких блоков повторно используется все доступное пространство вто-

ричного распределения.

Примечание: Об использовании администратора памяти в

DLL подробнее рассказывается в Главе 11 "Динамически компо-

нуемые библиотеки".

Переменная HeapLimit определяет порог между маленькими и

большими блоками динамически распределяемой памяти. По умолчанию

ее значение равно 1024 байтам. Переменная HeapBlock определяет

размер, используемый администратором памяти при распределении

блоков, выделенных для вторичного распределения. По умолчанию

значение HeapBlock равно 8192 байтам. Значения этих переменных

изменять не следует, но если вы это сделаете, убедитесь, что зна-

чение HeapBlock составляет не меньше четырехкратного размера

HeapLimit.

Переменная HeapAllocFpals определяет значение флагов атрибу-

тов, передаваемых GlobalAlloc, когда администратор памяти распре-

деляет блоки глобальной памяти. По умолчанию ее значение равно

gmem_Moveable.

Переменная HeapError

Переменная HeapError позволяет вам реализовать функцию обра-

ботки ошибки динамически распределяемой области памяти. Эта функ-

ция вызывается каждый раз, когда программа динамического распре-

деления памяти не может выполнить запрос на выделение памяти.

НеаpError является указателем, который ссылается на функцию со

следующим заголовком:

function HeapFunc(Size: word): integer; far;

Заметим, что директива far указывает функции обработки ошиб-

ки динамически распределяемой области не необходимость использо-

вать дальнюю модель вызова.

Функция обработки ошибки динамически распределяемой области

реализуется путем присваивания ее адреса переменной НеаpEror:

HeapError := @HeapFunc;

Функция обработки ошибки динамически распределяемой области

памяти получает управление, когда при обращении к процедурам New

или GetМем запрос не может быть выполнен. Параметр Size содержит

размер блока, для которого не оказалось области памяти соответс-

твующего размера, и функция обработки ошибки динамически распре-

деляемой области произведет попытку освобождения блока, размер

которого не меньше данного размера.

Перед вызовом функции обработки ошибки динамически распреде-

ляемой области памяти администратор динамически распределяемой

памяти пытается выделить свободный блок из блоков вторичного раз-

биения, а также использовать непосредственный вызов функции

GlobalAlloc.

В зависимости от успеха выполнения этой попытки функция об-

работки ошибки динамически распределяемой области возвращает зна-

чения 0, 1 или 2. Возвращаемое значение 0 свидетельствует о неу-

дачной попытке, что немедленно приводит к возникновению ошибки во

время выполнения программы. Возвращаемое значение 1 также свиде-

тельствует о неудачной попытке, но вместо ошибки этапа выполнения

оно приводит к тому, что процедуры New или GetМем возвращают ука-

затель nil. Наконец, возвращаемое значение 2 свидетельствует об

удачной попытке и вызывает повторную попытку выделить память (ко-

торая также может привести к вызову функции обработки ошибки ди-

намически распределяемой области).

Стандартная обработки функция ошибки динамически распределя-

емой области всегда возвращает значение 0, приводя, таким обра-

зом, к ошибке всякий раз, когда не могут быть выполнены процедуры

New или GetМем. Однако для многих прикладных задач более подходя-

щей является простая функция обработки ошибки динамически распре-

деляемой области, пример которой приведен ниже:

function HeapFunc(Size: Word): Integer; far;

begin

HeapFunc := 1;

end;

Если такая функция реализована, то вместо принудительного

завершения работы программы в ситуации, когда процедуры New или

GetМем не могут выполнить запрос, она будет возвращать пустой

указатель (указатель nil).

Использование памяти в программах Windows

В данном разделе поясняется использование памяти в програм-

мах Borland Pascal для Windows.

Атрибуты сегментов

Каждый сегмент кода имеет набор атрибутов, определяющих его

поведение при загрузке в память.

Атрибуты MOVEABLE или FIXED

Когда сегмент является перемещаемым (MOVEABLE), Windows,

чтобы удовлетворить потребности в распределяемой памяти, может

перемещать сегмент в физической памяти. Когда сегмент кода фикси-

рованный (FIXED), он не перемещается в физической памяти. Более

предпочтителен атрибут MOVEABLE, и если нет абсолютной необходи-

мости хранить сегмент кода по одному и тому же адресу в физичес-

кой памяти (как бывает в том случае, если он содержит драйвер

прерываний), следует использовать атрибут MOVEABLE.

Атрибуты PRELOAD или DEMANDLOAD

Сегмент кода, имеющий атрибут PRELOAD, при активизации прик-

ладной программы или библиотеки загружается автоматически. Атри-

бут DEMANDLOAD откладывает загрузку сегмента до тех пор, пока

подпрограмма в сегменте действительно не будет вызвана.

Атрибуты DISCARDABLE или PERMANENT

Когда сегмент имеет атрибут DISCARDABLE, Windows при необхо-

димости выделения дополнительной памяти может освобождать память,

занимаемую данным сегментом. Когда прикладная программа обращает-

ся к выгружаемому сегменту (DISCARDABLE), которого нет в памяти,

Windows загружает его сначала из файла .EXE. Это занимает большее

время, чем если бы сегмент был постоянным (PERMANENT), но позво-

ляет прикладной программе при выполнении занимать меньше места.

Грубо говоря, сегмент DISCARDABLE в прикладной программе

Windows очень напоминает оверлейный сегмент в программе DOS.

Изменение атрибутов

По умолчанию сегменту кода назначаются атрибуты MOVEABLE,

PRELOAD и PERMANEMT, но с помощью директивы компилятора $C вы мо-

жете их изменить. Например:

{$C MOVEABLE DEMANDLOAD DISCARDABLE}

Примечание: Более подробно о директиве $C рассказыва-

ется в Главе 2 ("Директивы компилятора") "Справочного руко-

водства программиста".

В прикладной программе Windows нет необходимости выделять

подсистему управления оверлеями. Администратор памяти Windows

включает в себя полный набор обслуживающих средств, управляемых

атрибутами сегмента кода. Эти средства доступны любой прикладной

программе Windows.

Сегмент локальных динамических данных

Каждая прикладная программа или библиотека имеет один сег-

мент данных, который называется сегментом локальных динамических

данных и может занимать до 64К. На сегмент локальных динамических

данных всегда указывает регистр сегмента данных DS. Он разделен

на четыре части:

Сегмент локальных динамических данных

ЪДДДДДДї

і і

і Локальная динамически распределя- і

і емая область памяти і

і і

ГДДДДДДґ

і і

і Стек і

і і

ГДДДДДДґ

і і

і Статические данные і

і і

ГДДДДДДґ

і і

і Заголовок задачи і

і і

АДДДДДДЩ

Рис. 21.7 Сегмент локальных динамических данных.

Первый 16 байт сегмента локальных динамических данных всегда

содержат заголовок задачи, в котором Windows сохраняет различную

системную информацию.

Область статических данных содержит все глобальные перемен-

ные и типизированные константы, описанные в прикладной программе

или библиотеке.

Сегмент стека используется для хранения локальных перемен-

ных, распределяемых процедурами и функциями. На входе в приклад-

ную программу регистр сегмента стека SS и указатель стека SP заг-

ружаются таким образом, что SS:SP указывает на первый байт после

области стека в сегменте локальных динамических данных. При вызо-

ве процедур и функций SP перемещается вниз, выделяя память для

параметров, адреса возврата и локальных переменных. Когда подп-

рограмма возвращает управление, процесс изменяется на обратный:

SP увеличивается и принимает то значение, которое было перед вы-

зовом. Используемый по умолчанию размер области стека в автомати-

ческом сегменте данных равен 8К, но с помощью директивы компиля-

тора $M это значение можно изменить.

В отличие от прикладной программы библиотека в сегменте ло-

кальных динамических данных не имеет области стека. При вызове в

динамически компонуемой библиотеке DLL процедуры или функции ре-

гистр DS указывает на сегмент локальных динамических данных биб-

лиотеки, но пара регистров SS:SP не изменяется. Таким образом,

библиотека всегда использует стек вызывающей прикладной програм-

мы.

Последняя часть в сегменте локальных динамических данных -

локальная динамически распределяемая область. Она содержит все

локальные динамические данные, которые распределялись с помощью

функции LocalAlloc в Windows. По умолчанию локальная динамически

распределяемая область имеет размер 8К, но это значение можно из-

менить с помощью директивы компилятора $M.

Windows допускает, чтобы сегмент локальных динамических дан-

ных был перемещаемым, но Borland Pascal этого не поддерживает.

Сегмент локальных динамических данных прикладной программы или

библиотеки Borland Pascal всегда блокируется, этим обеспечивает-

ся, что селектор (адрес сегмента) сегмента локальных динамических

данных никогда не изменяется. При работе в стандартном или расши-

ренном режиме это не приводит ни к какому ухудшению, поскольку

сегмент сохраняет тот же селектор даже при перемещении в физичес-

кой памяти. Однако в реальном режиме, если требуется расширение

локальной динамически распределяемой области, Windows, возможно,

не сможет этого сделать, поскольку сегмент локальных динамических

данных перемещаться не может. Если ваша прикладная программа ис-

пользует локальную динамически распределяемую область памяти и

должна выполняться в реальном режиме, то следует обеспечить, что-

бы начальный размер локальной динамически распределяемой области

был таким, чтобы он удовлетворял всем потребностям в распределе-

нии локальной динамической области (для этого используется дирек-

тива компилятора $M).

Администратор динамически распределяемой области памяти

Windows поддерживает динамическое распределение памяти в

двух различных динамически распределяемых областях: глобальной

динамически распределяемой области и локальной динамически расп-

ределяемой области.

Примечание: Более подробно о локальной и глобальной

динамически распределяемой области рассказывается в "Руко-

водстве программиста по Windows".

Глобальная динамически распределяемая область - это пул па-

мяти, доступный для всех прикладных программ. Хотя могут выде-

ляться блоки глобальной памяти любого размера, глобальная динами-

чески распределяемая область памяти предназначена только для

"больших" областей памяти (256 байт или более). Каждый блок гло-

бальной памяти имеет избыточный размер 20 байт, и при работе в

стандартной среде Windows в улучшенном режиме 386 существует ог-

раничение в 8192 блока памяти, только некоторые из которых дос-

тупны для отдельной прикладной программы.

Локальная динамически распределяемая область памяти - это

пул памяти, доступной только для вашей прикладной программы или

библиотеки. Она расположена в верхней части сегмента данных прик-

ладной программы или библиотеки. Общий размер блоков локальной

памяти, которые могут выделяться в локальной динамически распре-

деляемой области, равен 64К, минус размер стека прикладной прог-

раммы и статических данных. По этой причине локальная динамически

распределяемая область памяти лучше подходит для "небольших" бло-

ков памяти (26 байт или менее). По умолчанию размер локальной ди-

намически распределяемой области равен 8К, но с помощью директивы

компилятора $M это значение можно изменить.

Примечание: Borland Pascal не поддерживает механизм

распределения памяти с помощью процедур Mark и Release, ко-

торые предусмотрены в версии для DOS.

Borland Pascal включает в себя подсистему управления динами-

чески распределяемой памятью (администратор памяти), которая реа-

лизует стандартные процедуры New, Dispose, GetMem и FreeMem. Для

всех выделений памяти подсистема динамически управления распреде-

ляемой областью памяти использует глобальную динамически распре-

деляемую область. Поскольку глобальная динамически распределяемая

область памяти имеет системное ограничение в 8192 блока (что оп-

ределенно меньше, чем может потребоваться в некоторых прикладных

задачах), подсистема управления динамически распределяемой об-

ластью памяти Borland Pascal для улучшения производительности и

обеспечения выделения существенно большего числа блоков включает

в себя алгоритм вторичного распределения сегмента.

Примечание: Более подробно об этом рассказывается в

Главе 11 "Динамически компонуемые библиотеки".

Алгоритм вторичного выделения сегмента работает следующим

образом: при распределении большого блока администратор динами-

чески распределяемой области памяти просто выделяет глобальный

блок памяти, используя подпрограмму Windows ClobalAlloc. При вы-

делении маленького блока администратор динамически распределяемой

области памяти выделяет больший блок памяти, а затем делит его на

более мелкие блоки (как требуется). При выделении "маленьких"

блоков перед тем, как администратор динамически распределяемой

области памяти выделит блок глобальной динамически распределяемой

памяти (который будет в свою очередь разбит на блоки), повторно

используются все доступные мелкие блоки.

Границу между маленькими и большими блоками определяется пе-

ременной HeapLimit. По умолчанию она имеет значение 1024 байта.

Переменная HeapBlock определяет размер, который использует под-

система управления динамически распределяемой областью памяти при

выделении блоков для вторичного разбиения. По умолчанию она имеет

значение 8192 байта. Изменять эти значения вам незачем, но если

вы решите это сделать, убедитесь что HeapBlock имеет значение по

крайней мере в четыре раза превышающее HeapLimit.

Переменная HeapAllocFlags определяет значение флагов атрибу-

тов, передаваемых GlobalAlloc, когда администратор памяти распре-

деляет глобальные блоки. В программе по умолчанию используется

значение gmem_Moveable, а в библиотеке - gmem_Moveable +

gmem_SSEShure.

Блоки глобальной памяти, выделяемые администратором динами-

чески распределяемой области памяти, всегда блокируются непос-

редственно после своего выделения (с помощью GlobalLock) немед-

ленно после своего выделения и не разблокируются, пока не будут

освобождены. Этим обеспечивается, что селекторы (адреса сегмен-

тов) блоков не изменяются. В стандартной среде Windows и улучшен-

ных режимах процессора 386 фиксированные блоки могут, тем не ме-

нее, перемещаться в физической памяти, освобождая место для дру-

гих запросов по выделению памяти, поэтому это не ухудшает произ-

водительности администратора динамически распределяемой области

памяти Borland Pascal. Однако в реальном режиме, если от Windows

требуется расширение локальной динамически распределяемой облас-

ти, администратор памяти Windows, возможно, не сможет переместить

их, чтобы выделить другие блоки. Если ваша прикладная программа

использует локальную динамически распределяемую область и должна

выполняться в реальном режиме, можно рассмотреть при выделении

блоков динамической памяти возможность использования средств

распределения памяти, предоставляемых Windows.

Переменная HeapError

Переменная HeapError позволяет вам реализовать функцию обра-

ботки ошибки динамически распределяемой области памяти. Эта функ-

ция вызывается каждый раз, когда программа динамического распре-

деления памяти не может выполнить запрос на выделение памяти.

НеаpError является указателем, который ссылается на функцию со

следующим заголовком:

function HeapFunc(Size: word): integer; far;

Заметим, что директива far указывает функции обработки ошиб-

ки динамически распределяемой области не необходимость использо-

вать дальнюю модель вызова.

Функция обработки ошибки динамически распределяемой области

реализуется путем присваивания ее адреса переменной НеаpEror:

HeapError := @HeapFunc;

Функция обработки ошибки динамически распределяемой области

памяти получает управление, когда при обращении к процедурам New

или GetМем запрос не может быть выполнен. Параметр Size содержит

размер блока, для которого не оказалось области памяти соответс-

твующего размера, и функция обработки ошибки динамически распре-

деляемой области попытается освободить блок, размер которого не

меньше данного размера.

Перед вызовом функции обработки ошибки динамически распреде-

ляемой области памяти подсистема динамического распределения па-

мяти пытается выделить свободный блок из блоков вторичного разби-

ения, а также использовать непосредственный вызов функции

GlobalAlloc.

В зависимости от успеха выполнения этой попытки функция об-

работки ошибки динамически распределяемой области возвращает зна-

чения 0, 1 или 2. Возвращаемое значение 0 свидетельствует о неу-

дачной попытке, что немедленно приводит к возникновению ошибки во

время выполнения программы. Возвращаемое значение 1 также свиде-

тельствует о неудачной попытке, но вместо ошибки этапа выполнения

оно приводит к тому, что процедуры New или GetМем возвращают ука-

затель nil. Наконец, возвращаемое значение 2 свидетельствует об

удачной попытке и вызывает повторную попытку выделить память (ко-

торая также может привести к вызову функции обработки ошибки ди-

намически распределяемой области).

Стандартная обработки функция ошибки динамически распределя-

емой области всегда возвращает значение 0, приводя, таким обра-

зом, к ошибке всякий раз, когда не могут быть выполнены процедуры

New или GetМем. Однако для многих прикладных задач более подходя-

щей является простая функция обработки ошибки динамически распре-

деляемой области, пример которой приведен ниже:

function HeapFunc(Size: word) integer; far;

begin

HeapFunc := 1;

end;

Если такая функция реализована, то вместо принудительного

завершения работы программы в ситуации, когда процедуры New или

GetМем не могут выполнить запрос, она будет возвращать пустой

указатель (указатель nil).

Форматы внутреннего представления данных

Далее описываются форматы внутреннего представления данных

Borland Pascal.

Целочисленные типы

Формат, выбираемый для представления переменной целого типа,

зависит от ее минимальной и максимальной границ:

1. Если обе границы находятся в диапазоне -128..127

(Shotrint - короткое целое), то переменная хранится, как

байт со знаком.

2. Если обе границы находятся в диапазоне 0..255 (Byte -

байтовая переменная), то переменная хранится, как байт

без знака.

3. Если обе границы находятся в диапазоне -32768..32767

(Integer - целое), то переменная хранится, как слово со

знаком.

4. Если обе границы находятся в диапазоне 0..65535 (Word -

переменная длиной в слово), то переменная хранится, как

слово.

5. В противном случае переменная хранится, как двойное сло-

во со знаком (Longint - длинное целое).

Символьный тип

Символьный тип или поддиапазон (отрезок) символьного типа

(Char) хранится, как байт без знака.

Булевский тип

Значения и переменные булевского типа Boolean хранятся как

байт, WordBool - как слово, а LongBool - как значение Longint.

При этом подразумеваются, что они могут принимать значения 0

(Falsе) или 1 (Тruе).

Перечислимый тип

Значения перечислимого типа хранятся, как байт без знака,

если нумерация не превышает 256. В противном случае они хранятся,

как слово без знака.

Типы с плавающей точкой

Типы значений с плавающей точкой Real, Single, Double,

Extended и Comp (вещественный, с одинарной точностью, с двойной

точностью, с повышенной точностью и сложный) хранятся в виде дво-

ичного представления знака (+ или -), показателя степени и знача-

щей части числа. Представляемое число имеет значение:

+/- значащая_часть Х 2^показатель_степени

где значащая часть числа представляет собой отдельный бит слева

от двоичной десятичной точки (то есть 0 <= значащая часть <= 2).

В следующей далее схеме слева расположены старшие значащие

биты, а справа - младшие значащие биты. Самое левое значение хра-

нится в самых старших адресах. Например, для значения веществен-

ного типа e сохраняется в первом байте, f - в следующих пяти бай-

тах, а s - в старшем значащем бите последнего байта.

Вещественный тип

Шестибайтовое (48-битовое) вещественное число (Real) подраз-

деляется на три поля:

1 39 8

ЪДДДВДДДДДД..ДДДДДДДВДДДДДДДДї

і s і f і e і

АДДДБДДДДДД..ДДДДДДДБДДДДДДДДЩ

msb lsb msb lsb

Значение v числа определяется с помощью выражений:

if 0 < e <= 255, then v = (-1)^s * 2^(e-129)*(l.f).

if e = 0, then v = 0.

Вещественный тип не может использоваться для хранения ненор-

мализованных чисел, значений, не являющихся числом (NaN), а также

бесконечно малых и бесконечно больших значений. Ненормализованное

число при сохранении его в виде вещественного принимает нулевое

значение, а не числа, бесконечно малые и бесконечно большие зна-

чения при попытке использовать для их записи формат вещественного

числа приводят к ошибке переполнения.

Здесь и далее msb означает более значащий бит (старшие раз-

ряды), lsb - менее значащий (младшие разряды).

Тип числа с одинарной точностью

Четырехбайтовое (32-битовое) число типа Single подразделяет-

ся на три поля:

1 8 23

ЪДДДВДДДДДДВДДДДДДД..ДДДДДДДДДї

і s і e і f і

АДДДБДДДДДДБДДДДДДД..ДДДДДДДДДЩ

msb lsb msb lsb

Значение v этого числа определяется с помощью выражений:

if 0 < e < 255, then v = (-1)^s * 2^(e-12) * (l.f).

if e = 0 and f <> 0, then v = (-1)^s * 2^(126) * (o.f).

if e = 0 and f = 0, then v = (-1)^s * O.

if e = 255 and f = 0, then v = (-1)^s * Inf.

if e = 255 and f <> 0, then v = NaN.

Тип числа с двойной точностью

Восьмибайтовое (64-битовое) число типа Double подразделяется

на три поля:

1 11 52

ЪДДДВДДДДДДВДДДДДДД..ДДДДДДДДї

і s і e і f і

АДДДБДДДДДДБДДДДДДД..ДДДДДДДДЩ

msb lsb msb lsb

Значение v этого числа определяется с помощью выражений:

if 0 < e < 2047, then v = (-1)^s * 2^(e-1023) * (l.f).

if e = 0 and f <> 0, then v = (-1)^s * 2^(1022) * (o.f).

if e = 0 and f = 0, then v = (-1)^s * O.

if e = 2047 and f = 0, then v = (-1)^s * Inf.

if e = 2047 and f <> 0, then v = NaN.

Тип числа с повышенной точностью

Десятибайтовое (80-битовое) число типа Extended подразделя-

ется на четыре поля:

1 15 1 63

ЪДДДВДДДДДДДДВДДДВДДДДДДДД..ДДДДДДДї

і s і e і i і f і

АДДДБДДДДДДДДБДДДБДДДДДДДД..ДДДДДДДЩ

msb lsb msb lsb

Значение v этого числа определяется с помощью выражений:

if 0 < e < 32767, then v = (-1)^s * 2^(e-1023) * (l.f).

if e = 32767 and f = 0, then v = (-1)^s * Inf.

if e = 32767 and f <> 0, then v = NaN.

Сложный тип

Восьмибайтовое (64-битовое) число сложного типа (Comp) под-

разделяется на два поля:

1 63

ЪДДДВДДДДДДДДДДД..ДДДДДДДДДДДДДДї

і s і d і

АДДДБДДДДДДДДДДД..ДДДДДДДДДДДДДДЩ

msb lsb

Значение v этого числа определяется с помощью выражений:

if s = 1 and d = 0, then v = NaN.

в противном случае v представляет собой 64-битовое значение, яв-

ляющееся дополнением до двух.

Значения типа указатель

Значение типа указатель хранится в виде двойного слова, при

этом смещение хранится в младшем слове, а адрес сегмента - в

старшем слове. Значение указателя nil хранится в виде двойного

слова, заполненного 0.

Значения строкового типа

Строка занимает столько байт, какова максимальная длина

строки, плюс один байт. Первый байт содержит текущую динамическую

длину строки, а последующие байты содержат символы строки. Бит

длины и символы рассматриваются, как значения без знака. Макси-

мальная длина строки - 255 символов, плюс байт длины

(string[255]).

Значения множественного типа

Множество - это массив бит, в котором каждый бит указывает,

является элемент принадлежащим множеству или нет. Максимальное

число элементов множества - 256, так что множество никогда не мо-

жет занимать более 32 байт. Число байт, занятых отдельным мно-

жеством, вычисляется, как:

ByteSize = (Max div 8) - (Min div 8) + 1

где Мin и Мах - нижняя и верхняя граница базового типа этого мно-

жества. Номер байта для конкретного элемента E вычисляется по

формуле:

ByteNumber = (E div 8) - (Min div 8)

а номер бита внутри этого байта по формуле:

BitNumber = E mod 8

где E обозначает порядковое значение элемента.

Значения типа массив

Массив хранится в виде непрерывной последовательности пере-

менных, каждая из которых имеет тип массива. Элементы с наимень-

шими индексами хранятся в младших адресах памяти. Многомерный

массив хранится таким образом, что правый индекс возрастает быст-

рее.

Значения типа запись

Поля записи хранятся, как непрерывная последовательность пе-

ременных. Первое поле хранится в младших адресах памяти. Если в

записи содержатся различные части, то каждая часть начинается с

одного и того же адреса памяти.

Объектные типы

Внутренний формат данных объекта имеет сходство с внутренним

форматом записи. Поля объекта записываются в порядке их описаний

как непрерывная последовательность переменных. Любое поле, унас-

ледованное от родительского (порождающего) типа, записывается пе-

ред новыми полями, определенными в дочернем (порожденном) типе.

Если объектный тип определяет виртуальные методы, конструк-

тор или деструктор, то компилятор размещает в объектном типе до-

полнительное поле данных. Это 16-битовое поле, называемое полем

таблицы виртуальных методов (VMP), используется для запоминания

смещения таблицы виртуальных методов в сегменте данных. Поле таб-

лицы виртуальных методов следует непосредственно после обычных

полей объектного типа. Если объектный тип наследует виртуальные

методы, конструкторы или деструкторы (сборщики мусора), то он

также наследует и поле таблицы виртуальных методов, благодаря че-

му дополнительное поле таблицы виртуальных методов не выделяется.

Инициализация поля таблицы виртуальных методов экземпляра

объекта осуществляется конструктором (или конструкторами) объект-

ного типа. Программа никогда не инициализирует поле таблицы вир-

туальных методов явно и не имеет к нему доступа.

Следующие примеры иллюстрируют внутренние форматы данных

объектных типов.

type

PLocation = ^TLocation;

TLocation = object

X,Y: integer;

procedure Init(PX, PY: Integer);

function GetX: Integer;

function GetY: Integer;

end;

PPoint = ^TPoint;

TPoint = object(TLocation)

Color: Integer;

constructor Init(PX, PY, PColor: Integer);

destructor Done; virtual;

procedure Show; virtual;

procedure Hide; virtual;

procedure MoveTo(PX, PY: I+nteger); virtual;

end;

PCircle = ^TCircle;

TCircle = object(TPoint)

Radius: Integer;

constructor Init(PX, PY, PColor, PRadius: Integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Fill; virtual;

end;

Рисунок 21.8 показывает размещение экземпляров типов

TLocation, TPoint и TCircle: каждый прямоугольник соответствует

одному слову памяти.

TLocation TPoint TCircle

ЪДДДДДДДДДДї ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і X і і X і і X і

ГДДДДДДДДДДґ ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ

і Y і і Y і і Y і

АДДДДДДДДДДЩ ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ

і Color і і Color і

ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ

і VMT і і VMT і

АДДДДДДДДДДДЩ ГДДДДДДДДДДДґ

і Radius і

АДДДДДДДДДДДЩ

Рис. 21.8 Схема экземпляров типов TLocation, TPoint и

TCircle.

Так как TPoint является первым типом в иерархии, который

вводит виртуальные методы, то поле таблицы виртуальных методов

размещается сразу после поля Color.

Таблица виртуальных методов

Каждый объектный тип, содержащий или наследующий виртуальные

методы, конструкторы или деструкторы, имеет связанную с ним таб-

лицу виртуальных методов, в которой запоминается инициализируемая

часть сегмента данных программы. Для каждого объектного типа (но

не для каждого экземпляра) имеется только одна таблица виртуаль-

ных методов, однако два различных объектных типа никогда не раз-

деляют одну таблицу виртуальных методов, независимо от того, нас-

колько эти типы идентичны. Таблицы виртуальных методов создаются

автоматически компилятором, и программа никогда не манипулирует

ими непосредственно. Аналогично, указатели на таблицы виртуальных

методов автоматически запоминаются в реализациях объектных типов

с помощью конструкторов программа никогда не работает с этими

указателями непосредственно.

Первое слово таблицы виртуальных методов содержит размер

экземпляров соответствующего объектного типа. Эта информация ис-

пользуется конструкторами и деструкторами для определения того,

сколько байт выделяется или освобождается при использовании рас-

ширенного синтаксиса стандартных процедур New и Dispose.

Второе слово таблицы виртуальных методов содержит отрица-

тельный размер экземпляров соответствующего объектного типа эта

информация используется ратификационным (т.е. подтверждающим

действительность) механизмом вызова виртуального метода для выяв-

ления инициализируемых объектов (экземпляров, для которых должен

выполняться конструктор) и для проверки согласованности таблицы

виртуальных методов. Когда разрешена ратификация виртуального вы-

зова (с помощью директивы {$R+} компилятора, которая расширена и

включает в себя проверку виртуальных методов), компилятор генери-

рует вызов программы ратификации таблицы виртуальных методов пе-

ред каждым вызовом виртуального метода. Программа ратификации

таблицы виртуальных методов проверяет, что первое слово таблицы

виртуальных методов не равно нулю и что сумма первого и второго

слов равна нулю. Если любая из проверок неудачна, то генерируется

ошибка 210 исполняющей системы Borland Pascal.

Разрешение проверок границ диапазонов и проверок вызовов

виртуальных методов замедляет выполнение программы и делает ее

несколько больше, поэтому используйте {$R+} только во время от-

ладки и переключите эту директиву в состояние {$R-} в окончатель-

ной версии программы.

Наконец, начиная со смещения 4 таблицы виртуальных методов

следует список 32-разрядных указателей методов, один указатель на

каждый виртуальный метод в порядке их описаний. Каждая позиция

содержит адрес точки входа соответствующего виртуального метода.

На Рис. 21.9 показано размещение таблиц виртуальных методов

типов Point и Circle (тип Location не имеет таблицы виртуальных

методов, т.к. не содержит в себе виртуальных методов, конструкто-

ров и деструкторов): каждый маленький прямоугольник соответствует

одному слову памяти, а каждый большой прямоугольник - двум словам

памяти.

Point VMT Circle VMT

ЪДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДї

і 8 і і 8 і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і -8 і і -8 і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і 0 і і 0 і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і 0 і і 0 і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і і і і

і @TPoint.Done і і @TPoint.Done і

і і і і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і і і і

і @TPoint.Show і і @TCircle.Show і

і і і і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і і і і

і @TPoint.Hide і і @TCircle.Hide і

і і і і

ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДґ

і і і і

і @TPoint.MoveToі і @TPoint.MoveTo і

і і і і

АДДДДДДДДДДДДДДДЩ ГДДДДДДДДДДДДДДДДґ

і і

і @TCircle.Fill і

і і

АДДДДДДДДДДДДДДДДЩ

Рис. 21.9 Схемы таблиц виртуальных методов для TPoint и

TCircle.

Обратите внимание на то, как TCircle наследует методы Done и

MoveTo типа TPoint и как он переопределяет Show и Hide.

Как уже упоминалось, конструкторы объектных типов содержат

специальный код, который запоминает смещение таблицы виртуальных

методов объектного типа в инициализируемых экземплярах. Например,

если имеется экземпляр P типа TPoint и экземпляр C типа TCircle,

то вызов P.Init будет автоматически записывать смещение таблицы

виртуальных методов типа TPoint в поле таблицы виртуальных мето-

дов экземпляра P, а вызов C.Init точно так же запишет смещение

таблицы виртуальных методов типа TCircle в поле таблицы виртуаль-

ных методов экземпляра C. Эта автоматическая инициализация явля-

ется частью кода входа конструктора, поэтому если управление пе-

редается в начало операторной секции, то поле Self таблицы вирту-

альных методов также будет установлено. Таким образом, при воз-

никновении необходимости, конструктор может выполнить вызов вир-

туального метода.

Таблица динамических методов

Таблица виртуальных методов объектного типа содержит для

каждого описанного в объектном типе виртуального метода и его

предков четырехбайтовую запись. В тех случаях, когда в порождаю-

щих типах (предках) определяется большее число виртуальных мето-

дов, в процессе создания производных типов может использоваться

достаточно большой объем памяти, особенно если создается много

производных типов. Хотя в производных типах могут переопределять-

ся только некоторые из наследуемых методов, таблица виртуальных

методов каждого производного типа содержит указатели метода для

всех наследуемых виртуальных методов, даже если они не изменя-

лись.

Динамические методы обеспечивают в таких ситуациях альтерна-

тиву. В Borland Pascal имеется формат таблицы методов и новый

способ диспетчеризации методов с поздним связыванием. Вместо ко-

дирования для всех методов объектного типа с поздним связыванием,

в таблице динамических методов кодируются только те методы, кото-

рые были в объектном типе переопределены. Если в наследующих ти-

пах переопределяются только некоторые из большого числа методов с

поздним связыванием, формат таблицы динамических методов исполь-

зует меньшее пространство, чем формат таблицы виртуальных мето-

дов.

Формат таблицы динамических методов иллюстрируют следующие

два объектных типа:

type

TBase = object

X: Integer;

constructor Init;

destructor Done; virtual;

procedure P10; virtual 10;

procedure P20; virtual 20;

procedure P30; virtual 30;

procedure P30; virtual 30;

end;

type

TDerived = object(TBase)

Y: Integer;

constructor Init;

destructor Done; virtual;

procedure P10; virtual 10;

procedure P30; virtual 30;

procedure P50; virtual 50;

end;

На Рис. 21.10 и 21.11 показаны схемы таблицы виртуальных ме-

тодов и таблицы динамических методов для TBase и TDerived. Каждая

ячейка соответствует слову памяти, а каждая большая ячейка - двум

словам памяти.

ТВМ TBase ТДМ TBase

ЪДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДї

і 4 і і 0 і

ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і -4 і і индекс в кэш і

ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і Смещ. ТДМ TBase і і смещение записи і

ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і 0 і і 4 і

ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і і і 10 і

і @TBase.Done і ГДДДДДДДДДДДДДДДДДДґ

і і і 20 і

АДДДДДДДДДДДДДДДДДДЩ ГДДДДДДДДДДДДДДДДДДґ

і 30 і

ГДДДДДДДДДДДДДДДДДДґ

і 40 і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TBase.P10 і

і і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TBase.P20 і

і і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TBase.P30 і

і і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TBase.P40 і

і і

АДДДДДДДДДДДДДДДДДДЩ

Рис. 21.10 Схемы таблицы виртуальных методов и таблицы дина-

мических методов для TBase.

Объектный тип имеет таблицу динамических методов только в

том случае, если в нем вводятся или переопределяются динамические

методы. Если объектный тип наследует динамические методы, но они

не переопределяются, и новые динамические методы не вводятся, то

он просто наследует таблицу динамических методов своего предка.

Как и в случае таблицы виртуальных методов, таблица динами-

ческих методов записывается в инициализированную часть сегмента

данных прикладной программы.

ТВМ TDerived ТДМ TDerived

ЪДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДї

і 6 і і Смещ. ТДМ TBase і

ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і -6 і і индекс в кеше і

ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і Смещ. ТДМ TDerivedі і смещение записи і

ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і 0 і і 3 і

ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ

і і і 10 і

і @TBase.Done і ГДДДДДДДДДДДДДДДДДДґ

і і і 30 і

АДДДДДДДДДДДДДДДДДДДЩ ГДДДДДДДДДДДДДДДДДДґ

і 50 і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TDerived.P10 і

і і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TDerived.P30 і

і і

ГДДДДДДДДДДДДДДДДДДґ

і і

і @TDerived.T50 і

і і

АДДДДДДДДДДДДДДДДДДЩ

Рис. 21.11. Схемы таблицы виртуальных методов и таблицы ди-

намических методов для TDerived.

Первое слово таблицы динамических методов содержит смещение

сегмента данных родительской таблицы динамических методов, или 0,

если родительская таблица динамических методов отсутствует.

Второе и третье слово таблицы динамических методов использу-

ется в кеш-буфере просмотра динамических методов (см. далее).

Четвертое слово таблицы динамических методов содержит счет-

чик записи таблицы динамических методов. Непосредственно за ним

следует список слов, каждое из которых содержит индекс динамичес-

кого метода, а затем список соответствующих указателей методов.

Длина каждого списка задается счетчиком записи таблицы динамичес-

ких методов.

Значения файлового типа

Значения файлового типа представляются в виде записей. Типи-

зированные и нетипизированные файлы занимают 128 байт, которые

располагаются по следующей схеме:

type

TFileRec = record

Handle : word; { описатель }

Mode : word; { режим }

RecSize : word; { размер записи }

Private : array[1..26] of byte;

UserData : array[1..16] of byte;

Name : array[0..79] of char;

end;

Текстовые файлы занимают 256 байт со следующей схемой распо-

ложения:

type

TTextBuf = array[0..127] of char;

TTextRec = record

Handle : word;

Mode : word;

BufSize : word;

Private : word;

BufPos : word;

BufEnd : word;

BufPtr : ^TTextBuf;

OpenFunc : pointer;

InOutFunc : pointer;

FlushFunc : pointer;

CloseFunc : pointer;

UserData : array[1..16] of Byte;

Name : array[0..79] of Char;

Buffer : TTextBuf;

end;

В переменной Наndlе содержится номер описателя файла (когда

файл открыт). Это значение возвращается DOS.

Поле Моdе считается равным одному из следующих значений:

const

fmClosed = $D7B0;

fmInput = $D7B1;

fmOutput = $D7B2;

fmInOut = $D7B3;

Значение fmClosed показывает, что файл закрыт. Значения

fmInput и fmOutput показывают, что файл является текстовым файлом

и что для него была выполнена процедура Reset (fmInput) или

Rewrite (fmOutput). Значение fmOutput показывает, что переменная

файлового типа является типизированным или нетипизированным фай-

лом, для которого была выполнена процедура Reset или Rewrite. Лю-

бое другое значение говорит о том, что для файловой переменной

присваивание не было выполнено (и она, таким образом, не инициа-

лизирована).

Поле UserData в Borland Pascal недоступно, и пользователь-

ские программы могут сохранять в нем данные.

Поле Nаме содержит имя файла, которое представляет собой

последовательность символов, оканчивающуюся нулевым символом

(#0).

Для типизированных и нетипизированных полей RесSizе содержит

длину записи в байтах, а поле Рrivate зарезервировано, но являет-

ся свободным.

Для текстовых файлов BufPtr является указателем на буфер

размером BufSize, BufPоs представляет собой индекс следующего

символа в буфере, который должен быть записан или прочитан, а

BufEnd - счетчик допустимых символов в буфере. Указатели

OpenFunc, InOutFunc, FlushFunc и CloseFunc служат для ссылки на

программы ввода-вывода и используются для управления файлом. В

Главе 14 в разделе под заглавием "Драйверы устройств для тексто-

вых файлов" приводится дополнительная информация по этому вопро-

су.

Процедурные типы

Процедурные типы хранятся в виде двойного слова. При этом в

младшем слове содержится смещение процедуры, а в старшем - базо-

вый сегмент.

Прямой доступ к памяти

В Borland Pascal реализованы три предопределенных массива

Mem, MemW и MemL, которые используются для прямого доступа к па-

мяти. Каждый компонент массива Mem представляет собой байт, каж-

дый компонент массива MemW - слово, а каждый компонент MemL -

значение длинного целого типа (Longint).

Для индексирования массива Mem используется специальный син-

таксис. Два выражения целочисленного типа Word, разделенные запя-

тыми, используются для задания базового сегмента и смещения ячей-

ки памяти, к которой производится доступ. Например:

Mem[$0040:$0049] := 7;

Data := MemW[Seg(V):Ofs(V)];

MemLong := MemL[64:3*4];

Первый оператор записывает значение 7 в байт по адресу

$0040:$0049. Второй оператор помещает значение типа Word, запи-

санное в первые 2 байта переменной V, в переменную Data. Третий

оператор помещает значение типа Longint, записанное по адресу

$0040:$000C, в переменную MemLong.

Прямой доступ к портам

Для доступа к портам данных процессора 80х86 Borland Pascal

реализует два предопределенных массива - Port и PortW. Оба эти

массива являются одномерными массивами, где каждый элемент предс-

тавляет порт данных, адрес которого соответствует индексу. Индекс

имеет тип Word. Элементы массива Port имеют типа Byte, а элементы

массива PortW - Word.

Когда элементами массива Port или PortW присваивается значе-

ние, оно выводится в выбранный порт. Когда на элементы этих мас-

сивов имеются ссылки в выражениях, то значение вводится из задан-

ного порта.

Использование массивов Port и PortW ограничено только прис-

ваиванием и ссылками в выражениях, то есть элементы этих массивов

не могут использоваться в качестве параметров-переменных. Кроме

того, ссылки на весь массив Port или PortW (без индекса) не до-

пускаются.


Глава 22. Вопросы управления

В данной главе подробно описываются различные способы реали-

зации в Borland Pascal управления программой. Сюда включены сог-

лашения по вызовам, процедуры выхода, обработка прерываний и об-

работка ошибок.

Соглашения по вызовам

Параметры процедурам и функциям передаются через стек. Перед

вызовом процедуры или функции параметры помещаются в стек в по-

рядке их описания. Перед выходом из процедуры или функции все па-

раметры извлекаются из стека.

Примерный вызов процедуры или функции можно представить сле-

дующим образом:

PUSH Param1

PUSH Param2

.

.

.

PUSH ParamX

Call ProcOrFunc

Параметры могут передаваться по ссылке или по значению. Ког-

да параметр передается по ссылке, то указатель, который ссылается

на реальную ячейку памяти, помещается в стек. Когда параметр пе-

редается по значению, в стек помещается само фактическое значе-

ние.

Параметры-переменные

Параметры-переменные (параметры var) всегда передаются по

ссылке, то есть указатель ссылается на ячейку памяти с фактичес-

ким значением.

Параметры-значения

Параметры-значения передаются по значению или по ссылке, в

зависимости от их типа и размера. В общем случае, если пара-

метр-значение занимает 1, 2 или 4 байта, то значение помещается

непосредственно в стек. В противном случае в стек помещается ука-

затель на значение, а процедура или функция копирует затем значе-

ние в локальную ячейку памяти.

В процессоре 8086 не поддерживаются байтовые инструкции РUSН

и РОР, поэтому байтовые параметры всегда передаются в стеке, как

слова. Младший байт слова содержит значение, а старший байт слова

свободен (и неопределен).

Значение или параметр целого типа передается как байт, слово

или двойное слово. При этом используется такой же формат, как для

представления переменной целого типа. (Для двойных слов старшее

слово помещается в стек перед младшим словом, так что младшее

слово размещается в более младших адресах.)

Параметр символьного типа (Char) передается, как байт без

знака.

Параметр булевского типа (Boolean) передается, как байт со

значением 0 или 1.

Параметр перечислимого типа передается, как байт без знака,

если нумерация не превышает 256. В противном случае он передает-

ся, как слово без знака.

Параметр вещественного типа (Real, значения с одинарной,

двойной или повышенной точностью или сложного типа - Single,

Double, Extended, Comp), передаются через стек как 4, 6, 8 или 10

байт. Это является исключением из того правила, что 1-, 2- и

4-байтовые значение передаются непосредственно в стеке.

Параметр типа указатель передается в виде двойного слова

(адрес сегмента помещается в стек перед смещением, так что часть,

представляющая собой смещение, заканчивается в самом младшем ад-

ресе).

Параметр строкового типа передается, как указатель на значе-

ние.

Параметр множественного типа передается в виде байта (если

границы элемента установлены в диапазоне от 0 до 7) или слова

(если границы элемента установлены в диапазоне от 0 до 15). В

противном случае оно передается в виде указателя на "неупакован-

ное" множество длиной 32 байта.

Массив или запись из 1, 2 или 4 байт помещается непосредс-

твенно в стек. Другие массивы и записи передаются, как указатели

на значения.

Открытые строковые параметры

Открытые строковые параметры передаются занесением в стек

сначала указателя на строку, а затем слова, содержащего атрибут

размера (максимальную длину строки).

Открытые параметры-массивы передаются занесением в стек сна-

чала указателя на массив, а затем слова, содержащего атрибут раз-

мера (число элементов массива минус 1).

При использовании встроенного ассемблера, значение, возвра-

щаемое для открытого параметра с помощью стандартной функции

High, можно получить, загружая слово непосредственно под открытым

параметром. В данном примере это демонстрирует процедура

FillString, заполняющая строку до ее максимальной длины указанным

символом.

procedure FillString(var Str: OpenString; Chr: Char);

assebmler;

asm

LES DI,Str { ES:DI = @Str }

MOV CX,Str,Str.Word[-2] { Cx = igh(Str) }

MOV AL,CL

CLD

STOSB { установить Str[0] }

MOV AL,Chr

REP STOSB { установить Str[1..High] }

end;

Результаты функций

Результаты функций порядкового типа возвращаются в регистрах

центрального процессора: байты возвращаются в регистре AL, слова

- в регистре AХ, двойные слова - в DX:AX (старшее слово - в DХ,

младшее - в AХ).

Результаты функций вещественного типа (значения вещественно-

го типа Real) возвращаются в регистрах DХ:ВХ:AX (старшее слово -

в регистре DХ, среднее слово - в регистре ВХ, младшее слово - в

AX).

Результаты функции, имеющие один из типов, использующихся в

процессоре 8087, (значения с одинарной, двойной или повышенной

точностью или сложного типа - Single, Double, Extended и Comp),

возвращаются в регистре вершины стека сопроцессора 8087 (SТ(0)).

Результаты функции типа указатель возвращаются в регистре DХ:

AX (адрес сегмента - в DХ, а смещение - в AX).

Что касается результата функции строкового типа, то вызываю-

щая программа помещает в стек перед передачей каких-либо парамет-

ров временную ячейку памяти, а функция возвращает строковое зна-

чение в этой временной ячейке. Функция не должна удалять указа-

тель.

Ближние и дальние типы вызовов

В центральном процессоре 8086 поддерживается два типа вызо-

вов и инструкций возврата управления - ближние (NEAR) и дальние

(FAR). Ближние вызовы передают управление другой ячейке в преде-

лах того же программного сегмента, а дальние вызовы позволяют пе-

рейти в другой программный сегмент.

Инструкция ближнего обращения CALL помещает в стек 16-бито-

вый адрес возврата (только смещение), а инструкция дальнего вызо-

ва помещает в стек 32-битовый адрес возврата (адрес сегмента и

смещение). Соответствующая инструкция RET извлекает из стека

только смещение или адрес сегмента и смещение.

На основе описания процедуры в Borland Pascal будет автома-

тически выбираться правильный тип обращения. Процедуры, описанные

в интерфейсной секции модуля соответствуют дальнему обращению и

могут вызываться из других блоков. Процедуры, описанные в прог-

рамме в секции реализации модуля (implementation), являются ближ-

ними и могут вызываться только из этой программы или данного мо-

дуля.

Для некоторых конкретных целей можно потребовать, чтобы про-

цедура имела дальний тип вызова. Например, процедура выхода,

драйверы устройств для текстовых файлов и другие средства, ис-

пользующие указатели на процедуры. Директива компилятора {$F+}

указывает на необходимость использования дальнего типа вызовов.

Процедуры или функции, скомпилированные с данной директивой,

всегда будут иметь дальний тип вызова. При использовании в

Borland Pascal директивы {$F-} правильная схема вызова будет вы-

бираться автоматически. По умолчанию назначается режим {$F-}.

Вложенные процедуры и функции

Процедура или функция считается вложенной, когда она описы-

вается внутри другой процедуры или функции. По умолчанию вложен-

ные процедуры и функции всегда используют ближний тип вызова

(NEAR), поскольку они доступны только внутри определенной проце-

дуры или функции в том же сегменте кода. Однако в оверлейных за-

дачах обычно для того, чтобы обеспечить для всех процедур и функ-

ций дальний тип вызова (FAR), используется директива {$F+}.

При вызове вложенной процедуры или функции компилятор непос-

редственно перед инструкцией CALL генерирует инструкцию PUSH BP,

фактически передавая регистр BP вызывающей программы в качестве

дополнительного параметра. После того, как вызываемая процедура

установит свой собственный регистр BP, регистр ВР вызывающей про-

цедуры доступен, как слово, сохраненное в [BP+4] или в [BP+6]

(если процедура имеет дальний тип вызова). Используя связь через

[BP+4] и [BP+6], вызываемая процедура может получить доступ к ло-

кальным переменным в границах стека вызывающей процедуры. Следую-

щий пример показывает, как можно получить доступ к локальным пе-

ременным из оператора inline во вложенной процедуре:

procedure A; near;

var IntA: integer;

procedure B; far;

var IntB: integer;

procedure C; near;

var IntC: integer;

begin

inline(

$8B/$46/<IntC>/ { MOV AX,[BP+IntC] ;AX = IntC }

$8B/$5E/$04/ { MOV BX,[BP+4] ;BX = стек В }

$36/$8b/$47/<IntB>/ { MOV AX,SS:[BX+IntB] ;AX = IntB }

$8B/$5E/$04/ { MOV BX,[BP+4] ;BX = стек B }

$36/8B/$5F/$06/ { MOV BX,SS:[BX+6] ;BX = стек A }

$36/$8B/$47/<IntA>); { MOV AX,SS:[BX+IntA] ;AX =IntA }

end;

begin C end;

begin B end;

Примечание: Вложенные процедуры и функции нельзя описы-

вать с помощью директивы external, и они не могут иметь па-

раметры процедурного типа.

Примечание: Блок inline в приведенном примере можно записать

также в виде:

begin

asm

MOV AX,[BP+IntC] { AX = IntC }

MOV BX,[BP+4] { BX = стек В }

MOV AX,SS:[BX+IntB] { AX = IntB }

MOV BX,[BP+4] { BX = кадр стек B }

MOV BX,SS:[BX+6] { BX = кадр стек A }

MOV AX,SS:[BX+IntA] { AX =IntA }

end;

end;

Соглашения о вызовах методов

Методы используют те же соглашения о вызовах, что и обычные

процедуры и функции, за тем исключением, что каждый метод имеет

неявный дополнительный параметр Self, который соответствует пара-

метру-переменной того же типа, что и объектный тип данного мето-

да. Параметр Self всегда передается последним и всегда имеет фор-

му 32-разрядного указателя на экземпляр, из которого вызывается

метод. Например, если переменная PP имеет тип PPoint, как опреде-

лено выше, то вызов PP^.MoveTo (10, 20) кодируется следующим об-

разом:

mov ax, 10 ; загрузить 10 в AX

push ax ; передать PX как параметр

mov ax, 20 ; загрузить 20 в AX

push ax ; передать PY как параметр

les di, PP ; загрузить PP в ES:DI

push es ; передать, как параметр Self

push di

mov di, es:[di + 6] ; извлечь смещение ТВМ из поля ТВМ

call DWORD PTR [di + 16] ; вызвать запись ТВМ для MoveTo

Во время возврата метод должен удалить параметр Self из сте-

ка точно так же, как он удаляет обычные параметры.

Методы всегда используют дальний тип вызова, независимо от

состояния директивы $F компилятора.

Вызовы виртуальных методов

Для вызова виртуального метода компилятор генерирует код,

который выбирает адрес таблицы виртуальных методов из поля табли-

цы виртуальных методов объекта, и затем вызывает метод, используя

связанную с ним точку входа. Например, если дана переменная PP

типа Point, то вызов PP^.Show будет генерировать следующий код:

les di, PP ; загрузить PP в ES:DI

push es ; передать, как параметр Self

push di

mov di, es:[di + 6] ; извлечь смещение ТВМ из поля ТВМ

call DWORD PTR [di + 12] ; вызвать запись ТВМ для Show

Правила совместимости типов для объектных типов позволяют PP

указывать на Point и на TCircle или на любых других потомков

TPoint. И если вы просмотрите показанные здесь таблицы виртуаль-

ных методов, то вы увидите, что для типа TPoint точка входа со

смещением 12 в таблицы виртуальных методов указывает на

TPoint.Show. Таким образом, в зависимости от фактического во вре-

мя выполнения типа PP, инструкция CALL вызывает либо TPoint.Show,

либо TCircle.Show, либо метод любого другого потомка TPoint.

Если Show является статическим методом, то для вызова

PP.Show будет генерироваться следующий код:

les di, PP ; загрузить PP в ES:DI

push es ; передать, как параметр Self

push di

call TPoint.Show ; непосредственно вызвать TPonit.Show

В данном случае не имеет значения, на что указывает PP, и

код всегда будет вызывать метод TPoint.Show.

Вызовы динамических методов

Диспетчеризация вызова динамического метода несколько более

сложна и требует больше времени, чем диспетчеризация виртуального

метода. Вместо использования инструкции CALL для вызова через

указатель метода по статическому смещению в таблице виртуальных

методов, таблица динамических методов объектного типа и таблица

динамических методов его предка должны просматриваться в поиске

"самого верхнего" вхождения индекса конкретного динамического ме-

тода, а вызов затем должен выполняться через соответствующий ука-

затель метода. Этот процесс требует использования существенно

большего числа инструкций, которые можно записать, как "встроен-

ные" (inline), поэтому Турбо Паскаль обеспечивает подпрограмму

диспетчеризации, используемую при вызове динамического метода.

Если бы метод Show показанного выше типа TPoint

описывался как динамический метод (с индексом динамического

метода 200), то вызов PP^.Show, где PP имеет тип TPointPtr,

привел бы к генерации следующего кода:

les di,PP ; загрузка PP в ED:DI

push es ; передача, как параметра

; Self

push di

mow di,es:[di+6] ; извлечение смещения

; таблицы виртуальных методов

; из поля таблицы

; виртуальных методов

mov ax,200 ; загрузка в AX индекса

; динамического метода

call Dispatch ; вызов подпрограммы

; диспетчеризации

Диспетчер выбирает сначала смещение таблицы динамических ме-

тодов от таблицы виртуальных методов, на которое указывает ре-

гистр DI. Затем используется "индекс в кеше" - поле таблицы дина-

мических методов. Диспетчер проверяет, является ли индекс вызван-

ного динамического метода индексом того динамического метода, ко-

торый вызывался последним. Если это так, он немедленно передает

этому методу управление (путем перехода с помощью указателя мето-

да, записанного по смещению, заданному полем "смещение записи").

Если динамический индекс вызванного метода не совпадает с

тем, который записан в кеше, то диспетчер просматривает таблицу

динамических методов и родительскую таблицу динамических методов

(следуя по связям в таблице динамических методов), пока он не

найдет запись, с данным индексом динамического метода. Индекс и

смещение соответствующего указателя метода записываются затем в

поле таблицы динамических методов, а управление передается мето-

ду. Если по каким-либо причинам диспетчер не может найти запись с

данным индексом динамического метода, он завершает прикладную

программу с кодом ошибки этапа выполнения 210.

Вопреки кешированию и высокооптимизированной подпрограмме

диспетчеризации, диспетчеризация динамического метода может пот-

ребовать существенно больше времени, чем вызов виртуального мето-

да. Однако в тех случаях, когда сами действия, выполняемые дина-

мическим методом, требуют много времени, дополнительное прост-

ранство, сохраняемое таблицами динамических методов, может пере-

весить этот недостаток.

Конструкторы и деструкторы

Конструкторы и деструкторы используют те же соглашения о вы-

зовах, что и обычные методы, за тем исключением, что дополнитель-

ный параметр размером в слово, называемый параметром таблицы вир-

туальных методов, передается через стек непосредственно перед

параметром Self.

Для конструкторов параметр таблицы виртуальных методов со-

держит смещение таблицы виртуальных методов для запоминания поля

Self таблицы виртуального метода, чтобы инициализировать Self.

Более того, если конструктор вызывается для размещения дина-

мического объекта с помощью расширенного синтаксиса стандартной

процедуры New, через параметр Self передается указатель nil. Это

заставляет конструктор размещать новый динамический объект, адрес

которого передается вызывающей программе через DX:AX при возврате

из конструктора. Если конструктор не может разместить объект, то

в DX:AX возвращается пустой указатель nil. (См. далее "Обнаруже-

ние ошибок конструктора").

Наконец, если конструктор вызывается с использованием уточ-

ненного идентификатора метода (т.е. идентификатора типа объекта,

за которым следуют точка и идентификатор метода), то в параметре

таблицы виртуальных методов передается нулевое значение. Это яв-

ляется указанием конструктору на то, что ему не следует инициали-

зировать поле Self таблицы виртуальных методов. Для деструкторов

нулевое значение параметра таблицы виртуальных методов означает

обычный вызов, а ненулевое указывает, что деструктор был вызван с

использованием расширенного синтаксиса стандартной процедуры

Dispose. Это заставляет деструктор удалить Self непосредственно

перед возвратом (размер Self определяется из первого слова Self в

ТВМ).

Стандартный код входа и выхода

Каждая процедура и функция Borland Pascal начинается и за-

канчивается стандартным набором операторов, которые позволяют ак-

тивизировать и деактивизировать процедуру или функцию.

Стандартным входом служит следующая группа операторов:

PUSH BP ; сохранить регистр ВР

MOV BP,SP ; установить границы стека

SUB SP,LocalSize ; выделить память для локальных пере-

; менных

В этом примере LocalSize - это размер локальных переменных.

Инструкция SUВ присутствует только в том случае, когда LocalSize

не равно нулю. Если тип обращения к процедуре является ближним,

то параметры начинаются с BP+4, если для вызова процедуры исполь-

зуется дальний тип обращения, то они начинаются с BP+6.

Для программ DOS код входа и выхода для подпрограммы, ис-

пользующей дальнюю модель вызова, тот же, что и для подпрограммы

с ближним типом вызов, но для возврата из подпрограммы использу-

ется инструкция RETF. Это справедливо также для программы

Windows, cкомпилированной в состоянии {$W-}.

Примечание: Об использовании процедур входа и выхода в

DLL рассказывается в Главе 11 "Динамически компонуемые биб-

лиотеки".

Стандартной группой операторов выхода является:

MOV SP,BP ; освободить память, выделенную для

; локальных переменных

POP BP ; восстановить регистр ВР

RET ParamSize ; удалить параметры и выполнить возврат

; управления

Здесь РаrамSizе - это размер параметров. Инструкция RET яв-

ляется инструкцией ближнего или дальнего типа, в зависимости от

типа обращения к процедуре.

В состоянии {$W+} (по умолчанию) в подпрограмме, использую-

щей дальнюю модель вызова, код выхода и выхода выглядит следующим

образом:

INC BP ; указывает на кадр стека FAR

PUSH BP ; сохранить регистр ВР

MOV BP,SP ; установить кадр стека

PUSH DS ; сохранить DS

SUB SP,LocalSize ; выделить память для локальных переменных

.

.

.

MOV SP,BP ; освободить память, выделенную для

; локальных переменных

POP BP ; восстановить регистр ВР

DEC PB ; настроить BP

RETF ParamSize ; удалить параметры и выполнить возврат

; управления

Код входа и выхода для экспортируемой подпрограммы

(процедуры или функции, скомпилированной с директивой

компилятора export) выглядит следующим образом:

mov AXC,DS ; загрузить селектор DS в AX

nop ; дополнительное пространство для

; корректировок

inc BP ; указывает на дальний кадр стека

push BP ; сохранить BP

mov BP,SP ; установить кадр стека

push DS ; сохранить DS

mov DS,AX ; инициализация регистра DS

sub SP,LocalSize; распределении локальных переменных

. ; (если они имеются)

.

.

pop DI ; восстановить DI

pop SI ; восстановить SI

lea SP,[BP-2] ; освободить память, выделенную для

; локальных переменных

pop DS ; восстановить DS

pop BP ; восстановить BP

dec BP ; настроить регистр BP

retf ParamSize ; удаление параметров и возврат

; управления

Для всех моделей вызова, если подпрограмма не содержит ло-

кальных переменных, инструкции выделения и освобождения памяти

для локальных переменных можно опустить.

При работе в реальном режиме, чтобы различать ближний и

дальний кадр стека, Windows требует, чтобы все кадры стека (вклю-

чая кадры стека экспортируемых подпрограмм) сохраняли в слове по

адресу [BP+0] нечетное значение BP. Кроме того, Windows требует,

чтобы слово по адресу [BP-2] содержало селектор сегмента данных

вызывающей программы. Это объясняет использование инструкций INC

BP, PUSH DS и DEC BP (сгенерированных в состоянии {$W+}) на входе

и выходе для подпрограмм far и export.

Заметим, что использование {$W+} требуют только реальный

режим Windows. Если вы не поддерживаете реальный режим, укажите

{$W-}. Вы получите программу меньшего размера и некоторый выигрыш

в скорости.

При разработке программы защищенного режима Windows может

оказаться полезным использование состояния {$W+}. Некоторые

средства отладки, отличные от средств Borland, требуют этого для

корректной работы.

По умолчанию Borland Pascal автоматически генерирует эффек-

тивные системные вызовы для процедур и функций, экспортируемых

прикладной программой. При компоновке прикладной программы в сос-

тоянии {$K+} (по умолчанию) отладчик ищет в каждой экспортируемой

точке входа последовательность инструкций MOV AX,DS с последующей

инструкцией NOP, заменяя их на MOV AX.DS на MOV AX,SS. Это изме-

нение ослабляет требование использования при создании программ

системного вызова подпрограмм API Windows MakeProcInstanc и

FreeProcInstance (хотя это не возбраняется). Можно также вызывать

экспортируемые точки входа из самой прикладной программы.

В состоянии {$K-} при создании динамически компонуемой биб-

лиотеки компоновщик Borland Pascal не модифицирует код входа и

выхода экспортированной точки входа. Если подпрограмма системного

вызова в приложении должна вызываться из другой прикладной прог-

раммы, выбирать состояние {$K-} не следует.

При загрузке прикладной программы или динамически компонуе-

мой библиотеки Windows ищет в каждой экспортируемой точке входа

последовательность инструкций MOV AX,DS с последующей инструкцией

NOP. Для прикладной программы Windows изменяет первые три байта

на три инструкции NOP, чтобы подготовить подпрограмму для исполь-

зования ее функцией Windows MakeProcInstance. Для библиотек

Windows изменяет первые три байта в инструкции MOV AX,xxxx, где

xxxx - селектор (адрес сегмента) сегмента динамических локальных

данных библиотеки.

Соглашения по сохранению регистров

В процедурах и функциях следует сохранять регистры BP, SP,

SS и DS. Значения всех других регистров можно изменять. Кроме то-

го, экспортируемые подпрограммы должны сохранять регистры SI и

DI.

Процедуры выхода

В помощью процедур выхода (или процедур завершения) вы може-

те управлять процессом завершения работы программы. Это полезно в

том случае, когда вы хотите перед прекращением работы программы

обеспечить выполнение определенных действий (типичным примером

является обновление и закрытие файлов).

Реализовать процедуру выхода вам позволяет переменная-указа-

тель EхitProc. Процедура выхода всегда получает вызов при завер-

шении работы программы, независимо от того, является ли это за-

вершение нормальным окончанием работы программы, завершением

после обращения к функции Наlt, или работа программы прекратилась

из-за ошибки во время выполнения.

Параметры для процедуры выхода не требуются, и для того,

чтобы использовался дальний тип вызова, она должна компилировать-

ся с указанием директивы компилятора {$F+}.

Когда процедура выхода должным образом реализована, она в

действительности становится частью цепочки процедур выхода. Эта

цепочка позволяет реализовать процедуры выхода как для модулей,

так и для программ. В некоторых модулях процедура выхода реализу-

ется, как часть самого модуля, а выполнение некоторых завершающих

действий после выхода из модуля, например, закрытие файлов или

восстановление векторов прерываний, возлагается на конкретную

процедуру. Процедуры в цепочке выхода выполняются в последова-

тельности, обратной порядку их реализации. Этим обеспечивается,

что операторы выхода одного блока не выполняются, пока не будут

выполнены операторы выхода какого-либо зависящего от него модуля.

Чтобы сохранить цепочку выхода в неприкосновенности, вы

должны перед изменением указателя EхitPrос на адрес вашей собс-

твенной процедуры сохранить текущее содержимое этого указателя.

Далее, непосредственно перед возвратом управления ваша процедура

выхода должна восстановить сохраненное значение EхitProc. В сле-

дующей программе показаны основы метода реализации такой процеду-

ры выхода.

program Testexit;

var

ExitSave: Pointer;

procedure MyExit; far

begin

ExitProc := ExitSave; { всегда восстанавливает сначала

старый вектор }

.

.

.

end;

begin

ExitProc := ExitSave;

ExitProc := @MyExit;

.

.

.

end.

При входе в программу содержимое EхitProc сохраняется с

EхitSave, а затем следует процедура выхода МуEхit. После того,

как она будет вызвана в качестве элемента процесса завершения ра-

боты программы, процедура МуEхit восстановит предыдущую процедуру

выхода.

Программа завершения в библиотеке исполняющей системы будет

вызывать процедуры выхода, пока указатель EхitPrос не примет зна-

чение nil. Во избежании зацикливания EхitPrос устанавливается в

nil перед каждым обращением, так что следующая процедура выхода

вызывается только в том случае, если текущая процедура выхода ус-

танавливает для EхitPrос ее адрес. Если при выполнении процедуры

выхода возникает ошибка, то в ней не успеет еще выполниться прис-

ваивание нового адреса указателю EхitPrос, так как это делается

непосредственно перед тем, как процедура выхода выполнит возврат

управления.

Процедура выхода может распознавать причину завершения рабо-

ты программы путем проверки целочисленной переменной EхitCode и

переменной-указателя ErrorAddr. В случае нормального завершения в

EхitCode содержится нулевое значение и ErrorAddr имеет значение

nil. В случае завершения через обращение к процедуре Наlt

EхitCode содержит значение, переданное функции Наlt, а ErrorAddr

имеет значение nil. Наконец, в случае прекращения работы програм-

мы из-за ошибки во время ее выполнения EхitCode содержит код

ошибки, а ErrorAddr содержит адрес ошибочного оператора.

Примечание: О процедурах выхода для DLL рассказывается

в Главе 11.

Последняя процедура выхода (которая содержится в библиотеке

исполняющей системы) закрывает файлы Input и Output и восстанав-

ливает векторы прерываний, которые были перехвачены Турбо Паска-

лем. При этом, если указатель ErrorAddr имеет значение, отличное

от nil, то процедура выхода выводит сообщение об ошибке во время

выполнения программы. Если вы хотите выводить свои собственные

сообщения об ошибках во время выполнения, используйте процедуру

выхода, которая проверяет ErrorAddr и выводит сообщение об ошиб-

ке, если его значение отлично от nil. В добавок к этому перед

возвратом управления необходимо обеспечить, чтобы указатель

ErrorAddr был установлен в значение nil, чтобы сообщение об ошиб-

ке не выдавалось снова другой процедурой выхода.

После того, как библиотека исполняющей системы обращается в

процедурам выхода, она возвращает управление DOS и передает в ка-

честве кода возврата значение, содержащееся в ЕхitCode.

Обработка прерываний

Библиотека исполняющей системы Borland Pascal и код, созда-

ваемый компилятором, являются полностью прерываемыми. Большинство

из программ библиотеки исполняющей системы являются также реенте-

рабельными, что позволяет вам писать на Borland Pascal программы

обработки прерываний.

Для Windows подпрограммы обработки прерываний писать не сле-

дует. Если вы это сделаете, последствием может быть сбой системы.

Разработка процедур обработки прерываний

Процедуры обработки прерываний описываются с помощью дирек-

тивы Interrupt. В каждой процедуре обработки прерываний должен

определяться следующий заголовок процедуры (или, как будет пояс-

няться далее, его подмножество):

procedure IntHandler(Flags,CS,IPAX,BX,CX,DX,SI,DI,DS,ES,BP:

Word);

interrupt;

begin

.

.

.

end;

Как можно видеть, все регистры передаются в качестве псевдо-

параметров, так что вы можете их использовать и изменять в своей

программе. Вы можете опустить некоторые из параметров или все па-

раметры, начиная с параметра Flag и кончая ВР. Попытка описать

большее количество параметров или попытка опустить отдельный па-

раметр без пропуска также того параметра, за которым он следует,

является ошибкой, хотя сообщения о ней не выдается. Например:

procedure IntHandler(DI,ES,BP : Word); { недопустимый

заголовок }

procedure IntHandler(SI,DI,DS,ES,BP : Word); { допустимый

заголовок }

При входе в нее процедура обработки прерываний автоматически

сохраняет все регистры (независимо от заголовка процедуры) и ини-

циализирует регистр DS:

PUSH AX

PUSH BX

PUSH DX

PUSH SI

PUSH DI

PUSH DS

PUSH ES

PUSH BP

MOV BP,SP

SUB SP,LocalSize

MOV AX,SEG DATA

MOV DS,AX

Обратите внимание на отсутствие процедуры СLI, чтобы разре-

шить дальнейшие прерывания. С помощью оператора inline вы можете

написать ее сами (если это необходимо). Набор операторов выхода

восстанавливает регистры и выполняет функцию возврата прерывания:

MOV SP,BP

POP BP

POP ES

POP DS

POP DI

POP SI

POP DX

POP CX

POP BX

POP AX

IRET

Процедура обработки прерываний может модифицировать свои па-

раметры. Когда обработчик прерываний возвратит управление, изме-

нение описанных параметров приведет к изменению содержимого соот-

ветствующих регистров. Это может оказаться полезным, когда вы

используете обработчик прерываний в качестве пользовательского

сервисного средства, аналогичного вызову функции DOS по инструк-

ции INТ 21Н.

В процедурах обработки прерываний, обслуживающих прерывания,

получаемые от аппаратных схем, следует воздерживаться от исполь-

зования каких-либо программ ввода-вывода Турбо Паскаля или прог-

рамм распределения памяти, поскольку они не являются реентера-

бельными. Из-за их нереентерабельности нельзя также использовать

никакие функции DOS.


Глава 23. Автоматическая оптимизация

В Borland Pascal выполняется несколько различных типов опти-

мизации кода, начиная от свертывания констант и вычисления бу-

левских выражений по короткой схеме и кончая эффективной компо-

новкой. Рассмотрим некоторые виды оптимизации.

Свертывание констант

Если участвующие в операции операнды представляют собой

константы перечислимого типа, то в Borland такое выражение вычис-

ляется во время компиляции. Например, выражение:

Х := 3 + 4 * 2

приведет к генерации такого же кода, как выражение Х := 11, а вы-

ражение:

S := 'In' + 'Out'

генерирует тот же код, что S := 'InOut'.

Аналогично, если операнды функций Abs, Sqr, Succ, Pred, Odd,

Lo, Hi и Swap представляют собой константы перечислимого типа, то

функция вычисляется во время компиляции.

Если индексом массива является константа или выражение, сос-

тоящее из констант, то адрес элемента вычисляется во время компи-

ляции. Например, доступ к элементу Dаtа[5,5] так же эффективен,

как доступ к простой переменной.

Слияние констант

Использование одной и той же строковой константы два или бо-

лее раз приводит к генерации только одной копии константы. Напри-

мер, два или более оператора Write('Dоnе') в одной и той же части

программы приведет к ссылке на одну и ту же копию строковой конс-

танты 'Donе'.

Вычисление по короткой схеме

В Borland Pascal реализуется вычисление булевского выражения

по короткой схеме. Это означает, что вычисление булевского выра-

жения прекращается, как только результат всего булевского выраже-

ния становится очевидным. При этом обеспечивается минимальное

время выполнения и, обычно, минимальный размер объектного кода.

Вычисление по короткой схеме делает также возможным вычисление

конструкций, которые иначе были бы недопустимыми. Например:

while (I<=Length(S)) and (S[I]<>' ') do

Inc(I);

while (P<>nil) and (P^.Value<>5) do

P:=P^.Next;

В обоих случаях, если первая проверка имеет значение Falsе,

вторая проверка не вычисляется.

Противоположным вычислению по короткой схеме является полное

вычисление, которое можно выбрать с помощью директивы компилятора

{$В+}. В этом случае обеспечивается вычисление каждого операнда

булевского выражения.

Параметры-константы

Там, где это возможно, вместо параметров-значений следует

использовать параметры-константы. Параметры-константы настолько

же эффективны, что и параметры-переменные, а во многих случаях

превосходит их по эффективности. В частности, параметры-константы

генерируют получение кода меньшего размера и программы с ними вы-

полняются быстрее, чем программы с параметрами-значениями струк-

турного и строкового типов.

Параметры-константы более эффективны, чем параметры-значе-

ния, поскольку компилятору не приходится генерировать копии фак-

тических параметров на входе в процедуры и функции. Значения па-

раметров должны быть скопированы в локальные переменные, так что

модификации формальных параметров не приведут к модификации фак-

тических параметров. Поскольку формальные параметры-константы мо-

дифицироваться не могут, компилятору нет необходимости копировать

фактические параметры, что экономит код и пространство в стеке.

Устранение избыточной загрузки указателей

ndif]>

В определенных ситуациях генератор кода Borland Pascal может

устранить избыточные инструкции загрузки указателей, уменьшая тем

самым размер кода и обеспечивая более быстрое его выполнение.

Когда генератор кода может обеспечить, что указатель остается

постоянным на участке линейного кода (кода, не содержащего пере-

ходов на него), и когда указатель уже загружен в пару регистров

(например, ES:DI), генератор кода устраняет ненужные загрузки

указателей в блоке кода.

Указатель считается константой, если он получается из пара-

метра-переменной (параметры-переменные всегда передаются как ука-

затели) или из ссылки на переменную оператор with. Поэтому опера-

тор with часто является более эффективным (но никогда не будет

менее эффективным), чем запись для каждой ссылки на элемент пол-

ностью уточненной переменной.

Подстановка констант множественного типа

Когда правая часть оператор in является константой множест-

венного типа, компилятор генерирует включение проверки с помощью

команд CMP. Такие поставляемые проверки более эффективны чем код,

генерируемый в противном случае соответствующими булевскими выра-

жениями с использованием операций отношения. Например, оператор:

if ((Ch >= 'A') and (Ch <: 'Z')) or

((Ch >= 'a') and (Ch <= 'z')) then ...;

менее читаем и менее эффективен чем

if Ch in ['A'..'Z', 'a'..'z'] then ...;

Поскольку свертывание констант применяется к константам мно-

жественного типа также как к константам других типов, можно ис-

пользовать описания const без потери эффективности.

const

Upper = ['A'..'Z'];

Lower = ['a'..'z'];

Alpha = Upper + Lower;

С учетом данных описаний оператор if генерирует тот же код,

что и в случае предыдущего оператор if:

if Ch in Alpha then ... ;

Малые множества

Для операций с малыми множествами компилятор генерирует

очень эффективный код. Малое множество - это множество с нижним

порядковым значением в диапазоне 0..7 и верхним порядковым значе-

нием в диапазоне 0..15. Например, следующие множества TByteSet и

TWordSet являются малыми множествами:

type

TByteSet = set of 0..7;

TWordSet = set of 0..15;

Операции с малыми множествами, такие как объединение (+),

разность (-), пересечение (*) и проверка на включение in генери-

руют с помощью операций AND, OR, NOT и TEST вместо вызова библио-

тек исполняющей системы инструкции машинного кода. Аналогично,

стандартные процедуры Include и Exclude генерируют при применении

к малым множествам поставляемый код.

Порядок вычисления

Стандартами Паскаля допускается, что операнды в выражении

часто вычисляются в порядке, отличном от того, в котором они за-

писаны (слева направо). Например, оператор:

I := F(J) div G(J)

где F и G - функции целого типа, приводит к тому, что G вычисля-

ется перед вычислением F, так как это позволяет компилятору полу-

чить более оптимальный объектный код. Важно, поэтому, чтобы выра-

жение никогда не зависело от какого-то конкретного порядка

вычисления встроенных функций. Если вернуться к предыдущему при-

меру, то для того, чтобы вызвать функцию F перед функцией G, мож-

но использовать временную переменную:

T := F(J);

I := T div G(J);

Исключением из этого правила является вычисление по короткой

схеме (разрешенное директивой компилятора {$B-}, при котором опе-

ранды булевского типа, связанные операциями and или оr, всегда

вычисляются слева направо.

Проверка на допустимость границ

Присваивание константы переменной и использование константы

в качестве значения параметра проверяется во время компиляции на

допустимость нахождения в заданных границах. При этом генерирует-

ся такой код, что во время выполнения таких проверок не делается.

Например, Х := 999, где Х - переменная байтового типа (Bytе),

приводит к ошибке компиляции.

Использование сдвига вместо умножения

Операция X*C, где C - константа, являющаяся степенью числа

2, приводит к генерации объектного кода, в котором используется

инструкция Shl (сдвиг влево).

Аналогично, когда размерность массива представляет собой

степень числа 2, то для вычисления индексных выражений использу-

ется инструкция Shl (а не инструкция Мul).

Автоматическое выравнивание на границу слова

По умолчанию Borland Pascal выравнивает все переменные и ти-

пизованные константы, превышающие по размеру 1 байт, на границу

машинного слова. На всех 16-разрядных процессорах семейства 80х86

выравнивание на границу слова означает более быстрое выполнение,

поскольку доступ к элементам размером в слово или четным адресам

осуществляется быстрее, чем к словам по нечетному адресу.

Выравнивание данных управляется директивой компилятора $A.

B.Pascal 7 & Objects/LR - 409 -

По умолчанию в состоянии {$A+} переменные и типизованные констан-

ты выравниваются указанным выше образом. В состоянии {$A-} ника-

ких действий по выравниванию не производится.

Примечание: Дальнейшие подробности приведены в Главе 2

("Директивы компилятора") "Справочного руководства програм-

миста".

Удаление неиспользуемого кода

Операторы, о которых известно, что они никогда не будут вы-

полняться, не включаются в объектный код. Данные выражения, нап-

ример, не приведут к генерации объектного кода:

if false then

statement { оператор }

while false do

statement

Эффективная компоновка

Компоновщик Borland Pascal автоматически удаляет неиспользу-

емый код (по процедурам), то есть процедуры и функции, являющиеся

частью скомпилированной программы, но к которым нет обращений, не

включаются в файл типа .EXE. Процедуры, функции, переменные и ти-

пизованные константы, участвующие в процессе компиляции, но ссыл-

ки на которые отсутствуют, удаляются из файлa .EXE. Удаление не-

используемого кода выполняется по процедурам, а удаление неис-

пользуемых данных - по секциям, где эти данные описываются.

Рассмотрим следующую программу:

program SmartLink;

const

H: array[0..15] of char = '0123456789ABCDEF';

var

I,J : integer;

X,Y : real;

var

S: string[79];

var

A: array[1..10000] of integer;

procedure P1:

begin

A[1] = 1;

end;

procedure P2;

begin

I := 1;

B.Pascal 7 & Objects/LR - 410 -

end;

procedure P3;

begin

S := 'Borland Pascal';

P2;

end;

begin

P3;

end;

Основная программа вызывает процедуру P3, которая вызывает

процедуру P2, поэтому обе процедуры P2 и P3 включаются в файл

.EXE. Поскольку P2 ссылается на первый раздел описания перемен-

ных, а P3 ссылается на второй раздел описание переменных, пере-

менные I, J, X, Y, S также включаются в выполняемый файл. Однако

на процедуру P1 никаких ссылок нет, а включенные в выполняемый

файл процедуры не ссылаются на переменные Н и A, поэтому эти объ-

екты удаляются.

Эффективная компоновка имеет особую ценность в связи с ис-

пользованием модулей, которые реализуют библиотеки процедур и

функций. Примером такого модуля является стандартный модуль Dos,

который содержит ряд процедур и функций. При этом программа редко

использует все эти процедуры. Если она использует только одну или

две процедуры или функции, то только эти процедуры включаются в

полученный в результате код.


Часть IV. Использование Borland Pascal с языком ассемблера
Глава 24. Встроенный ассемблер

Встроенный ассемблер Borland Pascal позволяет вам непосредс-

твенно в программах Паскаля записывать код ассемблера процессоров

8087/8087 и 80286/80287. Вы, конечно, если требуется чередовать

код Паскаля и ассемблера, можете преобразовать код ассемблера в

машинные инструкции вручную и воспользоваться затем операторами

inline, либо выполнять компоновку с файлами .OBJ, которые содер-

жат внешние процедуры и функции (external).

Встроенные операторы ассемблера представляют собой большое

подмножество синтаксиса, поддерживаемого Турбо Ассемблером и Мак-

роассемблером фирмы Microsoft. Встроенный ассемблер поддерживает

все коды операций процессором 8086/8087 и 80286/80287 и некоторые

из операций, используемых в выражениях Турбо Ассемблера.

За исключением директив DB (определить байт), DW (определить

слово) и DD (определить двойное слово) никакие другие директивы

Турбо Ассемблера, типа EQU, STRUC, SEGMENT или MACRO, встроенным

ассемблером не поддерживаются. Однако, операции, реализуемые с

помощью директив Турбо Ассемблера, близко соответствуют конструк-

циям Borland Pascal. Например, большинство директив EQU соответс-

твуют описаниям Borland Pascal const, var и type, директива PROC

- описаниям procedure и function, а директива STRUC - типам

record Borland Pascal. Фактически, встроенный ассемблер Borland

Pascal можно рассматривать, как компилятор языка ассемблера, ис-

пользующий для всех описаний синтаксис Паскаля.

Оператор asm

Встроенный ассемблер становится доступным с помощью операто-

ров asm. Оператор asm имеет следующий синтаксис:

asm оператор_ассемблера < разделитель оператор_ассемблера > end

где "оператор_ассемблера" представляет собой оператор ассемблера,

а "разделитель " - это точка с запятой, новая строка или коммен-

тарий Паскаля. Приведем некоторые примеры операторов asm:

asm

mov ah,0 { считать с клавиатуры код функции }

int 16H { для чтения клавиши вызвать BIOS }

mov CharCode,al { сохранить код ASCII }

mov ScanCode,ah { сохранить код опроса }

end;

asm

push ds { сохранить DS }

lds si,Source { загрузить указатель источника }

les di,Dest { загрузить указатель приемника }

mov cx,Count { загрузить размер блока }

cld { переместить }

rep movsb { скопировать блок }

pop ds { восстановить DS }

end;

Заметим, что на одной строке можно разместить несколько опе-

раторов ассемблера, разделив их точками с запятой. Кроме того

следует отметить, что если операторы ассемблера размещаются на

разных строках, разделять их точками с запятой не требуется. За-

метим также, что точка с запятой не говорит о том, что остальная

часть строки представляет собой комментарий. Комментарии следует

записывать, используя синтаксис Паскаля: с помощью { и } или (* и

*).

Использование регистров

Правила использования регистров в операторе asm в основном

совпадают с этими правилами для внешних процедур и функций. Опе-

ратор asm должен сохранять регистры BP, SP, SS и DS, но может

свободно изменять AX, BX, CX, DX, SI, DI, ES и регистр флагов. На

входе в оператор asm BP указывает на текущую рамку стека, SP ука-

зывает на вершину стека, SS содержит адрес сегмента стека, а DS -

адрес сегмента данных. За исключением регистров BP, SP, SS и DS

оператор asm не может делать никаких предположений относительно

содержимого других регистров на входе в этот оператор.

Синтаксис операторa ассемблера

Оператор ассемблера имеет следующий синтаксис:

[ метка":" ] < префикс > [код_операции [операнд < "," операнд >]]

где "метка" - это идентификатор метки, "префикс" - префикс кода

операции ассемблера. "Код_операции" - код инструкции или директи-

ва ассемблера, а "операнд" - выражение ассемблера.

Между операторами ассемблера (но не в них) допускается вклю-

чать комментарии. Допустимо, например, следующее:

asm

mov ax,1 { начальное значение }

mov cx,100 { счетчик }

end;

однако следующая запись ошибочна:

asm

mov { начальное значение } ax,1

mov cx, { счетчик } 100

end;

Метки

Метки в ассемблере определяются также, как в Паскале: перед

оператором записывается идентификатор метки и двоеточие. Как и в

Паскале, метки в ассемблере должны описываться в объявлении label

того блока, который содержит оператор asm. Однако из этого прави-

ла есть одно исключение. Это локальные метки.

Локальные метки - это метки, которые начинаются с символа @.

Поскольку этот символ не может быть частью идентификатора Паска-

ля, такие локальные метки автоматически ограничиваются использо-

ванием их в операторах asm. Локальная метка известна только в оп-

ределяющем ее операторе asm (то есть область действия локальной

метки начинается от ключевого слова asm и заканчивается ключевым

словом end оператора asm, который ее содержит).

В отличие от обычной метки, локальную метку перед ее исполь-

зованием не требуется описывать в объявлении label.

Идентификатор локальной метки состоит из символа @, за кото-

рым следует одна или более букв (A..Z) цифр (0..9) символов под-

черкивания или символов @. Как и все метки, идентификатор завер-

шается двоеточием.

Коды инструкций

Встроенный ассемблер поддерживает инструкции процессоров

8086/8087 и 80286/80287. Инструкции процессора 8087 доступны

только в состоянии {$N+} (разрешено использование сопроцессора),

инструкции процессора 80286 - только в состоянии {$G+} (разрешена

генерация кода для процессора 80286), а инструкции сопроцессора

80287 - только в состоянии {$G+,N+}.

Полное описание каждой инструкции содержится в справочных

материалах по процессорам 80х86 и 80х87.

Размер инструкции RET

Инструкция REP генерирует код машинной инструкции возврата

ближнего или дальнего типа, в зависимости от модели вызова теку-

щей процедуры или функции.

procedure NearProc; near;

begin

asm

ret { генерируется ближний возврат }

end;

end;

procedure FarProc; far

begin

asm

ret { генерируется дальний возврат }

end;

end;

С другой стороны, инструкции RETN и RETF всегда генерируют

ближний или дальний возврат соответственно, независим от модели

вызова текущей процедуры или функции.

Автоматическое определение размера перехода

Если не указывается противное, встроенный ассемблер оптими-

зирует инструкции перехода, автоматически выбирая наиболее корот-

кую, и, следовательно, наиболее эффективную форму инструкции пе-

рехода. Такое автоматическое определение размера перехода

применяется к инструкции безусловного перехода (JMP) и всем инс-

трукциям условного перехода, когда переход выполняется на метку,

а не процедуру или функцию.

Для инструкции безусловного перехода встроенный ассемблер

генерирует короткий переход (один байт кода операции, за которым

следует один байт смещения), если расстояние до целевой метки на-

ходится в границах от -128 до 127 байт. В противном случае гене-

рируется ближний переход (один байт кода операции, за которым

следую два байта смещения).

Для инструкций условного перехода короткий переход (один

байт кода операции, за которым следует один байт смещения) гене-

рируется, если расстояние до целевой метки находится в пределах

от -128 до 127 байт, в противном случае встроенный ассемблер ге-

нерирует короткий переход с обратным условием, который выполняет

переход на целевую метку через ближний переход (в общем случае 5

байт). Например, оператор ассемблера:

JC Stop

где Stop не находится в границах короткого перехода, преобразует-

ся в последовательность машинных кодов, соответствующих инструк-

циям:

jnc Skip

jmp Stop

Skip:

Переходы на точки входа в процедуру или функцию всегда имеют

ближний или дальний тип (но не короткий), а условные переходы на

процедуру или функцию не допускаются. Вы можете указать встроен-

ному ассемблеру, что нужно генерировать ближний или дальний пере-

ход, используя конструкцию NEAR PTR или FAR PTR. Например, опера-

торы ассемблера:

jmp NEAR PTR Stop

jmp FAR PTR Stop

будут всегда генерировать соответственно ближний и дальний пере-

ход, даже если на метку Stop можно перейти с помощью короткого

перехода.

Директивы ассемблера

Встроенный ассемблер Borland Pascal поддерживает три дирек-

тивы ассемблера: DB (определить байт), DW (определить слово) и DD

(определить двойное слово). Каждая из них генерирует данные, со-

ответствующие разделенным запятым операндам, которые следуют за

директивой.

Директива DB генерирует последовательность байт. Каждый опе-

ранд может представлять собой выражение-константу со значением от

-128 до 255, или строку символов любой длины. Выражение-константа

генерирует 1 байт кода, а строки генерируют последовательность

байт со значениями, соответствующим коду ASCII каждого символа.

Директива DW генерирует последовательность слов. Каждый опе-

ранд может представлять собой выражение-константу со значением от

-32768 до 65535, или адресное выражение. Для адресного выражения

встроенный ассемблер генерирует указатель ближнего типа, что есть

слово, содержащие смещения адреса.

Директива DD генерирует последовательность двойных слов.

Каждый операнд может представлять собой выражение-константу со

значением от -2147483648 до 4294967295 или адресное выражение.

Для адресного выражения встроенный ассемблер генерирует указатель

дальнего типа, что есть слово, содержащие смещения адреса, за ко-

торым следует слово, содержащее сегментную часть адреса.

Данные, генерируемые по директивам DB, DW и DD, всегда запи-

сываются в сегмент кода, аналогично коду, генерируемому другими

операторами встроенного ассемблера. Чтобы сгенерировать инициали-

зированные или неинициализированные данные в сегменте данных, вам

следует использовать обычные описания Паскаля типа var или const.

Приведем некоторые примеры директив DB, DW и DD:

asm

DB 00FH { 1 байт }

DB 0,99 { 2 байта }

DB 'A' { Ord('A) }

DB 'Пример',0DH,OAH { строка, за которой

следуют возврат каретки и перевод строки }

DB 12,"Borland Pascal" { строка Паскаля }

DW 0FFFFH { 1 слово }

DW 0,9999 { 2 слова }

DW 'A' { эквивалентно DB 'A',0 }

DW 'BA' { эквивалентно DB 'A','B' }

DW MyVar { смещение MyVar }

DW MyProc { смещение MyProc }

DD 0FFFFFFFH { 1 двойное слово }

DD 0,99999999 { 2 двойных слова }

DD 'A' { эквивалентно DB 'A',0,0,0 }

DD 'DBCA' { эквивалентно DS 'A','B','C','D' }

DD MyVar { указатель на MyVar }

DD MyProc { указатель на MyProc }

end;

В Турбо Ассемблере, когда перед идентификатором указывается

DB, DW или DD, это приводит к генерации в том месте, где указана

директива, переменной размером в байт, слово или двойное слово.

Например, Турбо Ассемблер допускает следующее:

ByteVar DB ?

WordVar DW ?

.

.

.

mov al,ByteVar

mov bx,WordVar

Встроенный ассемблер не поддерживает такие описания перемен-

ных. В Borland Pascal единственным видом идентификатора, который

можно определить в операторе встроенного ассемблера, является

метка. Все переменные должны описываться с помощью синтаксиса

Паскаля, и предыдущая конструкция соответствует следующему:

var

ByteVar: Byte;

WordWat: Word;

.

.

.

asm

mov al,ByteVar

mov bx,WordVar

end;

Операнды

Операнды встроенного ассемблера представляют собой выраже-

ния, которые состоят из сочетания констант, регистров, идентифи-

каторов и операций. Хотя выражения встроенного ассемблера форми-

руются с использованием тех же основных принципов, что и

выражения Паскаля, имеется ряд важных отличий, которые необходимо

пояснить.

Во встроенном ассемблере предопределенный смысл имеют следу-

ющие зарезервированные слова:

AH CL FAR SEG

AL CS HIGH SHL

AND CX LOW SHR

AX DH MOD SI

BH DI NEAR SP

BL DL NOT SS

BP DS OFFSET ST

BX DWORD OR TBYTE

BYTE DX PTR TYPE

CH ES WQORD WORD

XOR

Зарезервированные слова всегда имеют больший приоритет, чем

определенные пользователем идентификаторы. Например, во фрагменте

программы:

var

ch: Char;

...

asm

mov ch,1

end;

1 будет загружаться в регистр CH, а не в переменную CH. Для дос-

тупа к определенному пользователем имени нужно использовать ам-

персанд - операцию переопределения идентификатора (&).

asm

mov &ch,1

end;

Мы настоятельно рекомендуем не использовать определенные

пользователем идентификаторы с теми же именами, что и зарезерви-

рованные слова встроенного ассемблера, поскольку такая путаница

имен может легко приводить к очень трудноуловимым ошибкам.

Выражения

Встроенный ассемблер вычисляет все выражения, как 32-разряд-

ные значения-указатели. Он не поддерживает значения с плавающей

точкой и строковые значения, за исключением строковых констант.

Выражения встроенного ассемблера строятся из элементов выра-

жений и операций, а каждая операция имеет соответствующий класс

выражения и тип выражения. Эти принципы поясняются в следующих

разделах.

Различия между выражениями Паскаля и ассемблера

Большинство важных различий между выражениями Паскаля и вы-

ражениями встроенного ассемблера состоит в том, что выражения

встроенного ассемблера должны при вычислении сводиться к значе-

нию-константе, другими словами, к значению, которое можно вычис-

лить на этапе компиляции. Например, с учетом описаний:

const

X = 10;

Y = 20;

var

Z: Integer;

следующий оператор является во встроенном ассемблере допустимым:

asm

mov Z,X+Y

end;

Поскольку X и Y - это константы, выражение X + Y представля-

ет собой просто удобный способ записи константы 30, и полученная

в результате инструкция помещает непосредственное значение 30 в

переменную Z размером в слово. Но если вы опишете X и Y, как пе-

ременные:

var

X, Y: Integer;

то встроенный ассемблер не сможет на этапе компиляции вычислить

значение X + Y. Корректной конструкцией встроенного ассемблера в

этом случае будет:

asm

mov ax,X

add ax,Y

mov Z,ax

end;

Другим важным отличием выражений Паскаля и встроенного Ас-

семблера является способ интерпретации переменных. В выражении

Паскаля ссылка не переменную интерпретируется, как содержимое пе-

ременной, но в выражении встроенного ассемблера ссылка на пере- p> менную означает адрес переменной. Например, в Паскале выражение X

+ 4, где X - переменная, означает содержимое X, плюс 4, а во

встроенном ассемблере это означает содержимое в слове по адресу

на 4 байта выше, чем адрес X. Поэтому, хотя допустима запись:

asm

mov ax,X+4

end;

этот код не загружает значения X, плюс 4 в AX, а загружает значе-

ние слова, записанного через 4 байта после X. Корректной записью

сложения 4 с содержимым X будет:

asm

MOV AX,X

ADD AX,4

end;

Элементы выражений

ДДДДДДД

Основными элементами выражения являются константы, регистры

и идентификаторы.

Константы

Встроенный ассемблер поддерживает два типа констант: число-

вые константы и строковые константы.

Числовые константы

Числовые константы должны быть целыми и принимать значения в

диапазоне от -2147483648 до 4294967295.

По умолчанию числовые константы являются десятичными, однако

встроенный ассемблер поддерживает также двоичные, восьмеричные и

шестнадцатиричные константы. Двоичное представление обозначается

записью после числа B, восьмеричное - записью буквы O, а шестнад-

цатиричное - записью после числа H или указанием перед числом $.

В выражениях Паскаля суффиксы B, O и H не поддерживаются.

Выражения Паскаля допускают только десятичную (по умолчанию) и

шестнадцатиричную запись (используется префикс $).

Числовые константы должны начинаться с одной из цифр или

символа $. Таким образом, когда вы записываете шестнадцатиричную

константу с помощью суффикса H, то если первой значащей цифрой

является одна из шестнадцатиричных цифр от A до F, то требуется

дополнительный ноль. Например, 0BAD4H и $BAD4 представляют собой

шестнадцатиричные константы, а BAD4H - это идентификатор, так как

он начинается с буквы, а не с цифры.

Строковые константы

Строковые константы должны заключаться в одиночные или двой-

ные кавычки. Указание двух последовательных кавычек одного типа в

качестве закрывающих кавычек считается за один символ. Приведем

некоторые примеры строковых констант:

'Z'

'Borland Pascal'

"That's all folks"

'"That''s all falks," he said.'

'100

'"'

"'"

Заметим, что в четвертой строке для обозначения одиночного

символы кавычки используется две последовательных одиночных ка-

вычки.

В директивах DB допускаются строковые кавычки любой длины.

Это приводит к выделению последовательности байт, содержащих зна-

чения (ASCII) символов строки. Во всех других случаях строковые

константы не могут превышать четырех символов и обозначают число-

вое значение, которое может участвовать в выражениях. Числовое

значение строки вычисляется следующим образом:

Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24

где Ch1 - это самый правый (последний) символ, а Ch4 - самый ле-

вый (первый) символ. Если строка короче 4 символов, то самые ле-

вые (первые) символы считаются нулевыми. Приведем некоторые при-

меры строковых констант и их значений:

Примеры строк и их значения

Таблица 24.1

ЪДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДї

і Строка і Значение і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДґ

і 'a' і 00000061H і

і 'ba' і 00006261H і

і 'cba' і 00636261H і

і 'dcba' і 64636261H і

і 'a' і 00006120H і

і ' a' і 20202061H і

і 'a'*2 і 000000E2H і

і 'a'-'A' і 00000020H і

і not 'a' і FFFFFF9EH і

АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДЩ

Регистры

Следующие зарезервированные идентификаторы обозначают ре-

гистры ЦП:

Регистры ЦП Таблица 24.2

ДДДДДДД

16-разрядные регистры общего назначения: AX BX CX DX

8-разрядные младшие полурегистры: AL BL CL DL

8-разрядные старшие полурегистры: AH BH CH DH

16-разрядные указатели или индексные регистры: SP BP SI DI

16-разрядные сегментные регистры: CS DS SS ES

регистр стека процессора 8087 ST

ДДДДДДД

Когда операнд состоит исключительно из имени регистра, он

называется регистровым операндом. Все регистры можно использо-

вать, как регистровые операнды. Кроме того, некоторые регистры

могут использоваться в других контекстах.

Базовые регистры (BX или BP) и индексные регистры (SI или

DI) можно записывать в квадратных скобках для указания индекса-

ции. Допустимым сочетанием базового/индексного регистра являются

[BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI] и [BP+DI].

Сегментные регистры (ES, CS, SS и DS) могут использоваться

вместе с операцией переопределения сегмента (:) и указывать на

другой сегмент, отличный от того, который процессор выбирает по

умолчанию. На каждый из 8 регистров с плавающей точкой можно ссы-

латься с помощью ST(x), где x - константа от 0 до 7, указывающая

на расстояние от вершины стека регистров.

Идентификаторы

Встроенный ассемблер позволяет в выражениях ассемблера полу-

чить доступ ко всем идентификаторам Паскаля, включая метки, конс-

танты, типы, переменные, процедуры и функции. Кроме того, во

встроенном ассемблере реализованы следующие специальные идентифи-

каторы:

@Code @Data @Result

Идентификаторы @Code и @Data представляют текущие сегменты

кода и данных соответственно. Их следует использовать только в

сочетании с операцией SEG:

asm

mov ax,SEG @Data

mov ds,ax

end;

Идентификатор @Result в операторной части функции переменную

- результат функции. Например, в функции:

function Sum(X, Y: Integer): Integer;

begin

Sum := X + Y;

end;

в операторе, присваивающем результат функции переменной Sum, мож-

но было бы при записи на встроенном ассемблере использовать пере-

менную @Result:

function Sum(X, Y: Integer): Integer;

begin

asm

mov ax,X

add ax,Y

mov @Result,ax

end;

end;

В выражениях встроенного ассемблера нельзя использовать сле-

дующие идентификаторы:

- стандартные процедуры и функции (например, WriteLn, Chr);

- специальные массивы Mem, MemW, MemL, Port, PortW;

- строки, значения с плавающей точкой и константы множест-

венного типа;

- метки, которые не описаны в текущем блоке;

- идентификатор @Result вне функции.

В Таблице 24.3 приведены значение, класс и тип различного

вида идентификаторов, которые можно использовать в выражениях

встроенного ассемблера (классы и типы выражений описываются в

следующем разделе):

Значения, классы и типы идентификаторов Таблица 24.3

ДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДВДДДДДДДДДДДДї

іИдентификат.і Значение і Класс і Тип і

ГДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДґ

і Метка і Адрес метки і Память і SHORT і

і Константа і Значение константы і Непосредствен- і 0 і

і і і ный і і

і Тип і 0 і Память і Размер типаі

і Поле і Смещение поля і Память і Размер типаі

і Переменная і Адрес переменной і Память і Размер типаі

і Процедура і Адрес процедуры і Память і NEAR / FARі

і Функция і Адрес функции і Память і NEAR / FARі

і Модуль і 0 і Непосредствен- і 0 і

і і і ный і і

і @Code і Адрес сегмента кодаі Память і 0FFF0H і

і @Data і Адрес сегмента і Память і 0FFF0H і

і і данных і і і

і @Result і Смещение перемен- і Память і Размер типаі

і і ной результата і і і

АДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДБДДДДДДДДДДДДЩ

Локальные переменные (переменные, описанные в процедурах и

Функциях) всегда распределяются в стеке и доступны относительно

SS:BP, а значение идентификатора локальной переменной представля-

ет собой ее смещение со знаком от SS:BP. ассемблер автоматически

добавляет [BP] к ссылкам на локальные переменные. Например, с

учетом описаний:

procedure Test;

var

Count: Integer;

инструкции:

asm

mov ax,Count

end;

ассемблируются в MOV AX,[BP-2].

Встроенный ассемблер всегда интерпретирует параметр-перемен-

ную, как 32-разрядный указатель, а размер параметра-переменной

всегда равен 4 (размеру 32-разрядного указателя). В Паскале син-

таксис для доступа к параметру-переменной и к значению параметра

одинаков. В случае встроенного ассемблера это не так. Поэтому для

доступа к содержимому параметра-переменной вам сначала придется

загрузить 32-разрядный указатель, а затем обратиться к ячейке, на

которую он указывает. Например, если X и Y - параметры-переменные

приведенной выше функции Sum, то она может выглядеть следующим

образом:

function Sum(var X, Y: Integer): Integer;

begin

asm

les bx,X

mov ax,es:[bx]

les bx,Y

add ax,es:[bx]

mov @Result,ax

end;

end;

Некоторые идентификаторы, такие, как переменные типа запись,

имеют область действия, позволяющую обращаться к ним с помощью

операции выбора элементы структуры - точки (.). Например, с уче-

том описаний:

type

Point = record

X, Y: Integer;

end;

Rect = record

A, B: Point;

end;

var

P: Point;

R: Rect;

для доступа к полям в переменных P и R можно использовать следую-

щие конструкции:

asm

mov ax,P.X

mov dx,P.Y

mov cx,R.A.X

mov bx,R.B.Y

end;

Для непосредственного построения переменной можно использо-

вать идентификатор типа. Каждая из приведенных ниже инструкций

генерирует один и тот же машинный код, загружающий в AX

ES:[DI+4]:

asm

mov ax,(Rect PTR es:[di]).B.X

mov ax,Rect(es:[di].B.X

mov ax,es:Rect[di].B.X

mov ax,Rect[es:di].B.X

mov ax,es:[di].Rect.B.X

end;

Область действия задается типов, полем и идентификатором пе-

ременной типа записи или объектного типа. Кроме того, идентифика-

тор модуля открывает область действия конкретного модуля (как

полностью уточненный идентификатор в Паскале).

Классы выражений

Выражения встроенного ассемблера подразделяются на три клас-

са: регистровые значения, ссылки на память и непосредственные

значения.

Выражение, состоящее только из имени регистра, является ре-

гистровым значением. Примерами регистровых значений являются AX,

CL, DI и ES. Используемые в качестве операндов, регистровые выра-

жения указывают ассемблеру на необходимость генерировать инструк-

ции, которые работают с регистрами ЦП.

Выражения, обозначающие адреса памяти, являются ссылками на

память. К этой категории относятся метки Паскаля, переменные, ти-

пизованные константы, процедуры и функции.

Выражения, которые не являются регистровыми и не связаны с

ячейками памяти, представляют собой непосредственные значения.

Эта группа включает в себя нетипизированные константы и идентифи-

каторы типа.

Непосредственные значения и ссылки на память при использова-

нии их в качестве операндов приводят к генерации различного кода.

Например:

const

Start = 10;

var

Count: Integer;

.

.

.

asm

mov ax,Start { MOV AX,xxxx }

mov bx,Count { MOV BX,[xxxx] }

mov cx,[Start] { MOV CX,[xxxx] }

mov dx,OFFSET Count { MOV DX,xxxx }

end;

Поскольку Start - это непосредственное значение, первая инс-

трукция MOV ассемблируется в непосредственную инструкцию. Однако

вторая инструкция MOV транслируется в инструкцию, ссылающуюся на

память, так как Count - это ссылка на память. В третьей инструк-

ции MOV для преобразования Start в ссылку на память (в данном

случае слово со смещением 10 в сегменте данных) используется опе-

рация квадратных скобок. В четвертой инструкции MOV для преобра-

зования Count в непосредственное значение (смещение Count в сег-

менте данных) используется операция OFFSET.

Как вы можете видеть, квадратные скобки и операция OFFSET

дополняют друг друга. В терминах результирующего машинного кода

следующий оператор asm идентичен первым двум строкам предыдущего

оператора asm:

asm

mov ax,OFFSET [Start]

mov bx,[OFFSET Count]

end;

Ссылки на память и непосредственные значения классифицируют-

ся, в свою очередь, как перемещаемые и абсолютные выражения. Пе-

ремещаемое выражение обозначает значение, которое требует на эта-

пе компоновки перемещения, а абсолютное выражение обозначает зна-

чение, которое такого перемещения не требует. Обычно выражение со

ссылкой на метку, переменную процедуру или функцию является пере-

мещаемым, а выражение, где операции выполняются исключительно с

константами - абсолютным.

Перемещение является процессом, с помощью которого компонов-

щик присваивает идентификаторам абсолютные адреса. На этапе ком-

поновки компилятору неизвестны конечные адреса метки, переменной,

процедуры или функции. Они не будут известны до этапа компоновки,

на котором компоновщик присваивает идентификатору конкретный аб-

солютный адрес.

Встроенный ассемблер позволяет вам выполнять любую операцию

с абсолютным значением, но операции с перемещаемыми значениями

ограничиваются сложением и вычитанием констант.

Типы выражений

Каждое выражение встроенного ассемблера имеет соответствую-

щий тип, или, если говорить точнее, размер, поскольку встроенный

Ассемблер рассматривает тип выражения просто как его размер в па-

мяти. Например, тип (размер) переменной Integer равен 2, так как

она занимает два байта.

Там, где это возможно, встроенный ассемблер выполняет про-

верку типов, поэтому в инструкциях:

var

QuitFlag: Boolean;

OutBufPtr: Word;

.

.

.

asm

mov al,QuitFlag

mov bx,OutBufPtr

end;

встроенный ассемблер проверяет, что размер QuitFlag равен 1

(байт), а размер OutBufPtr - двум (слово). Если проверка типа об-

наруживает несоответствие, возникает ошибка. Например, следующее

недопустимо:

asm

mov dl,OutBufPtr

end;

так как DL - это байтовый регистр, а OutBufPtr - слово. Тип ссыл-

ки на память можно изменить с помощью назначения типа. Корректным

способом записи предыдущих инструкций будет следующий:

asm

mov dl,BYTE PTR OutBufPtr

mov dl,Byte(OutBufPtr)

mov dl,OutBufPtr.Byte

end;

Все эти инструкции ссылаются на первый (менее значащий) байт

переменной OutBufPtr.

В некоторых случаях ссылка на память является нетипизирован-

ной, то есть не имеет соответствующего типа. Приведем пример с

непосредственным значением, заключенным в квадратные скобки:

asm

mov al,[100H]

mov bx,[100H]

end;

Встроенный ассемблер допускает обе этих функции, поскольку

выражение [100H] не имеет соответствующего типа, оно просто озна-

чает "содержимое по адресу 100H в сегменте данных", а тип можно

определить из первого операнда (байт для AL, слово для BX). В том

случае, когда тип нельзя определить из другого операнда, встроен-

ный ассемблер требует явного назначения типа:

asm

mov BYTE PTR [100H]

mov WORD PTR [100H]

end;

В Таблице 24.4 приведены предопределенные идентификаторы ти-

па, которые предусмотрены во встроенном ассемблере дополнительно

к типам, описанным в Паскале:

Предопределенные идентификаторы типа Таблица 24.4

ЪДДДДДДДДДДДДДДДДДДДДВДДДДї

і Идентификатор і Тип і

ГДДДДДДДДДДДДДДДДДДДДЕДДДДґ

і BYTE і 1 і

і WORD і 2 і

і DWORD і 4 і

і QWORD і 8 і

і TBYTE і 10 і

і NEAR і 0FFFEH і

і FAR і 0FFFFH і

АДДДДДДДДДДДДДДДДДДДДБДДДДЩ

Заметим в частности, что NEAR и FAR - это псевдотипы, кото-

рые используются с идентификаторами процедур и функций для указа-

ния их модели вызова. Вы можете использовать назначения типа NEAR

и FAR аналогично другим идентификаторам. Например, если FarProc -

процедура с дальним типом вызова (FAR):

procedure FarProc; far;

и если вы записываете код встроенного ассемблера в том же модуле,

где находится FarProc, то вы можете использовать для ее вызова

более эффективную инструкцию NEAR:

asm

push cs

call NEAR PTR FarProc

end

Операции в выражениях

Встроенный ассемблер предусматривает множество операций,

подразделяемых по старшинству на 12 классов. В Таблице 24.5 пере-

числены операции, использующиеся в выражениях встроенного ассемб-

лера в порядке убывания их старшинства:

Встроенные операции ассемблера Таблица 24.5

ЪДВДДДї

і Операция і Комментарий і

ГДЕДДДґ

і & і Операция переопределения иден-і

і і тификатора. і

ГДЕДДДґ

і (), [], * і Выбор элемента структуры. і

ГДЕДДДґ

і HIGH, LOW і Унарные операции. і

і +, - і і

ГДЕДДДґ

і : і Операция переопределения сег-і

і і мента. і

і OFFSET, SEG, TYPE, PTR, і і

і *, /, MOD, SHL, SHR і і

ГДЕДДДґ

і +, - і Бинарные операции сложения/вы- і

і і читания. і

ГДЕДДДґ

і NOT, AND, OR, XOR і Поразрядные операции. і

АДБДДДЩ

Определения операций встроенного ассемблера Таблица 24.6

ЪДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДї

іОпер. і Описание і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і & і Переопределение идентификатора. Идентификатор, непос-і

і і редственно следующий за амперсантом, интерпретируется,і

і і как идентификатор, определяемый пользователем, даже ес-і

і і ли он соответствует зарезервированному слову встроенно-і

і і го ассемблера. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і (...)і Подвыражение. Выражение в скобках полностью вычисляет-і

і і ся, после чего интерпретируется, как один элемент. Вы-і

і і ражению в скобках может предшествовать другое выраже-і

і і ние. Результатом в этом случае будет сумма значенийі

і і двух выражений с типом первого выражения. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і [...]і Ссылка на память. Выражение в квадратных скобках пол-і

і і ностью вычисляется, после чего интерпретируется, какі

і і один элемент. Выражение в квадратных скобках может ком-і

і і бинироваться с регистрами BX, BP, SI, DI с помощью опе-і

і і рации +, что указывает на индексирование регистра ЦП.і

і і Выражению в квадратных скобках может предшествоватьі

і і другое выражение. Результатом в этом случае будет суммаі

і і значений двух выражений с типом первого выражения. Ре-і

і і зультатом всегда будет ссылка на память. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і . і Выбор элемента структуры. Результатом будет сумма выра-і

і і жения перед точкой и выражения после точки с типом вы-і

і і ражения после точки. Идентификаторы, относящиеся к об-і

і і ласти действия, и указанные в выражении перед точкойі

і і доступны в выражении после точки. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і HIGH і Возвращает старшие 8 бит выражения размером в слово,і

і і следующего за операцией. Выражение должно представлятьі

і і собой непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і LOW і Возвращает младшие 8 бит выражения размером в слово,і

і і следующего за операцией. Выражение должно представлятьі

і і собой непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і + і Унарный плюс. Возвращает следующее за плюсом выражениеі

і і без изменений. Выражение должно представлять собой не-і

і і посредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і - і Унарный минус. Возвращает следующее за минусом выраже-і

і і ние с обратным знаком. Выражение должно представлятьі

і і собой непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і : і Переопределение сегмента. Указывает ассемблеру, что вы-і

і і ражение после двоеточия относится к сегменту, заданномуі

і і именем сегментного регистра (CS, DS, SS или ES) переді

і і двоеточием. Результатом является ссылка на память соі

і і значением выражения после двоеточия. Когда переопреде-і

і і ление сегмента используется в операнде инструкции, инс-і

і і трукции предшествует соответствующий префикс переопре-і

і і деления сегмента, обеспечивающий выбор указанногоі

і і сегмента. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

іOFFSETі Возвращает смещение следующего за операцией выраженияі

і і (младшее слово). Результатом будет непосредственноеі

і і значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SEG і Возвращает сегмент следующего за операцией выраженияі

і і (старшее слово). Результатом будет непосредственноеі

і і значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і TYPE і Возвращает тип (размер в байтах) следующего за операци-і

і і ей выражения. Типом непосредственного значения будет 0.і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і PTR і Операция назначения типа. Результатом будет ссылка наі

і і память со значением выражения, следующего за операциейі

і і и типом выражения перед операцией. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і * і Умножение. Оба выражения должны представлять собой не-і

і і посредственные абсолютные значения. Результатом будеті

і і непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і / і Целочисленное деление. Оба выражения должны представ-і

і і лять собой непосредственные абсолютные значения. Ре-і

і і зультатом будет непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і MOD і Остаток целочисленного деления. Оба выражения должныі

і і представлять собой непосредственные абсолютные значе-і

і і ния. Результатом будет непосредственное абсолютное зна-і

і і чение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SHL і Логический сдвиг влево. Оба выражения должны представ-і

і і лять собой непосредственные абсолютные значения. Ре-і

і і зультатом будет непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і SHR і Логический сдвиг вправо. Оба выражения должны представ-і

і і лять собой непосредственные абсолютные значения. Ре-і

і і зультатом будет непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і + і Сложение. Выражения могут представлять собой непосредс-і

і і твенные абсолютные значения или ссылки на память, ноі

і і перемещаемым значением может быть только одно выраже-і

і і ние. Если одно из выражений - перемещаемое значение, тоі

і і результатом также будет перемещаемое значение. Если од-і

і і но из выражений - ссылка на память, то результатом так-і

і і же будет ссылка на память. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і - і Вычитание. Первое выражение может иметь любой класс, аі

і і второе выражение должно быть непосредственным абсолют-і

і і ным выражением. Результат имеет тот же тип, что и пер-і

і і вое выражение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і NOT і Поразрядное отрицание. Выражение должно представлятьі

і і собой непосредственные абсолютные значения. Результатомі

і і будет непосредственное абсолютное значение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і AND і Поразрядная операция AND (И). Оба выражения должныі

і і представлять собой непосредственные абсолютные значе-і

і і ния. Результатом будет непосредственное абсолютное зна-і

і і чение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і OR і Поразрядная операция OR (ИЛИ). Оба выражения должныі

і і представлять собой непосредственные абсолютные значе-і

і і ния. Результатом будет непосредственное абсолютное зна-і

і і чение. і

ГДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

і XOR і Поразрядная операция XOR (исключающее ИЛИ). Оба выраже-і

і і ния должны представлять собой непосредственные абсолют-і

і і ные значения. Результатом будет непосредственное абсо-і

і і лютное значение. і

АДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Процедуры и функции ассемблера

До сих пор мы рассматривали конструкцию asm...end, как опе-

ратор с обычной частью begin...end. Директива assembler в Borland

Pascal позволяет вам писать на встроенном ассемблере целиком про-

цедуры и функции без необходимости begin...end. Приведем пример

функции на ассемблере:

function LongMul(X, Y: Integer) : Longint; assembler;

asm

mov ax,X

imul Y

end;

Директива assembler приводит к тому, что Borland Pascal вы-

полняет при генерации кода следующую оптимизацию:

- Компилятор не генерирует код для копирования парамет-

ров-значений в локальные переменные. Это влияет на все па-

раметры-значения строкового типа и другие значения-пара-

метры, размер которых не равен 1, 2 или 4 байтам. Внутри

процедуры или функции такие параметры должны интерпретиро-

ваться, как если бы они были параметрами-переменными.

- Компилятор не выделяет память для результата функции, и

ссылка на идентификатор @Result будет ошибкой. Однако

строковые функции являются исключением из этого правила -

они всегда имеют указатель @Result, который распределяется

пользователем.

- Для процедур и функций, не имеющих параметров и локальных

переменных, компилятор не генерирует кадров стека.

- Для процедуры и функции на ассемблере автоматически гене-

рируется код выхода:

push bp ; присутствует, если Locals <> 0 или

; Params <> 0

mov bp,sp ; присутствует, если Locals <> 0 или

; Params <> 0

sub sp,Locals ; присутствует, если Locals <> 0

...

mov sp,bp ; присутствует, если Locals <> 0

pop bp ; присутствует, если Locals <> 0 или

; Params <> 0

ret Params ; всегда присутствует

где Locals - размер локальных переменных, а Params - раз-

мер параметров. Если и Locals и Params = 0, то кода входа

не будет, и код выхода состоит просто из инструкции RET.

Функции, использующие директиву assembler, должны возвращать

результат следующим образом:

- результаты функции порядкового типа (Integer, Char,

Boolean, перечислимые типы) возвращаются в AL (8-разрядное

значение), AX (16-разрядное значение) или DX:AX (32-раз-

рядное значение);

- результаты функции вещественного типа (Real) возвращаются

в DX:BX:AX;

- результаты функции типов 8087 (Single, Double, Extended,

Comp) возвращаются в ST(0) (регистр стека сопроцессора

8087);

- результаты функции типа указатель возвращаются в DX:AX;

- результаты функции строкового типа возвращаются во времен-

ной ячейке, на которую указывает @Result.

Директива assembler во многом похожа на директиву external.

Процедуры и функции на ассемблере должны должны подчиняться тем

же правилам, что и процедуры и функции типа external. Следующие

примеры показывают некоторые отличия операторов asm в обычных

процедурах и функциях от процедур и функций ассемблера. В первом

примере оператор asm используется в обычной функции для преобра-

зования строки в верхний регистр. Заметим, что значение параметра

Str в этом случае ссылается на локальную переменную, поскольку

компилятор автоматически генерирует код входа, копирующий факти-

ческий параметр в локальную память.

function UpperCase(Str: String): String;

begin

asm

cld

lea si,Str

les di,@Result

SEGSS lodsb

stosb

xor ah,ah

xchg ax,cx

jcxz @3

@1:

SEGSS lodsb

cmp al,'a'

ja @2

cmp al,'a'

ja @2

cmp al,'z'

jb @2

sub al,20H

@2:

stosb

loop @1

@3:

end;

end;

Второй пример на ассемблере представляет собой версию функ-

ции UpperCase. В этом случае Str не копируется в локальную па-

мять, и функция должна интерпретировать Str, как параметр-пере-

менную.

function UpperCase(S: String): String; assembler;

asm

push ds

cld

lds si,Str

les di@Result

lodsb

stosb

xor ah,ah

xchg ax,cx

jcxz @3

@1:

lodsb

cmp al,'a'

ja @2

cmp al,'z'

jb @2

sub al,20H

@2:

stosb

loop @1

@3:

pop ds

end;


Глава 25. Компоновка с программами на языке ассемблера

С помощью директивы компилятора $L можно выполнить компонов-

ку программ или модулей на языке Паскаль и процедур и функций на

языке ассемблера. Из исходного файла на языке ассемблера можно с

помощью ассемблера получить объектный файл (с расширением .OBJ).

Используя компоновщик, несколько объектных файлов можно скомпоно-

вать с программой или модулем. При этом используется директива

компилятора $L.

В программе или модуле на языке Паскаль процедуры или функ-

ции, написанные на языке ассемблера, должны быть описаны как

внешние. Например:

function LoCase(Ch : Char): Char; external;

В соответствующем файле на языке ассемблера все процедуры

или функции должны находиться в сегменте с именем CОDЕ или CSEG,

или в сегменте, имя которого заканчивается на _TEXT, а имена

внешних процедур и функций должны быть указаны в директивах

PUВLIC.

Вы должны обеспечить соответствие процедуры или функции ее

определению в Паскале. Это относится в типу ее вызова (ближний

или дальний), числу и типу параметров и типу результата.

В исходном файле на языке ассемблера могут описываться ини-

циализированные переменные, содержащиеся в сегменте с именем

CONST или в сегменте, оканчивающемся на _DAТA, и неинициализиро-

ванные переменные в сегменте с именем DATA или DSEG, или в сег-

менте, имя которого оканчивается на _BSS. В исходном файле на

языке ассемблера эти переменные являются частными, и на них нель-

зя ссылаться из модуля или программы на Паскале. Они, однако, на-

ходятся в том же сегменте, что и глобальные переменные Паскаля, и

доступны через регистр сегмента DS.

На все процедуры, функции и переменные, описанные в модуле

или программе на Паскале и на те из них, которые описаны в интер-

фейсной секции используемых модулей, можно ссылаться из исходного

файла на языке ассемблера с помощью директивы EXTRN. При этом

обязанность обеспечить корректный тип в определении EXTRN также

возлагается на вас.

Когда объектный файл указывается в директиве $L, Borland

Pascal преобразует файл из формата перемещаемых объектных модулей

(.OBJ) фирмы Intel в свой собственный внутренний формат перемеща-

емых модулей. Это преобразование возможно лишь при соблюдении не-

которых правил:

1. Все процедуры и функции должны быть помещены в сегмент с

именем CODЕ или CSEG, или в сегмент, имя которого окан-

чивается на _TEXT. Все инициализированные частные пере-

менные должны помещаться в сегмент с именем Const или в

сегмент, имя которого оканчивается на _DATA. Все неини-

циализированные частные переменные должны быть помещены

в сегмент, имя которого оканчивается на _DAТA. Неинициа-

лизированные локальные переменные должны помещаться в

сегмент с именем DATA или DSEG, или в сегмент, имя кото-

рого оканчивается на _BSS. Все другие сегменты игнориру-

ются, поэтому имеется директива GRОUР. В определениях

сегмента может задаваться выравнивание на границу слова

или байта (WORD или ВYTE). При компоновке они всегда вы-

равниваются на границу слова. В определениях сегментов

могут указываться директивы PUВLIС и имя класса (они иг-

норируются).

2. Borland Pascal игнорирует все данные для сегментов, от-

личных от сегмента кода (CODE, CSEG или xxxx_TEXT) и

инициализированного сегмента данных (CONST или

xxxx_DATA). Поэтому при описании переменных в сегменте

неинициализированных данных (DAТA, DSEG или xxxx_BSS)

для определения значения всегда используйте вопроситель-

ный знак (?). Например:

Count DW ?

Buffer DB 128 DUP(?)

3. Байтовые ссылки на идентификаторы типа EXTRN недопусти-

мы. Это означает, например, что операторы НIGНТ и LОW

нельзя использовать с идентификаторами типа EXTRN.

Турбо Ассемблер и Borland Pascal

Турбо Ассемблер (TASM) значительно облегчает разработку

программ на языке ассемблера и организации в них интерфейса с

программами Borland Pascal. Турбо Ассемблер поддерживает специфи-

ческое использование сегментов, схему памяти и языковую поддержку

для программистов, работающих на Borland Pascal.

Используя ключевое слово PASCAL и директиву .MODEL, можно

обеспечить соблюдение соглашений о вызовах с Borland Pascal, оп-

ределить имена сегментов, выполнить инструкции PUSH BP и MOV

PB,SP, а также обеспечить возврат управления с помощью операторов

POP BP и RET N (где N - это число байт параметра). Директива

.MODEL имеет следующий синтаксис:

.MODEL xxxx, PASCAL

где xxxx - это модель памяти (обычно LARGE).

Задание в директиве .MODEL языка PASCAL сообщает Турбо

Ассемблеру, что параметры были занесены в стек слева-направо - в

том порядке, в котором они обнаружены в исходном операторе, вызы-

вающем процедуру.

Директива PROC позволяет вам задать параметры в том же по-

рядке, как они определены в программе Borland Pascal. Если вы оп-

ределяете функцию, которая возвращает строку, обратите внимание

на то, что директива PROC имеет опцию RETURNS, позволяющую вам

получить доступ к временному указателю строки в стеке и не оказы-

вающую влияния на число байт параметра, добавляемых в операторе

RET.

Приведем примеры кода, в которых используются директивы

.MODEL и PROC:

.MODEL LARGE, PASCAL

.CODE

MyProc PROC FAR 1:BYTE, j : BYTE RETURNS result : DWORD

PUBLIC MyProc

les di,result ; получить адрес временной строки

mov al,i ; получить первый параметр i

mov bl,j ; получить второй параметр j

.

.

.

ret

Определение функции в Borland Pascal будет выглядеть следую-

щим образом:

function MyProc(i,j : char) : string; external;

Примеры программ на языке ассемблера

Следующая программа является примером модуля и представляет

собой две программы на ассемблере, предназначенные для обработки

строк. Функция UppеrCаsе преобразует символы строки в прописные

буквы, а функция StringOf возвращает строку символов заданной

длины.

unit Strings;

interface

function UpperCase(S: string): string;

function StringOf(Ch: char; Count: byte): string;

inplementation

{$L STRS}

function UpperCase; external;

function StringOf; external;

end.

Далее приведен файл на языке ассемблера, в котором реализо-

ваны программы StringOf и UppеrCаsе. Перед компиляцией модуля

Strings этот файл должен быть ассемблирован в файл с именем

STRS.OBJ. Обратите внимание на то, что в программах используется

дальний тип вызова, так как они описаны в интерфейсной секции

блока.

CODE SEGMENT BYTE PUBLIC

ASSUME CS:CODE

PUBLIC UpperCase, StringOf ; объявить имена

function Uppercase(S: String): String

UpperRes EQU DWORD PTR [BP+10]

UpperStr EQU DWORD PTR [BP+6]

Uppercase PROC FAR

PUSH BP ; сохранить регистр BP

MOV BP,SP ; установить стек

PUSH DS ; сохранить регистр DS

LDS SI,UpperStr ; загрузить адрес строки

LES DI,UpperRes ; загрузить адрес результата

CLD ; переместить строку

LODSB ; загрузить длину строки

STOSB ; скопировать результат

MOV CL,AL ; поместить длину строки в СХ

XOR CH,CH

JCXZ U3 ; пропустить в случае пустой

; строки

U1: LODSB ; пропустить, если символ отличен

; от 'а'...'z'

CPM AL,'a'

JB U2

CPM AL,'z'

JA U2 ; переместить строку

SUB AL,'a'-'A' ; преобразовать в прописные буквы

U2: STOBS ; сохранить результат

LOOP U1 ; цикл по всем символам

U3: POP DS ; восстановить регистр DS

POP BP ; восстановить регистр ВР

RET 4 ; удалить параметры и возвратить

; управление

UpperCase ENDP

; function StringOf(Ch: Char; Count: Byte): String

StrOfRes EQU DWORD PTR [BP + 10]

StrOfChar EQU BYTE PTR [BP + 8]

StrOfCOunt EQU BYTE PTR [BP + 6]

StringOf PROC FAR

PUSH BP ; сохранить регистр ВР

MOV BP,SP ; установить границы стека

LES DI,StrOfRes ; загрузить адрес результата

MOV AL,StrOfCount ; загрузить счетчик

CLD ; продвинуться на строку

STOSB ; сохранить длину

MOV CL,AL ; поместить значение счетчика в CX

XOR CH,CH

MOV AL,StrOfChar ; загрузить символ

REP STOSB ; сохранить строку символов

POP ; восстановить ВР

RET ; извлечь параметры и выйти

SrtingOf ENDP

CODE ENDS

END

Чтобы ассемблировать этот пример и скомпилировать модуль,

можно использовать следующие команды:

TASM STR5

BPC stringer

Методы на языке ассемблера

Методы, реализованные на языке ассемблера, можно скомпоно-

вать с программами Borland Pascal с помощью директивы компилятора

$L и зарезервированного ключевого слова external. Описание внеш-

него метода в объектном типе не отличается от обычного метода;

однако в реализации метода перечисляется только заголовок метода,

за которым следует зарезервированной слово external. В исходном

тексте на ассемблере вместо точки (.) для записи уточненных иден-

тификаторов следует использовать операцию @ (точка в ассемблере

уже имеет другой смысл и не может быть частью идентификатора).

Например, идентификатор Паскаля Rect.Init записывается на ассемб-

лере как Rest@Init. Синтаксис @ можно использовать как в иденти-

фикаторах PUBLIC, так и EXTRN.

Включаемый машинный код

Для небольших подпрограмм на языке ассемблера очень удобно

использовать внутренние директивы и операторы Borland Pascal

(операторы inline). Они позволяют вставлять инструкции машинного

кода непосредственно в программу или текст блока, вместо того,

чтобы использовать объектный файл.

Операторы Inline

Оператор inline состоит из зарезервированного слова Inline,

за которым следует одна или более встроенных записей (записей ма-

шинного кода), разделенных косой чертой и заключенных в круглые

скобки:

inline(10/$2345/Count+1/Data-Offset);

Оператор inline имеет следующий синтаксис:

ЪДДДДДДДДї ЪДДДї ЪДДДДДДДДДДї ЪДДДї

подставляемый ДД>і inline ГД>і ( ГДДДД>і запись в ГДВД>і ) ГД>

оператор АДДДДДДДДЩ АДДДЩ ^ і машинном і і АДДДЩ

і і коде і і

і АДДДДДДДДДДЩ і

і ЪДДДї і

АДДДДДДґ / і<ДДДДЩ

АДДДЩ

Каждый оператор inline состоит из необязательного специфика-

тора размера, < или >, и константы или идентификатора переменой,

за которой следуют ноль или более спецификаторов смещения (см.

описанный далее синтаксис). Спецификатор смещения состоит из +

или -, за которым следует константа.

ЪДДДДДДДДДДДї

запись во ДДВДДДДДДДДДДДДДДДДДДДД>і константа ГДДДДДДДДДДДДДДД>

встроенном і ЪДДДї ^ АДДДДДДДДДДДЩ ^

машинном ГДД>і < ГДДДДДДґ і

коде і АДДДЩ і і

і ЪДДДї і і

ГДД>і > ГДДДДДДЩ і

і АДДДЩ і

і ЪДДДДДДДДДДДДДДДї і

АД>і идентификатор ГДВДДДДДДДДДДДДДДДДДДДДЩ

і переменной і і ^

АДДДДДДДДДДДДДДДЩ і і

ЪДДДДЩ АДДДДДДДДДї

і ЪДДДДї ЪДДДДДДДДДї і

АДДДДД>ізнакГДД>іконстантаіДДВДДДДЩ

^ АДДДДЩ АДДДДДДДДДЩ і

АДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Каждая запись inline порождает 1 байт или одно слово кода.

Значения вычисляется, исходя из значения первой константы или

смещения идентификатора переменной, к которому добавляется или из

которого вычитается значение каждой из последующих констант.

Если запись в машинном коде состоит только из констант и,

если ее значение лежит в 8-битовом диапазоне (0..255), то она по-

рождает один байт кода. Если значение выходит за границу 8-бито-

вого диапазона или если запись inline ссылается на переменную, то

генерируется одно слово кода (младший байт следует первым).

Операции < и > могут использоваться для отмены автоматичес-

кого выбора размера, который был описан ранее. Если оператор

inline начинается с операции <, то в код включается только млад-

ший значащий байт значения, даже если это 16-битовое значение.

Если оператор inline начинается с операции >, то в код включается

всегда слово, даже если старший значащий байт равен 0. Например,

оператор:

inline(<$1234/>$44);

гененирует код длиной три байта: $34,$44,$00.

Значение идентификатора переменной в записи inline представ-

ляет собой адрес смещения переменной внутри ее базового сегмента.

Базовый сегмент глобальных переменных (переменных, описанных на

самом внешнем уровне в модуле или программе) и типизованные конс-

танты, доступ к которым организован через регистр DS, представля-

ют собой сегмент данных. Базовый сегмент локальных переменных

(переменных, описанных внутри подпрограммы) является сегментом

стека. В этом случае смещение переменной относится к регистру ВР,

что автоматически влечет за собой выбор сегмента стека.

Примечание: Регистры BP, SP, SS и DS должны сохранять-

ся с помощью операторов inline. Значение всех других ре-

гистров можно изменять.

В следующем примере оператора inline генерируется машинный

код для записи заданного числа слов или данных в указанную пере-

менную. При вызове процедуры FillWord Count слов со значением

Data записывается в памяти, начиная с первого байта, обозначенно-

го как Dest.

procedure FillWord(var Dest, Count, Data: word);

begin

inline(

$C4/$BE/Dest/ { LES DI,Dest[BP] }

$8B/$8e/Count/ { MOV CX,Xount[BP] }

$8B/$86/Data/ { MOV AX,Data[BP] }

$FC/ { CLD }

$F3/$AB); { REP STOSW }

В операторной части блока операторы inline могут свободно

чередоваться с другими операторами.

Директивы inline

Директивы inline позволяют писать процедуры и функции, кото-

рые преобразуются при каждом вызове в заданную последовательность

инструкций, представляющих собой машинный код. Синтаксис у дирек-

тивы inline такой же, как у оператора inline:

ЪДДДДДДДДДДДДї

директива ДДДДДДДДДДДДДДДДДДДДДД>і оператор ГДДДДДДДДДДДД>

inline і inline і

АДДДДДДДДДДДДЩ

При вызове обычной процедуры или функции (включая те, кото-

рые содержат в себе операторы inline) компилятором генерируется

такой код, в котором параметры (если они имеются) помещаются в

стек, а затем уже для обращения к процедуре или функции генериру-

ется инструкция CALL. Однако, когда вы обращаетесь к процедуре

или функции типа inline, компилятор вместо инструкции CALL гене-

рирует код из директивы inline. Вот короткий пример двух директив

inline:

procedure DisableInterrupts; inline($FA); { CLI }

procedure EnableInterrupts; inline($FB); { STI }

Когда вызывается процедура DisableInterrupt то генерируется

один байт кода - инструкция CLI.

Процедуры или функции, описанные с помощью директив inline,

могут иметь параметры, однако на параметры нельзя ссылаться сим-

волически (хотя для других переменных это допускается). К тому

же, поскольку такие процедуры или функции фактически являются

макрокомандами, у них отсутствуют автоматический код с инструкци-

ями входа или выхода и никаких инструкций возврата управления не

требуется.

Следующая функция выполняет умножение двух целых значений, в

результате чего получается число длинного целого типа:

function LongMul(X,Y : Integer): Longint;

inline(

$58/ { POP DS ; извлечь из стека Y }

$5A/ { POP AX ; извлечь из стека X }

$F7/$EA); { IMUL DX ; DX:AX = X*Y }

Обратите внимание на отсутствие инструкций входа и выхода и

инструкции возврата управления. Их присутствия не требуется, пос-

кольку при вызове этой функции содержащиеся в ней четыре байта

просто включаются в текст программы.

Директивы inline предназначены только для очень коротких

(менее 10 байт) процедур и функций.

Из-за того, что процедуры и функции типа inline имеют харак-

тер макроопределений, они не могут использоваться в качестве ар-

гумента операции @ или в функциях Addr, Offs и Seg.


Руководство по языку

НННННННННННННННННННННННННННННННННННННННН

B.Pascal 7 & Objects/LR

Введение.......................................................10

О чем рассказывается в данном руководстве......................11

Часть I. Язык Borland Pascal...................................12

Глава 1. Что такое программа Borland Pascal?...................12

Программа Borland Pascal.......................................13

Процедуры и функции............................................13

Операторы......................................................15

Выражения......................................................16

Лексемы........................................................17

Типы, переменные, константы и типизированные константы.........17

Компоновка частей..............................................19

Модули.........................................................20

Синтаксические диаграммы.......................................21

Глава 2. Лексемы...............................................22

Специальные символы............................................22

Зарезервированные слова и стандартные директивы Borland Pascal........................................................24

Идентификаторы.................................................25

Числа..........................................................27

Метки..........................................................29

Строки символов................................................29

Комментарии....................................................31

Строки программы...............................................31

Глава 3. Константы.............................................32

Глава 4. Типы..................................................34

Простые типы...................................................35

Порядковые типы................................................35

Целочисленные типы.............................................37

Булевские типы.................................................39

Символьный тип (char)..........................................40

Перечислимые типы..............................................40

Отрезки типа...................................................41

Вещественные типы..............................................42

Программная поддержка чисел с плавающей точкой.................43

Аппаратная поддержка чисел с плавающей точкой..................43

Строковые типы.................................................44

Структурные типы...............................................45

Типы массив....................................................46

Типы запись....................................................48

Объектные типы.................................................50

Компоненты и область действия..................................54

Методы.........................................................54

Виртуальные методы.............................................54

Динамические методы............................................56

Создание экземпляров объектов..................................57

Активизация методов............................................59

Активизация уточненных методов.................................60

Множественные типы.............................................61

Файловые типы..................................................62

Ссылочные типы.................................................63

Тип Pointer....................................................64

Тип PChar......................................................64

Процедурные типы...............................................65

Процедурные значения...........................................65

Совместимость типов............................................67

Тождественные и совместимые типы...............................67

Тождественность типов..........................................67

Совместимость типов............................................68

Совместимость по присваиванию..................................69

Раздел описания типов..........................................70

Глава 5. Переменные и типизированные константы.................72

Описания переменных............................................72

Сегмент данных.................................................73

Сегмент стека..................................................73

Абсолютные переменные..........................................74

Ссылки на переменные...........................................76

Квалификаторы..................................................77

Массивы, строки и индексы......................................78

Записи и десигнаторы полей.....................................79

Десигнаторы компонентов объекта................................79

Переменные-указатели и динамические переменные.................79

Приведение типов переменных....................................81

Типизированные константы.......................................83

Константы простого типа........................................84

Константы строкового типа......................................84

Константы структурного типа....................................85

Константы типа массив..........................................86

Константы типа запись..........................................88

Константы объектного типа......................................89

Константы множественного типа..................................90

Константы ссылочного типа......................................91

Константы процедурного типа....................................92

Глава 6. Выражения.............................................93

Синтаксис выражений............................................94

Операции.......................................................99

Арифметические операции........................................99

Унарные арифметические операции...............................100

Логические операции...........................................101

Булевские операции............................................101

Операция со строками..........................................103

Операции над символьными указателями..........................104

Операции над множествами......................................105

Операции отношения............................................106

Сравнение простых типов.......................................107

Сравнение строк...............................................107

Сравнение упакованных строк...................................107

Сравнение указателей..........................................107

Сравнение символьных указателей...............................108

Сравнение множеств............................................108

Проверка на принадлежность к множеству........................108

Операция @....................................................109

Использование операции @ для переменной.......................110

Использование операции @ для процедуры или функции или

метода.......................................................111

Вызовы функции................................................111

Описатели множества...........................................113

Приведение типа значений......................................114

Процедурные типы в выражениях.................................115

Глава 7. Операторы............................................117

Простые операторы.............................................117

Оператор присваивания.........................................118

Операторы процедуры...........................................119

Операторы перехода............................................120

Структурные операторы.........................................120

Составные операторы...........................................121

Условные операторы............................................121

Оператор условия (if).........................................122

Оператор варианта (case)......................................123

Оператор цикла................................................125

Оператор цикла с постусловием (repeat)........................125

Операторы цикла с предусловием (while)........................127

Операторы цикла с параметром (for)............................128

Оператор with.................................................131

Глава 8. Блоки, локальность и область действия................133

Синтаксис.....................................................133

Правила для области действия..................................136

Область действия для блока....................................136

Область действия записи.......................................137

Область действия объекта......................................137

Область действия модуля.......................................137

Глава 9. Процедуры и функции..................................139

Описания near и far...........................................141

Описания export...............................................142

Описания interrupt............................................143

Описание forward..............................................143

Описания external.............................................145

Описания assembler............................................147

Описания inline...............................................147

Описания функций..............................................148

Описания методов..............................................150

Конструкторы и деструкторы....................................151

Восстановление ошибок конструктора............................154

Параметры.....................................................156

Параметры-значения............................................157

Параметры-константы...........................................157

Параметры-переменные..........................................157

Нетипизированные параметры....................................158

Открытые параметры............................................160

Открытые строковые параметры..................................160

Открытые параметры-массивы....................................162

Динамические переменные объектного типа.......................163

Процедурные переменные........................................165

Параметры процедурного типа...................................168

Глава 10. Программы и модули..................................170

Синтаксис программ............................................170

Заголовок программы...........................................170

Оператор uses.................................................171

Синтаксис модулей.............................................172

Заголовок модуля..............................................172

Интерфейсная секция...........................................173

Секция реализации.............................................174

Секция инициализации..........................................175

Косвенные ссылки на модули....................................175

Перекрестные ссылки на модули.................................177

Совместное использование описаний.............................179

Глава 11. Динамически компонуемые библиотеки..................180

Что такое DLL?................................................180

Использование DLL.............................................180

Модули импорта................................................182

Статический и динамический импорт.............................184

Написание DLL.................................................185

Директива процедуры export....................................187

Оператор exports..............................................187

Код инициализации библиотеки..................................189

Замечания по программированию библиотек.......................191

Глобальные переменные в DLL...................................191

Глобальные переменные и файлы в DLL...........................191

DLL и модуль System...........................................191

Ошибки этапа выполнения в DLL.................................192

DLL и сегменты стека..........................................192

Создание совместно используемых DLL...........................193

Глава 12. Библиотеки исполняющей системы......................194

Модули Borland Pascal.........................................194

Модуль System.................................................195

Модуль Dos и WinDos...........................................195

Модуль Crt....................................................195

Модуль WinCrt.................................................196

Модуль Printer................................................196

Модуль WinPrn.................................................196

Модуль Overlay................................................196

Модуль Strings................................................197

Модуль Graph..................................................197

Модули Turbo3 и Graph3........................................197

Модули WinTypes и WinProcs....................................197

Модуль Win31..................................................198

Модуль WinAPI.................................................198

Модули, поддерживающие Windows 3.1............................198

Глава 13. Стандартные процедуры и функции.....................199

Процедуры управления работой программы........................200

Функции преобразования........................................200

Арифметические функции........................................201

Порядковые процедуры и функции................................202

Строковые процедуры и функции.................................202

Процедуры и функции динамического распределения памяти........203

Функции для работы с указателями и адресами...................204

Прочие процедуры и функции....................................205

Предописанные переменные......................................206

Глава 14. Ввод и вывод........................................215

Файловый ввод-вывод...........................................217

Текстовые файлы...............................................219

Нетипизированные файлы........................................221

Переменная FileMode...........................................221

Устройства в Borland Pascal...................................222

Устройства DOS................................................223

Устройство CОN................................................224

Устройства LРT1, LРT2 и LРT3..................................224

Устройства CОМ1 и CОМ2........................................225

Устройство NUL................................................225

Устройства, предназначенные для текстовых файлов..............225

Ввод и вывод с помощью модуля Crt.............................226

Использование модуля CRT......................................227

Окна CRT......................................................227

Специальные символы...........................................228

Ввод строк....................................................228

Процедуры и функции модуля Crt................................230

Константы и переменные модуля Crt.............................232

Ввод и вывод с помощью модуля WinCrt..........................233

Использование модуля WinCrt...................................234

Специальные символы...........................................236

Ввод строк....................................................236

Процедуры и функции...........................................237

Переменные модуля WinCrt......................................239

Печать из программы Windows...................................241

Изменение заголовков..........................................241

Изменение шрифтов.............................................242

Остановка задания печати......................................243

Специальные символы...........................................243

Процедуры и функции модуля WinPrn.............................244

Функция Open..................................................246

Функция InOut.................................................246

Функция Flush.................................................247

Функция Clоsе.................................................247

Глава 15. Использование сопроцессора 80x87....................248

Типы данных процессора 80x87..................................251

Арифметические операции с повышенной точностью................252

Сравнение вещественных чисел..................................253

Стек вычислений сопроцессора 80x87............................253

Запись вещественных чисел при использовании сопроцессора 80x87........................................................255

Модули, в которых используется сопроцессор 80x87..............255

Распознавание сопроцессора 80х87 в программах DOS.............256

Распознавание сопроцессора 80x87 в программе Windows..........257

Использование эмуляции сопроцессора 80x87 на языке ассемблера...................................................258

Глава 16. Модуль Dоs..........................................259

Процедуры и функции модуля Dos................................260

Константы, типы и переменные модуля Dos.......................263

Типы..........................................................263

Переменные модуля Dos.........................................264

Процедуры и функции модуля WinDos.............................265

Константы, типы и переменные модуля WinDos....................268

Типы..........................................................269

Переменные модуля WinDos......................................269

Глава 17. Программирование в защищенном режиме DOS............270

Что такое защищенный режим?...................................270

Расширения Borland защищенного режима DOS.....................274

DPMI-сервер...................................................274

Администратор этапа выполнения................................274

Разработка прикладных программ DOS защищенного режима.........276

Надежное программирование в защищенном режиме.................276

Загрузка в сегментные регистры недопустимых значений..........277

Функция Ptr и массивы Mem.....................................277

Абсолютные переменные.........................................277

Операции с сегментами.........................................278

Использование сегментных......................................278

Доступ к памяти вне границ сегмента...........................278

Запись в сегмент кода.........................................279

Разыменование указателей nil..................................279

Сегменты кода и данных........................................279

Управление динамически распределяемой памятью.................280

Предопределенные селекторы....................................280

Переменная SelectorInc........................................281

Модуль WinAPI.................................................284

Управление памятью............................................284

Подпрограммы управления памятью API...........................285

Управление модулем............................................289

Управление ресурсами..........................................290

Управление селектором.........................................291

Другие подпрограммы API.......................................292

Прямой доступ к DPMI-серверу..................................293

Компиляция прикладной программы защищенного режима............293

Выполнение программы защищенного режима DOS...................294

Управление объемом используемой RTM памяти....................295

Глава 18. Строки с завершающим нулем..........................297

Что такое строка с завершающим нулем?.........................297

Функции модуля Strings........................................297

Функции модуля Strings........................................298

Использование строк с завершающим нулем.......................299

Символьные указатели и строковые литералы.....................301

Символьные указатели и символьные массивы.....................302

Индексирование символьного указателя..........................303

Операции с символьными указателями............................304

Строки с завершающим нулем и стандартные процедуры............305

Пример использования функций с завершающим нулем..............306

Глава 19. Использование графического интерфейса Borland.......308

Драйверы......................................................308

Поддержка устройства IBM 8514.................................310

Система координат.............................................311

Текущий указатель.............................................311

Текст.........................................................313

Графические изображения и их виды.............................314

Области просмотра и двоичные образы...........................314

Поддержка страниц и цветов....................................315

Обработка ошибок..............................................315

Начало работы.................................................316

Пользовательские программы управления динамически распределяемой памятью.......................................318

Процедуры модуля Graph........................................321

Константы, типы и переменные модуля Graph.....................326

Константы.....................................................326

Типы..........................................................328

Переменные....................................................328

Глава 20. Использование оверлеев..............................329

Администратор оверлеев........................................330

Управление оверлейным буфером.................................331

Процедуры и функции модуля Overlay............................334

Коды результата...............................................335

Разработка программ с оверлеями...............................335

Генерация оверлейного кода....................................336

Требование использования дальнего типа вызовов................337

Инициализация администратора оверлеев.........................338

Разделы инициализации в оверлейных модулях....................341

Что не должно использоваться в качестве оверлеев..............342

Отладка оверлеев..............................................343

Внешние программы в оверлеях..................................343

Задание функции чтения оверлея................................345

Оверлеи в файлах .EXE.........................................347

Часть III. В среде Borland Pascal.............................348

Глава 21. Использование памяти................................348

Использование памяти программами реального режима DOS.........348

Администратор динамически распределяемой области памяти DOS...350

Методы освобождения областей динамически распределяемой памяти.......................................................352

Список свободных блоков.......................................355

Переменная HeapError..........................................356

Использование памяти в программах DOS защищенного режима......359

Сегменты кода.................................................359

Атрибуты сегмента.............................................359

Атрибуты MOVEABLE или FIXED...................................359

Атрибуты PRELOAD или DEMANDLOAD...............................359

Атрибуты DISCARDABLE или PERMAMENT............................359

Сегменты данных и стека.......................................361

Изменение атрибутов...........................................361

Администратор динамически распределяемой области памяти DOS...362

Переменная HeapError..........................................363

Использование памяти в программах Windows.....................365

Атрибуты сегментов............................................365

Атрибуты MOVEABLE или FIXED...................................365

Атрибуты PRELOAD или DEMANDLOAD...............................365

Атрибуты DISCARDABLE или PERMANENT............................365

Изменение атрибутов...........................................365

Сегмент локальных динамических данных.........................367

Администратор динамически распределяемой области памяти.......368

Переменная HeapError..........................................370

Форматы внутреннего представления данных......................372

Целочисленные типы............................................372

Символьный тип................................................372

Булевский тип.................................................372

Перечислимый тип..............................................372

Типы с плавающей точкой.......................................373

Вещественный тип..............................................373

Тип числа с одинарной точностью...............................374

Тип числа с двойной точностью.................................374

Тип числа с повышенной точностью..............................375

Сложный тип...................................................375

Значения типа указатель.......................................375

Значения строкового типа......................................376

Значения множественного типа..................................376

Значения типа массив..........................................376

Значения типа запись..........................................376

Объектные типы................................................377

Таблица виртуальных методов...................................378

Таблица динамических методов..................................381

Значения файлового типа.......................................385

Процедурные типы..............................................387

Прямой доступ к памяти........................................387

Прямой доступ к портам........................................387

Глава 22. Вопросы управления..................................388

Соглашения по вызовам.........................................388

Параметры-переменные..........................................388

Параметры-значения............................................388

Открытые строковые параметры..................................389

Результаты функций............................................390

Ближние и дальние типы вызовов................................391

Вложенные процедуры и функции.................................391

Соглашения о вызовах методов..................................392

Вызовы виртуальных методов....................................394

Вызовы динамических методов...................................395

Конструкторы и деструкторы....................................396

Стандартный код входа и выхода................................396

Соглашения по сохранению регистров............................400

Процедуры выхода..............................................400

Обработка прерываний..........................................403

Разработка процедур обработки прерываний......................403

Глава 23. Автоматическая оптимизация..........................405

Свертывание констант..........................................405

Слияние констант..............................................405

Вычисление по короткой схеме..................................405

Параметры-константы...........................................406

Устранение избыточной загрузки указателей.....................406

Подстановка констант множественного типа......................406

Малые множества...............................................407

Порядок вычисления............................................407

Проверка на допустимость границ...............................408

Использование сдвига вместо умножения.........................408

Автоматическое выравнивание на границу слова..................408

Удаление неиспользуемого кода.................................409

Эффективная компоновка........................................409

Часть IV. Использование Borland Pascal с языком ассемблера....411

Глава 24. Встроенный ассемблер................................411

Оператор asm..................................................411

Использование регистров.......................................412

Синтаксис операторa ассемблера................................412

Метки.........................................................413

Размер инструкции RET.........................................413

Автоматическое определение размера перехода...................414

Директивы ассемблера..........................................415

Операнды......................................................417

Выражения.....................................................417

Различия между выражениями Паскаля и ассемблера...............418

Элементы выражений............................................419

Константы.....................................................420

Числовые константы............................................420

Строковые константы...........................................420

Регистры......................................................422

Идентификаторы................................................422

Классы выражений..............................................426

Типы выражений................................................427

Операции в выражениях.........................................430

Процедуры и функции ассемблера................................434

Глава 25. Компоновка с программами на языке ассемблера........437

Турбо Ассемблер и Borland Pascal..............................438

Примеры программ на языке ассемблера..........................440

Методы на языке ассемблера....................................441

Включаемый машинный код.......................................442

Операторы Inline..............................................442

Директивы inline..............................................444


Untitled

  • Введение

Часть I. Язык Borland Pascal

  • Глава 1. Что такое программа Borland Pascal?
  • Глава 2. Лексемы
  • Глава 3. Константы
  • Глава 4. Типы
  • Глава 5. Переменные и типизированные константы
  • Глава 6. Выражения
  • Глава 7. Операторы
  • Глава 8. Блоки, локальность и область действия
  • Глава 9. Процедуры и функции
  • Глава 10. Программы и модули
  • Глава 11. Динамически компонуемые библиотеки
  • Глава 12. Библиотеки исполняющей системы
  • Глава 13. Стандартные процедуры и функции
  • Глава 14. Ввод и вывод
  • Глава 15. Использование сопроцессора 80x87
  • Глава 16. Модуль Dоs
  • Глава 17. Программирование в защищенном режиме DOS
  • Глава 18. Строки с завершающим нулем
  • Глава 19. Использование графического интерфейса Borland
  • Глава 20. Использование оверлеев

Часть III. В среде Borland Pascal

  • Глава 21. Использование памяти
  • Глава 22. Вопросы управления
  • Глава 23. Автоматическая оптимизация

Часть IV. Использование Borland Pascal с языком ассемблера

  • Глава 24. Встроенный ассемблер
  • Глава 25. Компоновка с программами на языке ассемблера


Untitled

Часть I. Язык Borland Pascal
Глава 1. Что такое программа Borland Pascal?

  • Программа Borland Pascal
  • Процедуры и функции
  • Операторы
  • Выражения
  • Лексемы
  • Типы, переменные, константы и типизированные константы
  • Компоновка частей
  • Модули
  • Синтаксические диаграммы


Untitled

Глава 2. Лексемы

  • Специальные символы
  • Зарезервированные слова и стандартные директивы Borland Pascal
  • Идентификаторы
  • Числа
  • Метки
  • Строки символов
  • Комментарии
  • Строки программы


Untitled

Глава 4. Типы

  • Простые типы
  • Порядковые типы
  • Целочисленные типы
  • Булевские типы
  • Символьный тип (char)
  • Перечислимые типы
  • Отрезки типа
  • Вещественные типы
  • Программная поддержка чисел с плавающей точкой
  • Аппаратная поддержка чисел с плавающей точкой
  • Строковые типы
  • Структурные типы
  • Типы массив
  • Типы запись
  • Объектные типы
  • Компоненты и область действия
  • Методы
  • Виртуальные методы
  • Динамические методы
  • Создание экземпляров объектов
  • Активизация методов
  • Активизация уточненных методов
  • Множественные типы
  • Файловые типы
  • Ссылочные типы
  • Тип Pointer
  • Тип PChar
  • Процедурные типы
  • Процедурные значения
  • Совместимость типов
  • Тождественные и совместимые типы
  • Тождественность типов
  • Совместимость типов
  • Совместимость по присваиванию
  • Раздел описания типов


Untitled

Глава 5. Переменные и типизированные константы

  • Описания переменных
  • Сегмент данных
  • Сегмент стека
  • Абсолютные переменные
  • Ссылки на переменные
  • Квалификаторы
  • Массивы, строки и индексы
  • Записи и десигнаторы полей
  • Десигнаторы компонентов объекта
  • Переменные-указатели и динамические переменные
  • Приведение типов переменных
  • Типизированные константы
  • Константы простого типа
  • Константы строкового типа
  • Константы структурного типа
  • Константы типа массив
  • Константы типа запись
  • Константы объектного типа
  • Константы множественного типа
  • Константы ссылочного типа
  • Константы процедурного типа


Untitled

Глава 6. Выражения

  • Синтаксис выражений
  • Операции
  • Арифметические операции
  • Унарные арифметические операции
  • Логические операции
  • Булевские операции
  • Операция со строками
  • Операции над символьными указателями
  • Операции над множествами
  • Операции отношения
  • Сравнение простых типов
  • Сравнение строк
  • Сравнение упакованных строк
  • Сравнение указателей
  • Сравнение символьных указателей
  • Сравнение множеств
  • Проверка на принадлежность к множеству
  • Операция @
  • Использование операции @ для переменной
  • Использование операции @ для процедуры или функции или метода
  • Вызовы функции
  • Описатели множества
  • Приведение типа значений
  • Процедурные типы в выражениях


Untitled

Глава 7. Операторы

  • Простые операторы
  • Оператор присваивания
  • Операторы процедуры
  • Операторы перехода
  • Структурные операторы
  • Составные операторы
  • Условные операторы
  • Оператор условия (if)
  • Оператор варианта (case)
  • Оператор цикла
  • Оператор цикла с постусловием (repeat)
  • Операторы цикла с предусловием (while)
  • Операторы цикла с параметром (for)
  • Оператор with


Untitled

Глава 8. Блоки, локальность и область действия

  • Синтаксис
  • Правила для области действия
  • Область действия для блока
  • Область действия записи
  • Область действия объекта
  • Область действия модуля


Untitled

Глава 9. Процедуры и функции

  • Описания процедур
  • Описания near и far
  • Описания export
  • Описания interrupt
  • Описание forward
  • Описания external
  • Описания assembler
  • Описания inline
  • Описания функций
  • Описания методов
  • Конструкторы и деструкторы
  • Восстановление ошибок конструктора
  • Параметры
  • Параметры-значения
  • Параметры-константы
  • Параметры-переменные
  • Нетипизированные параметры
  • Открытые параметры
  • Открытые строковые параметры
  • Открытые параметры-массивы
  • Динамические переменные объектного типа
  • Процедурные переменные
  • Параметры процедурного типа


Untitled

Глава 10. Программы и модули

  • Синтаксис программ
  • Заголовок программы
  • Оператор uses
  • Синтаксис модулей
  • Заголовок модуля
  • Интерфейсная секция
  • Секция реализации
  • Секция инициализации
  • Косвенные ссылки на модули
  • Перекрестные ссылки на модули
  • Совместное использование описаний


Untitled

Глава 11. Динамически компонуемые библиотеки

  • Что такое DLL?
  • Использование DLL
  • Модули импорта
  • Статический и динамический импорт
  • Написание DLL
  • Директива процедуры export
  • Оператор exports
  • Код инициализации библиотеки
  • Замечания по программированию библиотек
  • Глобальные переменные в DLL
  • Глобальные переменные и файлы в DLL
  • DLL и модуль System
  • Ошибки этапа выполнения в DLL
  • DLL и сегменты стека
  • Создание совместно используемых DLL


Untitled

Глава 12. Библиотеки исполняющей системы

  • Модули Borland Pascal
  • Модуль System
  • Модуль Dos и WinDos
  • Модуль Crt
  • Модуль WinCrt
  • Модуль Printer
  • Модуль WinPrn
  • Модуль Overlay
  • Модуль Strings
  • Модуль Graph
  • Модули Turbo3 и Graph3
  • Модули WinTypes и WinProcs
  • Модуль Win31
  • Модуль WinAPI
  • Модули, поддерживающие Windows 3.1


Untitled

Глава 13. Стандартные процедуры и функции

  • Процедуры управления работой программы
  • Функции преобразования
  • Арифметические функции
  • Порядковые процедуры и функции
  • Строковые процедуры и функции
  • Процедуры и функции динамического распределения памяти
  • Функции для работы с указателями и адресами
  • Прочие процедуры и функции
  • Предописанные переменные


Untitled

Глава 14. Ввод и вывод

  • Файловый ввод-вывод
  • Текстовые файлы
  • Нетипизированные файлы
  • Переменная FileMode
  • Устройства в Borland Pascal
  • Устройства DOS
  • Устройство CОN
  • Устройства LРT1, LРT2 и LРT3
  • Устройства CОМ1 и CОМ2
  • Устройство NUL
  • Устройства, предназначенные для текстовых файлов
  • Ввод и вывод с помощью модуля Crt
  • Использование модуля CRT
  • Окна CRT
  • Специальные символы
  • Ввод строк
  • Процедуры и функции модуля Crt
  • Константы и переменные модуля Crt
  • Ввод и вывод с помощью модуля WinCrt
  • Использование модуля WinCrt
  • Специальные символы
  • Ввод строк
  • Процедуры и функции
  • Переменные модуля WinCrt
  • Печать из программы Windows
  • Изменение заголовков
  • Изменение шрифтов
  • Остановка задания печати
  • Специальные символы
  • Процедуры и функции модуля WinPrn
  • Функция Open
  • Функция InOut
  • Функция Flush
  • Функция Clоsе


Untitled

Глава 15. Использование сопроцессора 80x87

  • Типы данных процессора 80x87
  • Арифметические операции с повышенной точностью
  • Сравнение вещественных чисел
  • Стек вычислений сопроцессора 80x87
  • Запись вещественных чисел при использовании сопроцессора 80x87
  • Модули, в которых используется сопроцессор 80x87
  • Распознавание сопроцессора 80х87 в программах DOS
  • Распознавание сопроцессора 80x87 в программе Windows
  • Использование эмуляции сопроцессора 80x87 на языке ассемблера


Untitled

Глава 16. Модуль Dоs

  • Процедуры и функции модуля Dos
  • Константы, типы и переменные модуля Dos
  • Типы
  • Переменные модуля Dos
  • Процедуры и функции модуля WinDos
  • Константы, типы и переменные модуля WinDos
  • Типы
  • Переменные модуля WinDos


Untitled

Глава 17. Программирование в защищенном режиме DOS

  • Что такое защищенный режим?
  • Расширения Borland защищенного режима DOS
  • DPMI-сервер
  • Администратор этапа выполнения
  • Разработка прикладных программ DOS защищенного режима
  • Надежное программирование в защищенном режиме
  • Загрузка в сегментные регистры недопустимых значений
  • Функция Ptr и массивы Mem
  • Абсолютные переменные
  • Операции с сегментами
  • Использование сегментных
  • Доступ к памяти вне границ сегмента
  • Запись в сегмент кода
  • Разыменование указателей nil
  • Сегменты кода и данных
  • Управление динамически распределяемой памятью
  • Предопределенные селекторы
  • Переменная SelectorInc
  • Модуль WinAPI
  • Управление памятью
  • Подпрограммы управления памятью API
  • Управление модулем
  • Управление ресурсами
  • Управление селектором
  • Другие подпрограммы API
  • Прямой доступ к DPMI-серверу
  • Компиляция прикладной программы защищенного режима
  • Выполнение программы защищенного режима DOS
  • Управление объемом используемой RTM памяти


Untitled

Глава 18. Строки с завершающим нулем

  • Что такое строка с завершающим нулем?
  • Функции модуля Strings
  • Функции модуля Strings
  • Использование строк с завершающим нулем
  • Символьные указатели и строковые литералы
  • Символьные указатели и символьные массивы
  • Индексирование символьного указателя
  • Операции с символьными указателями
  • Строки с завершающим нулем и стандартные процедуры
  • Пример использования функций с завершающим нулем


Untitled

Глава 19. Использование графического интерфейса Borland

  • Драйверы
  • Поддержка устройства IBM 8514
  • Система координат
  • Текущий указатель
  • Текст
  • Графические изображения и их виды
  • Области просмотра и двоичные образы
  • Поддержка страниц и цветов
  • Обработка ошибок
  • Начало работы
  • Пользовательские программы управления динамически распределяемой памятью
  • Процедуры модуля Graph
  • Константы, типы и переменные модуля Graph
  • Константы
  • Типы
  • Переменные


Untitled

Глава 20. Использование оверлеев

  • Администратор оверлеев
  • Управление оверлейным буфером
  • Процедуры и функции модуля Overlay
  • Коды результата
  • Разработка программ с оверлеями
  • Генерация оверлейного кода
  • Требование использования дальнего типа вызовов
  • Инициализация администратора оверлеев
  • Разделы инициализации в оверлейных модулях
  • Что не должно использоваться в качестве оверлеев
  • Отладка оверлеев
  • Внешние программы в оверлеях
  • Задание функции чтения оверлея
  • Оверлеи в файлах .EXE


Untitled

Часть III. В среде Borland Pascal
Глава 21. Использование памяти

  • Использование памяти программами реального режима DOS
  • Администратор динамически распределяемой области памяти DOS
  • Методы освобождения областей динамически распределяемой памяти
  • Список свободных блоков
  • Переменная HeapError
  • Использование памяти в программах DOS защищенного режима
  • Сегменты кода
  • Атрибуты сегмента
  • Атрибуты MOVEABLE или FIXED
  • Атрибуты PRELOAD или DEMANDLOAD
  • Атрибуты DISCARDABLE или PERMAMENT
  • Сегменты данных и стека
  • Изменение атрибутов
  • Администратор динамически распределяемой области памяти DOS
  • Переменная HeapError
  • Использование памяти в программах Windows
  • Атрибуты сегментов
  • Атрибуты MOVEABLE или FIXED
  • Атрибуты PRELOAD или DEMANDLOAD
  • Атрибуты DISCARDABLE или PERMANENT
  • Изменение атрибутов
  • Сегмент локальных динамических данных
  • Администратор динамически распределяемой области памяти
  • Переменная HeapError
  • Форматы внутреннего представления данных
  • Целочисленные типы
  • Символьный тип
  • Булевский тип
  • Перечислимый тип
  • Типы с плавающей точкой
  • Вещественный тип
  • Тип числа с одинарной точностью
  • Тип числа с двойной точностью
  • Тип числа с повышенной точностью
  • Сложный тип
  • Значения типа указатель
  • Значения строкового типа
  • Значения множественного типа
  • Значения типа массив
  • Значения типа запись
  • Объектные типы
  • Таблица виртуальных методов
  • Таблица динамических методов
  • Значения файлового типа
  • Процедурные типы
  • Прямой доступ к памяти
  • Прямой доступ к портам


Untitled

Глава 22. Вопросы управления

  • Соглашения по вызовам
  • Параметры-переменные
  • Параметры-значения
  • Открытые строковые параметры
  • Результаты функций
  • Ближние и дальние типы вызовов
  • Вложенные процедуры и функции
  • Соглашения о вызовах методов
  • Вызовы виртуальных методов
  • Вызовы динамических методов
  • Конструкторы и деструкторы
  • Стандартный код входа и выхода
  • Соглашения по сохранению регистров
  • Процедуры выхода
  • Обработка прерываний
  • Разработка процедур обработки прерываний


Untitled

Глава 23. Автоматическая оптимизация

  • Свертывание констант
  • Слияние констант
  • Вычисление по короткой схеме
  • Параметры-константы
  • Устранение избыточной загрузки указателей
  • Подстановка констант множественного типа
  • Малые множества
  • Порядок вычисления
  • Проверка на допустимость границ
  • Использование сдвига вместо умножения
  • Автоматическое выравнивание на границу слова
  • Удаление неиспользуемого кода
  • Эффективная компоновка


Untitled

Часть IV. Использование Borland Pascal с языком ассемблера
Глава 24. Встроенный ассемблер

  • Оператор asm
  • Использование регистров
  • Синтаксис операторa ассемблера
  • Метки
  • Размер инструкции RET
  • Автоматическое определение размера перехода
  • Директивы ассемблера
  • Операнды
  • Выражения
  • Различия между выражениями Паскаля и ассемблера
  • Элементы выражений
  • Константы
  • Числовые константы
  • Строковые константы
  • Регистры
  • Идентификаторы
  • Классы выражений
  • Типы выражений
  • Операции в выражениях
  • Процедуры и функции ассемблера


Untitled

Глава 25. Компоновка с программами на языке ассемблера

  • Турбо Ассемблер и Borland Pascal
  • Примеры программ на языке ассемблера
  • Методы на языке ассемблера
  • Включаемый машинный код
  • Операторы Inline
  • Директивы inline


Untitled

Введение

  • О чем рассказывается в данном руководстве


Введение

Данное руководство посвящено используемому в Borland Pascal

with Objects языку Паскаль. Оно

* Дает формальное определение языка Borland Pascal.

* Поясняет, как использовать и писать динамически компонуе-

мые библиотеки.

* Знакомит вас с библиотекой исполняющей системы.

* Поясняет, как писать программы для защищенного режима DOS.

* Освещает такие вопросы Borland Pascal, как использование

памяти, форматы данных, соглашения по вызову, ввод и вывод

и автоматическая оптимизация.

* Описывает, как использовать Borland Pascal с языком ас-

семблера.

Примечания: Обзор всего набора документации по Borland

Pascal вы можете найти во введении к "Руководству пользова-

теля".

Если вы

- хотите узнать, как установить Borland Pascal в системе;

- использовали Turbo Pascal или Turbo Pascal for Windows ра-

нее и хотите узнать, что нового в этой версии;

- не знакомы с интерактивной интегрированной средой разра-

ботки программ (IDE) фирмы Borland;

- хотите познакомиться с введением в объектно-ориентирован-

ное программирование;

- не имеете опыта программирование на Паскале в Windows;

- хотите познакомиться с ObjectWindows;

то прочитайте "Руководство пользователя".

Чтобы найти справочные материалы по следующим темам:

- библиотеки исполняющей системы;

- директивы компилятора;

- сообщения об ошибках;

- работа с редактором;

прочтите "Справочное руководство программиста".

О чем рассказывается в данном руководстве

Данное руководство разбито на четыре части: грамматика язы-

ка, библиотеки, вопросы продвинутого программирования и использо-

вание с Borland Pascal языка ассемблера.

В Части I "Язык Borland Pascal" определяется язык Borland

Pascal. Сначала вы познакомитесь с общей структурой программы

Borland Pascal; затем о каждом элементе программы будет рассказа-

но более подробно.

Часть II "Библиотеки исполняющей системы" содержит информа-

цию о стандартных модулях, образующих библиотеку исполняющей сис-

темы, и о том, как их использовать. Здесь рассказывается также,

как писать программы для защищенного режима DOS.

В Части III "В среде Borland Pascal" дается техническая ин-

формация для продвинутых пользователей. Здесь рассказывается:

- об использовании памяти в Borland Pascal;

- о том, как в Borland Pascal реализовано управление прог-

раммой;

- о деталях по вводу и выводу;

- об оптимизации вашего кода.

В Части IV "Использование Borland Pascal с языком ассембле-

ра" поясняется, как использовать встроенный ассемблер и как ком-

поновать ваши программы Паскаля с кодом Турбо Ассемблера.


Глава 1. Установка и запуск Borland Pascal

Ваш пакет Borland Pascal включает в себя пять различных вер-

сий Borland Pascal:

* BP.EXE, интегрированную среду разработки программ (IDE),

которая работает в защищенном режиме DOS и генерирует

прикладные программы DOS реального режима, Windows и DOS

защищенного режима.

* BPW.EXE, интегрированную среду, которая работает под

Windows и генерирует прикладные программы DOS реального

режима, Windows и DOS защищенного режима.

* TURBO.EXE, интегрированную среду, которая работает в ре-

альном режиме DOS и генерирует только прикладные программы

DOS реального режима.

* BPC.EXE, компилятор, работающий в режиме командной строки

в защищенном режиме DOS и генерирующий прикладные програм-

мы DOS реального режима, Windows и DOS защищенного режима.

* TPC.EXE компилятор, работающий в режиме командной строки в

реальном режиме DOS и генерирующий только прикладные прог-

раммы DOS реального режима.

Borland Pascal поставляется с автоматической программой ус-

тановки, которая называется INSTALL. Из-за использования методов

упаковки файлов вы должны воспользоваться данной программой, а не

просто скопировать на свой жесткий диск файлы Borland Pascal.

Программа INSTALL автоматически копирует и распаковывает файлы

Borland Pascal и Borland Pascal for Windows.

Мы предполагаем, что вы уже знакомы с командами DOS. Напри-

мер, для создания резервных копий своих дистрибутивных дискет

(купленных вами дискет) вам потребуется команда DISKCOPY. Когда

вы получите дискеты, сделайте их полную рабочую копию, а оригина-

лы сохраните затем в надежном месте.

Данная глава содержит информацию по следующим вопросам:

* установка Borland Pascal и Borland Pascal for Windows в

вашей системе;

* доступ к файлу README;

* доступ к файлу HELPME!.DOC;

* использование примеров программ Borland.

Использование программы Install

Кроме выполнения других функций программа Install распознает

используемое аппаратное обеспечение и настраивает соответствующим

образом Borland Pascal. Она также автоматически создает необходи-

мые каталоги и переписывает файлы с ваших дистрибутивных дисков

на жесткий диск. Ее действия говорят сами за себя, а в приведен-

ном ниже тексте сообщается все, что вам нужно знать.

Чтобы установить Borland Pascal, сделайте следующее:

1. Вставьте установочную дискету (дискету 1) в дисковод A.

Наберите следующую команду, затем нажмите клавишу Enter:

A: INSTALL

2. Нажмите Enter, когда выведется экран установки.

3. Следуйте подсказкам.

4. Программе Install для записи временных файлов перед их

распаковкой требуется пространство на диске. В начальном

экране INSTALL перечисляются требования к пространству на

диске; если его недостаточно, выйдите из Install и осво-

бодите необходимое пространство. После завершения работы

Install эти временные файлы будут удалены.

5. В конце установки вам может потребоваться добавить в файл

CONFIG.SYS следующую строку:

FILES = 20

а в файл AUTOEXEC.BAT следующую строку:

PATH = C:\BP\BIN

(или модифицируйте оператор PATH, если он уже имеется).

Примечание: Если вы смените используемый по умолчанию

каталог, вам нужно изменить установку PATH.

Когда вы завершите работу, программа Install напомнит вам,

что нужно прочесть файл README, который содержит последнюю инфор-

мацию о деталях имеющегося у вас издания данной версии Borland

Pascal.

Программа Install и Windows

В следующий раз, когда вы запустите Microsoft Windows (после

выхода из программы просмотра файла README), выведется запрос,

хотите ли вы создать в администраторе программ программную груп-

пу Borland Pascal. Если вы выберете утвердительный ответ, Yes,

Windows создает программную группу, содержащую пиктограммы для

программ и утилит Borland Pascal и Borland Pascal for Windows.

Программа Install предполагает, что Windows устанавливается

в каталоге, который вы задали как каталог Windows в процессе ус-

тановки. Она предполагает также, что администратор программ за-

пускается автоматически как "оболочка" Windows при запуске

Windows. Если вы используете программную оболочку, отличную от

администратора программ, то вам может потребоваться отредактиро-

вать файл SYSTEM.INI в каталоге Windows и включить в него строку:

SHELL=PROGMAN.EXE

В противном случае при первоначальном открытии Windows и по-

пытке Borland Pascal создать новую группу администратора программ

вы получите сообщение, говорящее об "отсутствии связи с админист-

ратором программ". После того как Borland Pascal for Windows и

другие инструментальные средства будут установлены в группе адми-

нистратора программ, вы можете проверить эти установки и, если

хотите, переустановить их в альтернативной командной оболочке.

Запуск Borland Pascal

Для запуска Borland Pascal перейдите в созданный программой

Install подкаталог Borland Pascal BIN. Обычно этим каталогом яв-

ляется каталог C:\BP\BIN. Для запуска интегрированной среды защи-

щенного режима наберите:

BP

Примечание: Об интегрированной среде Windows рассказы-

вается ниже.

Файлы DPMI16BI.OVL и RTM.EXE должны присутствовать в ката-

логе по текущему маршруту, в противном случае BP.EXE не запустит-

ся.

Чтобы запустить интегрированную среду IDE, работающую в ре-

альном режиме, наберите:

TURBO

Защищенный режим и память

Интегрированная среда DOS защищенного режима, компилятор ре-

жима командной строки и другие инструментальные средства защищен-

ного режима используют интерфейс защищенного режима DOS, что дает

вам доступ ко всей памяти компьютера. Кроме того, вы можете ис-

пользовать Borland Pascal для написания своих собственных прило-

жений, работающих в защищенном режиме. Интерфейс защищенного ре-

жима полностью прозрачен для пользователя, и за немногими возмож-

ными исключениями вам не нужно о нем думать.

DPMIINST

Одним таким исключением может быть ситуация, когда вы запус-

каете Borland Pascal самый первый раз. Для определения того, как

разрешить использование защищенного режима на вашем компьютере,

Borland Pascal использует внутреннюю базу данных характеристик

различных машин и соответствующим образом настраивается. Если на

вашей машине более старый микропроцессор, чем 80286, то Borland

Pascal может его не распознать. При запуске Borland Pascal вы

увидите следующее сообщение:

Machine not in database (RUN DPMIINST)

(Машины нет в базе данных, запустите DPMIINST)

Если вы получили это сообщение, просто запустите программу

DPMIINST, набрав DPMIINST в ответ на подсказку DOS.

Для определения наилучшего способа разрешения защищенного

режима DPMIINST выполняет на вашей машине последовательность тес-

тов и автоматически конфигурирует соответствующим образом Borland

Pascal. После выполнения программы DPMIINST вам больше не потре-

буется ее запускать.

Некоторые администраторы памяти, драйверы устройств и рези-

дентные в памяти программы (TSR) могут нарушать способность

DPMIINST анализировать ваш компьютер. Если выполнение DPMIINST

завершается неудачно, временно запретите или удалите эти програм-

мы. Это дает DPMIINST неограниченный доступ, который необходим ей

для определения наилучшего пути перехода в защищенный режим.

Переменная DPMIMEM

По умолчанию интерфейс DPMIMEM Borland Pascal распределяет

для своего использования всю доступную дополнительную и расширен-

ную память. Если вы не хотите распределять всю доступную память

для ядра DPMIMEM, вы можете установить переменную операционной

среды таким образом, чтобы задать максимальный объем используемой

памяти. Эту переменную можно ввести непосредственно в ответ на

подсказку DOS или задать в виде строки файла AUTOEXEC.BAT, ис-

пользуя следующий синтаксис:

SET DPMIMEM=MAXMEM nnnn

где nnnn - объем памяти в килобайтах.

Например, если у вас есть система с 4 мегабайтами памяти и

вы хотите, чтобы ядро DPMIMEM использовало 2 мегабайта, оставляя

2 мегабайта свободными, переменную DPMIMEM следует установить

следующим образом:

SET DPMIMEM=MAXMEM 2000

RTMRES

RTMRES выполняет предзагрузку DPMI-сервера. Он разрешает

DPMI и порождает командный процессор DOS. Предзагрузка DPMI-сер-

вера позволяет вам загружать инструментальные средства Borland

Pascal, работающий в защищенном режиме, такие как BP, BPC, TASMX

и т.д. несколько быстрее. Для выхода из командного процессора на-

берите EXIT.

RTMRES особенно полезен, если вы используете BPC - компиля-

тор командной строки, работающий в защищенном режиме DOS. Каждый

раз, когда вы его вызываете, загружается DPMI-сервер. Если вы

предварительно запустили RTMRES, то сервер уже присутствует, и

компилятор режима командной строки загружается быстрее.

Borland Pascal и расширенная память

После загрузки ядра DPMI (выполнением BP или с помощью ути-

литы RTMRES) интегрированная интерактивная среда Borland Pascal

взаимодействует с DPMI-сервером черед администратор этапа выпол-

нения Borland (RTM.EXE) для распределения памяти таким образом,

что сможет загружаться и выполняться интегрированная среда IDE.

По умолчанию IDE использует всю расширенную память, зарезервиро-

ванную ядром DPMI.

Запуск Borland Pascal for Windows

Чтобы запустить Borland Pascal for Windows, щелкните кнопкой

"мыши" на пиктограмма Borland Pascal for Windows администратора

программ. Из Windows вы можете также запускать две интегрирован-

ные интерактивные среды для DOS; их пиктограммы можно найти в ад-

министраторе программ.

Запуск BP.EXE в расширенном режиме Windows 386

Интегрированная среда защищенного режима DOS должна прекрас-

но работать, если вы запускаете Windows в расширенном режиме 386.

Переменную DPMIMEM вам устанавливать не потребуется; вместо этого

используйте для конфигурирования объема памяти, которую вы хотите

отвести для использования Borland Pascal, файл PIF Borland Pascal

for Windows (BP\BIN\BP.PIF).

Запуск BP.EXE в стандартном режиме Windows

Предварительная загрузка DPMI-сервера позволяет вам запус-

кать инструментальные средства защищенного режима (BP, BPC, TASMX

и т.д.) в Windows в стандартном режиме DOS. Для этого запустите

RTMRES.EXE (как описано выше). Использование RTMRES.EXE в сочета-

нии с Windows всегда устанавливает переменную DPMIMEM в объем па-

мяти, меньший максимального. Это обеспечивает в Windows наличие

достаточного объема физической памяти для работы.

Если вы работаете под управлением оболочки RTMRES, то не

сможете запустить Windows в улучшенном режиме 386, поскольку

DPMI-сервер по умолчанию распределяет всю расширенную память для

своего собственного использования. Вам нужно сначала выйти из

оболочки, а затем запустить Windows или использовать для ограни-

чения объема памяти, распределяемой DPMI-сервером, переменную

DPMIMEM.

Запуск BP.EXE в окне Windows DOS

Если в окне Windows DOS вы выберите запуск интегрированной

среды защищенного режима, то вам нужно сначала модифицировать

файл DOSPRMPT.PIF (который можно найти в каталоге Windows), бла-

годаря чему IDE защищенного режима сможет использовать расширен-

ную память.

Откройте файл DOSPRMPT.PIF с помощью редактора PIF и укажите

объем расширенной памяти, который вы хотите использовать в интег-

рированной интерактивной среде защищенного режима. Если вы не

вполне знакомы с работой редактора PIF, см. "Руководство пользо-

вателя по Microsoft Windows".

Жидкокристаллические и плазменные экраны

Если у вас имеется портативный компьютер с жидкокристалли-

ческим ли плазменным дисплеем, то Borland Pascal следует запус-

тить с параметром запуска /L. Наберите:

BP /L

или

TURBO /L

Хотя вы всегда можете запустить Borland Pascal таким спосо-

бом, в IDE также можно настроить конфигурацию на использование

черно-белого экрана, используя для этого диалоговое окно Optionі

EnviromentіStartup. Выберите параметр LCD Color Set.

Файл README

Файл README содержит последнюю информацию, которая может от-

сутствовать в документации.

Borland Pascal автоматически помещает вам в файл README,

когда вы выполняете программу Install. Чтобы получить доступ к

файлу README позднее, вы можете использовать программу README

Borland Pascal, набрав в командной строке DOS следующие команды:

CD \BP

README

Файлы FILELIST.DOC и HELPME!.DOC

Ваш установочный диск содержит файл с именем FILELIST.DOC, в

котором перечислены все файлы, содержащиеся на дистрибутивных

дискетах, и даны краткие описания каждого из них, и файл

HELPME!.DOC, содержащий ответы на вопросы, с которыми обычно

сталкиваются пользователи. Если вы встретитесь с трудностями, об-

ратитесь к файлу HELPME!.DOC. Для просмотра файлов FILELIST.DOC и

HELPME!.DOC вы можете использовать программу README. Наберите в

командной строке следующее:

README HELPME!.DOC

или

README FILELIST.DOC

Примеры программ

Пакет Borland Pascal включает в себя большое количество при-

мером программ в исходном коде как для DOS, так и для Windows.

Эти программы находятся в подкаталогах каталога EXAMPLES, создан-

ного программой Install. Каталог EXAMPLES содержит также подката-

логи других инструментальных средств и утилит, поставляемых с

Borland Pascal (таких как Turbo Assembler, Turbo Debugger и

Resource Workshop). Потратьте немного времени и просмотрите эти

каталоги. Вы увидите, сколько для вас предусмотрено примеров

программ.


Глава 2. Что нового в Borland Pascal

Borland Pascal with Objects - это профессиональная объект-

но-ориентированная система программирования для реального и защи-

щенного режима DOS и Windows. Если вы работаете с программными

продуктами Turbo Pascal или Turbo Pascal for Windows, то из дан-

ной главы вы узнаете о новых средствах Borland Pascal и о том,

где найти информацию о них.

Три интегрированных интерактивных среды разработки программ

Borland Pascal предоставляет вам три интегрированных инте-

рактивных среды разработки программ (IDE):

* BP.EXE, интегрированную среду разработки программ, которая

работает в защищенном режиме DOS и генерирует по вашему

выбору прикладные программы DOS реального режима, Windows

и DOS защищенного режима. IDE защищенного режима означает,

что каждое ваше очень большое приложение будет иметь для

компиляции достаточно памяти.

* BPW.EXE, интегрированную среду, которая работает под

Windows и генерирует по вашему выбору прикладные программы

DOS реального режима, Windows и DOS защищенного режима.

* TURBO.EXE, интегрированную среду, которая работает в ре-

альном режиме DOS и генерирует только прикладные программы

DOS реального режима, работающие только на процессорах

80х86.

Примечание: Для работы IDE DOS защищенного режима ваш

компьютер должен иметь процессор 80286 или старше и не ме-

нее 2 мегабайт памяти.

Об интегрированных средах для DOS рассказывается в Главе 4

"Программирование в интегрированной интерактивной среде для DOS".

О специальных средствах интегрированной среды для Windows вы мо-

жете узнать из Главы 5 "Программирование в интегрированной инте-

рактивной среде для Windows".

Новые средства IDE

В перечисленных IDE вы можете найти следующие новые средс-

тва:

* Два средства просмотра объектов ObjectBrowser - одно в IDE

для DOS защищенного режима, другое в IDE для Windows. С

помощью ObjectBrowser вы можете просматривать в своей

программе объекты и модули, проверять свой исходный код,

получать полные перекрестные ссылки на каждый используемый

в программе идентификатор и видеть свою программу с новой

позиции. Об ObjectBrowser для DOS и Windows в данной книге

рассказывается ниже.

* Выделение синтаксиса. Во всех интегрированных средах в

программах используется цветовое выделение элементов кода,

благодаря чему вы можете быстро идентифицировать фрагменты

исходного кода. В IDE для Windows вы можете также исполь-

зовать синтаксические элементы, выделяемые жирным, наклон-

ным шрифтом или подчеркиванием. Во всех IDE вы можете пе-

чатать синтаксически выделенный код. О выделении синтакси-

са рассказывается в Главе 4 "Программирование в интегриро-

ванной интерактивной среде для DOS" и в Главе 5 "Програм-

мирование в интегрированной интерактивной среде для

Windows".

* Оперативная полоса SpeedBar в IDE для Windows. Эта опера-

тивная полоса предоставляет быстрый способ выбора команд

меню и других действий с помощью "мыши". Вы можете по сво-

ему выбору выводить ее горизонтально, вертикально или в

виде свободной палитры, либо даже выключить ее полностью.

* В редакторах для DOS и Windows имеются средства Undo (От-

мена) и Redo (Повтор). Если вы сделаете в процессе редак-

тирования ошибку, нажмите Undo, и ваша ошибка исчезнет.

Нажмите Redo - и она появится вновь.

* Меню инструментальных средств Tools. Инструментальные

средства и утилиты, поставляемые с Borland Pascal, вы мо-

жете запускать непосредственно из IDE. В IDE для DOS в ме-

ню Tools вы можете добавить свои собственные утилиты и из-

менить по своему усмотрению оперативные клавиши. О меню

Tools IDE для DOS и о меню Tools IDE для Windows рассказы-

вается в Главе 4.

* Окно сообщений Messages в IDE для DOS. Окно Messages вы

можете использовать для вывода сообщений из таких утилит

как GREP. Вы можете выбрать редактирование программной

строки со ссылкой на сообщение или отслеживать сообщения в

исходном коде при просмотре сообщений.

* Локальные меню во всех интегрированных средах. Простым на-

жатием правой кнопки "мыши" или клавиш Alt+F10 вы можете

вывести локальное меню со списком команд, относящихся к

активному окну.

* Информация об идентификаторах сохраняется от одного сеанса

к другому. Это позволяет после выхода и перезапуска IDE

вам просматривать, отлаживать или выполнять программы без

перекомпиляции.

* Информация об идентификаторах сохраняется от одной до дру-

гой компиляции. Если вы успешно скомпилируете программу,

измените исходный код, перекомпилируете ее, и компиляция

завершится неудачно, информация об идентификаторах сохра-

нится с момента последней компиляции. Ее просмотр может

помочь вам выявить ошибку в исходном коде.

* В интегрированных средах для DOS поддерживаются множест-

венные устанавливаемые пользователем справочные файлы. В

справочную систему Borland Pascal вы можете загрузить до-

полнительные справочные файлы. IDE объединяет тематические

указатели вновь загруженных справочных файлов и стандарт-

ного системного тематического указателя Help.

Компилятор командной строки, работающий в защищенном режиме

Компилятор командной строки BPC.EXE, работает в защищенном

режиме, благодаря чему очень большие программы имеют достаточно

памяти для успешной компиляции. Аналогично IDE для DOS, работаю-

щей в защищенном режиме, BPC.EXE может создавать прикладные прог-

раммы реального режима DOS, Windows и защищенного режима DOS. О

компиляторе, работающем в режиме командной строки, рассказывается

в Главе 3 "Компиляторы, работающие в режиме командной строки"

"Справочного руководства программиста".

Среда разработки программ защищенного режима DOS

С помощью компиляторов командной строки BP.EXE и BPC.EXE вы

можете создавать программы, работающие в защищенном режиме DOS

без использования оверлеев. Ваши программы смогут, наконец, пре-

высить барьер реального режима DOS в 640К.

Для многих ваших программ все, что нужно сделать для созда-

ния приложения DOS защищенного режима, это выбор защищенного ре-

жима DOS в качестве целевой платформы и установка соответствующих

параметров компилятора (см. Главу 4). Возможно, вы захотите про-

честь также Главу 17 "Руководства по языку" ("Программирование в

защищенном режиме DOS"), которой вопросы, касающиеся защищенного

режима, рассматриваются более углубленно.

Динамически компонуемые библиотеки DOS

Динамически компонуемые библиотеки (DLL) традиционно были

частью разработки программ Windows. С помощью Borland Pascal вы

сможете создавать DLL для DOS.

Библиотеки DLL загружаются на этапе выполнения отдельно от

ваших файлов .EXE и могут совместно использоваться несколькими

прикладными программами. DLL для DOS работают в защищенном режиме

DOS и полностью совместимы с DLL для Windows, что позволяет вам

совместно использовать DLL для прикладных программ DOS и Windows.

О динамически компонуемых библиотеках рассказывается в Главе 11

"Динамически компонуемые библиотеки" в "Руководстве по языку".

Добавления к языку Паскаль

Borland Pascal имеет несколько новых расширения языка, об-

легчающих написание программ:

* Открытые параметры. Открытые параметры позволяют переда-

вать в процедуру или функцию строки и массивы переменных

размеров. Об этих параметрах рассказывается в Главе 9

"Процедуры и функции", в разделе "Открытые параметры" кни-

ги "Руководство по языку".

* Стандартная директива public. В Turbo Pascal 6.0 и Turbo

Pascal for Windows допускаются секции компонентов объектов

private. Borland Pascal вводит секции компонентов объектов

public, не имеющие ограничений по области действия описан-

ных в них полей и методов. Если вы находите это удобным, в

объектах можно чередовать секции компонентов public и

private. Подробнее о новой стандартной директиве public

рассказывается в разделе "Компоненты и область действия"

Главы 4 "Типы" в "Руководстве программиста".

* Зарезервированное слово inherited. Зарезервированное слово

inherited может использоваться внутри метода для ссылки на

предка объектного типа метода. См. раздел "Активизация

уточненного метода" Главы 4 "Типы" книги "Руководство по

языку".

* Параметры-константы. Процедуры и формулы могут использо-

вать параметры-константы: группе параметров предшествует

зарезервированное слово const, за которым следует тип. Па-

раметры-константы защищают от случайных присваиваний фор-

мальному параметру, и в некоторых случаях это позволяет

компилятору генерировать более эффективный код. См. раздел

"Параметры" в Главе 9 "Процедуры и функции" в "Руководстве

по языку".

* Динамические методы и таблицы динамических методов (DMT).

Если вы работали с Turbo Pascal for Windows, то уже знаете

о динамических методах и таблицах динамических методов

DMT. В Borland Pascal их могут также использовать програм-

мы для DOS.

Динамические методы отличаются от виртуальных методов спо-

собом диспетчеризации динамических методов на этапе выпол-

нения. Вместо построения для динамических методов таблицы

виртуальных методов (VMT) компилятор строит DMT. Использо-

вание DMT уменьшает потребности вашей прикладной программы

в памяти при программировании с объектами. Более подробно

о динамических методах рассказывается в разделе "Динами-

ческие методы" в Главе 4 ("Типы") "Руководства по языку".

Чтобы узнать подробности о таблицах динамических методов,

см. раздел "Таблицы динамических методов" в Главе 21

("Вопросы использования памяти") "Руководства по языку".

Улучшения в библиотеке исполняющей системы

Borland Pascal имеет три библиотеки исполняющей системы:

TURBO.TPL для программ DOS реального режима, TPW.TPL для программ

Windows и TPP.TPL для программ DOS защищенного режима.

Улучшения библиотек исполняющей системы касаются следующих

элементов:

* более быстрого ввода-вывода текстового файла;

* более быстрой функции Pos;

* оптимизации 80386 для операций умножения, деления, сдвига

влево и вправо со значениями типа Longint.

Новые подпрограммы модуля System

Модуль System содержит семь новых процедур и функций. Вы мо-

жете найти их в Главе 1 ("Справочник по библиотеке") "Справочного

руководства программиста".

* Процедура Assigned выполняет проверку и анализ того, со-

держит ли указатель или процедурная переменная nil.

* Процедура Break завершает оператор for, while или repeat.

* Процедура Continue продолжает следующие итерации оператора

for, while или repeat.

* Процедура Include включает элемент в множество.

* Процедура Exclude исключает элемент из множества.

* Функция High возвращает наивысшее значение в диапазоне ар-

гумента.

* Функция Low возвращает низшее значение в диапазоне аргу-

мента.

Новые модули

Благодаря модулю Strings программисты, использующие Turbo

Pascal for Windows, могли работать со строками с завершающим ну-

лем (в стиле Си). Теперь модуль Strings могут использовать как

программисты, работающие в Windows, так и работающие в DOS. Под-

робнее о строках с завершающим нулем рассказывается в Главе 18

"Использование строк с завершающим нулем" в "Руководстве по язы-

ку". Полную информацию о строках с завершающим нулем вы можете

получить, прочитав Главу 1 ("Справочник по библиотеке") "Справоч-

ного руководства программиста".

Программисты, работающие с Turbo Pascal for Windows, уже

знакомы с модулем WinDos. Программисты, работающие в DOS, также

могут использовать модуль WinDos для реализации операционных сис-

тем и подпрограмм обработки файлов. Чтобы выяснить, следует вам

использовать модуль WinDos или Dos, прочитайте Главу 16 "Интер-

фейс с DOS" "Руководства по языку". Все процедуры и функции под-

робно поясняются в Главе 1 ("Справочник по библиотеке") "Справоч-

ного руководства программиста".

Модуль WinAPI дает вам возможность прямого доступа к расши-

рениям DOS защищенного режима. Интерфейс WinAPI разработан как

подмножество API (интерфейс прикладных программ) Windows, чтобы

облегчить написание переносимых приложений и двоично-совместимых

DLL. О модуле WinAPI рассказывается в Главе 17 "Программирование

в DOS в защищенном режиме" "Руководства по языку". Более полную

информацию о процедурах и функциях модуля WinAPI можно найти в

Главе 1 ("Справочник по библиотеке") "Справочного руководства

программиста".

Модуль WinPrn позволяет послать информацию, выводимую вашей

программой Windows, на выбранный принтер. О печати в программах

Windows рассказывается в Главе 14 ("Ввод и вывод") "Руководства

по языку". См. также расширенные пояснения по процедурам WinPrn в

Главе 1 ("Справочник по библиотеке") "Справочного руководства

программиста".

Модуль Win31 обеспечивает интерфейс с дополнительными прог-

раммами API, которые можно найти в DLL KERNEL и USER Windows 3.1.

Приложения, использующие модуль Win31, не будут работать под

Windows 3.0. Подробности о модуле Win31 вы можете узнать в спра-

вочной системе Borland Pascal.

Остальные расширения API Windows 3.1 Borland Pascal поддер-

живает в нескольких модулях (информацию о них вы можете получить

в справочной системе Borland Pascal):

ColorDlg LZExpand ShellAPI

CommDlg MMSystem Stress

Cpl OLE TooHelp

DDEML PenWin Ver

Dlgs Print WinMem32

Новые директивы компилятора

Borland Pascal имеет пять новых директив компилятора. Под-

робнее о них рассказывается в Главе 2 ("Директивы компилятора")

"Справочного руководства программиста":

* Директива $P (открытые строковые параметры) управляет

смыслом параметров-переменных, описанных с помощью ключе-

вого слова string.

* Директива $T (указатели с проверкой типа) управляет типами

значений указателей, генерируемых операцией @.

* Директива $Q (проверка переполнения) управляет генерацией

кода проверки переполнения для отдельных арифметических

операций.

* Директива $K (эффективные вызовы) управляет генерацией эф-

фективных вызовов процедур и функций, экспортируемых при-

ложением Windows. Подробнее об обработке в Borland Pascal

эффективных вызовов рассказывается в разделе "Код входа и

выхода" Главы 22 ("Вопросы управления") "Руководства по

языку".

* Директива $Y (информация об идентификаторе) генерирует

в компилируемой программе или модуле ссылочную информацию

идентификатора, благодаря чему ObjectBrowser может выво-

дить на экран определение идентификатора и ссылочную ин-

формацию для данного программного модуля.

Усовершенствования компилятора

Кроме расширений языка Borland Pascal и добавления новых ди-

ректив компилятора, в сам компилятор также внесены улучшения:

* Компилятор позволяет выполнять более простую компоновку с

Си и ассемблером путем передачи в ваш выполняемый файл ин-

формации о номерах строк .OBJ. Таким образом, вы можете

использовать встроенные отладчик для выполнения по шагам

кода языка Си и ассемблера. См. в данной книге Главу 6

"Отладка в интегрированной среде".

* Когда правым операндом операции in является константа мно-

жественного типа, компилятор генерирует более эффективный

код. См. раздел "Встраивание констант множественного типа"

в Главе 23 ("Оптимизация кода") в "Руководстве по языку".

* Компилятор генерирует более эффективный код для малых мно-

жеств. О малых множествах подробнее рассказывается в Главе

23 ("Оптимизация кода") в "Руководстве по языку".

* Компилятор допускает неограниченную вложенность модулей.

* Оператор uses в разделе implementation модуля не вызывает

теперь циклической ссылки на модуль.

* В определенных ситуациях компилятор подавляет операции из-

быточной загрузки указателей. См. Главу 23 ("Оптимизация

кода") "Руководства по языку".

Улучшения в Turbo Vision

В Turbo Vision 2.0 в иерархию добавлены новые объекты, а к

существующим объектам добавлены некоторые новые возможности. Из-

менения существующий объектов имеют обратную совместимость, поэ-

тому существующий код Turbo Vision следует компилировать без из-

менений, а существующие потоки и ресурсы загружаются без ошибок.

В Turbo Vision 2.0 имеются некоторые новые средства. О них

рассказывается в "Руководстве по программированию с Turbo

Vision":

* Поддержка проверки допустимости данных. Ваши приложения

Turbo Vision могут обеспечить получения для обработки до-

пустимых данных.

* Кнопки с независимой фиксацией с множеством состояний.

Кнопки с независимой фиксацией могут иметь состояния, от-

личные от "выбрана" и "не выбрана". IDE защищенного режима

DOS (BP.EXE) использует кнопки с независимой фиксацией с

множеством состояний в своем диалоговом окне параметров

компилятора Compiler Options.

* Средство просмотра схемы объектов. Для вывода схем ваши

приложения могут использовать два объекта - TOutlineViewer

и TOutline. Эти объекты использует средство DOS

ObjectBrowser.

* Поддержка версий объектов в потоках. Даже если ваши объек-

ты были созданы с помощью Turbo Vision 1.0, ваши программы

все равно смогут считывать их как объекты, совместимые с

Turbo Vision 2.0.

* Новое учебное руководство и пересмотренная документация.

Вы увидите, что можно быстро изучить и освоить Turbo

Vision.

Улучшения ObjectWindows

В иерархию ObjectWindows добавлены новые объекты. К сущест-

вующим объектам также добавлены некоторые новые возможности.

Модуль WObjects больше не существует. Существующий у вас ис-

ходный код можно будет успешно перекомпилировать, если вы замени-

те каждую ссылку на WObjects модулями OWindows, ODialogs, OMemory

и Objects.

ObjectWindows содержит новые средства. Узнать о них можно,

прочитав "Руководство по программированию с использованием

ObjectWindows":

* Поддержка проверки допустимости данных. Ваши приложения

ObjectWindows могут обеспечить получение для обработки до-

пустимых данных.

* Печать объектов. Благодаря новым объектам печати выводить

данные на печать в программах ObjectWindows стало проще.

* Поддержка специализированных управляющих элементов Windows

фирмы Borland. Ваши программы Windows могут иметь вид, со-

ответствующий стандарту Borland.

Новые средства и утилиты

Если вашей целевой платформой является Windows, помочь про-

цессу разработки могут два новых средства. Прочесть о них можно в

"Руководстве по инструментальным средствам и утилитам".

* Утилита WinSight. WinSight - это инструментальное средство

отладки, которое дает вам информацию об окнах, классах и

сообщениях. Используйте ее для изучения новых приложений

Windows (ваших или чужих), чтобы увидеть как создаются и

используются окна и классы окон, и какие сообщения получа-

ют окна во время выполнения программы.

* Утилита WinSpector. С помощью WinSpector вы можете прове-

рить приложение Windows после завершения его с невосста-

навливаемой ошибкой (Unrecoverable Application Error -

UAE). Это поможет вам понять причину ошибки.

Borland Pascal также включает в себя обновленные версии сле-

дующих инструментальных средств:

* Turbo Debugger (Турбо отладчик) включает отладчик TDW, ко-

торый может отлаживать прикладные программы Windows, и

TDX, который может отлаживать прикладные программы защи-

щенного режима DOS.

* Turbo Profiler (Турбо профилировщик), который включает в

себя два профилировщика - один для программ DOS, а другой

для программ Windows.

* Turbo Assembler (Турбо ассемблер), добавляющий к коду ас-

семблера объектно-ориентированное программирование.

* Resource Workshop (Пакет разработчика ресурсов), работаю-

щий в среде Windows и способный создавать для приложений

Windows все ресурсы (диалоговые окна, курсоры, битовые

массивы, пиктограммы и т.д.).


Глава 3. Основы интегрированной среды для DOS

Borland Pascal представляет собой не только быстрый компиля-

тор Паскаля. Это эффективный компилятор, интегрированную интерак-

тивную среду которого очень легко изучать и использовать. При ра-

боте с Borland Pascal для того, чтобы создавать, отлаживать и за-

пускать программы на Паскале, вам не требуется использовать от-

дельный редактор, компоновщик, компилятор и отладчик. Все эти

средства встроены в Borland Pascal и доступны из интегрированной

интерактивной среды разработки программ (IDE).

Примечание: Если вы предпочитаете использовать компи-

лятор режима командной строки. См. Главу 3 "Компиляторы,

работающие в режиме командной строки" в "Справочном руко-

водстве программиста".

Имея пакет Borland Pascal, вы можете выбрать одну из трех

интегрированных сред:

* BP.EXE, интегрированную среду разработки программ, которая

работает в защищенном режиме DOS и генерирует прикладные

программы DOS реального режима, DOS защищенного режима и

Windows.

Чтобы запустить BP.EXE, вы должны иметь компьютер с про-

цессором 80286 или старше и не менее 2 мегабайт памяти.

Поскольку IDE работает в защищенном режиме, ее емкость ог-

раничена только объемом доступной на компьютере памятью.

* TURBO.EXE, интегрированную среду, которая работает в ре-

альном режиме DOS и генерирует только прикладные программы

DOS реального режима.

* BPW.EXE, интегрированную среду, которая работает под

Windows и генерирует прикладные программы DOS реального

режима, Windows и DOS защищенного режима.

В этой главе поясняются основы использования IDE для DOS.

Если вы уже имеете опыт работы в Windows, то вам известны

основы работы в IDE Windows, поскольку вы знаете как работает

сама Windows. Если вы не знакомы с IDE для Windows фирмы Borland,

то следует просмотреть сначала Главу 4 "Программирование в интег-

рированной среде для DOS". IDE для DOS и Windows во многом похо-

жи: в обеих средах большинство задач выполняются аналогично. Ког-

да вы поймете, как выполнять задачи по программированию в IDE,

прочитайте Главу 5 "Программирование и интегрированной интерак-

тивной среде для Windows", где рассказывается о средствах, специ-

фических в IDE для Windows и об отличиях IDE для DOS и для

Windows.

Запуск IDE

Перейдите в подкаталог Borland Pascal, созданный программой

Install. Обычно этим каталогом является каталог C:\BP\BIN. Чтобы

запустить IDE защищенного режима, введите команду:

BP

Примечание: О параметрах запуска Borland Pascal расс-

казывается в Главе 4 "Программирование в интегрированной

среде для DOS".

Файлы DPMI16BI,OVL и RTM.EXE должны находиться в текущем ка-

талоге или в каталоге, указанном в маршруте, иначе BP.EXE не за-

пустится.

Чтобы запустить IDE, работающую в реальном режиме, введите

команду:

TURBO

Компоненты интегрированной среды

Интегрированная интерактивная среда разработки программ со-

держит три видимых компонента: строку меню в верхней части экра-

на, оперативную область и строку состояния в нижней части экрана.

При активной строке меню вы увидите подсвеченный заголовок меню.

Это текущее выбранное меню.

Выбрать команды меню вы можете с помощью клавиатуры или "мы-

ши".

Выбрать команды меню с помощью клавиатуры можно следующим

образом:

1. Нажмите клавишу F10. Это активизирует строку меню.

2. Для выбора меню, которое вы хотите вывести, используйте

клавиши стрелок. Затем нажмите Enter.

Примечание: Чтобы отменить действие, нажмите клавишу

Esc.

В качестве сокращения этого шага вы можете нажать подсве-

ченную букву заголовка меню. Например, находясь в строке

меню, нажмите E для быстрого вывода меню Edit. Либо без

активизации строки меню вы можете нажать для вывода нуж-

ного меню клавишу Alt и подсвеченную букву.

3. Для выбора нужной команды меню используйте клавиши стре-

лок. Затем нажмите клавишу Enter.

Здесь снова в качестве альтернативного варианта вы можете

для выбора команды при выводе меню просто нажать подсве-

ченную букву.

При этом Borland Pascal либо выполнит команду, либо выве-

дет диалоговое окно или другое меню.

Для выбора команд можно также использовать "мышь". Для это-

го:

1. Для вывода нужного меню щелкните кнопкой "мыши", остано-

вившись на нужном заголовке меню.

2. Щелкните кнопкой "мыши" на нужной команде.

Примечание: Вы можете настроить действие Ctrl+правая

кнопка "мыши" и даже поменять действия кнопок "мыши"; вы-

берите команду OptionsіEnviromentіMouse.

Вы можете также "вытянуть" меню из заголовка вниз до команды

меню. Освободите кнопку "мыши" на нужной команде (если ваши наме-

рения изменятся, просто убедите меню обратно - команда выбрана не

будет).

Если за командой меню следует многоточие (...), выбор коман-

ды приводит к выводу диалогового окна. Если за командой указана

закрашенная стрелка (>), то эта команда приводит к другому меню

(всплывающему меню). Команда баз многоточия или стрелки указы-

вает действие, выполняемое при ее выборе.

Иногда команды меню выводятся "тусклыми", и при их выборе

ничего не происходит. Это случается, когда выбор отдельной коман-

ды в данном контексте не имеет смысла. Например, если в текущем

окне редактирования у вас нет выделенного блока, вы не сможете

вырезать, копировать или стирать текст, поскольку не указали ре-

дактору, сколько текста нужно вырезать, скопировать или стереть.

Следовательно, соответствующие команды (Cut, Copy и Clear) будут

в меню Edit тусклыми. После выделения текста в окне редактирова-

ния вы сможете выбирать эти команды.

Оперативные клавиши

Работая с клавиатурой, для доступа к строке меню и командам

вы также можете использовать множество сокращенных вариантов ко-

манд (оперативных клавиш). Перейти в основное меню и активизиро-

вать его элементы можно нажатием клавиши Alt и подсвеченной бук-

вы. Когда вы находитесь в меню, можете нажать подсвеченную букву

элемента или следующее за ней сокращение. Оперативные клавиши

можно использовать в любом месте IDE - для этого не требуется

сначала выводить меню.

Строка состояния также содержит оперативные клавиши. Для вы-

бора соответствующей команды нажмите оперативную клавишу или

щелкните "мышью" на фактическом представлении оперативной клавиши

в строке состояния.

Окна IDE

Большинство и того, что вы видите в IDE, происходит в окне.

Окно - это область экрана, которую можно перемещать, масштабиро-

вать, перекрывать, выводить без перекрытия, закрывать, открывать

и изменять ее размер.

ЪДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДї Ъї

і Для быстрого і і Заголовок і і Кнопка масштабирования і

і закрытия окна і і содержит і і содержит значок, на і

і можно щелкнуть і і название і і котором можно щелкнуть і

і "мышью" на і і данного і і мыщью для распахивания і

і блоке закрытия і і окна і і или сжатия окна і

і окна і АДДДДДДДДВДДЩ АДДДДДДДДДДДДДДДДДДДДДДДВДЩ

АДДДВДДДДДДДДДДДДЩ і і

і і і

v v v

ЙНН[Ы]ННННННННННННННННН Заголовок окна НННННННННННННННННН 3 Н[^]»

є ^ ^

є і ±

є ЪБДДДї ±

є і Каждое открытое окно имеет і ±

є і номер. Используйте Alt и # і Ы

є і для открытия окна. і ±

є АДДДДЩ ±

є ±

є ЪДДДДДДДДДДї ЪДДДДДДДї ±

є і Строка и і і Используйте полосы прокрутки і ±

є і столбец і і с "мышью" для перемещения ГДДДДДДД>±

є і позиции і і содержимого окна. і ±

є і курcора і АДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДЩ ±

є АДДДВДДДДДДЩ і ±

є і і ±

є v v v

ИН*Н1:1ННН<±±±±±±±±±±±±±±Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>ДЩ

^ ЪДДДДДї ^

ЪБДДДДДДДДДДДДї і Чтобы сделать окно больше ГДДДЩ

і Индикатор і і или меньше, буксируйте угол і

і модификации і і изменения размера. і

і файла і АДДДДДЩ

АДДДДДДДДДДДДДЩ

Рис. 3.1 Типичное окно.

При работе в IDE вы можете открыть и использовать множество

окон, но в каждый момент времени активным может быть только одно

окно. Активное окно - это то окно, в котором вы в данный момент

работаете. Любая команда, которую вы выбираете, или текст, кото-

рый вы набираете, относится только к активному окну. Однако, ес-

ли вы открыли в нескольких окнах один и тот же файл, любое дейс-

твие, применяемое к этому файлу, может отражаться на всех окнах,

содержащих его.

Существует несколько типов окон, но большинство из них со-

держат следующие элементы:

- строку заголовку;

- элемент закрытия;

- полосы прокрутки;

- угол изменения размера;

- элемент "распахивания";

- номер окна.

IDE отмечает активное окно, обрисовывая его двойной рамкой,

благодаря чему его можно легко идентифицировать на экране. Если

ваши окна перекрываются, то активное окно всегда находится перед

всеми другими ("переднее" окно).

В активном окне редактирования в левом верхнем углу выводят-

ся также значения текущей строки и столбца. Если вы модифицирова-

ли файл, слева от значений столбца и строки выводится звездочка

*.

Элемент закрытия окна находится в верхнем левом углу. Пози-

ционировав на этот элемент "мышь" и щелкнув кнопкой, вы можете

быстро закрыть данное окно (в противном случае можно выбрать ко-

манду WindowіClose). Справочное окно Help считается временным,

поэтому закрыть его можно просто нажав клавишу Esc.

Верхняя горизонтальная строка меню, строка заголовка, содер-

жит название окна и его номер. Двойной щелчок кнопкой "мыши" на

заголовке окна приводит к его "распахиванию" (увеличению до раз-

мера полного экрана) или наоборот, восстановлению предыдущего

размера, если оно уже распахнуто. Вы можете также перемещать

(буксировать) с помощью "мыши" заголовок, что приводит к переме-

щению окна.

Каждому открываемому вами окну присваивается номер (он ука-

зывается справа вверху). Нажатие клавиш Alt+0 дает вам список

всех открытых окон. Окно можно сделать активным, нажав клавишу

Alt в сочетании с номером окна. Например, если справочное окно

Help имеет номер 5, но перекрыто сейчас другим окном, нажатие

Alt+5 переводит вас в это окно (оно становится первым).

Примечание: Borland Pascal нумерует только первые 9

открытых вами окон.

В правом верхнем углу окна выводится элемент распахивания

(масштабирования) окна. Если символ в этом элементе представляет

собой стрелку вверх, то щелчок кнопкой "мыши" при позиционирова-

нии в этом элементу приведет к максимальному увеличению его раз-

мера (распахиванию). Если это двойная стрелка, то окно уже имеет

максимальный размер. В этом случае щелчок кнопкой "мыши" возвра-

щает окно к предыдущему размеру. Чтобы "распахнуть" окно с по-

мощью клавиатуры, выберите команду WindowіZoom или нажмите клави-

шу F5.

Примечание: Двойной щелчок "мышью" на строке заголовка

окна также приведет к его распахиванию или восстановлению

размера.

Полоса прокрутки - это вертикальная или горизонтальная

полоса, которая выглядит следующим образом:

<±±±±±±±±±±±±±±±±±±±±±±±±±±±Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>

Эти полосы можно использовать при работе с "мышью" для прок-

рутки содержимого окна.

* Если щелкнуть кнопкой "мыши", позиционировавшись в конце

полосы, это приведет к прокрутке на одну строку.

* Если нажать и не отпускать кнопку, прокрутка будет продол-

жаться.

* Нажатие кнопки "мыши" на затененной области в конце полосы

прокрутки (стрелки) приведет к постраничному "листанию".

* Если вы с помощью "мыши" будете перемещать (буксировать)

по полосе прокрутки скользящий маркер (здесь он отмечен

символом Ы), то содержимое окна сместится (прокрутится) в

соответствии с относительной позицией полосы прокрутки.

Примечание: Полосы прокрутки позволяют любому пользо-

вателю (использующему "мышь" или клавиатуру) видеть, как

далеко он продвинулся в файле.

В правом нижнем углу окна находится элемент изменения разме-

ра. Вы можете буксировать этот угол с помощью "мыши", увеличивая

или уменьшая размеры окна. Указанный угол отмечен одинарной, а не

двойной линией, как остальная граница окна.

Чтобы изменить размер окна с помощью клавиатуры, сделайте

следующее:

1. Используйте команду Size/Move меню Window, или нажмите

клавиши Ctrl+F5.

2. Удерживая нажатой клавишу Shift, для изменения размера

окна используйте клавиши стрелок.

Чтобы переместить окно с помощью клавиатуры, сделайте следу-

ющее:

1. Используйте команду Size/Move меню Window, или нажмите

клавиши Ctrl+F5.

2. Для перемещения окна используйте клавиши стрелок.

Управление окном

В Таблице 3.1 кратко перечислено, как можно управлять окнами

в Borland Pascal. Заметим, что для выполнения этих действий вам

не требуется "мышь" - можно прекрасно обойтись и клавиатурой.

Работа с окнами Таблица 3.1

ЪДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДї

іЧтобы: і Используйте следующие методы: і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іОткрыть окно і Команду Choose FileіOpen для открытияі

іредактора і файла и вывода его на экран, или клави-і

і і шу F3. і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іОткрыть другие окна і Выберите нужное окно из меню Window ві

і і меню Tools или Debug. і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іЗакрыть окно і Команду Close меню Window (или клави-і

і і ши Alt+F3), либо щелкните кнопкой "мы-і

і і ши" на элементе закрытия окна. і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іУвидеть предыдущее і Выберите команду WindowіPrevious или і

іокно і используйте клавиши Shift+F6. і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іАктивизировать окно і Щелкните кнопкой в любом месте окна,і

і і или і

і і і

і і Нажмите клавишу Alt, плюс номер окна (ві

і і верхнем правом углу окна), или і

і і і

і і Используйте команду Choose WindowіListі

і і (Выбор окнаіСписок), или нажмите клави-і

і і ши Alt+0 и выделите окно из списка, илиі

і і і

і і Используйте команду Choose WindowіNextі

і і или F6, чтобы сделать активным следую-і

і і щее (в том порядке, как вы их открыва-і

і і ли) окно. Либо нажмите клавиши Alt+F6,і

і і чтобы сделать активным предыдущее окно.і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іПереместить активное і Переместите с помощью "мыши" строкуі

іокно і заголовка или нажмите клавиши Ctrl+F5і

і і (WindowіSize/Move - ОкноіРазмер/Переме-і

і і щение) и используйте для позиционирова-і

і і ния окна в нужное место клавиши управ-і

і і ления курсором, после чего нажмите кла-і

і і вишу Enter. і

ЪДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДї

і Чтобы: і Используйте следующие методы: і

ГДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іИзменить размер і Переместите с помощью "мыши" угол из-і

іактивного окна і менения размера (или любой другойі

і і угол). Либо выберите команду Windowіі

і і Size/Move и нажмите клавишу Shift, еслиі

і і вы используете для изменения размераі

і і окна клавиши управления курсором. Послеі

і і этого нажмите клавишу Enter. Можно ис-і

і і пользовать сокращенный вариант - нажатьі

і і Ctrl+F5 и использовать клавишу Shiftі

і і совместно с клавишами управления курсо-і

і і ром (стрелки). і

ГДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДґ

іМасштабировать і Нажмите кнопку "мыши", позиционировав-і

іактивное окно і шись на элементе в правом верхнем уг-і

і і лу окна, или і

і і і

і і Дважды щелкните кнопкой, позициониро-і

і і вавшись в строке заголовка, или і

і і і

і і Используйте команду ChooseіWindowіZoom,і

і і или нажмите клавишу F5. і

АДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДЩ

Строка состояния

Строка состояния выводится в нижней части экрана. Она выпол-

няет следующие четыре функции:

- напоминает вам об основных и оперативных клавишах, приме-

нимых в данный момент к активному окну;

- сообщает, какие можно вместо выбора команд меню и нажатия

оперативных клавиш использовать кнопки "мыши";

- сообщает, что делает программа, например, выводит сообще-

ние "Saving имя_файла" при сохранении файла редактором;

- предлагает краткие пояснения по некоторым командам меню и

элементам диалоговых окон.

При переключении окон или изменении выполняемых действий

строка состояния изменяется. Наиболее общий вид строка состояния

имеет, когда вы записываете или редактируете программы в окне

Edit. Это показано ниже:

ЪДДДДДДДДДДДДДї

іF1 Help F2 Save F3 Open Alt+F9 Compile F9 Make F10 Menuі

АДДДДДДДДДДДДДЩ

^ ^ ^ ^ ^ ^

і і і і і і

Справка Сохранение Открытие Компиляция Формирование Меню

Рис. 3.2 Типичная строка состояния.

Диалоговые окна

Если после команды меню указано многоточие (...), то по этой

команде открывается диалоговое окно. Диалоговое окно предоставля-

ет удобный способ просмотра и установки набора параметров.

Для установок значений в диалоговом окне используется пять

основных способов управления экраном: кнопки с зависимой фиксаци-

ей, командные кнопки, кнопки с независимой фиксацией (параметры),

элементы (блоки) ввода и блоки списка. Приведем пример типичного

диалогового окна, на котором иллюстрируются некоторые из этих

элементов:

ЙН[Ы]НННННННННН Типичное диалоговое окно НННННННННННННННННННН»

є є

є є

є Блок ввода Блок списка >[±±±OK±±±]<є

є ЪДДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДї є

є і±±±±±±±±±±±±±±±±±±±±±±ііvі іЭлемент 1 ^і є

є АДДДДДДДДДДДДДДДДДДДДДДЩ іЭлемент 2 Ыі [±Cancel±] є

є іЭлемент 3 Ыі є

є Кнопки с Кнопки с іЭлемент 4 Ыі є

є независимой зависимой іЭлемент 5 Ыі є

є фиксацией фиксацией іЭлемент 6 Ыі є

є іЭлемент 7 Іі є

є [X] Парам. 1 ( ) Парам. A іЭлемент 8 Ыі є

є [ ] Парам. 2 (.) Парам. B іЭлемент 9 vі [±±Help±±] є

є [X] Парам. 3 ( ) Парам. C АДДДДДДДДДДДДДДДЩ є

є [ ] Парам. 4 ( ) Парам. D є

є [ ] Парам. 5 є

є є

ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

Рис. 3.3 Типичное диалоговое окно.

Командные кнопки

Данное диалоговое окно содержит три стандартных командных

кнопки: OK, Cancel и Help.

* Если вы выберите OK (Подтверждение), то выбор, заданный в

диалоговом окне, будет зафиксирован в Borland Pascal.

* Если вы выбираете Cancel (Отмена), то внесенные изменения

игнорируются, а диалоговое окно остается на экране.

* Выбор Help (Справка) приводит к выводу в IDE справочной

информации по данному диалоговому окну. Клавиатурным экви-

валентом для кнопки Cancel всегда является клавиша Esc

(даже если Cancel не выводится).

Если вы работаете с "мышью", то можете просто щелкнуть кноп-

кой, позиционировавшись на нужной командной кнопке. При использо-

вании клавиатуры для активизации элемента (кнопки) можно нажимать

подсвеченные в нем буквы. Например, нажатие буквы K приводит к

выбору функциональной кнопки OK. Нажатие Tab или Shift+Tab приво-

дит к перемещению в диалоговом окне вперед или назад от одного

управляющего элемента к другому. Когда элемент становится актив-

ным, он подсвечивается. Если кнопка выбрана, чтобы задействовать

ее, просто нажмите Enter.

Командная кнопка OK диалогового окна является используемой

по умолчанию. Это означает, что для выбора данной кнопки нужно

только нажать клавишу Enter. (В системах с монохромным дисплеем

используемые по умолчанию кнопки указываются стрелками, в систе-

мах с цветным дисплеем они подсвечиваются.) Нужно помнить о том,

что переход к командной кнопке с помощью клавиши Tab делает ее

используемой по умолчанию.

Примечание: С помощью клавиши Tab вы можете выбрать

другую кнопку. Для выбора этой кнопки нажмите клавишу

Enter.

Кнопки с зависимой и независимой фиксацией

В любой момент вы можете установить любое число кнопок с не-

зависимой фиксацией. Когда вы выбираете кнопку с независимой фик-

сацией, в ней появляется символ X, показывающий, что она установ-

лена. Пустой элемент показывает, что он не установлен (выключен).

Вы можете установить (включить) кнопку с независимой фикса-

цией тремя способами:

* позиционировав на ней или на ее тексте "мышь" и щелкнув

кнопкой;

* нажимая клавишу Tab (и клавиши стрелок), пока не будет

подсвечена нужная кнопка с независимой фиксацией (или ее

группа), а затем нажав клавишу пробела.

* нажав подсвеченную в тексте букву.

На монохромных мониторах IDE отмечает активную кнопку с не-

зависимой фиксацией символом >> после этой кнопки. При нажатии

клавиши Tab символ >> перемещается к следующей группе кнопок с

зависимой или независимой фиксацией.

Примечание: Кнопки с зависимой фиксацией называются так

потому, что они действуют, как группа кнопок, где нажать в

каждый момент можно только одну кнопку (как при выборе те-

леканала). При нажатии кнопки прежняя нажатая кнопка возв-

ращается в исходное положение.

Кнопки с зависимой фиксацией отличаются от кнопок с незави-

симой фиксацией тем, что они представляют взаимоисключающие воз-

можности выбора. По этой причине кнопки с зависимой фиксацией

всегда выводятся в виде группы, и в любой группе в каждый момент

можно выбрать только одну кнопку.

Существует три способа выбора кнопок с зависимой фиксацией:

* щелкните на ней или ее тексте кнопкой "мыши";

* наберите подсвеченную в соответствующем тексте букву;

* нажимайте клавишу Tab, пока группа не будет подсвечена, а

затем для выбора конкретной кнопки используйте клавиши уп-

равления курсором: для выхода из группы с новым выбранным

функциональным переключателем нажмите клавишу Tab или

Shift+Tab.

Блоки ввода

Блок ввода - это тот элемент, в котором вы можете вводить в

свою прикладную программу текст. В элементах (полях) ввода можно

использовать большинство основных клавиш редактирования, напри-

мер, клавиши стрелок, Home, End и переключатель вставки/замены

Ins. Если вы продолжаете набирать текст при достижении конца эле-

мента ввода, его содержимое будет автоматически прокручиваться.

Если в элементе имеется больше текста, чем показано, то на концах

его выводятся закрашенные стрелки (< и >). Для прокрутки текста

вы можете позиционироваться на этих стрелках и щелкнуть кнопкой

"мыши".

Если вам требуется ввести в блоке ввода управляющие символы

(такие, как ^L или ^M), то перед этими символами нужно указать

префиксный символ ^P. Поэтому, например, для ввода ^L в поле вво-

да нужно использовать последовательность ^P^L. Это полезно приме-

нять при вводе строк.

Если справа от блока ввода указывается символ стрелки вниз,

то с этим элементом связан протокол ввода. Для просмотра протоко-

ла используйте клавишу со стрелкой вниз, а выбрав нужный элемент

из списка, нажмите Enter. В списке (протоколе) выводится весь

текст, который вы набирали в элементе ввода ранее. Если вы хотите

заново набрать текст, который уже был введен, нажмите клавишу

стрелки вниз или щелкните кнопкой "мыши" на символе "стрелка

вниз". Можно также редактировать запись из протокола. Для выхода

из списка протокола без выбора нажмите клавишу Esc.

Приведем пример протокола для элемента Find text (Поиск

текста). Если вы ранее использовали его семь раз, он может выгля-

деть следующим образом:

ЪДДДДДДДДДДДДДДДДДДДДДїЪДДДї

Text to find і±±±±±±±±±±±±±±±±±±±±±іі v і

АДДДДДДДДДДДДДДДДДДДДДЩАДДДЩ

ЙННННННННННННННННННННННННН»

є date = record ^

є Writeln(' ±

є string[7] ±

є { Ы

є AbortCode v

ИНННННННННННННННННННННННННј

Рис. 3.4 Пример протокола в диалоговом блоке.

Блоки списка

Во многих диалоговых окнах присутствует еще один компонент -

блок списка. Этот список позволяет вам просматривать и выбирать

запись из списка переменной длины, не выходя из диалогового окна.

Если в списке появляется мерцающий курсор и вы хотите что-то най-

ти, можно ввести слово (или его первые буквы), после чего IDE вы-

полнит его поиск.

Активизировать блок списка можно щелкнув на нем кнопкой "мы-

ши" или выбрав подсвеченную букву заголовка списка (либо нажать

клавишу Tab или клавиши управления курсором, пока он не будет

подсвечен). После вывода списка для его просмотра можно использо-

вать полосу прокрутки или клавиши стрелок (вверх и вниз) на кла-

виатуре.

Теперь вы познакомились с основными понятиями использования

IDE и готовы использовать интегрированную среду для разработки

прикладных программ. См. следующую главу - "Программирование в

интегрированной интерактивной среде для DOS".


Глава 4. Программирование в интегрированной интерактивной среде для DOS

При разработке прикладной программы в интегрированной среде

IDE вы можете выполнять следующие основные задачи:

* запускать IDE и выходить из нее;

* записывать и редактировать свой исходный код;

* работать с файлами (открывать, закрывать и сохранять их);

* компилировать и выполнять свои программы;

* отлаживать программы;

* просматривать исходный код;

* настраивать по своему усмотрению конфигурацию IDE;

* управлять программными проектами.

Данная глава дает основные понятия по каждой из перечислен-

ных тем, за исключением отладки программы (о которой рассказыва-

ется в Главе 6 "Отладка в интегрированной среде").

Запуск IDE и выход из нее

Перейдите в подкаталог Borland Pascal, созданный вами с по-

мощью программы Install. Обычно этим каталогом является каталог

C:\BP\BIN. Чтобы запустить IDE защищенного режима, введите коман-

ду:

BP

Файлы DPMI16BI.OVL и RTM.EXE должны находиться в текущем ка-

талоге по вашему маршруту, иначе BP.EXE не запустится.

Чтобы запустить IDE, работающую в реальном режиме, введите

команду:

TURBO

Вместе с командами для запуска IDE вы можете использовать

один или более параметров и имен файлов.

Параметры запуска

При запуске IDE вы можете указывать параметры запуска. Эти

параметры запуска имеют следующий синтаксис:

BP[/параметры][файлы]

или

TURBO[/параметры][файлы]

Указание после параметра символа + или пробела включает его,

указание символа - отключает. Например:

BP /G /P- myfile

запускает IDE, открывает окно редактирования, выводя в нем файл

MYFILE, разрешает сохранение графической памяти и запрещает пе-

реключений палитр.

Вместо символа косой черты (/) перед параметром вы можете

использовать минус (-). Например:

BP -G -P- myfile

Некоторые параметры запуска применяются только к IDE реаль-

ного режима - TURBO.EXE. В этом случае параметр помечен словами

"(только TURBO)". Два параметра применяются к IDE защищенного ре-

жима - BP.EXE. В этом случае параметр помечен словами (только

BP).

Параметр /C

Если вы используете параметр /C, за которым без пробела сле-

дует имя файла конфигурации, то IDE при запуске загружает этот

файл конфигурации. Например:

TURBO /Cmyconfig

О файлах конфигурации рассказывается ниже.

Параметр /D

Если вы укажете параметр /D, IDE сможет одновременно рабо-

тать с двумя мониторами. IDE проверяет, имеется ли на вашем

компьютере соответствующее оборудование, например, монохромная и

цветная плата. Если это не так, то IDE игнорирует данный пара-

метр. Режим с двумя мониторами полезно использовать при отладке

программы. Один монитор вы можете использовать как экран выводи-

мых программой данных, а другой - для работы с отладчиком. Ис-

пользуя два монитора, вы можете также выйти в командный процессор

DOS (FileіShell to DOS), так что на одном экране будет выводиться

IDE, а другой дает вам доступ к командной строке DOS.

Если система имеет два монитора, DOS интерпретирует один мо-

нитор как активный монитор. Для переключения между двумя монито-

рами можно использовать команду DOS MODE. Например, MODE CJ80 ак-

тивизирует цветной монитор, а MODE MONO активизирует монохромный

монитор. В режиме с двумя мониторами обычный экран IDE выводится

на неактивном мониторе, а вывод программы поступает на активный

монитор.

Поэтому когда вы на одном мониторе набираете в ответ на

подсказку DOS BP /D или TURBO /D, IDE выводится на другом монито-

ре. Когда вы хотите проверить программу на конкретном мониторе,

выйдите из IDE, переключите активный монитор на тот, на котором

вы хотите ее проверить, а затем снова дайте команду BP /D или

TURBO /D. Вывод программы затем будет поступать на тот монитор,

где вы набрали команду.

При использовании команды /D следует иметь в виду следующее:

* Не изменяйте активный монитор (используя, например, коман-

ду DOS MODE), когда вы находитесь в командном процессоре

DOS (FileіDOS Shell).

* Пользовательские программы, использующие прямой доступ к

портам видеоплаты неактивного монитора, не поддерживаются

и могут дать непредсказуемые результаты.

* Когда вы выполняете или отлаживаете программы, которые яв-

ным образом используют два монитора, не указывайте пара-

метр /D.

Параметр /E (только TURBO)

Используйте параметр /E для изменения размера динамически

распределяемой области памяти редактора. По умолчанию ее размер

равен 28К (минимальная установка). Максимальное значение - 128К.

Размер динамически распределяемой области памяти, превышающий

28К, улучшает производительность IDE только в том случае, если вы

используете в качестве устройства свопинга медленный диск. Если у

вас есть память EMS или вы разместили файл свопинга на виртуаль-

ном диске (см. параметр /S), не изменяйте используемый по умолча-

нию параметр.

Параметр /F (только BP)

С помощью параметра /F вы можете задать файл свопинга для

администратора Borland Pascal этапа выполнения (RTM.EXE). Напри-

мер, если вы компилируете прикладную программу, которая требует 4

мегабайта памяти, но на вашем компьютере доступно только два ме-

габайта, то вы можете задать файл свопинга 4-мегабайтной вирту-

альной памяти; ваша прикладная программа получит необходимую ей

для компиляции память. Для файла свопинга допустимы размеры от

1024К до 16384К. В следующем примере задается файл свопинга в 2

мегабайта:

BP /F2048

Когда вам больше не нужен будет файл свопинга виртуальной

памяти, выключите это параметр, задав файл нулевого размера:

BP /F0

Параметр /G

Используйте параметр /G для разрешения полного сохранения

графической памяти, при отладке графических программ в системах с

EGA, VGA или MCGA. При включении сохранения графического экрана

(Graphics Screen Save) IDE резервирует дополнительные 8 килобайт

для буфера, который размещается в EMS (при ее доступности).

Параметр /L

Используйте параметр /L, если вы работаете с IDE на жидкок-

ристаллическом или плазменном экране.

Параметр /N

Используйте параметр /N для разрешения или запрета проверки

на помехи на адаптере CGA. Если вы работаете с адаптером CGA, ко-

торый не дает на экране помехи ("снег") при обновлении изображе-

ния, запретите данный параметр. Если вы не работаете с CGA, этот

параметр не действует.

Параметр /O (только TURBO)

Используйте параметр /O для изменения размера оверлейной ди-

намически распределяемой памяти IDE. По умолчанию назначается

размер 90К. Если у вас есть EMS, то вы можете уменьшить размер

оверлейной динамически распределяемой памяти, не ухудшая произво-

дительности IDE и освободив дополнительную памяти для компиляции

и отладки программ.

Параметр /P

Используйте параметр /P, управляющий переключением палитр на

видеоадаптере EGA, когда ваша программа модифицирует регистры па-

литры. Палитра EGA будет восстанавливаться при каждом переключе-

нии экрана.

В общем случае вам не нужно использовать данный параметр,

пока ваша программа не модифицирует регистры палитры EGA или не

использует для переключения палитры BGI.

Параметр /R

Если параметр /R включен при запуске IDE, то текущим стано-

вится тот каталог, в котором вы находились во время последнего

выхода из IDE. По умолчанию этот параметр включен. Чтобы этот па-

раметр начал действовать, нужно также выбрать параметр IDE

OptionsіEnviromentіPreferences и установить параметр Desktop Auto

Save. Если вы не хотите, чтобы IDE запоминала последний каталог,

выключите параметр /R.

Параметр /S

Если ваша система не имеет дополнительной памяти, используй-

те параметр /S для задания диска и маршрута доступа к "быстрой"

области свопинга, такой как виртуальный диск (например, /Sd:\,

где d - дисковод). Если каталог свопинга не задан, то файл сво-

пинга создается в текущем каталоге.

Параметр /T

Если вы не хотите, чтобы IDE загружала библиотеку исполняю-

щей системы, запретите параметр /T. Для TURBO.EXE библиотекой ис-

полняющей системы является TURBO.TPL. В зависимости от целевой

платформы для BP.EXE библиотекой исполняющей системы может быть

TURBO.TPL (реальный режим), TPW.TPL (Windows) или TPP.TPL (защи-

щенный режим). Если библиотека исполняющей системы не загружена,

перед компиляцией или отладкой программ вам потребуется модуль

System. Запретив параметр /N и выделив SYSTEM.TPU из библиотеки

исполняющей системы с помощью TPUMOVER, вы можете увеличить ем-

кость IDE реального режима. Если вы используете IDE защищенного

режима, то может сделать тоже самое, выделив в зависимости от це-

левой платформы модуль SYSTEM.TPU, SYSTEM.TPW или SYSTEM.TPP. О

выборе целевой платформы рассказывается ниже.

Примечание: Об утилите TPUMOVER рассказывается в Главе

1 "Перемещение модулей" в "Руководстве по инструментальным

средствам и утилитам".

Параметр /W (только TURBO)

Используйте параметр /W, если вы хотите изменить размер ди-

намической памяти окна. По умолчанию этот размер устанавливается

в 32К. Минимальная установка - 24К; максимальная - 64К. Если вам

не требуется открывать в оперативной памяти много окон, уменьшите

размер динамической памяти окна. Размер по умолчанию дает IDE хо-

рошую емкость и достаточное оконное пространство.

Параметр /X (только TURBO)

Используйте параметр /X, если вы хотите, чтобы IDE использо-

вала дополнительную память (EMS). По умолчанию этот параметр

включен. При разрешении данного параметра IDE улучшает производи-

тельностье, размещая в дополнительной памяти перекрываемый код,

данные редактора и другие системные ресурсы.

Параметр /Y (только BP)

С помощью этого параметра вы можете сделать так, чтобы ком-

пилятор "запоминал" информацию об идентификаторах между компиля-

циями. Если данный параметр включен, и вы изменили свою програм-

му, но следующая компиляция завершается неудачно, то вам все рав-

но будет доступна информация об идентификаторах с предыдущей ком-

пиляции. Таким образом, вы сможете просмотреть свою программу и

определить, в чем состоит проблема. По умолчанию данный параметр

включен.

Установка параметров запуска в интегрированной среде

Параметры запуска вы можете также установить в самой интег-

рированной среде:

1. Выберите команду OptionsіEnviromentіStartup для вывода

диалогового окна Startup Options.

2. Выберите нужные параметры и задействуйте кнопку OK.

Установленные параметры будут действовать при следующем за-

пуске IDE.

Выход из IDE

Из IDE можно выйти двумя способами:

* Чтобы полностью выйти из IDE, выберите команду FileіExit.

Если вы внесли изменения, которые не были сохранены, IDE

выводит запрос, хотите ли вы сохранить перед выходом свои

программы.

* Чтобы временно выйти из IDE для ввода команд в ответ на

подсказку DOS, выберите команду FileіDOS Shell. IDE оста-

ется в памяти, но управление передается в DOS. Вы можете

ввести команды DOS и даже запустить другие программы. Ког-

да вы будете готовы вернуться в IDE, наберите в командной

строке EXIT и нажмите Enter. IDE выводится в том виде, как

вы из нее выше.

Использование справочной системы Help

Справочная система Help предоставляет вам возможность легко-

го доступа к детальной информации о языке Borland Pascal, интег-

рированной интерактивной среде, библиотеке исполняющей системе,

директивах компилятора и т.д.

Если вы ранее не работали со справочной системой Help для

DOS по языкам Borland, то, прочитав следующие разделы, вы можете

вывести справочный экран. Экран содержимого справочника Borland

Pascal Help Contents выводится при выборе команды HelpіContents.

Перемещение в справочной системе

Если вы посмотрите на справочный экран, то увидите текст,

который выводится цветом, отличным от окружающего текста. Это

ссылки. Вы можете использовать ссылки для вывода нового справоч-

ного экрана, содержащего новую информацию по соответствующей те-

ме. Выберите один из следующих методов:

* Дважды щелкните на ссылке "мышью".

* Если справочный экран не содержит командных кнопок:

Нажимайте повторно клавишу Tab, пока не будет подсвечена

ссылка, затем нажмите клавишу Enter.

* Если справочный экран представляет собой диалоговое окно с

командными кнопками:

- Если вы используете "мышь", щелкните "мышью" на кнопке

перекрестных ссылок Cross-ref.

- Если вы используете клавиатуру, нажмите Enter; при этом

по умолчанию выбирается кнопка Cross-ref. Чтобы выбрать

другую кнопку, нажимайте повторно клавишу Tab, пока не

будет подсвечена нужная кнопка, затем нажмите клавишу

Enter.

Выводится новый справочный экран Help с информацией по выб-

ранной теме. Вы перешли в новое место справочной системы. На этом

экране вы можете видеть другие ссылки, которые можно выбирать для

получения дальнейшей информации.

Чтобы вернуться к предыдущему экрану Help, выберите команду

HelpіPrevious Topic или нажмите клавиши Alt+F1.

Запрос помощи

Доступ к справочнику Help можно получить несколькими спосо-

бами:

* Выбрав команду Help в строке меню или нажав Alt+H для вы-

вода меню Help.

В меню Help вы можете выбрать вывод экрана оглавления

Contents, экрана тематического указателя Index по всей

справочной системе, детальную информацию по теме, на кото-

рую указывает курсор в окне редактирования или справку по

работе со справочной системой Help.

* Для вывода экрана тематического указателя Borland Pascal

Help Index нажмите клавиши Shift+F1.

Экран тематического указателя Index аналогичен тематичес-

кому указателю книги. Однако, вместо того, чтобы перевер-

нуть страницы для получения информации по нужной теме

здесь нужно дважды щелкнуть на ней кнопкой "мыши" или пе-

рейти к теме с помощью клавиши Tab и нажать Enter.

* Нажав клавишу F1.

Вы получите контекстно-зависимую информацию, зависящую от

того, что вы делаете в этот момент - редактируете, отлажи-

ваете программу, выбираете параметры меню и т.д.

Если вы находились в диалоговом окне, то увидите справоч-

ный экран по тому параметру, который выделен в момент на-

жатия F1.

* Выбрав командную кнопку Help в диалоговом окне.

При выборе командной кнопки Help вы получите информацию о

диалоговом окне.

* Поместив курсор на термин в окне редактирования и выбрав

Topic Search. Используйте любой из следующих методов:

- нажмите клавиши Ctrl+F1;

- выберите команду HelpіTopic Search;

- удерживая клавишу Ctrl, щелкните правой кнопкой "мыши"

(комбинация нажатий Ctrl+правая кнопка "мыши" должна

быть предварительно настроена - выберите команду

OptionsіEnviromentіMouse и выберите команду Topic

Search).

- выберите команду Topic Search в локальном меню окна ре-

дактирования (нажмите клавиши Alt+F10 для вывода на эк-

ран локального меню или щелкните правой кнопкой "мыши".

Примечание: О локальном меню окна редактирования расс-

казывается ниже.

В справочном экране выводится информация о термине, на кото-

ром установлен курсор в активном окне.

Копирование примеров исходного кода

Справочная система Help содержит примеры кода для каждой

процедуры и функции. Вы можете скопировать эти примеры из спра-

вочной системы в окно редактирования. Выполните следующие шаги:

1. Выведите справочный экран по интересующей вас процедуре

или функции.

2. Прокрутите справочное окно, пока не увидите в нем пример

исходного кода.

3. Для вывода локального меню Help нажмите клавиши Alt+F10

или щелкните правой кнопкой "мыши".

4. Скопируйте пример:

* для копирования всего примера выберите команду Copy

Example;

* для копирования части исходного кода подсветите то, что

вы хотите скопировать, и выберите команду Copy.

5. Вернитесь в окно редактирования и выберите команду

EditіPaste, нажмите клавишу Shift+Ins или выберите коман-

ду Paste в локальном меню окна редактирования.

Загрузка других справочных файлов

Интегрированная среда позволяет вам подключать к справочной

системе Help другие справочные файлы. Например, если вы хотите

получать справочную информацию по Turbo Vision, то можете загру-

зить справочный файл Turbo Vision. IDE объединяет тематические

указатели справочных файлов, что позволяет вам получить доступ в

экране Index как к обычной справочной системе Help, так и к спра-

вочной системе Turbo Vision.

Чтобы загрузить новую справочную систему Help, выполните

следующие шаги:

1. Выберите команду HelpіFiles (СправочникіФайлы).

Выводится диалоговое окно установки справочных файлов

Install Help Files.

2. Выберите команду New (Новый).

Выводится диалоговое окно Help Files (Справочные файлы).

Все справочные файлы в вашем каталоге BIN с расширением

.TPH выводится в блоке списка. Если вы не видите справоч-

ных файлов, измените каталог BP\BIN.

3. Дважды щелкните "мышью" на справочном файле, который вы

хотите включить в справочную систему, или выделите его и

нажмите клавишу Enter.

Примечание: Если вы хотите также выбрать второй

справочный файл, повторите этот шаг.

Диалоговое окно Install Help Files появляется вновь, и в

нем выводится справочный файл, который вы выбрали в блоке

списка.

4. Выберите командную кнопку OK.

5. Выберите команду HelpіIndex или нажмите клавиши Shift+F1.

В строке состояния вы можете увидеть краткое сообщение,

показывающее индексирование и слияние. После завершения

слияния вы можете прокрутить справочный экран и увидеть,

что вам доступны все темы в выбранных справочных файлах.

Тематические указатели остаются объединенными в течении

текущего сеанса. Если в диалоговом окне Preferences

(OptionsіEnviromentіPreferences) вы установили параметр

Enviroment Auto Save, то тематические указатели будут ос-

таваться объединенными и в следующих сеансах. Если вы не

сохранили свою операционную среду, то при следующем за-

пуске IDE тематический указатель Help возвращается в ис-

ходное состояние. Другими словами, объединение тематичес-

ких указателей не будет сохраняться от одного сеанса к

другому.

Примечание: О сохранении операционной среды расска-

зывается ниже.

Если вы не хотите видеть на экране тематического указателя

Index конкретные записи Help, то можете "удалить" справочный

файл:

1. Выберите команду HelpіFiles.

2. Выберите имя справочного файла, который вы больше не хо-

тите просматривать.

3. Выберите команду Delete.

4. Выберите командную кнопку OK.

Выход из справочника Help

Чтобы закрыть окно Help и вернуться в свою прикладную прог-

рамму вы можете выбрать один из следующих методов:

* Нажать клавишу Esc.

* Щелкнуть "мышью" на элементе закрытия окна Help.

* Щелкнуть "мышью" вне окна Help.

Если вы хотите вновь вывести предыдущий справочный экран,

нажмите клавиши Alt+F1.

Запись и редактирование исходного кода

Набирайте текст, как это делается в любом редакторе. Для за-

вершения строки нажимайте Enter. Когда вы введете достаточно

строк, чтобы заполнить экран, он будет прокручиваться.

Примечание: В IDE вы можете открыть столько окон,

сколько позволяет память вашей системы.

Полный список команд редактирования вы можете найти в Прило-

жении A ("Справочник по редактору") "Руководства программиста".

Настройка конфигурации редактора

Для модификации поведения редактора Borland Pascal имеется

несколько параметров. Для вывода диалогового окна Editor выберите

команду OptionsіEnviromentіEditor.

Чтобы подробнее узнать о каждом параметре, выделите данный

параметр и нажмите клавишу F1. Справочная система Help поясняет,

что делает данный параметр.

Примечание: О выделении синтаксиса в редакторе расска-

зывается ниже.

Изменение решения: команда Undo

Редактор имеет команду отмены Undo, которая облегчает изме-

нение вашего решения при выполнении каких-либо действий и исправ-

ление ошибки. Чтобы отменить действие предыдущей операции редак-

тирования, выберите команду EditіUndo или нажмите клавиши

Alt+Backspace. Если вы продолжаете выбирать команду Undo, редак-

тор продолжает отменять действия. С помощью команды возобновления

EditіRedo вы можете также отменить саму команду Undo.

Команда Undo вставляет все удаленные вами символы, удаляет

любой вставленный символ, заменяет все перезаписанные символы и

перемещает курсор обратно на предыдущую позицию. Если вы отменяе-

те блочную операцию, то файлы выводится в том виде, какой он имел

перед выполнение блочной операции.

Команда Undo не изменяет установку параметров, влияющих бо-

лее чем на одно окно. Например, если вы используете клавишу Ins

для изменения режима вставки на режим замены, а затем выберите

Undo, редактор не изменит режим обратно не режим вставки. Но если

вы удалили символ, перешли в режим замены, затем выбрали команду

Undo, то удаленные ранее символ выводится вновь.

Групповая отмена

Параметр Group Undo в диалоговом окне OptionsіEnviromentі

Editor влияет на то, как ведет себя команда Undo и соответствую-

щая команда Redo. Если вы выберите параметр Group Undo (Групповая

отмена), при нажатии клавиш Alt+Backspace или EditіUndo, редактор

изменяет последнюю группу команд.

Приведем пример того, как работает групповой параметр. Если

вы наберете, например, MISTAKE и параметр Group Undo установлен,

то Undo удаляет все слово. Если параметр Group Undo не выбран, и

вы набираете MISTAKE, Undo удаляет только последний символ, букву

E. Чтобы отменить слово MISTAKE при выключенном параметре Group

Undo, вам нужно использовать команду Undo семь раз.

Примечание: Группа - это последовательность команд од-

ного типа.

Вставки, удаления, замены и перемещения курсора являются

групповыми операциями. Когда вы меняете тип команды, старая груп-

па завершается и начинается новая. Для редактора вставка возврата

каретки нажатием клавиши Enter - это вставка, за которой следует

перемещение курсора. Поскольку тип редактирования изменился (вы

вставили символы, затем переместили курсор), группа вставки сим-

волов завершается, когда вы нажимаете Enter.

Отмена отмены

Команда EditіRedo изменяет на обратное действие последней

команды Undo. Redo немедленно действует только после команды Undo

или другой команды Redo. Последовательность команд Redo отменяет

действие последовательности команд Undo. Как и в случае команды

Undo, на команду Redo влияет параметр Group Undo.

Работа с блоками текста

Блок текста - это любой объем текста (от одного символа до

сотен строк), который выделен на экране. В каждый момент времени

в окне может быть выделен только один блок.

Выделение блока

Для выделения блока текста существует по крайней мере три

способа:

* Буксируйте "мышь" по тексту, который вы хотите выделить.

* Переместите свой курсор в начало блока текста, нажмите

клавишу Shift и, удерживая ее, переместите курсор к концу

блока с помощью клавиш стрелок.

* Щелкните "мышью" в начале блока, переместите курсор к кон-

цу блока с помощью клавиш стрелок, затем, нажав Shift,

снова щелкните кнопкой "мыши".

Если вы уже работали с редакторами Borland ранее, то можете

использовать команды работы с блоками как обычно. Таблицу команд

редактора Borland для работы с блоками можно найти в Приложении A

("Справочник по редактору") "Руководства программиста".

Вырезание, копирование и вставка блоков

После выбора блока его можно скопировать, переместить или

записать в файл.

* Чтобы вырезать выделенный текст, нажмите клавиши Shift+Del

или выберите команды EditіCut. Выделенный блок удаляется

из текста и помещается в буфер вырезанного изображения

("карман") - временную область памяти.

* Для копирования выделенного текста нажмите клавиши

Ctrl+Ins или выберите команду EditіCopy. Выделенный блок

остается в тексте, а его копия помещается в буфер вырезан-

ного изображения и готова для вставки в другом окне реда-

ктирования.

* Чтобы вставить (скопировать) текст, содержащийся в буфере

вырезанного изображения, в активное окно, нажмите клавиши

Shift+Ins или выберите команду EditіPaste. Блок, содержа-

щийся в буфере, вставляется в текущей позиции курсора.

* Для стирания (удаления) выделенного текста нажмите клавиши

Ctrl+Del или выберите команду EditіClear. Выделенный блок

удаляется из текста, и копия не помещается в буфер выре-

занного изображения. Единственный способ восстановления

удаленного текста состоит в применении команды EditіUndo.

Изменение поведения выделенных блоков

На поведение в редакторе выделенных блоков влияют два пара-

метра - Persistent Blocks и Overwrite Blocks. Их можно найти в

диалоговом окне OptionsіEnviromentіEditor.

* Если включен параметр Persistent Blocks (Постоянные бло-

ки), то выделенные блоки остаются выделенными пока вы их

не удалите или не отмените выделение (или пока не выделите

другой блок).

* Если параметр Persistent Blocks выключен, и вы перемещаете

курсор за выделенный блок, то выделение блока отменяется.

* Если параметр Persistent Blocks выключен, то параметр

Overwrite Blocks setting игнорируется.

* Если включен параметр Overwrite Block (Затирание блока), и

вы набираете букву, то выделенный блок заменяется набран-

ной буквой.

* Если параметр Overwrite Block выключен, и вы набираете

букву, то буква вставляется после выделенного текста.

* Если параметр Overwrite Block включен, а параметр

Persistent Block выключен, и вы нажимаете клавишу Del или

клавишу Backspace, то весь выделенный текста удаляется.

Если вы вставляете текст (нажимая символ или вставляя его из

буфера), то весь выделенный текст заменяется вставленным текстом.

Поиск

Вы можете использовать редактор для поиска строки в исходном

коде. Для поиска строки текста в активном окне редактирования вы-

полните следующие шаги:

1. Выберите команду SearchіFind. При этом открывается диало-

говое окно Find Text (Поиск текста).

2. Наберите искомую строку в блоке ввода Text to Find.

3. Вы можете также задать различные параметры поиска:

* Кнопки с независимой фиксацией Options (Параметры) оп-

ределяют, будет при поиске:

- различаться регистр символов;

- выполняться поиск только полного слова;

- использоваться регулярные выражения (об использовании

регулярных выражений в строках поиска рассказывается

в Приложении A "Справочного руководства программис-

та").

* Кнопки с зависимой фиксацией Scope (Область действия)

управляют тем, в какой части файла выполняется поиск -

во все файле или только в выделенном тексте.

* Кнопки с зависимой фиксацией Direction (Направление)

управляют тем, в каком направлении вы выполняете поиск

- в прямом или в обратном.

* Кнопки с зависимой фиксацией Origin (Начало) управляет

тем, откуда начинается поиск.

4. Для выполнения поиска выберите командную кнопку OK.

5. Если вы хотите выполнить повторный поиск того же элемен-

та, выберите команду SearchіSearch Again (ПоискіПовторный

поиск).

По умолчанию в блоке ввода Text to Find (Искомый текст) вы-

водится слово, на котором позиционируется курсор. Если вы не хо-

тите, чтобы это происходило, сделайте следующее:

1. Выберите команду OptionsіEnviromentіEditor.

2. Отмените установку параметра Find Text at Cursor.

При выводе диалогового окна Find в блоке ввода выводится

слово, на котором находится курсор. Если вы хотите вместо отдель-

ного слова найти предложение или группу слов, нажмите клавишу >

(когда курсор находится в блоке ввода Find Text). В блоке ввода

появляется дополнительный текст, как если бы он "вытягивался" из

окна редактирования.

Поиск и замена

Для поиска строки текста и замены ее другой строкой выберите

команду SearchіReplace (ПоискіЗамена). Выберите в диалоговом окне

параметры, как это делается для команды Search, но включите в

блок Next Text строку замены.

Если вы хотите заменить все вхождения строки в своем файле,

выберите параметр Change All. Если вы выберете параметр Prompt on

Replace, редактор будет выполнять поиск, пока не найдет указанную

строку, затем запросит, хотите ли вы ее заменить. Если вы не ис-

пользуете параметр Prompt on Replace, лучше выбрать параметр

Whole Words Only (Только полные слова). Это позволит избежать

случаев замены символов в середине слова - возможно, вы не хоти-

те, чтобы это происходило.

Соответствие пар ограничителей

Иногда вам не требуется искать текст, а нужно найти соот-

ветствующий парный ограничитель (фигурную или квадратную скобку,

одинарную кавычку, двойную кавычку, двойной знак вопроса или ком-

бинацию скобки и звездочки, обозначающую комментарий). Предполо-

жим у вас есть сложное выражение с множеством вложенных выраже-

ний, и вы хотите убедиться, что не пропущена ни одна скобка. Нуж-

но сделать следующее:

1. Поместите курсор на ограничителе (скобке).

2. Нажмите клавиши Ctrl+Q[.

Редактор немедленно перемещает курсор к ограничителю, соот-

ветствующему выбранному. Если он перемещается на ограничитель,

отличный от ожидаемого вами, то это указывает на ошибку.

Если для выбранного вами ограничителя нет соответствующего

парного, редактор не смещает курсор.

На самом деле есть две команды редактирования пар ограничи-

телей: одна для поиска соответствующей пары в прямом направлении

(Ctrl+Q[), а другая - в обратном (Ctrl+Q]).

Если вы поместите курсор на одинарную или двойную кавычку,

редактор не знает, в каком направлении нужно искать парный огра-

ничитель. В этом случае вы должны задать корректную команду поис-

ка пары.

Для круглых, квадратных и фигурных скобок не имеет значения,

какая команда используется для поиска пары. Редактор знает, в ка-

ком направлении искать соответствующий ограничитель.

Приведем пример, иллюстрирующий соответствие пар:

совпадающая совпадающая совпадающая

пара пара пара

ЪБї ЪДДБДДї ЪДДБДДї

Array1[Array2[x]] ((x > 0) and (y < 0)

АДДДДВДДДДЩ АДДДДДДДДДВДДДДДДДДЩ

совпадающая совпадающая

пара пара

Рис. 4.1 Поиск соответствующей квадратной или круглой скоб-

ки.

Переход к строке с заданным номером

Редактор отслеживает, на какой строке находится курсор в

строке состояния окна. Быстрый способ перехода в определенное

место в файле состоит в использовании команды Go to Line Number

(Переход на строку с номером):

1. Выберите команду SearchіGo to Line Number.

2. Наберите номер строки, на которую вы хотите перейти.

3. Выберите командную кнопку OK.

Использование локального меню окна редактирования

Многие из функций, выполняемых вами при работе в окне редак-

тирования, удобно расположены в локальном меню окна редактирова-

ния. Когда окно редактирования активно, вы можете вывести локаль-

ное меню двумя способами:

* Нажать клавиши Alt+F10.

* Щелкнуть правой кнопкой "мыши".

Интегрированная среда IDE содержит также другие локальные

меню. Прочитав об использовании справочника Help, отладке и прос-

мотре, вы узнаете, где находятся остальные локальные меню.

Выделение синтаксиса

Когда вы пишете или редактируете программу на Паскале, неко-

торые части исходного кода выводятся на экран разными цветами.

Например, зарезервированные слова Borland Pascal выводятся белым

цветом, а остальной текст - желтым. Такое цветовое выделение ис-

ходного кода облегчает быструю идентификацию частей кода.

Цвета текста

Чтобы изменить цвет элемента, выполните следующие шаги:

1. Выберите команду OptionsіEnviromentіColors. Выводится ди-

алоговое окно Colors (Цвета).

ЙН[ ]НННННННННННННННННННННННColorsННННННННННННННННННННННННН»

є є

є Group Item є

є Compiler ^ Whitespace ^ ЪДForegroundДДДДДї є

є Desktop ± Comments ± і±±±±ІІІІ±±±± і є

є Dialogs ± Reserved words ± іІІІІЫЫЫЫ °°°°і є

є Help ± Identifiers ± іЫЫЫЫ °°°°ЫЫЫЫі є

є Menus ± Symbols ± АДДДДДДДДДДДДДДДДЩ є

є Messages ± Strings ± ЪДBackgroundДДДДДї є

є Output ± Members ± і±±±±ІІІІ±±±± і є

є Register Ы Assembler ± іІІІІЫЫЫЫ °°°°і є

є ІSyntaxІІІІІІІ± ± іЫЫЫЫ °°°° і є

є Watches ± ± АДДДДДДДДДДДДДДДДЩ є

є ± Ы Text°Text°Text° є

є v v Text°Text°Text° є

є є

є ЫЫЫЫЫOKЫЫЫЫ ЫЫЫCancelЫЫЫ ЫЫЫHelpЫЫЫЫ є

є ±±±±±±±±±±± ±±±±±±±±±±±± ±±±±±±±±±±± є

є є

ИННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

Рис. 4.2 Диалоговое окно Colors.

Colors - цвета; Group - группа; Compiler - компилятор;

Desktop - оперативная область; Dialogs - диалоги; Help -

справочник; Menus - меню; Messages - сообщения; Output -

вывод; Register - регистр; Syntax - синтаксис; Watches -

выражения просмотра; Item - элемент; Whitespace - пробел;

Comments - комментарий; Reserved words - зарезервирован-

ные слова; Identifiers - идентификаторы; Symbols - имена;

Strings - строки; Members - элементы (члены); Assembler -

ассемблер; Foreground - основной цвет; Background - фоно-

вый цвет.

В блоке списка слева выводятся все группы элементов, ко-

торые вы можете выделять цветом в IDE.

2. Прокрутите блок списка Group, пока не увидите группу

Syntax (Синтаксис). Выберите группу Syntax и в блоке

списка Item выведутся элементы кода Паскаля, которые вы

можете выделять цветом.

3. Выделите элемент, который вы хотите изменять в блоке

списка Item.

4. Выберите основной и фоновый цвет, который вы хотите наз-

начить для элемента.

* Чтобы выбрать фоновый цвет с помощью "мыши", щелкните

ее кнопкой на нужном цвете матрицы цветов Foreground.

Чтобы выбрать цвет с помощью клавиатуры, нажимайте кла-

вишу Tab, пока не будет выбрана матрица фонового цвета,

затем для выделения цвета используйте клавиши стрелок.

* Чтобы выбрать фоновый цвет, выберите нужный цвет в мат-

рице цветов Background.

Как только вы сделаете выбор цвета, он будет отражен в

примере текстового окна.

5. Выберите командную кнопку OK.

Выбор файлов для выделения

По умолчанию выделение синтаксиса происходит только в файлах

с расширениями .PAS и .INC. Вы можете использовать выделение син-

таксиса в файлах другого типа.

Чтобы изменить тип файлов, выводимых с выделением синтакси-

са, сделайте следующее:

1. Выберите команду OptionsіEnviromentіEditor.

2. Измените текст в блоке Highlight Extensions.

Допускается любое разрешенное имя файла DOS, включая тра-

фаретные символы. Вы можете задать несколько имен файлов.

В этом случае их нужно разделить двоеточиями.

Запрещение выделения синтаксиса

Если вы не хотите использовать выделение синтаксиса, то мо-

жете выключить его:

1. Выберите команду OptionsіEnviromentіEditor.

2. Отмените выбор параметра Syntax Highlight (Выделение син-

таксиса).

Цвет обычного текста изменяется модификацией параметра

EditorіNormal Text в диалоговом окне OptionsіEnviromentіCoplors.

Если вы не выключите подсветку синтаксиса, изменение цвета обыч-

ного текста не действует.

Печать исходного кода

Если вы хотите получить печатную копию своего исходного ко-

да, выберите команду FileіPrint. IDE расширяет символы табуляции

(заменяя табуляцию соответствующим числом пробелов и затем печа-

тает ваш файл.

Выделение элементов синтаксиса при печати

Вы можете напечатать текст таким образом, чтобы синтаксичес-

кие элементы были выделены. Перед печатью вы должны пропустить

выводимый на принтер текст черед программу-фильтр PRNFLTR.EXE:

1. Выберите команду FileіPrinter Setup.

2. Если программа PRNFLTR.EXE не находится по вашему маршру-

ту или в текущем каталоге, добавьте информацию о текущем

маршруте в запись PRNFLTR в диалоговом окне Filter Path.

3. В блоке ввода Command Line вы можете указать принтер

Epsor, HP LaserJet или PostScript.

* Если вы используете принтер Epson, введите:

$NOSWAP /EPSON

* Если вы используете принтер HP LaserJet, введите:

$NOSWAP /HP

* Если вы используете принтер PostScript, введите:

$NOSWAP /PS

Если у вас другой тип принтера, то вы можете модифициро-

вать файл PRNFLTR.PAS, чтобы воспринимались соответству-

ющие коды.

4. Установите параметр Send Highllighting Escape Codes.

5. Выберите командную кнопку OK.

6. Выберите команду FileіPrint.

Если параметр Syntax Highlight установлен, ваш текст пе-

чатается с выделением синтаксических элементов.

Работа с файлами

При программировании в IDE вы можете создавать новые файлы,

открывать существующие файлы и сохранять их. Основные команды ра-

боты с файлами перечислены в следующей таблице:

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДї

і Команда і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіNew і Открывает новое окно редактиро-і

і і вания и присваивает ему временноеі

і і имя. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіOpen і Выводит диалоговое окно, с по-і

і і мощью которого можно открытьі

і і файл. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіSave і Сохраняет файл в активном окнеі

і і редактора на диске. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіSave As і Сохраняет файл в активном окнеі

і і редактора под другим именем. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіSave All і Сохраняет все модифицированныеі

і і файлы. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДЩ

Открытие файлов

Для открытия файла выполните следующие шаги:

1. Выберите команду FileіOpen. Выводится диалоговое окно

Open a File (Открытие файла). Для задания открываемого

файла вы можете выполнить одно из следующих действий.

* В блоке ввода наберите полное имя файла.

* Наберите имя файла с трафаретными символами. Это от-

фильтровывает список файлов в соответствии с вашими

спецификациями. В списке Files выберите имя файла, ко-

торый вы хотите редактировать.

* Для вывода списка протокола (спецификаций имен файлов,

которые вы задавали ранее), нажмите стрелку вниз. Выбе-

рите требуемое имя файла или спецификацию. Выбор специ-

фикации файла выводит файлы, соответствующие данной

спецификации.

* Дважды щелкнув "мышью" на имени другого каталога в

списке файлов, просмотрите содержимое этого каталога.

Выберите имя файла, который вы хотите редактировать.

2. После того как имя файла, который вы хотите редактиро-

вать, будет выделено в блоке ввода, выберите команду Open

(Открыть) или Replace (Заменить). Команда Open загружает

файл в новое окно редактирования; команда Replace заменя-

ет содержимое активного окна редактирования выбранным

файлом.

После выделения имени файла вы можете просто нажать клавишу

Enter или, когда увидите имя нужного файла в списке, дважды щелк-

нуть на нем кнопкой "мыши". Файл будет открыт.

Если вы откроете один или более файлов, а затем закроете их,

то увидите их список в нижней части меню File (до пяти файлов).

Если вы выберите в меню один из этих пяти файлов, то файл откры-

вается в окне редактирования. Когда вы работаете с несколькими

открытыми файлами, то можете закрыть некоторые из них, при необ-

ходимости быстро открывая их снова с помощью списка. Это уменьшит

перегруженность вашей оперативной области.

Вы можете также изменить используемый по умолчанию каталог

на тот, в котором находится открываемый вами файл. Это изменяет

поведение, заданное переменными операционной среды Windows:

1. Выберите команду OptionsіEnviromentіPreferences.

2. Установите параметр Change Dir (Смена каталога).

3. Выберите командную кнопку OK.

Открытие файла в позиции курсора

IDE дает вам быстрый способ открытия файла, имя которого на-

ходится в вашем исходном коде. Вы найдете это удобным, когда пот-

ребуется просмотреть код модуля или включаемого файла, используе-

мого в вашей программе.

1. Поместите курсор на имя файла, который вы хотите открыть.

2. Нажмите клавиши Ctrl+Enter или выведите локальное меню

окна редактирования и выберите команду Open File at

Cursor (Открытие файла в позиции курсора).

Компиляция и выполнение

IDE предоставляет вам несколько способов создания выполняе-

мой программы, модуля или (если вы используете защищенный режим)

динамически компонуемую библиотеку. Вы можете:

* Скомпилировать текущий файл с помощью команды (Compileі

Compile).

* Скомпилировать все измененные файлы (CompileіMake).

* Скомпилировать все файлы проекта (CompileіBuild).

* Скомпилировать и выполнить программу (RunіRun).

Каждая из этих возможностей подходит для конкретной ситуа-

ции. Следующие разделы помогут вам решить, какую возможность луч-

ше использовать.

Выбор целевой платформы

Если вы используете BP.EXE, то перед компиляцией своей прог-

раммы вам нужно сообщить IDE, какой вид прикладной программы вы

создаете: программу DOS реального режима, программу Windows или

программу DOS защищенного режима. Тип создаваемой прикладной

программы называется целевой платформой.

Примечание: TURBO.EXE может создавать приложение ре-

ального режима.

Для выбора целевой платформы сделайте следующее:

1. Выберите команду CompileіTarget.

2. В диалоговом окне Target выберите нужную целевую платфор-

му.

3. Выберите командную кнопку OK.

При компиляции модуля расширения имени файла полученного в

результате модуля в зависимости от целевой платформы будут разли-

чаться:

ЪДДВДДДДДДї

і Целевая платформа і Расширение имени файла модуля і

і і объектного кода і

ГДДЕДДДДДДґ

і Реальный режим DOS і .TPU і

і і і

і Windows і .TPW і

і і і

і Защищенный режим DOS і .TPP і

АДДБДДДДДДЩ

Компиляция

Команда CompileіCompile компилирует только файл в активном

окне редактирования. При компиляции программы выводится окно сос-

тояния, в котором сообщается о ходе и результатах компиляции.

Когда компиляция и компоновка будет выполнена, нажмите любую кла-

вишу. Окно состояния исчезнет. Если имеется ошибка, в верхней

части окна редактирования вы увидите сообщение об ошибке, а кур-

сор будет позиционирован на ту строку кода, где имеется ошибка.

Выбор места назначения

Если вы используете IDE защищенного режима, то с помощью ко-

манды CompileіDestination можете выбрать компиляцию программы на

диск или в память. Если вы выберете компиляцию на диск, ваш вы-

полняемый код сохраняется на диске в виде файла .EXE. Компиляция

на диск увеличивает объем памяти, доступной в IDE для компиляции

и отладки вашей программ. При выборе компиляции в память ваша

программа будет записываться в память, и, если вы ее не сохрани-

те, будет потеряна при выходе из IDE.

При компиляции на диск полученные в результате файлы .EXE

или .TPU сохраняются в том же каталоге, что и исходные файлы, или

в каталоге EXE and TPU (OptionsіDirectories), если он задан.

Формирование (Make)

Если ваша программа включает в себя не только исходный код в

активном окне, например, основной файл, один или более модулей,

внешние модули на языке ассемблера и т.д., то вы можете сформиро-

вать свою программу. При формировании компилируется весь исходный

код, который был модифицирован с момента последней компиляции.

Команда CompileіMake создает файл .EXE или модуль. Если вы

в качестве целевой платформы используете IDE защищенного режима и

Windows, она может также создавать динамически компонуемую библи-

отеку (DLL).

Примечание: О создании DLL рассказывается в Главе 11

"Библиотеки динамической компоновки" "Руководства по язы-

ку".

Команда Make (Формирование) использует следующие правила:

* Если задан основной файл, то он компилируется. В противном

случае компилируется файл в активном окне редактирования.

Перед компиляцией IDE проверяет все файлы, чтобы убедить-

ся, что они существуют и являются текущими.

Примечание: Более подробно об основных файла расска-

зывается ниже.

* Если исходный файл для данного модуля модифицирован с мо-

мента создания файла .TPU, .TPW или .TPP (объектный код),

то этот модуль перекомпилируется.

* Если изменяется интерфейс для данного модуля, перекомпили-

руются все другие зависящие от него модули.

Примечание: О модулях подробнее рассказывается в Гла-

ве 7 "Модули Borland Pascal".

* Если модуль компонуется с файлом .OBJ (внешние подпрограм-

мы) и файл .OBJ является более новым, чем модули .TPU,

.TPW или .TPP, то модуль перекомпилируется.

* Если модуль содержит включаемый файл, и включаемый файл

более новый, чем модули .TPU, .TPW или .TPP, то модуль пе-

рекомпилируется.

Если компилятор не может найти исходный код модуля, то мо-

дуль не компилируется и используется как есть.

Построение (Build)

Команда CompileіBuild (КомпиляцияіПостроение) перестраивает

все компоненты вашей программы независимо от того, являются они

текущими или нет.

Эта команда аналогична команде CompileіMake, только она вы-

полняет полную перекомпиляцию, даже если файл не изменялся. Если

вы остановите команду Build, нажав клавиши Ctrl+Break, или полу-

чите ошибки, которые прекращают построение, то, выбрав Compileі

Make, вы можете определить, где это произошло.

Если в IDE реального режима DOS вы выбрали компиляцию в па-

мять, то все файлы .TPU обновляются на диске (для всех перекомпи-

лируемых командой Build модулей).

Выполнение

После создания выполняемого файла вы можете попробовать, как

она работает. Для этого можно использовать команду RunіRun. На

самом деле вам не нужно предварительно компилировать свою прог-

рамму. Если ваш код изменился с момента последней компиляции, ко-

манда Run автоматически формирует вашу программу и затем выполня-

ет ее.

Если ваша программа представляет собой прикладную программу

DOS защищенного режима, то в текущем каталоге или по маршруту DOS

у вас должны находиться файлы DPMI16BL.OVL и RTM.EXE, в противном

случае программа выполняться не будет. Вы можете свободно расп-

ространять эти файлы с готовой прикладной программой.

Передача программе параметров

При запуске программы вы можете передать ей параметры ко-

мандной строки. Для вывода диалогового окна Parameters (Парамет-

ры) и набора списка параметров, которые вы хотите использовать,

выберите команду RunіParamenters.

Параметры компилятора и компоновщика

IDE позволяет вам выбрать несколько параметров, влияющих на

характер компиляции кода. Для вывода диалогового окна параметров

компилятора Compiler Options выберите команду OptionsіCompiler.

Если вы не уверены относительно того, что делает конкретный пара-

метр, выберите его в диалоговом окне, в строке состояния появится

поясняющая справка. Для вывода более подробной информации о дан-

ном параметре нажмите клавишу F1 или выберите команду Help для

получения справочной информации обо всем диалоговом окне Compiler

Options.

ЙН[ ]ННННННННННННННННННННННCompiler OptionsННННННННННННННННННННН»

є Compiler settings for: ±±Real mode target±±±±±±±±±±v±є

єДДДДДДДДДДДДДє

є Code generation є

є ±[±]±Force±far±calls±±±±±±±±±[±]±286±instructions±±±±±±±±± є

є ±[±]±Overlays±allowed±±±±±±±±[±]±Smart±callbacks±±±±±±±±±± є

є ±[X]±Word±align±data±±±±±±±±±[±]±Windows±stack±frames±±±±± є

є Runtime errors Syntax Options є

є ±[±]±Range±checking±±±±± ±[X]±Strict±var-strings±±±±±±± є

є ±[X]±Stack±checking±±±±± ±[±]±Complete±boolean±eval±±±± є

є ±[X]±I/O±checking±±±±±±± ±[X]±Extended±syntax±±±±±±±±±± є

є ±[ ]±Overflow±checking±± ±[±]±Typed±@±operator±±±±±±±±± є

є Debugging ±[±]±Open±parameters±±±±±±±±±± є

є ±[X]±Debug±Information±± Numeric processing є

є ±[X]±Local±symbols±±±±±± ±[±]±8087/80287±±±±±±±±±±±±±±± є

є ±[X]±Symbol±information± ±[X]±Emulation±±±±±±±±±±±±±±±± є

є є

є Conditional defines є

є ЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫvЫ є

є є

є ЫЫЫЫЫOKЫЫЫЫ ЫЫЫCancelЫЫЫ ЫЫЫHelpЫЫЫЫ є

є ±±±±±±±±±±± ±±±±±±±±±±±± ±±±±±±±±±±± є

є є

ИНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

Рис. 4.3 Диалоговое окно Compiler Options.

Compiler settings for - установки компилятора для; Real mode

target - целевая платформа реального режима; Code generation -

генерация кода; Force far calls - принудительное использование

вызовов дальнего типа; 286 instructions - инструкции процессора

286; Overlays allowed - допустимость оверлеев; Smart callbacks -

эффективные вызовы; Word align data - выравнивание данных на гра-

ницу слова; Windows stack frames кадры стека Windows; Runtime

errors - ошибки этапа выполнения; Syntax Options - синтаксические

ошибки; Range checking - проверка диапазона; Strict var-strings -

строгая проверка строковых переменных; Stack checking - проверка

стека; Complete boolean eval - полное вычисление булевских выра-

жений; I/O checking - проверка ввода-вывода; Extended syntax -

расширенный синтаксис; Overflow checking - проверка переполнения

стека; Typed @ operator - типизованная операция @; Debugging -

отладка; Open parameters - открытые параметры; Debug Information

- отладочная информация; Numeric processing - числовая обработка;

Local symbols - локальные идентификаторы; 8087/80287 - процессоры

8087/80287; Symbol information - локальная информация; Emulation

- эмуляция; Conditional defines - условные определения.

Задание параметров

компилятора для целевой платформы

Выбираемые вами параметры компилятора в сильной степени за-

висят от того, какую целевую платформу вы используете. Например,

если вы создаете приложение DOS реального режима, то можете раз-

решить использование оверлеев, что не требуется для программ

Windows или защищенного режима DOS.

Для указанной целевой платформы вы можете установить стан-

дартные параметры компилятора:

Примечание: Меню Compiler Options компилятора

TURBO.EXE не имеет параметра Compiler Setting For.

1. Для вывода прокручиваемого списка щелкните "мышью" на

стрелке v или нажмите клавишу стрелки вниз при выборе

блока списка Compiler Setting For.

2. Проверьте выбранную целевую платформу.

IDE автоматически устанавливает обычно используемые парамет-

ры. В любой момент вы можете переопределить эти выбранные в IDE

параметры.

Если параметр компилятора несовместим с выбранной целевой

платформой, то он становится тусклым, и вы не можете его выби-

рать.

Установка параметров

компилятора для всех целевых платформ

Если вы хотите чтобы конкретный параметр компилятора был ус-

тановлен независимо от используемой целевой платформы, сделайте

следующее:

1. В качестве значения параметра Compiler Settings For выбе-

рите All Targets.

2. Установите или отмените параметр компилятора, который вы

хотите установить.

Например, если вы хотите использовать инструкции процессора

286 для всех платформ, выберите All Targets (Все платформы), за-

тем установите параметр 286 instructions.

Когда вы выбираете All Targets, то можете увидеть, что в не-

которых кнопках с независимой фиксацией выводится вопросительный

знак (?). Это указывает, то установка параметра для всех платформ

не является одинаковой. Вы можете изменить этот параметр, сделав

его одинаковым для всех платформ, или оставить как есть.

Задание параметров компоновщика

Характер компоновки вашего кода зависит от установок в диа-

логовом окне Linker Options (Параметры компоновщика). Для его вы-

вода выберите команду OptionsіLinker. Если нужна более детальная

информация, выберите Help.

Включение в код директив компилятора

Существует еще один способ задания режима компиляции исход-

ного кода. Вместо использования для установки параметров диалого-

вых окон вы можете включить в свой код директивы компилятора.

Например, вы можете включить в свою программу проверку диапазона,

установив в диалоговом окне OptionsіCompiler параметр Range

Checking, или поместить в исходный код директиву {$R+}. Полное

описание директив компилятора и их использования вы можете найти

в Главе 2 ("Директивы компилятора") "Справочного руководства

программиста".

Включаемые в исходный код директивы компилятора имеют боль-

ший приоритет, чем параметры компилятора, устанавливаемые в IDE.

Например, если в IDE вы установите параметр Range Checking, но

ваша программа включает в себя директиву {R-}, то программа ком-

пилируется с выключенной проверкой диапазона.

Оптимизация кода

Некоторые параметры компилятора, поскольку они включают в

программу код проверки и обработки ошибок, влияют как на размер,

так и на скорость вашего кода. Хотя такие параметры полезно ис-

пользовать при разработке программы, без них вы можете получить

более быстрый и компактный код.

Приведем параметры, влияющие на оптимизацию кода. Каждая ди-

ректива компилятора сопровождается указанием соответствующего па-

раметра компилятора. Рассмотрим использование для завершающей

компиляции следующих параметров:

* Выравнивание данных на границу слова Word Align Data

({$A+}) выравнивает переменные и типизированные константы

на границу слова, что дает в системах с процессорами 80x86

более быстрый доступ к памяти.

* Выключение полного вычисления булевских выражений Complete

Boolean Evaluation ({$B-}) дает код, который в зависимости

от установки ваших булевских выражений более быстро рабо-

тает.

* При выключенной эмуляции Emulation ({$E-}) компилятор не

будет выполнять компоновку с библиотекой исполняющей сис-

темы, эмулирующей сопроцессор 80x87. Она должна использо-

вать сопроцессор 80х87 (в случае его наличия) или стан-

дартный 6-байтовый тип Real. При компиляции приложений

Windows эмуляция не используется; при наличии директивы

эмуляции компилятор ее игнорирует.

* Когда задана генерация кода процессора 80286 Code

Generation ({$G+}), компилятор для улучшения генерации ко-

да использует дополнительные инструкции процессора 80286.

Скомпилированные таким образом программы не будут работать

на процессорах 8088 и 8086.

* При выключенной проверке ввода-вывода I/O Checking ({$I-})

компилятор не проверяет ошибки ввода-вывода. Вызвав пре-

допределенную функцию IOResult, вы можете самостоятельно

проверить ошибки ввода-вывода.

* Когда выключена числовая обработка Numeric Processing

({$N-}), компилятор генерирует код, способный выполнять

все операции с плавающей точкой с помощью встроенного

6-байтового типа Real. Если параметр Numeric Processing

включен, ({$N+}), компилятор использует сопроцессор 80х87

или эмулирует сопроцессор с помощью программного обеспе-

чения, в зависимости от наличия сопроцессора 80х87. Полу-

ченный в результате код может использовать четыре дополни-

тельных вещественных типа (Single, Double, Extended и

Comp).

* Когда выключена проверка диапазона Stack Checking ({$R-}),

компилятор не выполняет проверку на ошибки индексирования

массива и присваивание значения вне диапазона.

* При выключенной проверке стека Stack Checking ({$S-}) ком-

пилятор не обеспечивает достаточного пространства в стеке

для каждого вызова процедуры или функции.

* Когда включена нестрогая проверка строк-переменных Relaxed

String Var Checking ($V-}), компилятор не проверяет пара-

метры-переменные строкового типа. Это позволяет вам пере-

давать строки фактических параметров, имеющие длину, от-

личную от длины, определенной для формального парамет-

ра-переменной.

* При разрешении расширенного синтаксиса Extended Syntax

({$X+}) вы можете использовать вызовы функций как операто-

ры, поскольку результат функциональных вызовов может отб-

расываться.

Оптимизация вашего кода с помощью этих параметров имеет два

преимущества. Во-первых, это делает ваш код более компактным и

быстрым. Во-вторых, позволяет делать вам некоторые вещи, которые

вы обычно делать не можете. Однако, все эти параметры вносят не-

который риск, поэтому используйте их аккуратно, и если ваша прог-

рамма начинает вести себя странно, вернитесь к исходной ситуации.

Условная компиляция

Чтобы облегчить ваши задачи, Borland Pascal предлагает

средство условной компиляции. Это означает, что можете компили-

ровать части своей программы на основе параметров или определен-

ных идентификаторов.

Условные директивы аналогичны по формату директивам компиля-

тора, с которыми вы уже познакомились. Они имеют следующий фор-

мат:

{$директива аргумент}

где "директива" - это такая директива как DEFINE, IFDEF и т.д., а

аргумент - необязательный аргумент. Между ними обязательно должен

присутствовать разделитель (пробел или табуляция). Все условные

директивы и их смысл приведены в Таблице 4.3.

Примечание: Полное описание директив условной компиля-

ции можно найти в Главе 2 ("Директивы компилятора") "Спра-

вочного руководства программиста".

Директива условной компиляции Таблица 4.3

ЪДДДДДВДДДДДДДї

і Директива і Описание і

ГДДДДДЕДДДДДДДґ

і {$DEFINE идентификатор} і Определяет "идентификатор" для і

і і других директив. і

ГДДДДДЕДДДДДДДґ

і {$UNDEF идентификатор} і Отменяет определение "иденти- і

і і фикатора". і

ГДДДДДЕДДДДДДДґ

і {$IFDEF идентификатор} і Компилирует следующий код, ес- і

і і ли определен "идентификатор". і

ГДДДДДЕДДДДДДДґ

і {$IFNDEF идентификатор} і Компилирует следующий код, ес- і

і і ли "идентификатор" не опреде- і

і і лен. і

ГДДДДДЕДДДДДДДґ

і {$IFOPT x+} і Компилирует следующий код, ес- і

і і ли разрешена директива x. і

ГДДДДДЕДДДДДДДґ

і {$IFOPT x-} і Компилирует следующий код, ес- і

і і ли запрещена директива x. і

ГДДДДДЕДДДДДДДґ

і {ELSE} і Компилирует следующий код, ес- і

і і ли предыдущее выражение IFxxx і

і і не равно True. і

ГДДДДДЕДДДДДДДґ

і {$ENDIF} і Отмечает конец блока IFxxx или і

і і ELSE. і

АДДДДДБДДДДДДДЩ

Директивы DEFINE и UNDEF

Директивы IFDEF и IFNDEF проверяют, определен ли заданный

идентификатор. Эти идентификаторы определяются с помощью DEFINE и

UNDEF. (Можно также определять идентификаторы в командной строке

или в IDE.)

Чтобы определить идентификатор, включите в программу следую-

щую директиву:

{$DEFINE идентификатор}

где "идентификатор" в плане длины, разрешенных символов и других

спецификаций подчиняется обычным правилам для имен идентификато-

ров. Например, вы можете записать:

{$DEFINE debug}

Этим для остальной части компилируемого модуля или до следу-

ющего оператора:

{$UNDEF debug}

определяется идентификатор debug.

Директива UNDEF "разопределяет" идентификатор. Если иденти-

фикатор не определен, то она не действует.

Определение условных идентификаторов в IDE

Вместо того, чтобы вставлять директиву DEFINE в свой исход-

ный код, вы можете также определить условные идентификаторы и

блоке ввода Conditional Defines (Условные определения). Определи-

те идентификаторы, введя их в блоке ввода и разделив точками за-

пятой. Например, в следующем примере определяются два условных

идентификатора - TestCode и DebugCode:

TestCode:DebugCode

Предопределенные идентификаторы

Кроме определяемых вами идентификаторов вы можете также про-

верять отдельные идентификаторы, определяемые компилятором:

Предопределенные условные идентификаторы Таблица 4.4

ЪДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДї

і Идентификатор і Показывает і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і CPU86 і Данная версия Borland Pascal предназначенаі

і і для семейства процессоров 80х86. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і CPU87 і Присутствует арифметический сопроцессорі

і і 80х87. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і DPMI і Данная версия предназначена для операцион-і

і і ной среды защищенного режима DOS. При соз-і

і і дании приложений для защищенного режимаі

і і DOS данный идентификатор доступен дляі

і і BP.EXE. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і MSDOS і Данная версия предназначена для операци-і

і і онной системы MS-DOS. Данный идентификаторі

і і доступен в BP.EXE только при создании при-і

і і ложений DOS реального или защищенного ре-і

і і жима и в TURBO.EXE. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і VER70 і Данная версия является версией 7.0 компи-і

і і лятора. і

ГДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДґ

і WINDOWS і Данная версия предназначена для операци-і

і і онной среды Windows. Данный идентификаторі

і і доступен в BP.EXE и в BPW.EXE, когда целе-і

і і вой платформой является Windows. і

АДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДЩ

Подробнее о предопределенных условных идентификаторах компи-

лятора рассказывается в Главе 2 ("Директивы компилятора") "Руко-

водства программиста".

Идентификаторы IFxxx, ELSE и ENDIF

Смысл условных директив в том, что если определен или нет

конкретный идентификатор либо если установлен или нет конкретный

параметр, вы можете выбирать для компиляции некоторые части ис-

ходного кода. Они имеют следующий общий формат, где IFxxx - это

директивы IFDEF, INDEF или IFOPT, за которыми следует соответс-

твующий аргумент, а "исходный код" - любое количество исходного

кода Паскаля.

{$IFxxx}

исходный код

{$ENDIF}

Если выражение в директиве IFxxx принимает значение True, то

"исходный код" компилируется; в противном случае он игнорируется

и интерпретируется как обычный комментарий в программе.

Часто у вас имеются альтернативные фрагменты кода. Если вы-

ражение принимает значение True, то компилируется один фрагмент

кода, а если False - другой. Компилятор позволяет сделать это с

помощью директивы $ELSE:

{$IFxxx}

исходный код A

{$ELSE}

исходный код B

{$ENDIF}

Если выражение в IFxxx равно True, то компилируется "исход-

ный код A", в противном случае компилируется "исходный код B".

Все директивы IFxxx должны завершаться с одном исходном фай-

ле. Это означает, что они не могут начинаться в одном исходном

файле и заканчиваться в другом. Однако, в директиве IFxxx может

указываться включаемый файл:

{$IFxxx}

{$I file1.pas}

{$ELSE}

{$I file2.pas}

{$ENDIF}

Таким образом, на основе некоторого условия вы можете выби-

рать альтернативные включаемые файлы

Допускается использовать вложенные конструкции IFxxx..ENDIF,

так что вы можете записать, например, следующее:

{$IFxxx} { первая директива IF }

.

.

.

{$IFxxx} { первая директива IF }

.

.

.

{$ENDIF} { завершает вторую директиву IF }

.

.

.

{$ENDIF} { завершает первую директиву IF }

Директивы IFDEF и IFNDEF

Директивы IFDEF и IFNDEF позволяют вам условно компилировать

код на основе определения или неопределения некоторых идентифика-

торов.

Директивы IFDEF и IFNDEF обычно используются для включения в

компилируемый код отладочной информации. Например, если вы помес-

тите в начало каждого модуля следующий код:

{$IFDEF debug}

{$D+,L+}

{$ELSE}

{$D-,L-}

{$ENDIF}

а в начало программы следующую директиву:

{$DEFINE debug}

и компилируете свою программу, для использования с Турбо отлад-

чиком генерируется полная отладочная информация. Аналогично, вы

можете иметь фрагменты кода, компилируемые только при отладке. В

этом случае можно записать:

{$IFDEF debug}

исходный код

{$ENDIF}

где "исходный код" компилируется только в том случае, если в дан-

ной точке определен идентификатор debug.

Директива IFOPT

Иногда включить или исключить код желательно в зависимости

от того, какой выбран параметр компилятора (проверка диапазона,

проверка ввода-вывода и т.д.). Вы можете сделать это с помощью

директивы IFOPT, которая имеет две формы:

{$IFOPT x+}

и

{$IFOPT x-}

где x - один из параметров компилятора. При использовании первой

формы содержащийся ниже код будет компилироваться, если параметр

компилятора в данный момент разрешен; при использовании второй

формы код компилируется при запрещении параметра. Например, чтобы

выбрать тип данных для списка переменных на основе того, разреше-

на или нет поддержка сопроцессора 80х87, можно использовать сле-

дующий исходный код:

var

{$IFOPT N+}

Radius,Circ,Area: Double;

{$ELSE}

Radius,Circ,Area: Real;

{$ENDIF}

Примечание: Полное описание всех параметров компилято-

ра вы можете найти в Главе 2 ("Директивы компилятора")

"Справочного руководства программиста".

Просмотр исходного кода

IDE защищенного режима DOS содержит новое программное инс-

трументальное средство просмотра объектов - ObjectBrowser. Оно

позволяет вам исследовать программы и модули в программах и мно-

гое другое. Даже если разрабатываемое вами приложение не исполь-

зует объектно-ориентированное программирование, вы найдете

ObjectBrowser чрезвычайно полезным средством. Вы можете просмат-

ривать иерархию объектов, модулей и всех процедур, функций, пере-

менных, типов, констант и другие используемые в программе иденти-

фикаторы. С помощью ObjectBrowser вы можете делать следующее:

* Просмотреть в своей прикладной программе иерархию объек-

тов. Затем выбрать объект и просмотреть все его процедуры,

функции и другие содержащиеся в программе идентификаторы.

При проверке идентификатора вы можете вывести перечень

всех ссылок на него в процедурах, функциях программы и,

если хотите, перейти на то место в исходном коде, где он

используется.

* Вывести список всех глобальных идентификаторов, используе-

мые в вашей программе, и увидеть их описания. Если вы вы-

берите одну переменную, то можете вывести список всех ссы-

лок не нее в своей программе и, если хотите, перейти на то

место в исходном коде, где она используется.

* Вывести список всех используемых в программе модулей, за-

тем выбрать один из них и просмотреть список всех иденти-

фикаторов его интерфейсной части.

* Выбрать идентификатор в исходном коде, затем просмотреть

детальную информацию по нему, нажав клавишу Ctrl и однов-

ременно щелкнув правой кнопкой "мыши".

* Открыть множество окон просмотра, сравнить идентификаторы,

выводимые в различных окнах, а затем вернуться в предыду-

щее открытое в средстве просмотра окно.

Перед использованием ObjectBrowser убедитесь, что в диалого-

вом окне OptionsіCompiler установлены следующие параметры:

* Debug Information (Информация для отладки).

* Locals Symbols (Локальные идентификаторы).

* Symbol Information (Информация об идентификаторах).

Убедитесь также, что в диалоговом окне Debugging/Browsing

(OptionsіDebugger) установлен параметр Integrated Debugging/

Browsing (Отладка с использование встроенного отладчика/Прос-

мотр).

Скомпилируйте программу, которую вы хотите просматривать.

Для активизации ObjectBrowser выберите в меню Search (Поиск)

команду Objects (Объекты), Units (Модули) или Globals (Глобальные

идентификаторы). Вы можете также поместить курсор на идентифика-

тор в исходном коде и выбрать для вывода ObjectBrowser команду

SearchіSymbol (ПоискіИдентификатор).

Вы можете также сделать так, чтобы компилятор "запоминал"

информацию об идентификаторах между компиляциями. Если этот пара-

метр включен, и вы измените программу, но следующая компиляция

завершиться неудачно, то вам все равно будет доступна информация

об идентификаторах, сохраненная с последней компиляции. Благодаря

этому вы сможете просмотреть свою программу, что поможет вам оп-

ределить источник проблемы. Чтобы компилятор сохранял информацию

об идентификаторах между компиляциями, нужно сделать следующее:

1. Выбрать команду OptionsіEnviromentіStartup.

2. Установить параметр Preserve Symbols (Сохранение иденти-

фикаторов); по умолчанию он установлен.

3. Выбрать командную кнопку OK.

4. Для выхода из IDE выбрать команду FileіExit.

5. Снова запустить IDE.

Примечание: Поскольку Preserve Symbols - это параметр

запуска, изменение его установки не будет иметь действие,

пока вы не выйдите из IDE и не запустите ее снова.

Если у вас есть "мышь", то исходный код удобнее просматри-

вать, если задать активизацию ObjectBrowser правой кнопкой "мы-

ши". Затем, удерживая нажатой клавишу Ctrl, вы можете использо-

вать правую кнопку "мыши" для указания и проверки объекта, проце-

дуры, функции, переменной или другого идентификатора в вашем ис-

ходном коде и его анализа (вывода детальной информации).

Примечание: Для быстрого просмотра идентификатора, на

котором в исходном коде позиционирован курсор, вы можете

также выбрать в локальном меню окна редактирования команду

Browse Symbol at Cursor (Просмотр идентификатора в позиции

курсора).

Чтобы задать просмотр с помощью "мыши", выполните следующие

шаги:

1. Выберите команду OptionsіEnviromentіMouse.

2. Выберите параметр Browse as the Ctrl + Right Mouse Button

(Просмотр по клавише Ctrl + правая кнопка "мыши").

3. Выберите командную кнопку OK.

Просмотр объектов

Команда SearchіObjects открывает окно, в котором выводятся

все используемые в программе объекты, упорядоченные в иерархичес-

кой структуре. В верхней части окна ObjectBrowser показывает ба-

зовый тип и выводит ниже и справа от базового типа потомков. Про-

яснить соотношения "предок-потомок" помогают соединяющие линии.

Примечание: Подробную информацию о типах объектов вы

можете найти в Главе 9 ("Объектно-ориентированной програм-

мирование").

±File±Edit±Search±±Run±±Compile±Debug±Tools±Options±Window±Help±±

ЙН[*]НННННННННННННННННННННН Browse: Object НННННННННННННННН2Н[*]»

є Global Scope є

єДґ I ГДДДДДДДє

єДObjects±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±^

є ГДДДДLocation Ы

є і ГДДДBlock ±

є і і ГДДДBall ±

є і і АДДДBrick ±

є і ГДДДCursor ±

є і і АДДДSaveScreen ±

є і ГДДДObstacle ±

є і і ГДДДBoundary ±

є і і і ГДДДLeftBound ±

є і і і ГДДДLowerBound ±

є і і і ГДДДRightBound ±

є і і і АДДДUpperBound ±

є і і ГДДДPaddle ±

є і і АДДДWall ±

є і АДДДTextString ±

є і АДДДCounter ±

є і АДДДDownCounter ±

є і АДДДLimitCounter v

И<±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>ДЩ

°F1°Help°<Щ°Browse°°Ctrl°<Щ°Go°to°source°Space°Track°src°F10°Menu

Рис. 4.4 Просмотр иерархии объектов прикладной программы.

Если иерархия объектов у вас слишком большая, вы можете за-

дать, чтобы потомки конкретного объекта на экран не выводились.

* С помощью клавиатуры сделайте следующее:

1. Выберите объект.

2. Нажмите клавишу - (минус).

* При наличии "мыши" просто щелкните "мышью" на горизонталь-

ной линии, соединяющей объект с иерархией.

Теперь после объекта выводится + (плюс), и он подсвечивает-

ся, показывая, что потомки данного объекта на экран не выводятся.

Вы можете вывести потомков объекта снова.

* С помощью клавиатуры:

1. Выделите объект с символом +.

2. Нажмите клавишу +.

* При наличии "мыши" щелкните ей на горизонтальной строке,

соединяющей объект с иерархией.

Потомки объекта появляются вновь.

Из иерархии объектов вы можете просматривать все описанные в

индивидуальном объекте идентификаторы. Выделите объект и нажмите

клавишу Enter, либо дважды щелкните на объекте кнопкой "мыши".

При просмотре описанных в объекте идентификаторов вы можете

задать различные виды вывода:

* Для вывода информации о наследовании для просматриваемого

объекта щелкните "мышью" на букве I в верхней части окна

ObjectBrowser или нажмите клавиши Ctrl+I.

* Для вывода на экран перечня строк программы или модуля,

где имеется ссылка на идентификатор объекта щелкните

"мышью" на букве R в верхней части окна ObjectBrowser или

нажмите клавиши Ctrl+R.

* Для вывода области действия объекта щелкните "мышью" на

букве S или нажмите клавиши Ctrl+S.

Чтобы найти идентификатор в списке выводимых идентификато-

ров, наберите первую букву имени идентификатора; ваш курсор быст-

ро перемещается на соответствующий идентификатор.

Чтобы ObjectBrowser мог находить, где расположены ваши ис-

ходные файлые, вам может потребоваться изменить маршрут каталога

модулей и включаемых файлов в диалоговом окне Optionsі

Directories.

Если после открытия окна просмотра вы модифицируете исходный

код, добавляя или удаляя строки программы, то программу лучше пе-

рекомпилировать. Хотя ObjectBrowser после модификации исходного

кода все равно сможет отслеживать информацию об идентификаторах,

номера строк исходного кода, выводимые в окне редактирования, не

будут обновляться, пока программа не будет перекомпилирована.

Изменения режима вывода информации ObjectBrowser

Вы можете управлять характером вывода в ObjectBrowser инфор-

мации.

Можно выбрать, какие идентификаторы должны выводить

ObjectBrowser:

1. Для вывода диалогового окна Browser Options выберите ко-

манду OptionsіBrowser.

2. В группе Symbols отметьте только те идентификаторы, кото-

рые вы хотите выводить в ObjectBrowser. Вы можете также

выбрать вывод идентификаторов, наследуемых от предков

объекта.

3. Выберите командную кнопку OK.

В ObjectBrowser выводятся только те идентификаторы, кото-

рые вы выбрали.

Вы можете также выбрать, какие идентификаторы выводить толь-

ко в активном окне просмотра. Для вывода диалогового окна Local

Browser Options (Локальные параметры просмотра) выберите в ло-

кальном меню окна просмотра команду Options или при отображении

окна просмотра нажмите клавиши Ctrl+O.

Если вы в качестве типа отображаемых идентификаторов выбере-

те типы, переменные и процедуры, а затем будете просматривать

объект Location в программе BREAKOUT.PAS (пример демонстрационной

программы в каталоге EXAMPLES\DOS\BREAKOUT), то увидите следующую

информацию об идентификаторах:

±File±Edit±Search±±Run±±Compile±Debug±Tools±Options±Window±Help±±

ЙН[*]НННННННННННННННННННННН Browse: Object НННННННННННННННН2Н[*]»

є Global Scope є

єДґ S і I і R ГДДДДДДДДДДДДДДДДДДДДДДДДє

є±var±±±±Location.X:±±Integer±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±^

є var Location.Y: Integer Ы

є var Location.Visible: Boolean ±

є var Location.Init(Integer, Integer) ±

є proc Location.Relocate(Integer, Integer) ±

є proc Location.MoveTo(Integer, Integer) ±

є proc Location.Show ±

є proc Location.Hide ±

є func Location.GetX: Integer ±

є func Location.GetY: Integer ±

є func Location.InVisible: Boolean ±

є ±

є ±

є v

И<Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>ДЩ

°F1°Help°<Щ°Browse°°Ctrl°<Щ°Go°to°source°Space°Track°src°F10°Menu

Рис. 4.5 Просмотр информации об идентификаторах.

Сокращения слева от перечисленных идентификаторов перечисля-

ют идентификаторы, представляющие вид выводимого идентификатора.

Заметим, что включение идентификаторов процедур также включает

идентификаторы функций.

ЪДДДДДДДДДДДДДДДВДДДДДДДДДДДї

і Идентификатор і Смысл і

ГДДДДДДДДДДДДДДДЕДДДДДДДДДДДґ

і const і Константа і

і func і Функция і

і label і Метка і

і proc і Процедура і

і type і Тип і

і var і Переменная или типизированная кон- і

і і станта. і

АДДДДДДДДДДДДДДДБДДДДДДДДДДДЩ

Для вывода предыдущего окна просмотра выберите команду

SearchіPrevious Browser или нажмите клавиши Ctrl+P.

Когда вы просматриваете выделенный идентификатор, окно

ObjectBrowser по умолчанию выводит информацию об области дейс-

твия. Если вы предпочитаете по умолчанию видеть информацию о

ссылках, то сделайте следующее:

1. Выберите команду OptionsіBrowser.

2. Выберите в качестве значения параметра Preferred Pane

Reference.

3. Выберите командную кнопку OK.

По умолчанию ObjectBrowser выводит для проверяемого иденти-

фикатора полную информацию об описании. Вы можете просмотреть все

поля и методы записей и объектов, включая полностью уточненные

идентификаторы. Если вы не хотите видеть полностью уточненные

идентификаторы, то сделайте следующее:

1. Выберите команду OptionsіBrowser.

2. Отмените параметр вывода Qualified Symbols.

3. Выберите командную кнопку OK.

Примечание: Вы не увидите видеть полностью уточненные

идентификаторы, пока не установите наследование в диалого-

вом окне Browser Options (OptionsіBrowser).

По умолчанию ObjectBrowser выводит идентификаторы в области

Scope в том порядке, в каком они описаны. Если вы предпочитаете

сортировать идентификаторы в алфавитном порядке, то сделайте сле-

дующее:

1. Выберите команду OptionsіBrowser.

2. Установите параметр вывода Sort Always.

3. Выберите командную кнопку OK.

Идентификаторы будут сортироваться только по именам, а не

по полностью уточненным именам. Например, считается, что

следующий список идентификаторов отсортирован в алфавит-

ном порядке:

THELPFILE.DONE

TOBJECT.FREE

THELPFILE.INDEX: PHELP

Когда вы открываете окно просмотра, затем просматриваете пе-

речисленный в нем идентификатор, то открывается новое окно прос-

мотра, но предыдущее окно остается. Вы можете изменить это пове-

дение таким образом, что новое окно просмотра будет заменять пре-

дыдущее:

1. Выберите команду OptionsіBrowser.

2. Установите параметр Replace Current sub-browsing.

3. Выберите командную кнопку OK.

Чтобы текущее окно просмотра сохранялось, когда вы выбираете

перечисленный в его списке идентификатор, сделайте следующее:

1. Выберите команду OptionsіBrowser.

2. Установите параметр New Browser sub-browsing.

3. Выберите командную кнопку OK.

Возможно, в большинстве случаев вы предпочитаете использо-

вать параметр Replace Current или New Browser, но иногда исполь-

зуете альтернативный параметры. Быстро выбрать альтернативный па-

раметр можно следующим образом:

1. Нажмите и удерживайте в нажатом состоянии клавишу Shift.

2. Выберите следующее действие просмотра.

Например, если действует параметр New Browser, то при на-

жатии клавиши Shift следующее открываемое окно просмотра

будет замещать текущее.

Отслеживание и редактирование ссылок на строки

Когда ObjectBrowser выводит ссылочную информацию, вы можете

выбирать и редактировать выводимые в ссылке строки программы или

отслеживать ссылки на идентификатор. Отслеживание означает, что

IDE, при перемещении по ссылкам в окне просмотра, подсвечивает в

программе одну строку за другой.

Чтобы отредактировать выводимую в ссылке строку программы,

сделайте следующее:

1. Выделите ссылку в окне просмотра.

2. Нажмите клавиши Ctrl+Enter или Ctrl+G.

Ваш курсор перемещается на строку программы в исходном

коде, ссылка на которую содержится в окне просмотра. Те-

перь вы можете редактировать строку программы.

По умолчанию, когда ваш курсор перемещается на строку ис-

ходного кода программы, окно ObjectBrowser закрывается.

Если вы предпочитаете, чтобы окно просмотра оставалось

открытым, то отмените параметр Close On Go To Source в

диалоговом окне Preferences.

Примечание: Параметр Close On Go To Source влияет

также на окно сообщений Messages (см. ниже).

Для отслеживания строк программы:

1. В окне просмотра выделите ссылку, которую вы хотите отс-

леживать.

2. Нажмите клавишу пробела.

Если вы всегда хотите отслеживать ссылки по исходному ко-

ду, установите параметр Auto Track Source и группе

Options диалогового окна Preferences. Тогда при прокрутке

ссылок строки в исходном коде программы будут прокручи-

ваться автоматически, и пробел вам нажимать не нужно.

Примечание: Параметр Auto Track Source влияет также

на окно сообщений Messages (см. ниже).

Теперь строки программы, на которые имеются ссылки в окне

просмотра, отслеживаются в зависимости от того, как вы установили

параметры отслеживания в диалоговом окне OptionsіEnviromentі

Preferences: если файл, на который имеется ссылка, не находится в

окне редактирования, то IDE открывает файл, и он выводится либо в

новом, либо в текущем окне редактирования.

* Если вы хотите, чтобы файл выводился в новом окне редакти-

рования, выберите в качестве значения параметра Source

Tracking New Window.

* Если вы хотите, чтобы файл замещал текущий в активном окне

редактирования, выберите в качестве значения параметра

Source Tracking Current Window.

Примечание: Параметр Source Tracking влияет также на

окно сообщений Messages (см. ниже).

Если выбранный модуль хранится в библиотеке исполняющей сис-

темы Borland Pascal или является одним из стандартных модулей, то

вы не сможете просматривать или редактировать исходный код, пос-

кольку эти модули скомпилированы без включения отладочной инфор-

мации.

Просмотр модулей

Команда SearchіUnits открывает диалоговое окно, в котором

выводятся используемые в вашей программе модули, перечисленные в

алфавитном порядке. Для просмотра идентификаторов, описанных в

интерфейсной части модуля, выделите конкретный модуль и нажмите

клавишу Enter или дважды щелкните не нем кнопкой "мыши". Анало-

гично тому, как это делается с объектами, вы можете видеть об-

ласть действия ссылочной информации для идентификатора. Если мо-

дуль, на который имеется ссылка, не является одним из поставля-

емых с Borland Pascal стандартных модулей, и скомпилирован таким

образом, что включает в себя всю необходимую для интегрированной

отладки/просмотра информацию, то вы можете при соответствующей

ссылке отслеживать и редактировать исходный код модуля.

В следующем примере модуль Walls описывается в строке 4 фай-

ла WALLS.PAS и вызывается на строке 37 BREAKOUT.PAS.

ЪДДДДДДДДДДДДДДДДД Browse: Units ДДДДДДДДДДДДД2ДДї

і Global scope і

ГДґ S ГДДДДДДДДДДДДДДДДДґ

і unit Bounds і

і unit Breakout і

і unit Bricks і

і unit Count і

і unit Crt ЙН[*]ННННН Browse: Walls НН3ННН[^]Н»

і unit Dos є unit Walls є

і unit Screen єДґ S і R Гє

і unit System є WALLS.PAS(4) є

і±unit±±Walls±±±± є°BREAKOUT.PAS(37)°°°°°°°°°°°°°°°°°°є

і є є

і є є

і ИНН2/2ННННННННННННННННННННННННННННННј

і і

і і

АДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 4.6 Просмотр модулей в вашей прикладной программе.

Просмотр глобальных идентификаторов

Команда SearchіGlobal открывает окно, в котором выводятся

используемые в программе глобальные идентификаторы, перечисленные

в алфавитном порядке. Как и в случае объектов, для просмотра

строк в программе, ссылающихся на этот идентификатор, описаний

идентификатора и иерархии наследования вы можете открывать допол-

нительные окна ObjectBrowser. При выводе ссылочной информации вы

можете отслеживать или редактировать строки программы. Например,

на следующем рисунке показан список глобальных идентификаторов,

используемых в программе BREAKOUTS.PAS:

ЙН[*]НННННННННННННННННННННН Browse: Object НННННННННННННННН2Н[^]»

є Global Scope є

єДґ S ГДДДДДДДє

є±func±±±Abs(..)±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±^

є func Addr(...) Ы

є const AnyFile = 63 ±

є proc Append(...) ±

є const Archive = 32 ±

є func ArcTan(...) ±

є proc Assing(...) ±

є proc AssingCrt(var Text) ±

є func Assigned(...) ±

є var b: Ball ±

є type Ball = object(Block) ±

є var Balls: DownCounter ±

є proc Beep ±

є const Block = 0 ±

є const Blink = 128 ±

є type Block = object(Location) ±

є proc BlockRead(...) ±

є const Blue = 1 v

И<Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>ДЩ

Рис. 4.7 Просмотр глобальных идентификаторов, используемых в

программе.

Просмотр идентификаторов в исходном коде

Вы можете просматривать идентификаторы в своем исходном ко-

де. Поместите курсор на идентификатор и выберите один из следую-

щих методов:

* Для вывода диалогового окна Browse Symbol выберите в меню

Search команду Symbol. Используйте идентификатор, выведен-

ный в диалоговом окне, или введите другой идентификатор и

выберите командную кнопку OK.

* Для вывода локального меню окна редактирования нажмите

клавиши Alt+F10 или щелкните правой кнопки "мыши" и выбе-

рите команду Browse Symbol at Cursor.

* Если для просмотра идентификаторов вы задали правую кнопку

"мыши" (выбором команды OptionsіEnviromentіMouse и Browse

at Cursor), то нажмите клавишу Ctrl и, удерживая ее, щел-

кните правой кнопкой "мыши".

Тип информации, которую вы видите, зависит от типа информа-

ции, доступной для выбранного идентификатора:

* Если для выделенного вами идентификатора информация об об-

ласти действия недоступна, то ObjectBrowser выводит для

него ссылочную информацию. Например, для простой константы

доступна только ссылочная информация.

* Если выделенный вами идентикатор не имеет доступной инфор-

мации об области действия, ObjectBrowser выводит для этого

идентификатора информацию об области действия. Он также

дает вам возможность видеть ссылочную информацию.

* Если выделенный вами идентификатор представляет собой

структурный тип, то ObjectBrowser выводит для этого типа

информацию об области действия. Он дает вам также возмож-

ность видеть информацию о наследовании и ссылочную инфор-

мацию. Если вы выберите информацию о наследовании, то уви-

дите непосредственного предка этого типа и непосредствен-

ных потомков, если они имеются.

Если вы выбрали просмотр структурного типа, ObjectBrowser

выводит на экран полностью уточненные имена, если удовлетворяются

следующие два условия:

* В диалоговом окне Browser Options (OptionsіBrowser) или в

диалоговом окне Local Browser Options (локальное меню

Options окна просмотра) выбран параметр Inherited symbol.

* В диалоговом окне Browser Options (OptionsіBrowser) или в

диалоговом окне Local Browser Options (локальное меню

Options окна просмотра) выбран параметр Qualified Symbols.

Например, на следующем рисунке показана полная информация об

описании области действия для переменной b типа Ball:

±File±Edit±Search±±Run±±Compile±Debug±Tools±Options±Window±Help±±

ЙН[*]НННННННННННННННННННННН Browse: b ННННННННННННННННННННН2Н[*]»

є var b: Ball є

єДґ S і I і R ГДДДДДДДДДДДДДДДДДДДДДДДДє

є±var±±±±Location.X:±±Integer±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±^

є var Location.Y: Integer Ы

є var Location.Visible: Boolean ±

є var Location.Init(Integer, Integer) ±

є proc Location.Relocate(Integer, Integer) ±

є proc Location.MoveTo(Integer, Integer) ±

є func Location.GetX: Integer ±

є func Location.GetY: Integer ±

є func Location.InVisible: Boolean ±

є var Block.Color: Integer ±

є var Block.Width: Integer ±

є var Block.BChar: Char ±

є proc Block.Show ±

є proc Block.Hide ±

є var Ball.XVel: Integer ±

є var Ball.YVel: Integer ±

є proc Ball.Init(Integer, Integer, Integer, Integer, Integer) ±

є func Ball.NextX: Integer ±

є func Ball.NextY: Integer ±

є proc Ball.MoveX v

И<Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>ДЩ

°F1°Help°<Щ°Browse°°Ctrl°<Щ°Go°to°source°Space°Track°src°F10°Menu

Рис. 4.8 Просмотр полной информации описания области дейс-

твия.

Если ObjectBrowser выводит сообщение, говорящее, что ка-

кой-то идентификатор не найден, проверьте и убедитесь, что вы за-

дали в ObjectBrowser проверку допустимого идентификатора, и что

идентификатор находится в нужной области действия. Например, при

выборе команды SearchіSymbol курсор может быть позиционирован на

комментарии. Либо курсор может находиться вне области действия, в

которой ObjectBrowser может найти информацию об идентификаторе.

Например, курсор может находиться на формальном параметре в опи-

сании функции, а не в реализации функции. В этом случае

ObjectBrowser не может найти идентификатор, но если вы найдете

параметр в реализации функции, то сможете просмотреть его.

Просмотр функций ObjectBrowser

В Таблице 4.5 перечислены клавиши и команды меню, активизи-

рующие функции ObjectBrowser:

Функции ObjectBrowser Таблица 4.5

ЪДДВДДДДДДДДДДї

і Чтобы выполнить: і Сделайте следующее: і

ГДДЕДДДДДДДДДДґ

і Просмотр объектов і Выберите команду SearchіObjects. і

ГДДЕДДДДДДДДДДґ

і Просмотр модулей і Выберите команду SearchіUnits. і

ГДДЕДДДДДДДДДДґ

і Просмотр глобальных і Выберите команду SearchіGlobals. і

і идентификаторов і і

ГДДЕДДДДДДДДДДґ

і Просмотр идентификатора і Поместите курсор на идентификатореі

і і своей программы, выберите командуі

і і SearchіSymbol или удерживайте на-і

і і жатой клавишу Ctrl и щелкните пра-і

і і вой кнопкой "мыши". і

ГДДЕДДДДДДДДДДґ

і Выбрать Browser Options і Выберите команду OptionsіBrowser. і

ГДДЕДДДДДДДДДДґ

і Выбрать параметр Source і Выберите команду OptionsіEnviro-і

і Tracking Options і mentіPreferences. і

ГДДЕДДДДДДДДДДґ

і Выбрать Mouse Options і Выберите команду OptionsіEnviro-і

і і mrntіMouse. і

ГДДЕДДДДДДДДДДґ

і Открыть предыдущее окно і Выберите команду SearchіPreviousі

і просмотра і Browser, выберите в локальном менюі

і і окна просмотра команду Previousі

і і или нажмите клавиши Ctrl+P. і

ГДДЕДДДДДДДДДДґ

і Выбрать Local Browser і Нажмите клавиши Ctrl+O или выбери-і

і Options і те команду Options в локальном і

і і меню окна просмотра. і

ГДДЕДДДДДДДДДДґ

і Отредактировать исходный і Нажмите Ctrl+Enter в ObjectBrowserі

і код і нажмите клавиши Ctrl+G или выбери-і

і і те в локальном меню окна просмотраі

і і команду Goto Source. і

ГДДЕДДДДДДДДДДґ

і Отслеживать исходный і Нажмите в ObjectBrowser пробелі

і код і нажмите клавиши Ctrl+T или выбери-і

і і те в локальном меню окна просмотраі

і і Track Source. і

ГДДЕДДДДДДДДДДґ

і Вывести ссылочную і Нажмите в ObjectBrowser клавишиі

і информацию і Ctrl+R или щелкните "мышью" наі

і і букве R в рамке окна. і

ГДДЕДДДДДДДДДДґ

і Вывести информацию і Нажмите в ObjectBrowser клавишиі

і об области действия і Ctrl+S или щелкните "мышью" наі

і і букве S в рамке окна. і

ГДДЕДДДДДДДДДДґ

і Вывод информации о і Нажмите в ObjectBrowser клавишиі

і наследовании і Ctrl+I или щелкните "мышью" наі

і і букве I в рамке окна. і

ГДДЕДДДДДДДДДДґ

і Изменить на обратный і Нажмите клавишу Shift, и, удержи-і

і режим вывода подокна і вая ее, выполните следующее дейст-і

і і вие. і

АДДБДДДДДДДДДДЩ

Выполнение в IDE других программ

Без выхода из IDE вы можете запускать другие программы и

утилиты. При установке пакета Borland Pascal IDE устанавливается

для выполнения таких инструментальных программных средств как

GREP, Turbo Assembler, Turbo Debugger и Turbo Profiler.

Чтобы запустить в интегрированной интерактивной среде прог-

рамму, сделайте следующее:

1. Откройте меню Tools (Инструментальные средства).

Вы увидите список программ и утилит, которые может запус-

кать.

2. Выберите в меню Tools программу, которую вы хотите запус-

тить.

При выборе программы ей передается управление. После выпол-

нения программы управление возвращается обратно в IDE.

Настройка меню Tools

Программы, которые вы найдете полезными, можно добавить в

меню Tools и запускать их потом из IDE.

Чтобы добавить программы в меню Tools, сделайте следующее:

1. Для вывода диалогового окна Tools выберите команду

OptionsіTools.

В блоке списка заголовков программ Program Titles вы уви-

дите краткое описание уже инсталлированных и готовых к

выполнению программ.

2. Для вывода диалогового окна Modify/New Tool (Модифика-

ция/Новое инструментальное средство) выберите команду

New.

3. В блоке ввода заголовка программы Program Title наберите

имя программы, как вы хотите выводить его в меню Tools.

Если вы хотите, чтобы программа вызывалась по оперативным

клавишам, укажите непосредственно перед и после того сим-

вола, который должен использоваться в качестве оператив-

ной клавиши, символ тильды (~). Этот символ будет выво-

диться в меню Tools жирным шрифтом и специальным шрифтом,

и при нажатии этой клавиши вы выберете программу. Напри-

мер, чтобы добавить в меню Tools редактор Brief и сделать

оперативной клавишей клавишу B, наберите:

~B~rief

4. Если вы хотите, чтобы ваша программа имела связанную с

ней оперативную клавишу, выберите один из параметров Hot

Key. Когда вы нажмете присвоенную оперативную клавишу,

программа начинает работать. Например, утилите GREP прис-

воены оперативные клавиши Shift+F2. В любой момент, чтобы

использовать GREP, просто нажмите Shift+F2.

5. В блоке ввода Program Path (Маршрут программы) наберите

имя программы.

Если вы не введете полного имени, то могут быть найдены

только программы в текущем каталоге или программы по

обычному маршруту DOS.

6. В блоке ввода Command Line (Командная строка) наберите

параметры или макрокоманды, которые вы хотите передать

программе.

Полную справочную информацию по макрокомандам, которые

можно использовать в блоке Command Line в Modify/New Tool

вы можете найти в оперативном справочнике Help Borland

Pascal.

7. Выберите командную кнопку OK.

Для редактирования имеющейся в меню Tools программы сделайте

следующее:

1. Выберите команду OptionsіTools.

2. В блоке списка Program Titles выберите нужную программу

для редактирования.

3. Выберите Edit (Редактирование).

4. Внесите изменения в заголовок программы, маршрут програм-

мы или командную строку.

5. Выберите командную кнопку OK.

Чтобы удалить указанную в меню Tools программу:

1. Выберите команду OptionsіTools.

2. Выберите программу, которую вы хотите удалить.

3. Выберите Delete (Удаление).

4. Выберите командную кнопку OK.

Работа с окном Messages

Некоторые инструментальные средства посылают вывод программы

черед фильтр DOS - программу, конвертирующую вывод в формат, ко-

торый можно выводить в окне сообщений Messages. Более подробную

информацию об использовании и написании ваших собственных филь-

тров DOS вы можете получить в оперативном справочнике Help. Одним

из таких инструментальных средств, использующих окно Messages,

является GREP, а ее фильтр называется GREP2MSG.EXE. Исходный код

CREP2MSG.PAS вы можете найти в каталоге UTILS.

При работе такого инструментального средства как GREP выво-

димая информация передается в появляющееся окно сообщений

Messages. Выводимые сообщения вы можете прокручивать. В окне

Messages вы можете выбирать и редактировать строку программы, на

которую имеется ссылка в сообщении, либо вы можете отслеживать

свои сообщения (подсвечивать одну за другой строки исходного кода

программы, при прохождении сообщений в этом окне).

Чтобы отредактировать строку программы, на которую имеется

ссылка в сообщении, сделайте следующее:

* Если вы используете "мышь", дважды щелкните "мышью" на ин-

тересующем вам сообщении.

* При работе с клавиатурой выберите сообщение со ссылкой на

нужную строку программы и нажмите Enter.

* Ваш курсор перемещается на строку программы в исходном ко-

де, на которую ссылается сообщение в окне Messages. Теперь

вы можете отредактировать строку программы.

По умолчанию, когда курсор переходит на строку исходного

кода программы, окно Messages закрывается. Если вы предпо-

читаете, чтобы окно Messages оставалось открытым, отмените

выбор параметра Close On Go To Source в диалоговом окне

Preferences.

Примечание: Параметр Close On Go To Source влияет

также на ObjectBrowser (см. выше).

Чтобы отслеживать строки программы:

1. В окне Messages выберите сообщение со ссылкой на строку

программы, которую вы хотите отслеживать первой.

2. Нажмите клавишу пробела.

Если вы всегда хотите отслеживать сообщения в исходном

коде, установите в группе Options диалогового окна

Preferences параметр Auto Track Source. После этого при

прокрутке окна сообщений в вашем исходном коде автомати-

чески будут подсвечиваться соответствующие строки; вам не

нужно будет нажимать на клавишу пробела.

Примечание: Параметр Auto Track Source влияет также

на ObjectBrowser (см. выше).

Теперь строки программы, на которые есть ссылки в сообщени-

ях, отслеживаются в зависимости от установленных в диалоговом ок-

не OptionsіEnviromentіPreferences параметров. Если соответствую-

щий файл не находится в окне редактирования, IDE открывает файл и

выводит его в новом окне редактирования или в текущем окне редак-

тирования.

* Если вы хотите, чтобы файл выводился в новом окне редакти-

рования, выберите в качестве значения параметра Source

Tracking New Window.

Примечание: Параметр Source Tracking влияет также на

ObjectBrowser (см. выше).

* Если вы хотите, чтобы файл замещал текущий файл в одном из

активных окон редактирования, выберите в качестве значения

параметра Source Tracking Current Window.

Если окно сообщений закрыто, с помощью команды Toolsі

Messages вы можете открыть его снова.

Настройка конфигурации IDE

При написании и редактировании программ вы можете установить

параметры редактирования и привилегированные параметры, выбрать

параметры компилятора и компоновщика или изменить по своему ус-

мотрению расположение и размер окон редактирования. IDE может за-

поминать установленные значения и файлы и использовать их в сле-

дующем сеансе редактирования.

Сохранение рабочей операционной среды

С помощью Auto Save вы можете сохранить параметры Editor

Files, Desktop и Enviroment в диалоговом окне OptionsіEnviroment.

* Если установлен параметр Editor Files, IDE при выходе из

интегрированной среды, выборе команды FileіDOS Shell, вы-

полнении или отладке программы сохраняет все модифициро-

ванные файлы, открытые в окне редактирования.

* Если установлен параметр Desktop, IDE при выходе из интег-

рированной среды, выборе команды FileіDOS Shell, выполне-

нии или отладке программы сохраняет имена всех файлов, с

которыми вы работали в оперативной области.

* Если установлен параметр Enviroment, IDE при выходе из ин-

тегрированной среды, выборе команды FileіDOS Shell, выпол-

нении или отладке программы сохраняет в файле конфигурации

выбранные вами все параметры редактирования, компоновки и

компиляции.

Использование файла конфигурации

Файл конфигурации сохраняет все параметры, установленные в

меню параметров Options, выбранные в диалоговом окне Find Text,

отслеживает все объединенные файлы Help, целевую платформу и имя

основного файла (если он имеется). Если в диалоговом окне

OptionsіEnviromentіPreferences установлен параметр Auto Save

Enviroment, то файл конфигурации обновляется при выходе из интег-

рированной среды, выборе команды FileіDOS Shell, выполнении или

отладке программы. При использовании компиляторов BP.EXE или

TURBO.EXE по умолчанию файл компилятором называется TP.EXE.

Чтобы создать новый файл конфигурации для другого проекта,

сделайте следующее:

1. Выберите команду OptionsіSave As.

2. В поле ввода Options File Name наберите новое имя.

3. Выберите командную кнопку OK.

Примечание: Об использовании файла конфигурации для

управления программным проектом рассказывается в следующей

главе.

Чтобы переключиться на другой существующий файл конфигура-

ции:

1. Выберите команду OptionsіOpen.

2. Задайте имя существующего файла конфигурации.

3. Выберите командную кнопку OK.

Чтобы модифицировать существующую конфигурацию:

1. Измените нужные параметры.

2. Выберите команду OptionsіSave.

IDE сохраняет все изменения в текущем файле конфигурации.

Использование файла оперативной области

ДДДДДДДДДДДДДДД

Если в диалоговом окне OptionsіEnvioromentіPreferences вы

установили параметр Auto Save Desktop, IDE при выходе из интегри-

рованной среды, выборе команды FileіDOS Shell, выполнении или от-

ладке программы обновляет файл оперативной области. Файл опера-

тивной области отслеживает все файлы, которые вы открывали, и

файлы, с которыми вы работали, но закрыли их в ходе сеанса прог-

раммирования (кроме файлов NONAMExx.PAS). Когда вы начнете новый

сеанс редактирования, ваши окна редактирования выводятся в том

виде, какой они имели при выходе. Когда вы откроете меню File, то

список закрытых файлов в меню File продолжает увеличиваться (мак-

симум до 5 значений).

Как определить, какой файл оперативной области использовать?

Вы можете непосредственно выбрать новый файл оперативной области,

но каждый раз, когда вы создаете файл конфигурации, IDE создает

новый файл оперативной области. Имя файла будет тем же самым, но

имя файла оперативной области вместо расширения .TP имеет расши-

рение .DSK. Например, если ваш файл конфигурации называется

MY.TP, то файл оперативной области будет называться MY.DSK.

По умолчанию IDE сохраняет файл оперативной области в том же

каталоге, что и текущий файл конфигурации. Если хотите, то можете

сохранять файлы конфигурации в текущем каталоге:

1. Выберите команду OptionsіEnviromentіPreferences.

2. В группе Desktop File выберите параметр Current

Directory.

Если вы хотите сохранять текущие параметры конфигурации, но

очищать оперативную область, так что IDE будет "забывать" список

всех файлов, с которыми вы работали, очищать все списки протоко-

лов и закрывать все окна, сделайте следующее:

1. Выберите команду OptionsіEnviromentіPreferences.

2. Убедитесь, что в диалоговом окне Preferences установлен

параметр Desktop, а параметр Enviroment выключен.

3. Перезапустите IDE.

IDE закрывает все ваши окна и сохраняет текущие парамет-

ры, но ваша оперативная область, списки протоколов и

списки закрытых файлов будут очищены.

Сохранение идентификаторов от одного сеанса к другому

Одновременно с сохранением файла оперативной области вы мо-

жете выбрать сохранение информации об идентификаторах в файле

идентификаторов (файл с расширением .PSM). Тогда при следующем

запуске IDE информация об идентификаторах, генерируемая при пос-

ледней компиляции, будет доступна, благодаря чему вы можете вы-

полнять немедленный просмотр и отладку.

Чтобы сохранять идентификаторы между сеансами, сделайте сле-

дующее:

1. Выберите команду OptionsіEnviromentіPreferences.

2. Убедитесь, что установлен параметр Auto Save Desktop.

3. В группе Desktop File Options выберите параметры Desktop

и Symbols.

4. Выберите командную кнопку OK.

Управление проектом

Если вы хотите сделать свое программирование проекта модуль-

ным и более легко управляемым, используйте основной файл. Задайте

в качестве основного файла главный файл программы и сделайте так,

чтобы он использовал несколько модулей или включаемых файлов, где

вы можете хранить большие фрагменты кода.

Чтобы задать, какой файл будет основным, выполните следующие

шаги:

1. Выберите команду CompileіPrimary File.

2. Когда выведется диалоговое окно, наберите имя своего фай-

ла или выберите его из окна списка Files.

3. Выберите командную кнопку OK.

Теперь, когда вы используете команду CompileіMake или Build,

компилируется основной файл, даже если он не является файлом в

активном окне редактирования.

Каждый проект, с которым вы работаете с IDE, имеет уникаль-

ные требования. Например, каждый проект имеет различный основной

и различные каталоги, где находятся ваши файлы. Вы можете настро-

ить IDE в соответствии со своим проектом.

Управление несколькими проектами

с файлом конфигурации для каждого из них

Секрет управления проектами в IDE состоит в использовании

для каждого проекта своего файла конфигурации. Когда вы начинаете

новый проект, создайте новый файл конфигурации:

1. Установите все параметры так, как это требуется для ново-

го проекта.

2. Задайте основной файл.

3. Для задания каталогов, где компилятор будет искать файлы

для вашего проекта используйте команду Optionsі

Directories.

4. В меню Options выберите команду Save As.

В диалоговом окне выводится подсказка для ввода имени но-

вого файла конфигурации.

5. Задайте имя нового файла конфигурации.

6. Выберите командную кнопку OK.

Если вы в этот момент выйдите из IDE, а параметры Auto Save

Desktop и Enviroment Options (OptionsіEnviromentіPreferences) ус-

тановлены, то, когда вы начинаете новый сеанс, IDE использует но-

вый файл конфигурации и файл оперативной области. Файлы, с кото-

рыми вы работаете, будут доступны для вас в окне редактирования

или в списке закрытых файлов в меню File, поскольку для вашего

проекта создается новый файл оперативной области.

Если вы храните каждый проект Паскаля в отдельном каталоге,

то ниже даны рекомендации по удобному управлению проектом. Когда

вы установите все параметры так, как это требуется для проекта, и

зададите основной файл (если он имеется), сделайте следующее:

1. В меню Options выберите команду Save As.

2. Задайте новое имя файла конфигурации, включив в каталог

проекта полный маршрут.

* Если вы используете BP.EXE, задайте в качестве нового

файла конфигурации BP.TP.

* Если вы используете TURBO.EXE, задайте в качестве ново-

го файла конфигурации TURBO.TP.

3. Выберите командную кнопку OK.

Сохранив в каталоге проекта файл BP.TP или TURBO.TP, вы

можете перейти в каталог проекта, запустить IDE, и IDE

автоматически загрузит в файл конфигурации в этом катало-

ге.

Если вы не хотите больше работать с файлом, заданным в ка-

честве основного файла, то можете использовать для очистки этого

файла следующие два метода:

* Выберите команду CompileіClear Primary File.

* Выберите в диалоговом окне Primary File параметр Primary

File и Clear (Очистка).

Если вы хотите работать с другим проектом, с помощью команды

OptionsіOpen загрузите новый файл конфигурации проекта.


Глава 5. Программирование в интегрированной интерактивной среде для Windows

Интегрированная среда (IDE) для Windows очень похожа на IDE

для DOS Borland Pascal. Большинство функций, которые вы выполняе-

те в IDE для DOS, аналогичным образом выполняется в IDE для

Windows. Если вы не читали предыдущую главу, потратьте некоторое

время и сделайте это.

Примечание: IDE для Windows может создавать прикладные

программы для Windows, защищенного режима DOS и реального

режима DOS.

В данной главе основное внимание уделяется уникальным средс-

твам IDE для Windows и поясняется некоторое различие между IDE

для DOS и для Windows.

Так как Borland Pascal for Windows работает под Windows, мы

подразумеваем, что вы уже знакомы с Windows. Если вы умеете рабо-

тать с Windows, то сможете работать и с IDE Borland Pascal for

Windows.

Запуск IDE для Windows

Для запуска IDE для Windows необходимо дважды щелкнуть

"мышью" над пиктограммой Borland Pascal for Windows в администра-

торе программ или выбрать ее с помощью клавиатуры и нажать клави-

шу Enter.

Вы можете также запустить IDE для Windows в ответ на подс-

казку DOS. Наберите:

WIN BWP

Продвигаясь на шаг дальше, вы можете в ответ на подсказку

DOS задать, какие файлы нужно открыть в окнах редактирования, и

какой нужно использовать файл конфигурации. При этом используется

следующий синтаксис:

WIN BWP [/Cфайл_конфигурации]файлы

Например, на следующей строке запускается Windows, и начина-

ет работать IDE для Windows, используя параметры в файле конфигу-

рации MYCONFIG.CFG и открывая два окна, одно из которых содержит

файл MYFILE.PAS, а другое - YOURFILE.INC:

WIN BWP /Cmyconfig myfile yourfile

Использование оперативной полосы

С помощью оперативной полосы SpeedBar и "мыши" вы можете

быстро выбирать команды и другие действия.

Командные кнопки в оперативной полосе представляют команды.

Это оперативные кнопки для "мыши", аналогично тому, как опреде-

ленные комбинации клавиш на клавиатуре являются оперативными кла-

вишами. Чтобы выбрать команду, щелкните на соответствующей ко-

мандной кнопке "мышью". Например, если вы щелкните "мышью" на ко-

мандной кнопке Open a File (Открыть файл), то реакция IDE будет

такой же, как при выборе команды Open меню File.

Оперативная полоса контекстно-зависима. То, какие командные

кнопки к ней выводятся, зависит от того, какое окно активно - ок-

но оперативной области или окно редактирования.

Оперативные полоса окна рабочей области выводится, когда в

IDE не открыты окна редактирования. В оперативной полосе рабочей

области выводятся следующие командные кнопки:

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Help (Контекстные экраны Make (Формирование)

справочной системы)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Open a File Make and Run

(Открыть файл) (Формирование и

запуск)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Exit the IDE Make and Run under

(Выход из IDE) Turbo Debugger

(Формирование и

запуск с отладчиком)

Рис. 5.1 Командные кнопки оперативной полосы рабочей облас-

ти.

Следующие командные кнопки выводятся в оперативной полосе

окна редактирования.

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Help on Editor Paste from Clipboard

(Справка по редактору) (Вставка из буфера)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Open a File Undo (Отмена)

(Открытие файла)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Save a file Compile

(Сохранение файла) (Компиляция)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Search for text Make (Формирование)

(Поиск текста)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Search again Make and Run

(Повторный поиск) (Формирование и запуск)

ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї

і і і і

і і і і

і і і і

АДДДДДДДДДДДЩ АДДДДДДДДДДДЩ

Cut to Clipboard Make and Run under

(Вырезание в буфер) Turbo Debugger

(Формирование и

запуск с отладчиком)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Copy to Clipboard

(Копирование в буфер

вырезанного изображения)

Рис. 5.2 Командные кнопки оперативной полосы окна редактиро-

вания.

Иногда определенные командные кнопки оперативной полосы вы-

водятся тусклыми. Это означает, что команда, представляемая дан-

ной кнопкой, в текущем контексте для вас недоступна. Например,

если окно редактирования открыто, и буфер вырезанного изображения

пуст, то кнопка Paste Text from Clipboard будет тусклой.

Настройка конфигурации оперативной полосы

При первом запуске IDE оперативная полоса представляет собой

горизонтальную группу командные кнопок, которая выводится непос-

редственно по строкой меню. Оперативная полоса может быть:

* горизонтальной полосой;

* вертикальной полосой в левой части оперативной области

IDE;

* всплывающим набором, который вы можете перемещать в любую

часть оперативной области.

Вы можете также выключить оперативную полосу.

Чтобы изменить конфигурацию оперативной полосы, выберите ко-

манду OptionsіEnviromentіPreferences и выберите нужный параметр

оперативной полосы.

Использование справочной системы Help

Справочная система Help дает вам возможность легкого доступа

к подробной информации о языке Borland Pascal, интегрированной

среде, библиотеке динамической компоновки, ObjectWindows, интер-

фейсе прикладных программ Windows (API) и дополнительных утили-

тах, предусмотренных в Borland Pascal. Вы можете просматривать

все эти темы в справочном окне Help или получать контекстно-зави-

симую справочную информацию об IDE или терминах, набираемых вами

в окне редактирования. Этот раздел знакомит вас со справочной

системой Borland Pascal for Windows.

Справочную систему Borland Pascal for Windows вы можете ис-

пользовать аналогично справочной системе Windows. Чтобы узнать о

работе Help Windows, выберите команду HelpіUsing Help. Вы узнаете

об общих средствах Help Windows (таких как аннотирование, исполь-

зование меток текста, просмотр и печать), о которых не упоминает-

ся в данном руководстве.

В следующих разделах описываются способы, с помощью которых

вы можете использовать справочную систему Borland Pascal for

Windows при разработке в IDE своих прикладных программ.

Чтобы больше узнать о справочнике Help, выберите команду

HelpіUsing Help или нажмите где-либо в справочной системе клавишу

F1.

Перемещение по справочной системе

Когда вы смотрите на экраны Help, то видите подчеркнутый

текст, цвет которого отличается от окружающего текста. Это ссыл-

ки. Ссылки могут представляться текстом, пиктограммами или графи-

кой, и вы можете выбирать их для получения более подробной инфор-

мации. Ссылки могут использоваться для вывода нового экрана Help,

представляющего новую информацию по отмеченной теме. Выбрать

ссылку можно следующими двумя способами:

* Щелкнув на ней кнопкой "мыши".

* Нажимая повторно клавиши Tab, пока ссылка не будет подсве-

чена, затем нажав клавишу Enter.

Новый справочный экран выводится с информацией по выбранной

теме: вы перешли на новое место в справочной системе. В этом эк-

ране вы можете видеть другие связи, которые вы можете выбрать для

вывода другой информации.

Чтобы вернуться к предыдущему справочному экрану, выберите

один из двух описанных ниже способов:

* Для возврата к последнему экрану выберите командную кнопку

Back.

* Чтобы увидеть список последних справочных экранов, выбери-

те командную кнопку History. Выберите из списка экран

Help, который вы хотите вывести.

Вы можете также видеть текст, подчеркнутый линией из точек,

цвет которого отличается от окружающего текста. Этот тип ссылки

вы можете использовать для вывода всплывающего окна с дальнейшей

информацией, воспользовавшись следующими способами:

* Щелкнув на ней кнопкой "мыши".

* Нажимая повторно клавиши Tab, пока ссылка не будет подсве-

чена, затем нажав клавишу Enter.

Выводится всплывающее окно, содержащее информацию по выбран-

ной теме. Оно остается на экране, пока вы не щелкните кнопкой

"мыши" или не нажмете клавишу Enter. Когда окно исчезнет, вы ос-

танетесь в том же месте справочной системы.

Запрос помощи

Получить доступ к справочной информации Help вы можете сле-

дующими способами:

* Щелкните "мышью" на команде Help полосы меню или для выво-

да меню Help нажмите клавиши Alt+H.

IDE выводит меню Help. В этом меню вы можете выбрать экран

Contents (Оглавление) системы Help, получить справку по

использованию справочной системы Help, вывести информацию

по теме, на которой позиционирован курсор в окне редакти-

рования, или вывести такую специфическую для Borland

Pascal информацию, как справка по языку, сообщениям об

ошибках Borland Pascal, примерах программ и т.д.

* Для вывода экрана оглавления справочной системы Borland

Pascal нажмите клавиши Shift+F1.

Экран оглавления Contents аналогичен оглавлению книги, но

вместо того, чтобы листать страницы, вы можете просто

щелкнуть "мышью" на подчеркнутой теме или нажать для пере-

хода к нужной теме клавишу Tab, а затем нажать клавишу

Enter.

* Нажмите клавишу F1.

- Если вы находитесь в окне редактирования, то экран Help

выводится со справочной информацией об использовании ре-

дактора. Чтобы увидеть детали, выберите одну из ссылок.

- Если выбрана команда меню, контекстно-зависимый экран

Help выводится с более подробной информацией об это эле-

менте меню.

* Выберите в диалоговом окне командную кнопку Help.

Выводится экран с кратким пояснением по всем командам,

доступным в данном диалоговом окне. Если вы щелкните

"мышью" на подчеркнутой теме или выберите ее помощью кла-

виатуры и нажмете Enter, то увидите более подробную инфор-

мацию о выбранной команде.

* Поместите курсор на термин в окне редактирования и выбери-

те Topic Search. Используйте любой из следующих методов:

- нажмите клавиши Ctrl+F1;

- выберите команду HelpіTopic Search;

- удерживая клавишу Ctrl, щелкните правой кнопкой "мыши"

(комбинация нажатий Ctrl+правая кнопка "мыши" должна

быть предварительно настроена - выберите команду

OptionsіEnviromentіMouse и выберите команду Topic

Search).

- выберите команду Topic Search в локальном меню окна ре-

дактирования.

Примечание: О локальных меню рассказывается в Главе 4.

Выводится справочный экран с информацией о ключевом слове,

на котором находится курсор в активном окне редактирова-

ния.

* В справочном окне Help выберите командную кнопку Search

(Поиск).

Выводится диалоговое окно поиска Search. В его верхнем

блоке списка вы можете прокручивать каждую тему в справоч-

ной системе Borland Pascal. Если вы знаете, какую тему вы

ищете, начните набирать эту тему в блоке ввода, и вы уви-

дите данную тему в блоке списка. Выделите тему и выберите

Show Topic.

Примечание: Если вы знаете, что нужно найти, то ко-

мандная кнопка Search дает вам скорейший способ для вывода

нужного экрана Help.

Если ваша тема имеет более детальное разбиение, вы увидите

в нижнем блоке списка другие темы. Выделите нужные темы и

выберите Go To. Выводится справочный экран по нужной теме.

* Выберите пиктограмму Help в оперативной полосе.

Выводится справочный экран Help.

Копирование примеров кода

Справочная система Help содержит пример кода для каждой про-

цедуры и функции. Вы можете скопировать эти примеры из справочной

системы в свое окно редактирования. Чтобы скопировать пример,

сделайте следующее:

1. Выведите экран Help по нужной процедуре или функции.

Вы увидите имя и пример исходного кода в нижней части ок-

на Help.

2. Для вывода примера щелкните кнопкой "мыши" на имени при-

мера кода.

3. Выберите команду EditіCopy.

Выводится диалоговое окно с примером кода. Вы можете вы-

делить часть кода для копирования в буфер вырезанного

изображения. Если вы этого не сделаете, пример будет ско-

пирован целиком.

4. Выберите команду Copy (Копирование).

5. Вернитесь в окно редактирования и выберите команду

EditіPaste, нажмите клавиши Shift+Ins или щелкните

"мышью" на командной кнопке Paste в буфере вырезанного

изображения или оперативной полосе.

Выход из справочной системы

Вы можете выбрать режим сохранения вывода экрана Help при

возврате в IDE или совсем закрыть основное окно Help.

* Для возврата в программу и сохранения фонового вывода те-

кущего окна Help щелкните "мышью" на окне, которое вы хо-

тите сделать активным. Когда вы захотите вывести окно Help

на переднем плане, щелкните на нем "мышью" - оно снова

станет активным.

* Чтобы закрыть основное окно Help и вернуться в прикладную

программу, выберите в меню окна Help команду FileіExit или

дважды щелкните "мышью" на его командной кнопке Cancel.

Запись и редактирование исходного кода

Поскольку редактор IDE ведет себя аналогично всем другим ре-

дакторам Windows, вероятно вы уже знаете, как редактировать

текст. В редакторе соблюдается стандарт общего доступа пользова-

теля CUA (Common User Access), который используется в большинстве

программ Windows. Те же команды редактирования, которые вы ис-

пользовали в других приложения Windows, работают также и в редак-

торе IDE. Полный перечень команд редактора вы найдете в Приложе-

нии A "Руководства программиста".

Примечание: Редактор позволяет вам открывать до 32

окон редактирования (что определяется доступной памятью).

Настройка конфигурации редактора

Для модификации поведения редактора Borland Pascal имеется

несколько параметров. Для вывода диалогового окна Editor выберите

команду OptionsіEnviromentіEditor.

Чтобы подробнее узнать о каждом параметре, выделите данный

параметр и нажмите клавишу F1. Справочная система Help поясняет,

что делает данный параметр.

Примечание: О выделении синтаксиса в редакторе расска-

зывается ниже.

Набор команд

Редактор IDE для Windows имеет два набора команд: набор ко-

манд CUA, при котором поведение редактора аналогично поведению

других редакторов Windows, и альтернативный набор команд, который

превращает редактор в редактор в стиле Borland. Кроме того, мно-

гие команды доступны в обоих наборах.

Примечание: Чтобы выбрать набор команд, обратитесь к

Приложению A "Справочного руководства программиста" или ис-

пользуйте справочник Help. Там вы найдете полный перечень

команд одного и другого набора.

При первоначальном запуске IDE для Windows редактор исполь-

зует набор команд CUA, который поддерживает все стандартные ко-

манды редактирования, общие для программ Windows, и многие из ко-

манд редактирования, знакомые тем, кто уже работал с языками и

программными продуктами Borland.

Чтобы использовать альтернативный набор команд, выберите ко-

манду OptionsіEnviromentіPreferences и параметр Alternate в груп-

пе Command Set.

Выбранный набор команд влияет не только на редактор. Напри-

мер, некоторые команды меню имеют оперативные клавиши, с помощью

которых вы можете выбирать команду, не прибегая к меню. В альтер-

нативном наборе команд F2 дает быстрый способ сохранения файла. В

наборе команд CUA оперативной клавиши для сохранения файла нет.

Использование редактора

В IDE для DOS и в IDE для Windows используется один редак-

тор. Если вы знаете, как работать с редактором в одной интегриро-

ванной среде, то сможете использовать его в другой. Чтобы узнать

об отмене ошибок, работе с блоками текста, поиске текста и огра-

ничителей и позиционировании курсора на строке с конкретным номе-

ром, см, соответствующие разделы предыдущей главы.

Редакторы для DOS и Windows имеют два небольших отличия:

* Редактор для Windows не имеет команды Find Text at Cursor.

* Редактор для Windows не имеет команды Block Insert Cursor,

которая назначает для курсора режима вставки форму прямоу-

гольника.

Выделение синтаксиса

IDE для Windows также обладает способностью выделения син-

таксиса. Как и в IDE для DOS вы можете выделять элементы исходно-

го кода цветом, но можете также изменять и атрибуты текста. Нап-

ример, не только изменить цвет элемента кода, но и сделать его

жирным, наклонным или подчеркнутым.

Для вывода диалогового окна Highlighting (Выделение) выбери-

те команду OptionsіEnviromentіHighlight.

ЙННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»

є=ЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫHighlightingЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫє

є і є

є Element±±±±±±±±±± Color±±±±±±±±±±±± Attribute±±±±±і є

є ЪДДДДДДДДДДДДДДДДїЪДДДДДДДДДДДДДДДДї±±±±±±±±±±±±±±і ЪДДДДДДДїє

є іWhitespace±±±±±±ііІFGІЫЫЫЫ °°°°і±< >±Normal±±±і і ы OK іє

є іComment±±±±±±±±±ііЫЫЫЫ °°°°ЫЫЫЫі±<*>±Bold±±±±±і АДДДДДДДЩє

є іЫReservedЫWordЫЫііІІІІЫЫЫЫ °°°°і±< >±Italic±±±і є

є іIdentifier±±±±±±ііЫЫЫЫ °°°°ЫЫЫЫіДДДДДДДДДДДДДДі є

є іSymbol±±±±±±±±±±ііІІІІЫЫЫЫ±±±±°°°°і±Ы±Underline±±і є

є іString±±±±±±±±±±ііЫЫЫЫ °°°°ЫЫЫЫі і є

є іNumber±±±±±±±±±±ііІІІІЫЫЫЫ °°°°і±±±±±±±±±±±±±±і є

є іAssembler±±±±±±±ііІІІІ±±±± °°°°і±Ы±Default±FG±і ЪДДДДДДДїє

є і±±±±±±±±±±±±±±±±ііЫЫЫЫ °°°°ЫЫЫЫі±ы±Default±BG±і іXCancelіє

є АДДДДДДДДДДДДДДДДЩАДДДДДДДДДДДДДДДДЩ±±±±±±±±±±±±±±і АДДДДДДДЩє

єЪїі є

єі {Syntax highlighting} іі є

єі Program Sample; іі є

єі uses WinCrt; іі є

єі var іі є

єі Number: integer; іі є

єі begin іі ЪДДДДДДДїє

єі Number := 123456; іі і ? Helpіє

єі Writeln['The number is',Number]; іі АДДДДДДДЩє

єі end. іі є

єАЩі є

ИНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

Рис. 5.3 Диалоговое окно Highlighting.

             Element -  элемент;
Color  -  цвет;
Attribute  -  атрибут;

Whitespace - разделитель; Normal - обычный текст; Comment - ком-

ментарий; Bold - жирный; Reserved Word - зарезервированной слово;

Italic - наклонный; Identifier - идентификатор; Symbol - иденти-

фикатор; Underline - подчеркивание; String - строка; Number -

число; Assembler - код ассемблера; Default FG - основной цвет по

умолчанию: Default BG - фоновый цвет по умолчанию.

Цветовое выделение текста

Чтобы изменить цвет элемента, выполните следующие шаги:

1. В блоке списка Element выделите элемент, который нужно

изменить.

2. Выберите нужные цвета в матрице цветов Colors.

Текущий основной цвет отмечен буквами FG; текущий фоновый

цвет отмечен буквами BG. Если основной и фоновый цвета

совпадают, то в цветном квадрате выводится FB.

* Чтобы выбрать основной цвет с помощью "мыши", щелкните

на нем ее кнопкой. Чтобы выбрать цвет с помощью клавиа-

туры, используйте для перемещения по матрице цветов

клавиши стрелок, а когда будете находиться на нужном

цвете, нажмите F.

* Чтобы выбрать фоновый цвет с помощью "мыши", щелкните

на нем ее правой кнопкой. Чтобы выбрать цвет с помощью

клавиатуры, нажимайте клавишу Tab до выбора матрицы

Colors, затем используйте для перемещения по матрице

цветов клавиши стрелок, а когда будете находиться на

нужном цвете, нажмите B.

3. Выберите командную кнопку OK.

Использование системных цветов Windows

Приложения Windows используют одни и те же цвета фонового

цвета и цвета текста. Чтобы изменить системные цвета Windows, ис-

пользуйте контрольную панель администратора программ.

Вы можете выбрать системные цвета Windows в редакторе IDE.

Чтобы использовать для элемента основной системный цвет, выполни-

те следующие шаги:

1. В диалоговом окне Highlighting выделите элемент в блоке

списка Elements.

2. Выберите параметр Default FG.

Для назначения фонового цвета используются те же шаги, но

выбирается параметр Default BG.

Изменение атрибутов текста

Чтобы выбрать атрибут элемента выполните следующие шаги:

1. В диалоговом окне Highlighting выделите элемент в блоке

списка Element.

2. Выберите атрибут в параметрах Attributes.

Примечание: Чтобы текст стал жирным или наклонным, ре-

дактор должен использовать шрифт фиксированного размера.

Для изменения шрифтов используйте команду Optionsі

EnviromentіEditor и выберите нужный параметр Font.

Выбор атрибутов отразится в окне с примером кода.

Вы можете выбрать элемент для изменения, щелкнув "мышью" на

его вхождении в окне примера кода. Например, если вы щелкните

"мышью" на зарезервированном слове program, в блоке списка

Element выбирается элемент Reserved Word (Зарезервированное сло-

во).

Печать исходного кода

Если вы хотите получить печатную копию своего исходного ко-

да, выберите команду FileіPrint. IDE расширяет символы табуляции

(заменяя табуляцию соответствующим числом пробелов и затем печа-

тает ваш файл.

Выделение элементов синтаксиса при печати

Вы можете напечатать текст таким образом, чтобы синтаксичес-

кие элементы были выделены. Перед печатью вы должны пропустить

выводимый на принтер текст черед программу-фильтр PRNFLTR.EXE:

1. Выберите команду FileіPrinter Setup.

2. Выберите параметр Syntax Printing.

3. Выберите командную кнопку OK.

4. Для печати текста выберите команду FileіPrint.

Если параметр Syntax Highlight установлен, ваш текст пе-

чатается с выделением синтаксических элементов.

При установке в своей системе Windows вы, вероятно, устано-

вили один или более драйверов принтеров, с помощью которых можно

выполнять печать из Windows. Команда FileіPrinter Setup позволяет

вам выбрать нужный принтер и задать его для печати из IDE и для

конфигурации. Например, вы можете печатать на бумаги с разным

размером. Из IDE вы можете с помощью выбора кнопки Set Up в диа-

логовом окне Printed Setup задать свой принтер для печати.

Примечание: Подробности о конфигурировании принтера

Windows см. в "Руководстве пользователя по Microsoft

Windows"

Работа с файлами

При программировании в IDE вы можете создавать новые файлы,

открывать существующие файлы и сохранять их. Основные команды ра-

боты с файлами перечислены в следующей таблице:

Операции с файлами Таблица 5.1

ЪДДДДДДДДДДДДДДДДДДВДДДДДДДДДї

і Команда і Описание і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіNew і Открывает новое окно редактиро-і

і і вания и присваивает ему временноеі

і і имя. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіOpen і Выводит диалоговое окно, с по-і

і і мощью которого можно открытьі

і і файл. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіSave і Сохраняет файл в активном окнеі

і і редактора на диске. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіSave As і Сохраняет файл в активном окнеі

і і редактора под другим именем. і

ГДДДДДДДДДДДДДДДДДДЕДДДДДДДДДґ

і FileіSave All і Сохраняет все модифицированныеі

і і файлы. і

АДДДДДДДДДДДДДДДДДДБДДДДДДДДДЩ

Открытие файлов

Чтобы открыть файл, выполните следующие шаги:

1. Выберите команду FileіOpen или командную кнопку open a

File оперативной полосы. Выводится диалоговое окно Open a

File (Открытие файла). Для задания открываемого файла вы

можете выполнить одно из следующих действий.

* В блоке ввода наберите полное имя файла.

* Наберите имя файла с трафаретными символами. Это от-

фильтровывает список файлов в соответствии с вашими

спецификациями. В списке Files выберите имя файла, ко-

торый вы хотите редактировать.

* Для вывода списка протокола (спецификаций имен файлов,

которые вы задавали ранее) щелкните "мышью" на символе

"стрелка вниз". Выберите требуемое имя файла или специ-

фикацию. Выбор спецификации файла выводит файлы, соот-

ветствующие данной спецификации.

* Дважды щелкнув "мышью" на имени другого каталога в

списке файлов, просмотрите содержимое этого каталога.

Выберите имя файла, который вы хотите редактировать.

2. Выберите командную кнопку OK.

Где находятся файлы?

Если это ваша первая попытка программирования под Windows,

вы можете не разобраться в том, где IDE ищет и сохраняет свои

файлы. IDE использует текущий рабочий каталог. Как определяется

текущий рабочий каталог, поясняется в следующем перечне:

* Если вы задаете основной файл, то каталог, в котором он

находится, становится текущим рабочим каталогом. При наи-

меновании файла вы можете указать полное имя маршрута.

Примечание: Подробнее об основных файлах рассказыва-

ется выше.

* Если вы не задаете основной файл, каталог, содержащий файл

в активном окне редактирования, становится текущим рабочим

каталогом. При сохранении файла в окне редактирования вы

можете указать полное имя маршрута.

* При отсутствии текущего окна редактирования текущим рабо-

чим каталогом становится каталог, в котором находится

BPW.EXE.

После открытия или создания файла Borland Pascal запоминает

его полный маршрут, даже если текущий рабочий каталог изменяется.

Поскольку текущий рабочий каталог обычно определяется основ-

ным файлом или файлом в активном окне редактирования, и эти эле-

менты сохраняются в файле конфигурации и файле оперативной облас-

ти, файл конфигурации и файл оперативной области неявно определя-

ют для проекта текущий рабочий каталог.

Примечание: Подробнее о рабочей операционной среде

рассказывается выше.

Работа с файлами в другом каталоге

Чтобы открыть файл в другом каталоге, выберите команду Fileі

Open и наберите в блоке ввода полное имя маршрута и имя файла.

Либо вы можете использовать для вывода файла в другом каталоге

список каталогов и выбор нужного файла. После того, как вы выбе-

рете имя файла и нажмете Enter, при следующем выборе команды

FileіOpen в том же сеансе вы увидите файлы в этом другом катало-

ге. Однако ваш текущий рабочий каталог не изменяется. Если вы

создали и сохранили новый файл, IDE сохраняет его в текущем рабо-

чем каталоге.

Если вы хотите работать с файлами в нескольких каталогах, то

можете использовать список протокола в диалоговом окне File Open.

Чтобы увидеть протокол, щелкните "мышью" на стрелке вниз справа

от блока ввода или нажмите клавиши Alt+"стрелка вниз"; в списке

может выводиться нужный вам файл.

Компиляция и выполнение

В IDE для Windows вы можете компилировать и выполнять прог-

раммы аналогично тому, как это делается в IDE для DOS. Об этом вы

можете прочитать в Главе 4. Стоит отметить следующее:

* Если ваша программа использует модуль WinCrt, когда прог-

рамма завершает выполнение, окно программы становится не-

активным. Чтобы продолжить работы в IDE, закройте его.

Чтобы пользователю не нужно было закрывать окно WinCrt,

используйте процедуру DoneWinCrt. Прочитайте о DoneWinCrt

в Главе 1 "Справочного руководства программиста".

* Если в программе, использующей модуль WinCrt, во время ее

выполнения происходит ошибка, окно, в котором работает

программа, становится неактивным перед ее завершением.

Чтобы посмотреть, что произошло, закройте окно программы.

Тогда вы увидите информационное окно, в котором выводится

номер ошибки и адрес, на котором она произошла.

Просмотр исходного кода

IDE для Windows содержит новое программное инструментальное

средство просмотра объектов - ObjectBrowser. Оно позволяет вам

исследовать программы и модули в программах и многое другое. Даже

если разрабатываемое вами приложение не использует объектно-ори-

ентированное программирование, вы найдете ObjectBrowser чрезвы-

чайно полезным средством. Вы можете просматривать иерархию объек-

тов, модулей и всех процедур, функций, переменных, типов, конс-

тант и другие используемые в программе идентификаторы. С помощью

ObjectBrowser вы можете делать следующее:

* Просмотреть в графическом виде в своей прикладной програм-

ме иерархию объектов, затем выбрать объект и просмотреть

все его процедуры, функции и другие содержащиеся в прог-

рамме идентификаторы.

* Вывести список всех глобальных идентификаторов, используе-

мые в вашей программе, и увидеть их описания, вывести все

ссылки на них в своей программе или перейти туда, где они

описываются в исходном коде.

* Вывести список всех используемых в программе модулей, за-

тем выбрать один из них и просмотреть список всех иденти-

фикаторов его интерфейсной части. В этом списке вы можете

выбрать идентификатор и просмотреть его, как любой другой

идентификатор своей программы.

* Выбрать идентификатор в исходном коде, затем просмотреть

детальную информацию по нему, щелкнув на этом идентифика-

торе правой кнопкой "мыши".

Перед использованием ObjectBrowser убедитесь, что в диалого-

вом окне OptionsіCompiler установлены следующие параметры:

* Debug Information (Информация для отладки).

* Locals Symbols (Локальные идентификаторы).

* Symbol Information (Информация об идентификаторах).

Для активизации ObjectBrowser выберите в меню Search (Поиск)

команду Objects (Объекты), Units (Модули) или Globals (Глобальные

идентификаторы). Если программа находится в текущем окне, или ос-

новной файл еще не скомпилирован, то перед выводом окна просмотра

IDE компилирует вашу программу.

Если программа будет успешно скомпилирована, сформирована

или построена, вы можете внести в исходный код некоторые измене-

ния, а если следующая компиляция завершиться неудачно, то вы все

равно сможете просматривать свою программу в том виде, как вышли

из нее при последней успешной компиляции. Чтобы это происходило,

должен быть установлен параметр Preserve Symbols (он установлен

по умолчанию). Данный параметр можно найти в диалоговом окне

OptionsіEnviromentіStartup. Поскольку Preserve Symbols - это па-

раметр запуска, изменение его установки не будет иметь действие,

пока вы не выйдите из IDE и не запустите ее снова.

Если у вас есть "мышь", то исходный код удобнее просматри-

вать, если задать активизацию ObjectBrowser правой кнопкой "мы-

ши". Затем, удерживая нажатой клавишу Ctrl, вы можете использо-

вать правую кнопку "мыши" для указания и проверки объекта, проце-

дуры, функции, переменной или другого идентификатора в вашем ис-

ходном коде и его анализа (вывода детальной информации).

Примечание: Для быстрого просмотра идентификатора, на

котором в исходном коде позиционирован курсор, вы можете

также выбрать в локальном меню окна редактирования команду

Browse Symbol at Cursor (Просмотр идентификатора в позиции

курсора).

Чтобы задать просмотр с помощью "мыши", выполните следующие

шаги:

1. Выберите команду OptionsіEnviromentіMouse.

2. Выберите параметр Browse as the Ctrl + Right Mouse Button

(Просмотр по клавише Ctrl + правая кнопка "мыши").

3. Выберите командную кнопку OK.

В верхней части окна ObjectWindows находится оперативная по-

лоса ObjectBrowser. Вы можете выбирать любую командную кнопку

оперативной полосы, щелкая на ней кнопкой "мыши" или используя

оперативную клавишу. Выбрав кнопку или соответствующую оператив-

ную клавишу, вы указываете ObjectBrowser на выполнение некоторого

действия. Вы будете видеть следующие командные кнопки, их клавиа-

турные эквиваленты и соответствующие действия:

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

F1 Help (Вывод справочной информации)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+G Go to (Переход на исходный код для выбранного

элемента)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+B Browse (Просмотр детальной информации по выб-

ранному элементу)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+V View (Вывод предыдущего окна просмотра):

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+O Display (Вывод иерархии объектов)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+R List (Перечень всех ссылок на идентификатор)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+P Print (Печать иерархии объектов)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctrl+W Replace (Замена текущего окна просмотра)

ЪДДДДДДДДДДДї

і і

і і

і і

АДДДДДДДДДДДЩ

Ctr+W Open (Открытие нового окна просмотра)

Рис. 5.4 Командные кнопки оперативной полосы ObjectBrowser.

Примечание: От того, с каким именно окном

ObjectBrowser вы работаете, зависит, какие в точности кноп-

ки выводятся в оперативной полосе.

Последние две кнопки показывают в действительности два раз-

личных представления одной кнопки. При первом использовании

ObjectBrowser вы увидите командную кнопку одиночного окна. Щелк-

ните на ней "мышью", и вы увидите кнопку множественных окон.

Когда вы выбираете кнопку одиночного окна и начинаете прос-

мотр, новое окно просмотра при каждом действии просмотра заменяет

текущее окно просмотра. При выборе кнопки множественных окон окна

остаются на экране, пока вы их не закроете.

Вы можете быстро изменить действие этих кнопок на обратное:

удерживая нажатой кнопку Shift, выберите следующее действие прос-

мотра. Например, если выводится кнопка множественных окон, то при

нажатии клавиши Shift следующее окно просмотра будет замещать те-

кущее.

Просмотр объектов

ObjectBrowser позволяет вам увидеть "общий план" иерархии

объектов, а также мелкие детали. Чтобы активизировать

ObjectBrowser и графически показать на экране объекты, выберите

команду SearchіObject. ObjectBrowser рисует ваш объект и показы-

вает в виде горизонтального дерева его соотношения "предок-пото-

мок". Красные линии в иерархии помогают вам ясно увидеть непос-

редственные отношения "предок-потомок" текущего объекта.

ЙННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»

є = і ? і О і П і * і Ы іЫЫЫЫЫЫЫЫЫBrowsing ObjectsЫЫЫЫЫЫЫЫЫЫіvі^є

єДДДБДДДБДДДБДДДБДДДБДДДБДДДДДДДДДДБДБДє

є і ^

є Ъґ±TWindowsObject±ГВґ±TWindow±ГДБДВВВґ±TMDIClient±іЫ

є і і ііі ±

є і і ііАґ±TListBox±Гґ±±

є і і іі ±

є і і іАДґ±TGroupBox±ГД±

є і і і ±

є і і АДДґ±TButton±Гґ±T±

єі±TObject±±ГЕВВї і ±

є іііі Аґ±TDialog±Гґ±TDglWindow±і ±

є іііі ±

є іііАґ±TStream±ГВґ±TEmsStream±і ±

є ііі і ±

є ііі Аґ±TDosStream±ГДґ±TBufSteram±і ±

є ііі ±

є ііАДґ±TScroller±і ±

є іі ±

є іАДДґ±TCollection±Гґ±TSortedCollection±ГВґ±TStringC±

є і і ±

є і Аґ±TStrColl±

є АДДДґЫApplicationЫГґ±THelloApp±і ±

є v

И<±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>±

Рис. 5.5 Просмотр иерархии объектов прикладной программы.

Чтобы увидеть более подробную информацию о конкретном объек-

те, дважды щелкните на нем кнопкой "мыши". Если вы не используете

"мышь", выберите объект с помощью клавиш управления курсором

(стрелок) и нажмите Enter. ObjectBrowser перечисляет используемые

в объекте символы (процедуры, функции, переменные и т.д.)

ЙННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»

є = і ? і О і П і * і Ы іЫЫЫЫЫЫЫЫЫBrowsing TApplicationЫЫЫЫЫіvі^є

єДДДБДДДБДДДБДДДБДДДБДДДБДДДДДДДДДДБДБДє

є є

є Browsing TApplication є

є ЪДДДДДДДДДДї є

є іv±±±±±±Status:Integer±±±±±±±±±±±±±^і є

є іv Name:PChar ±і є

є іv MainWindow:PWindowsObject ±і є

є ЪДДДДДДДї іv HAccTable:Word ±і ЪДДДДДДДДДДї є

є іTObjectГДґv KBHandleWnd:PWindowsObject ±ГДґTHelloApplі є

є АДДДДДДДЩ іp Init[PChar] ±і АДДДДДДДДДДЩ є

є іp ы Done ±і є

є іf ы IdleAction:Boolean ±і є

є іp ы InitApplication vі є

є і<Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>±і є

є АДДДДДДДДДДЩ є

є є

є Filters: ы F P V і p v є

є X і і і є і і є

є є

ИНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННј

Рис. 5.6 Просмотр детальной информации по объекту.

Слева от каждого идентификатора объекта выводятся одна или

более букв. Символ описывает вид идентификатора.

Буквенные символы в ObjectBrowser

Таблица 5.2

ЪДДДДДДДДДДДДВДДДї

і Буква і Функция і

ГДДДДДДДДДДДДЕДДДґ

і F і Функция і

і P і Процедура і

і T і Тип і

і V і Переменная і

і C і Константа і

і L і Метка і

і I і Наследование от предка і

і p і Приватный идентификатор і

і v і Виртуальный идентификатор і

АДДДДДДДДДДДДБДДДЩ

Фильтры

Те же символы, что идентифицируют вид идентификатора, выво-

дятся в матрице фильтров Filters в нижней части окна

ObjectBrowser. Вы можете использовать фильтры для выбора типа

идентификаторов, список которых хотите видеть.

Матрица фильтров содержит столбец для каждой буквы; буква

может выводиться в верхней или нижней строке.

Примечание: Для выбора типа идентификаторов вы можете

также использовать окно Browser Options. Выберите команду

OptionsіBrowser и идентификаторы, которые вы хотите вывести

в списке.

Чтобы просмотреть все экземпляры символа конкретного типа,

щелкните "мышью" на верхней ячейке столбца буквы. Например, чтобы

просмотреть все переменные в текущем выбранном объекте, щелкните

на верхней ячейке столбца V. Выводятся все переменные, используе-

мые в объекте.

Чтобы скрыть все экземпляры конкретного объекта или иденти-

фикатора, щелкните "мышью" на нижней ячейке столбца буквы. Напри-

мер, чтобы просмотреть только функции или процедуры объекта, вам

нужно сделать скрытыми все переменные. Щелкните "мышью" на нижней

ячейке в столбце V, и на верхних ячейках в столбцах F и P.

Примечание: Вы можете изменить несколько установок

фильтра сразу. Буксируйте "мышь" по ячейкам, которые вы хо-

тите выбрать в матрице Filters.

В некоторых случаях для идентификатора выводится более одной

буквы. Второй буквенный символ выводится непосредственно после

первой буквы, обозначающей тип идентификатора и служит для даль-

нейшего описания идентификатора:

* I обозначает наследуемый идентификатор;

* p обозначает приватный идентификатор;

* v обозначает виртуальный идентификатор.

Просмотр описаний перечисленных идентификаторов

Для просмотра описания конкретного содержащегося в списке

идентификатора используйте следующие методы:

* дважды щелкните на идентификаторе кнопкой "мыши";

* выберите идентификатор и щелкните "мышью" на кнопке Browse

или нажмите Ctrl+B;

* выберите идентификатор и нажмите клавишу Enter.

При просмотре в режиме одного окна (кнопка Window оператив-

ной полосы выводит на экран только одно окно), если вы хотите

вернуться на уровень выше, щелкните "мышью" на кнопке вывода пре-

дыдущего окна просмотра или нажмите клавиши Ctrl+V.

Хотя очень легко использовать оперативную полосу для выбора

одно- и многооконного режима, тоже самое вы можете делать с по-

мощью меню и диалогового окна. Чтобы новое окно просмотра заменя-

ло текущее, сделайте следующее:

1. Выберите команду OptionsіBrowser.

2. Установите параметр Replace Current sub-browsing.

3. Выберите командную кнопку OK.

Чтобы новое окно просмотра при выборе перечисленного в спис-

ке идентификатора оставалось на экране, сделайте следующее:

1. Выберите команду OptionsіBrowser.

2. Установите параметр New Browser sub-browsing.

3. Выберите командную кнопку OK.

Просмотр глобальных идентификаторов

С помощью ObjectBrowser вы можете просматривать все модули,

которые использует программа. Чтобы открыть окно со списком всех

используемых в программе модулей и именем самой прикладной прог-

раммы выберите команду SearchіUnits.

Выберите модуль, о котором вам нужно получить более подроб-

ную информацию. Как и в случае глобальных идентификаторов, вы мо-

жете выполнять поиск в списке модулей, набирая первые несколько

букв имени модуля в блоке ввода Search в нижней части окна.

После выбора модуля вы можете перечислить вывести список

всех идентификаторов в интерфейсной части модуля.

* В выводимом списке модулей дважды щелкните "мышью" на име-

ни модуля, выделите имя модуля и выберите Inspect или вы-

делите имя и нажмите клавишу Enter.

В этом списке вы можете выделить идентификатор и сделать

следующее:

- Чтобы увидеть описание идентификатора, дважды щелкните

на идентификаторе кнопкой "мыши", выделите его и выбери-

те командную кнопку Inspect (Проверка), или выделите его

и нажмите клавишу Enter.

- Чтобы перейти на строку исходного кода, где описывается

идентификатор, выберите командную кнопку Go To Source

Code.

- Чтобы вывести список всех ссылок на идентификаторы, вы-

берите командную кнопку Reference. Если вы выберите одну

из этих ссылок, ObjectBrowser позиционирует ваш курсор в

исходном коде.

* Чтобы перейти на описание идентификатора в исходном коде,

выберите командную кнопку Go to Source Code.

* Чтобы перечислить все ссылки на идентификатор в вашем при-

ложении, выберите командную кнопку Reference.

Просмотр идентификаторов в исходном коде

Вы можете просматривать идентификаторы в своем исходном ко-

де, не выводя сначала иерархию объектов или список идентификато-

ров. Выберите один из следующих методов:

* Подсветите идентификатор в своем коде и выберите команду

SearchіSymbol.

* Если ваша "мышь" настроена для просмотра, удерживая нажа-

той клавишу Ctrl, щелкните на идентификаторе в вашем коде

правой кнопкой "мыши".

* Для вывода локального меню окна редактирования нажмите

клавиши Alt+F10 или щелкните правой кнопки "мыши" и выбе-

рите команду Browse Symbol at Cursor.

Если выделенный вами идентификатор представляет собой струк-

турный тип, то ObjectBrowser выводит для этого типа информацию

обо всех идентификаторов в его области действия. Любой из них вы

можете выбрать для дальнейшей проверки. Например, если вы выбери-

те объектный тип, то увидите список всех идентификаторов в облас-

ти действия этого объекта.

Выполнение в IDE других программ

В IDE вы можете запускать по вашему выбору четыре других

программы: отладчик Turbo Debugger, пакет разработчика ресурсов

Resource Workshop, утилиту WinSight и профилировщик Turbo

Profiler. Чтобы запустить любую из этих программ, выберите коман-

ду Tools для открытия меню Tools и затем выберите инструменталь-

ное средство, которое вы хотите запустить. Чтобы изменить маршрут

или передать этим программам аргументы, используйте диалоговое

окно Tools (OptionsіTools).

В отличие от IDE для DOS вы не можете добавить в меню Tools

другие утилиты. Так как IDE для Windows представляет собой прик-

ладную программу Windows, на самом деле это не дает преимуществ.

Вы всегда можете переключиться на другую программу, используя

средства переключения задач Windows.

Настройка конфигурации IDE

Вы можете настраивать конфигурацию IDE, сохранять свою опе-

рационную среду и управлять проектами аналогично тому, как это

делается в IDE для DOS (см. выше). При сохранении операционной

среды и управлении проектами между IDE для DOS и IDE для Windows

существуют следующие различия:

* IDE для Windows всегда сохраняет файл оперативной области

в том же каталоге, что и текущий файл конфигурации. Вы не

можете переопределить это, как в IDE для DOS.

* Чтобы очистить основной файл, выберите в IDE для Windows

команду CompileіClear Primary File. Вы можете использовать

этот метод также и в IDE для DOS, но у вас есть возмож-

ность выбора в диалоговом окне Primary File командной

кнопки Clear Primary File.


Глава 6. Отладка в интегрированной среде

Интегрированная интерактивная среда разработки программ

Borland Pascal (IDE) включает в себя ряд средств, облегчающих

разработку программ: автоматическое управление проектами, средств

обеспечения модульной структуры программы, быструю компиляцию и

простые в использовании оверлеи. Но несмотря на все это ваша

программа все равно может содержать ошибки, что не позволит ей

корректно работать.

IDE для DOS Borland Pascal предоставляет вам инструменталь-

ные средства для отладки программ, то есть поиска и исправления

ошибок. В этой главе описываются инструментальные средства и про-

цедуры отладки программы в интегрированной среде, включая следую-

щие темы:

* обзор ошибок и методов отладки;

* управление выполнением программы;

* проверка значений;

* остановки выполнения программы.

Данная глава посвящена встроенному отладчику IDE для DOS.

Все описанные процедуры применимы также к Турбо отладчику (Turbo

Debugger) и Турбо отладчику для Windows, хотя имена меню и нажи-

маемые клавиши в них могут отличаться.

Что такое отладка?

Отладка - это процесс поиска и исправления ошибок в програм-

ме, препятствующих корректной работе программы. Перед тем как уг-

лубиться в специфические средства IDE Borland Pascal, которые по-

могают при отладке, дадим краткое описание видов ошибок, которые

вы можете наблюдать, и различного рода операций, которые вы будет

использовать для их поиска.

Какие существуют виды ошибок?

Существует три основных типа ошибок: ошибки этапа компиля-

ции, ошибки этапа выполнения и логические ошибки. Если вы уже хо-

рошо знакомы с этими понятиями, то можете пропустить следующий

раздел до методов отладки.

Ошибки этапа компиляции

Ошибки этапа компиляции или синтаксические ошибки происхо-

дят, когда ваш исходный код нарушает правила синтаксиса Паскаля.

Borland Pascal на может скомпилировать вашу программу, пока она

не будет содержать допустимые операторы Паскаля. Когда компилятор

встречает оператор, который он не может распознать, соответствую-

щий файл выводится в окне редактирования, курсор позиционируется

на то место, которое не понял компилятор, и выводится сообщение

об ошибке.

Компилятор, работающий в режиме командной строки, также дает

вам некоторую информацию. Когда он находит синтаксическую ошибку,

то выводит содержащую ошибку строку с номером этой строки и сооб-

щением об ошибке.

Наиболее общей причиной ошибок этапа компиляции являются

ошибки набора (опечатки), пропущенные точки с запятой, ссылки на

неописанные переменные, передача неверного числа (или типа) пара-

метров процедуры или функции и присваивание переменной значений

неверного типа.

После исправления ошибки вы можете выполнить компиляцию за-

ново. После устранения в программе всех синтаксических ошибок и

ее успешной компиляции программа будет готова к выполнению и по-

иску ошибок этапа выполнения и логических ошибок.

Ошибки этапа выполнения

Ошибки этапа выполнения или семантические ошибки происходят,

когда вы компилируете полную программу, которая при ее выполнении

делает что-то недопустимое. То есть, программа содержит допусти-

мые операторы Паскаля, но при выполнении операторов что-то проис-

ходит неверно. Например, ваша программа может пытаться открыть

для ввода несуществующий файл или выполнить деление на ноль.

Когда программа Borland Pascal обнаруживает такую ошибку,

она завершает выполнение и выводит сообщение следующего вида:

Run-time error ## at seg:ofs

Если вы выполняете программу из IDE, Borland Pascal автома-

тически находит вызвавший ошибку оператор (как в случае синтакси-

ческих ошибок). Если вы выполняете программу вне IDE, то вы може-

те запустить IDE и, чтобы найти вызвавший ошибку оператор, ис-

пользовать команду SearchіFind Error, которая дает вам адрес сег-

мента и смещения (seg:ofs). Если вы используете компилятор ко-

мандной строки, то можете для поиска ошибки использовать параметр

/F.

Логические ошибки

Логические ошибки - это ошибки проектирования и реализации

программы. То есть, ваши операторы допустимы и что-то делают, но

не то, что вы предполагали. Эти ошибки часто трудно отследить,

поскольку IDE не может найти их автоматически, как синтаксические

и семантические ошибки. К счастью, IDE включает в себя средства

отладки, помогающие вам найти логические ошибки.

Логические ошибки приводят к некорректному или непредвиден-

ному значению переменных, неправильному виду графических изобра-

жений или невыполнению кода, когда это ожидается. В остальной

части этой главы обсуждаются методы отслеживания этих логических

ошибок.

Методы отладки

Иногда, когда программа делает что-то непредвиденная, причи-

на достаточно очевидна, и вы можете быстро исправить код програм-

мы. Но другие ошибки более трудноуловимы и вызываются взаимодейс-

твие различных частей программы. В этих случаях лучше всего оста-

новить вашу программу в заданной точке, пройти ее шаг за шагом и

просмотреть состояние переменных и выражений. Такое управляемое

выполнение - ключевой элемент отладки.

В данном разделе описываются различные возможности отладки в

IDE для DOS Borland Pascal.

Выполнение по шагам и трассировка

Команды выполнения по шагам Step Over и трассировки Trace

Into меню выполнения Run дают вам возможность построчного выпол-

нения программы. Единственное отличие выполнения по шагам и трас-

сировки состоит в том, как они работают с вызовами процедур и

функций. Выполнение по шагам вызова процедуры или функции интерп-

ретирует вызов как простой оператор и после завершения подпрог-

раммы возвращает управление на следующую строку. Трассировка

подпрограммы загружает код этой подпрограммы и продолжает ее

построчное выполнение.

Остановка выполнения

Существует два способа сообщить IDE, что программу нужно вы-

полнить до определенной точки, а затем остановить. Первый и прос-

тейший способ состоит в том, чтобы найти позицию в программе, где

вы хотите остановиться, затем выбрать в меню Run команду Go to

Cursor (Выполнение до позиции курсора). Ваша программа выполняет-

ся как обычно, пока не достигнет оператора, где она должна оста-

новиться. В этой точке вы можете проверить значения и продолжать

выполнение непрерывно или по шагам.

Второй способ состоит в том, чтобы остановить в определенной

заданной точке вашу программу. Эта точка называется точкой оста-

нова. Когда вы выполняете программу, она останавливается перед

выполнением оператора в точке останова. Точки останова - это бо-

лее гибкий механизм, чем использование метода выполнения до пози-

ции курсора (Go to Cursor), поскольку в программе вы можете уста-

новить несколько точек останова.

Отслеживание и модификация

При выполнении программы по шагам вы можете наблюдать ее вы-

вод несколькими способами. Первый состоит в переключении в случае

необходимости экранов. При втором способе используется второй мо-

нитор. В-третьих, для вывода программы вы можете открыть окно в

IDE для DOS.

Кроме того, чтобы показать вывод программы, встроенный от-

ладчик позволяет вам просматривать значения переменных, выражений

и структур данных. С помощью команды Wathes в меню Debug в окне

просмотра Watches вы можете добавлять или удалять отслеживаемые

элементы. В этом диалоговом окне вы можете проверять переменные и

выражения и изменять значения любых переменных, включая строки,

указатели, элементы массива и поля записей, что позволяет вам

проверять реакцию программы на различные условия.

Поиск

Если вам нужно найти в программе описания процедуры или

функции, либо определения объекта, это легко можно сделать с по-

мощью средства просмотра объектов ObjectBrowser. С помощью меню

Search и выбора команд Objects, Globals, Units или Symbols выбе-

рите соответствующее окно просмотра. См. Главу 4 ("Программирова-

ние в интегрированной среде для DOS"), где о просмотре кода расс-

казывается более подробно.

Генерация отладочной информации

Перед отладкой программы вам нужно указать компьютеру, что

нужно сообщить компилятору на необходимость генерации некоторой

дополнительной информации, благодаря которой он сможет отслежи-

вать, какие строки исходного кода соответствуют отдельным частям

выполняемой программы. Эта дополнительная информация называется

отладочной информацией. Вы можете включить эту информации, выбрав

соответствующий параметр (кнопку с независимой фиксацией)

диалогового окна Compiler Options интегрированной среды (команда

OptionsіCompiler) или включив в код программы соответствующую ди-

рективу компилятора.

Когда вы компилируете программу Borland Pascal, компилятор

всегда сохраняет список используемых идентификаторов, который на-

зывается таблицей идентификаторов. В этом списке отслеживаются

имена всех переменных, констант, типов, процедур и функций. Для

целей отладки там сохраняются также номера строк исходных файлов,

где встречаются все эти идентификаторы. Выбрав в диалоговом окне

Compiler Options параметр Debug Information (Отладочная информа-

ция) или задав директиву компилятора $D+, вы указываете компиля-

тору, что в таблицу идентификаторов нужно добавить информацию о

номерах строк.

Встроенная и автономная отладка

В диалоговом окне параметров отладчика Debugger Options

(OptionsіDebugger) вы можете сообщить компилятору, нужно ли гене-

рировать отладочную информацию для использования встроенного или

автономного отладчика (такого как Turbo Debugger), или для обоих.

Если вы хотите использовать встроенный отладчик, то нужно выбрать

параметр Integrated (который устанавливается по умолчанию).

Информация в модулях

Если вы пишете большую программу, которая использует модули,

и отладочная информация получается слишком объемной, вы можете

сократить объем этой информации для отдельных модулей, используя

в них директиву компилятора $L- или отменив в диалоговом окне

Compiler Options параметр Local Symbols (Информация о локальных

идентификаторах).

Если вы выключите для модуля генерацию информации о локаль-

ных идентификаторах, то из отладочной информации для этого модуля

исключаются все идентификаторы, описанные в секции реализации мо-

дуля. Для всех идентификаторов в интерфейсной секции будет гене-

рироваться информацию для всех идентификаторов, так что вы сможе-

те использовать средства отладки.

Управление выполнением

Основной смысл использования встроенного отладчика состоит в

управляемом выполнении. Отслеживая выполнение каждой инструкции,

вы можете легко определить, какая часть вашей программы вызывает

проблемы. В отладчике предусмотрено пять основных механизмов уп-

равления выполнением программы, которые позволяют вам:

- выполнять инструкции по шагам;

- трассировать инструкции;

- выполнять программу до заданной точки;

- находить определенную точку;

- выполнять сброс программы.

Само по себе выполнение программы по шагам может быть недос-

таточно полезным, разве что поможет найти то место, где что-то

происходит совершенно неверно. Но управляемое выполнение дает вам

возможность проверять состояние программы и ее данных, например,

отслеживать вывод программы и ее переменные, как описывается в

данной главе.

Что такое шаг?

Когда вы отлаживаете программу, наименьшим выполняемым эле-

ментом является строка. Этот означает, что вы можете управлять

отладкой до уровня отдельной строки исходного кода программы. По-

этому, если на одной строке программы содержится несколько опера-

торов Паскаля, вы не сможете отладить эти операторы индивидуаль-

но. С другой стороны, с целью отладки вы можете разбить оператор

на несколько строк, которые будут выполняться за один шаг.

Все выполнение в отладчике, включая выполнение по шагам,

трассировку и останов, основывается на строках. Подсвечивая стро-

ку, встроенный отладчик всегда сообщает вам, какую строку вы вы-

полняете следующей (строка выполнения). Строка выполнения выво-

дится цветом, отличным от нормального цвета. Благодаря этому вы

можете легко видеть, где находитесь.

Выполнение программы по шагам

Выполнение по шагам - это простейший способ выполнения прог-

раммы по элементарным фрагментам. Выбор команды RunіStep Over или

нажатие клавиши F8 вызывает выполнение отладчиком всего кода в

операторе, указанном строкой выполнения, включая любые вызываемые

на ней процедуры или функции, пока управление не вернется обратно

к вам. После этого строка выполнения указывает следующий выполня-

емый оператор.

Возьмем, например, следующий пример программы:

program StepTest;

function Negate(X: Integer): Integer;

begin

Negate := -X;

end;

var

I: Integer;

begin

for I := 1 to 10 do Writeln(Negate(I));

end.

Пример 6.1 Простая программа, выполняемая по шагам.

Если в окне редактирования вы выведите StepTest и нажмете

клавишу F8, то строка выполнения перемещается на оператор begin в

начале основного цикла, поскольку это первое, что выполняется в

программе. Второе нажатие клавиши F8 выполняет begin и перемещает

строку выполнения вниз до оператора for на следующей строке. Пос-

ле этого нажатие F8 вызывает выполнение всего цикла for; на экран

пользователя выводятся числа от -1 до -10, а строка выполнения

перемещается к end.

Хотя функция Negate вызывается 10 раз, строка выполнения ни-

когда на нее не перемещается. Выполнение по шагам позволяет от-

ладчику не показывать детали любых вызовов для отдельной строки.

Выполнение по шагам вызывает выполнение всего цикла for сразу,

поэтому вы не сможете видеть изменения в ходе выполнения цикла.

Если вы хотите видеть подробности цикла, внесите в пример следую-

щее простое изменение.

begin

for I := 1 to 10 do

Writeln(Negate(I));

end.

Пример 6.2 Изменение формата кода для лучшего выполнения по

шагам.

Поскольку оператор Паскаля может занимать несколько строк,

такая программа будет в точности эквивалентна предыдущей версии,

а генерируемый код будет идентичен. Но поскольку оператор Writeln

теперь находится на отдельной строке, отладчик может интерпрети-

ровать его отдельно. Если теперь вы будете нажимать клавишу F8,

то увидите, что строка выполнения будет при выполнении цикла 10

раз возвращаться на Writeln.

Трассировка программы

Трассировка программы во многом аналогичная ее выполнению по

шагам. Единственное исключение состоит в том, что когда встреча-

ется оператор вызова процедуры или функции, при трассировке эти

процедуры и функции также выполняются по шагам, а при простом вы-

полнении по шагам управление возвращается вам после завершения

выполнения подпрограммы.

Например, чтобы выполнить трассировку кода в Примере 6.1,

загрузите файл, затем выберите команду RunіTrace Into или нажмите

клавишу F7. Когда вы в первый раз делаете это, управление переме-

щается на оператор begin основной программы. Повторное нажатие F7

снова перемещает строку управления на оператор for. После этого

нажатие клавиши F7 трассирует вызов функции Negate - строка вы-

полнения перемещается на оператор begin в блоке функции. Если вы

продолжаете нажимать F7, строка выполнения перемещается по функ-

ции, а затем, когда вы дойдете до оператора end, возвращается к

оператору вызова.

Формат вашей программы влияет на поведение строки выполнения

при трассировке, хотя и не в такой степени как при пошаговом вы-

полнении. Если код сформатирован как в Примере 6.1, то трассиров-

ка оператора for приводит к выполнению 10 раз функции Negate. Ес-

ли вы разобъете оператор for на две строки, как в Примере 6.2, то

трассировка оператора end функции возвращает строку выполнения ту

строку основной программы, которая будет выполняться следующей.

Первые девять раз это снова будет вызов функции. В десятый раз

строка выполнения перемещается на оператор end программы.

Трассировка или выполнение по шагам?

Пошаговое выполнение или трассировка выполняет одно и то же

действие, кроме того случая, когда строка выполнения находится

под строкой вызова процедуры или функции, или когда вы выполняете

оператор begin в начале программы или модуля, который использует

другие модули.

Выполнение begin в блоке begin..end основной программы вызы-

вает код инициализации для любого используемого в программе моду-

ля в том порядке, который указывается в операторе uses программы.

Аналогично, выполнение оператора begin в начале секции инициали-

зации вызывает код инициализации для любых модулей, используемых

в данном модуле. Выполнение по шагам и трассировка работает в

этих случаях как и можно ожидать - пошаговое выполнение begin вы-

полняет всю инициализацию, возвращая управление на следующий опе-

ратор только после того, как все будет завершено; при трассировке

выполняется трассировка кода инициализации.

Примечание: О модулях и их секциях инициализации расс-

казывается в Главе 7 "Модули Borland Pascal".

Пошаговое выполнение и трассировка методов объектов

Если вы используете в своей программе объекты, отладчик ин-

терпретирует свои методы аналогично тому, как он интерпретирует

обычные процедуры и функции. Пошаговое выполнение метода интерп-

ретирует метод как один шаг, возвращая управление к отладчику

после того как метод завершает выполнение. Трассировка метода

загружает и выводит на экран код метода и трассирует его операто-

ры.

Пошаговое выполнение и трассировка внешнего кода

Если вы выполняете в программе компоновку с внешним кодом,

используя для этого директиву компилятора {$L имя_файла}, то если

компонуемый файл .OBJ содержит отладочную информацию, вы можете

трассировать этот код или выполнять его по шагам. Borland Pascal

ничего не знает об отлаживаемом вами коде в этих модулях, но он

будет показывать вам соответствующие строки в исходном коде.

Примечание: Требования к внешнему коду поясняются в

Главе 25 "Руководства по языку".

Вы можете отлаживать внешний код, написанный на любом языке,

включая Си, С++ и ассемблер. Если код отвечает требованиям для

внешней компоновки и содержит полную стандартную отладочную ин-

формацию, интегрированная среда отладчика может выполнять его по

шагам или трассировать.

Выполнение больших фрагментов

Иногда, конечно, нежелательно выполнять по шагам всю прог-

рамму только для того, чтобы добраться до того места, где возни-

кает проблема. Отладчик дает вам возможность выполнять сразу

большой фрагмент программы до той точки, где вы хотите начать вы-

полнение по шагам.

Чтобы задать в программе точку, до которой вы хотите ее вы-

полнить, а затем остановиться, используйте команду RunіGo To

Cursor (ВыполнениеіВыполнение до курсора) или клавишу F4. (Этим

вы сообщите отладчику, что не хотите выполнять программу по ша-

гам, пока не достигнете заданной точки.) Позиционируйте курсор на

той строке, где вы хотите возобновить управление отладкой, затем

нажмите клавишу F4. Заметим, что вы можете сделать это как в на-

чале сеанса отладки, так и когда уже выполните часть программы по

шагам или протрассируете.

Поиск нужного места

IDE предусматривает два способа поиска в программе заданного

места. Простейший способ предоставляет команда Find Procedure ме-

ню Search. Команда Find Procedure (Поиск процедуры) запрашивает у

вас имя процедуры или функции, затем находит соответствующую

строку в файле, где определяется эта подпрограмма. Этот подход

полезно использовать при редактировании, но его можно комбиниро-

вать с возможностью выполнения программы до определенной точки,

чтобы пройти программу до той части кода, которую вы хотите отла-

дить.

Возврат

Иногда в ходе отладки полезно узнать, как вы попали в данную

часть кода. Окно Call Stack (Стек вызова) показывает вам последо-

вательность вызовов процедур или функций, которые привели к теку-

щему состоянию (глубиной до 128 уровней). Для вывода окна Call

Stack используйте команду DebugіCall Stack.

Окно Call Stack особенно полезно использовать, если вы слу-

чайно начали трассировку кода, который хотели бы выполнить за

один шаг. В стеке вызовов вы можете найти тот вызов, который на-

чали трассировать по ошибке, затем выбрать команду Run to Cursor,

чтобы выполнить за один шаг остальную часть вызова.

Повторное выполнение

В ходе сеанса отладки иногда желательно начать все сначала.

Выберите команду RunіReset Program или нажмите клавиши Ctrl+F2.

Это приведет к полному сбросу, так что выполнение по шагам, или

трассировка начнется в начале основной программы.

Отслеживание вывода программы

При выполнении программы по шагам часто полезно просмотреть

вывод программы, называемый экраном пользователя. В прикладной

программе Windows это достаточно просто, так как программа уже

выполняется в отдельном окне. Однако в DOS это не так легко. К

счастью, Borland Pascal предоставляет вам несколько способов

просмотра экрана пользователя.

Переключение экранов

В любой момент сеанса отладки вы можете выполнять переключе-

ние экрана IDE и экрана пользователя. Чтобы вывести экран пользо-

вателя, нажмите клавиши Alt+F5. Чтобы вернуться в IDE, нажмите

любую клавишу или щелкните "мышью".

При выполнении программы отладчик также может переключать

экраны автоматически. Управлять характером переключения экранов

вы можете с помощью параметров Display Swapping (Переключение эк-

рана) диалогового окна Debugger. По умолчанию задано эффективное

переключение. Это означает, что экран пользователя выводится

только в том случае, если выполняемый оператор выводит информацию

на экран или вызывает процедуру (даже если эта процедура ничего

на экран не выводит). После завершения вывода экран переключается

обратно в IDE.

Вы можете также сообщить отладчику, что переключать экран

нужно на каждой строке, независимо от вывода, или не переключать

их вовсе. Переключение экранов для каждой строки полезно исполь-

зовать, если ваша программа посылает информацию непосредственно

на экран, что может затереть содержимое экрана IDE.

Окно Output

IDE для DOS предусматривает для экрана пользователя окно,

которое называется окном вывода. Выбрав команду меню Debugі

Output, вы можете открыть (вывести на переднем плане) активное

окно, содержащее вывод программы. Настроить размер этого окна

можно аналогично окну редактирования.

Использование двух мониторов

IDE предоставляет вам возможность использования для целей

отладки второго монитора. Этот монитор должен быть монохромным

дисплеем (поскольку использует память, отличную от цветного дисп-

лея), и вам нужно будет запустить IDE с параметром /D. В режиме с

двумя мониторами экран IDE выводится на монохромном экране, вывод

вашей программы - на цветном экране, а переключение экранов не

выполняется.

Просмотр значений

Выполнение программы по шагам или ее трассировка могут по-

мочь вам найти ошибки в алгоритме программы, но обычно желательно

также знать, что происходит на каждом шаге со значениями отдель-

ных переменных. Например, при выполнении по шагам цикла for по-

лезно знать значение переменной цикла. IDE Borland Pascal имеет

два инструментальных средства для проверки содержимого переменных

программы: окно Watches (Просмотр) и диалоговое окно Evaluate and

Modify (Вычисление и модификация).

Что такое выражение?

Оба средства вычисление и просмотра работают на уровне выра-

жений, поэтому важно определить, что считается выражением. Выра-

жение состоит из констант, переменных и структур данных, скомби-

нированных с помощью операций и большинства встроенных функций.

Почти все, что вы можете использовать в правой части оператора

присваивания, может также использоваться в качестве отладочного

выражения. Точные спецификации показаны в Таблице 6.1.

Элементы выражений отладчика Таблица 6.1

ЪДВДДДДДДДДДДДї

і Элемент выражения і Допустимые значения і

ГДЕДДДДДДДДДДДґ

і Константы і Все допустимые типы: Boolean, Byte,і

і і Char, перечислимый тип, Integer,і

і і Longint, Real, Shortint, Word иі

і і строковый тип. і

ГДЕДДДДДДДДДДДґ

і Переменные і Все типы, включая типы, определен-і

і і ные пользователям. і

і і і

і целочисленный тип і Любое целочисленное выражение с пе-і

і і ременными границами диапазона. і

і і і

і тип с плавающей точкойі Любые выражения с плавающей точкойі

і і или целочисленные выражения; лишниеі

і і значащие цифры отбрасываются. і

і і і

і символьный тип і Любое символьное выражение, включаяі

і і печатаемые символы в одинарных ка-і

і і вычках, целочисленные выражения,і

і і тип которых приведен к типу Char, иі

і і контанты ASCII (#xx). і

і і і

і булевский тип і True, False и все булевские выраже-і

і і ния. і

і і і

і перечислимый тип і Любые совместимые перечислимые кон-і

і і станты или целочисленные выраженияі

і і в рамках диапазона, тип которыхі

і і приведен к совместимому перечисли-і

і і мому типу. і

і і і

і указатель і Любые совместимые указатели или вы-і

і і ражения с приведенными к ним типа-і

і і ми; функция Ptr с соответствующимі

і і параметрами. і

і і і

і строковый тип і Любая строковая константа (текст ві

і і одинарных кавычках); строковые пе-і

і і ременные; строковые выражения, сос-і

і і тоящие из конкатенированных строко-і

і і вых констант и переменных. і

і і і

і множество і Любая множественная константа; лю-і

і і бое выражение, совместимое с мно-і

і і жественным типа, в котором исполь-і

і і зуются операции +, - и *. і

ГДЕДДДДДДДДДДДґ

і Приведение типа і Соблюдаются стандартные правилаі

і і Паскаля. і

ГДЕДДДДДДДДДДДґ

і Операции і Все операции Borland Pascal. і

ГДЕДДДДДДДДДДДґ

і Встроенные функции і Все функции, допустимые в выражени-і

і і ях-константах. і

ГДЕДДДДДДДДДДДґ

і Массивы і Массивы Borland Pascal - Mem, MemL,і

і і MemW. і

АДБДДДДДДДДДДДЩ

Просмотр выражений

Если вы хотите отслеживать значение переменной или выражения

при выполнении программы по шагам, то можете открыть окно прос-

мотра Watches. Это окно IDE показывает переменные и их значения в

каждый конкретный момент.

Чтобы открыть окно Watches, выберите команду WindowіWatch.

IDE открывает активное окно Watches без активных записей. Если вы

выберите переменную для просмотра, IDE автоматически открывает

окно Watches (если вы этого еще не сделали).

Добавление просматриваемого выражения

Чтобы добавить в окно Watches переменную, выберите команду

DebugіWatchіAdd Watch или нажмите клавиши Ctrl+F7. Если окно

Watches является активным окном, вы можете добавить выражение

просмотра, нажав клавишу Ins. Отладчик открывает диалоговое окно,

запрашивающее у вас тип просматриваемого выражения. По умолчанию

выражением считается слово в позиции курсора в текущем окне ре-

дактирования. Просматриваемые выражения, которые вы отслеживали

ранее, сохраняются в списке протокола.

Отслеживание текущего просматриваемого выражения

Последнее добавленное или модифицированное просматриваемое

выражение является текущим просматриваемым выражением, которое

указывается выводимым слева от него символом жирной левой точки.

Если окно Watches активно, вы можете также удалить текущее выра-

жение, нажав клавишу Del или Ctrl+Y. Чтобы удалить все просматри-

ваемые выражения, выберите команду DebugіWatchіRemove All

Watches.

Редактирование просматриваемых выражений

Чтобы отредактировать просматриваемое выражение, нужно дваж-

ды щелкнуть на этом выражении "мышью" или сделать это выражение

текущим, затем нажать клавишу Enter или выбрать команду Debugі

WatchіEdit Watch. Отладчик открывает диалоговое окно, аналогичное

тому, которое используется для добавления просматриваемого выра-

жения, которое позволяет вам отредактировать текущее выражение.

При выборе командной кнопки OK или нажатии клавиши Enter отредак-

тированное выражение заменяет оригинал.

Форматирование просматриваемых выражений

Окно Watches позволяет вам несколькими способами форматиро-

вать просматриваемые выражения, добавляя запятую и один или более

спецификаторов формата. Например, хотя целочисленные значения вы-

водятся обычно в десятичном виде, указав после него ,H, вы можете

задать вывод выражения в шестнадцатиричном формате. Допустимые

спецификаторы формата и их действие перечисляются в Таблице 6.2.

Спецификаторы формата в выражениях отладчика

і Символ і Тип, на который і Функция і

і і он влияет і і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і $, H или X і целочисленные типы і Шестнадцатиричный. Выво-і

і і і дит целочисленные значе-і

і і і ния в префиксом $, вклю-і

і і і чая те, которые содержат-і

і і і ся в структуре данных. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і C і Char, строковые і Символьный. Выводит спе-і

і і типы і циальные символы для ко-і

і і і дов ASCII 0..31. По умол-і

і і і чанию такие символы выво-і

і і і дятся в виде значенийі

і і і #xx. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і D і целочисленные і Десятичный. Выводят цело-і

і і типы і численные значения в де-і

і і і сятичном виде (включаяі

і і і те, которые содержатся ві

і і і структурах данных). і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і Fn і с плавающей точкой і С плавающей точкой. Выво-і

і і і дит n значащих цифр, гдеі

і і і n лежит в диапазоне 2..18і

і і і (по умолчанию - 11). і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і nM і все і Дамп памяти. Выводит nі

і і і байт памяти, начиная сі

і і і адреса, указываемого вы-і

і і і ражением. Если n не зада-і

і і і но, то по умолчанию оноі

і і і равно значению размера ві

і і і байтах типа переменной. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і P і указатели і Указатель. Выводит указа-і

і і і тели по адресу сегм:смещі

і і і (на не Ptr(сегм:смещ),і

і і і как это делается по умол-і

і і і чанию. і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і R і записи, объекты і Запись. Выводит имена по-і

і і і лей, например, (X:1;і

і і і Y:10; Z:5) вместо (1, 10,і

і і і 5). і

ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДЕДґ

і S і Char, строки і Строки. Выводит символыі

і і і ASCII 0..31 в виде #xx.і

і і і Использует только для мо-і

і і і дификации дампов памятиі

і і і (см. выше nM). і

АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДБДЩ

Вычисление и модификация

Кроме добавления просматриваемых выражений при выполнении

программы, отладчик имеет средство, позволяющее вам в любой мо-

мент вычислять выражения и изменять на этапе выполнения значения

переменных.

Вычисление выражений

Чтобы вычислить выражение, выберите команду DebugіEvaluate/

Modify или нажмите клавиши Ctrl+F4. Отладчик выводит диалоговое

окно Evaluate and Modify (Вычисление и модификация). По умолчанию

слово в позиции курсора в текущем окне редактирования выводится

подсвеченным в поле Expression (Выражение). Вы можете отредакти-

ровать это выражение, набрать другое выражение или выбрать вычис-

ляемое вами ранее выражение из списка протокола.

Когда вы нажимаете Enter или щелкаете "мышью" на командной

кнопке Evaluate, текущее значение выражения в поле Expression по-

казывается в поле Result.

Допустимые выражения для вычисления подчиняются тем же пра-

вилам, что и выражения для сравнения. Для вывода результатов вы-

числения выражения действую спецификаторы формата, перечисленные

в Таблице 6.2.

Модификация переменных

Во время отладки с помощью диалогового окна Evaluate and

Modify вы можете изменить значение переменной. Введите переменную

в поле Expression, затем в поле New Value наберите новое значение.

При изменении значений переменных следует иметь в виду сле-

дующее:

* Вы можете изменять только отдельные переменные или элемен-

ты массивов или записей, но не сами массивы и записи.

* Выражения в поле New Value должны отвечать ограничениям

для выражений, перечисленных в Таблице 6.1.

* Выражение в поле New Value (Новое значение) должно в ре-

зультате вычисления давать результат, совместимый по прис-

ваиванию с переменной, которой вы хотите ее присвоить.

Здесь можно руководствоваться следующим правилом: если

присваивание дает при компиляции ошибку, то оно не являет-

ся допустимым значением модификации.

Использование точек останова

Borland Pascal дает вам возможность устанавливать в свое

программе для целей отладки точки останова. Точка останова - это

обозначенная в коде программы позиция, в которой вы хотите прек-

ратить выполнение программы и вернуть выполнение отладчику. В

этом смысле точка останова работает аналогично команде Go to

Cursor, при которой программа выполняется обычным путем до дости-

жения определенной точки. Основное различие состоит в том, что вы

можете задать несколько точке останова и точки останова, которые

будут срабатывать не при каждом их достижении.

Задание точек останова

Чтобы установить в своем в своем коде точку останова, пере-

местите курсор на той строке, где вы хотите остановиться. Строка

должна содержать выполняемый код и не может быть комментарием,

описанием или пустой строкой. Выбор команды Toggle Breakpoint в

локальном меню окна редактирования или нажатие клавиш Ctrl+F8 ус-

танавливает на строке точку останова, которая обозначается подс-

веткой всей строки.

Теперь при выполнении программы из IDE она будет останавли-

ваться при достижении данной строки, но перед ее выполнением.

Строка, содержащая точку останова, выводится при этом в окне ре-

дактирования как строка выполнения. В этот момент вы можете вы-

полнить любые другие действия по отладке (выполнение программы по

шагам, трассировку, просмотр и вычисление).

Отмена точке останова

Чтобы отменить точку останова, поместите курсор на содержа-

щую ее строку и выберите в локальном меню окна редактирования ко-

манду Toggle Breakpoint или нажмите клавиши Ctrl+F8.

Модификация точек останова

В процессе сеанса отладки IDE отслеживает все точки остано-

ва. Вместо того, чтобы шарить по исходному коду в поиске точек

останова, она обслуживать точки останова в одном диалоговом окне

Breakpoints. Для вывода диалогового окна Breakpoints выберите ко-

манду ViewіBreakpoints. В этом диалоговом окне вы можете устанав-

ливать, удалять, редактировать и просматривать свои точки остано-

ва.

Командные кнопки диалогового окна Breakpoints работают сле-

дующим образом:

* Чтобы добавить новую точку останова, подсветите пустую

строку в списке и выберите командную кнопку Edit.

* Чтобы отменить точку останова, подсветите ее и выберите

кнопку Clear.

* Чтобы модифицировать существующую точку останова, подсве-

тите ее и выберите командную кнопку Edit.

* Чтобы найти в своем исходном коде точку останова, подсве-

тите ее и выберите кнопку View.

* Чтобы удалить все точки останова, выберите командную кноп-

ку Clear All.

Создание условный точек останова

Точки останова, добавленные командой Toggle Breakpoint, яв-

ляются безусловными: когда вы попадаете на эту строку, отладчик в

любом случае останавливает программу. Если вы редактируете новую

или существующую точку останова, то у вас есть две дополнительные

возможности - с помощью параметров диалогового окна Edit

Breakpoint вы можете создать условные точки останова. В этих точ-

ках останова вы можете задать два вида условий: счетчик проходов

и логические условия.

Подсчет числа проходов

Задание для точки останова счетчика проходов сообщает отлад-

чику, что останавливать программу нужно не при каждом достижении

точки останова, а только на n-ый раз. То есть, если счетчик про-

ходов равен 3, то отладчик останавливает программу только при

третьем достижении данной точки останова.

Проверка условий

В качестве условия для точки останова можно также задать вы-

ражение типа Boolean. Например, вы можете проверить, попадает ли

переменная в заданный диапазон, или установлен ли некоторый флаг.

В таких условиях для точек останова вы можете задавать любые бу-

левские выражения, подчиняющиеся правилам Таблицы 6.1.

Прерывание программы без точек останова

Даже если вы не установите точек останова, то все равно смо-

жете выйти в отладчик при выполнении программы из IDE. В любой

момент работа программы нажмите клавиши Ctrl+Break. Отладчик на-

ходит позицию в исходном коде, где вы прервали программу. Как и в

случае обычно точки останова вы можете затем выполнить программу

по шагам, трассировать ее, отследить или вычислить выражения.


Глава 7. Модули Borland Pascal

В данной главе разъясняется, что такое модуль, как он ис-

пользуется, какие встроенные модули доступны пользователю, как

писать собственные программные модули и как компилировать их.

Что такое модуль?

Borland Pascal обеспечивает вам доступ к большому числу

встроенных констант, типов данных, переменных, процедур и функ-

ций. Некоторые из них специфичны для Borland Pascal, другие спе-

цифичны для приложений Windows. Их количество велико, однако, в

своей программе вы редко используете их все сразу. Поэтому они

разделены на связанные группы, называемые модулями. В этом случае

можно использовать только те модули, которые необходимы в прог-

рамме.

Используя модули, вы можете разбивать программу на отдельные

части и компилировать их отдельно. Программный модуль (unit)

представляет собой набор констант, типов данных, переменных, про-

цедур и функций, которые могут совместно использоваться несколь-

кими программами. Каждый модуль аналогичен отдельной программе на

Паскале: он может иметь основное тело, которое вызывается перед

запуском вашей программы и осуществляет необходимую инициализа-

цию.

Все описания внутри модуля связаны друг с другом. Например,

модуль Strings содержит все описания, необходимые для подпрограмм

работы со строками с завершающим нулем.

Borland Pascal предоставляет пользователю ряд стандартных

модулей, таких как System, Crt WinCrt и др. Они поддерживают наши

программы Borland Pascal и все записаны в одну из трех библиотех

исполняющей системы (в зависимости от целевой платформы).

Библиотеки исполняющей

системы для целевой платформы Таблица 7.1

ЪВДДДДДДДДДДДДДДДДДДДДДДДДї

і Имя библиотеки і Целевая платформа і

ГЕДДДДДДДДДДДДДДДДДДДДДДДДґ

і TURBO.TPL і DOS реального режима і

і TPW.TPL і Windows і

і TPP.TPL і DOS защищенного режима і

АБДДДДДДДДДДДДДДДДДДДДДДДДЩ

Ваша программа может использовать любую из процедур и функ-

ций в этих модулях, и вам не потребуется писать их заново.

Структура модуля

Структура модуля аналогична структуре программы, однако есть

несколько существенных различий. Например, рассмотрим модуль:

unit <идентификатор>;

interface

uses <список модулей>; { Необязательный }

{ глобальные описания }

implementation

uses <список_модулей>; { Необязательный }

{ локальные описания }

{ реализация процедур и функций }

begin

{ код инициализации }

end.

Заголовок модуля начинается зарезервированным словом unit,

за которым следует имя модуля (идентификатор) точно так же, как и

в случае имени программы. Следующим элементом в модуле является

ключевое слово interface. Оно обозначает начало интерфейсной

секции модуля - части, доступной всем другим модулям или програм-

мам, в которых он используется.

Программный модуль может использовать другие модули, для

этого они определяются в операторе uses. Оператор uses (если он

имеет место) может содержаться в двух местах. Во-первых он может

следовать сразу после ключевого слова interface. В этом случае

любые константы и типы данных, описанные в интерфейсной секции

этих модулей, могут использоваться в любом описании в интерфейс-

ной части данного модуля.

Во-вторых, он может следовать немедленно за ключевым словом

implementation. В этом случае все описания из этих модулей могут

использоваться только в секции реализации.

Интерфейсная секция

Интерфейсная часть - "общедоступная" часть в модуле - начи-

нается зарезервированным словом interface, следует сразу после

заголовка модуля и заканчивается перед зарезервированным словом

implementation. Интерфейс определяет, что является "видимым"

(доступным) для любой программы (или модуля), использующей данный

модуль.

В интерфейсной части (секции) модуля можно определять

константы, типы данных, переменные, процедуры и функции. Как и в

программе, они могут быть расположены в любом порядке, и секции

могут встречаться повторно (например, ваша программа может содер-

жать секцию var, за которой следует секция const, а затем другая

секция var).

Процедуры и функции, видимые для любой программы,

использующей данный модуль, описываются в секции интерфейса,

однако их действительные тела - реализации - находятся в секции

реализации. Вам не нужно использовать описания forward, и они не

допускаются. В интерфейсной части перечисляются все заголовки

процедуры и функции. Секция реализации содержит программную логи-

ку процедур и функций.

Секция реализации

Секция реализации - "приватная" часть - начинается

зарезервированным словом implementation. Все, что описано в

секции интерфейса, является видимым в секции реализации:

константы, типы, переменные, процедуры и функции. Кроме того, в

секции реализации могут быть свои дополнительные описания,

которые не являются видимыми для программ, использующих этот

модуль. Программа не знает об их существовании и не может

ссылаться на них или обращаться к ним. Однако, эти скрытые эле-

менты могут использоваться (и, как правило, используются) "види-

мыми" процедурами и функциями, то есть теми подпрограммами, чьи

заголовки указаны в секции интерфейса.

Оператор 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

then IMax := V1

else IMax := V2

end; { конец функции Max }

Подпрограммы, локальные для секции реализации (то есть не

описанные в секции реализации), должны иметь полный (несокращен-

ный) заголовок procedure/function.

Секция инициализации

Обычно вся секция реализации модуля заключена между зарезер-

вированными словами implementation и end. Однако, если перед end

поместить зарезервированное слово begin, а между ними - операто-

ры, то получившийся составной оператор, очень похожий на основное

тело программы, становится секцией инициализации модуля

(initialization).

Секция инициализации представляет собой место, где инициали-

зируются структуры данных (переменных), которые использует прог-

раммный модуль или которые он делает доступными программе, ис-

пользующей данный модуль. Вы можете использовать эту секцию для

открытия файлов, которые программа использует позднее.

При выполнении программы, использующей некоторый модуль,

секция инициализации этого модуля вызывается перед запуском ос-

новного тела программы. Если программа использует более одного

модуля, то секции инициализации всех модулей вызываются (в поряд-

ке, указанном в операторе uses в программе) перед тем, как выпол-

нить основное тело программы.

Как используются модули?

Модули, которые использует ваша программа, уже оттранслиро-

ваны и хранятся, как машинный код, а не как исходный код на Пас-

кале, поскольку они не являются включаемыми файлами. Даже интер-

фейсная секция хранится в специальном двоичном формате таблицы

идентификаторов, используемом в Borland Pascal. Более того, опре-

деленные стандартные модули хранятся в специальном файле

(TURBO.TPL, TPW.TPL или TPP.TPL) и автоматически загружаются в

память вместе с Borland Pascal.

В результате использование одного или нескольких модулей

очень незначительно увеличивает время компиляции вашей программы

(обычно менее, чем на секунду).

Фактически, если модуль скомпилирован, его использование

сохраняет вам время при перекомпиляции. Поскольку компилятор не

перекомпилирует модуль, пока он не изменяется, использование мо-

дулей в программе ускорит процесс ее построения.

Как указывалось ранее, для использования специального модуля

или набора модулей необходимо в начале программы поместить опера-

тор 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 может вызывать любую подпрог-

рамму модуля 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

.

.

.

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

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);

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.

Отметим, что имя модуля может предшествовать любому иденти-

фикатору: константе, типу данных, переменной или подпрограмме.

Оператор uses секции реализации

Borland Pascal позволяет вам размещать в секции реализации

оператор uses. В случае его присутствия оператор uses должен сле-

довать непосредственно за ключевым словом implementation (анало-

гично тому, как в интерфейсной секции оператор uses должен следо-

вать непосредственно за ключевым словом interface).

Размещение в секции реализации оператора uses позволяет

"скрыть" внутренние детали модуля, поскольку используемые в сек-

ции реализации модули оказываются "невидимыми" для того, кто этот

модуль использует. Более важным, однако, является то, что это

позволяет вам строить взаимнозависимые модули.

Поскольку программные модули в Borland Pascal не обязаны

иметь строго иерархическую структуру, то допускается использовать

циклические ссылки на модули. О циклических ссылках на модули

рассказывается в Главе 10 "Справочного руководства по языку".

Стандартные модули

Модули библиотек исполняющей системы Borland Pascal загружа-

ются в память вместе с Borland Pascal; вы всегда можете их ис-

пользовать. Обычно библиотеки исполняющей системы (TURВО.TPL,

TPW.TPL и TPP.TPL) находятся в том же каталоге, что и компилятор

(TURBO.EXE. BPW.EXE и BP.EXE).

Создание ваших собственных модулей

Если вы хотите написать модуль, содержащий некоторые полез-

ные подпрограммы, и использовать эти подпрограммы в своих прог-

раммах, напишите модули и сохраните его под именем, заданным в

заголовке модуля. Borland Pascal сохраняет файл с расширением

.PAS, как и любой другой файл, созданный в редакторе Borland

Pascal. В исходном файле может содержаться только один модуль.

Компиляция модуля

Скомпилировать модуль вы можете двумя способами. Вы можете:

* Скомпилируйте модуль с помощью команды CompileіCompile.

Вместо создания файла .EXE Borland Pascal создает файл

.TPU, .TPW или .TPP.

ЪДДДДДДДДДДДДДДДДДДДДДДДДВї

і Целевая платформа і Расширение имени файла і

і і модуля і

ГДДДДДДДДДДДДДДДДДДДДДДДДЕґ

і DOS реального режима і .TPU і

і Windows і .TPW і

і DOS защищенного режима і .TPP і

АДДДДДДДДДДДДДДДДДДДДДДДДБЩ

Например, если ваш модуль называется MYUNIT.PAS, если це-

левой платформой является Windows, он компилируется в

MYUNIT.TWP.

* Для компиляции программы, которая включает в себя оператор

uses, используйте команду CompileіMake или CompileіBuild.

В зависимости от целевой платформы, создается файл .TPU,

.TPW или .TPP.

Примечание: О том, как использовать оператор uses,

рассказывается в следующем разделе.

Доступность модуля для программы

Скопируйте свой новый файл .TPU, .TPW или .TPP в каталог мо-

дулей, заданный в диалоговом окне OptionsіDirectories, или ис-

пользуйте параметр командной строки /U при работе с компилятором

режима командной строки.

Если вы поместите свой модуль в заданный каталог модулей, то

сможете ссылаться на этот модуль, даже если он не находится в те-

кущем каталоге или в библиотеках исполняющей системы.

Включите в любую программу, где вы хотите использовать свой

новый модуль, оператор uses. Например, если ваш новый модуль на-

зывается INTLIB.TPW, то задайте в своей программе оператор следу-

ющего вида:

uses IntLib;

Чтобы найти модуль, имя которого указано в операторе uses,

Borland Pascal проверяет его наличие в библиотеке исполняющей

системы, загруженной в память в время инициализации.

Примечание: О том, как поместить модуль в библиотеку

исполняющей системы, рассказывается ниже.

Если модуль в библиотеке исполняющей системы отсутствует, то

компилятор ищет его на диске, сначала в текущем каталоге, затем в

каталогах, заданных в качестве каталогов модулей (Optionsі

Directories). Компилятор предполагает, что имя файла совпадает с

именем модуля, а расширение имени файла - это .TPU, .TPW или

.TPP. Исходный текст модуля имеет расширение .PAS.

Пример

Теперь напишем небольшой модуль. Назовем его IntLib и вста-

вим в него две простые подпрограммы для целых чисел - процедуру и

функцию:

unit IntLib;

interface

procedure ISwap(var I,J : integer);

function IMax(I,J : integer) : integer;

implementation

procedure ISwap;

var

Temp : integer;

begin

Temp := I; I := J; J := Temp

end; { конец процедуры ISwap }

function IMax;

begin

if I > J

then IMax := I

else IMax := J

end; { конец функции IMax }

end. { конец модуля IntLib }

Наберите этот модуль, запишите его в файл INTLIВ.PAS, а за-

тем скомпилируйте, задав в качестве целевой платформы защищенный

режим DOS. В результате получим код модуля в файле INTLIВ.ТРP.

Перешлем его в каталог модулей (если такой имеется), или оставив

в том же каталоге, где находится следующая программа, которая ис-

пользует модуль IntLib:

program IntTest;

uses IntLib;

var

A,B : integer;

begin

Write('Введите два целочисленных значения: ');

Readln(A,B);

ISwap(A,B);

Writeln('A = ',A,' B = ',B);

Writeln('Максимальное значение равно ',IMax(A,B));

end. { конец программы IntTest }

Модули и большие программы

До сих пор мы говорили о модулях как о библиотеках - наборах

полезных подпрограмм, которые могут использоваться несколькими

программами. Однако, у модуля есть еще одна функция - разбивать

большую программу на составные части.

Два аспекта Borland Pascal способствуют использованию моду-

лей в такой функции:

* высокая скорость компиляции и компоновки;

* способность работать с несколькими файлами одновременно,

например, с программой и несколькими модулями.

Обычно большая программа разбивается на модули, которые

группируют процедуры по их функциям. Например, программа редакто-

ра может быть разделена на части, выполняющие инициализацию, рас-

печатку, чтение и запись файлов, форматирование и так далее. Так-

же, как имеется основная программа, определяющая глобальные конс-

танты, типы данных, переменные, процедуры и функции, так же может

иметь место и "глобальный" модуль, который используется всеми

другими модулями.

Набросок большой программы-редактора может иметь вид:

program Editor;

uses

WinCrt, String { стандартные модули из TPW.TPL }

EditGlobals, { модули, написанные пользователем }

EditInuit,

EditPrint,

EditRead, EditWrite,

EditFormat;

{ описание, процедуры и функции программы }

begin { основная программа }

end. { конец программы Editor }

Модули в данной программе могут содержаться в TPW.TPL, биб-

лиотеке исполняющей системы Windows, или быть отдельными файлами

.TPW. В последнем случае Borland Pascal выполняет за вас управле-

ние проектом. Это означает, что при перекомпиляции программы

Editor с помощью встроенного в компилятор средства формирования

Borland Pascal сравнивает даты каждого файла .PAS и .TPW и пере-

компилирует любой модуль, исходный код которого перекомпилирован.

Другая причина использования модулей в больших программах

состоит в ограничения кодового сегмента. Процессоры 8086 (и родс-

твенные им) ограничивают размер сегмента кода 64 килобайтами. Это

означает, что основная программа и любой данный сегмент на может

превышать 64К. Borland Pascal интерпретирует это, создавая для

каждого модуля отдельный сегмент кода. Без этого объем кода вашей

программы не мог бы превышать 64К.

Примечание: Подробнее о работе с большими программными

проектами рассказывается в Главе 4 "Программирование в ин-

тегрированной среде для DOS".

Утилита TPUMOVER

Допустим, вы хотите добавить стандартным модулям хорошо на-

писанный и полностью отлаженный модуль с тем, чтобы он загружался

в память при запуске компилятора. Переслать его в библиотечный

файл стандартных модулей можно с помощью утилиты TPUMOVER.EXE.

Кроме того, утилита TPUMOVER используется для удаления моду-

лей из библиотечного файла стандартных модулей Borland Pascal,

благодаря чему уменьшается его размер и количество памяти, необ-

ходимой для его загрузки.

Примечание: Более подробно об использовании утилиты

TPUMOVER см. в "Руководстве по инструментальным средствам и

утилитам".

Как вы вероятно поняли, писать собственные модули абсолютно

не сложно. Хорошо написанный, хорошо реализованный программный

модуль упрощает разработку программы; проблемы решаются только

один раз, а не повторно для каждой новой программы. Более того,

использование модулей обеспечивает простое средство для написания

больших программ.


Глава 8. Использование указателей

Указатель - это ссылка на данные или код вашей программы. Он

представляет адрес в памяти элемента, на который указывает. Ис-

пользование указателей позволяет писать большие и более гибкие

программы и особенно полезно, когда вы начинаете писать объект-

но-ориентированные программы.

Данная глава должна помочь вам лучше использовать указатели,

независимо от того, начинаете ли вы работать с Паскалем или уже

давно программируете на Паскале, но раньше не работали с указате-

лями. Она охватывает следующие темы:

* Зачем и когда используются указатели.

* Что такое указатель.

* Как использовать указатели.

* Эффективная работа с указателями.

Для чего используются указатели?

Рано или поздно каждый программист, работающий на Паскале,

попадает в ситуацию, требующую использования указателей. Указате-

ли требуется применять в следующих случаях:

* Если ваша программа работает с большими объемами данных

(общий объем которых превышает 64К).

* Если ваша программа во время компиляция использует данные

неизвестного размера.

* Если программа использует временные буферы данных.

* Если ваша программа работает с несколькими типами данных.

* Если ваша программа использует связанные списки данных или

объектов.

Давайте подробнее рассмотрим каждую причину использования

указателей.

Работа с большими объемами данных

По мере того как программы становятся более сложными, и тре-

буются работа с большим количеством данных, область объемом в

64К, зарезервированная в Borland Pascal для данных, может ока-

заться недостаточной, чтобы содержать все необходимые программе

данные. Указатели позволяют вам обойти эту проблему.

Когда вы описываете в Borland Pascal глобальные переменные,

компилятор выделяет для них память в области, которая называется

сегментом данных. Сегмент данных имеет максимальный размер 64К.

Это означает, что общий объем всех ваших глобальных переменных не

может превышать 64К. Для многих программ этот предел значения не

имеет, но в некоторых случаях вам может потребоваться больший

объем.

Примечание: Локальные переменные не помещаются в сег-

мент данных и в пределе 64К не учитываются.

Предположим, например, что у вас есть программа, требующая

массива в 400 строк по 100 символов каждая. Для этого массива

требуется примерно 40К, что меньше максимума в 64К. Если осталь-

ные ваши переменные помещаются в оставшиеся 24К, массив такого

объема проблемы не представляет.

Но что если вам нужно два таких массива? Это потребовало бы

80К, и 64К сегмента данных не хватит. Чтобы работать с большими

объемами данных, вам нужно использовать динамически распределяе-

мую область памяти. Ваша программа может выделить в динамически

распределяемой области 80К, поддерживая указатель в виде ссылку

на адрес данных. Указатель занимает в сегменте данных только 4

килобайта.

Что такое динамически распределяемая область памяти?

Динамически распределяемая область памяти - это вся память,

которую ваша операционная система делает доступной для программы

и которая не используется ее кодом, сегментом данных и стеком.

Объемом распределяемой динамической памяти вы можете управлять с

помощью директивы компилятора $M.

Обычно в Borland Pascal вы можете зарезервировать память в

динамически распределяемой области, получить к ней доступ через

указатель, а затем снова освободить память. Подробности о распре-

делении памяти в динамически распределяемой области вы можете

найти ниже в разделе "Как использовать указатели?".

Работа с данными неизвестного размера

Некоторые элементы данных Borland Pascal (в частности, стро-

ки и массивы) требуют задания размеров во время компиляции, даже

если при выполнении программы вам не потребуется вся выделенная

память. Простым примером может быть программа, считывающая вводи-

мую пользователем строку, например, имя пользователь. Чтобы запи-

сать имя в обычной строковой переменной, вам потребовалось бы за-

резервировать достаточно памяти для максимальной возможной стро-

ки, даже если набранное имя содержит всего несколько букв. Если

вы распределяете переменные в динамически распределяемой области

памяти во время выполнения, то можете выделить точно столько

байт, сколько необходимо для фактической строки данных.

Это тривиальный пример, но в приложении, содержащем сотни и

тысячи таких элементов данных (таких как множественные окна или

считываемые из файлов списки) выделение точного объема пространс-

тва может вместо ситуации нехватки памяти привести к успешному

выполнению.

Работа с временными буферами данных

Указатели и динамически распределяемая область памяти осо-

бенно полезны в тех случаях, когда вам требуется временное выде-

ление памяти, и вы не хотите удерживать ее на все время выполне-

ния программы. Например, редактору файлов обычно требуется буфер

данных для каждого редактируемого файла. Вместо описания на этапе

компиляции, что вам необходимо определенное число буфером задан-

ного размера, которые всегда распределяются для файлов, вы можете

выделить их столько, сколько необходимо в каждый конкретный мо-

мент, что делает память доступной для других целей.

Другим общим примером использования временной памяти являет-

ся сортировка. Обычно когда вы сортируете большой объем данных,

то делаете копию массива, сортируете копию, а затем записываете

отсортированные данные обратно в исходный массив. Это сохраняет

целостность ваших данных, но требует также наличия во время сор-

тировки двух копий данных. Если вы хотите распределить сортируе-

мый массив в динамически распределяемой памяти, то можете отсор-

тировать его и скопировать обратно в оригинал, а затем уничтожить

сортируемый массив, освободив память для других нужд.

Работа с несколькими типами данных

Одной из общих причин использования указателей является

ссылка на переменные структуры данных, то есть записи или масси-

вы, которые не всегда имеют одну и ту же структуру. Например, вы

можете выделить блок памяти, зарезервированный для "протокола"

элементов строк различной длины, набранных в поле ввода данных.

Чтобы прочитать список протокола, подпрограмма должна просмотреть

блок и найти отдельные строки. Для указания начала блока вы може-

те использовать простой указатель. В этом случае указатель рабо-

тает аналогично передаче функции или процедуре нетипизированного

параметра var - вы просто хотите сообщить, где что-то находится,

без указания того, что это такое.

Примечание: О нетипизированных параметрах-переменных

рассказывается в Главе 9 ("Процедуры и функции") "Руководс-

тва по языку".

Связанные списки

Одним из общих случаев использования указателей является со-

единение связанных списков записи. Во многих простых приложениях

типа баз данных вы можете размещать записи данных в массивах или

типизированных файлах, но иногда требуется что-то более гибкое

чем массив, который имеет фиксированный размер. Распределяя дина-

мические записи, так что каждое поле имеет запись, указывающую на

следующие записи, вы можете построить список, содержащий столько

элементов, сколько вам требуется.

Что такое указатель?

Указатель - это какой-либо адрес в памяти вашего компьютера.

Это может быть адрес переменной, записи данных, либо процедуры

или функции. Обычно вам не важно, где расположен элемент в памя-

ти. Вы можете просто ссылаться на него по имени, и Borland Pascal

знает, где его нужно искать.

Именно это происходит, когда вы описываете переменную. Нап-

ример, если программа включает в себя следующий код, то вы указы-

ваете компилятору на необходимость зарезервировать область в па-

мяти, на которую будете ссылаться по имени SomeNumber.

var SomeNumber: Integer;

Вам не нужно беспокоиться о том, где SomeNumber находится в

памяти. Именно для этого задается имя.

Адрес размещения SomeNumber в памяти можно найти с помощью

операции @. @SomeNumber - это адрес вашей целочисленной перемен-

ной. Вы можете присвоить этот адрес переменной-указателю, то есть

переменной, содержащей адрес данных или кода в памяти.

Ссылочный тип

Чтобы хранить указатели, вам требуется переменная-указатель,

а для создания переменной-указателя вам необходим ссылочный тип

(или тип "указатель"). Простейшим ссылочным типом является стан-

дартный тип с именем Pointer. Переменная типа Pointer - это общий

(нетипизированный) указатель, то есть, просто адрес. Он не содер-

жит информации о том, на что он указывает.

Таким образом, чтобы использовать тот же пример SomeNumber,

вы можете присвоить его адрес переменной-указателю:

var

SomeNumber: Integer;

SomeAddress: Pointer;

begin

SomeNumber := 17; {присвоить SomeNumber значение}

SomeAddress := @SomeNumber; {присвоить SomeAddress адрес}

SomeAddress := Addr(SomeNumber); {другой способ получения

адреса}

end.

Нетипизированные указатели в Паскале не используются, пос-

кольку они очень ограничены. Они наиболее полезны, когда указыва-

емый элемент будет изменяться, так как нетипизированный указатель

совместим с любым другим указателем. Типизированные указатели

значительно более полезны, и как вы узнаете в следующем разделе,

они более надежны.

Типизированные указатели

Обычно вы определяете ссылочные типы, которые указывают на

конкретный вид элемента, например, целое значение или запись дан-

ных. Как вы далее увидите, можно извлечь преимущество из того

факта, что указателю известно, на что он указывает. Чтобы опреде-

лить типизированный указатель, вы можете описать новый тип, опре-

деленный символом каре (^), за которым следуют один или более

идентификаторов. Например, чтобы определить указатель на Integer,

вы можете сделать следующее:

type PIneger = ^Integer;

Теперь вы можете описать переменные типа PInteger. Если вы

не собираетесь часто использовать ссылочный тип, то можете прос-

то описать переменные, как указатели на уже определенный тип.

Например, если вы определили PInteger как ^Integer, то следующие

описания переменной эквивалентны:

var

X: ^Integer:

Y: PInteger;

Разыменование указателей

До сих пор мы видели, как можно присваивать указателям зна-

чения, но если вы не можете получить значения обратно, польза от

этого невелика. Разыменовав типизированный указатель, вы можете

интерпретировать так, как если бы это была переменная типа, на

которую он указывает. Чтобы разыменовать указатель, поместите

символ каре (^) после идентификатора указателя.

Ниже показаны некоторые примеры разыменования указателя:

type PInteger = ^Integer;

var

SomeNumber: Integer; { присвоить SomeNumber 17 }

SomeAddress := @SomeNumber; { SomeAddress указывает на

SomeNumber }

Writeln(SomeNumber); { напечатать 17 }

Writeln(SomeAddress); { не допускается; указатели печатать

нельзя }

Writeln(SomeAddress^); { напечатать 17 }

AnotherAddress := SomeAddress; { также указывает на

SomeNumber }

AnotehrAddress^ := 99; { новое значение для SomeNumber }

Writeln(SomeNumber); { напечатать 99 }

end.

Пример 8.1 Простые примеры разыменования указателей.

Наиболее важными строками в Примере 8.1 являются следующие:

AnotherAddress := SomeAddress; { также указывает на

SomeNumber }

AnotehrAddress^ := 99; { новое значение для SomeNumber }

Если вы поймете разницу между этими двумя операторами, то

поймете основные моменты в использовании указателей. Первый опе-

ратор присваивает адрес переменной AnotherAddress; он сообщает

ей, куда нужно указывать. Второй оператор присваивает новое зна-

чение элементу, на который указывает AnotherAddress. На Рис. 8.1

графически показано, как изменяется переменная.

ЪДДДДДДДДДДїЪДДДДДДДДДДДїЪДДДДДДДДДДДїЪДДДДДДДДДДДї

SomeNumber і 17 іі 17 іі 17 іі 99 і

ГДДДДДДДДДДґГДДДДДДДДДДДґГДДДДДДДДДДДґГДДДДДДДДДДДґ

і не іі іі іі і

SomeAddress іопределеноіі@SomeNumberіі@SomeNumberіі@SomeNumberі

ГДДДДДДДДДДґГДДДДДДДДДДДґГДДДДДДДДДДДґГДДДДДДДДДДДґ

і не іі не іі іі і

AnotherAddressіопределеноііопределено іі@SomeNumberіі@SomeNumberі

АДДДДДДДДДДЩАДДДДДДДДДДДЩАДДДДДДДДДДДЩАДДДДДДДДДДДЩ

^ ^ ^ ^

SomeNumber := 17; і і і

SomeAddress := і і

@SomeNumber; і і

AnotherAddress і

:= SomeAddress: і

AnotherAddress^

:= 99;

Как использовать указатели?

Теперь вы получили достаточно хорошее представление о том, в

каких ситуациях вы можете использовать указатели, и можно расс-

мотреть их фактическое применение. В данном разделе охватываются

следующие темы:

* Распределение динамических переменных.

* Освобождение выделенной для динамических переменных памя-

ти.

* Распределение и освобождение выделенных объемов памяти.

* Проверка доступного в динамически распределяемой области

пространства.

Borland Pascal предусматривает две пары процедур для выде-

ления и освобождения памяти, распределяемой для динамических пе-

ременных. Чаще всего используются процедуры New и Dispose, кото-

рые отвечают большинству потребностей. Процедуры GetMem и FreeMem

выполняют те же функции, но на более низком уровне.

Выделение памяти для динамических переменных

Одним из наиболее важных моментов использования указателей

является распределение динамических переменных в динамически

распределяемой области памяти. Borland Pascal предусматривает два

способа выделения для указателя памяти: процедура New и процедура

GetMem.

Использование New как процедуры

New - это очень простая процедура. После описания перемен-

ной-указателя вы можете вызвать процедуру New для выделения

пространства в динамически распределяемой памяти для указываемого

переменной элемента. Приведем пример:

var

IntPointer: ^Integer;

StringPointer: ^String;

begin

New(IntPointer); { выделяет в динамически распреде-

ляемой области два байта }

New(StringPointer); { выделяет в динамически распреде-

. ляемой области 256 байт }

.

.

end.

Пример 8.2 Распределение динамической переменной с помощью

процедуры New.

После вызова процедуры New переменная-указатель указывает на

память, выделенную в динамически распределяемой памяти. В данном

примере IntPointer указывает на двухбайтовую область, выделенную

процедурой New, а IntPointer^ - это допустимая целочисленная пе-

ременная (хотя это целочисленное значение еще не определено).

Аналогично, StringPointer указывает на выделенный для строки

256-байтовый блок, а его разыменование дает доступную для исполь-

зования строковую переменную.

Использование New как функции

Кроме выделения памяти для конкретной динамической перемен-

ной вы можете использовать New как функцию, возвращающую указа-

тель конкретного типа. Например, если PInteger - это тип, опреде-

ленный как ^Integer, а IntPopinter имеет тип PInteger, то следую-

щие два оператора эквивалентны:

New(IntPointer);

IntPointer := New(PInteger);

Это особенно полезно в случаях, когда может потребоваться

присваивать переменной-указателю элементы различных типов. Иногда

желательно распределять динамическую переменную, не присваивая

явно указатель конкретной переменной. Вероятно, вы можете сделать

это только создав для процедуры и функции параметр:

SomeProcedure(New(PointerType));

В этом случае SomeProcedure будет добавлять передаваемый па-

раметр к некоторому списку. В противном случае распределяемая па-

мять будет потеряна. Библиотеки Borland Turbo Vision и Borland

Pascal широко используют этот метод для присваивания динамических

объектов спискам.

Использование New с объектами

Когда вы используете New как функцию или процедуру для выде-

ления динамического объекта, то можете добавить необязательный

второй параметр, который задает применяемый для инициализации

объекта конструктор. В Примере 8.3 первое обращение к New распре-

деляет пространство для объекта, но не инициализирует этот объ-

ект. Второй вызов выделяет память и вызывает для задания объекта

конструктор Init.

type

PMyObject = ^TMyObject;

TMyObject = object

constructor Init;

end;

var

MyObject, YourObject: PMyObject;

begin

New(MyObject); { объект не инициализируется }

New(YourObject, Init); { вызов Init для инициализации

объекта }

end.

Пример 8.3 Создание динамических объектов.

Примечание: Об объектах и их конструкторах рассказыва-

ется в Главе 9 "Объектно-ориентированное программирование".

Освобождение памяти, выделенной для динамических переменных

Память, распределенная для переменных с помощью New, после

завершения работы с ними должна освобождаться. Это позволит ис-

пользовать динамически распределяемую память для других перемен-

ных. Чтобы освободить память, выделенную для динамической пере-

менной, вы должны использовать процедуру Dispose. В Примере 8.2

вы можете добавить следующее:

Dispose(StringPointer);

Dispose(IntPointer);

Нужно помнить, что если вы распределяете динамические пере-

менные с помощью New, то освобождать выделенную для них память

после завершения работы с этими переменными нужно с помощью

Dispose.

Процедуры GetMem и FreeMem

Иногда нежелательно выделять память тем способом, как это

делает New. Вам может потребоваться выделить больше или меньше

памяти, чем это делает New по умолчанию, либо до начала выполне-

ния вы можете просто не знать, сколько памяти вам нужно использо-

вать. Borland Pascal выполняет такое распределение с помощью про-

цедуры GetMem.

Процедура GetMem воспринимает два параметра: переменную-ука-

затель, для которой вы хотите распределить память, и число расп-

ределяемых байт.

Динамическое выделение памяти для строки

Пусть, например, у вас есть прикладная программа, которая

считывает 1000 строк из файла и записывает их в динамическую па-

мять. Вы не знаете, насколько длинной будет каждая из этих строк,

поэтому вам потребуется описать строковый тип такого размера,

который будет соответствовать максимальной возможной строке. Если

предположить, что не все строки имеют максимальную длину, то у

вас будет бесполезно использоваться память.

Чтобы решить эту проблему, вы можете считать каждую строку в

буфер, затем выделить столько памяти, сколько требуется для фак-

тических данных в строке. Пример этого показан ниже:

type PString = ^String;

var

ReadBuffer: String;

LinewRead: array[1..1000] of PString;

TheFile: Text;

LineNumber: Integer;

begin

Assign(TheFile, 'FOO.TXT');

Reset(TheFile);

for LineNumber := 1 to 1000 do

begin

Readln(ReadBuffer);

GetMem(LinesRead[LineNumber], Length(ReadBuffer) + 1);

LinesRead[LineNumber]^ := ReadBuffer;

end;

end.

Пример. 8.4 Динамическое распределение памяти для строки.

Вместо выделения для строк 256К (256 символов на строку 1000

раз) вы выделили 4К (4 байта на указатель 1000 раз), плюс объем,

фактически занимаемый текстом.

Освобождение выделенной памяти

Аналогично тому, как требуется освобождать память, выделен-

ную с помощью New, вам нужно освобождать память, распределенную с

помощью процедуры GetMem. Это можно сделать с помощью процедуры

FreeMem. Аналогично тому, как каждому вызову New должен соответс-

твовать парный вызов Dispose, каждому вызову процедуры GetMem

должен соответствовать вызов FreeMem.

Как и GetMem, процедура FreeMem воспринимает два параметра:

освобождаемую переменную и объем освобождаемой памяти. Важно,

чтобы объем освобождаемой памяти точно совпадал с объемом выде-

ленной памяти. New и Dispose, основываясь на типе указателя,

всегда знают, сколько байт нужно выделять или освобождать. Но в

случае GetMem и FreeMem объем выделяемой памяти находится всецело

под вашим контролем.

Если вы освободите меньше байт, чем было выделено, то остав-

шиеся байты теряются (происходит "утечка" динамически распределя-

емой памяти). Если вы освободите большее число байт, чем было вы-

делено, то можете освободить память, распределенную для другой

переменной, что может привести к порче данных. В защищенном режи-

ме освобождение большего объема памяти, чем было выделено, вызо-

вет ошибку по нарушению защиты (GP).

Предположим, например, что вы собираетесь выделить память

для одной или более записей данных типа TCheck:

type

PCheck = ^ TCheck;

TCheck = record

Amount: Real;

Mounth: 1..12;

Day: 1..31;

Year: 1990..2000;

Payee: string[39];

end.

Пример 8.5 Простой тип записи.

Каждая запись типа TCheck занимает 50 байт, поэтому, если у

вас есть переменная ThisCheck типа PCheck, вы можете распределить

динамическую запись следующим образом:

GetMem(ThisGheck, 50);

а позднее освободить ее с помощью вызова:

FreeMem(ThisCheck, 50);

Использование с процедурой GetMem функции SizeOf

Однако убедиться, что вы каждый раз выделяете и освобождаете

один и тот же объем памяти, недостаточно. Вы должны обеспечить

распределение правильного объема памяти. Предположим, вы изменили

определение TCheck. Например, если вы переопределили TCheck.Payee

как 50-символьную строку вместо 39-символьной, то не сможете по-

лучить и освобождать достаточно памяти. Надежнее всего использо-

вать в программе функцию SizeOf, например:

GetMem(ThisCheck, SizeOf(TCheck));

.

.

.

FreeMem(ThisCheck, SizeOf(TCheck));

Это не только обеспечивает, что вы выделяете и освобождаете

один и тот же объем, но гарантирует, что при изменении размера

типа ваша программа все равно будет выделять нужную память.

Проверка объема доступной динамически распределяемой памяти

В Borland Pascal определены две функции, возвращающие важную

информацию о динамически распределяемой области памяти: MemAvail

и MaxAvail.

Функция MemAvail возвращает общее число байт, доступных для

распределения в динамической памяти. Перед выделением большого

объема в динамически распределяемой памяти полезно убедиться, что

такой объем памяти доступен.

Функция MaxAvail возвращает размер наибольшего доступного

блока непрерывной памяти в динамически распределяемой области.

Первоначально при запуске программы MaxAvail равно MemAvail, пос-

кольку вся динамически распределяемая область памяти является

доступной и непрерывной. После распределения нескольких блоков

памяти пространство в динамически распределяемой области скорее

всего станет фрагментированным. Это означает, что между частями

свободного пространства имеются распределенные блоки. Функция

MaxAvail возвращает размер наибольшего свободного блока.

Подробнее о том, как Borland Pascal работает с динамически

распределяемой областью памяти, рассказывается Главе 21 ("Вопросы

использования памяти") "Руководства по языку".

Общие проблемы использования указателей

Указатели позволяют вам делать в Паскале некоторые важные

вещи, но есть пара моментов, которые при работе с указателями

нужно отслеживать. При использовании указателей допускаются сле-

дующие общие ошибки:

- разыменование неинициализированных указателей;

- потери динамически распределяемой памяти ("утечки").

Разыменование неинициализированных указателей

Одним из общих источников ошибок при работе с указателями

является разыменование указателя, который еще не был инициализи-

рован. Как и в случае других переменных Паскаля, значение пере-

менной-указателя не будет определено, пока вы не присвоите ей

значение, так что она сможет указывать на какой-то адрес в памя-

ти.

Перед использованием указателей им всегда нужно присваивать

значения. Если вы разыменовываете указатель, которому еще не

присвоено значение, то считанные из него данные могут представ-

лять собой случайные биты, а присваивание значения указываемому

элементу может затереть другие данные, вашу программу или даже

операционную систему. Это звучит несколько пугающе, но при опре-

деленной дисциплине такие вещи легко отслеживаются.

Использование пустого указателя

Чтобы избежать разыменования указателей, которые не указыва-

ют на что-либо значащее, нужен некоторый способ информирования о

том, что указатель недопустим. В Паскале предусмотрено зарезерви-

рованное слово nil, которое вы можете использовать в качестве со-

держательного значения указателей, которые в данный момент ни на

что не указывают. Указатель nil является допустимым, но ни с чем

не связанным. Перед разыменованием указателя вы должны убедиться,

что он отличен от nil (не пуст).

Предположим, например, что у вас есть функция, возвращающая

указатель на некоторый элемент в памяти. Вы можете указать, что

такая функция не может найти элемент, возвращая значение nil.

var ItemPointer: Pointer;

function FindIten: Pointer;

begin

.

.

.

{ найти элемент, возвращая указатель на него или nil,

если элемент не найден }

end;

begin

ItemPointer := nil; { начнем в предположении nil }

ItemPointer := FindItem; { вызвать функцию }

if ItemPointer <> nil then ... { для надежности разымено-

вания ItemPointer }

end.

Потери динамически распределяемой памяти

При использовании динамически распределяемых переменных час-

то возникает общая проблема, называемая утечкой динамической па-

мяти. Утечка памяти - это ситуация, когда пространство выделяется

в динамически распределяемой памяти и затем теряется - по ка-

ким-то причинам ваш указатель не указывает больше на распределен-

ную область, так что вы не можете освободить пространство.

Общей причиной утечек памяти является переприсваивание дина-

мических переменных без освобождения предыдущих. Простейшим слу-

чаем является следующий:

var IntPointer: ^Integer;

begin

New(IntPointer);

New(IntPointer);

end.

Пример 8.6 Простая утечка памяти.

При первом вызове New в динамически распределяемой памяти

выделяется 8 байт, и на них устанавливается указатель IntPointer.

Второй вызов New выделяет другие 8 байт, и IntPointer устанавли-

вается на них. Теперь у вас нет указателя, ссылающегося на первые

8 байт, поэтому вы не можете их освободить. В программе эти байты

будут потеряны.

Естественно, утечка памяти может быть не такой очевидной,

как в Примере 8.6. Выделение памяти почти никогда не происходит в

последовательных операторах, но может выполняться в отдельных

процедурах или далеко отстоящих друг от друга частях программы. В

любом случае лучший способ отслеживания динамических переменных -

это установка их в nil при освобождении. Тогда при попытке расп-

ределить их снова вы можете убедиться что они имеют значение nil:

var IntPointer: ^Integer;

begin

New(IntPointer);

.

.

.

Dispose(IntPointer);

IntPointer := nil;

.

.

.

if IntPointer = nil then New(IntPointer);

end.

Управление связанным списком

Предположим, вы хотите написать программу для ведения своих

личных счетов. Вы можете хранить все данные о счетах в записях,

таких как запись типа TCheck, определенная в Примере 8.5. Но при

написании программы трудно предположить, с каким количеством сче-

тов вам придется иметь дело. Одно из решений здесь состоит в соз-

дании большого массива записей счетов, но это приведет к лишним

затратам памяти. Более элегантное и гибкое решение состоит в рас-

ширении определения записи и включении в нее указателя на следую-

щую запись списка, что приведет к образованию связанного списка,

показанного ниже:

type

PCheck = ^TCheck;

TCheck = record

Amount: Real;

Month: 1..12;

Day: 1..31;

Year: 1990..2000;

Payee: string[39];

Next: PCheck; { указывает на следующую запись }

end.

Пример 8.7 Записи в связанном списке.

Теперь вы можете считать каждую запись счета из файла и вы-

делить для нее память. Если запись находится в конце списка, поле

Next следует сделать равным nil. В вашей программе требуется отс-

леживать только два указателя: первый счет в списке и "текущий"

счет.

Построение списка

Ниже приведена процедура, которая строит связанный список

записей, считывая их из файла. Здесь подразумевается, что вы отк-

рыли файл записей TCheck и именем CheckFile, который содержит по

крайней мере одну запись.

var ListChecks, CurrentCheck: PCheck;

procedure ReadChecks;

begin

New(ListOfChecks); { выделить память для первой записи }

Read(CheckFile, ListOfChecks^); { считать первую запись }

CurrentCheck := ListOfChecks; { сделать первую запись

текущей }

while not Eof(CheckFile do

begin

New(CurrentCheck^.Next); { выделить память для

следующей записи }

Read(CheckFile, CurrentCheck^.Next^); { считать

следующую запись }

CurrentCheck := CurrentCheck^.Next; { сделать следующую

запись текущей }

end;

CurrentCheck^.Next := nil; { после последней считанной

записи следующей нет }

end.

Пример 8.8 Построение связанного списка.

Перемещение по списку

Когда у вас есть список, вы можете легко выполнять поиск в

нем конкретной записи. В Примере 8.9 показана функция, которая

находит первый счет с конкретной суммой и возвращает указатель на

него.

function FindCheckByAmount(AnAmount: Real): PCheck;

var Check: PCheck;

begin

TempCheck := ListOfChecks; { указывает на первую запись }

while (Check^.Amount <> AnAmount) and

(Check^.Next <> nil) do

Check := Check^.Next;

if Check^.Amount = AnAmount then

FindCheckByAmount := Check { возвращает указатель на

найденную запись }

else FindCheckByAmount := nil; { или nil, если таких

записей нет }

end;

Рис. 8.9 Поиск в связанном списке.

Освобождение выделенной для списка памяти

Как показано в процедуре DisposeChecks в Примере 8.10, вы

можете перебрать список, дойдя до каждого элемента и освободив

его.

procedure DisposeChecks;

var Temp: PCheck;

begin

CurrentCheck := ListOfChecks; { указывает на первую

запись }

while CurrentCheck <> nil do

begin

Temp := CurrentCheck^.Next { сохранить указатель Next }

Dispose(CurrentCheck); { освобождение текущей записи }

CurrentCheck := Temp; { сделать сохраненную запись

текущей }

end;

end;

Пример 8.10 Освобождение памяти для связанного списка.


Глава 9. Объектно-ориентированное программирование

Объектно-ориентированное программирование представляет собой

метод программирования, который весьма близко напоминает наше по-

ведение. Оно является естественной эволюцией более ранних новов-

ведений в разработке языков программирования. Объектно-ориентиро-

ванное программирование является более структурным, чем все пре-

дыдущие разработки, касающиеся структурного программирования. Оно

также является более модульным и более абстрактным, чем предыду-

щие попытки абстрагирования данных и переноса деталей программи-

рования на внутренний уровень. Объектно-ориентированный язык

программирования характеризуется тремя основными свойствами:

1. Инкапсуляция. Комбинирование записей с процедурами и

функциями, манипулирующими полями этих записей, формирует

новый тип данных - объект.

2. Наследование. Определение объекта и его дальнейшее ис-

пользование для построения иерархии порожденных объектов

с возможностью для каждого порожденного объекта, относя-

щегося к иерархии, доступа к коду и данным всех порождаю-

щих объектов.

3. Полиморфизм. Присваивание действию одного имени, которое

затем совместно используется вниз и вверх по иерархии

объектов, причем каждый объект иерархии выполняет это

действие способом, именно ему подходящим.

Языковые расширения Borland Pascal предоставляют вам все

средства объектно-ориентированного программирования: большую

структурированность и модульность, большую абстрактность и встро-

енную непосредственно в язык возможность повторного использова-

ния. Все эти характеристики соответствуют коду, который является

более структурированным, более гибким и более легким для обслужи-

вания.

Объектно-ориентированное программирование порой требует от

вас оставить в стороне характерные представления о программирова-

нии, которые долгие годы рассматривались, как стандартные. Однако

после того, как это сделано, объектно-ориентированное программи-

рование становится простым, наглядным и превосходным средством

разрешения многих проблем, которые доставляют неприятности тради-

ционному программному обеспечению.

Дадим хороший совет тому, кто уже имел дело с объектно-ори-

ентированным программированием на других языках. Оставьте в сто-

роне ваши прежние впечатления об объектно-ориентированном прог-

раммировании и изучайте объектно-ориентированные характеристики

Borland Pascal в их собственных терминах. Объектно-ориентирован-

ное программирование не является единственным путем, оно предс-

тавляет собой континуум идей. По своей объектной философии

Borland Pascal больше напоминает С++, чем Smalltalk. Smalltalk

является интерпретатором, тогда как Borland Pascal с самого нача-

ла был чистым компилятором реального кода. Компилятор реального

кода выполняет работу иначе (и значительно быстрее), чем интерп-

ретатор. Borland Pascal был сконструирован, как инструмент разра-

ботки продуктов, а не как инструмент исследования.

Для тех, кто не имеет об этом ни малейшего понятия, мы не

будем подробно объяснять, что такое объектно-ориентированное

программирование. В этот вопрос и так уже внесено достаточно пу-

таницы. Поэтому забудьте о том, что люди говорили вам об объектно

-ориентированное программировании. Наилучший способ (и, фактичес-

ки, единственный) изучить что-либо полезное об объектно-ориенти-

рованное программировании - это сделать то, что вы уже почти сде-

лали: сесть и попытаться узнать все самостоятельно.

Объекты

Посмотрите вокруг себя... и вы обнаружите яблоко, которое вы

купили к завтраку. Допустим, что вы намерены описать яблоко в

терминах программирования. Первое, что вы, возможно, попытаетесь

сделать, так это рассмотреть его по частям; пусть S представляет

площадь кожуры, J представляет содержащийся в яблоке объем жидко-

го сока, F представляет вес фрукта внутри кожуры, D - число семе-

чек...

Не думайте таким образом. Думайте как живописец. Вы видите

яблоко и вы рисуете яблоко. Изображение яблока не есть само ябло-

ко. Оно является символом на плоской поверхности. Но оно не может

быть абстрагировано в несколько чисел, каждое из которых располо-

жено отдельно и независимо где-нибудь в сегменте данных. Его ком-

поненты остаются вместе в их существенной взаимосвязи друг с дру-

гом.

Объекты моделируют характеристики и поведение элементов ми-

ра, в котором мы живем. Они являются окончательной абстракцией

данных.

Примечание: Объекты содержат вместе все свои характе-

ристики и особенности поведения.

Яблоко можно разрезать на части, но как только оно будет

разрезано, оно больше не будет яблоком. Отношения частей к целому

и взаимоотношения между частями становятся понятнее тогда, когда

все содержится вместе в одной упаковке. Это называется инкапсуля-

цией и является очень важным понятием. Немного позже мы к нему

вернемся.

Не менее важным является и тот факт, что объекты могут нас-

ледовать характеристики и поведение того, что мы называем порож-

дающие, родительские объекты (или предки). Здесь происходит ка-

чественный скачок: наследование, возможно, является сегодня

единственным самым крупным различием между обычным программирова-

нием на Паскале и объектно-ориентированным программированием в

Borland Pascal.

Наследование

Целью науки является описание взаимодействий во вселенной.

Большая часть работы в науке при продвижении к цели заключается

просто в построении генеалогических деревьев. Когда энтомолог

возвращается с Амазонки с неизвестным ранее насекомым в банке, то

главной его заботой является определение, где это насекомое рас-

полагается в гигантской схеме, на которой собраны научные назва-

ния всех других насекомых. Аналогичные схемы составлены для рас-

тений, рыб, млекопитающих, рептилий, химических элементов, эле-

ментарных частиц и космических галактик. Все они выглядят как ге-

неалогические деревья: с единой всеобщей категорией в вершине и

все увеличивающимися число категорий, которые лежат ниже этой

единственной категории, и разворачиваются веером по мере прибли-

жения к границам разнообразия.

Внутри категории "насекомые" имеется два подразделения: на-

секомые с видимыми крыльями и насекомые со спрятанными крыльями

или вообще бескрылые. Среди крылатых имеется большее число кате-

горий; мотыльки, бабочки, мухи и т.д. Каждая категория содержит

большое число подкатегорий, а ниже этих подкатегорий может иметь-

ся даже еще большее число подкатегорий (см. Рис. 9.1).

Этот процесс классификации называется таксономией. Это прек-

расная начальная метафора для механизма наследования в объект-

но-ориентированном программировании.

Вопросами, которые задает ученый при попытке классификации

некоторого животного или объекта, являются следующие. В чем этот

объект похож на другие объекты из общего класса? В чем он отлича-

ется от других объектов? Каждый конкретный класс имеет множество

свойств поведения и характеристик, определяющих этот класс. Уче-

ный начинает с вершины конкретного генеалогического дерева и про-

ходит по дочерним областям, задавая себе эти вопросы. Наивысший

уровень самый общий, а вопросы самые простые, например, крылатое

или бескрылое? Каждый последующий уровень является более специфи-

ческим, чем предыдущий, и менее общим. В конце концов, ученый до-

бирается до точки подсчета волосков на третьем сегменте задней

ноги насекомого - что воистину специфично. (И скорее всего о нем

следует сказать, что он не энтомолог.)

ЪДДДДДДДДДДДї

і насекомые і

АДВДДДДДДДВДЩ

ЪДДДДДДДДДДДДДДДЩ і

і і

ЪДДДДБДДДДДї ЪДДДБДДДДДДДї

і крылатые і і бескрылые і

АВДДДВДДДВДЩ АДДДДДДДДДДДЩ

ЪДДДДДДДДЩ і АДДДДДДДї

ЪДДДБДДДДДДїЪДДДДБДДДДїЪДДДДДБДДДДї

і мотыльки іі бабочки іі мухи і

АДДДДДДДДДДЩАДДДДДДДДДЩАДДДДДДДДДДЩ

Рис. 9.1 Таксономическая схема насекомых.

Важно помнить то, что если характеристика однажды определе-

на, то все категории, расположенные ниже данного определения, со-

держат эту характеристику. Таким образом, как только вы определи-

ли насекомое, как члена отряда diptera (мухи), то вам не следует

отмечать снова, что у мух имеется одна пара крыльев. Разновид-

ность насекомых, которую мы зовем мухи, наследует эту характерис-

тику от своего отряда.

Как вы поняли, объектно-ориентированное программирование в

большой степени является процессом построения генеалогического

дерева для структур данных. Одной из важных особенностей, которые

объектно-ориентированное программирование добавляет традиционным

языкам типа Паскаль, является механизм, с помощью которого типы

данных могут наследовать характеристики более простых, более об-

щих типов. Этим механизмом является наследование.

Объекты: наследующие записи

В терминах Паскаля, объект во многом схож с записью, которая

является оболочкой для объединения нескольких связанных элементов

под одним именем. Предположим, вы хотите разработать программу

вывода платежной ведомости, печатающую отчет и показывающую,

сколько нужно выплатить каждому служащему за рабочий день. Запись

можно организовать следующим образом:

TEmployee = record

Name: string[25];

Title: string[25];

Rate: Real;

end;

Примечание: По соглашению все типы начинаются с буквы

T. Вы также можете следовать этому правилу.

Здесь TEmployee является типом записи, т.е. шаблоном, ис-

пользуемым компилятором для создания переменных типа запись. Пе-

ременная типа TEmployee является экземпляром этого типа. Термин

"экземпляр" будет вам нередко встречаться в Паскале. Он постоянно

применяется теми, кто использует методы объектно-ориентированного

программирования, поэтому будет хорошо, если вы начнете мыслить в

терминах типов и экземпляров этих типов.

Вы можете оперировать с типом TEmployee двояко. Вы можете

рассматривать поля Name, Title и Rate по отдельности, а когда о

полях, как о работающих одновременно для описания конкретного

рабочего, вы можете рассматривать их совокупность, как TEmployee.

Предположим, что на вашей фирме работает несколько типов

рабочих. Одни из них имеют почасовую оплату, другие - оклад,

третьи - тарифную ставку и так далее. Ваша программа должна учи-

тывать все эти типы. Вы можете создать другой тип записи для каж-

дого типа рабочего. Например, для получения данных о том, сколько

должен получить рабочий с почасовой оплатой, нужно знать, сколько

часов он отработал. Можно построить запись THourly вида:

THourly = record

Name: string[25];

Title: string[25];

Rate: Real;

end;

Вы можете также оказаться несколько догадливее и сохранить

тип TEmployee путем создания поля типа TEmployee внутри типа

THourly:

THourly = record

Worker: TEmployee;

Time: integer;

end;

Такая конструкция работает, и программисты, работающие на

Паскале, делают это постоянно. Единственное, чего этот метод не

делает, так это то, что он заставляет вас думать о том, с чем вы

работаете в вашем программном обеспечении. Вам следует задаться

вопросом типа; "Чем почасовик отличается от дpугих pабочих?" От-

вет прост: почасовик - это pабочий, котоpому платится за коли-

чество часов pаботы. Продумайте снова первую часть предложения;

почасовик - это pабочий...

Теперь вы поняли!

Запись для pаботника-почасовика hourly должна иметь все за-

писи, котоpые имеются в записи employee. Tип THourly является до-

черним типом для типа TEmployee. THourly наследует все, что при-

надлежит TEmployee, и кроме того имеет кое-что новое, что делает

THourly уникальным.

Этот процесс, с помощью которого один тип наследует характе-

ристики другого типа, называется наследованием. Наследник называ-

ется порожденным (дочерним) типом, а тип, которому наследует до-

черний тип, называется порождающим (родительским) типом.

Ранее известные типы записей Паскаля не могут наследовать.

Однако Borland Pascal расширяет язык Паскаль для поддержки насле-

дования. Одним из этих расширений является новая категория струк-

туры данных, связанная с записями, но значительно более мощная.

Типы данных в этой новой категории определяются с помощью нового

зарезервированного слова object. Тип объекта может быть определен

как полный, самостоятельный тип в манере описания записей Паска-

ля, но он может определяться и как потомок существующего типа

объекта путем помещения порождающего (родительского) типа в скоб-

ки после зарезервированного слова object.

В приводимом здесь примере платежной ведомости два связанных

типа объектов могли бы определяться следующим образом:

type

TEmployee = object

Name: string[25];

Title: string[25];

Rate : Real;

end;

THourly = object(TEmployee)

Time : Integer;

end;

Примечание: Обратите внимание, что здесь использование

скобок означает наследование.

Здесь TEmployee является родительским типом, а THourly - до-

черним типом. Как вы увидите чуть позднее, этот процесс может

продолжаться неопределенно долго. Вы можете определить дочерний

тип THourly, дочерний к типу THourly тип и т.д. Большая часть

конструирования объектно-ориентированных прикладных программ сос-

тоит в построении такой иерархии объектов, являющейся отражением

генеалогического дерева объектов в приложениях.

Все возможные типы, наследующие тип TEmployee, называются

дочерними типами типа TEmployee, тогда как THourly является не-

посредственным дочерним типом типа TEmployee. Наоборот, TEmployee

является непосредственным родителем типа THourly. Тип объекта (в

точности как подкаталог в DOS) может иметь любое число непосредс-

твенных дочерних типов, но в то же время только одного непосредс-

твенного родителя.

Как показали данные определения, объекты тесно связаны с за-

писями. Новое зарезервированное слово object является наиболее

очевидным различием, но как вы увидите позднее, имеется большое

число других различий, некоторые из которых довольно тонкие.

Например, поля Name, Title и Rate в типе TEmployee не указа-

ны явно в типе THourly, но в любом случае тип THourly содержит их

благодаря свойству наследования. Вы можете говорить о величине

Name типа THourly в точности так же, как о величине Name типа

TEmployee.

Экземпляры объектных типов

Экземпляры объектных типов описываются в точности так же,

как в Паскале описывается любая переменная, либо статическая, ли-

бо указатель, ссылающийся на размещенную в динамической памяти

переменную:

type

PHourly = ^THourly;

var

StatHourly: THourly; { готово }

DynaHourly: PHourly; { перед использованием память должна

выделяться с помощью New }

Поля объектов

Вы можете обратиться к полю объекта в точности так же, как к

полю обычной записи, либо с помощью оператора with, либо путем

уточнения имени с помощью точки. Например:

AnHourly.Rate := 9.45;

with AnHourly do

begin

Name := 'Sanderson, Arthur';

Title := 'Word processor';

end;

Примечание: Не забывайте о том, что наследуемые поля

объектов не интерпретируются особым образом только потому,

что они являются наследуемыми.

Именно сейчас вы должны запомнить (в конце концов это придет

само собой), что наследуемые поля являются столь же доступными,

как если бы они были объявлены внутри типа объекта. Например, да-

же если Name, Title и Rate не являются частью описания типа

THourly (они наследованы от типа TEmployee), то вы можете ссы-

латься на них, словно они описаны в THourly:

AnHourly.Name := 'Arthur Sanderson';

Хорошая и плохая техника программирования

Даже если вы можете обратиться к полям объекта непосредс-

твенно, это будет не совсем хорошей идеей. Принципы объектно-ори-

ентированного программирования требуют, чтобы поля объектов были

исключены из исходного кода, насколько это возможно. Это ограни-

чение поначалу может показаться спорным и жестким, но оно являет-

ся только частью огромной картины объектно-ориентированное прог-

раммирования, которую мы нарисуем в этой главе. Со временем вы

увидите смысл, скрытый в этом новом определении хорошей практики

программирования, хотя имеются некоторые основания приоткрыть его

перед тем, как все придет само. А пока же примите на веру: избе-

гайте прямого обращения к полям данных.

Примечание: Borland Pascal позволяет вам делать поля

объекта и его методы частными. Подробнее об этом рассказы-

вается ниже.

Итак, как же обращаться к полям объекта? Как читать их и как

присваивать им значения?

Примечание: Поля данных объекта - это то, что объект

"знает", а методы объекта - это то, что объект "делает".

Ответом заключается в том, что при всякой возможности для

доступа к полям данных должны использоваться методы объекта. Ме-

тод является процедурой или функцией, описанной внутри объекта и

жестко ограниченной этим объектом.

Методы

Методы являются одними из наиболее примечательных атрибутов

объектно-ориентированное программирования и требуют некоторой

практики перед использованием. Вернемся сначала к исходному воп-

росу о тщетной попытке структурного программирования, связанной с

инициализацией структур данных. Рассмотрим задачу инициализации

записи со следующим определением:

TEmployee = object

Name: string[25];

Title: string[25];

Rate: Real;

end;

Большинство программистов использовали бы оператор with для

присвоения полям Name, Title и Rate начальных значений:

var

MyEmployee: Employee;

with MyEmployee do

begin

Name := 'Sanderson, Arthur';

Title := 'Word processor';

Rate := 9.45;

end;

Это тоже неплохо, но здесь мы жестко ограничены одной специ-

фическим экземпляром записи - MyEmployee. Если потребуется иници-

ализировать более одной записи типа Employee, то вам придется ис-

пользовать большее число операторов with, которые выполняют в

точности те же действия. Следующим естественным шагом является

создание инициализирующей процедуры, которая обобщает оператор

with применительно к любому экземпляру типа TEmployee, пересылае-

мой в качестве параметра:

procedure InitTEmployee(var Worker: TEmployee; AName,

ATitle: String; ARate: Real);

begin

with Worker do

begin

Name := NewName ;

Title := NewTitle;

Rate := NewRate;

end;

end;

Это будет работать, все в порядке, однако если вы почувству-

ете, что при этом тратите больше времени, чем необходимо, то вы

почувствуете то же самое, что чувствовал ранний сторонник объект-

но-ориентированного программирования.

Это чувство значит, что, ну, скажем, вы разрабатываете про-

цедуру InitTEmployee специально для обслуживания типа TEmployee.

Тогда почему вы должны помнить, какой тип записи и какой его эк-

земпляр обрабатывает InitTEmployee? Должен существовать некоторый

путь объединения типа записи и обслуживающего кода в одно единое

целое.

Такой путь имеется и называется методом. Метод - это проце-

дура или функция, объединенная с данным типом столь тесно, что

метод является как бы окруженным невидимым оператором with, что

делает экземпляр данного типа доступными изнутри для метода. Оп-

ределение типа включает заголовок метода. Полное определение ме-

тода квалифицируется в имени типа. Тип объекта и метод объекта

являются двумя лицами этой новой разновидности структуры, именуе-

мой методом.

type

TEmployee = object

Name, Title: string[25];

Rate: Real;

procedure Init (NewName, NewTitle: string[25];

NewRate: Real);

end;

procedure TEmployee.Init (NewName, NewTitle: string[25];

NewRate: Real);

begin

Name := NewName ; { Поле Name объекта TEmployee }

Title := NewTitle; { Поле Tutle объекта TEmployee }

Rate := NewRate; { Поле Rate объекта TEmployee }

end;

Теперь для инициализации экземпляра типа TEmployee вы просто

вызываете его метод, словно метод является полем записи, что име-

ет вполне реальный смысл:

var

AnEmployee: TEmployee;

AnEmployee.Init('Sara Adams, Account manager, 15000');

{пpосто, не так ли?}

Совмещенные код и данные

Одним из важнейших принципов объектно-ориентированного прог-

раммирования является то, что программист во время разработки

программы должен думать о коде и о данных совместно. Ни код, ни

данные не существуют в вакууме. Данные управляют потоком кода, а

код манипулирует образами и значениями данных.

Если ваши код и данные являются разделенными элементами, то

всегда существует опасность вызова правильной процедуры с невер-

ными данными или ошибочной процедуры с правильными данными. Забо-

та о совпадении этих элементов возлагается на программиста, и хо-

тя строгая типизация Паскаля здесь помогает, самое лучшее, что он

может сделать - это указать на несоответствие.

О том, что действительно существует вместе, Паскаль нигде не

сообщает. Если это не отмечено комментарием или не то, о чем вы

все время помните, то вы играете с судьбой.

Объект осуществляет синхронизацию кода и данных путем сов-

местного построения их описаний. Реально, чтобы получить значение

одного из полей объекта, вы вызываете относящийся к этому объекту

метод, который возвращает значение нужного поля. Чтобы присвоить

полю значение, вы вызываете метод, который назначает данному полю

новое значение.

Однако, Borland Pascal не вынуждает вас делать это. Как вся-

кое структурное программирование, объектно-ориентированное прог-

раммирование является дисциплиной, которую вы должны навязать се-

бе, используя предоставляемые языком средства. Borland Pascal

позволяет вам обращаться к полям объекта непосредственно извне

объекта, однако он поощряет вас использовать преимущества объект-

но-ориентированного программирования и создавать методы для мани-

пулирования полями объекта внутри самого объекта. Borland Pascal

позволяет задать принудительную инкапсуляцию с помощью использо-

вания описания private в объявлении объекта.

Примечание: Подробнее о принудительной инкапсуляции

рассказывается ниже в разделе "Секция private".

Определение методов

Процесс определения методов объектов напоминает модули

Borland Pascal. Внутри объекта метод определяется заголовком про-

цедуры или функции, действующей как метод:

type

TEmployee = object

Name, Title: string[25];

Rate: Real;

procedure Init (AName, ATitle: String; ARate: Real);

function GetName : String;

function GetTitle : String;

function GetRate : Real;

end;

Примечание: Поля данных должны быть описаны перед пер-

вым описанием метода.

Как и описания процедур и функций в интерфейсной секции мо-

дуля (interface), описание методов внутри объекта говорит, что

методы делают, но не говорит, как.

Это определяется вне определения объекта, в отдельном описа-

нии процедуры или функции. Если метод полностью определяется вне

объекта, то имени метода должно предшествовать имя типа объекта,

которому принадлежит этот метод, с последующей точкой:

procedure TEmployee.Init(AName, ATitle: string;ARate: Real);

begin

Name := AName;

Title := ATitle;

Rate := ARate;

end;

function TEmployee.GetName: String;

GetName := Name;

end;

function TEmployee.GetTitle: String;

begin

GetTitle := Title;

end;

function TEmployee.GetRate: Real;

begin

GetRate := Rate;

end;

Метод опpеделения следует методу интуитивного pазделения

точками для указания поля записи. Кpоме наличия опpеделения

TEmployee.GetName можно было бы опpеделить пpоцедуpу с именем

GetName, в имени котоpой нет пpедшествующего идентификатоpа

TEmployee. Однако, такая "внешняя" GetName не будет иметь никакой

связи с объектом типа TEmployee и будет только запутывать смысл

пpогpаммы.

Область действия метода и параметр Self

Заметьте, что ни в одном из предыдущих примеров конструкция:

with объект do... не встречается в явном виде. Поля данных объек-

та легко доступны с помощью методов объекта. Хотя в исходном коде

поля данных объекта и тела методов разделены, на самом деле они

совместно используют одну и ту же область действия.

Именно поэтому один из методов TEmployee может содержать

оператор GetTitle := Title без какого-либо квалификатора перед

Title. И именно поэтому Title принадлежит тому объекту, который

вызывает метод. Если объект вызывает метод, то выполняется неяв-

ный оператор with myself do method, связывающий объект и его ме-

тоды в области действия.

Неявный оператор with выполняется путем передачи невидимого

параметра методу всякий раз, когда этот метод вызывается. Этот

параметр называется Self и в действительности является 32-разряд-

ным указателем на экземпляр объекта, осуществляющего вызов мето-

да. Относящийся к TEmployee метод GetRate приблизительно эквива-

лентен следующему:

function TEmployee.GetRate(var Self: TEmployee): integer;

begin

GetRate := Self.Rate;

end;

Примечание: Синтаксически этот пример не совсем кор-

ректен. Он приведен только затем, чтобы дать вам более пол-

ное представление о специфической связи между объектом и

его методом.

Но важно ли вам знать о существовании параметра Self? Обычно

нет. Генерируемый Borland Pascal код выполняет все это автомати-

чески. Однако в некоторых немногочисленных случаях вы можете за-

хотеть проникнуть внутрь метода и использовать параметр Self яв-

но.

Примечание: Явное использование параметра Self допуска-

ется, но вы должны избегать ситуаций, в которых это требует-

ся.

Параметр Self является частью физического кадра стека при

всех вызовах методов. Методы, используемые как внешние на языке

Ассемблера, должны учитывать Self при получении доступа к пара-

метрам метода в стеке.

Примечание: Более подробно об использовании методом

границ стека рассказывается в Главе 22 "Руководства по язы-

ку".

Поля данных объекта и формальные параметры метода

Выводом из того факта, что методы и их объекты разделяют об-

щую область действия, является то, что формальные параметры мето-

да не могут быть идентичными любому из полей данных объекта. Это

является не каким-то новым ограничением, налагаемым объектно-ори-

ентированным программированием, а скорее теми же самыми старыми

правилами области действия, которые Паскаль имел всегда. Это то

же самое, что и запрет для формальных параметров процедуры быть

идентичными локальным переменным этой процедуры:

procedure CrunchIt(Crunchee: MyDataRec,

Crunchby, ErrorCode: integer);

var

A, B: char;

ErrorCode: integer;

begin

.

.

.

Локальные переменные процедуры и ее формальные параметры

совместно используют общую область действия и поэтому не могут

быть идентичными. Вы получите сообщение "Error 4: Duplicate

identifier" (Ошибка 4; Повторение идентификатора), если попытае-

тесь компилировать что-либо подобное, та же ошибка возникает при

попытке присвоить формальному параметру метода имени поля объек-

та, которому данный метод принадлежит.

Обстоятельства несколько отличаются, так как помещение заго-

ловка процедуры внутрь структуры данных является намеком на нов-

шество в Турбо Паскале, но основные принципы области действия

Паскаля не изменились.

Объекты, экспортируемые модулями

Имеет смысл определять объекты в модуле посредством описаний

типа объекта в интерфейсной части модуля, а тела процедур и мето-

ды объекта - в секции реализации. Для определения объекта в моду-

ле не требуется никаких специальных соглашений.

Примечание: Экспортируемый - означает "определенный в

интерфейсной части модуля".

Модули могут иметь свои собственные приватные (частные) оп-

ределения типов объектов внутри выполняемой секции, и эти типы

подвержены тем же ограничениям, как и всякие другие типы, опреде-

ленные в секции реализации. Типы объектов, определенные в интер-

фейсной части модуля, могут иметь дочерние типы объектов, опреде-

ленные в секции реализации модуля. В том случае, когда модуль B

использует модуль A, модуль B также может определять дочерние ти-

пы любого типа объекта, экспортируемого модулем A.

Описанные ранее типы объектов и методы можно определить в

модуле, как показано в программе WORKERS.PAS на дистрибутивном

диске. Чтобы использовать типы объектов и методы, определенные в

модуле Workers, вы можете просто использовать этот модуль в своей

программе и описать экземпляр типа THourly в секции переменных

программы:

program HourPrt;

uses WinCrt, Workers;

var

AnHourly: THourly;

.

.

.

Для создания и вывода фамилии pабочего-почасовика, его долж-

ности и pазмеpа выплаты, пpедставленной пеpеменной AnHourly, вы

просто вызываете методы AnHourly, используя следующий синтаксис:

AnHourlye.Init('Sara Adams', 'Account manager', 1400);

{ записывает в экземпляp THourly }

{ данные для Саpы Адамс: фамилию, }

{ должность и pазмеp выплаты. }

AnHourly.Show;

Примечание: Объектами могут быть также типизированные

константы.

Объекты, будучи очень схожими с записями, могут использо-

ваться внутри оператора with. В этом случае указание имени объек-

та, являющегося собственником методов, не является необходимым:

with AnHourly do

begin

Init('Sara Adams', 'Account manager', 1400);

Show;

end;

Как и в случаях с записями, объекты могут передаваться в ка-

честве параметра процедуре и (как вы увидите позднее) могут раз-

мещаться в динамически распределяемой памяти.

Секция private

В некоторых случаях у вас могут иметься части описаний объ-

ектов, которые экспортировать нежелательно. Например, вы можете

предусмотреть объекты для других программистов, которые они могут

использовать, но не могут непосредственно манипулировать с данны-

ми объекта. Чтобы облегчить это, Borland Pascal позволяет зада-

вать внутри объектов приватные (закрытые) поля и методы.

Приватные поля и методы доступны только внутри того модуля,

в котором описан объект. В предыдущем примере, если бы тип

THourly содержал приватные поля, то доступ к ним можно было бы

получить только в модуле THourly. Даже если другие части объекта

THourly можно было бы экспортировать, (части, описанные, как при-

ватные, были бы недоступными.

Приватные поля и методы описываются непосредственно после

обычных полей и методов, вслед за зарезервированным словом

private. Таким образом, полный синтаксис описания объекта будет

следующим:

type

NewObject = object(родитель)

поля; { общедоступные }

методы; { общедоступные }

private

поля; { приватные }

методы; { приватные }

end;

Программирование в "действительном залоге"

Большая часть из того, что говорилось об объектах до сих

пор, исходит из удобств и перспектив Borland Pascal, поскольку

наиболее вероятно, что это именно то, с чего вы начнете. Теперь

начнутся изменения, поскольку мы подошли к концепциям объект-

но-ориентированного программирования с помощью некоторых принци-

пов программирования на стандартном Паскале. Объектно-ориентиро-

ванное программирование имеет свое собственное отдельное множест-

во понятий, частично благодаря началам объектно-ориентированного

программирования (до некоторой степени ограниченным) в научных

кругах, однако также и потому, что эта концепция действительно

является радикально отличной от других.

Примечание: Объектно-ориентированные языки однажды ме-

тафорично назвали "языками актеров".

Одним, часто забавным, следствием этого явилось то, что объ-

ектно-ориентированное программирование фанатично "одушевляет"

свои объекты. Отныне данные для вас не емкости, которые вы можете

наполнять значениями. С точки зрения нового взгляда на вещи, объ-

екты выглядят как актеры на подмостках со множеством заученных

ролей (методов). Если вы (директор) даете им слово, то актеры на-

чинают декламировать в соответствии со сценарием.

Было бы полезно представить функцию AnHourly.GetPayAmount

как, например, дающую распоряжение объекту AnHourly "Вычислить

размер вашей ежедневной платы". Центральной концепцией здесь яв-

ляется объект. Этот объект обслуживают как список методов, так и

список полей данных, содержащихся в объекте. И ни код, ни данные

не являются здесь "директором".

Чтобы быть совсем привлекательным, объект не может быть опи-

сан как актер на сцене. Образцу объектно-ориентированного прог-

раммирования с большим трудом удается моделировать составляющие

проблемы как компоненты, а не как логические абстракции. Случай-

ности и закономерности, наполняющие нашу жизнь (от тостеров до

телефонных звонков по поводу махровых полотенец) все имеют харак-

теристики (данные) и линии поведения (методы). Характеристики

тостера могут включать требуемое напряжение, число гренок, кото-

рые он может поджарить одновременно, установку слабого или силь-

ного уровней поджаривания, цвет тостера, его фабричную марку и т.

д. Его поведение может включать загрузку кусков хлеба, поджарива-

ние этих кусков и автоматическое выталкивание готовых гренок на-

ружу.

Если мы хотим написать программу имитации кухни, то какой же

имеется наилучший способ смоделировать различные приспособления,

кроме объектов, с их характеристиками и линиями поведения, зако-

дированными в поля данных и в методах? Фактически, это уже сдела-

но: один из первых объектно-ориентированных языков (Симула-67)

был создан как язык для написания таких имитаций.

Есть также причина того, что объектно-ориентированное прог-

раммирование довольно крепко связано в традиционном смысле с ори-

ентированной на построение графиков средой. Объекты должны быть

моделями, и есть ли лучший способ смоделировать объект, чем нари-

совать его изображение? Объекты в Borland Pascal должны имитиро-

вать компоненты проблему, которую вы пытаетесь разрешить. Примите

это во внимание, если в дальнейшем вы намерены эксплуатировать

новые объектно-ориентированные расширения Borland Pascal.

Инкапсуляция

Объединение в объекте кода и данных называется инкапсуляци-

ей. Возможно вы сможете предоставить достаточное количество мето-

дов, благодаря чему пользователь объекта никогда не будет обра-

щаться к полям объекта непосредственно. Некоторые другие объектно

-ориентированные языки, например Smalltalk, требуют обязательной

инкапсуляции, однако в Borland Pascal у вас есть выбор, а хорошая

практика объектно-ориентированного программирования во многом за-

висит от вашей добросовестности.

Объекты TEmployee и THourly написаны таким образом, что со-

вершенно исключена необходимость прямого обращения к их внутрен-

ним полям данных:

type

TEmployee = object

Name, Title: string[25];

Rate: Real;

procedure Init (AName, ATitle: string; ARate: Real);

function GetName : String;

function GetTitle : String;

function GetRate : Real;

function GetPayAmount : Real;

end;

THourly = object(TEmployee)

Time: Integer;

procedure Init(AName, ATitle: string; ARate:

Real, Atime: Integer);

function GetPayAmount : Real;

end;

Здесь присутствуют только четыpе поля данных: Name, Title,

Rate и Time. Методы ShowName и ShowTitle выводят фамилию pаботаю-

щего и его должность, соответственно. Метод GetPayAmount исполь-

зует Rate, а в случае pаботающего THourly и Time для вычисления

суммы выплат pаботающему. Здесь уже нет необходимости обpащаться

непосpедственно к этим полям данных.

Предположив существование экземпляра AnHourly типа THourly,

вы могли бы использовать набор методов для манипулирования полями

данных AnHourly, например:

with AnHourly do

begin

Init ('Allison Karlon, Fork lift operator' 12.95, 62);

{ Выводит на экpан фамилию, должность и сумму выплат}

Show;

end;

Обратите внимание, что доступ к полям объекта осуществляется

не иначе, как только с помощью методов этого объекта.

Методы: никакого ухудшения

Добавление этих методов незначительно увеличивает объем ис-

ходного кода, однако развитый компоновщик Borland Pascal выбрасы-

вает код любого метода, который ни разу не вызывается в програм-

ме. Поэтому вам не следует отступать при предоставлении объекту

того или иного метода, который имеет одинаковые шансы быть как

использованным, так и неиспользованным в каждой программе, в ко-

торой задействован данный тип объекта. Неиспользуемые методы ни-

чего не будут стоить вам как в части качества выполнения програм-

мы, так и в части ее размера, - если они не используются в прог-

рамме, то они попросту отсутствуют в ней.

Замечание по поводу абстрактности данных: Имеется громадное

преимущество в возможности полностью отсоединить THourly от гло-

бальных ссылок. Если ничто вне объекта не "знает" о представлении

его внутренних данных, то программист, контролирующий объект, мо-

жет изменять детали внутреннего представления данных до тех пор,

пока не изменится заголовок метода.

Внутри самого объекта данные могут быть представлены в виде

массива, однако позднее (возможно, что сфера действия прикладной

программы расширяется и объем ее данных растет) в качестве более

эффективного представления данных может быть признано двоичное

дерево. Если объект полностью инкапсулирован, изменение представ-

ления данных с массива на двоичное дерево вообще не изменит ис-

пользование объекта. Интерфейс с объектом останется полностью тем

же, позволяя программисту изящно приспосабливать эксплуатационные

качества объекта без изменения кода, использующего объект.

Расширяющиеся объекты

Люди, которые впервые сталкиваются с Паскалем, зачастую счи-

тают само собой разумеющейся гибкость стандартной процедуры

Writeln, которая позволяет единственной процедуре обрабатывать

параметры многих различных типов:

Writeln(CharVar); { Вывести значение символьного типа }

Writeln(IntegerVar); { Вывести целое значение }

Writeln(RealVar); { Вывести значение с плавающей

точкой }

К сожалению, стандартный Паскаль не предоставляет лично вам

никаких возможностей для создания столь же гибких процедур.

Объектно-ориентированное программирование решает эту пробле-

му с помощью наследования: если определен порожденный тип, то ме-

тоды порождающего типа наследуются, однако при желании они могут

переопределяться. Для переопределения наследуемого метода попрос-

ту опишите новый метод с тем же именем, что и наследуемый метод,

но с другим телом и (при необходимости) с другим множеством пара-

метров.

Простой пример прояснит как процесс так и его смысл. Давайте

определим дочерний по отношению к TEmployee тип, пpедставляющий

pаботника, котоpому платится часовая ставка:

const

PayPeriods = 26; { периоды выплат }

OvertimeThreshold = 80; { на период выплаты }

OvertimeFactor = 1.5; { почасовой коэффициент }

type

THourly = object(TEmployee)

Time: Integer;

procedure Init(AName, ATitle: string; ARate:

Real, Atime: Integer);

function GetPayAmount : Real;

end;

procedure THourly.Init(AName, ATitle: string;

ARate: Real, Atime: Integer);

begin

TEmployee.Init(AName, ATitle, ARate);

Time := ATime;

end;

function THourly.GetPayAmount: Real;

var

Overtime: Integer;

begin

Overtime := Time - OvertimeThreshold;

if Overtime > 0 then

GetPayAmount := RoundPay(OvertimeThreshold * Rate +

Rate OverTime * OvertimeFactor * Rate)

else

GetPayAmount := RoundPay(Time * Rate)

end;

Человек, котоpому платится часовая ставка, является pаботаю-

щим: он обладает всем тем, что мы используем для опpеделения объ-

екта TEmployee (фамилией, должностью, ставкой), и лишь количество

получаемых почасовиком денег зависит от того, сколько часов он

отpаботал за пеpиод, подлежащий оплате. Таким обpазом, для

THourly тpебуется еще и поле вpемени, Time.

Так как THourly опpеделяет новое поле, Time, его инициализа-

ция тpебует нового метода Init, котоpый инициализиpует и вpемя, и

наследованные поля. Вместо того, чтобы непосpедственно пpисвоить

значения наследованным полям, таким как Name, Title и Rate, поче-

му бы не использовать вновь метод инициализации объекта TEmployee

(иллюстpиpуемый пеpвым опеpатоpом THourly.Init), где Ancestor

есть идентификатоp типа pодового типа объекта, а Method есть

идентификатоp метода данного типа.

Заметьте, что вызов метода, который вы переопределяете, не

является единственно хорошим стилем. В общем случае возможно, что

TEmployee.Init выполняет важную, однако скрытую инициализацию.

Вызывая переопределяемый метод, вы должны быть уверены в том, что

порожденный тип объекта включает функциональность родителя. Кроме

того, любое изменение в родительском методе автоматически оказы-

вает влияние на все порожденные.

После вызова TEmployee.Init, THourly.Init может затем выпол-

нить свою собственную инициализацию, которая в этом случае состо-

ит только в присвоении значения, переданного в ATime.

Дpугим пpимеpом пеpеопpеделяемого метода является функция

THourly.GetPayAmount, вычисляющая сумму выплат pаботающему на по-

часовой ставке. В действительности, каждый тип объекта TEmployee

имеет свой метод GetPayAmount, так как тип pаботающего зависит от

того, как пpоизводится pасчет. Метод THourly.GetPayAmount должен

учитывать, сколько часов pаботал сотрудник, были ли свеpхуpочные

pаботы, каков коэффициент увеличения за свеpхуpочные pаботы и так

далее. Метод TSalaried.GetPayAmount должен лишь делить ставку

pаботающего на число выплат в каждом году (в нашем пpимеpе 26).

unit Workers;

interface

const

PayPeriods = 26; {в год}

OvertimeThreshold = 80; {за каждый период оплаты}

OvertimeFactor =1.5; {увеличение против обычной оплаты}

type

TEmployee = object

Name, Title: string[25];

Rate: Real;

procedure Init (AName, ATitle: string; ARate: Real);

function GetName : String;

function GetTitle : String;

function GetRate : Real;

function GetPayAmount : Real;

end;

THourly = object(TEmployee)

Time: Integer;

procedure Init(AName, ATitle: string; ARate:

Real, Atime: Integer);

function GetPayAmount : Real;

function GetTime : Real;

end;

TSalaried = object(TEmployee)

function GetPayAmount : Real;

end;

TCommissioned = object(TSalaried)

Commission : Real;

SalesAmount : Real;

constructor Init (AName, ATitle: String;

ARate, ACommission, ASalesAmount: Real);

function GetPayAmount : Real;

end;

implementation

function RoundPay(Wages: Real) : Real;

{ окpугляем сумму выплат, чтобы игноpиpовать

суммы меньше пенни }

begin

RoundPay := Trunc(Wages * 100) / 100;

.

.

.

TEmployee является веpшиной нашей иеpаpхии объектов и со-

деpжит пеpвый метод GetPayAmount.

function TEmployee.GetPayAmount : Real;

begin

RunError(211); { дать ошибку этапа выполнения }

end;

Может вызвать удивление тот факт, что метод дает ошибку

вpемени выполнения. Если вызывается TEmployee.GetPayAmount, то в

пpогpамме возникает ошибка. Почему? Потому что TEmployee является

веpшиной нашей иеpаpхии объектов и не опpеделяет pеального pабо-

чего; следовательно, ни один из методов TEmployee не вызывается

опpеделенным обpазом, хотя они и могут быть наследованными. Все

наши pаботники являются либо почасовиками, либо имеют оклады, ли-

бо pаботают на сдельщине. Ошибка на этапе выполнения пpекpащает

выполнение пpогpаммы и выводит 211, что соответствует сообщению

об ошибке, связанной с вызовом абстpактного метода (если ваша

пpогpамма по ошибке вызывает TEmployee.GetPayAmount).

Ниже пpиводится метод THourly.GetPayAmount, в котоpом учиты-

ваются такие вещи как свеpхуpочная оплата, число отpаботанных ча-

сов и так далее.

function THourly.GetPayAMount : Real;

var

OverTime: Integer;

begin

Overtime := Time - OvertimeThreshold;

if Overtime > 0 then

GetPayAmount := RoundPay(OvertimeThreshold * Rate +

Rate OverTime * OvertimeFactor * Rate)

else

GetPayAmount := RoundPay(Time * Rate)

end;

Метод TSalaried.GetPayAmount намного пpоще; в нем ставка де-

лится на число выплат:

function TSalaried.GetPayAmount : Real;

begin

GetPayAmount := RoundPay(Rate / PayPeriods);

end;

Если взглянуть на метод TСommissioned.GetPayAmount, то будет

видно, что он вызывает TSalaried.GetPayAmount, вычисляет комисси-

онные и пpибавляет их к величине, возвpащаемой методом

TSalaried.GetPayAmount.

function TСommissioned.GetPayAmount : Real;

begin

GetPayAmount := RoundPay(TSalaried.GetPayAmount +

Commission * SalesAmount);

end;

Важное замечание: Хотя методы могут быть переопределены,

поля данных переопределяться не могут. После того, как вы опреде-

лили поле данных в иерархии объекта, никакой дочерний тип не мо-

жет определить поле данных в точности с таким же именем.

Наследование статических методов

Все показанные до сих пор методы, связанные с типами объек-

тов TEmployee, THourly, TSalaried и TCommissioned, являются ста-

тическими методами. Однако, со статическими методами связана

пpоблема наследования.

Для того, чтобы разобраться с этой проблемой, отложим в сто-

рону наш пример с платежной ведомостью и рассмотрим другой упро-

щенный и нереалистичный, но показательный пример. Вернемся к кры-

латым насекомым. Предположим, что нужно создать программу, кото-

рая будет рисовать на экране различные типы летающих насекомых.

Предположим, вы решили, что на вершине иерархии будет находиться

объект Winged. Пусть вы планируете, что новые типы объектов лета-

ющих насекомых как будут строиться как потомки Winged. Например,

вы можете создать тип объекта Bee, который отличается от родс-

твенных крылатых насекомых тем, что имеет жало и полосы. Конечно,

у пчелы есть другие отличающие ее характеристики, но в нашем при-

мере это может выглядеть следующим образом:

type

TWinged = object(Insect)

procedure Init(AX, AY: Integer) { инициализирует

экземпляр }

рrocedure Show; { отображает крылатое насекомое на

экране }

рrocedure Hide; { стирает крылатое насекомое с

экрана }

рrocedure MoveTo(NewX, NewY : Integer);

{ перемещает крылатое насекомое }

end;

tyрe

TBee = object(Winged)

.

.

.

рrocedure Init(AX, AY: Integer) { инициализирует

экземпляр Bee }

рrocedure Show; { отображает пчелу на экране }

рrocedure Hide; { стирает пчелу с экрана }

рrocedure MoveTo(NewX, NewY : Integer);

{ перемещает пчелу }

end;

И TWinged, и TBee имеют по четыре метода. TWinged.Init и

TBee.Init инициализируют экземпляр соответствующих объектов. Ме-

тод TWinged.Show знает, как рисовать крылатое насекомое на

экране, а метод TBee.Show - как рисовать пчелу (крылатое насеко-

мое с полосками на теле и с жалом). Метод TWinged.Hide знает, как

стирать крылатое насекомое с экрана, а метод TBee.Hide - как

стирать пчелу. Два метода Show отличаются друг от друга, равно

как и два метода Hide.

Однако, методы TWinged.MoveTo и TBee.MoveTo полностью одина-

ковы. В нашем примере X и Y определяют положение на экране.

рrocedure TWinged.MoveTo(NewX, NewY: Integer);

begin

Hide;

X := NewX; {новая координата X на экране}

Y := NewY; {новая координата Y на экране}

Show;

end;

рrocedure TBee.MoveTo(NewX, NewY: Integer);

begin

Hide;

X := NewX; {новая координата X на экране}

Y := NewY; {новая координата Y на экране}

Show;

end;

Не изменилось ничего, кроме копирования программы и поста-

новки квалификатора TBee перед идентификатором MoveTo. Так как

методы одинаковы, зачем нужно помещать MoveTo в TBee? Ведь Bee

автоматически наследует MoveTo от TWinged. Поэтому не нужно

переопределять метод MoveTo из TWinged, но это именно то место,

где возникает проблема в случае статических методов.

Термин "статический" был выбран для описания методов, не яв-

ляющихся виртуальными - термин, который мы введем далее. Факти-

чески, виртуальные методы являются решением этой проблемы, но

прежде чем понять решение, вам следует разобраться в самой проб-

леме.

Признаки проблемы состоят в следующем: пока копия метода

MoveTo не будет помещена в область действия TBee для подавления

метода MoveTo объекта TWinged, метод не будет работать правильно,

если он будет вызываться из объекта типа TBee. Если TBee запуска-

ет метод MoveTo объекта TWinged, так то, что движется по экрану,

является крылатым насекомым, а не пчелой. Только когда TBee вызы-

вает копию метода MoveTo, определенного в его собственной области

действия, на экране с помощью вызовов Show и Hide будут рисовать-

ся и стираться пчелы.

Почему это так? Это объясняется способом, которым компилятор

разрешает вызовы методов. Когда компилируются методы Bee, то сна-

чала встречаются TWinged.Show и TWinged.Hide и их код компилиру-

ется в сегмент кода. Немного позднее в файле встречается метод

Winged.MoveTo, который вызывает TWinged.Show и TWinged.Hide. Как

и при вызове любой процедуры, компилятор замещает ссылки на

TWinged.Show и TWinged.Hide в исходном коде на их адреса, сгене-

рированные в сегменте кода. Таким образом, когда вызывается код

TWinged.MoveTo, он, в свою очередь, вызывает TWinged.Show и

TWinged.Hide со всеми вытекающими последствиями.

До сих пор это был типичный для Borland Pascal сценарий и он

был бы справедлив (за исключением номенклатуры), начиная с версии

1.0 Turbo Pascal 1983 года. Однако, дело меняется, когда вы вклю-

чаете в этот сценарий принцип наследования. Когда TBee наследует

метод от TWinged, он (TBee) использует метод в точности так, как

тот был откомпилирован.

Снова посмотрите, что должен наследовать TBee, если он нас-

ледует TWinged.MoveTo:

рrocedure TWinged.MoveTo(NewX, NewY: integer);

begin

Hide; { Вызов Winged.Hide }

X := NewX;

Y := NewY;

Show { Вызов Winged.Show }

end;

Комментарии здесь приведены для того, чтобы подчеркнуть тот

факт, что если Bee вызывает метод TWinged.MoveTo, то он также вы-

зывает TWinged.Show и TWinged.Hide, а не TBee.Show и TBee.Hide.

Поскольку TWinged.MoveTo вызывает методы TWinged.Show и

TWinged.Hide, TWinged.MoveTo нельзя наследовать. Вместо этого, он

должен быть переопределен своей второй копией, которая вызывает

копии Show и Hide, определенные внутри области действия второй

копии, то есть, TBee.Show и TBee.Hide.

При разрешении вызовов методов, логика компилятора работает

так: при вызове метода компилятор сначала ищет метод, имя которо-

го определено внутри типа объекта. Тип TBee определяет методы с

именами Init, Hide, Show и MoveTo. Если метод TBee должен был

вызвать один из этих четырех методов, то компилятор заменил бы

вызов на адрес одного из собственных методов Bee.

Если в типе объекта не определен метод с таким именем, то

компилятор поднимается выше к непосредственному родительскому ти-

пу в поисках метода с указанным именем. Если метод с таким именем

найден, то адрес родительского метода замещает имя в исходном ко-

де дочернего метода. Если метод с таким именем не найден, то ком-

пилятор продолжает продвигаться вверх по родительским объектам в

поисках метода. Если компилятор наталкивается на самый первый

(высший) тип объекта, то он выдает сообщение об ошибке, указываю-

щее, что ни одного такого метода не определено.

Однако, если статический наследуемый метод найден и исполь-

зуется, то вы должны помнить, что вызываемый метод является в

точности таким, как он определен и компилирован для родительского

типа. Если родительский метод вызывает другие методы, то вызывае-

мые методы будут также родительскими методами, даже если дочерний

объект содержит методы, которые переопределяют родительские.

Виртуальные методы и полиморфизм

Обсуждаемые до сих пор методы являются статическими. Они яв-

ляются статическими в том же смысле, в каком статической является

статическая переменная: компилятор размещает ее и разрешает все

ссылки на нее во время компиляции. Как вы видели, объекты и ста-

тические методы могут быть мощным инструментом для составления

сложных программ.

Однако иногда это не лучший способ для управления методами.

Проблемы, аналогичные описанной в предыдущем разделе, возни-

кают из-за разрешения ссылок на метод во время компиляции. Выход

заключается в том, что метод должен быть динамическим, а ссылки

на него должны разрешаться во время выполнения. Чтобы это стало

возможным, нужно иметь некоторые специальные механизмы, однако

Borland Pascal предоставляет эти механизмы за счет поддержки им

виртуальных методов.

Важное замечание: Виртуальные методы предоставляют макси-

мально мощный инструмент для обобщения, именуемого полиморфизмом.

Полиморфизм в переводе с греческого означает "многообразие" и яв-

ляется способом присвоения действию имени, которое разделяется

вверх и вниз объектами иерархии, причем каждый объект иерархии,

использует это действие соответствующим ему образом.

Уже описанная простая иерархия крылатых насекомых являет со-

бой хороший пример полиморфизма в действии, предоставляемого пос-

редством виртуальных методов.

Каждый тип объекта в нашей иерархии представляет отдельный

тип фигуры на экране: крылатое насекомое или пчелу. Определенно,

имеет смысл сказать, что вы можете показать на экране точку или

окружность. Позднее, если вам понадобится определить объекты для

представления на экране других типов крылатых насекомых, таких

как мотыльки, стрекозы, бабочки и т.д., вы могли бы написать ме-

тод для каждого из них, который будет выводить объект на экран. В

новых терминах объектно-ориентированного программирования вы мог-

ли бы сказать, что все эти типы крылатых насекомых имеют способ-

ность показать самих себя на экране. Это большая часть из того,

что является для них общим.

Что является особым для каждого типа объекта, так это спо-

соб, которым он должен показать самого себя на экране. Например,

у пчелы на экране должны рисоваться черные полоски на туловище.

Можно показать на экране любой тип крылатых насекомых, но меха-

низм рисования каждого является сугубо специфическим. Одно слово

"нарисовать" используется для рисования (буквально) многих крыла-

тых насекомых. Аналогично, если вернуться к нашему примеру пла-

тежной ведомости, то слово "GetPayAmount" вычисляет размер выплат

для нескольких категорий работающих.

Это были примеры полиморфизма, а виртуальными методами явля-

ется то, что реализует его в Borland Pascal.

Раннее связывание против позднего связывания

Различие между вызовом статического метода и динамического

метода является различием между решением сделать немедленно и ре-

шением отложить. Когда вы кодируете вызов статического метода, вы

по существу говорите компилятору; "Ты знаешь, чего я хочу. Пойди

и вызови это." С другой стороны, применение вызова виртуального

метода, подобно разговору с компилятором; "Ты не знаешь пока, че-

го я хочу. Когда придет время, задай вопрос о конкретном экземп-

ляре."

Подумайте об этой метафоре в терминах проблемы MoveTo, упо-

мянутой в предыдущем разделе. Вызов TBee.MoveTo может привести

только к одному - выполнению MoveTo, ближайшей в объектной иерар-

хии. В этом случае TBee.MoveTo по-прежнему будет вызывать опреде-

ление MoveTo для TWinged, так как TWinged является ближайшим к

TBee типом вверх по иерархии. Если предположить, что не определен

никакой дочерний тип, который определяет собственный метод

MoveTo, переопределяющий MoveTo типа TWinged, то любой порожден-

ный по отношению к TWinged тип будет по-прежнему вызывать тот же

самый экземпляр метода MoveTo. Решение может быть принято во вре-

мя компиляции и это все, что должно быть сделано.

Однако совсем другое дело, когда метод MoveTo вызывает Show.

Каждый тип фигуры имеет свой собственный экземпляр Show, поэтому

то, какой экземпляр Show вызывается методом MoveTo, полностью за-

висит от того, какая реализация объекта вызывает MoveTo. Именно

поэтому решение о вызове метода Show внутри экземпляра MoveTo

должно быть отложено: при компиляции кода MoveTo не может принято

никакого решения относительно того, какой метод Show должен быть

вызван. Эта информация недоступна во время компиляции, поэтому

решение должно быть отложено до тех пор, пока программа не начнет

выполняться, и пока нельзя будет запросить экземпляр объекта, вы-

зывающий MoveTo.

Процесс, с помощью которого вызовы статических методов од-

нозначно разрешаются компилятором во время компиляции в один ме-

тод, называется ранним связыванием. При раннем связывании вызыва-

ющий и вызываемый методы связываются при первой же возможности,

т.е. во время компиляции. При позднем связывании вызывающий и вы-

зываемый методы не могут быть связаны во время компиляции, поэто-

му включается механизм, позволяющий осуществить связывание нес-

колько позднее, когда вызов действительно произойдет.

Сущность механизма интересна и тонка, и немного позднее вы

увидите, как он работает.

Совместимость типов объектов

Наследование до некоторой степени изменяет правила совмести-

мости типов в Borland Pascal. Помимо всего прочего, порожденный

тип наследует совместимость типов всех своих порождающих типов.

Эта расширенная совместимость типов принимает три формы:

- между реализациями объектов;

- между указателями на реализации объектов;

- между формальными и фактическими параметрами.

Однако очень важно помнить, что во всех трех формах совмес-

тимость типов расширяется только от потомка к родителю. Другими

словами, дочерние типы могут свободно использоваться вместо роди-

тельских, но не наоборот.

В модуле WORKERS.TPU TSalaried является потомком TEmployee,

а TCommissioned - потомком TSalaried. Помня об этом, рассмотрим

следующие описания:

tyрe

PEmрloyee = ^TEmployee;

PSalaried = ^TSalfried;

PCommissioned = ^TCommissioned;

var

AnEmрloyee: TEmployee;

ASalaried: TSalaried;

PCommissioned: TCommissioned;

TEmployeePtr: PEmрloyee;

TSalariedPtr: PSalaried;

TCommissionedPtr: PCommissioned;

При данных описаниях справедливы следующие операторы присва-

ивания:

AnEmрloyee :=ASalaried;

ASalaried := ACommissioned;

TCommissionedPtr := ACommissioned;

Примечание: Порождающему объекту можно присвоить эк-

земпляр любого из его порожденных типов.

Обратные присваивания недопустимы.

Эта концепция является новой для Паскаля, и в начале, воз-

можно, вам будет трудновато запомнить, в каком порядке следует

совместимость типов. Думайте следующим образом: источник должен

быть в состоянии полностью заполнить приемник. Порожденные типы

содержат все, что содержат их порождающие типы благодаря свойству

наследования. Поэтому порожденный тип имеет либо в точности такой

же размер, либо (что чаще всего и бывает) он больше своего роди-

теля, но никогда не бывает меньше. Присвоение порождающего (роди-

тельского) объекта порожденному (дочернему) могло бы оставить не-

которые поля порожденного объекта неопределенными, что опасно и

поэтому недопустимо.

В операторах присваивания из источника в приемник будут ко-

пироваться только поля, являющиеся общими для обоих типов. В опе-

раторе присваивания:

AnEmрloyee := ACommissioned;

Только поля Name, Title и Rate из ACommissioned будут скопи-

рованы в AnEmрloyee, т.к. только эти поля являются общими для

TCommissioned и TEmployee. Совместимость типов работает также

между указателями на типы объектов и подчиняется тем же общим

правилам, что и для реализаций объектов. Указатель на потомка мо-

жет быть присвоен указателю на родителя. Если дать предыдущие оп-

ределения, то следующие присваивания указателей будут допустимы-

ми:

TSalariedPtr := TCommissionedPtr;

TEmployeePtr := TSalariedPtr;

TEmployeePtr := PCommissionedPtr;

Помните, что обратные присваивания недопустимы.

Формальный параметр (либо значение, либо параметр-перемен-

ная) данного объектного типа может принимать в качестве фактичес-

кого параметра объект своего же типа или объекты всех дочерних

типов. Если определить заголовок процедуры следующим образом:

рrocedure CalcFedTax(Victim: TSalaried);

то допустимыми типами фактических параметров могут быть TSalaried

или TCommissioned, но не тип TEmployee. Victim также может быть

параметром-переменной. При этом выполняются те же правила совмес-

тимости.

Замечание: Имейте в виду, что между параметрами-значениями и

параметрами-переменными есть коренное отличие. Параметр-значение

является указателем на действительный, посылаемый в качестве па-

раметра объект, тогда как параметр-переменная является только ко-

пией фактического параметра. Более того, эта копия включает толь-

ко те поля, которые входят в тип формального параметра-значения.

Это означает, что фактический параметр буквально преобразуется к

типу формального параметра. Параметр-переменная больше напоминает

приведение к образцу, в том смысле, что фактический параметр ос-

тается неизменным.

Аналогично, если формальный параметр является указателем на

тип объекта, фактический параметр может быть указателем на этот

тип объекта или на любой дочерний тип. Пусть дан заголовок проце-

дуры:

рrocedure Worker.Add (AWorker: PSalaried);

тогда допустимыми типами фактических параметров могут быть

PSalaried или PCommissioned, но не тип PEmрloyee.

Полиморфические объекты

При чтении предыдущего раздела вы, возможно, задали себе

вопрос: "Если любой порожденный от типа параметра тип может пере-

даваться в качестве параметра, то как же пользователь параметра

узнает, какой тип объекта он получил?" Фактически, пользователь

явно этого и не знает. Точный тип фактического параметра не из-

вестен во время компиляции. Фактический параметр может быть объ-

ектом любого дочернего от параметра-переменной типа, и именно по-

этому он называется полиморфическим объектом.

Теперь, чем же именно хорош полиморфический объект? Прежде

всего полиморфические объекты позволяют обрабатывать объекты, чей

тип неизвестен на момент компиляции. Это общее замечание настоль-

ко ново для образа мышления Паскаля, что пример для вас не поя-

вится незамедлительно. (Со временем вы будете удивлены, насколько

естественно это выглядит. То есть, когда вы действительно станете

объектно-ориентированным программистом.)

Предположим, что вы написали инструментальное средство для

вычерчивания графиков, поддерживающее многочисленные типы фигур:

точки, окружности, квадраты, прямоугольники, кривые и т.д. В ка-

честве части этого инструментального средства вы хотите написать

программу, которая будет перемещать графическую фигуру по экрану

с помощью устройства типа "мышь".

При старом способе необходимо было написать отдельную проце-

дуру перемещения для каждого типа графической фигуры, поддержива-

емой инструментальным средством. Вы должны были бы написать

DragButterfly, DragBee, DragMoth и т.д. Несмотря на то, что стро-

гая типизация (проверка типов) Паскаля позволяла это (и не забы-

вайте, что всегда существуют способы обойти строгую типизацию),

различия между типами графических фигур едва ли позволили бы на-

писать действительно общую программу перемещения.

В конце концов, пчела имеет полоски и жало, бабочка имеет

большие цветные крылья, а стрекоза имеет переливчатые цвета,

хвост, да что говорить...

С этой точки зрения, "сообразительные" программисты, работа-

ющие на Турбо Паскале, сделают шаг вперед и скажут: "Поступайте

так: передайте запись о крылатом насекомом процедуре DragIt в ка-

честве ссылки указателя общего вида. В процедуре DragIt проверяй-

те свободное поле по фиксированному смещению внутри записи о

крылатом насекомом для определения, какого вида это насекомое, а

затем сделайте переход с помощью оператора case:

case FigureIDTag of

Bee : DragBee;

Butterfly : DragButterfly;

Dragonfly : DragDragonfly;

Mocquito : DragMocquito;

.

.

.

Ну, размещение семнадцати маленьких чемоданчиков внутри од-

ного большого является незначительным шагом вперед, но в чем же

заключается проблема, ожидающая нас на этом пути?

Что случится, если пользователь инструментального средства

определит несколько новых типов крылатых насекомых?

В самом деле, что? Что если пользователь захочет работать со

среднеазиатскими фруктовыми мухами? В вашей программе нет типа

Fruitfly, поэтому DragIt не содержит метки Fruitfly в операторе

case и, следовательно, отвергнет перемещение нового рисунка

Fruitfly. Будучи представленным процедуре DragIt, Fruitfly будет

выпадать из оператора case в ветвь else этого оператора как "не-

распознанное насекомое".

Откровенно говоря, создание для продажи инструментального

средства без исходного кода страдает этой проблемой. Инструмен-

тальное средство может работать только с типами данных, которые

"известны" ему, т.е. которые определены разработчиком инструмен-

тального средства. Пользователь инструментального средства оказы-

вается бессильным перед расширением его функций в направлении, не

предвиденном разработчиком. То, что пользователь купил, то он и

получил. И точка.

Выходом из проблемы является использование правил расширен-

ной совместимости типов Borland Pascal для объектов и разработка

прикладных программ с использованием полиморфических методов. Ес-

ли процедура DragIt инструментального средства установлена так,

что может работать с полиморфическими объектами, то она будет ра-

ботать с любыми объектами, определенными в инструментальном

средстве, и с любыми дочерними объектами, которые вы определите

сами. Если типы объектов инструментального средства используют

виртуальные методы, то объекты и программы инструментального

средства могут работать со сделанными вами графическими фигурами

в собственных терминах самих фигур. Определенный вами сегодня

виртуальный метод может вызываться файлом модуля (.TPU, .TPW или

. TPP) инструментального средства, который был написан и оттранс-

лирован год назад. Объектно-ориентированное программирование дает

такую возможность, а виртуальные методы являются ключом к ней.

Понимание того, как виртуальные методы делают возможными та-

кие вызовы полиморфических методов требует пояснения описания и

использования виртуальных методов.

Виртуальные методы

Метод становится виртуальным, если за его объявлением в типе

объекта стоит новое зарезервированное слово virtual. Помните, что

если вы объявляете метод в родительском типе как virtual, то все

методы с аналогичными именами в дочерних типах также должны объ-

являться виртуальными во избежание ошибки компилятора.

Ниже приведены знакомые вам объекты из примера платежной ве-

домости, должным образом виртуализированные:

tyрe

PEmрloyee = ^TEmployee;

TEmployee = object

Name, Title: string[25];

Rate: Real;

constructor Init (AName, ATitle: String; ARate: Real);

function GetPayAmount : Real; virtual;

function GetName : String;

function GetTitle : String;

function GetRate : Real;

рrocedure Show; virtual;

end;

PHourly = ^THourly;

THourly = object(TEmployee);

Time: Integer;

constructor Init (AName, ATitle: String; ARate: Real;

Time: Integer);

function GetPayAmount : Real; virtual;

function GetTime : Integer;

end;

PSalaried = ^TSalaried;

TSalaried = object(TEmployee);

function GetPayAmount : Real; virtual;

end;

PCommissioned = ^TCommissioned;

TCommissioned = object(Salaried);

Commission : Real;

SalesAmount : Real;

constructor Init (AName, ATitle: String;

ARate, ACommission, ASalesAmount: Real);

function GetPayAmount : Real; virtual;

end;

А ниже приводится пример для насекомых, дополненный вирту-

альными методами.

tyрe

TWinged = object(Insect)

constructor Init (AX, AY : Integer)

рrocedure Show; virtual;

рrocedure Hide; virtual;

end;

tyрe

TBee = object(TWinged)

constructor Init (AX, AY : Integer)

рrocedure Show; virtual;

рrocedure Hide; virtual;

end;

Прежде всего обратите внимание, что метод MoveTo, показанный

для типа TBee, теперь удален из его определения. Теперь типу TBee

больше нет нужды переопределять метод MoveTo типа TWinged с по-

мощью немодифицируемой копии, компилируемой в его собственной об-

ласти действия. Вместо этого, MoveTo теперь может наследоваться

от TWinged со всеми вложенными в MoveTo вызовами, которые, одна-

ко, будут вызывать методы из TBee, а не из TWinged, как это про-

исходило в полностью статической иерархии объектов.

Отметьте также новое зарезервированное слово constructor

(конструктор), заменившее зарезервированное слово рrocedure для

TWinged.Init и TBee.Init. Конструктор является специальным типом

процедуры, которая выполняет некоторую установочную работу для

механизма виртуальных методов. Более того, конструктор должен вы-

зываться перед вызовом любого виртуального метода. Вызов вирту-

ального метода без предварительного вызова конструктора может

привести к блокированию системы, а у компилятора нет способа про-

верить порядок вызова методов.

Каждый тип объекта, имеющий виртуальные методы, обязан иметь

конструктор.

Предупреждение: Конструктор должен вызываться перед вызовом

любого другого виртуального метода. Вызов виртуального метода без

предыдущего обращения к конструктору может вызвать блокировку

системы, и компилятор не сможет проверить порядок, в котором вы-

зываются методы.

Примечание: Для конструкторов объекта мы предлагает

использовать идентификатор Init.

Каждый отдельный экземпляр объекта должен инициализироваться

отдельным вызовом конструктора. Недостаточно инициализировать

один экземпляр объекта и затем присваивать этот экземпляр другим.

Другие экземпляры, даже если они могут содержать правильные дан-

ные, не будут инициализированы оператором присваивания и заблоки-

руют систему при любых вызовах их виртуальных методов. Например:

var

FBee, GBee: Bee; { создать два экземпляра Bee }

begin

FBee.Init(5, 9) { вызов конструктора для FBee }

GBee := FBee; { Gbee недопустим! }

end;

Что же именно создает конструктор? Каждый тип объекта содер-

жит нечто, называемое таблицей виртуального метода (ТВМ) в сег-

менте данных. ТВМ содержит размер типа объекта и для каждого вир-

туального метода указатель на код, выполняющий данный метод.

Конструктор устанавливает связь между вызывающей его реализацией

объекта и ТВМ типа объекта.

Важно помнить, что имеется только одна ТВМ для каждого типа

объекта. Отдельные экземпляры типа объекта (т.е. переменные этого

типа) содержат только соединение с ТВМ, но не саму ТВМ. Конструк-

тор устанавливает значение этого соединения в ТВМ. Именно благо-

даря этому вы нигде не можете запустить выполнение перед вызовом

конструктора.

Проверка диапазонов при вызове виртуальных методов

В процессе разработки программы вам, возможно, захочется по-

высить меры безопасности, которая снижается из-за вызовов вирту-

альных методов Borland Pascal. Если директива $R находится во

включенном состоянии, {$R+}, то все вызовы виртуальных методов

будут проверяться на состояние инициализации для выполняющих вы-

зовы реализаций. Если выполняющая вызов реализация еще не была

инициализирована конструктором, то произойдет ошибка проверки ди-

апазона исполняющей системы.

Примечание: Состоянием по умолчанию является {$R-}.

После того, как вы хорошенько перетрясли программу и удосто-

верились, что отсутствуют вызовы методов из неинициализированных

реализаций, вы можете до некоторой степени ускорить выполнение

программы путем переключения директивы $R в пассивное состояние.

После этого проверка вызовов методов из неинициализированных реа-

лизаций осуществляться не будет, что оставляет вероятность блоки-

ровки системы, если будет выявлена такая ошибка.

Виртуальный однажды - виртуальный всегда

Вы уже вероятно обратили внимание, что как TWinged, так и

TBee содержат методы, называемые Show и Hide. Все заголовки мето-

дов для Show и Hide объявлены виртуальными и снабжены зарезерви-

рованным словом virtual. Как только родительский тип объекта объ-

являет метод виртуальным, все его потомки также должны объявить

этот метод виртуальным. Другими словами, статический метод никог-

да не сможет переопределить виртуальный метод. Если вы попытае-

тесь сделать это, то компилятор выдаст сообщение об ошибке.

Также следует помнить, что после того, как метод стал вирту-

альным, его заголовок не может изменяться в объектах более низко-

го уровня иерархии. Вы можете представлять себе каждое определе-

ние виртуального метода как ворота для всех их. Исходя из этих

соображений, заголовки всех реализаций одного и того же виртуаль-

ного метода должны быть идентичными, включая число параметров и

их типы. Это не относится к статическим методам: статический ме-

тод, переопределяющий другой, может иметь отличное число парамет-

ров и типы этих параметров, в зависимости от необходимости.

Это целый новый мир.

Расширяемость объекта

Важным замечанием, касающимся модулей типа WORKERS.PAS, яв-

ляется то, что типы объектов и методы, определенные в модуле, мо-

гут поставляться пользователю в форме .TPU, .TPW или .TPP т.е. в

форме, способной к непосредственной компоновке, без исходного ко-

да. (Нужно просмотреть только листинг интерфейсной части модуля.)

Используя полиморфические объекты и виртуальные методы, пользова-

тель файла .TPU, .TPW или .TPP сможет добавлять характеристики

для приспособления модуля к своим нуждам.

Новое понятие о добавлении функциональных характеристик в

программу без предоставления ее исходного кода называется способ-

ностью к расширению. Способность к расширению является естествен-

ным следствием наследования: вы наследуете все, чем обладают по-

рождающие типы, а затем добавляете новые нужные вам возможности.

Позднее связывание позволяет, чтобы новое связывалось со старым

во время выполнения программы, благодаря чему расширение сущест-

вующего кода выглядит "бесшовным" и стоит вам в части выполнения

не более, чем быстрое путешествие по таблице виртуального метода.

Статические методы или виртуальные методы?

В общем случае, вам следует делать методы виртуальным. Ис-

пользуйте статические методы только в том случае, если вы хотите

получить оптимальную эффективность скорости выполнения и исполь-

зования памяти. Однако в этом случае, как вы видели, вы теряете

возможности расширения.

Предположим, что вы описываете объект с именем Ancestor и

внутри этого объекта вы описываете метод с именем Action. Как вы

определяете, каким должен быть метод, виртуальным или статичес-

ким? Здесь приводится правило большого пальца: сделайте метод

Action виртуальным, если имеется вероятность, что будущие наслед-

ники объекта Ancestor будут переопределять Action, а вы хотите,

чтобы будущий код был доступен Ancestor.

С другой стороны, помните, что если у объекта имеются любые

виртуальные методы, то для этого объекта в сегменте данных будет

создана таблица виртуальных методов (ТВМ) и каждый экземпляр это-

го объекта будет иметь связь с ТВМ. Каждый вызов виртуального ме-

тода должен проходить через ТВМ, тогда как статические методы вы-

зываются непосредственно. Хотя просмотр ТВМ является весьма эф-

фективным, вызов статического метода все равно остается немного

более быстрым, чем вызов виртуального. И если в вашем объекте нет

виртуальных методов, то и ТВМ отсутствует в сегменте данных и

(что более важно) в каждом экземпляре объекта отсутствуют связи с

ТВМ.

Дополнительная скорость и эффективное использование памяти

для статических методов должно уравновешиваться гибкостью, кото-

рую допускают виртуальные методы: вы можете расширить имеющийся

код спустя много времени после его компиляции. Помните, что поль-

зователь вашего типа объекта может рассматривать пути его исполь-

зования, которые вам и не снились, что, в конечном счете, имеет

основное значение.

Динамические объекты

Все приведенные до сих пор объекты имели статические реали-

зации типов объектов, которым в объявлении var присваивались име-

на и которые размещались в сегменте данных или в стеке.

var

ASalaried: TSalaried;

Примечание: Использование здесь слова "статический" не

имеет отношения к статическим методам.

Объекты могут размещаться в динамической памяти и ими можно

манипулировать с помощью указателей, как и с тесно связанными с

ними типами записей, что всегда имело место в Паскале. Турбо Пас-

каль включает несколько мощных расширений для выполнения динами-

ческого размещения и удаления объектов более легкими и более эф-

фективными способами.

Объекты могут размещаться, как области памяти, на которые

ссылается указатель, с помощью процедуры New:

var

CurrentPay: Real;

P: ^TSalaried;

New(P);

Как и для типов записей, процедура New выделяет в динамичес-

кой памяти пространство, достаточное для размещения реализации

указателя базового типа и возвращает адрес этого пространства в

указателе.

Если динамический объект содержит виртуальные методы, то он

должен инициализироваться с помощью вызова конструктора перед

тем, как будет вызван любой из его методов:

P^.Init('Sara Adams', 'Account manager', 2400);

Затем вызовы методов могут происходить в обычном порядке, с

использованием имени указателя и ссылочного символа вместо имени

реализации, которое использовалось бы при обращении к статически

размещенному объекту:

CurrentPay := P^.GetPayAmount;

Размещение и инициализация с помощью процедуры New

Borland Pascal расширяет синтаксис процедуры New, что явля-

ется более компактным и более удобным средством выделения прост-

ранства для объекта в динамически распределяемой области памяти и

инициализации объекта с помощью только одной операции. Теперь

процедура New может вызываться с двумя параметрами: имя указателя

используется в качестве первого параметра, а вызов конструктора -

в качестве второго параметра:

New(P, Init('Sara Adams', 'Account manager', 2400));

Если для процедуры New используется расширенный синтаксис,

то конструктор Init действительно выполняет динамическое размеще-

ние, используя специальный код входа, сгенерированного как часть

компиляции конструктора. Имя реализации не может предшествовать

Init, т.к. в то время, когда процедура New вызвана, реализация,

инициализируемая с помощью Init, еще не существует. Компилятор

идентифицирует правильный вызываемый метод Init посредством типа

указателя, пересылаемого в качестве первого параметра.

Процедура New также была расширена для возможности использо-

вания ее как функции, которая возвращает значение указателя. По-

сылаемый New параметр является типом указателя на объект, а не

самой переменной-указателем:

tyрe

PSalaried = ^TSalaried;

var

P: PSalaried;

P := New(PSalaried);

Обратите внимание, что в данной версии функциональная форма

расширения процедуры New применима ко всем типам данных, а не

только к типам объектов.

Функциональная форма New, как и процедурная форма, также мо-

жет воспринимать конструктор объектного типа в качестве второго

параметра:

P := New(PSalaried, Init('Sara Adams',

'Account manager', 2400));

В Borland Pascal осуществлено также параллельное расширение

процедуры Disрose, это подробно обсуждается в следующем разделе.

Примечание: Новая стандартная процедура Fail поможет

вам в конструкторах выполнить восстановление при ошибке

(см. Главу 9 в "Руководстве по языку").

Удаление динамических объектов

Также, как и обычные записи Паскаля, размещаемые в динами-

чески распределяемой области памяти, объекты могут удаляться про-

цедурой Disрose, если они больше не нужны:

Disрose (P);

Однако, при избавлении от ненужного объекта может понадо-

биться нечто большее, чем простое освобождение занимаемой им ди-

намической памяти. Объект может содержать указатели на динамичес-

кие структуры или объекты, которые нужно освободить или очистить

в определенном порядке, особенно если вы оперируете сложной дина-

мической структурой данных. Что бы ни нужно было сделать для

очистки динамического объекта в каком-либо порядке, это все долж-

но быть объединено в один метод таким образом, чтобы объект мог

быть уничтожен с помощью одного вызова метода:

MyComрlexObject.Done;

Метод Done должен инкапсулировать все детали очистки своего

объекта, а также всех структур данных и вложенных объектов.

Примечание: Мы советуем использовать для удаления ме-

тодов, работающих с объектами, которые более не нужны, ис-

пользовать идентификатор Done.

Допустимо и часто бывает полезно определять несколько мето-

дов очистки для данного типа объекта. В зависимости от того, как

они размещены или используются, или в зависимости от состояния и

режима объекта на момент очистки, сложные объекты могут потребо-

вать очистки несколькими различными путями

Деструкторы

Borland Pascal предоставляет специальный тип метода, называ-

емый "сборщиком мусора" или деструктором, для очистки и удаления

динамически размещенного объекта. Деструктор объединяет шаг уда-

ления объекта с какими-либо другими действиями или задачами, не-

обходимыми для данного типа объекта. Для единственного типа объ-

екта можно определить несколько деструкторов.

Деструктор определяется совместно со всеми другими методами

объекта в определении типа объекта:

tyрe

TEmployee = object

Name: string[25];

Title: string[25];

Rate: Real;

constructor Init(AName, ATitle: String; ARate: Real);

destructor Done; virtual;

function GetName: String;

function GetTitle: String;

function GetRate: Rate; virtual;

function GetPayAmount: Real; virtual;

end;

Деструкторы можно наследовать, и они могут быть либо стати-

ческими, либо виртуальными. Поскольку различные программы завер-

шения обычно требуют различные типы объектов, мы рекомендуем,

чтобы деструкторы всегда были виртуальными, благодаря чему для

каждого типа объекта будет выполнен правильный деструктор.

Запомните, что зарезервированное слово destructor не требу-

ется указывать для каждого метода очистки, даже если определение

типа объекта содержит виртуальные методы. Деструкторы в действи-

тельности работают только с динамически размещенными объектами.

При очистке динамически размещенного объекта, деструктор осущест-

вляет специальные функции: он гарантирует, что в динамически

распределяемой области памяти всегда будет освобождаться правиль-

ное число байтов. Не может быть никаких опасений по поводу ис-

пользования деструктора применительно к статически размещенным

объектам; фактически, не передавая типа объекта деструктору, вы

лишаете объект данного типа полных преимуществ управления динами-

ческой памятью в Borland Pascal.

Деструкторы в действительности становятся самими собою тог-

да, когда должны очищаться полиморфические объекты и когда должна

освобождаться занимаемая ими память. Полиморфические объекты -

это те объекты, которые были присвоены родительскому типу благо-

даря правилам совместимости расширенных типов Borland Pascal. Эк-

земпляр объекта типа THourly, присвоенный переменной типа

TEmployee, является примером полиморфического объекта. Эти прави-

ла также могут быть применены к объектам; указатель на THourly

может свободно быть присвоен указателю на TEmployee, а указуемый

этим указателем объект опять же будет полиморфическим объектом.

Термин "полиморфический" является подходящим, так как код,

обрабатывающий объект, не знает точно во время компиляции, какой

тип объекта ему придется в конце концов обработать. Единственное,

что он знает, это то, что этот объект принадлежит иерархии объек-

тов, являющихся потомками указанного типа объекта.

Очевидно, что размеры типов объектов отличаются. Поэтому,

когда наступает время очистки размещенного в динамической памяти

полиморфического объекта, то как же Disрose узнает, сколько байт

динамического пространства нужно освобождать? Во время компиляции

из полиморфического объекта нельзя извлечь никакой информации от-

носительно размера объекта.

Деструктор разрешает эту головоломку путем обращения к тому

месту, где эта информация записана: в ТВМ переменных реализаций.

В каждой ТВМ типа объекта содержится размер в байтах данного типа

объекта. Таблица виртуальных методов любого объекта доступна пос-

редством скрытого параметра Self, посылаемого методу при вызове

метода. Деструктор является всего лишь разновидностью метода и

поэтому, когда объект вызывает его, деструктор получает копию

Self через стек. Таким образом, если объект является полиморфи-

ческим во время компиляции, он никогда не будет полиморфическим

во время выполнения благодаря позднему связыванию.

Для выполнения этого освобождения памяти при позднем связы-

вании деструктор нужно вызывать, как часть расширенного синтакси-

са процедуры Disрose:

Disрose(P, Done);

(Вызов деструктора вне процедуры Disрose вообще не выполняет

никакого освобождения памяти.) Здесь происходит на самом деле то,

что сборщик мусора объекта, на который указывает P, выполняется

как обычный метод. Однако, как только последнее действие выполне-

но, деструктор ищет размер реализации своего типа в ТВМ и пересы-

лает размер процедуре Disрose. Процедура Disрose завершает про-

цесс путем удаления правильного числа байт пространства динами-

ческой памяти, которое (пространство) до этого относилось к P^.

Число освобождаемых байт будет правильным независимо от того,

указывал ли P на экземпляр типа TSalaried, или он указывал на

один из дочерних типов типа TSalaried, например, на

TCommissioned.

Заметьте, что сам по себе метод деструктора может быть пуст

и выполнять только эту функцию:

destructor AnObject.Done;

begin

end;

То, что делается полезного в этом деструкторе, не является

достоянием его тела, однако при этом компилятором генерируется

код эпилога в ответ на зарезервированное слово destructor. Это

напоминает модуль, который ничего не экспортирует, но который

осуществляет некоторые невидимые действия за счет выполнения сво-

ей секции инициализации перед стартом программы. Все действия

происходят "за кулисами".

Пример размещения динамического объекта

Последний пример программы даст вам возможность приобрести

некоторые навыки в использовании размещенных в динамической памя-

ти объектов, включая использование для удаления объекта деструк-

тора. Программа показывает, как в динамической памяти может быть

создан связанный список рабочих объектов и как он за ненадоб-

ностью может быть очищен при помощи деструктора.

Построение связанного списка объектов требует, чтобы каждый

объект содержал указатель на следующий объект списка. Тип

TEmployee не содержит таких указателей. Простым выходом из этой

ситуации было бы добавление указателя в TEmployee , благодаря че-

му можно быть уверенным, что все потомки TEmployee наследуют та-

кой указатель. Однако, добавление чего-либо в TEmployee требует

от вас наличия исходного кода, а как говорилось ранее, одним из

преимуществ объектно-ориентированного программирования является

возможность расширения объектов без необходимости их перекомпиля-

ции.

Решение, которое не требует никаких изменений TEmployee,

создает новый тип объекта, не являющийся потомком TEmployee. Тип

StaffList представляет собой очень простой объект, целью которого

является создание заголовков для объектов типа TEmployee. Так как

TEmployee не содержит никаких указателей на следующий объект в

списке, то простой тип записи TNode осуществляет этот сервис.

TNode даже проще, чем StaffList в том, что TNode не является объ-

ектом, не содержит ни одного метода и не имеет никаких данных, за

исключением указателя на тип TEmployee и указателя на следующий

узел списка.

TStaffList содержит метод, который позволяет ему добавлять

нового рабочего в связанный список записей TNode путем внесение

нового экземпляра TNode непосредственно после самого себя в ка-

честве указуемого с помощью указателя поля TNodes. Метод Add при-

нимает указатель на объект типа TEmployee, но не сам объект.

Из-за расширенной совместимости типов Турбо Паскаля указатели на

любого потомка типа TEmployee также должны передаваться в

TList.Add в параметре Item.

Программа WorkList описывает статическую переменную Staff

типа TStaffList и строит связанный список из пяти узлов. Каждый

узел указывает на отдельный рабочий объект, который является либо

TEmployee, либо одним из его потомков. Перед созданием каждого

динамического объекта и после того, как объект создан, возвращает

число байт свободной динамической памяти. Наконец, полная струк-

тура, включающая пять записей TNode и пять объектов типа

TEmployee, очищается и удаляется из динамической памяти с помощью

одного вызова деструктора статического объекта Staff типа

TStaffList.

і

List і Node Node Node

ЪДДДДДДДї ЪДДДДВДДДДї ЪДДДДВДДДДї ЪДДДДВДДДДї

і і і і і і і і і і і і

і OДДЕДДДД> і O і OДЕДДД> і O і OДЕДДДД> і O і OДЕДДДї

і і і і і і і і і і і і і і і і

АДДДДДДДЩ АДДЕДБДДДДЩ АДДЕДБДДДДЩ АДДЕДБДДДДЩ і

і і і і ДДБДД

v v v ДДД

і ЪДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДї Д

і Name і і Name і і Name і

і ГДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДґ

і Title і і Title і і Title і

і ГДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДґ

і Rate і і Rate і і Rate і

і ГДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДґ

і і і і і і

Сегмент і Динамически распределяемая область

данных памяти (динамический)

(статическиеі

объекты)

і

Рис. 9.2 Схема структур данных программы ListDemo.

Удаление сложной структуры данных из динамической памяти

Деструктор Staff.Done стоит того, чтобы рассмотреть его вни-

мательно. Уничтожение объекта TStaffList включает удаление трех

различных типов структур: полиморфических объектов рабочих струк-

тур в списке, записей TNode, поддерживающих список, и (если он

размещен в динамической памяти) объект TList, который озаглавли-

вает список. Весь процесс запускается путем единственного вызова

деструктора объекта TStaffList:

Staff.Done;

Код деструктора заслуживает более подробного изучения:

destructor StaffList.Done;

var

N: TNodePtr;

begin

while TNodes <> nil do

begin

N := TNodes;

Disрose(N^.Item, Done);

TNodes := N^.Next;

Disрose (N);

end;

end;

Список очищается начиная с "головы" списка с помощью алго-

ритма "из руки в руку", который до некоторой степени напоминает

дерганье за веревку воздушного змея: два указателя (указатель

TNodes внутри Staff и рабочий указатель N) изменяют свои ссылки в

списке, тогда как первый элемент списка удаляется. Вызов процеду-

ры Disрose освобождает память, занимаемую первым объектом

TEmployee в списке (Item^), затем TNodes продвигается на следую-

щую запись списка с помощью оператора TNodes := N^.Next, сама за-

пись TNode удаляется, и процесс продолжается до полного очищения

списка.

Важным моментом в деструкторе Done является способ, которым

удаляются из списка объекты TEmployee:

Disрose(N.Item, Done);

Здесь N.Item является первым объектом TEmployee в списке, а

вызываемый метод Done является деструктором этого объекта. Запом-

ните, что действительный тип N^.Item^ не обязательно является ти-

пом TEmployee, однако он может быть любым дочерним типом типа

TEmployee. Очищаемый объект является полиморфическим и поэтому

нельзя сделать никаких предположений относительно его действи-

тельного размера или точного его типа на этапе компиляции. В при-

веденном выше вызове Disрose, как только Done выполнит все содер-

жащиеся в нем операторы, "невидимый" код эпилога ищет размер реа-

лизации очищаемого объекта в ТВМ этого объекта. Метод Done пере-

дает размер процедуре Disрose, которая затем освобождает точное

количество динамической памяти, в действительности занимаемой по-

лиморфическим объектом.

Помните, что если должно освобождаться правильное количество

динамической памяти, то полиморфический объект должен очищаться

только посредством вызова передаваемого Disрose деструктора.

В примере программы Staff объявляется как статическая пере-

менная в сегменте данных. Staff мог бы столь же легко разместить-

ся в динамической памяти и "прикрепиться к реальному миру" пос-

редством указателя типа ListPtr. Если заголовок списка также яв-

ляется динамическим объектом, то удаление структуры можно осу-

ществить путем вызова деструктора, выполняющегося внутри Disрose:

var

Staff: TStaffListPtr;

begin

Disрose(Staff, Done);

.

.

.

Здесь процедура Disрose вызывает метод деструктора Done для

очистки структуры в динамической памяти. Затем, когда Done завер-

шается, Disрose освобождает память, на которую указывает Staff,

удаляя, как правило, из динамической памяти также и заголовок

списка.

Программа WORKLIST.PAS (находящаяся на вашем диске) исполь-

зует тот же модуль WORKERS.PAS, что и раньше Она создает объект

List, являющийся оглавлением связанного списка из пяти полиморфи-

ческих объектов, совместимых с TEmployee, а затем удаляет всю ди-

намическую структуру данных с помощью единственного вызова дест-

руктора Staff.Done.

Что же дальше?

Как и во всяком другом аспекте машинного программирования,

вы не преуспеете в объектно-ориентированном программировании, ес-

ли будете только читать о нем, но вы добъетесь результата, если

начнете программировать. Большинство людей, при первом столкнове-

нии с объектно-ориентированном программированием, начинают бормо-

тать с придыханием; "Я не могу постичь этого". "Ага!" приходит

позднее, ночью, когда целостная концепция является к нам в одно

прекрасное мгновение, и мы, побросав свои никчемные дела, исполь-

зуем это мгновение для обращения к богу. Как лицо женщины, возни-

кающее из чернильных пятен Роша, то, что до этого было смутным,

становится очевидным и затем легким.

Самое лучшее, что вы можете сделать в качестве первого шага

в объектно-ориентированном программировании, так это взять модуль

WORKERS.PAS (он находится на вашем диске) и расширить его. Как

только вы воскликните "Ага!", начинайте строить ориентированные

на объекты концепции в вашей повседневной программистской жизни.

Возьмите несколько имеющихся утилит, которые вы используете каж-

дый день, и переосмыслите их в ориентированных на объекты терми-

нах. Посмотрите критически на "овощное рагу" вашей библиотеки

процедур и попытайтесь найти в них объекты, затем перепишите про-

цедуры в объектной форме. Вы убедитесь, что библиотеки объектов

станет намного легче использовать в будущих проектах. Даже самые

незначительные ваши начальные инвестиции в программные усилия

станут навсегда излишними. У вас едва ли возникнет необходимость

переписывать объект с самого начала. Если он работает как надо,

то используйте его. Если объекту чего-либо не хватает, то рас-

ширьте его. Но если он работает хорошо, то нет смысла выбрасывать

из него что-либо.

Заключение

Объектно-ориентированное программирование является прямым

следствием усложнения современных приложений, усложнения, которое

часто заставляет многих программистов в отчаянии вскидывать вверх

руки. Наследование и инкапсуляция являются максимально эффектив-

ными средствами для управления сложностью. (Существует разница

между десятью тысячами насекомых, классифицированных по таксоно-

мической схеме, и десятью тысячами насекомых, жужжащих возле ва-

ших ушей.) Представляя собой значительно большее, чем просто

структурное программирование, объектно-ориентированное программи-

рование вносит рациональный порядок в структуру программного

обеспечения ЭВМ, что, как и таксономическая схема, устанавливает

порядок, не устанавливая пределов.

Добавьте сюда перспективы возможности расширения и повторно-

му использования существующего кода и все это начнет звучать нас-

только хорошо, что будет походить на правду. Вы думаете, что это

невозможно?

Но это же Borland Pascal! Слово "невозможно" в нем не определено!


Глава 10. Взгляд на Windows

В данной главе содержится обзор программирования для

Microsoft Windows с использованием Borland Pascal (с акцентом на

объектно-ориентированное программирование). В представленном

здесь примере используется поставляемая с Borland Pascal библио-

тека ObjectWindows. Вы узнаете о поведении прикладной программы

Windows и о том, как с помощью ObjectWindows автоматизировать од-

ни задачи и упростить другие.

Чтобы извлечь из данной главы максимум пользы, вы должны по-

нимать принципы объектно-ориентированного программирования. Если

вы не знакомы с объектно-ориентированным программированием, про-

читайте Главу 9 "Объектно-ориентированное программирование". Вы

должны также знать, как работать с Windows.

Что такое приложение Windows?

На Рис. 10.1 показаны основные компоненты приложения

Windows. Чтобы понять обсуждаемые темы, вам должны быть знакомы

эти компоненты, и вы должны уметь с ними работать.

Кнопка минимизации

Кнопка максимизации і

Строка заголовка і і

Командная кнопка і і і

управления меню Строка меню і Вертикальная полоса і і

і і і прокрутки і і і

ЬЬіЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬіЬЬЬЬЬЬЬЬЬЬЬЬіЬЬЬЬЬЬЬЬЬЬЬЬЬіЬЬЬЬЬЬЬЬЬЬЬЬіЬіЬЬ

ЫЙі[]НННННННННННННіННННННННННННіНННННННННННННіННННННННННННіНі»Ы

Ыє±=± і Borland Pascal і і^іvєЫ

ЫєДДДДДДДДДДДДДДДДДіДіДДДДДДДДДДДБДБДєЫ

Ыє File Edit Search Run Compile Tools Options іindows Help єЫ

ЫєДДДДДДДДДДДДДДДДДДДіДДДДДДДДДДДДДДДєЫ

ЫєЙНННННННННННННННННННННННННННННННННННННННННННі» єЫ

Ыєє±=± c:\bpw\helloapp.pas і^ііє єЫ

ЫєєДДДДДДДДДДДДДДДБДБіє єЫ

Ыєє program Hello; ^є єЫ

Ыєє ±є єЫ

Ыєє uses WObjects, WinTypes, WinProcs; ±є єЫ

Ыєє ±є єЫ

Ыєє type ±є єЫ

Ыєє Ьє єЫ

Ыєє { Define a TApplications descendant } ±є єЫ

Ыєє THelloApp = object(TApplication) ±є єЫ

Ыєє procedure InitMaЙНННННННННННННННННННННННННННННННН» єЫ

Ыєє end; і єЫ=ЫЫЫЫЫЫGo to Line NumberЫЫЫЫЫЫЫє єЫ

Ыєє<±Ы±±±±±±±±±±±±±±±і±±±є є єЫ

ЫєИННННННННіНННННННННіНННє ±±Enter new line number±ЫЫЫЫЫv є єЫ

Ыє і і є є єЫ

Ыє і і є є єЫ

Ыє і і є ЪДДДДДДДДїЪДДДДДДДДїЪДДДДДДДДї є єЫ

Ыє і і є іы OK ііX Cancelіі? Help і є єЫ

Ыє і і є АДДДіДДДДЩАДДДДДДДДЩАДДДДДДДДЩ є єЫ

Ыє і і є і і є єЫ

Ыє і і ИНННННіНННННННННННННННіННННННННННј єЫ

Ыє±±±1:1±±±і±±Insert±і±±±±±±±±±і±±±±±±±±±±±±±±±і±±±±±±±±±±±±±±єЫ

ЫИНННННННННіНННННННННіНННННННННіНННННННННННННННіННННННННННННННјЫ

ЫЫЫЫЫЫЫЫЫЫЫіЫЫЫЫЫЫЫЫЫіЫЫЫЫЫЫЫЫЫіЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫіЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫ

і і і і

і Окно Управляющий Диалоговое окно

Горизонтальная элемент

полоса прокрутки

Рис. 10.1 Выводимые на экран компоненты приложения Windows.

Приложение Windows - это специальный тип программы PC, кото-

рая:

- должна иметь специальный выполняемый формат файла (.EXE);

- работать только с Windows;

- обычно работать в прямоугольном окне на экране;

- при выводе на экран следовать рекомендациям по стандартно-

му интерфейсу с пользователем;

- может работать одновременно с другими программами Windows

и прочими программами, включая другие экземпляры самое се-

бя;

- может взаимодействовать и совместно использовать данные с

другими приложениями Windows.

Преимущества Windows

Как для пользователей, так и для разработчиков Windows пред-

лагает множество преимуществ, которые включают в себя:

* Стандартные и предсказуемые операторы: если вы знаете, как

использовать одно приложение Windows, то сможете работать

со всеми остальными.

* Для каждого приложения нет необходимости устанавливать

драйверы устройств и устройства: в Windows предусмотрены

драйверы для поддержки периферийной аппаратуры.

* Межпрограммное взаимодействие и связь.

* Многозадачность: возможность одновременно запускать мно-

жество программ.

* Доступ к большему объему памяти: Windows поддерживает за-

щищенный режим.

Для разработчиков эти преимущества включают в себя:

* Независимую от устройств графику, благодаря чему графичес-

кие приложения могут работать на всех стандартных дисплей-

ных адаптерах.

* Непосредственную поддержку широкого диапазона принтеров,

мониторов и устройств типа "мышь".

* Богатую библиотеку графических подпрограмм.

* Больше памяти для больших программ.

* Поддержку меню, пиктограмм, битовых массивов и др.

Требования

Обратной стороной ряда предлагаемых Windows пользователю

преимуществ является перечень более строгих аппаратных требова-

ний. В общем случае Windows требует для производительности, срав-

нимой с приложением DOS лучших графических средств, больших объ-

емов памяти, более быстрых процессоров. Если вы располагаете про-

цессором 80286 или старше и не менее 2 мегабайтами памяти, то

Windows будет прекрасно работать.

Программные средства

Для разработчиков прикладных программ Windows предусматрива-

ет широкий диапазон программных средств.

Архитектура с управлением по событиям

Windows основана на архитектуре с управлением по событиям.

Это означает, что весь ввод от пользователя интерпретируется как

события. Когда событием является щелчок кнопкой "мыши" или нажа-

тие клавиши клавиатуры, то происходит событие, и Windows генери-

рует сообщение. Например, если пользователь щелкает левой кнопкой

"мыши", Windows генерирует сообщение wm_LButtonDown. Если пользо-

ватель нажимает клавишу, Windows генерирует событие wm_KeyDown.

При выборе "мышью" или с помощью клавиатуры Windows интерп-

ретирует все команды меню и управляющие команды как сообщения

wm_Command. Эта архитектура с управлением по событиям отлично со-

гласуется с объектно-ориентированным подходом Borland Pascal.

Графика, независимая от устройств

Windows унифицирует процесс вывода на экран и печать в одном

модуле, который называется интерфейсом с графическими устройства-

ми (GDI) и который обеспечивает общий интерфейс для каждой прог-

раммы Windows. Кроме того, в Windows предусмотрены драйверы уст-

ройств для большинства стандартных графических адаптеров и прин-

теров. В результате система позволяет вам писать одно приложение,

которое без изменений работает на большинстве существующих в мире

аппаратных средствах.

Графика, независимая от устройств, предлагает некоторые пре-

имущества, которые не бросаются в глаза сразу. Одним из них явля-

ется то, что приложения Windows легкоустанавливаемы, так как не

требуют переконфигурации системы с конкретными драйверами уст-

ройств. Другое состоит в том, что приложения Windows часто лучше

работают в локальной сети, поскольку каждый пользователь имеет

свою собственную локальную конфигурацию.

Но графика, независимая от устройств, требует жертв. Для

разработчика эти "жертвы" состоят в соблюдении в чем-то строгих

требований GDI. GDI ограничивает возможности программиста в про-

ектировании приложений.

Многозадачность

Windows позволяет пользователям выполнять несколько приложе-

ний параллельно, устраняя необходимость использования резидентных

в памяти программ (TSR). В Windows реализована не просто многоза-

дачность. Она поддержана набором средств и межпроцессорных комму-

никаций, таких как буфер вырезанного изображения Clipboard и ди-

намический обмен данными (DDE).

Windows управляет множеством приложений, ограничивая исполь-

зование экрана каждым приложением одной или более прямоугольной

областью, которые называются окнами. Эти окна можно перемещать,

изменять их размер и временно сворачивать в пиктограммы, позволяя

пользователю быстро переключаться от одной задачи к другой.

С точки зрения программиста это означает, что программа не

должна записывать текст или графику непосредственно по экранным

адресам, а должна выводить их в пользовательскую область окна -

область внутри рамки окна. Аналогично, прикладная программа долж-

на использовать память компьютера совместно с другими приложения-

ми. Хорошо построенное приложение Windows корректно соблюдает

правила Windows работы с экраном и управления памятью.

Управление памятью

В типичном сеансе работы с Windows множество приложений мно-

гократно открываются и закрываются, поэтому нерационально загру-

жать каждое приложение в память после предыдущего - Windows скоро

исчерпает память. Вместо этого Windows, чтобы удовлетворить пот-

ребности других приложений или самой Windows, может перемещать

память большинства приложений в другую часть памяти или на диск.

Таким образом, приложение Windows должно соответствовать ди-

намическому распределению памяти Windows и избегать прямого обра-

щения к адресам памяти. Например, традиционный указатель на ячей-

ку памяти может быстро стать недопустимым, когда Windows пере-

распределит память, так как может указывать на ячейку памяти, ко-

торая используется для чего-то еще.

Вместо указателей Windows использует описатели, которые по

существу представляют собой указатели на указатели. Описатели -

это номера, использующиеся в качестве индексов в обслуживаемой

Windows таблице указателей. Таким образом, приложения Windows

ссылаются на окно или содержимое экрана (область для отображения

на экране) по описателю. Имеются также указатели экземпляров при-

ложений, строк, средств отображения и ресурсов, таких как меню и

пиктограммы.

При обычной работе вам не потребуется иметь дело с самими

описателями. Вы можете выделять и освобождать память в динамичес-

ки распределяемой области с помощью обычных подпрограмм New,

Dispose, GetMem и FreeMem, а Borland Pascal будет взаимодейство-

вать с Windows и обеспечивать для нее информацию о том, на что

фактически ссылаются эти указатели.

Одним из основных преимуществ управления памятью в Windows

является возможность совместно использовать в приложениях скомпи-

лированный код. Например, если пользователь выполняет два экземп-

ляра одного и того же приложения, то эти приложения используют

один и тот же скомпилированный код в памяти. Аналогично, приложе-

ние может динамически загружать библиотечный модуль, совместно

используемый в нескольких прикладных программах. Это называется

динамически компонуемой библиотекой - DLL.

Ресурсы

Ресурсы представляют собой описания устройств пользователь-

ского интерфейса приложения Windows: его меню, диалоговых окон,

курсоров, пиктограмм, битовые массивы, строки и командные клави-

ши. Windows обеспечивает средства для поддержки этих описаний вне

исходного кода приложения. Ресурсы приложения объединяются с его

выполняемым файлом перед выполнением приложения. Чтобы ограничить

использование памяти, приложение вызывает ресурсы в память только

когда они необходимы.

Отделение спецификаций ресурса от исходного кода имеет до-

полнительное преимущество: вы можете изменить вид приложения, не

затрагивая исходного кода программы. Фактически, для модификации

ресурсов приложения вам не нужно даже иметь исходный код. Это об-

легчает настройку или трансляцию существующих приложений Windows.

Для создания и настройки ресурсов Borland Pascal включает в

себя пакет разработчика ресурсов Resourse Workshop.

Динамическая компоновка

Windows позволяет приложениям, включая программы Borland

Pascal, загружать и освобождать библиотечные модули на этапе ком-

поновки. Эти модули должны быть записаны в специальном выполняе-

мом формате (EXE), который называется динамически компонуемой

библиотекой (DLL). Часто эти библиотеки выполняют специальные и

сложные задачи, такие как преобразование форматов файлов. В этом

случае программа может применять DLL как фильтры для экспорта и

импорта файлов. Кроме того, DLL могут совместно использоваться

группой приложений, что способствует совместному использованию и

экономии памяти.

Буфер вырезанного изображения

Буфер вырезанного изображения Clipboard позволяет пользова-

телям передавать информацию, такую как текст, графику и данные,

между приложениями, между различными частями приложения или в ка-

честве временной памяти для последующего использования. Например,

программа обработки текста может использовать буфер вырезанного

отображения для операций удаления, копирования и вставки текста.

Динамический обмен данными

Динамический обмен данными (DDE) - это еще один механизм пе-

редачи информации. В то время как буфер вырезанного изображения

полностью находится под контролем пользователя, DDE выполняет

"закулисную" работу под управлением программы. Посылая DDE-сооб-

щения, программа передает информацию другой программе.

Множественный документальный интерфейс

Множественный документальный интерфейс (MDI) устанавливает

соглашения по пользовательскому интерфейсу для создания окон, со-

держащих внутри себя дочерние окна. Примером MDI может служить

интегрированная интерактивная среда для Windows Borland Pascal. В

оперативной области Borland Pascal пользователь может открыть

сразу несколько окон. Каждое окно редактирования является дочер-

ним окном.

ЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬЬ

ЫЙН[]ННННННННННННННННННННННННННННННННННННННННННННННННННННННННН»Ы

Ыє±=±ЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫBorland PascalЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫі^іvєЫ

ЫєДДДДДДДБДБДєЫ

Ыє File Edit Search Run Compile Tools Options Windows Help єЫ

ЫєДДДДДДДДДДДєЫ

ЫєЙНННННННННННННННННННННННННННННННННННННННННННН» єЫ

Ыєє±=± c:\bpw\helloapp.pas і^іvє єЫ

ЫєєДДДДДДДДДДДДДДДБДБДє єЫ

Ыєє program Hello; ^є єЫ

Ыєє ±є єЫ

Ыєє uses WObjects, WinTypes, WinProcs; ±є єЫ

Ыєє ±є єЫ

Ыєє type ±є єЫ

Ыєє Ьє єЫ

Ыєє { Define a TApplications descendant } ±є єЫ

Ыєє THelloApp = object(TApplication) ±є єЫ

Ыєє procedure InitMainWindow; virtual; vє єЫ

Ыєє<±Ы±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±>±є єЫ

ЫєИННННННННННННННННННННННННННННННННННННННННННННј єЫ

Ыє єЫ

Ыє єЫ

Ыє єЫ

Ыє±±±1:1±±±±±±Insert±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±єЫ

ЫИНННННННННННННННННННННННННННННННННННННННННННННННННННННННННННННјЫ

ЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫ

Рис. 10.2 Окна IDE для Windows Borland Pascal - приложение

MDI.

Типы данных Windows

Благодаря схеме управления данными Windows и ее подобию язы-

ку программирования Си, программированию для Windows с помощью

Borland Pascal способствуют некоторые специализированные типы

данных. Например, описатель окна сохраняется под типом HWnd.

Borland Pascal и ObjectWindows определяют новые типы, содействую-

щие работе с таким типом как HWnd. Все эти новые типы и структуры

данных описаны в "Руководстве по программированию с использовани-

ем ObjectWindows".

Объектно-ориентированная работа с окнами

Как можно видеть, программирование в многооконной среде тре-

бует знания многих событий, форматов, описателей и других прило-

жений, поэтому разработка программы Windows может показаться

трудной задачей. К счастью, объектно-ориентированное программиро-

вание упрощает задачу программирования для многооконной среды и

позволяет разработчику прикладной программы сосредоточиться на

функциях приложения, а не на его форме. Используя объекты для

представления таких сложных структур как окна, программы Borland

Pascal могут инкапсулировать свои операции и хранение данных.

Этой цели служит ObjectWindows.

Объектно-ориентированное программирование обеспечивает ту

основу, в рамках которой программист может использовать объекты

для представления элементов интерфейса с пользователем программы

Windows. Это означает, что окно является объектом.

Окно ObjectWindows объектные типы прикладной программы под-

держивают требуемую от программы Windows работу с сообщениями,

значительно упрощая взаимодействие программиста с пользователем.

Фактически, объекты ObjectWindows представляют не только окна,

они представляют диалоговые блоки и управляющие элементы, такие

как блоки списка и командные кнопки.

Лучший интерфейс с Windows

ObjectWindows использует объектно-ориентированные расширения

Borland Pascal для инкапсуляции прикладного программного интер-

фейса Windows (API), скрывая от вас детали программирования для

Windows. В результате вы можете использовать IDE для Windows

Borland Pascal для написания программ Windows, затрачивая сущест-

венно меньше времени и усилий, чем это потребовалось бы при не-

объектно-ориентированном программировании. ObjectWindows предус-

матривает три полезных средства: инкапсуляцию информации окна,

абстракцию функций API Windows и автоматический ответ на сообще-

ния.

Интерфейсные объекты

Хотя библиотека ObjectWindows определяет объекты для окон,

диалоговых окон и управляющих элементов, она предусматривает

только поведение объектов, их атрибуты и типы данных. Физической

реализацией, визуальным отображением элемента на экране, управля-

ет сама Windows. Таким образом, объекты ObjectWindows, которые мы

называем интерфейсными объектами, взаимодействуют с соответствую-

щими визуальными элементами, которые мы называет интерфейсными

элементами. Успешное управление взаимодействием объект/элемент -

это ключ к успешному программированию в Windows с использованием

ObjectWindows.

Взаимодействие объект/элемент осуществляется через описатель

окна. Когда вы строите интерфейсный объект, одной из его функций

является указание Windows на создание интерфейсного элемента.

Windows возвращает идентифицирующий этот элемент описатель, кото-

рый объект сохраняет в поле с именем HWindow. Многие функции

Windows воспринимают описатель окна в качестве параметра, поэтому

сохранение его в поле поддерживает готовность доступа к оконному

объекту. Аналогично, поля интерфейсного объекта можно использо-

вать для сохранения информации о средствах отображения или инфор-

мации о состоянии конкретного окна.

Абстрагирование функций Windows

Приложения Windows управляют своим видом и поведением путем

вызова функций Windows - набора почти из 600 функций, образующий

интерфейс с прикладной программой Windows (API). Каждая функция

воспринимает разнообразные параметры различных типов, которые мо-

гут быть достаточно запутанными. Хотя из Borland Pascal вы можете

непосредственно вызывать любую функцию Windows, ObjectWindows уп-

рощает задачу, предлагая объектные методы, абстрагирующие функци-

ональные вызовы.

Как отмечалось выше, многие из параметров для функций

Windows уже записаны в полях интерфейсных объектов. Таким обра-

зом, методы могут использовать эти данные для подстановки пара-

метров для функций Windows. Кроме того, ObjectWindows группирует

родственные функциональные вызовы в отдельных методах, которые

выполняют задачи более высокого уровня. Результатом является уп-

рощенный и простой в использовании интерфейс API, улучшающий су-

ществующий API Windows.

Хотя такой подход существенно уменьшает вашу зависимость от

сотен функций API Windows, он не запрещает вам вызывать API не-

посредственно. ObjectWindows предлагает лучшее из обоих "миров":

объектно-ориентированную разработку высокого уровня, плюс макси-

мальное управление графической операционной средой.

Автоматизация ответа на сообщения

Кроме сообщения операционной среде Windows, что надо что-то

сделать, большинство приложений должны иметь возможность отвечать

на сотни сообщений Windows, являющихся результатом действий поль-

зователя (например, щелчка кнопкой "мыши"), других приложений или

прочих источников. Корректная обработка сообщений и ответ на них

является решающим для правильной работы вашей программы. Кроме

того, ваша программа должна как-то отвечать на выбор меню, и в

ответ на конкретное сообщение реализовать это не трудно. Но напи-

сание программы, которая знает как отвечать почти на 200 различ-

ных сообщений Windows, также затруднительно, как вызов нужных

функции Windows.

Объекты с их предопределенным поведением (методами) прекрас-

но подходят для задачи ответа на внешние воздействия (сообщения

Windows). ObjectWindows превращает сообщения Windows в вызовы ме-

тодов Borland Pascal. Таким образом, с помощью ObjectWindows вы

просто определяете метод для ответа на каждое сообщение, которое

вам нужно обрабатывать в программе. Например, когда пользователь

щелкает левой кнопкой "мыши", Windows генерирует сообщение

wm_LButtonDown. Если вы хотите, чтобы окно или управляющий эле-

мент в вашей программе реагировали на такие нажатия кнопки "мы-

ши", нужно определить метод WMLButtonDown, настроенный на сообще-

ние wm_LButtonDown. Затем, когда Windows посылает это сообщение,

ваш объект автоматически вызывает определенный вами метод.

Такие методы называются методами ответа на сообщение. Без

объектно-ориентированного программирования и ObjectWindows вам

пришлось бы написать длинный оператор case для каждого окна и уп-

равлять поступлением этого сообщения, отсортировывать вид сообще-

ния и наконец, решать, что с ним делать. ObjectWindows берет на

себя все эти функции.

Структура программы Windows

При наличии такого множества взаимодействующих элементов

программного обеспечения как DOS, Windows и прикладные программы

полезно знать о том, какие части ваших приложений Windows взаимо-

действуют с окружающей их программной средой. В данном разделе

исследуется структура типичных приложений Windows, написанных в

Borland Pascal with Objects.

Структура Windows

На этапе выполнения функциональные возможности Windows и ее

API сосредоточены в трех библиотечных модулях, вызываемых работа-

ющими в данный момент приложениями. Это следующие модули Windows:

* KERNEL.EXE - управляет распределением памяти и ресурсов,

выполняет планирование и взаимодействие с DOS.

* GDI.EXE - выводит на экран и на принтер графику.

* USER.EXE - работает с окнами, вводом данных от пользовате-

ля и коммуникациями.

Эти модули являются компонентами распространяемой версии

Windows, поэтому пользователи Windows могут найти их на своих

дисках. Поставляемые программы используют эти библиотечные моду-

ли; он не включают их.

Взаимодействие с Windows и DOS

Из-за ограниченного масштаба операционной системы DOS легко

предвидеть, как будет влиять DOS на успешное выполнение ваших

прикладных программ. Тем не менее, программа для DOS работает

благодаря взаимодействию между вашей программой и средствами опе-

рационной системы.

Аналогичное правило действует для программы Windows. Пос-

кольку Windows предлагает значительно большее число функциональ-

ных вызовов операционной системы, взаимодействие между Windows и

вашей программой отследить значительно труднее. Например, чтобы

вывести графику на экран, ваша программа должна вызвать функцию

GDI Windows. Для реакции на нажатие пользователем кнопки "мыши"

программа должна определять метод реакции (ответа) на сообщение.

Ваша программа должна непрерывно взаимодействовать с операционной

системой (DOS + Windows).

Элементарная программа

Традиционным введением в новый язык программирования или

операционную среду является программа, выводящая сообщение

"Hello, Word!", написанная на данном языке или для данной опера-

ционной среды. Это программа содержит количество программного ко-

да, достаточное для вывода на экран строки "Hello, Word!".

Конечно, в Windows нужно сделать значительно больше. Вам

нужно вывести окно, записать в него текст и сделать так, чтобы

окно взаимодействовало с "окружающим миром", по крайней мере вы

должны иметь возможность закрыть окно и выйти. Если вы будете де-

лать все это с самых основ, то даже для выполнения этих элемен-

тарных задач потребуется очень большой объем кода. Например,

программа GENERIC.PAS, содержащаяся на дистрибутивных дисках, вы-

полняя такие элементарные действия, занимает более 100 строк.

Это связано с тем, что предъявляет к прикладной программе

ряд требований, которые та должна удовлетворять перед тем, как

она сможет работать в Windows. Даже простейшая программа требует

большого объема исходного кода. К счастью, программы, написанные

с использованием ObjectWindows, автоматически удовлетворяют этим

требованиям (включая создание и вывод на экран основного окна и

сохранение для приложения описателя окна). Таким образом, прог-

рамма "Hello, Word!" сокращается всего до 16 строк.

program HelloApp;

uses WObjects;

type

THelloWord = object(TApplication)

procedure InitMainWindow; virtual;

end;

procedure THelloWord.InitMainWindow;

begin

MainWindow := New(PWindow,

Init(nil, 'Hello, Borland Pascal'));

end;

var HelloWord: THelloWord;

begin

HelloWord.Init('HelloWord');

HelloWord.Run;

HelloWord.Done;

end.

Действия программы при запуске

При запуске программа ObjectWindows должна сначала получить

от Windows четыре значения и сохранить их в соответствующих гло-

бальных переменных. (Это происходит автоматически, но если бы вы

писали программу, не используя ObjectWindows, то пришлось бы об

этом позаботиться.)

* В HInstance сохраняется описатель экземпляра программы.

* В HPrevInst сохраняется описатель последнего экземпляра

той же прикладной программы.

* В CmdShow записывается целое значение, представляющее на-

чальный режим вывода основного окна. Оно используется для

вызова метода Show.

* В CmdLine записывается командная строка вызова прикладной

программы, включая параметры и имя файла, например

"CALC.EXE /M" или "WORDPROC.EXE LETTER1.DOC".

Как приложение ObjectWindows, программа HelloApp должна

строить и инициализировать объект основного окна. Она может ини-

циализировать только первый экземпляр HelloApp с помощью метода

InitApplication, либо инициализировать каждый экземпляр HelloApp

с помощью метода InitInstance.

HelloApp начинает цикл сообщения, вызывая метод Run. Нако-

нец, она завершается путем освобождения объекта прикладной прог-

раммы с помощью метода Done.

Назначение основного окна

Основное окно прикладной программы - это окно первоначально-

го вывода прикладной программы при ее запуске. Оно отвечает за

представления пользователю списка доступных команд (меню). Во

время сеанса работы с прикладной программой основное окно управ-

ляет интерфейсом прикладной программы и во многих случаях являет-

ся единственной рабочей областью программы, создавая, когда это

требуется, диалоговые окна. Другие, более сложные приложения, мо-

гут использовать в качестве рабочих областей несколько окон. Ког-

да пользователь закрывает основное окно, он инициализирует про-

цесс закрытия прикладной программы.

Цикл разработки прикладной программы

Из-за наличия определенных требований ко многим прикладным

программам Windows (например, инициализация основного окна) вам

будет легче всего начать писать программу, используя существующее

приложение Windows и приспосабливая его для своих нужд. В

ObjectWindows вы можете найти много примеров программ. Выберите

для своего приложения наиболее подходящую.

Используя в Windows интегрированную интерактивную среду раз-

работки программ, вы значительно сократите время разработки. Бла-

годаря многозадачным возможностям Windows, вы можете запустить

IDE, Resource Workshop и свою прикладную программу одновременно.

Поставляемые с Borland Pascal for Windows инструментальные средс-

тва не только облегчают каждую задачу, но и сокращают число задач

при разработке приложения Windows. По существу, это процесс можно

сократить до следующих нескольких шагов:

1. Создание исходного кода программы и включение в программу

имен файлов ресурсов, которые будут использоваться по ди-

рективе {$R имя_файла}.

2. Создание ресурсов для диалоговых окон, меню и т.д.

3. Компиляция программы.

4. Интерактивная отладка программы.

Изучение ObjectWindows

Теперь, когда вы поняли основы программирования в Windows и

имеете начальные сведения об ObjectWindows, можете начинать прог-

раммировать. Начните с чтения "Руководства по программированию с

использованием ObjectWindows" и шаг за шагом изучите, как разра-

батывать приложение ObjectWindows.


Untitled

Borland Pascal. Руководство пользователя
  • Введение
  • Глава 1. Установка и запуск Borland Pascal
  • Глава 2. Что нового в Borland Pascal
  • Глава 3. Основы интегрированной среды для DOS
  • Глава 4. Программирование в интегрированной интерактивной среде для DOS
  • Глава 5. Программирование в интегрированной интерактивнойсреде для Windows
  • Глава 6. Отладка в интегрированной среде
  • Глава 7. Модули Borland Pascal
  • Глава 8. Использование указателей
  • Глава 9. Объектно-ориентированное программирование
  • Глава 10. Взгляд на Windows


Untitled

Глава 1. Установка и запуск Borland Pascal

    • Использование программы Install
    • Программа Install и Windows
    • Запуск Borland Pascal
    • Защищенный режим и память
  • Запуск Borland Pascal for Windows
    • Запуск BP.EXE в расширенном режиме Windows 386
    • Запуск BP.EXE в стандартном режиме Windows
    • Запуск BP.EXE в окне Windows DOS
    • Жидкокристаллические и плазменные экраны
    • Файл README
    • Файлы FILELIST.DOC и HELPME!.DOC
    • Примеры программ


Untitled

Глава 2. Что нового в Borland Pascal

    • Три интегрированных интерактивных среды разработки программ
    • Новые средства IDE
    • Компилятор командной строки, работающий в защищенном режиме
    • Среда разработки программ защищенного режима DOS
    • Динамически компонуемые библиотеки DOS
  • Добавления к языку Паскаль
    • Улучшения в библиотеке исполняющей системы
  • Новые подпрограммы модуля System
    • Новые модули
    • Новые директивы компилятора
    • Усовершенствования компилятора
    • Улучшения в Turbo Vision
    • Улучшения ObjectWindows
    • Новые средства и утилиты


Untitled

Глава 3. Основы интегрированной среды для DOS

    • Запуск IDE
    • Компоненты интегрированной среды
  • Окна IDE
  • Управление окном
  • Строка состояния
  • Диалоговые окна


Untitled

Глава 4. Программирование в интегрированной интерактивной среде для DOS

    • Запуск IDE и выход из нее
    • Параметры запуска
    • Установка параметров
  • Выход из IDE
    • Использование справочной системы Help
    • Перемещение в справочной системе
    • Запрос помощи
    • Копирование примеров исходного кода
    • Загрузка других справочных файлов
    • Выход из справочника Help
    • Запись и редактирование исходного кода
    • Настройка конфигурации редактора
    • Изменение решения: команда Undo
    • Групповая отмена
    • Отмена отмены
  • Работа с блоками текста
    • Выделение блока
    • Вырезание, копирование и вставка блоков
    • Изменение поведения выделенных блоков
  • Поиск
    • Поиск и замена
    • Соответствие пар ограничителей
  • Переход к строке с заданным номером
    • Использование локального меню окна редактирования
  • Выделение синтаксиса
    • Выбор файлов для выделения
    • Запрещение выделения синтаксиса
  • Печать исходного кода
  • Работа с файлами
  • Открытие файлов
    • Открытие файла в позиции курсора
  • Компиляция и выполнение
    • Выбор целевой платформы
  • Компиляция
    • Формирование (Make)
  • Построение (Build)
    • Выполнение
    • Передача программе параметров
  • Параметры компилятора и компоновщика
    • Оптимизация кода
  • Условная компиляция
  • Директивы DEFINE и UNDEF
  • Предопределенные идентификаторы
    • Идентификаторы IFxxx, ELSE и ENDIF
    • Директивы IFDEF и IFNDEF
    • Директива IFOPT
  • Просмотр исходного кода
  • Просмотр объектов
  • Просмотр модулей
  • Просмотр глобальных идентификаторов
  • Просмотр идентификаторов в исходном коде
  • Просмотр функций ObjectBrowser
  • Выполнение в IDE других программ
    • Настройка меню Tools
    • Работа с окном Messages
  • Настройка конфигурации IDE
    • Сохранение рабочей операционной среды
    • Использование файла конфигурации
    • Использование файла оперативной области
  • Управление проектом


Untitled

Глава 5. Программирование в интегрированной интерактивнойсреде для Windows

    • Запуск IDE для Windows
  • Использование оперативной полосы
    • Настройка конфигурации оперативной полосы
  • Использование справочной системы Help
    • Перемещение по справочной системе
    • Запрос помощи
  • Копирование примеров кода
    • Выход из справочной системы
    • Запись и редактирование исходного кода
    • Настройка конфигурации редактора
  • Набор команд
    • Использование редактора
  • Выделение синтаксиса
    • Цветовое выделение текста
    • Использование системных цветов Windows
    • Изменение атрибутов текста
  • Печать исходного кода
  • Работа с файлами
    • Открытие файлов
    • Где находятся файлы?
    • Работа с файлами в другом каталоге
    • Компиляция и выполнение
    • Просмотр исходного кода
  • Просмотр объектов
    • Буквенные символы в ObjectBrowser
    • Фильтры
  • Просмотр глобальных идентификаторов
    • Просмотр идентификаторов в исходном коде
    • Выполнение в IDE других программ
    • Настройка конфигурации IDE


Untitled

Глава 6. Отладка в интегрированной среде

    • Что такое отладка?
    • Какие существуют виды ошибок?
  • Методы отладки
  • Генерация отладочной информации
  • Управление выполнением
  • Что такое шаг?
    • Выполнение программы по шагам
  • Трассировка программы
    • Трассировка или выполнение по шагам?
  • Выполнение больших фрагментов
    • Поиск нужного места
    • Повторное выполнение
    • Отслеживание вывода программы
    • Переключение экранов
    • Окно Output
    • Использование двух мониторов
    • Просмотр значений
  • Что такое выражение?
  • Просмотр выражений
  • Спецификаторы формата в выражениях отладчика
    • Вычисление и модификация
  • Использование точек останова
    • Задание точек останова
    • Отмена точке останова
    • Модификация точек останова
    • Создание условный точек останова
    • Прерывание программы без точек останова


Untitled

Глава 7. Модули Borland Pascal

    • Что такое модуль?
  • Структура модуля
  • Интерфейсная секция
    • Секция реализации
    • Секция инициализации
    • Как используются модули?
    • Ссылки на описания модуля
    • Оператор uses секции реализации
  • Стандартные модули
    • Создание ваших собственных модулей
    • Компиляция модуля
    • Доступность модуля для программы
  • Пример
    • Модули и большие программы
    • Утилита TPUMOVER


Untitled

Глава 8. Использование указателей

    • Для чего используются указатели?
    • Работа с большими объемами данных
    • Работа с данными неизвестного размера
    • Работа с временными буферами данных
    • Работа с несколькими типами данных
    • Связанные списки
    • Что такое указатель?
    • Ссылочный тип
    • Типизированные указатели
    • Разыменование указателей
  • Как использовать указатели?
    • Выделение памяти для динамических переменных
  • Освобождение памяти, выделенной для динамических переменных
    • Процедуры GetMem и FreeMem
  • Проверка объема доступной динамически распределяемой памяти
    • Общие проблемы использования указателей
    • Разыменование неинициализированных указателей
    • Потери динамически распределяемой памяти
    • Управление связанным списком
  • Построение списка
    • Перемещение по списку
    • Освобождение выделенной для списка памяти


Untitled

Глава 9. Объектно-ориентированное программирование

    • Объекты
    • Наследование
  • Объекты: наследующие записи
    • Экземпляры объектных типов
    • Поля объектов
    • Хорошая и плохая техника программирования
  • Методы
    • Совмещенные код и данные
    • Определение методов
    • Область действия метода и параметр Self
    • Поля данных объекта и формальные параметры метода
    • Объекты, экспортируемые модулями
    • Секция private
    • Программирование в "действительном залоге"
  • Инкапсуляция
    • Методы: никакого ухудшения
    • Расширяющиеся объекты
    • Наследование статических методов
  • Виртуальные методы и полиморфизм
    • Раннее связывание против позднего связывания
    • Совместимость типов объектов
  • Полиморфические объекты
    • Виртуальные методы
    • Проверка диапазонов при вызове виртуальных методов
    • Расширяемость объекта
    • Статические методы или виртуальные методы?
    • Динамические объекты
    • Размещение и инициализация с помощью процедуры New
    • Удаление динамических объектов
  • Деструкторы
    • Пример размещения динамического объекта
    • Что же дальше?
    • Заключение


Untitled

Глава 10. Взгляд на Windows

  • Что такое приложение Windows?
    • Преимущества Windows
    • Требования
    • Программные средства
    • Архитектура с управлением по событиям
    • Графика, независимая от устройств
    • Многозадачность
    • Управление памятью
    • Ресурсы
    • Динамическая компоновка
    • Буфер вырезанного изображения
    • Динамический обмен данными
  • Множественный документальный интерфейс
  • Типы данных Windows
    • Объектно-ориентированная работа с окнами
    • Лучший интерфейс с Windows
    • Интерфейсные объекты
    • Абстрагирование функций Windows
    • Автоматизация ответа на сообщения
  • Структура программы Windows
    • Структура Windows
    • Взаимодействие с Windows и DOS
  • Элементарная программа
    • Действия программы при запуске
    • Назначение основного окна
  • Цикл разработки прикладной программы
    • Изучение ObjectWindows


Untitled

Введение

  • Как использовать руководства по Borland Pascal


Untitled

Введение

Borland Pascal with Objects (Паскаль с объектами фирмы

Borland) предназначен для всех тех пользователей, которые хотят

разрабатывать прикладные программы для операционной системы DOS

или операционной среды Windows. Вы можете создавать прикладные

программы, работающие в реальном режиме DOS, Windows или приклад-

ные программы для защищенного режима DOS или Windows. Borland

Pascal предлагает богатую среду программирования, которая делает

разработку программного обеспечения более производительной и бо-

лее приятной. Используя структурированный язык высокого уровня

Паскаль, вы можете писать программы для приложений любого типа и

размера.

Borland Pascal 7.0, оставаясь совместимым с программным ко-

дом, написанным для Turbo Pascal или Turbo Pascal for Windows,

предоставляет вам новые возможности. Исследуя эти возможности

следует учитывать, что этот компилятор Паскаля является быстрым и

эффективным компилятором и принят в качестве общемирового стан-

дарта.

Как использовать руководства по Borland Pascal

Borland Pascal поставляется с одиннадцатью руководствами,

каждое из которых имеет свое назначение.

Примечание: Если раньше вы никогда не работали с прог-

раммным продуктом Turbo Pascal, прочтите руководство поль-

зователя.

"Руководство пользователя" будет для вас полезным, если:

- вы хотите знать, как установить Borland Pascal;

- ранее вы использовали Turbo Pascal или Borland Pascal for

Windows и хотите знать, что нового в этой версии;

- вы хотите узнать, как работать с интегрированной средой

разработки программ Borland (IDE) и использовать ее для

разработки и отладки программ;

- вы хотите узнать о программных модулях и о том, как напи-

сать свой собственный модуль;

- ранее вы не использовали в своих программах указатели или

хотите освежить свои знания об указателях;

- вы не знакомы с объектно-ориентированным программировани-

ем;

- хотите получить начальные сведения об ObjectWindows.

Примечание: Если вы хотите узнать о языке Borland

Pascal, прочтите "Руководство по языку".

"Руководство по языку" посвящено языку Borland Pascal и по-

ясняет как извлечь из него максимум возможного. Используйте "Ру-

ководство по языку", чтобы:

- найти формальное определение языка Borland Pascal, включая

подробные диаграммы синтаксиса, описывающие каждую конс-

трукцию Borland Pascal;

- узнать как использовать библиотеки динамической компоновки

и как написать свою собственную библиотеку;

- изучить организацию библиотек исполняющей системы и их ис-

пользование;

- изучить процедуры, функции, предописанные переменные,

константы и т.д. и узнать о том, что собой представляют

доступные вашим программам библиотеки исполняющей системы;

- исследовать, как программы Borland Pascal использую па-

мять;

- узнать, как Borland Pascal реализует управление програм-

мой;

- узнать об оптимизации кода в Borland Pascal;

- выяснить, как Borland Pascal использует язык ассемблера.

Примечание: При программировании в Borland Pascal в

качестве основного справочника используйте "Справочное ру-

ководство программиста".

"Справочное руководство программиста" представляет собой

справочник со всеми необходимыми вам подробностями. При програм-

мировании держите это руководство рядом с компьютером. Используй-

те "Справочное руководство программиста", если вы хотите:

- узнать о деталях конкретной процедуры, функции, типе или

константе библиотеки исполняющей системы и выяснить, как

их использовать;

- понять, как работают директивы компилятора, что делает

каждая директива компилятора и как пользоваться этими ди-

рективами;

- выяснить, что означает сообщение об ошибке;

- познакомиться с использованием компилятора, работающего в

режиме командной строки;

- просмотреть команды редактора;

- просмотреть краткую справочную информацию по директивам

компилятора;

- ознакомиться со списком зарезервированных слов и стандарт-

ных директив.

Примечание: Чтобы узнать о разработке программ для DOS

с помощью Turbo Vision, используйте "Руководство по прог-

раммированию с использованием Turbo Vision"

"Руководство по программированию с использованием Turbo

Vision" знакомит вас с Turbo Vision и поясняет как с ней рабо-

тать. Turbo Vision - это интегрированная система разработки прик-

ладных программ, позволяющая вам быстро начать объектно-ориенти-

рованное программирование в DOS. Чтобы освоить Turbo Vision, вам

нужно:

- проработать руководство и приобрести некоторые навыки раз-

работки прикладной программы Turbo Vision;

- изучить иерархию Turbo Vision и познакомиться с типами

объектов;

- понять смысл программирования, управляемого событиями, и

что оно за собой влечет;

- использовать справочник по Turbo Vision для нахождения де-

тальной информации об объектах и соответствующих типах,

константах и переменных в иерархии Turbo Vision.

Примечание: Чтобы узнать о том как писать программы

для Windows с помощью ObjectWindows, используйте "Руководс-

тво по программированию с использованием ObjectWindows"

"Руководство по программированию с использованием

ObjectWindows" - это ваше руководство для разработки приложений

Windows с помощью библиотеки ObjectWindows Borland Pascal with

Objects. Чтобы овладеть ObjectWindows, вам понадобится:

- проработать руководство и приобрести некоторые навыки раз-

работки прикладной программы ObjectWindows;

- изучить иерархию ObjectWindows и прочитать о том, какие

задачи могут выполнять объекты ObjectWindows;

- научиться работать в своей программе ObjectWindows с гра-

фикой, ресурсами и данными;

- использовать справочник по ObjectWindows для нахождения

детальной информации об объектах и соответствующих типах,

константах и переменных в иерархии ObjectWindows.

Примечание: Чтобы узнать об инструментальных средс-

твах, которые могут сделать программирование на Паскале еще

более продуктивным, познакомьтесь с "Руководством по инс-

трументальным средствам и утилитам".

"Руководство по инструментальным средствам и утилитам" пояс-

няет как использовать поставляемые с Borland Pascal инструмен-

тальные средства и утилиты. Познакомьтесь с "Руководством по инс-

трументальным средствам и утилитам", если вы хотите:

- узнать о том, как использовать утилиту TPUMOVER для добав-

ления и удаления модулей из библиотеки исполняющей системы

Borland Pascal.

- выяснить, как можно использовать администратор проектов

MAKE;

- использовать утилиту WinSight для проверки сообщений, по-

лучаемых и контролируемых вашим приложением Windows;

- использовать утилиту WinSpector для проверки вашего прило-

жения Windows после получения невосстанавливаемой ошибки

прикладной программы (Unrecoverable Application Error),

чтобы помочь вам выяснить, что здесь неверно;

- прочитать о компиляторе ресурсов Resource Compiler, позво-

ляющем компилировать ваши ресурсы Windows; для создания

ресурсов вы, несомненно, предпочтете использовать пакет

разработчика ресурсов фирмы Borland (Resource Workshop),

но для полноты мы включили компилятор ресурсов;

- узнать о том, как создавать справочные файлы Windows 3.0 и

3.1;

- выяснить, как использовать при работе с инструментальными

средствами режима командной строки резидентную в памяти

справочную систему Borland Pascal - Turbo Help.

Кроме этих книг, специально посвященных Borland Pascal, па-

кет Borland Pascal включает в себя также следующие руководства,

которые можно использовать с Borland Pascal и другими языковыми

средствами Borland:

- "Руководство пользователя по Турбо отладчику";

- "Руководство пользователя по Турбо профилировщику";

- "Руководство пользователя по Турбо Ассемблеру";

- "Краткий справочник по Турбо Ассемблеру".


Untitled

Арифметические функции

Модуль System

Function Abs(X);

Возвращает абсолютное значение параметра.

X - выражение вещественного или целочисленного типа.

Function ArcTan(X: Extended): Extended;

Возвращает арктангенс аргумента.

X - выражение вещественного или целочисленного типа.

Function Exp(X: Real): Real;

Возвращает экспоненту.

X - выражение вещественного или целочисленного типа.

Function Frac(X: Real): Real;

Возвращает дробную часть аргумента.

X - выражение вещественного типа. Результат - дробная часть X, то есть

Frac (X) = X - Int (X).

Function Int(X: Real): Real;

Возвращает целочисленную часть аргумента.

X - выражение вещественного типа. Результат - целочисленная часть X, то есть X, округленный к нулю.

Function Ln(X: Real): Real;

Возвращает натуральный логарифм (Ln е = 1) выражения Х вещественного типа.

Function Pi: Extended;

Возвращает значение Pi, которое определено как 3.1415926535897932385.

Function Sin(X: Extended): Extended;

Возвращает синус аргумента.

X - выражение вещественного типа. Sin возвращает синус угла X в радианах.

Function Sqr(X: Extended): Extended;

Возвращает квадрат аргумента.

X - выражение с плавающей запятой. Результат того же самого типа, что и X.

Function Sqrt(X: Extended): Extended;

Возвращает квадратный корень аргумента.

X - выражение с плавающей запятой. Результат - квадратный корень X.


Untitled

Строковые процедуры и функции

Модуль System

Function Copy(S: String; Index, Count: Integer): String;

Возвращает подстроку строки.

S - выражение типа String. Index и Count - выражения целого типа. Функция возвращает строку, содержащую Count символов, начинающихся с позиции Index.

Если Index больше, чем длина S, функция возвращает пустую строку.

Procedure Delete(var S: String; Index, Count:Integer);

Удаляет подстроку символов длиной Count из строки S, начиная с позиции Index.

S - переменная типа String. Index и Count - выражения целого типа.

Если Index больше, чем длина S, символы не удаляются.

Procedure Insert(Source: String; var S: String; Index: Integer);

Объединяет подстроку в строку, начиная с определенной позиции.

Source - выражение типа String. S - переменная типа String любой длины. Index - выражение целочисленного типа.

Insert вставляет Source в S, начиная с позиции S[Index].

Function Length(S: String): Integer;

Возвращает число символов, фактически используемое в строке S.

Обратите внимание: при использовании строк с нуль-окончанием, число символов не обязательно равно числу байтов.

Function Pos(Substr: String; S: String): Integer;

Ищет подстроку в строке.

Pos ищет Substr внутри S и возвращает целочисленное значение, которое является индексом первого символа Substr внутри S.

Если Substr не найден, Pos возвращает ноль.


Untitled

Процедуры работы с динамической памятью

Модуль System

Procedure Dispose(var P: Pointer);

Освобождает память, распределенную для динамической переменной.

После вызова Dispose значение P не определено. При включенной директиве {$I+}, вы можете использовать исключительные ситуации, чтобы обработать эту ошибку.

Procedure FreeMem(var P: Pointer [; Size: Integer]);

Освобождает память, выделенную динамической переменной данного размера.

P - переменная любого типа-указателя, предварительно созданная процедурой GetMem.

Size определяет размер динамической переменной в байтах. Для корректной работы этой процедуры, Size должно быть точно равно числу байтов, предварительно распределенных для этой переменной процедурой GetMem.

FreeMem уничтожает переменную P и возвращает память «куче». Если P не указывает на память в «куче», возникает ошибка времени выполнения.

После вызова FreeMem, значение P не определено, и происходит ошибка, если Вы впоследствии ссылаетесь на P^. Вы можете использовать исключительные ситуации, чтобы обработать эту ошибку.

Procedure GetMem(var P: Pointer; Size: Integer);

GetMem создает динамическую переменную определенного размера и помещает адрес блока в переменную Р.

P - переменная любого типа-указателя. Size - выражение, определяющее размер в байтах динамической переменной. Вы должны ссылаться на созданную переменную как P^.

Procedure New(var P: Pointer);

Создает новую динамическую переменную и помещает ее адрес в переменную Р. P - переменная любого типа-указателя. Размер распределенного блока памяти равен размеру типа, на который указывает P. Если памяти недостаточно, чтобы распределить динамическую переменную, возникает исключительная ситуация EOutOfMemory.

При завершении программы, все динамические переменные, созданные процедурами New или GetMem, должны быть уничтожены соответственно процедурами Dispose или FreeMem.


Untitled

Процедуры управления программой

Модуль System

Procedure Break;

Процедура осуществляет досрочный выход из циклов For, While или Repeat.

Процедура должна находиться внутри этих циклов, иначе транслятор сообщит об ошибке.

Procedure Continue;

Процедура досрочно начинает следующую итерацию циклов For, While или Repeat.

Процедура должна находиться внутри этих циклов, иначе транслятор сообщит об ошибке.

Procedure Exit;

Осуществляет немедленный выход из текущей подпрограммы. Если текущей подпрограммой является главная программа, она завершает работу.

Procedure Halt[(Exitcode: Integer)];

Процедура выполняет аварийное завершение программы. Чтобы нормально завершить приложение, вызовите Application.Terminate. Exitcode - необязательное выражение, которое определяет код завершения вашей программы.

Пример:

   if 1 = 1 then

     begin

       if 2 = 2 then

         begin

           if 3 = 3 then

             begin

               Halt(1); { Программа остановится здесь! }

             end;

         end;

     end;

   Canvas.TextOut(10, 10, 'Это не будет выполнено');

 end;


Untitled

Процедуры и функции работы с файлами

Модуль System

Procedure AssignFile(var F; FileName: String);

Для избежания конфликтов, AssignFile заменяет стандартную процедуру Assign в Delphi. Однако для обратной совместимости вы можете использовать Assign. Процедура AssignFile сопоставляет имя внешнего файла с файловой переменной.

F - файловая переменная любого файлового типа, FileName - выражение типа String или выражение типа PChar, если допускается расширенный синтаксис. Все дальнейшие операции с F производятся с внешним файлом.

Не используйте AssignFile с уже открытой файловой переменной.

Procedure CloseFile(var F);

Из-за конфликтов наименования, процедура CloseFile заменяет стандартную процедуру Close из Borland Pascal. Используйте CloseFile чтобы разорвать связь между файловой переменной и внешним дисковым файлом и закрыть файл.

F - файловая переменная любого файлового типа, открытая процедурами Reset, Rewrite или Append. Внешний файл, связанный с F, полностью модифицируется и затем закрывается, освобождая дескриптор файла для повторного использования.

Директива {$I+} позволяет вам обрабатывать ошибки во время выполнения программы, используя обработку исключительных ситуаций.

При выключенной директиве {$I-}, вы должны использовать IOResult для проверки ошибок ввода - вывода.

Function Eof(var F): Boolean;

(типизированные или нетипизированные файлы)

Function Eof[(var F: Text)]: Boolean;

(текстовые файлы)

Проверяет, является или нет текущая позиция файла концом файла.

Eof(F) возвращает True, если текущая позиция файла находится за последним символом файла или если файл пуст; иначе, Eof (F) возвращает False.

Директива {$I+} позволяет вам обрабатывать ошибки во время выполнения программы, используя обработку исключительных ситуаций.

При выключенной директиве {$I-}, вы должны использовать IOResult для проверки ошибок ввода - вывода.

Procedure Erase(var F);

Удаляет внешний файл, связанный с F.

F - файловая переменная любого файлового типа.

Перед вызовом процедуры Erase, файл необходимо закрыть.

Директива {$I+} позволяет вам обрабатывать ошибки во время выполнения программы, используя обработку исключительных ситуаций.

При выключенной директиве {$I-}, вы должны использовать IOResult для проверки ошибок ввода - вывода.

Function FileSize(var F): Integer;

Возвращает размер в байтах файла F. Однако, если F - типизированный файл, FileSize возвратит число записей в файле.

Перед использованием функции FileSize, файл должен быть открыт.

Если файл пуст, FileSize(F) возвращает 0.

F - переменная любого файлового типа.

Function FilePos(var F): LongInt;

Возвращает текущую позицию файла внутри файла.

Перед использованием функции FilePos, файл должен быть открыт. Функция FilePos не используется с текстовыми файлами.

F - переменная любого файлового типа, кроме типа Text.

Procedure Reset(var F [: File; RecSize: Word]);

Открывает существующий файл.

F - переменная любого файлового типа, связанного с внешним файлом с помощью AssignFile. RecSize - необязательное выражение, которое используется, если F - нетипизированный файл. Если F - нетипизированный файл, RecSize определяет размер записи, который используется при передаче данных. Если RecSize опущен, заданный по умолчанию размер записи равен 128 байт.

Процедура Reset открывает существующий внешний файл, ассоциированный с файловой переменной F. Если внешнего файла с таким именем нет, возникает ошибка времени выполнения. Если файл, связанный с F уже открыт, он сначала закрывается и затем вновь открывается. Текущая позиция файла устанавливается к началу файла.

Procedure Rewrite(var F: File [; Recsize: Word]);

Создает и открывает новый файл.

F - переменная любого файлового типа, связанного с внешним файлом с использованием AssignFile.RecSize - необязательное выражение, которое используется, если F - нетипизированный файл. Если F - нетипизированный файл, RecSize определяет размер записи, который используется при передаче данных. Если RecSize опущен, заданный по умолчанию размер записи равен 128 байт.

Процедура Rewrite создает новый внешний файл с именем, связанным с F.

Если внешний файл с тем же самым именем уже существует, он удаляется, и создается новый пустой файл.

Procedure Seek(var F; N: LongInt);

Перемещает текущую позицию файла к определенному компоненту. Вы можете использовать процедуру только с открытыми типизированными или нетипизированными файлами.

Текущая позиция файла F перемещается к номеру N. Номер первого компонента файла - 0.

Инструкция Seek(F, FileSize(F)) перемещает текущую позицию файла в конец файла.

Procedure Append(var F: Text);

Открывает существующий текстовый файл для добавления информации в конец файла (дозаписи).

Если внешнего файла с данным именем не существует, происходит ошибка времени выполнения.

Если файл F уже открыт, он закрывается и вновь открывается. Текущая позиция файла устанавливается к концу файла.

Function Eoln[(var F: Text)]: Boolean;

Проверяет, является ли текущая позиция файла концом строки текстового файла.

Eoln(F) возвращает True, если текущая позиция файла - в конце строки или файла; иначе Eoln(F) возвращает False.

Procedure Read(F, V1 [, V2, ..., Vn]);

(типизированные и нетипизированные файлы)

Procedure Read([var F: Text;] V1 [, V2, ..., Vn]);

(текстовые файлы)

Для типизированных файлов процедура читает компонент файла в переменную. При каждом считывании текущая позиция в файле продвигается к следующему элементу.

Для текстовых файлов читается одно или несколько значений в одну или несколько переменных.

С переменными типа String Read считывает все символы вплоть до следующей метки конца строки (но не включая ее), или пока функция Eof(F) не примет значение True. Переменной присваивается получившаяся в результате символьная строка.

В случае переменной целого или вещественного типа процедура ожидает поступления последовательности символов, образующих число по правилам синтаксиса языка Object Pascal. Считывание прекращается при обнаружении первого пробела, символа табуляции или метки конца строки, или в том случае, если функция Eof(F) принимает значение True. Если числовая строка не соответствует ожидаемому формату, то происходит ошибка ввода-вывода.

Procedure Readln([var F: Text;] V1 [, V2, ..., Vn]);

Является расширением процедуры Read и определена для текстовых файлов. Считывает строку символов в файле (включая маркер конца строки) и переходит к началу следующей строки. Вызов функции Readln(F) без параметров приводит к перемещению текущей позиции файла на начало следующей строки, если она имеется, в противном случае происходит переход к концу файла.

Function SeekEof[(var F: Text)]: Boolean;

Возвращает признак конца файла и может использоваться только для открытых текстовых файлов. Обычно применяется для считывания числовых значений из текстовых файлов.

Function SeekEoln[(var F: Text)]: Boolean;

Возвращает признак конца строки в файле и может использоваться только для открытых текстовых файлов. Обычно применяется для считывания числовых значений из текстовых файлов.

Procedure Write([var F: Text;] P1 [, P2, ..., Pn]);

(текстовые файлы)

Записывает одну или более величин в текстовый файл.

Каждый параметр записи должен иметь тип Char, один из целочисленных типов (Byte, ShortInt, Word, LongInt, Cardinal), один из типов с плавающей запятой (Single, Real, Double, Extended, Currency), один из строковых типов (PChar, AnsiString, ShortString), или одного из логических типов (Boolean, Bool).

Procedure Write(F, V1, ..., Vn);

(типизированные файлы)

Записывает переменную в компонент файла. Переменные V1,...,Vn должны быть того же типа, что и элементы файла. При каждой записи переменной текущая позиция в файле передвигается к следующему элементу.

Procedure Writeln([var F: Text;] P1 [, P2, ..., Pn]);

(текстовые файлы)

Выполняет операцию Write, затем помещает метку конца строки в файл.

Вызов Writeln(F) без параметров записывает в файл маркер конца строки.

Файл должен быть открыт для вывода.


Untitled

Процедуры работы с множествами

Модуль System

Procedure Exclude(var S: Set of T; I:T);

Удаляет элемент I из множества S.

S - переменная типа-множество, и I - выражение типа, совместимого с исходным типом S.

Конструкция Exclude(S, I) соответствует S: = S - [I], но генерирует более эффективный код.

Procedure Include(var S: Set of T; I:T);

Добавляет элемент I к множеству S.

S - переменная типа-множество, и I - выражение типа, совместимого с типом S.

Конструкция Include(S, I) соответствует S: = S + [I], но генерирует более эффективный код.


Untitled

Процедуры и функции работы с порядковыми величинами

Модуль System

Procedure Dec(var X [; N: LongInt]);

Вычитает единицу или N из переменной X.

Dec(X) соответствует X := X - 1, и Dec(X, N) соответствует X := X - N.

X - переменная перечислимого типа или типа PChar, если допускается расширенный синтаксис, и N - выражение с целочисленного типа.

Процедура Dec генерирует оптимальный код и особенно полезна в длительных циклах.

Procedure Inc(var X [; N: LongInt]);

Прибавляет единицу или N к переменной X.

X - переменная перечислимого типа или типа PChar, если допускается расширенный синтаксис, и N - выражение целочисленного типа.

Inc (X) соответствует инструкции X := X + 1, и Inc (X, N) соответствует инструкции X := X + N.

Процедура Inc генерирует оптимальный код и особенно полезна в длительных циклах.

Function Odd(X: LongInt): Boolean;

Возвращает True, если X - нечетное число и False в противном случае.

Function Pred(X);

Возвращает предыдущее значение параметра.

X - выражение перечислимого типа. Результат того же самого типа.

Function Succ(X);

Возвращает следующее значение параметра.

X - выражение перечислимого типа. Результат того же самого типа.


Untitled

Процедуры и функции преобразования величин

Модуль System

Procedure Str(X [: Width [: Decimals]]; var S);

Преобразовывает число X в строковое представление согласно Width и параметрам форматирования Decimals.

X - выражение вещественного или целого типа. Width и Decimals - выражения целого типа. S - переменная типа String или символьный массив с нулевым окончанием, если допускается расширенный синтаксис.

Function Chr(X: Byte): Char;

Возвращает символ с порядковым номером Х в ASCII-таблице.

Function High(X);

Возвращает наибольшее значение в диапазоне параметра.

Function Low(X);

Возвращает наименьшее значение в диапазоне параметра.

Function Ord(X): LongInt;

Возвращает порядковое значение выражения перечислимого типа.

X - выражение перечислимого типа.

Function Round(X: Extended): LongInt;

Округляет значение вещественного типа до целого.

X - выражение вещественного типа. Round возвращает значение Longint, которое является значением X, округленным до ближайшего целого числа. Если X - точно посередине между двумя целыми числами, возвращается число с наибольшей абсолютной величиной.

Если округленное значение X выходит за диапазон LongInt, генерируется ошибка времени выполнения программы, которую вы можете обработать с использованием исключительной ситуации EInvalidOp .

Function Trunc(X: Extended): LongInt;

Усекает значение вещественного типа до целого.

Если округленное значение X выходит за диапазон LongInt, генерируется ошибка времени выполнения программы, которую вы можете обработать с использованием исключительной ситуации EInvalidOp .

Procedure Val(S; var V; var Code: Integer);

Преобразовывает число из строкового значения S в числовое представление V.

S - выражение строкового типа - последовательность символов, которая формирует целое или вещественное число.

Если выражение S недопустимо, индекс неверного символа сохраняется в переменной Code. В противном случае Code устанавливается в ноль


Untitled

Процедуры и функции работы с датой и временем

Модуль SysUtils

Function Date: TDateTime;

Возвращает текущую дату.

Function DateTimeToStr(DateTime: TDateTime): String;

Преобразовывает параметр типа TDateTime, содержащий дату и время, в строку. Если параметр DateTime не содержит значения даты, дата устанавливается в 00/00/00. Если параметр DateTime не содержит значения времени, время устанавливается в 00:00:00.

Function DateToStr(Date: TDateTime): String;

Преобразовывает параметр типа TDateTime, содержащий дату, в строку.

Преобразование использует формат, определенный глобальной переменной ShortDateFormat.

Function DayOfWeek(Date: TDateTime): Integer;

Возвращает день недели определенной даты как целое число между 1 и 7. Воскресенье - первый день недели, и суббота - седьмой.

Procedure DecodeDate(Date: TDateTime; var Year, Month, Day: Word);

Извлекает дату из формата TDateTime и помещает год, месяц и день соответственно в переменные Year, Month и Day.

Procedure DecodeTime(Time: TDateTime; var Hour, Min, Sec, MSec: Word);

Извлекает время из формата TDateTime и помещает часы, минуты, секунды, и миллисекунды соответственно в переменные Hour, Min, Sec и MSec.

Function EncodeDate(Year, Moth, Day: Word): TDateTime;

Возвращает дату в формате TDateTime, полученную из значений параметров Year, Month и Day.

Year должен быть между 1 и 9999.

Допустимые значения Month - от 1 до 12.

Допустимые значения Day - от 1 до 28, 29, 30, или 31, в зависимости от значения Month. Например, возможные значения Day в течение второго месяца (февраля) - от 1 до 28, или от 1 до 29, в зависимости от того, определяет или нет значение Year високосный год.

Если значения находятся вне диапазона, возникает исключительная ситуация EConvertError. Возникающее в результате значение - единица плюс число дней между 30/12/1899 и данной датой.

Function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTime;

Возвращает время в формате TDateTime, полученное из значений параметров Hour, Min, Sec и MSec.

Если значение типизированной константы Time24Hour ложно (False), значения Hour лежат в пределах от 0 до 12, если истинно (True) - от 0 до 23.

Допустимые значения Min и Sec - от 0 до 59. Допустимые значения MSec - от 0 до 999.

Если значения находятся вне диапазона, возникает исключительная ситуация EConvertError.

Function Now: TDateTime;

Возвращает текущую дату и время в формате TDateTime (дата + время).

Function StrToDate(const S: String): TDateTime;

Преобразовывает строковое представление даты в формат TDateTime. Дата в параметре S должна быть записана по правилам Windows, т.е. должна состоять из двух или трех чисел, отделяемых символом, определенным в глобальной переменной DateSeparator. Порядок следования месяца, дня и года определен в глобальной переменной ShortDateFormat. Возможные комбинации - m/d/y, d/m/y, и y/m/d.

Если строка содержит только два числа, это интерпретируется как дата (m/d или d/m) в текущем году. Значения Year между 0 и 99 означают год в текущем столетии.

Если строка содержит дату в неверном формате, возбуждается исключительная ситуация EConvertError.

Function StrToTime(Const S: String): TDateTime;

Преобразовывает строковое представление времени в формат TDateTime.

Строка должна состоять из двух или трех чисел, отделяемых символом, определенным в глобальной переменной TimeSeparator. Числа представляют собой часы, минуты, и (необязательно) секунды, в соответствующем порядке.

Если строка содержит время в неверном формате, возбуждается исключительная ситуация EConvertError.

Function Time: TDateTime;

Возвращает текущее время.

Function TimeToStr(Time: TDateTime): String;

Преобразовывает время из формата TDateTime в строковое представление.

Преобразование использует формат, определенный в глобальной переменной LongTimeFormat. Вы можете изменять формат отображения времени, изменяя значения переменных даты и времени (или используя Панель управления Windows).


Untitled

Функции форматирования

Модуль SysUtils

Function FloatToStrF(Value: Extended; Format: TFloatFormat; Precision,  Digits: Integer): String;

Преобразовывает число с плавающей запятой, в строковое представление.

Параметр Format определяет формат результирующей строки.

Параметр Precision определяет точность данного значения. Он должен быть равен 7 или меньше для значений типа Single, 15 или меньше для значений типа Double и 18 или меньше для значений типа Extended.

Значение параметра Digit зависит от выбранного формата.

Возможные значения параметра Format описаны ниже.

ffGeneral - общий формат числа. Значение преобразовывается в самую короткую возможную десятичную строку, использующую фиксированный или научный формат. Конечные нули удаляются, а десятичная точка появляется только в случае необходимости. Параметр Digit определяет минимальное число цифр в порядке числа (между 0 и 4).

ffExponent - научный формат. Значение преобразовывается в строку вида «-d. ddd ... E + dddd». Общее количество цифр в результирующей строке (включая одну перед десятичной точкой) определяется параметром Precision. Параметр Digit определяет минимальное число цифр в порядке числа (между 0 и 4).

ffFixed - фиксированный формат. Значение преобразовывается в строку вида «-ddd. ddd ...». Число цифр после десятичной точки определяется параметром Digit и должно быть не больше 18. Если число цифр слева от десятичной точки больше, чем определено в параметре Precision, используется научный формат.

ffNumber - числовой формат. Значение преобразовывается в строку вида «-d, ddd, ddd.ddd ...». Формат ffNumber соответствует формату ffFixed, за исключением того, что возникающая в результате строка содержит разделитель тысяч.

ffCurrency - денежный формат. Значение преобразовывается в строку, которая представляет собой денежную величину. Преобразование управляется глобальными переменными CurrencyString, CurrencyFormat, NegCurrFormat, ThousandSeparator и DecimalSeparator, которые определяются соответствующими значениями раздела «Язык и стандарты» Панели управления Windows. Число цифр после десятичной точки определяется параметром Digits и должно быть не больше 18.

Если параметр Value принимает бесконечное значение (с плюсом или минусом), функция возвращает соответственно «INF» или «-INF».

Function StrToFloat(const S: String): Extended;

Преобразовывает данную строку в число с плавающей запятой. Строка должна состоять из необязательного знака (+ или -), последовательности цифр с необязательной десятичной точкой и необязательным «E» или «e», сопровождаемым целым числом со знаком.

Глобальная переменная DecimalSeparator определяет символ, который будет использоваться как десятичная точка. Разделитель тысяч и обозначения денежной единицы не допускаются. Если строка имеет неверный формат, возбуждается исключительная ситуация EConvertError.

Function IntToStr(Value: Integer): String;

Преобразовывает целое число в строку.

Function StrToInt(const S: String): Integer;

Преобразовывает строку, представляющую целое число в десятичной или шестнадцатеричной записи, в целое число. Если строка имеет неверный формат, возбуждается исключительная ситуация EConvertError.


Function Abs(X);

Разные процедуры и функции

Модуль System

Function Hi(X): Byte;

Возвращает старший байт X как целое значение без знака. X - выражение типа Integer или Word.

Function Lo(X): Byte;

Возвращает байт младшего разряда параметра X как целое значение без знака. X - выражение типа Integer или Word.

Function Random[(Range: Integer)];

Возвращает случайное число внутри диапазона 0 < = X < Range.

Если параметр Range не определен, возвращается случайное число в диапазоне 0 < = X < 1.

Procedure Randomize;

Инициализирует встроенный генератор случайных чисел произвольным значением (полученным из часов системы).

Function SizeOf(X): Integer;

Возвращает размер объекта X в байтах.

X является переменной или идентификатором типа.

Всегда используйте SizeOf при обращениях к GetMem и т.п.


Function Abs(X);

Процедуры и функции Pascal
  1. Арифметические функции
  2. Строковые процедуры и функции
  3. Процедуры работы с динамической памятью
  4. Процедуры управления программой
  5. Процедуры и функции работы с файлами
  6. Процедуры работы с множествами
  7. Процедуры и функции работы с порядковыми величинами
  8. Процедуры и функции преобразования величин
  9. Процедуры и функции работы с датой и временем
  10. Функции форматирования
  11. Разные процедуры и функции


Untitled

1. Основные понятия алгоритмического языка

СОСТАВ ЯЗЫКА. Обычный разговорный язык состоит из четырех основных

элементов: символов, слов, словосочетаний и предложений. Алгоритми-

ческий язык содержит подобные элементы, только слова называют элемен-

тарными конструкциями, словосочетания-выражениями, предложения-опера-

торами. Символы, элементарные конструкции, выражения и операторы

составляют иерархическую структуру, поскольку элементарные конструк-

ции образуются из последовательности символов, выражения-это последо-

вательность элементарных конструкций и символов, а оператор-последо-

вательность выражений, элементарных конструкций и символов.

ОПИСАНИЕ ЯЗЫКА есть описание четырех названных элементов. Описание

символов заключается в перечислении допустимых символов языка. Под

описанием элементарных конструкций понимают правила их образования.

Описание выражений-это правила образования любых выражений, имеющих

смысл в данном языке. Описание операторов состоит из рассмотрения

всех типов операторов, допустимых в языке. Описание каждого элемента

языка задается его СИНТАКСИСОМ и СЕМАНТИКОЙ. Синтаксические определе-

ния устанавливают правила построения элементов языка. Семантика опре-

деляет смысл и правила использования тех элементов языка, для которых

были даны синтаксические определения.

СИМВОЛЫ языка-это основные неделимые знаки, в терминах которых пи-

шутся все тексты на языке.

ЭЛЕМЕНТАРНЫЕ КОНСТРУКЦИИ -это минимальные единицы языка, имеющие

самостоятельный смысл. Они образуются из основных символов языка.

ВЫРАЖЕНИЕ в алгоритмическом языке состоит из элементарных конс-

трукций и символов, оно задает правило вычисления некоторого значе-

ния.

ОПЕРАТОР задает полное описание некоторого действия, которое необ-

ходимо выполнить. Для описания сложного действия может потребоваться

группа операторов. В этом случае операторы объединяются в СОСТАВНОЙ

ОПЕРАТОР или БЛОК.

Действия, заданные операторами, выполняются над ДАННЫМИ. Предложе-

ния алгоритмического языка, в которых даются сведения о типах данных,

называются ОПИСАНИЯМИ или неисполняемыми операторами.

Объединенная единым алгоритмом совокупность описаний и операторов

образует ПРОГРАММУ на алгоритмическом языке.

В процессе изучения алгоритмического языка необходимо отличать ал-

горитмический язык от того языка, с помощью которого осуществляется

описание изучаемого алгоритмического языка. Обычно изучаемый язык на-

зывают просто языком, а язык, в терминах которого дается описание

изучаемого языка - МЕТАЯЗЫКОМ.

Синтаксические определения могут быть заданы формальными или не-

формальным способами. Существуют три формальных способа:

-металингвистическая символика, называемая Бэкуса-Наура формулами;

-синтаксические диаграммы;

-скобочные конструкции.

Мы в последующем изложении будем пользоваться неформальным способом.


Untitled

2. Основные символы

Основные символы языка-буквы, цифры и специальные символы-состав-

ляют его алфавит. ТУРБО ПАСКАЛЬ включает следующий набор основных

символов:

1) 26 латинских строчных и 26 латинских прописных букв:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

a b c d e f g h i j k l m n o p q r s t u v w x y z

2) _ подчеркивание

3) 10 цифр:

0 1 2 3 4 5 6 7 8 9

4) знаки операций:

+ - * / = <> < > <= >= := @

5) ограничители:

. , ' ( ) [ ] (. .) { } (* *) .. : ;

6) спецификаторы:

^ # $

7) служебные (зарезервированные) слова:

ABSOLUTE EXPORTS LIBRARY SET

ASSEMBLER EXTERNAL MOD SHL

AND FAR NAME SHR

ARRAY FILE NIL STRING

ASM FOR NEAR THEN

ASSEMBLER FORWARD NOT TO

BEGIN FUNCTION OBJECT TYPE

CASE GOTO OF UNIT

CONST IF OR UNTIL

CONSTRUCTOR IMPLEMENTATION PACKED USES

DESTRUCTOR IN PRIVATE VAR

DIV INDEX PROCEDURE VIRTUAL

DO INHERITED PROGRAM WHILE

DOWNTO INLINE PUBLIC WITH

ELSE INTERFACE RECORD XOR

END INTERRUPT REPEAT

EXPORT LABEL RESIDENT

Кроме перечисленных, в набор основных символов входит пробел. Про-

белы нельзя использовать внутри сдвоенных символов и зарезервирован-

ных слов.


Untitled

3. Элементарные конструкции

Элементарные конструкции языка ПАСКАЛЬ включают в себя имена, чис-

ла и строки.

Имена (идентификаторы) называют элементы языка - константы, метки,

типы, переменные, процедуры, функции, модули, объекты. Имя - это пос-

ледовательность букв и цифр, начинающаяся с буквы. В именах может ис-

пользоваться символ _ подчеркивание. Имя может содержать произвольное

количество символов, но значащими являются 63 символа.

Не разрешается в языке ПАСКАЛЬ использовать в качестве имен слу-

жебные слова и стандартные имена, которыми названы стандартные конс-

танты, типы, процедуры, функции и файлы.

Для улучшения наглядности программы в нее могут вставляться пробе-

лы. По крайней мере один пробел требуется вставить между двумя

последовательными именами, числами или служебными и стандартными име-

нами. Пробелы нельзя использовать внутри имен и чисел.

Примеры имен языка ПАСКАЛЬ:

A b12 r1m SIGMA gamma I80_86

Числа в языке ПАСКАЛЬ обычно записываются в десятичной системе

счисления. Они могут быть целыми и действительными. Положительный

знак числа может быть опущен. Целые числа записываются в форме без

десятичной точки, например:

217 -45 8954 +483

Действительные числа записываются в форме с десятичной точкой или

в форме с использованием десятичного порядка, который изображается

буквой Е:

28.6 0.65 -0.018 4.0 5Е12 -1.72Е9 73.1Е-16

ПАСКАЛЬ допускает запись целых чисел и фрагментов действительных

чисел в форме с порядком в шестнадцатиричной системе счисления:

$7F $40 $ABC0

Строки в языке ПАСКАЛЬ - это последовательность символов, записан-

ная между апострофами. Если в строке в качестве содержательного сим-

вола необходимо употребить сам апостроф, то следует записать два

апострофа. Примеры строк:

'СТРОКА' 'STRING' 'ПРОГРАММА' 'АД''ЮТАНТ'


Untitled

4. Концепция типа для данных

В математике принято классифицировать переменные в соответствии с

некоторыми важными характеристиками. Производится строгое разграниче-

ние между вещественными, комплексными и логическими переменными, меж-

ду переменными, представляющими отдельные значения и множество значе-

ний и так далее.

При обработке данных на ЭВМ такая классификация еще более важна. В

любом алгоритмическом языке каждая константа, переменная, выражение

или функция бывают определенного типа.

В языке ПАСКАЛЬ существует правило: тип явно задается в описании

переменной или функции, которое предшествует их использованию. Кон-

цепция типа языка ПАСКАЛЬ имеет следующие основные свойства:

-любой тип данных определяет множество значений, к которому при-

надлежит константа, которые может принимать переменная или выражение,

или вырабатывать операция или функция;

-тип значения, задаваемого константой, переменной или выражением,

можно определить по их виду или описанию;

-каждая операция или функция требует аргументов фиксированного ти-

па и выдает результат фиксированного типа.

Отсюда следует, что транслятор может использовать информацию о ти-

пах для проверки вычислимости и правильности различных конструкций.

Тип определяет:

-возможные значения переменных, констант, функций, выражений, при-

надлежащих к данному типу;

-внутреннюю форму представления данных в ЭВМ;

-операции и функции, которые могут выполняться над величинами,

принадлежащими к данному типу.

Обязательное описание типа приводит к избыточности в тексте прог-

рамм, но такая избыточность является важным вспомогательным средс-

твом разработки программ и рассматривается как необходимое свойство

современных алгоритмических языков высокого уровня. В языке ПАСКАЛЬ

существуют скалярные и структурированные типы данных.

К cкалярным типам относятся стандартные типы и типы, определяе-

мые пользователем.

Стандартные типы включают целые, действительные, символьный,

логические и адресный типы. Типы, определяемые пользователем, - пере-

числяемый и интервальный.

Структурированные типы имеют четыре разновидности: массивы, мно-

жества, записи и файлы.

Кроме перечисленных, TURBO PASCAL включает еще два типа - проце-

дурный и объектный.

Из группы скалярных типов можно выделить порядковые типы,

которые характеризуются следующими свойствами:

-все возможные значения порядкового типа представляют собой

ограниченное упорядоченное множество;

-к любому порядковому типу может быть применена стандартная

функция Ord, которая в качестве результата возвращает порядко-

вый номер конкретного значения в данном типе;

-к любому порядковому типу могут быть применены стандартные

функции Pred и Succ, которые возвращают предыдущее и последую-

щее значения соответственно;

-к любому порядковому типу могут быть применены стандартные функ-

ции Low и High, которые возвращают наименьшее и наибольшее значения

величин данного типа.

В языке ПАСКАЛЬ введены понятия эквивалентности и совместимости типов.

Два типа Т1 и Т2 являются эквивалентными (идентичными), если выполняется

одно из двух условий:

-Т1 и Т2 представляют собой одно и то же имя типа;

-тип Т2 описан с использованием типа Т1 с помощью равенства или

последовательности равенств. Например:

type

T1 = Integer;

T2 = T1;

T3 = T2;

Менее строгие ограничения определены совместимостью типов. Напри-

мер, типы являются совместимыми, если:

-они эквивалентны;

-являются оба либо целыми, либо действительными;

-один тип - интервальный, другой - его базовый;

-оба интервальные с общим базовым;

один тип - строковый, другой - символьный.

В ТУРБО ПАСКАЛЬ ограничения на совместимость типов можно обойти с

помощью приведения типов. Приведение типов позволяет рассматривать

одну и ту же величину в памяти ЭВМ как принадлежащую разным типам.

Для этого используется конструкция

Имя_Типа(переменная или значение).

Напрмер,

Integer('Z')

представляет собой значение кода символа 'Z' в двухбайтном представ-

лении целого числа, а

Byte(534)

даст значение 22, поскольку целое число 534 имеет тип Word и занима-

ет два байта, а тип Byte занимает один байт, и в процессе приведения

старший байт будет отброшен.


Untitled

5. Стандартные типы данных

К стандартным относятся целые, действительные, символьный и адресный типы.

Целые типы определяют константы,переменные и функции,значения

которых реализуются множеством целых чисел, допустимых в данной ЭВМ.

тип диапазон значений требуемая память

__________________________________________________________

Shortint -128 .. 127 1 байт

Integer -32768 .. 32767 2 байта

Longint -2147483648 .. 2147483647 4 байта

Byte 0 .. 255 1 байт

Word 0 .. 65535 2 байта

__________________________________________________________

Над целыми операндами можно выполнятьследующие арифметические

операции: сложение,вычитание, умножение, деление, получение остатка

от деления. Знаки этих операций:

+ - * div mod

Результат арифметической операции над целыми операндами есть вели-

чина целого типа. Результат выполнения операции деления целых величин

есть целая часть частного.Результат выполненияоперации получения

остатка от деления - остаток от деления целых. Например:

17 div 2 = 8, 3 div 5 = 0.

17 mod 2 = 1, 3 mod 5 = 3.

Операции отношения, примененные к целым операндам, дают результат

логического типа TRUE или FALSE ( истинаили ложь ).

В языке ПАСКАЛЬ имеются следующие операции отношения: равенство =,

неравенство <>, больше или равно >=, меньше или равно <=,больше >,

меньше < .

К аргументам целого типа применимы следующие стандартные (встроен-

ные) функции, результат выполнения которых имеет целый тип:

Abs(X), Sqr(X), Succ(X), Pred(X),

и которые определяют соответственно абсолютное значение Х, Х в квад-

рате, Х+1, Х-1.

Следующая группа стандартных функций для аргумента целого типа да-

ет действительный результат:

Sin(X), Cos(X), ArcTan(X), Ln(X), Exp(X), Sqrt(X).

Эти функции вычисляют синус, косинус и арктангенс угла, заданного

в радианах,логарифм натуральный, экспоненту и корень квадратный со-

ответственно.

Результат выполнения функции проверки целой величины на нечетность

Odd(X) имеет значение истина, если аргумент нечетный, и значение

ложь, если аргумент четный:

X=5 Odd(X)=TRUE , X=4 Odd(X)=FALSE.

Для быстрой работы с целыми числами определены процедуры:

Inc(X) X:=X+1

Inc(X,N) X:=X+N

Dec(X) X:=X-1

Dec(X,N) X:=X-N

Действительные типы определяет теданные, которые реализуются

подмножеством действительных чисел, допустимых в данной ЭВМ.

Тип Диапазон Количество цифр Требуемая

значений мантиссы память (байт)

---------------------------------------------------------------

Real 2.9e-39 .. 1.7e+38 11 6

Single 1.5e-45 .. 3.4e+38 7 4

Double 5.0e-324 .. 1.7e+308 15 8

Extended 3.4e-4932 .. 1.1e+4932 19 10

Comp -9.2e+18 .. 9.2e+18 19 8

---------------------------------------------------------------

Тип Real определен в стандартном ПАСКАЛЕ иматематическим сопро-

цессором не поддерживается.

Остальные действительные типы определены стандартом IEEE 457 и ре-

ализованы на всех современных компьютерах.

Для их использования при наличии сопроцессора илипри работе на

ЭВМ типа 80486 необходимо компилировать программу с ключом {$ N+}, а

при отсутствии сопроцессора - с ключами {$N-,E+}.

Тип Comp хотяи относится к действительным типам,хранит только

длинные целые значения.

Над действительными операндами можно выполнять следующие арифмети-

ческие операции, дающие действительный результат:

сложение + , вычитание - ,умножение * ,деление / .

К величинам действительного типа применимы все операции отношения,

дающие булевский результат.

Один из операндов, участвующих в этих операциях, может быть целым.

К действительным аргументам применимы функции, дающие действитель-

ный результат:

Abs(X), Sqr(X), Sin(X), Cos(X), ArcTan(X), Ln(X),Exp(X),

Sqrt(X), Frac(X), Int(X), Pi.

Функция Frac(X) возвращает дробную часть X, функция Int(X) - целую

часть X.

Безаргументная функция Pi возвращаетзначение числа Пи действи-

тельного типа.

К аргументам действительного типа применимы также функции

Trunc(X) и Round(X),

дающие целыйрезультат. Первая из них выделяет целую часть действи-

тельного аргумента путемотсечения дробной части, вторая округляет

аргумент до ближайшего целого.

ЛОГИЧЕСКИЙ тип (Boolean) определяет те данные, которые могут при-

нимать логические значения TRUE и FALSE.

К булевским операндам применимы следующие логические операции:

not and or xor.

Логический тип определен таким образом, что FALSE < TRUE. Это поз-

воляет применять к булевским операндам все операции отношения.

В ТУРБО ПАСКАЛЬ введены еще разновидности логического типа:

ByteBool, WordBool и LongBool, которые занимают в памяти ЭВМ один, два

и четыре байта соответственно.

Символьный тип (Char) определяет упорядоченную совокупность симво-

лов, допустимых в данной ЭВМ. Значение символьной переменной или

константы - это один символ из допустимого набора.

Символьная константа может записываться в тексте программы тремя

способами:

-как один символ, заключенный в апострофы, например:

'A' 'a' 'Ю' 'ю';

-с помощью конструкции вида #K, где K - код соответствущего симво-

ла, при этом значение K должно находиться в пределах 0..255;

-с помощью конструкции вида ^C, где C - код соответствущего управ-

ляющего символа, при этом значениеC должно быть на 64 больше

кода управляющего символа.

К величинам символьного типа применимы все операции отношения.

Для величин символьного типа определены две функции преобразования

Ord(C) Chr(K).

Первая функция определяет порядковый номер символа С в наборе сим-

волов, вторая определяет по порядковому номеру К символ, стоящий на

К-ом месте в наборе символов. Порядковый номер имеет целый тип.

К аргументам символьного типа применяются функции, которые опреде-

ляют предыдущий и последующий символы:

Pred(C) Succ(C). Pred('F') = 'E' ; Succ('Y') = 'Z' .

При отсутствии предыдущего или последующего символов значение со-

ответствующих функций не определено.

Для литер из интервала 'a'..'z' применима функция UpCase(C), кото-

рая переводит эти литеры в верхний регистр 'A'..'Z'.

Адресный тип (Pointer) определяет переменные, которые могут содер-

жать значенияадресов данных или фрагментов программы.Для хранения

адреса требуются два слова (4 байта), одно из них определяет сегмент,

второе - смещение.

Работа с адресными переменными (указателями) будетрассмотрена

позже, сейчасотметим, что для получения значения адреса какой-либо

переменной введена унарная операция @.


Untitled

Константы

Тип констант в языке ПАСКАЛЬ определяется по их виду: константы

целого типа - это целые числа, не содержащие десятичной точки, конс-

танты действительного типа - действительные числа, логические конс-

танты - логические значения TRUE и FALSE, символьные константы - либо

строки длиной в один символ, либо конструкции вида #K или ^K.

Язык ПАСКАЛЬ допускает использовать синонимы для обозначения конс-

тант, в этом случае текст программы содержит раздел описания

констант, например: {}


Untitled

7. Переменные. Инициализация переменных.

Тип переменных определяется пользователем в разделе описания пере-

менных:{}

В настоящее время в профессиональном программировании принято

записывать имена переменных с использованием так называемой венгерс-

кой нотации.

Венгерская нотация - это соглашение о наименованиях переменных и

функций. Соглашение широко используется при программировании на язы-

ках PASCAL, C и в среде WINDOWS.

Венгерская нотация основывается на следующих принципах:

-имена переменных и функций должны содержать префикс, описывающий

их тип;

-имена переменных и функций записываются полными словами или сло-

восочетаниями или их сокращениями, но так, чтобы по имени можно было

понять назначение переменной или действие, выполняемое функцией.

Префиксы записываются малыми буквами, первая буква каждого слова -

заглавная, префиксы и слова записываются либо слитно, либо через сим-

вол _ (подчеркивание).

Для языка PASCAL могут быть рекомендованы следующие префиксы для

скалярных переменных и функций:

Префикс Тип

---------------------

by Byte

sh Shortint

i Integer

w Word

l Longint

r Real

si Single

d Double

e Extended

c Comp

ch Char

b Boolean

p Pointer

x,у координаты символа или точки на экране

Для величин структурированного типа могут быть использованы следу-

ющие префиксы:

a Array

s String

sz Stringz

se Set

re Record

f File

t Text

Например:

rV, arVector[1..20], sName, iCount.

В откомпилированной программе для всех переменных отведено место в

памяти, и всем переменным присвоены нулевые значения.

Для задания начальных значений переменным (инициализации перемен-

ных) TURBO PASCAL позволяет присваивать начальные значения переменным

одновременно с их описанием. Для этого используется конструкция

имя переменной: тип = значение;

которая должна быть размещена в разделе описания констант, например:

const rWeight: Real = 0.4;


Untitled

8. Выражения

Выражение состоит из констант, переменных, указателей функций,

знаков операций и скобок. Выражение задает правило вычисления некото-

рого значения. Порядок вычисления определяется старшинством (приори-

тетом) содержащихся в нем операций. В языке ПАСКАЛЬ принят следующий

приоритет операций:

1. унарная операция not, унарный минус -, взятие адреса @

2. операции типа умножения * / div mod and shl shr

3. операции типа сложения + - or xor

4. операции отношения = <> < > <= >= in

{}

Выражения входят в состав многих операторов языка ПАСКАЛЬ, а также

могут быть аргументами встроенных функций.


Untitled

9. Оператор присваивания

Тип переменной и тип выражения должны совпадать кроме случая, когда выражение относится к целому типу, а переменная - к действительно-

му. При этом происходит преобразование значения выражения к

действительному типу.{}


Untitled

10. Операторы ввода и вывода

Рассмотрим организацию ввода и вывода данных с терминального уст-

ройства. Терминальное устройство - это устройство, с которым работает

пользователь, обычно это экран (дисплей) и клавиатура.

Для ввода и вывода данных используются стандартные процедуры ввода

и вывода Read и Write, оперирующие стандартными последовательными файлами

INPUT и OUTPUT.

Эти файлы разбиваются на строки переменной длины, отделяемые друг

от друга признаком конца строки. Конец строки задается нажатием кла-

виши ENTER.

Для ввода исходных данных используются операторы процедур ввода:

Read(A1,A2,...AK);

ReadLn(A1,A2,...AK);

ReadLn;

Первый из них реализует чтение К значений исходных данных и прис-

ваивание этих значений переменным А1, А2, ..., АК. Второй оператор

реализует чтение К значений исходных данных, пропуск остальных значе-

ний до начала следующей строки, присваивание считанных значений пере-

менным А1, А2, ..., АК. Третий оператор реализует пропуск строки ис-

ходных данных.

При вводе исходных данных происходит преобразование из внешней

формы представления во внутреннюю, определяемую типом переменных. Пе-

ременные, образующие список ввода, могут принадлежать либо к целому,

либо к действительному, либо к символьному типам. Чтение исходных

данных логического типа в языке ПАСКАЛЬ недопустимо.

Операторы ввода при чтении значений переменных целого и

действительного типа пропускает пробелы, предшествующие числу. В то

же время эти операторы не пропускают пробелов, предшествующих значе-

ниям символьных переменных, так как пробелы являются равноправными

символами строк. Пример записи операторов ввода:

var rV, rS: Real;

iW, iJ: Integer;

chC, chD: Char;

................

Read(rV, rS, iW, iJ);

Read(chC, chD);

Значения исходных данных могут отделяться друг от друга пробелами

и нажатием клавиш табуляции и Enter.

Для вывода результатов работы программы на экран используются

операторы:

Write(A1,A2,...AK);

WriteLn(A1,A2,...AK);

WriteLn;

Первый из этих операторов реализует вывод значений переменных А1,

А2,...,АК в строку экрана. Второй оператор реализует вывод

значений переменных А1, А2, ..., АК и переход к началу следующей

строки. Третий оператор реализует пропуск строки и переход к началу

следующей строки.

Переменные, составляющие список вывода, могут относиться к целому,

действительному, символьному или булевскому типам. В качестве элемен-

та списка вывода кроме имен переменных могут использоваться выражения

и строки.

Вывод каждого значения в строку экрана происходит в соот-

ветствии с шириной поля вывода, определяемой конкретной реализацией

языка.

Форма представления значений в поле вывода соответствует типу пе-

ременных и выражений: величины целого типа выводятся как целые деся-

тичные числа, действительного типа - как действительные десятичные

числа с десятичным порядком, символьного типа и строки - в виде сим-

волов, логического типа - в виде логических констант TRUE и FALSE.

Оператор вывода позволяет задать ширину поля вывода для каждого

элемента списка вывода. В этом случае

элемент списка вывода имеет вид А:К, где

А - выражение или строка, К - выражение либо константа целого типа.

Если выводимое значение занимает в поле вывода меньше позиций, чем К,

то перед этим значением располагаются пробелы. Если выводимое значе-

ние не помещается в ширину поля К, то для этого значения будет отве-

дено необходимое количество позиций. Для величин действительного типа

элемент списка вывода может иметь вид А:К:М, где А - переменная или выраже-

ние действительного типа, К - ширина поля вывода, М - число цифр

дробной части выводимого значения. К и М - выражения или константы

целого типа. В этом случае действительные значения выводятся в форме

десятичного числа с фиксированной точкой.

Пример записи операторов вывода:

. . . . . . . . . . . .

var rA, rB: Real; iP,iQ:Integer;

bR, bS: Boolean; chT, chV, chU, chW: Char;

. . . . . . . . . . . .

WriteLn(rA, rB:10:2);

WriteLn(iP, iQ:8);

WriteLn(bR, bS:8);

WriteLn(chT, chV, chU, chW);


Untitled

11. Структрура программы

Программа на языке ПАСКАЛЬ состоит из заголовка, разделов описаний

и раздела операторов.

Заголовок программы содержит имя программы, например:

Program PRIM;

Описания могут включать в себя раздел подключаемых библиотек (мо-

дулей), раздел описания меток, раздел описания констант, раздел опи-

сания типов, раздел описания переменных, раздел описания процедур и

функций.

Раздел описания модулей определяется служебным словом USES и со-

держит имена подключаемых модулей (библиотек) как входящих в состав

системы TURBO PASCAL, так и написанных пользователем. Раздел описания

модулей должен быть первым среди разделов описаний. Имена модулей от-

деляются друг от друга запятыми:

uses CRT, Graph;

Любой оператор в программе может быть помечен меткой. В качестве

метки используются произвольные целые без знака, содержащие не более

четырех цифр, либо имена. Метка ставится перед оператором и отделяет-

ся от него двоеточием. Все метки, используемые в программе, должны

быть перечислены в разделе описания меток, например:

label 3, 471, 29, Quit;

Описание констант позволяет использовать имена как синонимы конс-

тант, их необходимо определить в разделе описаний констант:

const K= 1024; MAX= 16384;

В разделе описания переменных необходимо определить тип всех пере-

менных, используемых в программе:

var P,Q,R: Integer;

A,B: Char;

F1,F2: Boolean;

Описание типов, прцедур и функций будет рассмотрено ниже. Отдель-

ные разделы описаний могут отсутствовать, но следует помнить, что в

ПАСКАЛЬ - программе должны быть обязательно описаны все компоненты

программы.

Раздел операторов представляет собой составной оператор, который

содержит между служебными словами

begin.......end

последовательность операторов. Операторы отделяются друг от друга

символом ;.

Текст программы заканчивается символом точка.

Кроме описаний и операторов ПАСКАЛЬ - программа может содержать

комментарии, которые представляют собой произвольную последователь-

ность символов, расположенную между открывающей скобкой комментариев

{ и закрывающей скобкой комментариев }.

Текст ПАСКАЛЬ - программы может содержать ключи компиляции, кото-

рые позволяют управлять режимом компиляции. Синтаксически ключи ком-

пиляции записываются как комментарии. Ключ компиляции содержит символ

$ и букву-ключ с последующим знаком + (включить режим) или - (выклю-

чить режим). Например:

{$E+} - эмулировать математический сопроцессор;

{$F+} - формировать дальний тип вызова процедур и функций;

{$N+} - использовать математический сопроцессор;

{$R+} - проверять выход за границы диапазонов.

Некоторые ключи компиляции могут содержать параметр, например:

{$I имя файла} - включить в текст компилируемой программы назван-

ный файл.

Пример записи простой программы:

Program TRIANG;

var A, B, C, S, P: Real;

begin

Read(A,B,C);

WriteLn(A,B,C);

P:=(A+B+C)/2;

S:=Sqrt(P*(P-A)*(P-B)*(P-C));

WriteLn('S=',S:8:3)

end.


Untitled

12. Битовая арифметика

Битовая или поразрядная арифметика введена в TURBO PASCAL для

обеспечения возможности работы с двоичными разрядами (битами). Опера-

ции битовой арифметики применимы только к целым типам.

Первая группа операций - логические операции not, and, or и xor.

Операция not является одноместной, она изменяет каждый бит целого

числа на обратный.

Операции and, or и xor - двуместные, операнды этих операций - целые

величины одинаковой длины. Операции выполняются попарно над всеми

двоичными разрядами операндов.

Вторая группа операций - это операции сдвига влево shl и сдвига

вправо shr:

I shl N

I shr N.

Эти операции сдвигают двоичную последовательность значения I влево

или вправо на N двоичных разрядов. При этом биты, уходящие за пределы

разрядной сетки, теряются, а освободившиеся двоичные разряды заполня-

ются нулями. При сдвиге вправо отрицательных значений освободившиеся

разряды заполняются единицами.


Untitled

13. Оператор перехода

Обычно операторы в программе выполняются в том порядке, в каком

они записаны. Оператор перехода прерывает естественный порядок выпол-

нения программы и указывает, что дальнйшее выполнение должно продол-

жаться, начиная с оператора, помеченного меткой, указанной в операто-

ре перехода. Пример записи оператора перехода:


Untitled

14. Элементы программирования

Структуризованная программа (или подпрограмма) - это программа,

составленная из фиксированного множества базовых конструкций. Расс-

мотрим основные определения и способы образования этих конструкций в

схемах алгоритмов.

{}

Из операций, развилок и слияний строятся базовые конструкции: сле-

дование, ветвление, цикл. Применяя только эти три конструкции, можно

реализовать алгоритм решения любой задачи.

Конструкция, представляющая собой последовательное выполнение двух

или более операций, называется следованием.

Конструкция, состоящая из развилки, двух операций и слияния, назы-

вается ветвлением. Одна из операций может отсутствовать.

Конструкция, имеющая линии управления, ведущие к предидущим опера-

циям или развилкам, называется циклом.

Конструкции следование, ветвление и цикл можно представить как

операции, так как они имеют единственный вход и единственный выход.

Произвольную последовательность операций можно представить как одну

операцию.

Операция может быть реализована любым оператором языка ПАСКАЛЬ

(простым или составным), либо группой операторов, за исключением опе-

ратора перехода GOTO.

В языке ПАСКАЛЬ количество базовых конструкций увеличено до шести,

это:

-следование;

-ветвление;

-цикл с предусловием;

-цикл с постусловием;

-цикл с параметром;

-вариант.

Далее рассмотрим, как эти базовые конструкции реализуются в языке

ПАСКАЛЬ.


Untitled

15. Условный оператор

Условный оператор в короткой форме работает по правилу: если бу-

левское выражение B истинно, то выполняется оператор ОР1, далее вы-

полняется оператор, следующий за условным. Если булевское выражение B

ложно, то будет выполняться оператор, следующий за этим условным опе-

ратором.


Untitled

16. Цикл с предусловием {}

17. Цикл с постусловием {}

18. Цикл с параметром {}

19. Операторы завершения цикла

Для всех операторов цикла выход из цикла осуществляется как

вследствие естественного окончания оператора цикла, так и с

помощью операторов перехода и выхода.

В версии ТУРБО ПАСКАЛЬ 7.0 определены стандартные процедуры Break и

Continue. Процедура Break выполняет безусловный выход из цикла. Проце-

дура Continue обеспечивает переход к началу новой итерации цикла.


Untitled

20. Оператор варианта

21. Перечисляемый тип данных

Перечисляемый тип представляет собой ограниченную упорядоченную

последовательность скалярных констант, составляющих данный тип. Зна-

чение каждой константы задается ее именем. Имена отдельных констант

отделяются друг от друга запятыми, а вся совокупность констант, сос-

тавляющих данный перечисляемый тип, заключается в круглые скобки.

Программист объединяет в одну группу в соответствии с каким - либо

признаком всю совокупность значений, составляющих перечисляемый тип.

Например, перечисляемый тип Rainbow(РАДУГА) объединяет скалярные значения

RED, ORANGE, YELLOW, GREEN, LIGHT_BLUE, BLUE, VIOLET (КРАСНЫЙ,

ОРАНЖЕВЫЙ, ЖЕЛТЫЙ, ЗЕЛЕНЫЙ, ГОЛУБОЙ, СИНИЙ, ФИОЛЕТОВЫЙ). Пе-

речисляемый тип Traffic_Light (СВЕТОФОР) объединяет скалярные

значения RED, YELLOW, GREEN (КРАСНЫЙ, ЖЕЛТЫЙ, ЗЕЛЕНЫЙ).

Перечисляемый тип описывается в разделе описания типов, который

начинается со служебного слова type, например:

type

Rainbow = (RED, ORANGE, YELLOW, GREEN, LIGHT_BLUE, BLUE, VIOLET);

Каждое значение является константой своего типа и может принадле-

жать только одному из перечисляемых типов, заданных в программе. Нап-

ример, перечисляемый тип Traffic_Light не может быть определен в одной

программе с типом Rainbow, так как оба типа содержат одинаковые конс-

танты.

Описание переменных, принадлежащих к скалярным типам, которые объ-

явлены в разделе описания типов, производится с помощью имен типов.

Например:

type Traffic_Light= (RED, YELLOW, GREEN);

var Section: Traffic_Light;

Это означает, что переменная Section может принимать значения RED,

YELLOW или GREEN.

Переменные перечисляемого типа могут быть описаны в разделе описа-

ния переменных, например:

var Section: (RED, YELLOW, GREEN);

При этом имена типов отсутствуют, а переменные определяются сово-

купностью значений, составляющих данный перечисляемый тип.

К переменным перечисляемого типа может быть применим оператор

присваивания:

Section:= YELLOW;

Упорядоченная последовательность значений, составляющих перечисля-

емый тип, автоматически нумеруется, начиная с нуля и далее через еди-

ницу. Отсюда следует, что к перечисляемым переменным и константам мо-

гут быть применены операции отношения и стандартные функции Pred,

Succ, Ord.

Переменные и константы перечисляемого типа не могут быть элемента-

ми списка ввода или вывода.


Untitled

22. Интервальный тип данных

Отрезок любого порядкового типа

может быть определен как интервальный или ограниченный

тип. Отрезок задается диапазоном от минимального до максимального

значения констант, разделенных двумя точками. В качестве констант мо-

гут быть использованы константы, принадлежащие к целому, символьному,

логическому или перечисляемому типам. Скалярный тип, на котором стро-

ится отрезок, называется базовым типом.

Минимальное и максимальное значения констант называются нижней и

верхней границами отрезка, определяющего интервальный тип. Нижняя

граница должна быть меньше верхней.

{}

Над переменными, относящимися к интервальному типу, могут выпол-

няться все операции и применяться все стандартные функции, которые

допустимы для соответствующего базового типа.

При использовании в программах интервальных типов данных может осущест-

вляться контроль за тем, чтобы значения переменных не выходили за

границы, введенные для этих переменных в описании интервального типа.


Untitled

23. Массивы

Массивы представляют собой ограниченную упорядоченную совокупность

однотипных величин. Каждая отдельная величина называется компонентой

массива. Тип компонент может быть любым, принятым в языке ПАСКАЛЬ,

кроме файлового типа. Тип компонент называется базовым типом.

Вся совокупность компонент определяется одним именем. Для обозна-

чения отдельных компонент используется конструкция, называемая пере-

менной с индексом или с индексами:

A[5] S[k+1] B[3,5].

В качестве индекса может быть использовано выражение. Тип индексов

может быть только интервальным или перечисляемым. Действительный и

целый типы недопустимы. Индексы интервального типа, для которого ба-

зовым является целый тип, могут принимать отрицательные, нулевое и

положительные значения.{}

В операторной части

программы один массив может быть присвоен другому, если их типы иден-

тичны, например:

R1:=Z.

Для ввода или вывода массива в список ввода или вывода помещается

переменная с индексом, а операторы ввода или вывода выполняются в

цикле.

{}

Первый индекс определяет номер строки, второй - номер столбца.

Двумерные массивы хранятся в памяти ЭВМ по строкам.

Инициализация массивов (присвоение начальных значений всем компо-

нентам массивов) осуществляется двумя способами.

Первый способ - с использованием типизированных констант, напри-

мер:

type Dim10= Array[1..10] of Real;

const

raM10: Dim10 = ( 0, 2.1, 4, 5.65, 6.1, 6.7, 7.2, 8, 8.7, 9.3 );

При инициализации двумерных массивов значения компонент каждого из

входящих в него одномерных массивов записывается в скобках:

type Dim3x2= Array[1..3,1..2] of Integer;

const

iaM3x2: Dim3x2= ( (1, 2)

(3, 4)

(5, 6) );

Второй способ инициализации - использование разновидности процеду-

ры FillChar:

FillChar( var V; NBytes: Word; B: Byte );

Эта процедура заполняет участок памяти однобайтовым значением. Напри-

мер, для обнуления массива A[1..10] of Real можно записать:

FillChar(A, 40, 0);

или

FillChar(A, SizeOf(A), 0);


Untitled

24. Строки

Особое место в языке ПАСКАЛЬ занимают массивы символов. Стандарт-

ный ПАСКАЛЬ допускает два способа хранения символьных массивов в па-

мяти ЭВМ: распакованный и упакованный. Распакованные массивы символов

хранятся в памяти ЭВМ по одному символу в машинном слове, упакованные

- по одному символу в байте. При описании упакованного массива симво-

лов используют служебное слово PACKED, например:

var MAS: Packed Array[1..20] of Char;

Описание распакованного массива символов имеет вид:

var M: Array[1..20] of char;

Для преобразования символьного массива из распакованной формы в

упакованную и наоборот, из упакованной в распакованную, в язык ПАС-

КАЛЬ введены две стандартные функции Pack, UnPack.

Упакованный массив символов образует символьную строку. Символьная

строка может быть либо строковой константой, либо строковой перемен-

ной. Строковая константа, или строка, представляет собой совокупность

символов, заключенную в апострофы. Строка - это элементарная конс-

трукция языка ПАСКАЛЬ. Строковые константы могут входить в состав вы-

ражений. Как и числовые константы, они могут быть описаны в разделе

описания констант.

Строковые переменные - это одномерные упакованные массивы симво-

лов, для описания которых в TURBO PASCAL введен тип String.

Например, если строка содержит до 30 символов, ее тип будет опре-

делен как

type s= String[30];

Длина строки не может содержать более, чем 255 символов.

В TURBO PASCAL определено понятие строки переменной длины, в этом

случае ее описание задается как

type s= String;

Тип String без указания длины совместим со всеми типами строк.

Особенностью строковых переменных является то, что к ним можно об-

ращаться как к скалярным переменным, так и к массивам. Во втором слу-

чае применяется конструкция "переменная с индексом", что обеспечивает

доступ к отдельным символам строки. При этом нижняя граница идекса

равна 1. Отдельный символ строки совместим с типом Char.

В памяти ЭВМ строка занимает количество байтов, на единицу большее

ее длины. Нулевой байт строки содержит ее длину.

Для строк определены операции присваивания, слияния (конкатенации)

и сравнения.

Для сравнения строк применяются все операции отношения. Сравнение

строк происходит посимвольно, начиная с первого символа. Строки рав-

ны, если имеют одинаковую длину и посимвольно эквивалентны.

Строки могут быть элементами списка ввода - вывода, при этом запи-

сывается имя строки без индекса.

При вводе строковых переменных количество вводимых символов может

быть меньше, чем длина строки. В этом случае вводимые символы разме-

щаются с начала строки, а оставшиеся байты заполняются пробелами. Ес-

ли количество вводимых символов превышает длину строки, лишние

символы отбрасываются.

Инициализация строк может производиться как с помощью типизирован-

ных констант:

const sName: String[9]= 'IBM PC/AT';

так и с использованием второй разновидности функции FillChar:

FillChar( var V; NBytes: Word; C: Char );

например:

FillChar(A, SizeOf(A), '0');

Для работы со строками в TURBO PASCAL включены процедуры и функ-

ции, которые обеспечивают редактирование и преобразование строк.


Untitled

25. Процедуры и функции

Алгоритм решения задачи проектируется путем декомпозиции всей за-

дачи в отдельные подзадачи. Обычно подзадачи реализуются в виде подп-

рограмм.

Подпрограмма - это последовательность операторов, которые опреде-

лены и записаны только в одном месте программы, однако их можно

вызвать для выполнения из одной или нескольких точек программы. Каж-

дая подпрограмма определяется уникальным именем. В языке ПАСКАЛЬ су-

ществуют два типа подпрограмм - процедуры и функции.

Процедура и функция - это именованная последовательность описаний

и операторов. При использовании процедур или функций ПАСКАЛЬ - прог-

рамма должна содержать текст процедуры или функции и обращение к про-

цедуре или функции. Тексты процедур и функций помещаются в раздел

описаний процедур и функций.

{}

Процедура может содержать такие - же разделы описаний, что и ПАС-

КАЛЬ - программа, а именно: разделы описания модулей, меток, конс-

тант, типов, переменных, процедур и функций.

{}

ПЕРЕДАЧА ИМЕН ПРОЦЕДУР И ФУНКЦИЙ В КАЧЕСТВЕ ПАРАМЕТРОВ. Во многих

задачах, особенно в задачах вычислительной математики, необходимо пе-

редавать имена процедур и функций в качестве параметров. Для этого в

TURBO PASCAL введен новый тип данных - процедурный или функциональ-

ный, в зависимости от того, что описывается.

Описание процедурных и функциональных типов производится в разделе

описания типов:

type

FuncType = Function(z: Real): Real;

ProcType = Procedure (a,b: Real; var x,y: Real);

Функциональный и процедурный тип определяется как заголовок проце-

дуры и функции со списком формальных параметров, но без имени. Можно

определить функциональный или процедурный тип без параметров, напри-

мер:

type

Proc = Procedure;

После объявления процедурного или функционального типа его можно

использовать для описания формальных параметров - имен процедур и

функций.

Кроме того, необходимо написать те реальные процедуры или функции,

имена которых будут передаваться как фактические параметры. Эти про-

цедуры и функции должны компилироваться в режиме дальней адресации с

ключом {$F+}.

Пример. Составить программу для вычисления определенного интеграла

tk

2t

I= S--------------- dt

sqrt(1-sin2t)

tn

по методу Симпсона. Вычисление подинтегральной функции реализовать с

помощью функции, имя которой передается как параметр. Значение опре-

деленного интеграла по формуле Симпсона вычисляется по формуле:

ISimps=2*h/3*(0.5*F(A)+2*F(A+h)+F(A+2*h)+2*F(A+3*h)+...

+2*F(B-h)+0.5*F(B))

где A и B - нижняя и верхняя границы интервала интегрирования,

N - число разбиений интервала интегрирования,

h=(B-A)/N, причем N должно быть четным.

Program INTEGRAL;

type

Func= function(x: Real): Real;

var

I,TN,TK:Real;

N:Integer;

{$F+}

Function Q(t: Real): Real;

begin

Q:=2*t/Sqrt(1-Sin(2*t));

end;

{$F-}

Procedure Simps(F:Func; a,b:Real; N:Integer; var INT:Real);

var

sum, h: Real;

j:Integer;

begin

if Odd(N) then N:=N+1;

h:=(b-a)/N;

sum:=0.5*(F(a)+F(b));

for j:=1 to N-1 do

sum:=sum+(j mod 2+1)*F(a+j*h);

INT:=2*h*sum/3

end;

begin

WriteLn(' ВВЕДИ TN,TK,N');

Read(TN,TK,N);

Simps(Q,TN,TK,N,I);

WriteLn('I=',I:8:3)

end.


Untitled

26. Операторы выхода

Для завершения работы программ, процедур и функций без предвари-

тельного перехода по меткам к закрывающему end в TURBO PASCAL введены

процедуры Exit и Halt.

Вызов Exit завершает работу своего программного блока и передает

управление вызывающей программе. Если Exit выполняется в подпрограм-

ме, то выполнение этой подпрограммы прекратится, и далее будет выпол-

няться следующий за вызовом этой подпрограммы оператор. Если Exit вы-

полняется в основной программе, выход из нее будет эквивалентен ее

нормальному завершению.

Вызов процедуры Halt, где бы она не находилась, завершает работу

программы и передает управление операционной системе.

Процедура Halt имеет структуру Halt(n), где n - код возврата, ко-

торый может быть проанализирован операционной системой с помощью ко-

манды IF ERRORLEVEL. Значение n=0 соответствует нормальному заверше-

нию работы программы. Вызов процедуры Halt без параметра эквивалентен

вызову Halt(0).


Untitled

27. Модули

Модуль (UNIT) в TURBO PASCAL - это особым образом оформленная биб-

лиотека подпрограмм. Модуль в отличие от программы не может быть за-

пущен на выполнение самостоятельно, он может только участвовать в

построении программ и других модулей.

Модули позволяют создавать личные библиотеки процедур и функций и

строить программы практически любого размера.

Модуль в TURBO PASCAL представляет собой отдельно хранимую и неза-

висимо компилируемую программную единицу.

В общем случае модуль - это совокупность программных ресурсов,

предназначенных для использования другими программами. Под программ-

ными ресурсами понимаются любые элементы языка TURBO PASCAL: констан-

ты, типы, переменные, подпрограммы. Модуль сам по себе не является

выполняемой программой, его элементы используются другими программны-

ми единицами.

Все программные элементы модуля можно разбить на две части:

- программные элементы, предназначенные для использования другими

программами или модулями, такие элементы называют видимыми вне моду-

ля;

- программные элементы, необходимые только для работы самого моду-

ля, их называют невидимыми или скрытыми.

В соответствии с этим модуль, кроме заголовка, содержит две основ-

ные части, называемые интерфейсом и реализацией.

В общем случае модуль имеет следующую структуру:

unit <имя модуля>; {заголовок модуля}

interface

{ описание видимых программных элементов модуля }

{ описание скрытых программных элементов модуля }

begin

{ операторы инициализации элементов модуля }

end.

В частном случае модуль может не содержать части реализации и час-

ти инициализации, тогда структура модуля будет такой:

unit <имя модуля>; {заголовок модуля}

interface

{ описание видимых программных элементов модуля }

implementation

end.

Использование в модулях процедур и функций имеет свои особенности.

Заголовок подпрограммы содержит все сведения, необходимые для ее вы-

зова: имя, перечень и тип параметров, тип результата для функций, эта

информация должна быть доступна для других программ и модулей. С дру-

гой стороны, текст подпрограммы, реализующий ее алгоритм, другими

программами и модулями не может быть использован. Поэтому заголовок

процедур и функций помещают в интерфейсную часть модуля, а текст - в

часть реализации.

Интерфейсная часть модуля содержит только видимые (доступные для

других программ и модулей) заголовки процедур и функций (без служеб-

ного слова forward). Полный текст процедуры или функции помещают в

часть реализации, причем заголовок может не содержать список формаль-

ных параметров.

Исходный текст модуля должен быть откомпилирован с помощью дирек-

тивы Make подменю Compile и записан на диск. Результатом компиляции

модуля является файл с расширением .TPU (Turbo Pascal Unit). Основное

имя модуля берется из заголовка модуля.

Для подключения модуля к программе необходимо указать его имя в

разделе описания модулей, например:

uses CRT, Graph;

В том случае, если имена переменных в интерфейсной части модуля и

в программе, использующей этот модуль, совпадают, обращение будет

происходить к переменной, описанной в программе. Для обращения к пе-

ременной, описанной в модуле, необходимо применить составное имя,

состоящее из имени модуля и имени переменной, разделенных точкой.

Например, пусть имеется модуль, в котором описана переменная К:

unit M;

interface

var K: Integer;

implementation

.................

end.

Пусть программа, использующая этот модуль, также содержит перемен-

ную К:

Program P;

uses M;

var K: Char;

begin

.............

end.

Для того, чтобы в программе P иметь доступ к переменной K из моду-

ля M, необходимо задать составное имя M.K.

Использование составных имен применяется не только к именам пере-

менных, а ко всем именам, описанным в интерфейсной части модуля.

Рекурсивное использование модулей запрещено.

Если в модуле имеется раздел инициализации, то операторы из этого

раздела будут выполнены перед началом выполнения программы, в которой

используется этот модуль.


Untitled

28. Множества

Понятие множества в языке ПАСКАЛЬ основывается на математическом

представлении о множествах: это ограниченная совокупность различных

элементов. Для построения конкретного множественного типа использует-

ся перечисляемый или интервальный тип данных. Тип элементов, состав-

ляющих множество, называется базовым типом.

Множественный тип описывается с помощью служебных слов Set of,

например:

type M= Set of B;

Здесь М - множественный тип, В - базовый тип.

Пример описания переменной множественного типа:

type

M= Set of 'A'..'D';

var

MS: M;

Принадлежность переменных к множественному типу может быть опреде-

лена прямо в разделе описания переменных:

var

C: Set of 0..7;

Константы множественного типа записываются в виде заключенной в

квадратные скобки последовательности элементов или интервалов базово-

го типа, разделенных запятыми, например:

['A', 'C'] [0, 2, 7] [3, 7, 11..14].

Константа вида

[ ]

означает пустое подмножество.

Множество включает в себя набор элементов базового типа, все подм-

ножества данного множества, а также пустое подмножество. Если базовый

тип, на котором строится множество, имеет К элементов, то число подм-

ножеств, входящих в это множество, равно 2 в степени К. Пусть имеется

переменная Р интервального типа:

var P: 1..3;

Эта переменная может принимать три различных значения - либо 1,

либо 2, либо 3. Переменная Т множественного типа

var T: Set of 1..3;

может принимать восемь различных значений:

[ ] [1,2]

[1] [1,3]

[2] [2,3]

[3] [1,2,3]

Порядок перечисления элементов базового типа в константах безраз-

личен.

Значение переменной множественного типа может быть задано конс-

трукцией вида [T], где T - переменная базового типа.

К переменным и константам множественного типа применимы операции

присваивания(:=), объединения(+), пересечения(*) и вычитания(-):

['A','B'] + ['A','D'] даст ['A','B','D']

['A'] * ['A','B','C'] даст ['A']

['A','B','C'] - ['A','B'] даст ['C'].

Результат выполнения этих операций есть величина множественного

типа.

К множественным величинам применимы операции: тождественность (=),

нетождественность (<>), содержится в (<=), содержит (>=). Результат

выполнения этих операций имеет логический тип, например:

['A','B'] = ['A','C'] даст FALSE

['A','B'] <> ['A','C'] даст TRUE

['B'] <= ['B','C'] даст TRUE

['C','D'] >= ['A'] даст FALSE.

Кроме этих операций для работы с величинами множественного типа в

языке ПАСКАЛЬ используется операция

in

проверяющая принадлежность элемента базового типа, стоящего слева

от знака операции, множеству, стоящему справа от знака операции. Ре-

зультат выполнения этой операции - булевский. Операция проверки при-

надлежности элемента множеству часто используется вместо операций от-

ношения, например:

A in ['A', 'B'] даст TRUE,

2 in [1, 3, 6] даст FALSE.

При использовании в программах данных множественного типа

выполнение операций происходит над битовыми строками данных. Каждому

значению множественного типа в памяти ЭВМ соответствует один двоичный

разряд. Например, множество

['A','B','C','D']

представлено в памяти ЭВМ битовой строкой

1 1 1 1.

Подмножества этого множества представлены строками:

['A','B','D'] 1 1 0 1

['B','C'] 0 1 1 0

['D'] 0 0 0 1

Величины множественного типа не могут быть элементами списка вво-

да - вывода.

В каждой конкретной реализации транслятора с языка ПАСКАЛЬ коли-

чество элементов базового типа, на котором строится множество, огра-

ничено. В TURBO PASCAL количество базовых элементов не должно превы-

шать 256.

Инициализация величин множественного типа производится с помощью

типизированных констант:

const seLit: Set of 'A'..'D'= [];

Проиллюстрируем применение данных множественного типа на примере.

Пример. Составить программу, которая вырабатывает и выводит на эк-

ран дисплея наборы случайных чисел для игры в "Спортлото 5 из 36".

Для заполнения каждой карточки спортлото необходимо получить набор

из пяти псевдослучайных чисел. К этим числам предъявляются два требо-

вания:

-числа должны находиться в диапазоне 1..36;

-числа не должны повторяться.

Program Lotto;

var

nb, k: Set of 1..36;

kol, l, i, n: Integer;

begin

Randomize;

WriteLn('ВВЕДИ kol');

ReadLn(kol);

nb:=[1..36];

for i:=1 to kol do

begin

k:=[];

for l:=1 to 5 do

begin

repeat

n:=Random(36)

until (n in nb) and not (n in k);

k:=k+[n];

Write(n:4)

end;

WriteLn

end

end.


Untitled

29. Записи

Запись представляет собой совокупность ограниченного числа логи-

чески связанных компонент, принадлежащих к разным типам. Компоненты

записи называются полями, каждое из которых определяется именем. Поле

записи содержит имя поля, вслед за которым через двоеточие указывает-

ся тип этого поля. Поля записи могут относиться к любому типу, допус-

тимому в языке Паскаль, за исключением файлового типа.

Описание записи в языке ПАСКАЛЬ осуществляется с помощью

служебного слова RECORD, вслед за которым описываются компоненты за-

писи. Завершается описание записи служебным словом END.

Например, записная книжка содержит фамилии, инициалы и номера те-

лефона, поэтому отдельную строку в записной книжке удобно представить

в виде следующей записи:

type Row=Record

FIO: String[20];

TEL: String[7]

end;

var str: Row;

Описание записей возможно и без использования имени типа, напри-

мер:

var str: Record

FIO: String[20];

TEL: String[7]

end;

Обращение к записи в целом допускается только в операторах присва-

ивания, где слева и справа от знака присваивания используются имена

записей одинакового типа. Во всех остальных случаях оперируют отдель-

ными полями записей. Чтобы обратиться к отдельной компоненте записи,

необходимо задать имя записи и через точку указать имя нужного поля,

например:

str.FIO, str.TEL

Такое имя называется составным. Компонентой записи может быть так-

же запись, в таком случае составное имя будет содержать не два, а

большее количество имен.

Обращение к компонентам записей можно упростить, если воспользо-

ваться оператором присоединения with.

Он позволяет заменить составные имена, характеризующие каждое по-

ле, просто на имена полей, а имя записи определить в операторе присо-

единения:

with M do OP;

Здесь М - имя записи, ОР - оператор, простой или составной.

Оператор ОР представляет собой область действия оператора присоедине-

ния, в пределах которой можно не использовать составные имена.

Иногда содержимое отдельной записи зависит от значения одного из

ее полей. В языке ПАСКАЛЬ допускается описание записи, состоящей из

общей и вариантной частей. Вариантная часть задается с помощью конс-

трукции

case P of,

где Р - имя поля из общей части записи. Возможные значения, прини-

маемые этим полем, перечисляются так же, как и в операторе варианта.

Однако вместо указания выполняемого действия, как это делается в опе-

раторе варианта, указываются поля варианта, заключенные в круглые

скобки. Описание вариантной части завершается служебным словом end.

Тип поля Р можно указать в заголовке вариантной части, например:

case P: Integer of

Инициализация записей осуществляется с помощью типизированных

констант:

type

RecType= Record

x,y: Word;

ch: Char;

dim: Array[1..3] of Byte

end;

const

Rec: RecType= ( x: 127; y: 255;

ch: 'A';

dim: (2, 4, 8) );


Untitled

30. Файлы

Введение файлового типа в язык ПАСКАЛЬ вызвано необходимостью

обеспечить возможность работы с периферийными (внешними) устройствами

ЭВМ, предназначенными для ввода, вывода и хранения данных.

Файловый тип данных или файл определяет упорядоченную совокупность

произвольного числа однотипных компонент.

Общее свойство массива, множества и записи заключается в том, что

количество их компонент определено на этапе написания программы, тог-

да как количество компонент файла в тексте программы не определяется

и может быть произвольным.

Понятие файла достаточно широко. Это может быть обычный файл на

диске, коммуникационный порт ЭВМ, устройство печати, клавиатура или

другие устройства.

При работе с файлами выполняются операции ввода - вывода. Операция

ввода означает перепись данных с внешнего устройства (из входного

файла) в основную память ЭВМ, операция вывода - это пересылка данных

из основной памяти на внешнее устройство (в выходной файл).

Файлы на внешних устройствах часто называют физическими файлами.

Их имена определяются операционной системой. В программах на языке

Паскаль имена файлов задаются с помощью строк. Например, имя файла на

диске может иметь вид:

'A:LAB1.DAT'

'c:\ABC150\pr.pas'

'lab3.pas'.

Операционная система MS-DOS не делает особого различия между фай-

лами на дисках и лентах и устройствами ЭВМ и портами коммуникаций. В

TURBO PASCAL могут использоваться имена устройств и портов, опреде-

ленные в MS-DOS, например:

'CON', 'LPT1', 'PRN', 'COM1', 'AUX', 'NUL'.

С файловой системой TURBO PASCAL связано понятие буфера ввода -

вывода. Ввод и вывод данных осуществляется через буфер. Буфер - это

область в памяти, которая выделяется для каждого файла. При записи в

файл вся информация сначала направляется в буфер и там накапливается

до тех пор, пока весь объем буфера не будет заполнен. Только после

этого или после специальной команды сброса происходит передача данных

на внешнее устройство. При чтении из файла данные вначале считываются

в буфер, причем данных считывается не столько, сколько запрашивается,

а сколько поместится в буфер.

Механизм буферизации позволяет более быстро и эффективно обмени-

ваться информацией с внешними устройствами.

Для работы с файлами в программе необходимо определить файловую

переменную. TURBO PASCAL поддерживает три файловых типа: текстовые

файлы, компонентные файлы, бестиповые файлы.

Описание файловых переменных текстового типа производится с по-

мощью служебного слова Text, например:

var tStory: Text;

Описание компонентных файлов имеет вид:

var fComp: File of T;

где T - тип компоненты файла. Примеры описания файловой переменной

компонентного типа:

type M= array[1..500] of Longint;

var f1: File of Real;

f2: File of Integer;

fLi: File of M;

Бестиповые файлы описываются с помощью служебного слова File:

var f: File;

Файловые переменные, которые описаны в программе, называют логи-

ческими файлами. Все основные процедуры и функции, обеспечивающие

ввод - вывод данных, работают только с логическими файлами. Физичес-

кий файл должен быть связан с логическим до выполнения процедур отк-

рытия файлов.

TURBO PASCAL вводит ряд процедур и функций, применимых для любых

типов файлов: Assign, Reset, Rewrite, Close, Rename, Erase, Eof,

IOResult.

Процедура Assign( var f; FileName: String ) связывает логический

файл f с физическим файлом, полное имя которого задано в строке

FileName.

Процедура Reset( var f ) открывает логический файл f для последую-

щего чтения данных или, как говорят, открывает входной файл. После

успешного выполнения процедуры Reset файл готов к чтению из него пер-

вого элемента.

Процедура Rewrite( var f ) открывает логический файл f для после-

дующей записи данных (открывает выходной файл). После успешного вы-

полнения этой процедуры файл готов к записи в него первого элемента.

Процедура Close( var f ) закрывает открытый до этого логический

файл. Вызов процедуры Close необходим при завершении работы с файлом.

Если по какой-то причине процедура Close не будет выполнена, файл

все-же будет создан на внешнем устройстве, но содержимое последнего

буфера в него не будет перенесено. Для входных файлов использование

оператора закрытия файла необязательно.

Логическая функция EOF( var f ): Boolean возвращает значение TRUE,

когда при чтении достигнут конец файла. Это означает, что уже прочи-

тан последний элемент в файле или файл после открытия оказался пуст.

Процедура Rename( var f; NewName: String ) позволяет переименовать

физический файл на диске, связанный с логическим файлом f. Переимено-

вание возможно после закрытия файла.

Процедура Erase( var f ) уничтожает физический файл на диске, ко-

торый был связан с файловой переменной f. Файл к моменту вызова про-

цедуры Erase должен быть закрыт.

Функция IOResult: Integer возвращает целое число, соответствующее

коду последней ошибки ввода - вывода. При нормальном завершении опе-

рации функция вернет значение 0. Значение функции IOResult необходимо

присваивать какой - либо переменной, так как при каждом вызове функ-

ция обнуляет свое значение. Функция IOResult работает только при вык-

люченном режиме проверок ошибок ввода - вывода или с ключом компиля-

ции {$I-}.


Untitled

31. Текстовые файлы

Особое место в языке ПАСКАЛЬ занимают текстовые файлы, компоненты

которых имеют символьный тип. Для описания текстовых файлов в языке

определен стандартный тип Тext:

var TF1, TF2: Text;

Текстовые файлы представляют собой последовательность строк, а

строки - последовательность символов. Строки имеют переменную длину,

каждая строка завершается признаком конца строки.

С признаком конца строки связана функция EOLn(var T:Text):Boolean,

где Т - имя текстового файла. Эта функция принимает значение TRUE,

если достигнут конец строки, и значение FALSE, если конец строки не

достигнут.

Для операций над текстовыми файлами, кроме перечисленных, опреде-

лены также операторы обращения к процедурам:

ReadLn(T) - пропускает строку до начала следующей;

WriteLn(T) - завершает строку файла, в которую производится за-

пись, признаком конца строки и переходит к началу следующей.

Для работы с текстовыми файлами введена расширенная форма операто-

ров ввода и вывода. Оператор

Read(T,X1,X2,...XK)

эквивалентен группе операторов

begin

Read(T,X1);

Read(T,X2);

...........

Read(T,XK)

end;

Здесь Т - текстовый файл, а переменные Х1, Х2,...ХК могут быть ли-

бо переменными целого, действительного или символьного типа, либо

строкой. При чтении значений переменных из файла они преобразуются из

текстового представления в машинное.

Оператор

Write(T,X1,X2,...XK)

эквивалентен группе операторов

begin

Write(T,X1);

Write(T,X2);

...........

Write(T,XK)

end;

Здесь Т - также текстовый файл, но переменные Х1,Х2,...ХК могут

быть целого, действительного, символьного, логического типа или стро-

кой. При записи значений переменных в файл они преобразуются из внут-

реннего представления в текстовый.

К текстовым файлам относятся стандартные файлы INPUT, OUTPUT.

Рассмотренные ранее операторы ввода - вывода являются частным слу-

чаем операторов обмена с текстовыми файлами, когда используются стан-

дартные файлы ввода - вывода INPUT, OUTPUT.

Работа с этими файлами имеет особенности:

-имена этих файлов в списках ввода - вывода не указываются;

-применение процедур Reset, Rewrite и Close к стандартным файлам

ввода - вывода запрещено;

-для работы с файлами INPUT, OUTPUT введена разновидность функции

EOLn без параметров.

TURBO PASCAL вводит дополнительные процедуры и функции, применимые

только к текстовым файлам, это SetTextBuf, Append, Flush, SeekEOLn,

SeekEOF.

Процедура SetTextBuf( var f: Text; var Buf; BufSize: Word ) служит

для увеличения или уменьшения буфера ввода - вывода текстового файла

f. Значение размера буфера для текстовых файлов по умолчанию равно

128 байтам. Увеличение размера буфера сокращает количество обращений

к диску. Рекомендуется изменять разиер буфера до открытия файла. Бу-

фер файла начнется с первого байта переменной Buf. Размер буфера за-

дается в необязательном параметре BufSize, а если этот параметр от-

сутствует, размер буфера определяется длиной переменной Buf.

Процедура Append( var f: Text ) служит для специального открытия

выходных файлов. Она применима к уже существующим физическим файлам и

открывает из для дозаписи в конец файла.

Процедура Flush( var f: Text ) применяется к открытым выходным

файлам. Она принудительно записывает данные из буфера в файл незави-

симо от степени его заполнения.

Функция SeekEOLn( var f: Text ): Boolean возвращает значение True,

если до конца строки остались только пробелы.

Функция SeekEOF( var f: Text ): Boolean возвращает значение True,

если до конца файла остались строки, заполненные пробелами.


Untitled

32. Компонентные файлы

Компонентный или типизированный файл - это файл с объявленным ти-

пом его компонент. Компонентные файлы состоят из машинных представле-

ний значений переменных, они хранят данные в том же виде, что и па-

мять ЭВМ.

Описание величин файлового типа имеет вид:

type M= File Of T;

где М - имя файлового типа, Т - тип компоненты. Например:

type

FIO= String[20];

SPISOK=File of FIO;

var

STUD, PREP: SPISOK;

Здесь STUD, PREP - имена файлов, компонентами которых являются

строки.

Описание файлов можно задавать в разделе описания переменных:

var

fsimv: File of Char;

fr: File of Real;

Компонентами файла могут быть все скалярные типы, а из структури-

рованных - массивы, множества, записи. Практически во всех конкретных

реализациях языка ПАСКАЛЬ конструкция "файл файлов" недопустима.

Все операции над компонентными файлами производятся с помощью

стандартных процедур:

Reset, Rewrite, Read, Write, Close.

Для ввода - вывода используются процедуры:

Read(f,X);

Write(f,X);

где f - имя логического файла, Х - либо переменная, либо массив,

либо строка, либо множество, либо запись с таким же описанием, какое

имеет компонента файла.

Выполнение процедуры Read(f,X) состоит в чтении с внешнего уст-

ройства одной компоненты файла и запись ее в X. Повторное применение

процедуры Read(f,X) обеспечит чтение следующей компоненты файла и за-

пись ее в X.

Выполнение процедуры Write(f,X) состоит в записи X на внешнее уст-

ройство как одной компоненты. Повторное применение этой процедуры

обеспечит запись X как следующей компоненты файла.

Для работы с компонентными файлами введена расширенная форма опе-

раторов ввода и вывода:

Read(f,X1,X2,...XK)

Write(f,X1,X2,...XK)

Здесь f - компонентный файл, а переменные Х1, Х2,...ХК должны

иметь тот-же тип, что и объявленный тип компонент файла f.


Untitled

33. Бестиповые файлы

Бестиповые файлы позволяют записывать на диск произвольные участки

пвмяти ЭВМ и считывать их с диска в память. Операции обмена с бести-

повыми файлами осуществляется с помощью процедур BlokRead и

BlockWrite. Кроме того, вводится расширенная форма процедур Reset и

Rewrite. В остальном принципы работы остаются такими же, как и с ком-

понентными файлами.

Перед использованием логический файл

var f: File;

должен быть связан с физическим с помощью процедуры Assign. Далее

файл должен быть открыт для чтения или для записи процедурой Reset

или Rewrite, а после окончания работы закрыт процедурой Close.

При открытии файла длина буфера устанавливается по умолчанию в 128

байт. TURBO PASCAL позволяет изменить размер буфера ввода - вывода,

для чего следует открывать файл расширенной записью процедур

Reset(var f: File; BufSize: Word )

или

Rewrite(var f: File; BufSize: Word )

Параметр BufSize задает число байтов, считываемых из файла или за-

писываемых в него за одно обращение. Минимальное значение BufSize - 1

байт, максимальное - 64 К байт.

Чтение данных из бестипового файла осуществляется процедурой

BlockRead( var f: File; var X; Count: Word; var QuantBlock: Word );

Эта процедура осуществляет за одно обращение чтение в переменную X

количества блоков, заданное параметром Count, при этом длина блока

равна длине буфера. Значение Count не может быть меньше 1. За одно

обращение нельзя прочесть больше, чем 64 К байтов.

Необязательный параметр QuantBlock возвращает число блоков (буфе-

ров), прочитанных текущей операцией BlockRead. В случае успешного за-

вершения операции чтения QuantBlock = Count, в случае аварийной ситу-

ации параметр QuantBlock будет содержать число удачно прочитанных

блоков. Отсюда следует, что с помощью параметра QuantBlock можно

контролировать правильность выполнения операции чтения.

Запись данных в бестиповой файл выполняется процедурой

BlockWrite( var f: File; var X; Count: Word; var QuantBlock: Word );

которая осуществляет за одно обращение запись из переменной X коли-

чества блоков, заданное параметром Count, при этом длина блока равна

длине буфера.

Необязательный параметр QuantBlock возвращает число блоков (буфе-

ров), записанных успешно текущей операцией BlockWrite.


Untitled

34. Последовательный и прямой доступ

Смысл последовательного доступа заключается в том, что в каждый

момент времени доступна лишь одна компонента из всей последователь-

ности. Для того, чтобы обратиться (получить доступ) к компоненте с

номером К, необходимо просмотреть от начала файла К-1 предшествующую

компоненту. После обращения к компоненте с номером К можно обращаться

к компоненте с номером К+1. Отсюда следует, что процессы формирования

(записи) компонент файла и просмотра (чтения) не могут произвольно

чередоваться. Таким образом, файл вначале строится при помощи после-

довательного добавления компонент в конец, а затем может последова-

тельно просматриваться от начала до конца.

Рассмотренные ранее средства работы с файлами обеспечивают после-

довательный доступ.

TURBO PASCAL позволяет применять к компонентным и бестиповым фай-

лам, записанным на диск, способ прямого доступа. Прямой доступ озна-

чает возможность заранее определить в файле блок, к которому будет

применена операция ввода - вывода. В случае бестиповых файлов блок

равен размеру буфера, для компонентных файлов блок - это одна компо-

нента файла.

Прямой доступ предполагает, что файл представляет собой линейную

последовательность блоков. Если файл содержит n блоков, то они нуме-

руются от 1 через 1 до n. Кроме того, вводится понятие условной гра-

ницы между блоками, при этом условная граница с номером 0 расположена

перед блоком с номером 1, граница с номером 1 расположена перед бло-

ком с номером 2 и, наконец, условная граница с номером n находится

после блока с номером n.

Реализация прямого доступа осуществляется с помощью функций и про-

цедур FileSize, FilePos, Seek и Truncate.

Функция FileSize( var f ): Longint возвращает количество блоков в

открытом файле f.

Функция FilePos( var f ): Longint возвращает текущую позицию в

файле f. Позиция в файле - это номер условной границы. Для только что

открытого файла текущей позицией будет граница с номером 0. Это зна-

чит, что можно записать или прочесть блок с номером 1. После чтения

или записи первого блока текущая позиция переместится на границу с

номером 1, и можно будет обращаться к ьлоку с номером 2. После проч-

тения последней записи значение FilePos равно значению FileSize.

Процедура Seek( var f; N: Longint) обеспечивает назначение текущей

позиции в файле (позиционирование). В параметре N должен быть задан

номер условной границы, предшествующей блоку, к которому будет произ-

водиться последующее обращение. Например, чтобы работать с блоком 4,

необходимо задать значение N, равное 3. Процедура Seek работает с от-

крытыми файлами.

Процедура Truncate( var f ) устанавливает в текущей позиции приз-

нак конца файла и удаляет (стирает) все последующие блоки.

Пример. Пусть на НМД имеется текстовый файл ID.DAT, который содер-

жит числовые значения действительного типа по два числа в каждой

строке - значения аргумента и функции соответственно. Количество пар

чисел не более 200. Составить программу, которая читает файл, значе-

ния аргумента и функции записывает в одномерные массивы, подсчитывает

их количество, выводит на экран дисплея и записывает в файл компо-

нентного типа RD.DAT.

Program F;

var

rArg, rF: Array[1..200] of Real;

inf: Text;

outf: File of Real;

n, l: Integer;

begin

Assign(inf,'ID.DAT');

Assign(outf,'RD.DAT');

Reset(inf);

Rewrite(outf);

n:=0;

while not EOF(inf) do

begin

n:=n+1;

ReadLn(inf,rArg[n],rF[n])

end;

for l:=1 to n do

begin

WriteLn(l:2,rArg[l]:8:2,rF[l]:8:2);

Write(outf,rArg[l], rF[l]);

end;

close(outf)

end.


Untitled

35. Указатели

Операционная система MS - DOS все адресуемое пространство делит на

сегменты. Сегмент - это участок памяти размером 64 К байт. Для зада-

ния адреса необходимо определить адрес начала сегмента и смещение от-

носительно начала сегмента.

В TURBO PASCAL определен адресный тип Pointer - указатель. Пере-

менные типа Pointer

var p: Pointer;

содержат адрес какого - либо элемента программы и занимают 4 байта,

при этом адрес хранится как два слова, одно из них определяет сег-

мент, второе - смещение.

Переменную типа указатель можно описать другим способом.

type NameType= ^T;

var p: NameType;

Здесь p - переменная типа указатель, связанная с типом Т с помощью

имени типа NameType. Описать переменную типа указатель можно непос-

редственно в разделе описания переменных:

var p: ^T;

Необходимо различать переменную типа указатель и переменную, на

которую этот указатель ссылается. Например если p - ссылка на пере-

менную типа Т, то p^ - обозначение этой самой переменной.

Для переменных типа указатель введено стандартное значение NIL,

которое означает, что указатель не ссылается ни к какому объекту.

Константа NIL используется для любых указателей.

Над указателями не определено никаких операций, кроме проверки на

равенство и неравенство.

Переменные типа указатель могут быть записаны в левой части опера-

тора присваивания, при этом в правой части может находиться либо

функция определения адреса Addr(X), либо выражение @ X, где @ - унар-

ная операция взятия адреса, X - имя переменной любого типа, в том

числе процедурного.

Переменные типа указатель не могут быть элементами списка ввода -

вывода.


Untitled

36. Динамические переменные

Статической переменной (статически размещенной) называется описан-

ная явным образом в программе переменная, обращение к ней осуществля-

ется по имени. Место в памяти для размещения статических переменных

определяется при компиляции программы.

В отличие от таких статических переменных в программах, написанных

на языке ПАСКАЛЬ, могут быть созданы динамические переменные. Основ-

ное свойство динамических переменных заключается в том, что они соз-

даются и память для них выделяется во время выполнения программы.

Размещаются динамические переменные в динамической области памяти

(heap - области).

Динамическая переменная не указывается явно в описаниях переменных

и к ней нельзя обратиться по имени. Доступ к таким переменным осу-

ществляется с помощью указателей и ссылок.

Работа с динамической областью памяти в TURBO PASCAL реализуется с

помощью процедур и функций New, Dispose, GetMem, FreeMem, Mark,

Release, MaxAvail, MemAvail, SizeOf.

Процедура New( var p: Pointer ) выделяет место в динамической об-

ласти памяти для размещения динамической переменной p^ и ее адрес

присваивает указателю p.

Процедура Dispose( var p: Pointer ) освобождает участок памяти,

выделенный для размещения динамической переменной процедурой New, и

значение указателя p становится неопределенным.

Проуедура GetMem( var p: Pointer; size: Word ) выделяет участок

памяти в heap - области, присваивает адрес его начала указателю p,

размер участка в байтах задается параметром size.

Процедура FreeMem( var p: Pointer; size: Word ) освобождает учас-

ток памяти, адрес начала которого определен указателем p, а размер -

параметром size. Значение указателя p становится неопределенным.

Процедура Mark( var p: Pointer ) записывает в указатель p адрес

начала участка свободной динамической памяти на момент ее вызова.

Процедура Release( var p: Pointer ) освобождает участок динамичес-

кой памяти, начиная с адреса, записанного в указатель p процедурой

Mark, то-есть, очищает ту динамическую память, которая была занята

после вызова процедуры Mark.

Функция MaxAvail: Longint возвращает длину в байтах самого длинно-

го свободного участка динамической памяти.

Функция MemAvail: Longint полный объем свободной динамической па-

мяти в байтах.

Вспомогательная функция SizeOf( X ): Word возвращает объем в бай-

тах, занимаемый X, причем X может быть либо именем переменной любого

типа, либо именем типа.

Рассмотрим некоторые примеры работы с указателями.

var

p1, p2: ^Integer;

Здесь p1 и p2 - указатели или пременные ссылочного типа.

p1:=NIL; p2:=NIL;

После выполнения этих операторов присваивания указатели p1 и p2 не

будут ссылаться ни на какой конкретный объект.

New(p1); New(p2);

Процедура New(p1) выполняет следующие действия:

-в памяти ЭВМ выделяется участок для размещения величины целого

типа;

-адрес этого участка присваивается переменной p1:

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є є

ИНННННј ИНННННј

p1 p1^

Аналогично, процедура New(p2) обеспечит выделение участка памяти,

адрес которого будет записан в p2:

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є є

ИНННННј ИНННННј

p2 p2^

После выполнения операторов присваивания

p1^:=2; p2^:=4;

в выделенные участки памяти будут записаны значения 2 и 4 соответ-

ственно:

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є 2 є

ИНННННј ИНННННј

p1 p1^

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є 4 є

ИНННННј ИНННННј

p2 p2^

В результате выполнения оператора присваивания

p1^:=p2^;

в участок памяти, на который ссылается указатель p1, будет записано

значение 4:

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є 4 є

ИНННННј ИНННННј

p1 p1^

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є 4 є

ИНННННј ИНННННј

p2 p2^

После выполнения оператора присваивания

p2:=p1;

оба указателя будут содержать адрес первого участка памяти:

ЙННННН» ЙННННН»

є *ДДєДДДДДДДДД>є 4 є

ИНННННј ЪДД>ИНННННј

p1 і p1^ p2^

і

ЙННННН» і

є *ДДєДДДДДДЩ

ИНННННј

p2

Переменные p1^, p2^ являются динамическими, так как память для них

выделяется в процессе выполнения программы с помощью процедуры New.

Динамические переменные могут входить в состав выражений, напри-

мер:

p1^:=p1^+8; Write('p1^=',p1^:3);

Пример. В результате выполнения программы:

Program DemoPointer;

var p1,p2,p3:^Integer;

begin

p1:=NIL; p2:=NIL; p3:=NIL;

New(p1); New(p2); New(p3);

p1^:=2; p2^:=4;

p3^:=p1^+Sqr(p2^);

writeln('p1^=',p1^:3,' p2^=',p2^:3,' p3^=',p3^:3);

p1:=p2;

writeln('p1^=',p1^:3,' p2^=',p2^:3)

end.

на экран дисплея будут выведены результаты:

p1^= 2 p2^= 4 p3^= 18

p1^= 4 p2^= 4


Untitled

37. Динамические структуры данных

Структурированные типы данных, такие, как массивы, множества, за-

писи, представляют собой статические структуры, так как их размеры

неизменны в течение всего времени выполнения программы.

Часто требуется, чтобы структуры данных меняли свои размеры в ходе

решения задачи. Такие структуры данных называются динамическими, к

ним относятся стеки, очереди, списки, деревья и другие. Описание ди-

намических структур с помощью массивов, записей и файлов приводит к

неэкономному использованию памяти ЭВМ и увеличивает время решения за-

дач.

Каждая компонента любой динамической структуры представляет собой

запись, содержащую по крайней мере два поля: одно поле типа указа-

тель, а второе - для размещения данных. В общем случае запись может

содержать не один, а несколько укзателей и несколько полей данных.

Поле данных может быть переменной, массивом, множеством или записью.

Для дальнейшего рассмотрения представим отдельную компоненту в ви-

де:

ЙННННН»

є D є

єНННННє

є p є

ИНННННј

где поле p - указатель;

поле D - данные.

Описание этой компоненты дадим следующим образом:

type

Pointer = ^Comp;

Comp = record

D:T;

pNext:Pointer

end;

здесь T - тип данных.

Рассмотрим основные правила работы с динамическими структурами

данных типа стек, очередь и список, базируясь на приведенное описание

компоненты.


Untitled

38. Стеки

Стеком называется динамическая структура данных, добавление компо-

ненты в которую и исключение компоненты из которой производится из

одного конца, называемого вершиной стека. Стек работает по принципу

LIFO (Last-In, First-Out) -

поступивший последним, обслуживается первым.

Обычно над стеками выполняется три операции:

-начальное формирование стека (запись первой компоненты);

-добавление компоненты в стек;

-выборка компоненты (удаление).

Для формирования стека и работы с ним необходимо иметь две пере-

менные типа указатель, первая из которых определяет вершину стека, а

вторая - вспомогательная. Пусть описание этих переменных имеет вид:

var pTop, pAux: Pointer;

где pTop - указатель вершины стека;

pAux - вспомогательный указатель.

Начальное формирование стека выполняется следующими операторами:

ЙННННН» ЙННННН»

New(pTop); є *ДДєДДДї є є

ИНННННј і єНННННє

pTop АДД>є є

ИНННННј

ЙННННН» ЙННННН»

pTop^.pNext:=NIL; є *ДДєДДДї є є

ИНННННј і єНННННє

pTop АДД>є NIL є

ИНННННј

ЙННННН» ЙННННН»

pTop^.D:=D1; є *ДДєДДДї є D1 є

ИНННННј і єНННННє

pTop АДД>є NIL є

ИНННННј

Последний оператор или группа операторов записывает содержимое

поля данных первой компоненты.

Добавление компоненты в стек призводится с использованием вспо-

могательного указателя:

ЙННННН» ЙННННН» ЙННННН»

New(pAux); є *ДДєДДДї є є ЪДДДєДД* є

ИНННННј і єНННННє і ИНННННј

pTop і є є<ДДЩ pAux

і ИНННННј

і

і ЙННННН»

і є D1 є

і єНННННє

АДД>є NIL є

ИНННННј

ЙННННН» ЙННННН» ЙННННН»

pAux^.pNext:=pTop; є *ДДєДДДї є є ЪДДДєДД* є

ИНННННј і єНННННє<ДДЩ ИНННННј

pTop і є *ДДєДї pAux

і ИНННННј і

і і

і ЙННННН» і

і є D1 є і

і єНННННє і

АДД>є NIL є<Щ

ИНННННј

ЙННННН» ЙННННН» ЙННННН»

pTop:=pAux; є *ДДєДДДї є є ЪДДДєДД* є

ИНННННј і єНННННє<ДДЩ ИНННННј

pTop АДД>є *ДДєДї pAux

ИНННННј і

і

ЙННННН» і

є D1 є і

єНННННє і

є NIL є<Щ

ИНННННј

ЙННННН» ЙННННН»

pTop^.D:=D2; є *ДДєДДДї є D2 є

ИНННННј і єНННННє

pTop АДД>є *ДДєДї

ИНННННј і

і

ЙННННН» і

є D1 є і

єНННННє і

є NIL є<Щ

ИНННННј

Добавление последующих компонент производится аналогично.

Рассмотрим процесс выборки компонент из стека. Пусть к моменту на-

чала выборки стек содержит три компоненты:

ЙННННН» ЙННННН»

є *ДДєДДДї є D3 є

ИНННННј і єНННННє

pTop АДД>є *--єДї

ИНННННј і

і

ЙННННН» і

є D2 є і

єНННННє і

ЪДєДД* є<Щ

і ИНННННј

і

і ЙННННН»

і є D1 є

і єНННННє

А>є NIL є

ИНННННј

Первый оператор или группа операторов осуществляет чтение данных

из компоненты - вершины стека. Второй оператор изменяет значение ука-

зателя вершины стека:

ЙННННН» ЙННННН»

D3:=pTop^.D; є *ДДєДДДї є D3 є

pTop:=pTop^.pNext; ИНННННј і єНННННє

pTop і є є

і ИНННННј

і

і ЙННННН»

і є D2 є

і єНННННє

АДД>є *ДДєДї

ИНННННј і

і

ЙННННН» і

є D1 є і

єНННННє і

є NIL є<Щ

ИНННННј

Как видно из рисунка, при чтении компонента удаляется из стека.

Пример. Составить программу, которая формирует стек, добавляет в

него произвольное количество компонент, а затем читает все компоненты

и выводит их на экран дисплея, В качестве данных взять строку симво-

лов. Ввод данных - с клавиатуры дисплея, признак конца ввода - строка

символов END.

Program STACK;

uses Crt;

type

Alfa= String[10];

PComp= ^Comp;

Comp= Record

sD: Alfa;

pNext: PComp

end;

var

pTop: PComp;

sC: Alfa;

Procedure CreateStack(var pTop: PComp; var sC: Alfa);

begin

New(pTop);

pTop^.pNext:=NIL;

pTop^.sD:=sC

end;

Procedure AddComp(var pTop: PComp; var sC: Alfa);

var pAux: PComp;

begin

NEW(pAux);

pAux^.pNext:=pTop;

pTop:=pAux;

pTop^.sD:=sC

end;

Procedure DelComp(var pTop: PComp; var sC:ALFA);

begin

sC:=pTop^.sD;

pTop:=pTop^.pNext

end;

begin

Clrscr;

writeln(' ВВЕДИ СТРОКУ ');

readln(sC);

CreateStack(pTop,sC);

repeat

writeln(' ВВЕДИ СТРОКУ ');

readln(sC);

AddComp(pTop,sC)

until sC='END';

writeln('****** ВЫВОД РЕЗУЛЬТАТОВ ******');

repeat

DelComp(pTop,sC);

writeln(sC);

until pTop = NIL

end.


Untitled

39. Очереди

Очередью называется динамическая структура данных, добавление ком-

поненты в которую производится в один конец, а выборка осуществляется

с другого конца. Очередь работает по принципу:

FIFO (First-In, First-Out) -

поступивший первым, обслуживается первым.

Для формирования очереди и работы с ней необходимо иметь три пере-

менные типа указатель, первая из которых определяет начало очереди,

вторая - конец очереди, третья - вспомогательная.

Описание компоненты очереди и переменных типа указатель дадим сле-

дующим образом:

type

PComp=^Comp;

Comp=record

D:T;

pNext:PComp

end;

var

pBegin, pEnd, pAux: PComp;

где pBegin - указатель начала очереди, pEnd - указатель конца очере-

ди, pAux - вспомогательный указатель.

Тип Т определяет тип данных компоненты очереди.

Начальное формирование очереди выполняется следующими операторами:

ЙННННН» ЙННННН» ЙННННН»

New(pBegin); є *ДДєДДДї є є є є

ИНННННј і єНННННє ИНННННј

pBegin АДД>є є pEnd

ИНННННј

ЙННННН» ЙННННН» ЙННННН»

pBegin^.pNext:=NIL; є *ДДєДДДї є є є є

ИНННННј і єНННННє ИНННННј

pBegin АДД>є NIL є pEnd

ИНННННј

ЙННННН» ЙННННН» ЙННННН»

pBegin^.D:=D1; є *ДДєДДДї є D1 є є є

ИНННННј і єНННННє ИНННННј

pBegin АДД>є NIL є pEnd

ИНННННј

ЙННННН» ЙННННН» ЙННННН»

pEnd:=pBegin; є *ДДєДДДї є D1 є ЪДДДєДД* є

ИНННННј і єНННННє і ИНННННј

pBegin АДД>є NIL є<ДДЩ pEnd

ИНННННј

Добавление компоненты в очередь производится в конец очереди:

New(pAux);

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є ЪДДДєДД* є є є ЪДДДєДД* є

ИНННННј і єНННННє і ИНННННј єНННННє і ИНННННј

pBegin АДД>є NIL є<ДДЩ pEnd є є<ДДЩ pAux

ИНННННј ИНННННј

pAux^.pNext:=NIL;

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є ЪДДДєДД* є є є ЪДДДєДД* є

ИНННННј і єНННННє і ИНННННј єНННННє і ИНННННј

pBegin АДД>є NIL є<ДДЩ pEnd є NIL є<ДДЩ pAux

ИНННННј ИНННННј

pBegin^.pNext:=pAux;

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є ЪДДДєДД* є є є ЪДДДєДД* є

ИНННННј і єНННННє і ИНННННј єНННННє і ИНННННј

pBegin АДД>є * є<ДДЩ pEnd є NIL є<ДДЩ pAux

ИНННННј ИНННННј

і ^

і і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

pEnd:=pAux;

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є є *ДДєДДДї є є ЪДДДєДД* є

ИНННННј і єНННННє ИНННННј і єНННННє і ИНННННј

pBegin АДД>є * є pEnd АДД>є NIL є<ДДЩ pAux

ИНННННј ИНННННј

і ^

і і

АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

pEnd^.D:=D2;

ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є є D2 є ЪДДДєДД* є

ИНННННј і єНННННє єНННННє і ИНННННј

pBegin АДД>є *ДДєДДДДДДДДДДДДДДДДДДДД>є NIL є<ДДЩ pEnd

ИНННННј ИНННННј

Добавление последующих компонент производится аналогично.

Выборка компоненты из очереди осуществляется из начала очереди,

одновременно компонента исключается из очереди. Пусть в памяти ЭВМ

сформирована очередь, состоящая из трех элементов:

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є є D2 є є D3 є ЪДДДєДД* є

ИНННННј і єНННННє єНННННє єНННННє і ИНННННј

pBegin АДД>є *ДДєДДДДДД>є *ДДєДДДДДД>є NIL є<ДДЩ pEnd

ИНННННј ИНННННј ИНННННј

Выборка компоненты выполняется следующими операторами:

D1:=pBegin^.D;

pBegin:=pBegin^.pNext;

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДДДї є D1 є є D2 є є D3 є ЪДДДєДД* є

ИНННННј і єНННННє єНННННє єНННННє і ИНННННј

pBegin і є є ЪДД>є *ДДєДДДДДД>є NIL є<ДДЩ pEnd

і ИНННННј і ИНННННј ИНННННј

і і

АДДДДДДДДДДДДДЩ

Пример. Составить программу, которая формирует очередь, добавляет

в нее произвольное количество компонент, а затем читает все компонен-

ты и выводит их на экран дисплея. В качестве данных взять строку сим-

волов. Ввод данных - с клавиатуры дисплея, признак конца ввода -

строка символов END.

Program QUEUE;

uses Crt;

type

Alfa= String[10];

PComp= ^Comp;

Comp= record

sD:Alfa;

pNext:PComp

end;

var

pBegin, pEnd: PComp;

sC: Alfa;

Procedure CreateQueue(var pBegin,pEnd: PComp; var sC: Alfa);

begin

New(pBegin);

pBegin^.pNext:=NIL;

pBegin^.sD:=sC;

pEnd:=pBegin

end;

Procedure AddQueue(var pEnd:PComp; var sC:Alfa);

var pAux: PComp;

begin

New(pAux);

pAux^.pNext:=NIL;

pEnd^.pNext:=pAux;

pEnd:=pAux;

pEnd^.sD:=sC

end;

Procedure DelQueue(var pBegin: PComp; var sC: Alfa);

begin

sC:=pBegin^.sD;

pBegin:=pBegin^.pNext

end;

begin

Clrscr;

writeln(' ВВЕДИ СТРОКУ ');

readln(sC);

CreateQueue(pBegin,pEnd,sC);

repeat

writeln(' ВВЕДИ СТРОКУ ');

readln(sC);

AddQueue(pEnd,sC)

until sC='END';

writeln(' ***** ВЫВОД РЕЗУЛЬТАТОВ *****');

repeat

DelQueue(pBegin,sC);

writeln(sC);

until pBegin=NIL

end.


40. Линейные списки

В стеки или очереди компоненты можно добавлять тольков какой -

либо один конец структуры данных, это относится и к извлечению компо-

нент.

Связный (линейный) список является структурой данных, в произволь-

но выбранное место которого могут включаться данные, а также изымать-

ся оттуда.

Каждая компонента списка определяется ключом.Обычно ключ - либо

число, либострока символов. Ключ располагается в поле данных компо-

ненты, он может занимать как отдельное поле записи, так и быть частью

поля записи.

Основные отличия связного списка от стека и очереди следующие:

-для чтения доступна любая компонента списка;

-новые компоненты можно добавлять в любое место списка;

-при чтении компонента не удаляется из списка.

Над списками выполняются следующие операции:

-начальное формирование списка (запись первой компоненты);

-добавление компоненты в конец списка;

-чтение компоненты с заданным ключом;

-вставка компоненты в заданное место списка (обычнопосле компо-

ненты с заданным ключом);

-исключение компоненты с заданным ключом из списка.

Для формирования списка и работы с ним необходимо иметь пять пере-

менных типа указатель,первая из которых определяетначало списка,

вторая - конец списка, остальные- вспомогательные.

Описание компоненты списка и переменных типа указатель дадимсле-

дующим образом:

type

PComp= ^Comp;

Comp= record

D:T;

pNext:PComp

end;

var

pBegin, pEnd, pCKey, pPreComp, pAux: PComp;

где pBegin - указатель начала списка, pEnd - указатель конца списка,

pCKey, pPreComp, pAux - вспомогательные указатели.

Начальное формирование списка, добавление компонент в конец списка

выполняется так же, как и при формировании очереди.

ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН» ЙННННН»

є *ДДєДї є D1є є D2є є DN1 є є DNє ЪДєДД* є

ИНННННј і єНННННє єНННННє єНННННє єНННННє і ИНННННј

pBegin АДД>є *ДДєДДД>є *ДДєД....Д>є*ДДєДДД>є NIL є<ДЩ pEnd

ИНННННј ИНННННј ИНННННј ИНННННј

Для чтения и вставки компоненты по ключу необходимо выполнить по-

иск компоненты с заданным ключом:

pCKey:=pBegin;

while (pCKey<>NIL) and (Key<>pCKey^.D) DO

pCKey:=pCKey^.pNext;

Здесь Key - ключ, тип которого совпадает с типом данных компоненты.

После выполнения этих операторов указатель pСKey будет определять

компоненту с заданным ключом или такая компонента не будет найдена.

Пусть pCKey определяет компоненту с заданным ключом. Вставка новой

компоненты выполняется следующими операторами:

New(pAux); ЙННН»

pAux^.D:= DK1; ЪДДєД* є

і ИНННј

і pCKey

і

ЙННН» ЙННН» ЙННН» ЙННН» ЙННН» ЙННН»

є *ДєДДїєD1 є єKeyє єKK1є єDN є ЪДДєД* є

ИНННј і єНННє єНННє єНННє єНННєі ИНННј

pBegin АД>є *ДєД...Д>є *ДєДДДД>є *ДєД...Д>єNILє<ДЩ pEnd

ИНННј ИНННј ИНННј ИНННј

ЙННН» ЙННН»

єDK1єЪДДєД* є

єНННєі ИНННј

є є<ДЩ pAux

ИНННј

pAux^.pNext:=pCKey^.pNext;

pCKey^.pNext:=pAux;

ЙННН»

ЪДДєД* є

і ИНННј

і pCKey

і

ЙННН» ЙННН» ЙННН» ЙННН» ЙННН» ЙННН»

є *ДєДДїєD1 є єKeyє єKK1є єDN є ЪДДєД* є

ИНННј і єНННє єНННє єНННє єНННєі ИНННј

pBegin АД>є *ДєД...Д>є * є є *ДєД...Д>єNILє<ДЩ pEnd

ИНННј ИНННј ИНННј ИНННј

і ^

і і ЙННН» ЙННН»

і і єDK1єЪДДє-* є

і АДДДДДДДДДДєНННє і ИНННј

АДДДДДДДДДДДДДДДДДДД>єД* є<ДЩ pAux

ИНННј

Для удаления компоненты с заданным ключом необходимо при поиске

нужной компоненты помнить адрес предшествующей:

pCKey:=pBegin;

while (pCKey<>NIL) and (Key<>pCKey^.D) do

begin

pPreComp:=pCKey;

pCKey:=pCKey^.pNext

end;

Здесь указатель pCKey определяет компоненту с заданным ключом, указа-

тель pPreComp содержит адрес предидущей компоненты.

Удаление компоненты с ключом Key выполняется оператором:

pPreComp^.pNext:=pCKey^.pNext;

pPreComp pCKey

ЙННН» ЙННН»

є * є є * є

ИНННј ИНННј

і і

і і

і і

ЙННН» ЙННН» ЙННН» ЙННН» ЙННН» ЙННН» ЙННН»

є *ДєДДїєD1 є єKK1є єKeyє єKK2є єDN єЪДДєД* є

ИНННј і єНННє єНННє єНННє єНННє єНННє і ИНННј

pBegin АД>є *ДєД...Д>є *ДєДї є *ДєДДД>є *ДєД...Д>єNILє<ДЩ pEnd

ИНННј ИНННј і ИНННј ИНННј ИНННј

і ^

і і

АДДДДДДДДДДДДДДЩ

Пример. Составить программу, которая формирует список, добавляет в

него произвольное количество компонент, выполняет вставку и удаление

компоненты по ключу,а затем читает и выводит весь списокна экран

дисплея. В качестве данных взять строку символов.Ввод данных - с

клавиатуры дисплея, признак конца ввода - строка символов END.

Program LISTLINKED;

uses Crt;

type

Alfa= String[10];

PComp= ^Comp;

Comp= record

sD:Alfa;

pNext:PComp

end;

var

pBegin, pEnd, pAux, pCKey, pPreComp: PComp;

sC, sKey: Alfa;

bCond: Boolean;

Procedure CreateLL(var pBegin,pEnd: PComp; var sC: Alfa);

begin

New(pBegin);

pBegin^.pNext:=NIL;

pBegin^.sD:=sC;

pEnd:=pBegin

end;

Procedure AddLL(var pEnd: PComp; var sC: Alfa);

var pAux: PComp;

begin

New(pAux);

pAux^.pNext:=NIL;

pEnd^.pNext:=pAux;

pEnd:=pAux;

pEnd^.sD:=sC

end;

Procedure Find(var sKey: Alfa; var pBegin,pCKey,pPreComp: PComp;

var bCond: Boolean);

begin

pCKey:=pBegin;

while (pCKey <> NIL) and (sKey <> pCKey^.D) do

begin

pPreComp:=pCKey;

pCKey:=pCKey^.pNext

end;

if (pCKey = NIL) and (sKey <> pCKey^.sD) then bCond:=FALSE

else bCond:=TRUE

end;

Procedure InsComp(var sKey,sC: Alfa);

var pAux:PComp;

begin

Find(sKey,pBegin,pCKey,pPreComp,bCond);

New(pAux);

pAux^.sD:=sC;

pAux^.pNext:=pCKey^.pNext;

pCKey^.pNext:=pAux

end;

Procedure DelComp(var sKey: Alfa; var pBegin: PComp);

begin

Find(sKey,pBegin,pCKey,pPreComp,bCond);

pPreComp^.pNext:=pCKey^.pNext

end;

begin

ClrScr;

writeln(' ВВЕДИ СТРОКУ ');

readln(sC);

CreateLL(pBegin,pEnd,sC);

repeat

writeln('ВВЕДИ СТРОКУ ');

readln(sC);

AddLL(pEnd,sC)

until sC='END';

writeln(' ***** ВЫВОД ИСХОДНОГО СПИСКА *****');

pAux:=pBegin;

repeat

writeln(pAux^.sD);

pAux:=pAux^.pNext;

until pAux=NIL;

writeln;

writeln('ВВЕДИ КЛЮЧ ДЛЯ ВСТАВКИ СТРОКИ');

readln(sKey);

writeln('ВВЕДИ ВСТАВЛЯЕМУЮ СТРОКУ');

readln(sC);

InsComp(sKey,sC);

writeln;

writeln('ВВЕДИ КЛЮЧ УДАЛЯЕМОЙ СТРОКИ');

readln(sKey);

DelComp(sKey,pBegin);

writeln;

writeln(' ***** ВЫВОД ИЗМЕНЕННОГО СПИСКА *****');

pAux:=pBegin;

repeat

writeln(pAux^.sD);

pAux:=pAux^.pNext;

until pAux=NIL

end.


Pascal. Курс лекций.
  1. Основные понятия алгоритмического языка
  2. Основные символы
  3. Элементарные конструкции
  4. Концепция типа для данных
  5. Стандартные типы данных
  6. Константы
  7. Переменные. Инициализация переменных.
  8. Выражения
  9. Оператор присваивания
  10. Операторы ввода и вывода
  11. Структрура программы
  12. Битовая арифметика
  13. Оператор перехода
  14. Элементы программирования
  15. Условный оператор
  16. Цикл с предусловием {}
  17. Цикл с постусловием {}
  18. Цикл с параметром {}
  19. Операторы завершения цикла
  20. Оператор варианта
  21. Перечисляемый тип данных
  22. Интервальный тип данных
  23. Массивы
  24. Строки
  25. Процедуры и функции
  26. Операторы выхода
  27. Модули
  28. Множества
  29. Записи
  30. Файлы
  31. Текстовые файлы
  32. Компонентные файлы
  33. Бестиповые файлы
  34. Последовательный и прямой доступ
  35. Указатели
  36. Динамические переменные
  37. Динамические структуры данных
  38. Стеки
  39. Очереди
  40. Линейные списки


Реляционные базы данных и язык SQL

Глава 1. Реляционные базы данных и язык SQL
1.1. Реляционная база данных

Реляционная база данных представляется пользователю как совокупность таблиц и ничего кроме таблиц. На рис.1.1 приведен пример реляционной базы данных ПАНСИОН. Этот простой пример используется для иллюстрации большинства вопросов, рассматриваемых в нашей книге. Поэтому советуем потратить немного времени, чтобы хорошо с ним разобраться*.

Кладовая пансионата периодически пополняется продуктами из списка, часть которого показана в таблице Продукты. Каждый продукт имеет кроме названия (столбец Продукт) уникальный номер этого продукта (столбец ПР). Химический состав продуктов приведен для 1 кг их съедобной части: основные пищевые вещества (белки, жиры и углеводы) даны в граммах, а минеральные вещества (калий, кальций, натрий) и витамины (B2, PP, C) - в миллиграммах.

В таблице Блюда представлены уникальные номера блюд (столбец БЛ), их названия, коды видов (см. таблицу Вид_блюд), основной продукт (столбец Основа), масса порции в граммах (столбец Выход) и приведенная стоимость в копейках приготовления одной порции (столбец Труд).

В таблице Рецепты приведена технология приготовления блюд. Их выделение в отдельную таблицу произведено потому, что одно и то же блюдо может иметь несколько разных рецептов.

Таблица Состав связывает между собой таблицы Блюда и Продукты, оговаривая, какая масса (в граммах) того или иного продукта (столбец Вес) должна входить в состав одной порции блюда. Так, порция блюда с номером 12 (Суп молочный) должна состоять из 350 г продукта с номером 7 (Молоко), 35 г продукта с номером 13 (Рис), 5 г продукта с номером 3 (Масло) и 5 г продукта с номером 16 (Сахар).

Шеф-повар ежедневно получает от завхоза сведения о количестве в килограммах имеющихся продуктов и их текущей стоимости (столбцы К_во и Стоимость таблицы Наличие). Используя эти сведения он определяет по таблице Состав перечень тех блюд, которые можно приготовить из этих продуктов, а также калорийность и стоимость таких блюд. При этом стоимость блюда складывается из стоимости и массы продуктов, необходимых для приготовления одной его порции, а также из трудозатрат на ее приготовление (см. таблицу Блюда). Калорийность же определяется по массе и калорийности каждого из продуктов блюда. (Для получения значения калорийности продукта исходят из того, что при окислении 1 г углеводов или белков в организме освобождается в среднем 4.1 ккал, а при окислении 1 г жиров - 9.3 ккал.)

Блюда Рецепты
БЛ | Блюдо | В | Основа | Выход | Труд
1 | Салат летний | З | Овощи | 200. | 3
2 | Салат мясной | З | Мясо | 200. | 4
3 | Салат витаминный | З | Овощи | 200. | 4
4 | Салат рыбный | З | Рыба | 200. | 4
5 | Паштет из рыбы | З | Рыба | 120. | 5
6 | Мясо с гарниром | З | Мясо | 250. | 3
7 | Сметана | З | Молоко | 140. | 1
8 | Творог | З | Молоко | 140. | 2
9 | Суп харчо | С | Мясо | 500. | 5
10 | Суп-пюре из рыбы | С | Рыба | 500. | 6
11 | Уха из судака | С | Рыба | 500. | 5
12 | Суп молочный | С | Молоко | 500. | 3
13 | Бастурма | Г | Мясо | 300. | 5
14 | Бефстроганов | Г | Мясо | 210. | 6
15 | Судак по-польски | Г | Рыба | 160. | 5
16 | Драчена | Г | Яйца | 180. | 4
17 | Морковь с рисом | Г | Овощи | 260. | 3
18 | Сырники | Г | Молоко | 220. | 4
19 | Омлет с луком | Г | Яйца | 200. | 5
20 | Каша рисовая | Г | Крупа | 210. | 4
21 | Пудинг рисовый | Г | Крупа | 160. | 6
22 | Вареники ленивые | Г | Молоко | 220. | 4
23 | Помидоры с луком | Г | Овощи | 260. | 4
24 | Суфле из творога | Г | Молоко | 280. | 6
25 | Рулет с яблоками | Д | Фрукты | 200. | 5
26 | Яблоки печеные | Д | Фрукты | 160. | 3
27 | Суфле яблочное | Д | Фрукты | 220. | 6
28 | Крем творожный | Д | Молоко | 160. | 4
29 | "Утро" | Н | Фрукты | 200. | 5
30 | Компот | Н | Фрукты | 200. | 2
31 | Молочный напиток | Н | Молоко | 200. | 2
32 | Кофе черный | Н | Кофе | 200. | 1
33 | Кофе на молоке | Н | Кофе | 200. | 2

БЛ | Рецепт
1 | Помидоры ...
2 | Вареное ...
3 | Зелень ме...
4 | Вареные р...
5 | Филе суда...
6 | Мясо варе...
7 | Сметану п...
8 | Протертый ..
9 | Грудинку ...
10 | Филе суда...
11 | Судак очи...
12 | Промытый ...
13 | Мясо наре...
14 | Говядину ...
15 | Подготовл...
16 | Сырые яйц...
17 | Нарезать ...
18 | В протерт...
19 | К свежим ...
20 | Рис свари...
21 | Готовую р...
22 | В протерт...
23 | Спассеров...
24 | В протерт...
25 | Очистить ...
26 | Не прорез...
27 | Запеченны...
28 | Яйца разм...
29 | Очищенную ..
30 | Яблоки оч...
31 | Яблоки на...
32 | Кофеварку ..
33 | Сварить ч...

Поставщики
ПС | Название | Статус | Город | Адрес | Телефон
1 | СЫТНЫЙ | рынок | Ленинград | Сытнинская, 3 | 2329916
2 | ПОРТОС | кооператив | Резекне | Садовая, 27 | 317664
3 | ШУШАРЫ | совхоз | Пушкин | Новая, 17 | 4705038
4 | ТУЛЬСКИЙ | универсам | Ленинград | Тульская, 3 | 2710837
5 | УРОЖАЙ | коопторг | Луга | Песчаная, 19 | 789000
6 | ЛЕТО | агрофирма | Ленинград | Пулковское ш.,8 | 2939729
7 | ОГУРЕЧИК | ферма | Паневежис | Укмерге, 15 | 127331
8 | КОРЮШКА | кооператив | Йыхви | Нарвское ш., 64 | 432123

Состав Поставки
БЛ | ПР | Вес | БЛ | ПР | Вес | БЛ | ПР | Вес | БЛ | ПР | Вес
1 | 11 | 100 | 9 | 11 | 25 | 16 | 7 | 35 | 24 | 8 | 80
1 | 15 | 80 | 9 | 13 | 35 | 16 | 6 | 15 | 24 | 7 | 100
1 | 12 | 5 | 9 | 12 | 15 | 16 | 14 | 9 | 24 | 5 | 40
1 | 4 | 15 | 9 | 3 | 15 | 16 | 3 | 5 | 24 | 6 | 30
2 | 1 | 65 | 10 | 2 | 70 | 17 | 9 | 150 | 24 | 16 | 20
2 | 9 | 40 | 10 | 7 | 250 | 17 | 7 | 50 | 24 | 3 | 10
2 | 11 | 35 | 10 | 3 | 20 | 17 | 13 | 25 | 24 | 14 | 10
2 | 12 | 20 | 10 | 14 | 15 | 17 | 3 | 20 | 25 | 15 | 120
2 | 5 | 20 | 10 | 12 | 5 | 17 | 12 | 10 | 25 | 16 | 35
2 | 4 | 20 | 11 | 2 | 100 | 17 | 14 | 5 | 25 | 14 | 30
3 | 11 | 55 | 11 | 9 | 20 | 18 | 8 | 140 | 25 | 8 | 20
3 | 15 | 55 | 11 | 10 | 20 | 18 | 6 | 30 | 25 | 3 | 20
3 | 6 | 50 | 11 | 3 | 5 | 18 | 14 | 15 | 26 | 15 | 150
3 | 12 | 20 | 11 | 12 | 2 | 18 | 5 | 10 | 26 | 16 | 20
3 | 10 | 15 | 12 | 7 | 350 | 18 | 16 | 15 | 26 | 3 | 2
3 | 16 | 5 | 12 | 13 | 35 | 19 | 5 | 120 | 27 | 15 | 50
4 | 2 | 50 | 12 | 3 | 5 | 19 | 7 | 45 | 27 | 7 | 150
4 | 11 | 50 | 12 | 16 | 5 | 19 | 10 | 20 | 27 | 5 | 80
4 | 4 | 40 | 13 | 1 | 180 | 19 | 3 | 15 | 27 | 16 | 35
4 | 9 | 35 | 13 | 11 | 100 | 20 | 13 | 50 | 27 | 3 | 2
4 | 5 | 20 | 13 | 10 | 40 | 20 | 7 | 75 | 28 | 8 | 100
4 | 12 | 5 | 13 | 12 | 20 | 20 | 15 | 75 | 28 | 5 | 20
5 | 2 | 80 | 13 | 3 | 5 | 20 | 16 | 10 | 28 | 6 | 20
5 | 9 | 40 | 14 | 1 | 90 | 20 | 3 | 5 | 28 | 16 | 15
5 | 3 | 25 | 14 | 7 | 50 | 21 | 13 | 70 | 28 | 3 | 10
5 | 12 | 5 | 14 | 6 | 20 | 21 | 6 | 30 | 29 | 15 | 150
6 | 1 | 80 | 14 | 10 | 10 | 21 | 3 | 20 | 29 | 9 | 200
6 | 11 | 150 | 14 | 3 | 5 | 21 | 5 | 20 | 29 | 16 | 15
6 | 4 | 30 | 14 | 12 | 5 | 21 | 16 | 15 | 30 | 15 | 70
6 | 12 | 10 | 14 | 14 | 3 | 22 | 8 | 140 | 30 | 16 | 10
7 | 6 | 125 | 15 | 2 | 100 | 22 | 6 | 30 | 31 | 7 | 150
7 | 16 | 15 | 15 | 9 | 20 | 22 | 14 | 20 | 31 | 15 | 150
8 | 8 | 75 | 15 | 5 | 20 | 22 | 16 | 15 | 31 | 16 | 25
8 | 6 | 50 | 15 | 3 | 20 | 22 | 5 | 8 | 32 | 17 | 8
8 | 16 | 15 | 15 | 10 | 10 | 23 | 11 | 250 | 33 | 17 | 8
9 | 1 | 80 | 15 | 12 | 5 | 23 | 10 | 65 | 33 | 16 | 25
9 | 10 | 30 | 16 | 5 | 120 | 23 | 3 | 20 | 33 | 7 | 75

ПС | ПР | Цена | К_во
1 | 9
1 | 11 | 1.50 | 50
1 | 12 | 3.00 | 10
1 | 15 | 2.00 | 170
2 | 1 | 3.60 | 300
2 | 3
2 | 5 | 1.80 | 100
2 | 6 | 3.60 | 80
2 | 8
3 | 7 | 0.40 | 200
3 | 9
3 | 12 | 2.50 | 20
3 | 15 | 1.50 | 200
4 | 2
4 | 4 | 2.04 | 50
4 | 13 | 0.88 | 150
4 | 14
4 | 16 | 0.94 | 200
4 | 17 | 4.50 | 50
5 | 4 | 3.00 | 50
5 | 9
5 | 10 | 0.50 | 130
5 | 11
5 | 13 | 1.20 | 40
5 | 14 | 0.50 | 70
5 | 16 | 1.00 | 50
6 | 10 | 0.70 | 90
6 | 11
6 | 12
7 | 1 | 4.20 | 70
7 | 3 | 4.00 | 250
7 | 6 | 2.20 | 140
7 | 7
7 | 8 | 1.00 | 150
8 | 2
8 | 5 | 2.00 | 70
8 | 11 | 1.00 | 100

Продукты Наличие
ПР | Продукт | Белки | Жиры | Углев | K | Ca | Na | B2 | PP | C
1 | Говядина | 189. | 124. | 0. | 3150 | 90 | 600 | 1.5 | 28. | 0
2 | Судак | 190. | 80. | 0. | 1870 | 270 | 0 | 1.1 | 10. | 30
3 | Масло | 60. | 825. | 90. | 230 | 220 | 740 | 0.1 | 1. | 0
4 | Майонез | 31. | 670. | 26. | 480 | 280 | 0 | 0. | 0. | 0
5 | Яйца | 127. | 115. | 7. | 1530 | 550 | 710 | 4.4 | 1.9 | 0
6 | Сметана | 26. | 300. | 28. | 950 | 850 | 320 | 1. | 1. | 2
7 | Молоко | 28. | 32. | 47. | 1460 | 1210 | 1500 | 1.3 | 1. | 10
8 | Творог | 167. | 90. | 13. | 1120 | 1640 | 1410 | 2.7 | 4. | 5
9 | Морковь | 13. | 1. | 70. | 2000 | 510 | 210 | 0.7 | 9.9 | 50
10 | Лук | 17. | 0. | 95. | 1750 | 310 | 180 | 0.2 | 2. | 100
11 | Помидоры | 6. | 0. | 42. | 290 | 140 | 400 | 0.4 | 5.3 | 250
12 | Зелень | 9. | 0. | 20. | 340 | 275 | 75 | 1.2 | 4. | 380
13 | Рис | 70. | 6. | 773. | 540 | 240 | 260 | 0.4 | 16. | 0
14 | Мука | 106. | 13. | 732. | 1760 | 240 | 120 | 1.2 | 22. | 0
15 | Яблоки | 4. | 0. | 113. | 2480 | 160 | 260 | 0.3 | 3. | 130
16 | Сахар | 0. | 0. | 998. | 30 | 20 | 10 | 0. | 0. | 0
17 | Кофе | 127. | 36. | 9. | 9710 | 180 | 180 | 0.3 | 1.8 | 0

ПР | К_во | Стоим
1 | 108 | 429.84
2 | 0 | 0.00
3 | 73 | 274.61
4 | 39 | 97.46
5 | 61 | 111.83
6 | 88 | 206.60
7 | 214 | 83.08
8 | 92 | 82.80
9 | 0 | 0.00
10 | 77 | 46.30
11 | 46 | 51.70
12 | 13 | 34.96
13 | 54 | 51.14
14 | 91 | 43.77
15 | 117 | 189.92
16 | 98 | 96.14
17 | 37 | 166.50

Вид_блюд Трапезы Меню Выбор Выбрано
В | Вид
З | Закуска
С | Суп
Г | Горячее
Д | Десерт
Н | Напиток

Т | Трапеза
1 | Завтрак
2 | Обед
3 | Ужин

Т | В | БЛ | Т | В | БЛ | Т | В | БЛ
1 | З | 3 | 2 | З | 1 | 3 | З | 6
1 | З | 6 | 2 | З | 6 | 3 | З | 8
1 | Г | 19 | 2 | С | 9 | 3 | Г | 20
1 | Г | 21 | 2 | С | 12 | 3 | Г | 16
1 | Н | 31 | 2 | Г | 14 | 3 | Н | 30
1 | Н | 32 | 2 | Г | 16 | 3 | Н | 31
 |  |  | 2 | Г | 18 |
 |  |  | 2 | Д | 26 |
 |  |  | 2 | Д | 28 |
СМ | Т | В | БЛ
2 | 1 | З | 3
2 | 1 | Г | 19
2 | 1 | Н | 31
2 | 2 | З | 1
2 | 2 | С | 12
2 | 2 | Г | 16
2 | 2 | Д | 26
2 | 3 | З | 8
2 | 3 | Г | 21
2 | 3 | Н | 32

СМ | Т | БЛ
1 | 1 | 3
1 | 1 | 21
.
2 | 2 | 16
2 | 2 | 26
.
3 | 1 | 6
.
32 | 3 | 30

Рис. 1.1. Основные таблицы базы данных ПАНСИОН

Учитывая примерную стоимость и необходимую калорийность дневного рациона отдыхающих, шеф-повар составляет меню на следующий день. В этом меню (таблица Меню) предлагается по несколько альтернативных блюд каждого вида (таблица Вид_блюд) и для каждой трапезы (таблица Трапезы). Перед завтраком каждый отдыхающий вводит в ЭВМ номер закрепленного за ним места в столовой пансионата (столбец СМ в таблице Выбор) и желаемый набор блюд для каждой из трапез следующего дня (в примере таблица заполнялась отдыхающим, сидящим на месте с номером 2). Таблицы Выбор объединяются по мере их создания в общую таблицу Выбрано, по которой определяют, сколько порций того или иного блюда надо приготовить для каждой трапезы.

Завхоз связан с поставщиками продуктов, сведения о которых хранятся в таблице Поставщики. Эта таблица содержит уникальный номер поставщика (столбец ПС), его название, статус, месторасположение и телефон.

Таблица Поставки связывает между собой таблицы Продукты и Поставщики, оговаривая, какое количество продукта (столбец К_во) и по какой цене поставил тот или иной поставщик. Отсутствие в строке цены и количества говорит о том, что поставщик ПС может поставлять продукт ПР, но в данный момент не осуществил такой поставки.

Легко заметить, что все таблицы примера (как и все таблицы любой реляционной базы данных) состоят из строки заголовков столбцов и одной или более строк значений данных под этими заголовками. Эти столбцы и строки должны иметь следующие свойства:

  • всякому столбцу таблицы присвоено имя, которое должно быть уникальным для этой таблицы;
  • столбцы таблицы упорядочиваются слева направо, т.е. столбец 1, столбец 2, ..., столбец n. С математической точки зрения это утверждение некорректно, потому что в реляционной системе столбцы не упорядочены. Однако с точки зрения пользователя, порядок, в котором определены имена столбцов, становится порядком, в котором должны вводиться в них данные, если не предварять при вводе каждое значение именем соответствующего столбца (подробнее это описано в Приложении А литературы [2]);
  • строки таблицы не упорядочены (их последовательность определяется лишь последовательностью ввода в таблицу);
  • в поле на пересечении строки и столбца любой таблицы всегда имеется только одно значение данных и никогда не должно быть множества значений (правда, это "атомарное" значение может быть достаточно объемным, например, таким, как рецепт блюда);
  • всем строкам таблицы соответствует одно и то же множество столбцов, хотя в определенных столбцах любая строка может содержать пустые значения (NULL-значения), т.е. может не иметь значений для этих столбцов;
  • все строки таблицы обязательно отличаются друг от друга хотя бы единственным значением, что позволяет однозначно идентифицировать любую строку такой таблицы;
  • при выполнении операций с таблицей ее строки и столбцы можно обрабатывать в любом порядке безотносительно к их информационному содержанию.

Почему же база данных, составленная из таких таблиц, называется реляционной? А потому, что отношение - relation - просто математический термин для обозначения неупорядоченной совокупности однотипных записей или таблиц определенного специфического вида, описанного выше. Таким образом, можно, например, сказать, что база данных ПАНСИОН состоит из одиннадцати отношений.

Реляционные системы берут свое начало в математической теории множеств. Они были предложены в конце 1968 года доктором Э.Ф.Коддом из фирмы IBM, который первым осознал, что можно использовать математику для придания надежной основы и строгости области управления базами данных.

Нечеткость многих терминов, используемых в сфере обработки данных, заставила Кодда отказаться от них и придумать новые или дать более точные определения существующим. Так, он не мог использовать широко распространенный термин "запись", который в различных ситуациях может означать экземпляр записи, либо тип записей, запись в стиле Кобола (которая допускает повторяющиеся группы) или плоскую запись (которая их не допускает), логическую запись или физическую запись, хранимую запись или виртуальную запись и т.д. Вместо этого он использовал термин "кортеж длины n" или просто "кортеж", которому дал точное определение. В литературе [2,3] можно подробно познакомиться с терминологией реляционных баз данных, а здесь мы будем использовать неформальные их эквиваленты:

таблица | для отношения,
строка или запись | для кортежа,
столбец или поле | для атрибута.

Мы также принимаем, по определению, что "запись" означает "экземпляр записи", а "поле" означает "имя и тип поля".

* Так как иллюстративная база данных создавалась для лекционного курса в 1988 году, когда существовали "смешные" цены, а также исчезнувшие названия статусов (коопторг) и городов (Ленинград), то автор пытался несколько раз ее модифицировать. Однако поняв, что изменение цен, статусов и названий идет быстрее, чем подготовка и, тем более, выпуск издания, он решил сохранить в книге старые цены и названия.


Почему SQL?

1.2. Почему SQL?

Все языки манипулирования данными (ЯМД), созданные до появления реляционных баз данных и разработанные для многих систем управления базами данных (СУБД) персональных компьютеров, были ориентированы на операции с данными, представленными в виде логических записей файлов. Это требовало от пользователей детального знания организации хранения данных и достаточных усилий для указания не только того, какие данные нужны, но и того, где они размещены и как шаг за шагом получить их.

Рассматриваемый же ниже непроцедурный язык SQL (Structured Query Language - структуризованный язык запросов) ориентирован на операции с данными, представленными в виде логически взаимосвязанных совокупностей таблиц. Особенность предложений этого языка состоит в том, что они ориентированы в большей степени на конечный результат обработки данных, чем на процедуру этой обработки. SQL сам определяет, где находятся данные, какие индексы и даже наиболее эффективные последовательности операций следует использовать для их получения: не надо указывать эти детали в запросе к базе данных.

Для иллюстрации различий между ЯМД рассмотрим следующую ситуацию. Пусть, например, вы собираетесь посмотреть кинофильм и хотите воспользоваться для поездки в кинотеатр услугами такси. Одному шоферу такси достаточно сказать название фильма - и он сам найдет вам кинотеатр, в котором показывают нужный фильм. (Подобным же образом, самостоятельно, отыскивает запрошенные данные SQL.)

Для другого шофера такси вам, возможно, потребуется самому узнать, где демонстрируется нужный фильм и назвать кинотеатр. Тогда водитель должен найти адрес этого кинотеатра. Может случиться и так, что вам придется самому узнать адрес кинотеатра и предложить водителю проехать к нему по таким-то и таким-то улицам. В самом худшем случае вам, может быть, даже придется по дороге давать указания: "Повернуть налево... проехать пять кварталов... повернуть направо...". (Аналогично больший или меньший уровень детализации запроса приходится создавать пользователю в разных СУБД, не имеющих языка SQL.)

Появление теории реляционных баз данных и предложенного Коддом языка запросов "alpha", основанного на реляционном исчислении [2, 3], инициировало разработку ряда языков запросов, которые можно отнести к двум классам:

  1. Алгебраические языки, позволяющие выражать запросы средствами специализированных операторов, применяемых к отношениям (JOIN - соединить, INTERSECT - пересечь, SUBTRACT - вычесть и т.д.).
  2. Языки исчисления предикатов представляют собой набор правил для записи выражения, определяющего новое отношение из заданной совокупности существующих отношений. Другими словами исчисление предикатов есть метод определения того отношения, которое нам желательно получить (как ответ на запроc) из отношений, уже имеющихся в базе данных.

Разработка, в основном, шла в отделениях фирмы IBM (языки ISBL, SQL, QBE) и университетах США (PIQUE, QUEL) [3]. Последний создавался для СУБД INGRES (Interactive Graphics and Retrieval System), которая была разработана в начале 70-х годов в Университете шт. Калифорния и сегодня входит в пятерку лучших профессиональных СУБД. Сегодня из всех этих языков полностью сохранились и развиваются QBE (Query-By-Example - запрос по образцу) и SQL, а из остальных взяты в расширение внутренних языков СУБД только наиболее интересные конструкции.

В начале 80-х годов SQL "победил" другие языки запросов и стал фактическим стандартом таких языков для профессиональных реляционных СУБД. В 1987 году он стал международным стандартом языка баз данных и начал внедряться во все распро-страненные СУБД персональных компьютеров. Почему же это произошло?

Непрерывный рост быстродействия, а также снижение энергопотребления, размеров и стоимости компьютеров привели к резкому расширению возможных рынков их сбыта, круга пользователей, разнообразия типов и цен. Естественно, что расширился спрос на разнообразное программное обеспечение.

Борясь за покупателя, фирмы, производящие программное обеспечение, стали выпускать на рынок все более и более интеллектуальные и, следовательно, объемные программные комплексы. Приобретая (желая приобрести) такие комплексы, многие организации и отдельные пользователи часто не могли разместить их на собственных ЭВМ, однако не хотели и отказываться от нового сервиса. Для обмена информацией и ее обобществления были созданы сети ЭВМ, где обобществляемые программы и данные стали размещать на специальных обслуживающих устройствах - файловых серверах.

СУБД, работающие с файловыми серверами, позволяют множеству пользователей разных ЭВМ (иногда расположенных достаточно далеко друг от друга) получать доступ к одним и тем же базам данных. При этом упрощается разработка различных автоматизированных систем управления организациями, учебных комплексов, информационных и других систем, где множество сотрудников (учащихся) должны использовать общие данные и обмениваться создаваемыми в процессе работы (обучения). Однако при такой идеологии вся обработка запросов из программ или с терминалов пользовательских ЭВМ выполняется на этих же ЭВМ. Поэтому для реализации даже простого запроса ЭВМ часто должна считывать из файлового сервера и (или) записывать на сервер целые файлы, что ведет к конфликтным ситуациям и перегрузке сети.

Для исключения указанных и некоторых других недостатков была предложена технология "Клиент-Сервер", по которой запросы пользовательских ЭВМ (Клиент) обрабатываются на специальных серверах баз данных (Сервер), а на ЭВМ возвращаются лишь результаты обработки запроса. При этом, естественно, нужен единый язык общения с Сервером и в качестве такого языка выбран SQL. Поэтому все современные версии профессиональных реляционных СУБД (DB2, Oracle, Ingres, Informix, Sybase, Progress, Rdb) и даже нереляционных СУБД (например, Adabas) используют технологию "Клиент-Сервер" и язык SQL. К тому же приходят разработчики СУБД персональных ЭВМ, многие из которых уже сегодня снабжены языком SQL.

Бытует мнение: Поскольку большая часть запросов формулируется на SQL, практически безразлично, что это за СУБД - был бы SQL.

Реализация в SQL концепции операций, ориентированных на табличное представление данных, позволило создать компактный язык с небольшим (менее 30) набором предложений. SQL может использоваться как интерактивный (для выполнения запросов) и как встроенный (для построения прикладных программ). В нем существуют:

  • предложения определения данных (определение баз данных, а также определение и уничтожение таблиц и индексов);
  • запросы на выбор данных (предложение SELECT);
  • предложения модификации данных (добавление, удаление и изменение данных);
  • предложения управления данными (предоставление и отмена привилегий на доступ к данным, управление транзакциями и другие). Кроме того, он предоставляет возможность выполнять в этих предложениях:
  • арифметические вычисления (включая разнообразные функциональные преобразования), обработку текстовых строк и выполнение операций сравнения значений арифметических выражений и текстов;
  • упорядочение строк и (или) столбцов при выводе содержимого таблиц на печать или экран дисплея;
  • создание представлений (виртуальных таблиц), позволяющих пользователям иметь свой взгляд на данные без увеличения их объема в базе данных;
  • запоминание выводимого по запросу содержимого таблицы, нескольких таблиц или представления в другой таблице (реляционная операция присваивания).
  • агрегатирование данных: группирование данных и применение к этим группам таких операций, как среднее, сумма, максимум, минимум, число элементов и т.п.

В SQL используются следующие основные типы данных, форматы которых могут несколько различаться для разных СУБД:

INTEGER
- целое число (обычно до 10 значащих цифр и знак);
SMALLINT
- "короткое целое" (обычно до 5 значащих цифр и знак);
DECIMAL(p,q)
- десятичное число, имеющее p цифр (0 < p < 16) и знак; с помощью q задается число цифр справа от десятичной точки (q < p, если q = 0, оно может быть опущено);
FLOAT
- вещественное число с 15 значащими цифрами и целочисленным порядком, определяемым типом СУБД;
CHAR(n)
- символьная строка фиксированной длины из n символов (0 < n < 256);
VARCHAR(n)
- символьная строка переменной длины, не превышающей n символов (n > 0 и разное в разных СУБД, но не меньше 4096);
DATE
- дата в формате, определяемом специальной командой (по умолчанию mm/dd/yy); поля даты могут содержать только реальные даты, начинающиеся за несколько тысячелетий до н.э. и ограниченные пятым-десятым тысячелетием н.э.;
TIME
- время в формате, определяемом специальной командой, (по умолчанию hh.mm.ss);
DATETIME
- комбинация даты и времени;
MONEY
- деньги в формате, определяющем символ денежной единицы ($, руб, ...) и его расположение (суффикс или префикс), точность дробной части и условие для показа денежного значения.

В некоторых СУБД еще существует тип данных LOGICAL, DOUBLE и ряд других. СУБД INGRES предоставляет пользователю возможность самостоятельного определения новых типов данных, например, плоскостные или пространственные координаты, единицы различных метрик, пяти- или шестидневные недели (рабочая неделя, где сразу после пятницы или субботы следует понедельник), дроби, графика, большие целые числа (что стало очень актуальным для российских банков) и т.п.

Ориентированный на работу с таблицами SQL не имеет достаточных средств для создания сложных прикладных программ. Поэтому в разных СУБД он либо используется вместе с языками программирования высокого уровня (например, такими как Си или Паскаль), либо включен в состав команд специально разработанного языка СУБД (язык систем dBASE, R:BASE и т.п.). Унификация полных языков современных профессиональных СУБД достигается за счет внедрения объектно-ориентированного языка четвертого поколения 4GL. Последний позволяет организовывать циклы, условные предложения, меню, экранные формы, сложные запросы к базам данных с интерфейсом, ориентированным как на алфавитно-цифровые терминалы, так и на оконный графический интерфейс (X-Windows, MS-Windows).


Таблицы SQL

1.3. Таблицы SQL

До сих пор понятие "таблица", как правило, связывалось с реальной или базовой таблицей, т.е. c таблицей, для каждой строки которой в действительности имеется некоторый двойник, хранящийся в физической памяти машины (рис.1.2). Однако SQL использует и создает ряд виртуальных (как будто существующих) таблиц: представлений, курсоров и неименованных рабочих таблиц, в которых формируются результаты запросов на получение данных из базовых таблиц и, возможно, представлений. Это таблицы, которые не существуют в базе данных, но как бы существуют с точки зрения пользователя.

Базовые таблицы создаются с помощью предложения CREATE TABLE (создать таблицу), подробное описание которого приведено в главе 5. Здесь же приведем пример предложения для создания описания таблицы Блюда:

Рис. 1.2. База данных в восприятии пользователя

CREATE TABLE Блюда
    (БЛ	SMALLINT,
    Блюдо	CHAR (70),
    В		CHAR (1),
    Основа	CHAR (10),
    Выход	FLOAT,
    Труд	SMALLINT);

Предложение CREAT TABLE специфицирует имя базовой таблицы, которая должна быть создана, имена ее столбцов и типы данных для этих столбцов (а также, возможно, некоторую дополнительную информацию, не иллюстрируемую данным примером). CREAT TABLE - выполняемое предложение. Если его ввести с терминала, система тотчас построит таблицу Блюда, которая сначала будет пустой: она будет содержать только строку заголовков столбцов, но не будет еще содержать никаких строк с данными. Однако можно немедленно приступить к вставке таких строк данных, возможно, с помощью предложения INSERT и создать таблицу, аналогичную таблице Блюда рис.1.1.

Если теперь потребовалось узнать какие овощные блюда может приготовить повар пансионата, то можно набрать на терминале следующий текст запроса:

SELECT	БЛ,Блюдо
FROM 	Блюда
WHERE	Основа = 'Овощи';
и мгновенно получить на экране следующий результат его реализации:

БЛ | Блюдо
1 | Салат летний
3 | Салат витаминный
17 | Морковь с рисом
23 | Помидоры с луком

Для выполнения этого предложения SELECT (выбрать), подробное описание которого будет дано в главах 2 и 3, СУБД должна сначала сформировать пустую рабочую таблицу, состоящую из столбцов БЛ и Блюдо, тип данных которых должен совпадать с типом данных аналогичных столбцов базовой таблицы Блюда. Затем она должна выбрать из таблицы Блюда все строки, у которых в столбце Основа хранится слово Овощи, выделить из этих строк столбцы БЛ и Блюдо и загрузить укороченные строки в рабочую таблицу. Наконец, СУБД должна выполнить процедуры по организации вывода содержимого рабочей таблицы на экран терминала (при этом если в рабочей таблице содержится более 20-24 строк, она должна использовать процедуры постраничного вывода и т.п.). После выполнения запроса СУБД должна уничтожить рабочую таблицу.

Если, например, надо получить значение калорийности всех овощей, включенных в таблицу Продукты, то можно набрать на терминале запрос

SELECT	Продукт, Белки, Жиры, Углев,
    ((Белки+Углев)*4.1+Жиры*9.3)
FROM 	Продукты
WHERE	Продукт IN ('Морковь','Лук','Помидоры','Зелень');
и получить на экране следующий результат его реализации:
Продукт | Белки | Жиры | Углев | ((Белки+Углев)*4.1+Жиры*9.3)
Морковь | 13. | 1. | 70. | 349.6
Лук | 17. | 0. | 95. | 459.2
Помидоры | 6. | 0. | 42. | 196.8
Зелень | 9. | 0. | 20. | 118.9

В последнем столбце этой рабочей таблицы приведены данные о калорийности продуктов, отсутствующие в явном виде в базовой таблице Продукты. Эти данные вычислены по хранимым значениям основных питательных веществ продуктов, помещены в рабочую таблицу и будут существовать до момента смены изображения на экране. Однако если необходимо сохранить эти данные в какой-либо базовой таблице, то существует предложение (INSERT), позволяющее переписать содержимое рабочей таблицы в указанные столбцы базовой таблицы (реляционная операция присваивания).

Часто пользователя не устраивает как способ описания нужного набора выводимых строк, так и результат выполнения запроса, сформированного из данных одной таблицы. Ему хотелось бы уточнить выводимые (запрашиваемые) данные сведениями из других таблиц.

Например, в запросе на получение состава овощных блюд

SELECT 	БЛ,ПР,Вес
FROM 	Состав
WHERE 	БЛ IN (1,3,17,23);

пришлось перечислять номера этих блюд, так как в таблице Состав нет данных об основных продуктах блюда (они есть в таблице Блюда). Полученный состав овощных блюд (рис.1.3,а) оказался "слепым": в нем и блюда и продукты представлены номерами, а не именами. Удобнее и нагляднее (рис.1.3,б)

а) б)
БЛ | ПР | Вес | Блюдо
1 | 11 | 100 | Салат летний
1 | 15 | 80 | Салат летний
1 | 12 | 5 | Салат летний
1 | 4 | 15 | Салат летний
3 | 11 | 55 | Салат витаминный
3 | 15 | 55 | Салат витаминный
3 | 6 | 50 | Салат витаминный
3 | 12 | 20 | Салат витаминный
3 | 10 | 15 | Салат витаминный
3 | 16 | 5 | Салат витаминный
17 | 9 | 150 | Морковь с рисом
17 | 7 | 50 | Морковь с рисом
17 | 13 | 25 | Морковь с рисом
17 | 3 | 20 | Морковь с рисом
17 | 12 | 10 | Морковь с рисом
17 | 14 | 5 | Морковь с рисом
23 | 11 | 250 | Помидоры с луком
23 | 10 | 65 | Помидоры с луком
23 | 3 | 20 | Помидоры с луком

Продукт | Вес
Помидоры | 100
Яблоки | 80
Зелень | 5
Майонез | 15
Помидоры | 55
Яблоки | 55
Сметана | 50
Зелень | 20
Лук | 15
Сахар | 5
Морковь | 150
Молоко | 50
Рис | 25
Масло | 20
Зелень | 10
Мука | 5
Помидоры | 250
Лук | 65
Масло | 20

Рис. 1.3. Состав овощных блюд базы данных ПАНСИОН

запрос сформированный по трем таблицам:
SELECT	Блюдо, Продукт, Вес
FROM	Состав,Б люда, Продукты
WHERE	Состав.БЛ = Блюда.БЛ
AND	Состав.ПР = Продукты.ПР
AND	Основа = 'Овощи';

В нем для получения рабочей таблицы выполняется естественное соединение [2] таблиц Блюда, Продукты и Состав (условие соединения - равенство значений номеров блюд и значений номеров продуктов). Затем выделяются строки, у которых в столбце Основа хранится слово Овощи, и из этих строк - столбцы Блюдо, Продукт и Вес.

Если пользователи достаточно часто интересуются составом различных блюд, то для упрощения формирования запросов целесообразно создать представление.

Представление - это пустая именованная таблица, определяемая перечнем тех столбцов таблиц и признаками тех их строк, которые хотелось бы в ней увидеть. Представление является как бы "окном" в одну или несколько базовых таблиц. Оно создается с помощью предложения CREATE VIEW (создать представление), подробное описание которого приведено в главе 5. Здесь же приведем пример предложения для создания представления Состав_блюд:

CREATE VIEW 	Состав_блюд
AS SELECT 	Блюдо, Продукт, Вес
FROM 	Состав,Блюда,Продукты
WHERE 	Состав.БЛ = Блюда.БЛ
AND 	Состав.ПР = Продукты.ПР;

Оно описывает пустую таблицу, в которую при реализации запроса будут загружаться данные из столбцов Блюдо, Продукт и Вес таблиц Блюда, Продукты и Состав, соответственно. Теперь для получения состава овощных блюд можно дать запрос

SELECT 	Блюдо,Продукт,Вес
FROM 	Состав_блюд
WHERE 	Основа = 'Овощи';

и получить на экране терминала данные, которые представлены на рис. 1.3,б. А для получения состава супа Харчо можно дать запрос

SELECT	Блюдо, Продукт, Вес
FROM 	Состав_блюд
WHERE	Блюдо = 'Суп харчо';

О целесообразности создания представлений будет рассказано ниже, а здесь лишь отметим, что они позволяют повысить уровень логической независимости данных, упростить их восприятие и "скрыть" от некоторых пользователей те или иные данные, например, данные о новых ценах на продукты первой необходимости или из какой рыбы приготавливается "Судак по-польски".

Наконец, еще об одних виртуальных таблицах - курсорах. Курсор - это пустая именованная таблица, определяемая перечнем тех столбцов базовых таблиц и признаками тех их строк, которые хотелось бы в ней увидеть. В чем же различие между курсором и представлением?

Для пользователя представления почти не отличаются от базовых таблиц (есть лишь некоторые ограничения при выполнении различных операций манипулирования данными). Они могут использоваться как в интерактивном режиме, так и в прикладных программах. Курсоры же созданы для процедурной работы с таблицей в прикладных программах. Например, после объявления курсора

DECLARE	Блюд_состав CURSOR FOR
SELECT	Блюдо,Продукт,Вес
FROM	Состав,Блюда,Продукты
WHERE	Состав.БЛ = Блюда.БЛ
AND	Состав.ПР = Продукты.ПР
AND	Блюдо = 'Суп харчо';

и его активизации (OPEN Блюд_состав) будет создана временная таблица с составом блюда "Суп харчо" и специальным указателем, определяющим в качестве текущей первую строку этой таблицы. С помощью предложения FETCH (выбрать), которое обычно исполняется в программном цикле, можно присвоить определенным переменным значения указанных столбцов этой строки. Одновременно курсор будет передвинут к следующей строке таблицы. После обработки в программе полученных значений переменных выполняется следующее предложение FETCH и т.д. до окончания перебора всех продуктов Харчо.


Запросы с использованием единственной таблицы

Глава 2. Запросы с использованием единственной таблицы
2.1. О предложении SELECT

Все запросы на получение практически любого количества данных из одной или нескольких таблиц выполняются с помощью единственного предложения SELECT. В общем случае результатом реализации предложения SELECT является другая таблица (см. примеры п.1.3). К этой новой (рабочей) таблице может быть снова применена операция SELECT и т.д., т.е. такие операции могут быть вложены друг в друга. Представляет исторический интерес тот факт, что именно возможность включения одного предложения SELECT внутрь другого послужила мотивировкой использования прилагательного "структуризированный" в названии языка SQL.

Предложение SELECT может использоваться как:

  • самостоятельная команда на получение и вывод строк таблицы, сформированной из столбцов и строк одной или нескольких таблиц (представлений);
  • элемент WHERE- или HAVING-условия (сокращенный вариант предложения, называемый "вложенный запрос");
  • фраза выбора в командах CREAT VIEW, DECLARE CURSOR или INSERT;
  • средство присвоения глобальным переменным значений из строк сформированной таблицы (INTO-фраза).

В данной и следующей главах будут рассмотрены только две первые функции предложения SELECT, а здесь – его синтаксис, ограниченный конструкциями, используемыми при реализации этих функций. Здесь (так же как и в других главах книги) в синтаксических конструкциях используются следующие обозначения:

  • звездочка (*) для обозначения "все" - употребляется в обычном для программирования смысле, т.е. "все случаи, удовлетворяющие определению";
  • квадратные скобки ([]) – означают, что конструкции, заключенные в эти скобки, являются необязательными (т.е. могут быть опущены);
  • фигурные скобки ({}) – означают, что конструкции, заключенные в эти скобки, должны рассматриваться как целые синтаксические единицы, т.е. они позволяют уточнить порядок разбора синтаксических конструкций, заменяя обычные скобки, используемые в синтаксисе SQL;
  • многоточие (...) – указывает на то, что непосредственно предшествующая ему синтаксическая единица факультативно может повторяться один или более раз;
  • прямая черта (|) – означает наличие выбора из двух или более возможностей. Например обозначение ASC|DESC указывает, можно выбрать один из терминов ASC или DESC; когда же один из элементов выбора заключен в квадратные скобки, то это означает, что он выбирается по умолчанию (так, [ASC]|DESC означает, что отсутствие всей этой конструкции будет восприниматься как выбор ASC);
  • точка с запятой (;) – завершающий элемент предложений SQL;
  • запятая (,) – используется для разделения элементов списков;
  • пробелы ( ) – могут вводиться для повышения наглядности между любыми синтаксическими конструкциями предложений SQL;
  • прописные жирные латинские буквы и символы – используются для написания конструкций языка SQL и должны (если это специально не оговорено) записываться в точности так, как показано;
  • строчные буквы – используются для написания конструкций, которые должны заменяться конкретными значениями, выбранными пользователем, причем для определенности отдельные слова этих конструкций связываются между собой символом подчеркивания (_);
  • термины таблица, столбец, ... – заменяют (с целью сокращения текста синтаксических конструкций) термины имя_таблицы, имя_столбца, ..., соответственно;
  • термин таблица – используется для обобщения таких видов таблиц, как базовая_таблица, представление или псевдоним; здесь псевдоним служит для временного (на момент выполнения запроса) переименования и (или) создания рабочей копии базовой_таблицы (представления).

Предложение SELECT (выбрать) имеет следующий формат:

подзапрос [UNION [ALL] подзапрос] ...
[ORDER BY	{[таблица.]столбец | номер_элемента_SELECT}
                               [[ASC] | DESC]
[,{[таблица.]столбец | номер_элемента_SELECT}
                         [[ASC] | DESC]] ...;

и позволяет объединить (UNION) а затем упорядочить (ORDER BY) результаты выбора данных, полученных с помощью нескольких "подзапросов". При этом упорядочение можно производить в порядке возрастания - ASC (ASCending) или убывания DESC (DESCending), а по умолчанию принимается ASC.

В этом предложении подзапрос позволяет указать условия для выбора нужных данных и (если требуется) их обработки

SELECT
(выбрать) данные из указанных столбцов и (если необходимо) выполнить перед выводом их преобразование в соответствии с указанными выражениями и (или) функциями
FROM
(из) перечисленных таблиц, в которых расположены эти столбцы
WHERE
(где) строки из указанных таблиц должны удовлетворять указанному перечню условий отбора строк
GROUP BY
(группируя по) указанному перечню столбцов с тем, чтобы получить для каждой группы единственное агрегированное значение, используя во фразе SELECT SQL-функции SUM (сумма), COUNT (количество), MIN (минимальное значение), MAX (максимальное значение) или AVG (среднее значение)
HAVING
(имея) в результате лишь те группы, которые удовлетворяют указанному перечню условий отбора групп

и имеет формат

SELECT	[[ALL] | DISTINCT]{ * | элемент_SELECT
                  [,элемент_SELECT] ...}
FROM		{базовая_таблица | представление} [псевдоним]
[,{базовая_таблица | представление} [псевдоним]] ...
[WHERE		фраза]
[GROUP BY фраза [HAVING фраза]];

Элемент_SELECT - это одна из следующих конструкций:

[таблица.]* | значение | SQL_функция |
               системная_переменная

где значение – это:

[таблица.]столбец | (выражение) | константа |
                       переменная

Синтаксис выражений имеет вид

( {[ [+] | - ] {значение | функция_СУБД}
             [ + | - | * | ** ]}... )

а синтаксис SQL_функций – одна из следующих конструкций:

{SUM|AVG|MIN|MAX|COUNT}
           ( [[ALL]|DISTINCT][таблица.]столбец )

{SUM|AVG|MIN|MAX|COUNT} ( [ALL] выражение )

COUNT(*)

Фраза WHERE включает набор условий для отбора строк:

WHERE [NOT] WHERE_условие
           [[AND|OR][NOT] WHERE_условие]...

где WHERE_условие – одна из следующих конструкций:

значение { = | <> | < | <= | > | >= }
                     { значение | ( подзапрос ) }

значение_1 [NOT] BETWEEN значение_2 AND значение_3

значение [NOT] IN { ( константа [,константа]... )
                                 | ( подзапрос ) }

значение IS [NOT] NULL

[таблица.]столбец [NOT] LIKE 'строка_символов'
                             [ESCAPE 'символ']

EXISTS ( подзапрос )

Кроме традиционных операторов сравнения (= | <> | < | <= | > | >=) в WHERE фразе используются условия BETWEEN (между), LIKE (похоже на), IN (принадлежит), IS NULL (не определено) и EXISTS (существует), которые могут предваряться оператором NOT (не). Критерий отбора строк формируется из одного или нескольких условий, соединенных логическими операторами:

AND
- когда должны удовлетворяться оба разделяемых с помощью AND условия;
OR
- когда должно удовлетворяться одно из разделяемых с помощью OR условий;
AND NOT
- когда должно удовлетворяться первое условие и не должно второе;
OR NOT
- когда или должно удовлетворяться первое условие или не должно удовлетворяться второе,

причем существует приоритет AND над OR (сначала выполняются все операции AND и только после этого операции OR). Для получения желаемого результата WHERE условия должны быть введены в правильном порядке, который можно организовать введением скобок.

При обработке условия числа сравниваются алгебраически - отрицательные числа считаются меньшими, чем положительные, независимо от их абсолютной величины. Строки символов сравниваются в соответствии с их представлением в коде, используемом в конкретной СУБД, например, в коде ASCII. Если сравниваются две строки символов, имеющих разные длины, более короткая строка дополняется справа пробелами для того, чтобы они имели одинаковую длину перед осуществлением сравнения.

Наконец, синтаксис фразы GROUP BY имеет вид

GROUP BY [таблица.]столбец [,[таблица.]столбец] ...
                                 [HAVING фраза]

GROUP BY инициирует перекомпоновку формируемой таблицы по группам, каждая из которых имеет одинаковое значение в столб-цах, включенных в перечень GROUP BY. Далее к этим группам применяются агрегирующие функции, указанные во фразе SELECT, что приводит к замене всех значений группы на единственное значение (сумма, количество и т.п.).

С помощью фразы HAVING (синтаксис которой почти не отличается от синтаксиса фразы WHERE)

HAVING [NOT] HAVING_условие
            [[AND|OR][NOT] HAVING_условие]...

можно исключить из результата группы, не удовлетворяющие заданным условиям:

значение { = | <> | < | <= | > | >= } { значение
| ( подзапрос ) | SQL_функция }

{значение_1 | SQL_функция_1} [NOT] BETWEEN
    {значение_2 | SQL_функция_2} AND {значение_3
                        | SQL_функция_3}

{значение | SQL_функция} [NOT] IN
 { ( константа [,константа]... ) | ( подзапрос ) }

{значение | SQL_функция} IS [NOT] NULL

[таблица.]столбец [NOT] LIKE 'строка_символов'
         [ESCAPE 'символ']

EXISTS ( подзапрос )


Выборка без использования фразы WHERE

2.2. Выборка без использования фразы WHERE
2.2.1. Простая выборка

Запрос выдать название, статус и адрес поставщиков

SELECT	Название, Статус, Адрес
FROM	Поставщики;
дает результат, приведенный на рис. 2.1,а.

При необходимости получения полной информации о поставщиках, можно было бы дать запрос

SELECT	ПС, Название, Статус, Город, Адрес, Телефон
FROM	Поставщики;
или использовать его более короткую нотацию:
SELECT	*
FROM	Поставщики;

Здесь "звездочка" (*) служит кратким обозначением всех имен полей в таблице, указанной во фразе FROM. При этом порядок вывода полей соответствует порядку, в котором эти поля определялись при создании таблицы.

Еще один пример. Выдать основу всех блюд:

SELECT	Основа
FROM	Блюда;

дает результат, показанный на рис. 2.1,б.

а) б) в)
Название | Статус | Адрес
СЫТНЫЙ | рынок | Сытнинская, 3
ПОРТОС | кооператив | Садовая, 27
ШУШАРЫ | совхоз | Новая, 17
ТУЛЬСКИЙ | универсам | Тульская, 3
УРОЖАЙ | коопторг | Песчаная, 19
ЛЕТО | агрофирма | Пулковское ш.,8
ОГУРЕЧИК | ферма | Укмерге, 15
КОРЮШКА | кооператив | Нарвское ш., 64
Основа
Овощи
Мясо
Овощи
Рыба
Рыба
Мясо
Молоко
Молоко
...
Кофе
Основа
Кофе
Крупа
Молоко
Мясо
Овощи
Рыба
Фрукты
Яйца

Рис. 2.1. Примеры простой выборки

2.2.2. Исключение дубликатов

В предыдущем примере был выдан правильный, но не совсем удачный перечень основных продуктов: из него не были исключены дубликаты. Для исключения дубликатов и одновременного упорядочения перечня необходимо дополнить запрос ключевым словом DISTINCT (различный, различные), как показано в следующем примере:

SELECT DISTINCT Основа
FROM	Блюда;

Результат приведен на рис. 2.1,в.


Выборка вычисляемых значений

2.2.3. Выборка вычисляемых значений

Из синтаксиса фразы SELECT (п.2.1) видно, что в ней может содержаться не только перечень столбцов таблицы или символ *, но и выражения.

Например, если нужно получить значение калорийности всех продуктов, то можно учесть, что при окислении 1 г углеводов или белков в организме освобождается в среднем 4.1 ккал, а при окислении 1 г жиров - 9.3 ккал, и выдать запрос:

SELECT	Продукт, ((Белки+Углев)*4.1+Жиры*9.3)
FROM	Продукты;

результат которого приведен на рис. 2.2,а.

а) б) в)
Продукт |
Говядина | 1928.1
Судак | 1523.
Масло | 8287.5
Майонез | 6464.7
Яйца | 1618.9
Сметана | 3011.4
Молоко | 605.1
Творог | 1575.
Морковь | 349.6
Лук | 459.2
Помидоры | 196.8
Зелень | 118.9
Рис | 3512.1
Мука | 3556.7
Яблоки | 479.7
Сахар | 4091.8
Кофе | 892.4

Продукт |
Говядина | Калорий = | 1928.1
Судак | Калорий = | 1523.
Масло | Калорий = | 8287.5
Майонез | Калорий = | 6464.7
Яйца | Калорий = | 1618.9
Сметана | Калорий = | 3011.4
Молоко | Калорий = | 605.1
Творог | Калорий = | 1575.
Морковь | Калорий = | 349.6
Лук | Калорий = | 459.2
Помидоры | Калорий = | 196.8
Зелень | Калорий = | 118.9
Рис | Калорий = | 3512.1
Мука | Калорий = | 3556.7
Яблоки | Калорий = | 479.7
Сахар | Калорий = | 4091.8
Кофе | Калорий = | 892.4

Продукт |
Зелень | 118.9
Помидоры | 196.8
Морковь | 349.6
Лук | 459.2
Яблоки | 479.7
Молоко | 605.1
Кофе | 892.4
Судак | 1523.
Творог | 1575.
Яйца | 1618.9
Говядина | 1928.1
Сметана | 3011.4
Рис | 3512.1
Мука | 3556.7
Сахар | 4091.8
Майонез | 6464.7
Масло | 8287.5

Рис. 2.2. Примеры запросов с вычисляемыми полями

Фраза SELECT может включать не только выражения, но и отдельные числовые или текстовые константы. Следует отметить, что текстовые константы должны заключаться в апострофы ('). На рис. 2.2,б приведен результат запроса:

 SELECT	Продукт, 'Калорий =',
            ((Белки+Углев)*4.1+Жиры *9.3)
  FROM		Продукты;

А что произойдет, если какой-либо член выражения не определен, т.е. имеет значение NULL и каким образом появилось такое значение?

Если при загрузке строк таблицы в какой-либо из вводимых строк отсутствует значение для какого-либо столбца, то СУБД введет в такое поле NULL-значение. NULL-значение "придумано" для того, чтобы представить единым образом "неизвестные значения" для любых типов данных. Действительно, так как при вводе данных в столбец или их изменении СУБД запрещает ввод значений не соответствующих описанию данных этого столбца, то, например, нельзя использовать пробел для отсутствующего значения числа. Нельзя для этих целей использовать и ноль: нет месяца или дня недели равного нулю, да и для чисел ноль не может рассматриваться как неизвестное значение в одном месте и как известное - в другом. При выводе же NULL-значения на экран или печатающее устройство его код воспроизводится каким-либо специально заданным символом или набором символов: например, пробелом (если его нельзя перепутать с текстовым значением пробела) или сочетанием -0-.

С помощью специальной команды можно установить в СУБД один из режимов представления NULL-значений при выполнении числовых расчетов: запрет или разрешение замены NULL-значения нулем. В первом случае любое арифметическое выражение, содержащее неопределенный операнд, будет также иметь неопределенное значение. Во втором случае результат вычислений будет иметь численное значение (если это значение попадает в диапазон представления соответствующего типа данных).

Например, при выполнении запроса

SELECT	ПР, Цена, К_во, (Цена * К_во)
FROM	Поставки;

и разных "настройках" СУБД могут быть получены разные результаты:

ПР | Цена | К_во | (Цена*К_во)
9 | -0- | -0- | -0-
11 | 1.5 | 50 | 75.
12 | 3. | 10 | 30.
15 | 2. | 170 | 340.

ПР | Цена | К_во | (Цена*К_во)
9 | -0- | -0- | 0.
11 | 1.5 | 50 | 75.
12 | 3. | 10 | 30.
15 | 2. | 170 | 340.


Выборка c использованием фразы WHERE

2.3. Выборка c использованием фразы WHERE

2.3.1. Использование операторов сравнения

В синтаксисе фразы WHERE (п.2.1) показано, что для отбора нужных строк таблицы можно использовать операторы сравнения = (равно), <> (не равно), < (меньше), <= (меньше или равно), > (больше), >= (больше или равно), которые могут предваряться оператором NOT, создавая, например, отношения "не меньше" и "не больше".

Так, для получения перечня продуктов, практически не содержащих углеводов, можно сформировать запрос

SELECT	Продукт, Белки, Жиры, Углев, K, Ca, Na,
                                      B2, PP, C
FROM	Продукты
WHERE 	Углев = 0;

и получить:

Продукт | Белки | Жиры | Углев | K | Ca | Na | B2 | PP | C
Говядина | 189. | 124. | 0. | 3150 | 90 | 600 | 1.5 | 28. | 0
Судак | 190. | 80. | 0. | 1870 | 270 0 | 1.1 | 10. | 30

Возможность использования нескольких условий, соединенных логическими операторами AND, OR, AND NOT и OR NOT, позволяет осуществить более детальный отбор строк. Так, для получения перечня продуктов, практически не содержащих углеводов и натрия, можно сформировать запрос:

SELECT	Продукт, Белки, Жиры, Углев, K, Ca, Na,
                                      B2, PP, C
FROM	Продукты
WHERE	Углев = 0 AND Na = 0;

Результат запроса имеет вид

Продукт | Белки | Жиры | Углев | K | Ca | Na | B2 | PP | C
Судак | 190. | 80. | 0. | 1870 | 270 | 0 | 1.1 | 10. | 30

Добавим к этому запросу еще одно условие

SELECT	Продукт, Белки, Жиры, Углев, K, Ca, Na,
                                     B2, PP, C
FROM	Продукты
WHERE	Углев = 0 AND Na = 0 AND Продукт <> 'Судак';

и получим на экране сообщение "No rows exist or satisfy the specified clause" или аналогичное (в зависимости от вкусов разработчиков разных СУБД), информирующее об отсутствии строк, удовлетворяющих заданному(ым) условию(ям).


Использование BETWEEN

2.3.2. Использование BETWEEN

С помощью BETWEEN ... AND ... (находится в интервале от ... до ...) можно отобрать строки, в которых значение какого-либо столбца находятся в заданном диапазоне.

Например, выдать перечень продуктов, в которых значение содержания белка находится в диапазоне от 10 до 50:
Результат:
SELECT	Продукт, Белки
FROM	Продукты
WHERE 	Белки BETWEEN 10 AND 50;
Продукт | Белки
Майонез | 31.
Сметана | 26.
Молоко | 28.
Морковь | 13.
Лук | 17.

Можно задать и NOT BETWEEN (не принадлежит диапазону между), например:

Результат:
SELECT	Продукт, Белки, Жиры
FROM	Продукты
WHERE	Белки NOT BETWEEN
                10 AND 50
AND	Жиры > 100;
Продукт | Белки | Жиры
Говядина | 189. | 124.
Масло | 60. | 825.
Яйца | 127. | 115.

BETWEEN особенно удобен при работе с данными, задаваемыми интервалами, начало и конец которых расположен в разных столбцах.

Для примера воспользуемся таблицей "минимальных окладов" (табл. 2.1), величина которых непосредственно связана со студенческой стипендией. В этой таблице для текущего значения минимального оклада установлена запредельная дата окончания 9 сентября 9999 года.

Таблица 2.1
Минимальные оклады

Миноклад | Начало | Конец
2250 | 01-01-1993 | 31-03-1993
4275 | 01-04-1993 | 30-06-1993
7740 | 01-07-1993 | 30-11-1993
14620 | 01-12-1993 | 30-06-1994
20500 | 01-07-1994 | 09-09-9999

Если, например, потребовалось узнать, какие изменения минимальных окладов производились в 1993/94 учебном году, то можно выдать запрос

SELECT	Начало, Миноклад
FROM	Миноклады
WHERE	Начало BETWEEN '1-9-1993' AND '31-8-1994'
и получить результат:
Начало | Миноклад
01-12-1993 | 14620
01-07-1994 | 20500

Отметим, что при формировании запросов значения дат следует заключать в апострофы, чтобы СУБД не путала их с выражениями и не пыталась вычитать из 31 значение 8, а затем 1994.

Для выявления всех значений минимальных окладов, которые существовали в 1993/94 учебном году, можно сформировать запрос

SELECT	*
FROM	Миноклады
WHERE	Начало	BETWEEN '1-9-1993' AND '31-8-1994'
OR	Конец 	BETWEEN '1-9-1993' AND '31-8-1994'
Миноклад | Начало | Конец
7740 | 01/07/1993 | 30/11/1993
14620 | 01/12/1993 | 30/06/1994
20500 | 01/07/1994 | 09/09/9999

Наконец, для получения минимального оклада на 15-5-1994:

Результат:
SELECT	Миноклад
FROM	Миноклады
WHERE	'15-05-1994' BETWEEN Начало AND Конец
Миноклад
14620


Использование IN

2.3.3. Использование IN

Выдать сведения о блюдах на основе яиц, крупы и овощей

SELECT	*
FROM	Блюда
WHERE	Основа IN (Яйца Крупа Овощи);

Результат:

БЛ | Блюдо | В | Основа | Выход | Труд
1 | Салат летний | З | Овощи | 200. | 3
3 | Салат витаминный | З | Овощи | 200. | 4
16 | Драчена | Г | Яйца | 180. | 4
17 | Морковь с рисом | Г | Овощи | 260. | 3
19 | Омлет с луком | Г | Яйца | 200. | 5
20 | Каша рисовая | Г | Крупа | 210. | 4
21 | Пудинг рисовый | Г | Крупа | 160. | 6
23 | Помидоры с луком | Г | Овощи | 260. | 4

Рассмотренная форма IN является в действительности просто краткой записью последовательности отдельных сравнений, соединенных операторами OR. Предыдущее предложение эквивалентно такому:

SELECT	*
FROM	Блюда
WHERE	Основа=Яйца OR Основа=Крупа OR Основа=Овощи;

Можно задать и NOT IN (не принадлежит), а также возможность использования IN (NOT IN) с подзапросом (см. главу 3).


Использование LIKE

2.3.4. Использование LIKE

Выдать перечень салатов

Результат:
SELECT	Блюдо
FROM	Блюда
WHERE	Блюдо LIKE 'Салат%';
Блюдо
Салат летний
Салат мясной
Салат витаминный
Салат рыбный

Обычная форма "имя_столбца LIKE текстовая_константа" для столбца текстового типа позволяет отыскать все значения указанного столбца, соответствующие образцу, заданному "текстовой_константой". Символы этой константы интерпретируются следующим образом:

  • символ _ (подчеркивание) – заменяет любой одиночный символ,
  • символ % (процент) – заменяет любую последовательность из N символов (где N может быть нулем),
  • все другие символы означают просто сами себя.

Следовательно, в приведенном примере SELECT будет осуществлять выборку записей из таблицы Блюда, для которых значение в столбце Блюдо начинается сочетанием 'Салат' и содержит любую последовательность из нуля или более символов, следующих за сочетанием 'Салат'. Если бы среди блюд были "Луковый салат", "Фруктовый салат" и т.п., то они не были бы найдены. Для их отыскания надо изменить фразу WHERE:

WHERE Блюдо LIKE '%салат%'

или при отсутствии различий между малыми и большими буквами (такую настройку допускают некоторые СУБД):

WHERE Блюдо LIKE '%Салат%'

Это позволит отыскать все салаты.


Вовлечение неопределенного значения (NULL-значения)

2.3.5. Вовлечение неопределенного значения (NULL-значения)

Как было рассказано в п.2.2.3, если при загрузке данных не введено значение в какое-либо поле таблицы, то СУБД поместит в него NULL-значение. Аналогичное значение можно ввести в поле таблицы, выполняя операцию изменения данных. Так, при отсутствии сведений о наличии у поставщиков судака и моркови в столбцы Цена и К_во соответствующих строк таблицы Поставки вводится NULL и там будет храниться код NULL-значения, а не 0, 0. или пробел. (Отметим, что в распечатке таблицы Поставки рис.1.1 в этих местах расположен пробел, установленный в СУБД для представления NULL-значения при выводе на печать).

В этом случае для выявления названий продуктов, отсутствующих в кладовой, шеф-повар может дать запрос

Результат: ПР
SELECT	DISTINCT ПР
FROM	Наличие
WHERE	К_во IS NULL;
 2
9

Естественно, что для выявления продуктов, существующих в кладовой, следует дать запрос

SELECT	DISTINCT ПР
FROM	Наличие
WHERE	К_во IS NOT NULL;

Использование условий

столбец IS NULL  и  столбец IS NOT NULL

вместо, например,

столбец = NULL	  и  столбец <> NULL

связано с тем, что ничто - и даже само NULL-значение - не считается равным другому NULL-значению. (Несмотря на это, два неопределенных значения рассматриваются, однако, как дубликаты друг друга при исключении дубликатов, и предложение SELECT DISTINCT даст в результате не более одного NULL-значения.)


Выборка с упорядочением

2.4. Выборка с упорядочением

Синтаксис фразы упорядочения был дан в п. 2.1. Простейший вариант этой фразы - упорядочение строк результата по значению одного из столбцов с указанием порядка сортировки или без такого указания. (По умолчанию строки будут сортироваться в порядке возрастания значений в указанном столбце.)

Например, выдать перечень продуктов и содержание в них основных веществ в порядке убывания содержания белка

Результат:
SELECT	Продукт, Белки,
         Жиры, Углев
FROM	Продукты
ORDER	BY Белки DESC;

Продукт | Белки | Жиры | Углев
Судак | 190. | 80. | 0.
Говядина | 189. | 124. | 0.
Творог | 167. | 90. | 13.
Яйца | 127. | 115. | 7.
Кофе | 127. | 36. | 9.
Мука | 106. | 13. | 732.
. . .

При включении в список ORDER BY нескольких столбцов СУБД сортирует строки результата по значениям первого столбца списка пока не появится несколько строк с одинаковыми значениями данных в этом столбце. Такие строки сортируются по значениям следующего столбца из списка ORDER BY и т.д.

Например, выдать содержимое таблицы Блюда, отсортировав ее строки по видам блюд и основе:

Результат:
SELECT	*
FROM	Блюда
ORDER	BY В
     Основа;

БЛ | Блюдо | В | Основа | Выход | Труд
21 | Пудинг рисовый | Г | Крупа | 160. | 6
20 | Каша рисовая | Г | Крупа | 210. | 4
18 | Сырники | Г | Молоко | 220. | 4
. . .
16 | Драчена | Г | Яйца | 180. | 4
28 | Крем творожный | Д | Молоко | 160. | 4
. . .
26 | Яблоки печеные | Д | Фрукты | 160. | 3
7 | Сметана | З | Молоко | 140. | 1
8 | Творог | З | Молоко | 140. | 2
2 | Салат мясной | З | Мясо | 200. | 4
6 | Мясо с гарниром | З | Мясо | 250. | 3
1 | Салат летний | З | Овощи | 200. | 3
. . .

Кроме того, в список ORDER BY можно включать не только имя столбца, а его порядковую позицию в перечне SELECT. Благодаря этому возможно упорядочение результатов на основе вычисляемых столбцов, не имеющих имен.

Например, запрос

SELECT	Продукт, ((Белки+Углев)*4.1+Жиры*9.3)
FROM	Продукты
ORDER	BY 2;

позволит получить список продуктов, показанный на рис.2.2,в – переупорядоченный по возрастанию значений калорийности список рис.2.2,а.


Агрегирование данных. SQL-функции

2.5. Агрегирование данных
2.5.1 SQL-функции

В SQL существует ряд специальных стандартных функций (SQL-функций). Кроме специального случая COUNT(*) каждая из этих функций оперирует совокупностью значений столбца некоторой таблицы и создает единственное значение, определяемое так:

COUNT
- число значений в столбце,
SUM
- сумма значений в столбце,
AVG
- среднее значение в столбце,
MAX
- самое большое значение в столбце,
MIN
- самое малое значение в столбце.

Для функций SUM и AVG рассматриваемый столбец должен содержать числовые значения.

Следует отметить, что здесь столбец - это столбец виртуальной таблицы, в которой могут содержаться данные не только из столбца базовой таблицы, но и данные, полученные путем функционального преобразования и (или) связывания символами арифметических операций значений из одного или нескольких столбцов. При этом выражение, определяющее столбец такой таблицы, может быть сколь угодно сложным, но не должно содержать SQL-функций (вложенность SQL-функций не допускается). Однако из SQL-функций можно составлять любые выражения.

Аргументу всех функций, кроме COUNT(*), может предшествовать ключевое слово DISTINCT (различный), указывающее, что избыточные дублирующие значения должны быть исключены перед тем, как будет применяться функция. Специальная же функция COUNT(*) служит для подсчета всех без исключения строк в таблице (включая дубликаты).


Функции без использования фразы GROUP BY

2.5.2. Функции без использования фразы GROUP BY

Если не используется фраза GROUP BY, то в перечень элементов_SELECT можно включать лишь SQL-функции или выражения, содержащие такие функции. Другими словами, нельзя иметь в списке столбцы, не являющихся аргументами SQL-функций.

Например, выдать данные о массе лука (ПР=10), проданного поставщиками, и указать количество этих поставщиков:

Результат:
SELECT	SUM(К_во),COUNT(К_во)
FROM	Поставки
WHERE	ПР = 10;
SUM(К_во) | COUNT(К_во)
220 | 2

Если бы для вывода в результат еще и номера продукта был сформирован запрос

SELECT	ПР,SUM(К_во),COUNT(К_во)
FROM	Поставки
WHERE	ПР = 10;

то было бы получено сообщение об ошибке. Это связано с тем, что SQL-функция создает единственное значение из множества значений столбца-аргумента, а для "свободного" столбца должно быть выдано все множество его значений. Без специального указания (оно задается фразой GROUP BY) SQL не будет выяснять, одинаковы значения этого множества (как в данном примере, где ПР=10) или различны (как было бы при отсутствии WHERE фразы). Поэтому подобный запрос отвергается системой.

Правда, никто не запрещает дать запрос

SELECT	'Кол-во лука =',SUM(К_во),COUNT(К_во)
FROM	Поставки
WHERE	ПР = 10;

Результат:
'Кол-во лука =' | SUM(К_во) | COUNT(К_во)
Кол-во лука = | 220 | 2

Отметим также, что в столбце-аргументе перед применением любой функции, кроме COUNT(*), исключаются все неопределенные значения. Если оказывается, что аргумент - пустое множество, функция COUNT принимает значение 0, а остальные - NULL.

Например, для получения суммы цен, средней цены, количества поставляемых продуктов и количества разных цен продуктов, проданных коопторгом УРОЖАЙ (ПС=5), а также для получения количества продуктов, которые могут поставляться этим коопторгом, можно дать запрос

SELECT	SUM(Цена),AVG(Цена),COUNT(Цена),
    COUNT(DISTINCT  Цена),COUNT(*)
FROM	Поставки
WHERE	ПС = 5;

и получить

(*)
SUM(Цена) | AVG(Цена) | COUNT(Цена) | COUNT(DISTINCT Цена) | COUNT
6.2 | 1.24 | 5 | 4 | 7

В другом примере, где надо узнать "Сколько поставлено моркови и сколько поставщиков ее поставляют?":

SELECT SUM(К_во),COUNT(К_во)
FROM	Поставки
WHER	ПР = 2;

будет получен ответ:

SUM(К_во) | COUNT (К_во)
-0- | 0

Наконец, попробуем получить сумму массы поставленного лука с его средней ценой ("Сапоги с яичницей"):

Результат:
SELECT	(SUM(К_во)	+AVG(Цена))
FROM	Поставки
WHERE	ПР = 10;
SUM(К_во)+AVG(Цена)
220.6


Фраза GROUP BY

2.5.3. Фраза GROUP BY

Мы показали, как можно вычислить массу определенного продукта, поставляемого поставщиками. Предположим, что теперь требуется вычислить общую массу каждого из продуктов, поставляемых в настоящее время поставщиками. Это можно легко сделать с помощью предложения

SELECT	ПР, SUM(К_во)
FROM	Поставки
GROUP	BY ПР;

Результат показан на рис. 2.3,а.

а) б) в) г)
ПР
9 | 0
11 | 150
12 | 30
15 | 370
1 | 370
3 | 250
5 | 170
6 | 220
8 | 150
7 | 200
2 | 0
4 | 100
13 | 190
14 | 70
16 | 250
17 | 50
10 | 220

ПС | ПР | Цена | К_во
1 | 9 | -0- | -0-
3 | 9 | -0- | -0-
5 | 9 | -0- | -0-
1 | 11 | 1.50 | 50
5 | 11 | -0- | -0-
6 | 11 | -0- | -0-
8 | 11 | 1.00 | 100
1 | 12 | 3.00 | 10
3 | 12 | 2.50 | 20
6 | 12 | -0- | -0-
1 | 15 | 2.00 | 170
3 | 15 | 1.50 | 200
2 | 1 | 3.60 | 300
7 | 1 | 4.20 | 70
2 | 3 | -0- | -0-
7 | 3 | 4.00 | 250
. . .

ПР
1 | 370
2 | 0
3 | 250
4 | 100
5 | 170
6 | 220
7 | 200
8 | 150
9 | 0
10 | 220
11 | 150
12 | 30
13 | 190
14 | 70
15 | 370
16 | 250
17 | 50

ПР
9 | 0
11 | 150
12 | 30
15 | 70
1 | 370
3 | 250
5 | 70
6 | 140
8 | 150
7 | 200
2 | 0
4 | 100
13 | 190
14 | 70
16 | 250
17 | 50
10 | 220

Рис. 2.3. Иллюстрации к фразе GROUP BY

Фраза GROUP BY (группировать по) инициирует перекомпоновку указанной во FROM таблицы по группам, каждая из которых имеет одинаковые значения в столбце, указанном в GROUP BY. В рассматриваемом примере строки таблицы Поставки группируются так, что в одной группе содержатся все строки для продукта с ПР = 1, в другой – для продукта с ПР = 2 и т.д. (см. рис. 2.3.б). Далее к каждой группе применяется фраза SELECT. Каждое выражение в этой фразе должно принимать единственное значение для группы, т.е. оно может быть либо значением столбца, указанного в GROUP BY, либо арифметическим выражением, включающим это значение, либо константой, либо одной из SQL-функций, которая оперирует всеми значениями столбца в группе и сводит эти значения к единственному значению (например, к сумме).

Отметим, что фраза GROUP BY не предполагает ORDER BY. Чтобы гарантировать упорядочение по ПР результата рассматриваемого примера (рис. 2.3,в) следует дать запрос

SELECT	ПР, SUM(К_во)
FROM	Поставки
GROUP	BY ПР
ORDER	BY ПР;

Наконец, отметим, что строки таблицы можно группировать по любой комбинации ее столбцов. Так, по запросу

SELECT	Т, БЛ, COUNT(БЛ)
FROM	Заказ
GROUP	BY Т, БЛ;

можно узнать коды и количество порций блюд, заказанных отдыхающими пансионата (32 человека) на каждую из трапез следующего дня:

Т | БЛ | COUNT(БЛ)
1 | 3 | 18
1 | 6 | 14
1 | 19 | 17
1 | 21 | 15
...

Если в запросе используются фразы WHERE и GROUP BY, то строки, не удовлетворяющие фразе WHERE, исключаются до выполнения группирования.

Например, выдать для каждого продукта его код и общий объем возможных поставок, учитывая временную недееспособность поставщика с ПС=2:

SELECT	ПР, SUM(К_во)
FROM	Поставки
WHERE	ПС <> 2
GROUP	BY ПР;

Результат, приведенный на рис. 2.3,г, отличается от результата (рис. 2.3,а) аналогичного запроса для всех поставщиков объемом поставок продуктов с кодами 15, 5 и 6.


Использование фразы HAVING

2.5.4. Использование фразы HAVING

Фраза HAVING (рис.2.3) играет такую же роль для групп, что и фраза WHERE для строк: она используется для исключения групп, точно так же, как WHERE используется для исключения строк. Эта фраза включается в предложение лишь при наличии фразы GROUP BY, а выражение в HAVING должно принимать единственное значение для группы.

Например, выдать коды продуктов, поставляемых более чем двумя поставщиками:

SELECT
FROM	Поставки
GROUP	BY ПС
HAVING	COUNT(*) > 2;
 Результат: ПР
9
11
12

В п.3.6 можно познакомиться с более содержательным примером использования этой фразы.


Запросы с использованием нескольких таблиц

Глава 3. Запросы с использованием нескольких таблиц
3.1. О средствах одновременной работы с множеством таблиц

Затрагивая вопросы проектирования баз данных [2], мы выяснили, что базы данных - это множество взаимосвязанных сущностей или отношений (таблиц) в терминологии реляционных СУБД. При проектировании стремятся создавать таблицы, в каждой из которых содержалась бы информация об одном и только об одном типе сущностей. Это облегчает модификацию базы данных и поддержание ее целостности. Но такой подход тяжело усваивается начинающими проектантами, которые пытаются привязать проект к будущим приложениям и так организовать таблицы, чтобы в каждой из них хранилось все необходимое для реализации возможных запросов. Типичен вопрос: как же получить сведения о том, где купить продукты для приготовления того или иного блюда и определить его калорийность и стоимость, если нужные данные "рассыпаны" по семи различным таблицам? Не лучше ли иметь одну большую таблицу, содержащую все сведения базы данных ПАНСИОН ?

Даже при отсутствии средств одновременного доступа ко многим таблицам нежелателен проект, в котором информация о многих типах сущностей перемешана в одной таблице. SQL же обладает великолепным механизмом для одновременной или последовательной обработки данных из нескольких взаимосвязанных таблиц. В нем реализованы возможности "соединять" или "объединять" несколько таблиц и так называемые "вложенные подзапросы". Например, чтобы получить перечень поставщиков продуктов, необходимых для приготовления Сырников, возможен запрос

SELECT	Продукт, Цена, Название, Статус
FROM	Продукты, Состав, Блюда, Поставки, Поставщики
WHERE	Продукты.ПР = Состав.ПР
AND	Состав.БЛ = Блюда.БЛ
AND	Поставки.ПР = Состав.ПР
AND	Поставки.ПС = Поставщики.ПС
AND	Блюдо = 'Сырники'
AND	Цена IS NOT NULL;
Продукт | Цена | Название | Статус
Яйца | 1.8 | ПОРТОС | кооператив
Яйца | 2. | КОРЮШКА | кооператив
Сметана | 3.6 | ПОРТОС | кооператив
Сметана | 2.2 | ОГУРЕЧИК | ферма
Творог | 1. | ОГУРЕЧИК | ферма
Мука | 0.5 | УРОЖАЙ | коопторг
Сахар | 0.94 | ТУЛЬСКИЙ | универсам
Сахар | 1. | УРОЖАЙ | коопторг

Он получен следующим образом: СУБД последовательно формирует строки декартова произведения таблиц, перечисленных во фразе FROM, проверяет, удовлетворяют ли данные сформированной строки условиям фразы WHERE, и если удовлетворяют, то включает в ответ на запрос те ее поля, которые перечислены во фразе SELECT.

Следует подчеркнуть, что в SELECT и WHERE (во избежание двусмысленности) ссылки на все (*) или отдельные столбцы могут (а иногда и должны) уточняться именем соответствующей таблицы, например, Поставки.ПС, Поставщики.ПС, Меню.*, Состав.БЛ, Блюда.* и т.п.

Очевидно, что с помощью соединения несложно сформировать запрос на обработку данных из нескольких таблиц. Кроме того, в такой запрос можно включить любые части предложения SELECT, рассмотренные в главе 2 (выражения с использованием функций, группирование с отбором указанных групп и упорядочением полученного результата). Следовательно, соединения позволяют обрабатывать множество взаимосвязанных таблиц как единую таблицу, в которой перемешана информация о многих типах сущностей. Поэтому начинающий проектант базы данных может спокойно создавать маленькие нормализованные таблицы, так как он всегда может получить из них любую "большую" таблицу.

Кроме механизма соединений в SQL есть механизм вложенных подзапросов, позволяющий объединить несколько простых запросов в едином предложении SELECT. Иными словами, вложенный подзапрос - это уже знакомый нам подзапрос (с небольшими огра-ничениями), который вложен в WHERE фразу другого вложенного подзапроса или WHERE фразу основного запроса.

Для иллюстрации вложенного подзапроса вернемся к предыдущему примеру и попробуем получить перечень тех поставщиков продуктов для Сырников, которые поставляют нужные продукты за минимальную цену.

SELECT	Продукт, Цена, Название, Статус
FROM	Продукты, Состав, Блюда, Поставки, Поставщики
WHERE	Продукты.ПР = Состав.ПР
AND	Состав.БЛ = Блюда.БЛ
AND	Поставки.ПР = Состав.ПР
AND	Поставки.ПС = Поставщики.ПС
AND	Блюдо = 'Сырники'
AND	Цена = (	SELECT	MIN(Цена)
        FROM	Поставки X
        WHERE	X.ПР = Поставки.ПР );

Результат запроса имеет вид

Продукт | Цена | Название | Статус
Яйца | 1.8 | ПОРТОС | кооператив
Сахар | 0.94 | ТУЛЬСКИЙ | универсам
Мука | 0.5 | УРОЖАЙ | коопторг
Сметана | 2.2 | ОГУРЕЧИК | ферма
Творог | 1. | ОГУРЕЧИК | ферма

Здесь с помощью подзапроса, размещенного в трех последних строках запроса, описывается процесс определения минимальной цены каждого продукта для Сырников и поиск поставщика, предлагающего этот продукт за такую цену. Механизм реализации подзапросов будет подробно описан в п.3.3. Там же будет рассмотрено, как и для чего вводится псевдоним X для имени таблицы Поставки.


Запросы, использующие соединения. Декартово произведение таблиц

3.2. Запросы, использующие соединения
3.2.1. Декартово произведение таблиц

В литературе [2] показано, что соединения - это подмножества декартова произведения. Так как декартово произведение n таблиц - это таблица, содержащая все возможные строки r, такие, что r является сцеплением какой-либо строки из первой таблицы, строки из второй таблицы, ... и строки из n-й таблицы (а мы уже научились выделять с помощью SELECT любое подмножество реляционной таблицы), то осталось лишь выяснить, можно ли с помощью SELECT получить декартово произведение. Для получения декартова произведения нескольких таблиц надо указать во фразе FROM перечень перемножаемых таблиц, а во фразе SELECT – все их столбцы.

Так, для получения декартова произведения Вид_блюд и Трапезы надо выдать запрос

SELECT	Вид_блюд.*, Трапезы.*
FROM	Вид_блюд, Трапезы;

Получим таблицу, содержащую 5 х 3 = 15 строк:

В | Вид | Т | Трапеза
З | Закуска | 1 | Завтрак
З | Закуска | 2 | Обед
З | Закуска | 3 | Ужин
С | Суп | 1 | Завтрак
С | Суп | 2 | Обед
С | Суп | 3 | Ужин
Г | Горячее | 1 | Завтрак
Г | Горячее | 2 | Обед
Г | Горячее | 3 | Ужин
Д | Десерт | 1 | Завтрак
Д | Десерт | 2 | Обед
Д | Десерт | 3 | Ужин
Н | Напиток | 1 | Завтрак
Н | Напиток | 2 | Обед
Н | Напиток | 3 | Ужин

В другом примере, где перемножаются таблицы Меню, Трапезы, Вид_блюд, Блюда:

SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.*
FROM	Меню, Трапезы, Вид_блюд, Блюда;

образуется таблица (рис 3.1), содержащая 21 х 3 х 5 х 33 = 10395 строк.

Из первых 39 строк этой таблицы лишь две актуальных (отмечены "*"): в них совпадают номера блюд таблиц Меню и Блюда. В остальных – полная чепуха: к закускам отнесены супы и напитки, на завтрак предлагается незапланированный суп и т.д.


Эквисоединение таблиц

3.2.2. Эквисоединение таблиц

Если из декартова произведения убрать ненужные строки и столбцы, то можно получить актуальные таблицы, соответствующие любому из соединений.

Меню |  |  | Трапезы |  | Вид_блюд |  | Блюда
Т | В | БЛ | Т | Трапеза | В | Вид | БЛ | Блюдо | В | Основа | Выход | Труд
1 | З | 3 | 1 | Завтрак | З | Закуска | 1 | Салат летний | З | Овощи | 200. | 3
1 | З | 3 | 1 | Завтрак | З | Закуска | 2 | Салат мясной | З | Мясо | 200. | 4
1 | З | 3 | 1 | Завтрак | З | Закуска | 3 | Салат витаминный | З | Овощи | 200. | 4 *
. . .
1 | З | 3 | 1 | Завтрак | З | Закуска | 12 | Суп молочный | С | Молоко | 500. | 3
1 | З | 3 | 1 | Завтрак | З | Закуска | 13 | Бастурма | Г | Мясо | 300. | 5
. . .
1 | З | 3 | 1 | Завтрак | З | Закуска | 32 | Кофе черный | Н | Кофе | 100. | 1
1 | З | 3 | 1 | Завтрак | З | Закуска | 33 | Кофе на молоке | Н | Кофе | 200. | 2
1 | З | 6 | 1 | Завтрак | З | Закуска | 1 | Салат летний | З | Овощи | 200. | 3
1 | З | 6 | 1 | Завтрак | З | Закуска | 2 | Салат мясной | З | Мясо | 200. | 4
1 | З | 6 | 1 | Завтрак | З | Закуска | 3 | Салат витаминный | З | Овощи | 200. | 4
1 | З | 6 | 1 | Завтрак | З | Закуска | 4 | Салат рыбный | З | Рыба | 200. | 4
1 | З | 6 | 1 | Завтрак | З | Закуска | 5 | Паштет из рыбы | З | Рыба | 120. | 5
1 | З | 6 | 1 | Завтрак | З | Закуска | 6 | Мясо с гарниром | З | Мясо | 250. | 3 *
. . .

Рис. 3.1. Иллюстрация декартова произведения

Очевидно, что отбор актуальных строк обеспечивается вводом в запрос WHERE фразы, в которой устанавливается соответствие между:

  • кодами трапез (Т) в таблицах Меню и Трапезы (Меню.Т = Трапезы.Т),
  • кодами видов блюд (В) в таблицах Меню и Вид_блюд (Меню.В = Вид_блюд.В),
  • номерами блюд (БЛ) в таблицах Меню и Блюда (Меню.БЛ = Блюда.БЛ).

Такой скорректированный запрос

SELECT	Меню.*, Трапезы.*, Вид_блюд.*, Блюда.*
FROM	Меню, Трапезы, Вид_блюд, Блюда
WHERE	Меню.Т = Трапезы.Т
AND	Меню.В = Вид_блюд.В
AND	Меню.БЛ = Блюда.БЛ;

позволит получить эквисоединение таблиц Меню, Трапезы, Вид_блюд и Блюда:

Т | В | БЛ | Т | Трапеза | В | Вид | БЛ | Блюдо | В | Основа | Выход | Труд
1 | З | 3 | 1 | Завтрак | З | Закуска | 3 | Салат витаминный | З | Овощи | 200. | 4
1 | З | 6 | 1 | Завтрак | З | Закуска | 6 | Мясо с гарниром | З | Мясо | 250. | 3
1 | Г | 19 | 1 | Завтрак | Г | Горячее | 19 | Омлет с луком | Г | Яйца | 200. | 5
. . .
3 | Г | 16 | 3 | Ужин | Г | Горячее | 16 | Драчена | Г | Яйца | 180. | 4
3 | Н | 30 | 3 | Ужин | Н | Напиток | 30 | Компот | Н | Фрукты | 200. | 2
3 | Н | 31 | 3 | Ужин | Н | Напиток | 31 | Молочный напиток | Н | Молоко | 200. | 2


Естественное соединение таблиц

3.2.3. Естественное соединение таблиц

Легко заметить, что в эквисоединение таблиц вошли дубликаты столбцов, по которым проводилось соединение (Т, В и БЛ). Для исключения этих дубликатов можно создать естественное соединение тех же таблиц:

SELECT	Т, В, БЛ, Трапеза, Вид, Блюдо, Основа,
                                  Выход, Труд
FROM	Меню, Трапезы, Вид_блюд, Блюда
WHERE	Меню.Т = Трапезы.Т
AND	Меню.В = Вид_блюд.В
AND	Меню.БЛ = Блюда.БЛ;

Реализация естественного соединения таблиц имеет вид

Т | В | БЛ | Трапеза | Вид | Блюдо | Основа | Выход | Труд
1 | З | 3 | Завтрак | Закуска | Салат витаминный | Овощи | 200. | 4
1 | З | 6 | Завтрак | Закуска | Мясо с гарниром | Мясо | 250. | 3
1 | Г | 19 | Завтрак | Горячее | Омлет с луком | Яйца | 200. | 5
...
3 | Г | 16 | Ужин | Горячее | Драчена | Яйца | 180. | 4
3 | Н | 30 | Ужин | Напиток | Компот | Фрукты | 200. | 2
3 | Н | 31 | Ужин | Напиток | Молочный напиток | Молоко | 200. | 2


Композиция таблиц

3.2.4. Композиция таблиц

Для исключения всех столбцов, по которым проводится соединение таблиц, надо создать композицию

SELECT	Трапеза, Вид, Блюдо, Основа, Выход, Труд
FROM	Меню, Трапезы, Вид_блюд, Блюда
WHERE	Меню.Т = Трапезы.Т
AND	Меню.В = Вид_блюд.В
AND	Меню.БЛ = Блюда.БЛ;

имеющую вид

Трапеза | Блюдо | Вид | Основа | Выход | Труд
Завтрак | Салат витаминный | Закуска | Овощи | 200. | 4
Завтрак | Мясо с гарниром | Закуска | Мясо | 250. | 3
Завтрак | Омлет с луком | Горячее | Яйца | 200. | 5
. . .
Ужин | Драчена | Горячее | Яйца | 180. | 4
Ужин | Компот | Напиток | Фрукты | 200. | 2
Ужин | Молочный напиток | Напиток | Молоко | 200. | 2


Тета-соединение таблиц

3.2.5. Тета-соединение таблиц

В базе данных ПАНСИОН трудно подобрать несложный пример, иллюстрирующий тета-соединение таблиц. Поэтому сконструируем такой надуманный запрос:

SELECT	Вид_блюд.*, Трапезы.*
FROM	Вид_блюд, Трапезы
WHERE	Вид > Трапеза;

позволяющий выбрать из полученного в п.3.2.1 декартова произведения таблиц Вид_блюд и Трапезы лишь те строки, в которых значение трапезы "меньше" (по алфавиту) значения вида блюда:

В | Вид | Т | Трапеза
З | Закуска | 1 | Завтрак
С | Суп | 1 | Завтрак
С | Суп | 2 | Обед
Н | Напиток | 1 | Завтрак


Соединение таблиц с дополнительным условием

3.2.6. Соединение таблиц с дополнительным условием

При формировании соединения создается рабочая таблица, к которой применимы все операции, рассмотренные в главе 2: отбор нужных строк соединения (WHERE фраза), упорядочение получаемого результата (ORDER BY фраза) и агрегатирование данных (SQL-функции и GROUP BY фраза).

Например, для получения перечня блюд, предлагаемых в меню на завтрак, можно сформировать запрос на основе композиции (п. 3.2.4):

SELECT	Вид, Блюдо, Основа, Выход, 'Номер -', БЛ
FROM	Меню, Трапезы, Вид_блюд, Блюда
WHERE	Меню.Т = Трапезы.Т
AND	Меню.В = Вид_блюд.В
AND	Меню.БЛ = Блюда.БЛ
AND	Трапеза = ’Завтрак’;

Получим

Вид | Блюдо | Основа | Выход | 'Номер -' | БЛ
Закуска | Салат витаминный | Овощи | 200. | Номер - | 3
Закуска | Мясо с гарниром | Мясо | 250. | Номер - | 6
Горячее | Омлет с луком | Яйца | 200. | Номер - | 19
Горячее | Пудинг рисовый | Крупа | 160. | Номер - | 21
Напиток | Молочный напиток | Молоко | 200. | Номер - | 31
Напиток | Кофе черный | Кофе | 100. | Номер - | 32

В п.3.6 можно познакомиться с достаточно полным примером соединения таблиц с различными дополнительными фразами.


Соединение таблицы со своей копией

3.2.7. Соединение таблицы со своей копией

В ряде приложений возникает необходимость одновременной обработки данных какой-либо таблицы и одной или нескольких ее копий, создаваемых на время выполнения запроса.

Например, при создании списков студентов (таблица Студенты) возможен повторный ввод данных о каком-либо студенте с присвоением ему второго номера зачетной книжки. Для выявления таких ошибок можно соединить таблицу Студенты с ее временной копией, установив в WHERE фразе равенство значений всех одноименных столбцов этих таблиц кроме столбцов с номером зачетной книжки (для последних надо установить условие неравенства значений).

Временную копию таблицы можно сформировать, указав имя псевдонима за именем таблицы во фразе FROM. Так, с помощью фразы

FROM Блюда X, Блюда Y, Блюда Z
будут сформированы три копии таблицы Блюда с именами X, Y и Z.

В качестве примера соединения таблицы с ней самой сформируем запрос на вывод таких пар блюд таблицы Блюда, в которых совпадает основа, а название первого блюда пары меньше (по алфавиту), чем номер второго блюда пары. Для этого можно создать запрос с одной копией таблицы Блюда (Копия):

SELECT	Блюдо, Копия.Блюдо, Основа
FROM	Блюда, Блюда Копия
WHERE	Основа = Копия.Основа
AND	Блюдо < Копия.Блюдо;

или двумя ее копиями (Первая и Вторая):

SELECT	Первая.Блюдо, Вторая.Блюдо, Основа
FROM	Блюда Первая, Блюда Вторая
WHERE	Первая.Основа = Вторая.Основа
AND	Первая.Блюдо < Вторая.Блюдо;

Получим результат вида

Первая.Блюдо | Вторая.Блюдо | Основа
Морковь с рисом | Помидоры с луком | Овощи
Морковь с рисом | Салат летний | Овощи
Морковь с рисом | Салат витаминный | Овощи
Помидоры с луком | Салат витаминный | Овощи
Помидоры с луком | Салат летний | Овощи
Салат витаминный | Салат летний | Овощи
Бастурма | Бефстроганов | Мясо
Бастурма | Мясо с гарниром | Мясо
Бефстроганов | Мясо с гарниром | Мясо


Вложенные подзапросы. Виды вложенных подзапросов

3.3. Вложенные подзапросы
3.3.1. Виды вложенных подзапросов

Вложенный подзапрос - это подзапрос, заключенный в круглые скобки и вложенный в WHERE (HAVING) фразу предложения SELECT или других предложений, использующих WHERE фразу. Вложенный подзапрос может содержать в своей WHERE (HAVING) фразе другой вложенный подзапрос и т.д. Нетрудно догадаться, что вложенный подзапрос создан для того, чтобы при отборе строк таблицы, сформированной основным запросом, можно было использовать данные из других таблиц (например, при отборе блюд для меню использовать данные о наличии продуктов в кладовой пансионата).

Существуют простые и коррелированные вложенные подзапросы. Они включаются в WHERE (HAVING) фразу с помощью условий IN, EXISTS или одного из условий сравнения ( = | <> | < | <= | > | >= ). Простые вложенные подзапросы обрабатываютя системой "снизу вверх". Первым обрабатывается вложенный подзапрос самого нижнего уровня. Множество значений, полученное в результате его выполнения, используется при реализации подзапроса более высокого уровня и т.д.

Запросы с коррелированными вложенными подзапросами обрабатываются системой в обратном порядке. Сначала выбирается первая строка рабочей таблицы, сформированной основным запросом, и из нее выбираются значения тех столбцов, которые используются во вложенном подзапросе (вложенных подзапросах). Если эти значения удовлетворяют условиям вложенного подзапроса, то выбранная строка включается в результат. Затем выбирается вторая строка и т.д., пока в результат не будут включены все строки, удовлетворяющие вложенному подзапросу (последовательности вложенных подзапросов).

Следует отметить, что SQL обладает большой избыточностью в том смысле, что он часто предоставляет несколько различных способов формулировки одного и того же запроса. Поэтому во многих примерах данной главы будут использованы уже знакомые нам по предыдущей главе концептуальные формулировки запросов. И несмотря на то, что часть из них успешнее реализуется с помощью соединений, здесь все же будут приведены их варианты с использованием вложенных подзапросов. Это связано с необходимостью детального знакомства с созданием и принципом выполнения вложенных подзапросов, так как существует немало задач (особенно на удаление и изменение данных), которые не могут быть реализованы другим способом. Кроме того, разные формулировки одного и того же запроса требуют для своего выполнения различных ресурсов памяти и могут значительно отличаться по времени реализации в разных СУБД.


Простые вложенные подзапросы

3.3.2. Простые вложенные подзапросы

Простые вложенные подзапросы используются для представления множества значений, исследование которых должно осуществляться в каком-либо предикате IN, что иллюстрируется в следующем примере: выдать название и статус поставщиков продукта с номером 11, т.е. помидоров.

Результат:
SELECT	Название, Статус
FROM	Поставщики
WHERE	ПС IN
    (	SELECT	ПС
        FROM	Поставки
        WHERE	ПР = 11 );

Название | Статус
СЫТНЫЙ | рынок
УРОЖАЙ | коопторг
ЛЕТО | агрофирма
КОРЮШКА | кооператив

Как уже отмечалось в п.3.3.1, при обработке полного запроса система выполняет прежде всего вложенный подзапрос. Этот подзапрос выдает множество номеров поставщиков, которые поставляют продукт с кодом ПР = 11, а именно множество (1, 5, 6, 8). Поэтому первоначальный запрос эквивалентен такому простому запросу:

SELECT	Название, Статус
FROM	Поставщики
WHERE	ПС IN (1, 5, 6, 8);

Подзапрос с несколькими уровнями вложенности можно проиллюстрировать на том же примере. Пусть требуется узнать не поставщиков продукта 11, как это делалось в предыдущих запросах, а поставщиков помидоров, являющихся продуктом с номером 11. Для этого можно дать запрос

SELECT	Название, Статус
FROM	Поставщики
WHERE	ПС IN
    (	SELECT	ПС
        FROM	Поставки
        WHERE	ПР IN
            (	SELECT	ПР
                FROM	Продукты
                WHERE	Продукт = 'Помидоры' ));

В данном случае результатом самого внутреннего подзапроса является только одно значение (11). Как уже было показано выше, подзапрос следующего уровня в свою очередь дает в результате множество (1, 5, 6, 8). Последний, самый внешний SELECT, вычисляет приведенный выше окончательный результат. Вообще допускается любая глубина вложенности подзапросов.

Тот же результат можно получить с помощью соединения

SELECT	Название, Статус
FROM	Поставщики, Поставки, Продукты
WHERE	Поставщики.ПС = Поставки.ПС
AND	Поставки.ПР = Продукты.ПР
AND	Продукт = 'Помидоры';

При выполнении этого компактного запроса система должна одновременно обрабатывать данные из трех таблиц, тогда как в предыдущем примере эти таблицы обрабатываются поочередно. Естественно, что для их реализации тебуются различные ресурсы памяти и времени, однако этого невозможно ощутить при работе с ограниченным объемом данных в иллюстративной базе ПАНСИОН.


Использование одной и той же таблицы во внешнем и вложенном подзапросе

3.3.3. Использование одной и той же таблицы во внешнем и вложенном подзапросе

Выдать номера поставщиков, которые поставляют хотя бы один продукт, поставляемый поставщиком 6.

Результат:
SELECT	DISTINCT ПС
FROM	Поставки
WHERE	ПР IN
    (	SELECT	ПР
        FROM	Поставки
        WHERE	ПС = 6);
ПС
1
3
5
6
8

Отметим, что ссылка на Поставки во вложенном подзапросе означает не то же самое, что ссылка на Поставки во внешнем запросе. В действительности, два имени Поставки обозначают различные значения. Чтобы этот факт стал явным, полезно использовать псевдонимы, например, X и Y:

SELECT	DISTINCT X.ПС
FROM	Поставки X
WHERE	X.ПР IN
    (	SELECT	Y.ПР
        FROM	Поставки Y
        WHERE	Y.ПС = 6 );

Здесь X и Y – произвольные псевдонимы таблицы Поставки, определяемые во фразе FROM и используемые как явные уточнители во фразах SELECT и WHERE. Напомним, что псевдонимы определены лишь в пределах одного запроса.


Вложенный подзапрос с оператором сравнения, отличным от IN

3.3.4. Вложенный подзапрос с оператором сравнения, отличным от IN

Выдать номера поставщиков, находящихся в том же городе, что и поставщик с номером 6.

Результат:
SELECT	ПС
FROM	Поставщики
WHERE	Город =
    (	SELECT	Город
        FROM	Поставщики
        WHERE 	ПС = 6 );
ПС
1
4
6

В подобных запросах можно использовать и другие операторы сравнения (<>, <=, <, >= или >), однако, если вложенный подзапрос возвращает более одного значения и не используется оператор IN, будет возникать ошибка.


Коррелированные вложенные подзапросы

3.3.5. Коррелированные вложенные подзапросы

Выдать название и статус поставщиков продукта с номером 11.

SELECT	Название, Статус
FROM	Поставщики
WHERE	11 IN
    (	SELECT	ПР
        FROM	Поставки
        WHERE	ПС = Поставщики.ПС );

Такой подзапрос отличается от рассмотренного в п.3.3.2 тем, что вложенный подзапрос не может быть обработан прежде, чем будет обрабатываться внешний подзапрос. Это связано с тем, что вложенный подзапрос зависит от значения Поставщики.ПС а оно изменяется по мере того, как система проверяет различные строки таблицы Поставщики. Следовательно, с концептуальной точки зрения обработка осуществляется следующим образом:

  1. Система проверяет первую строку таблицы Поставщики. Предположим, что это строка поставщика с номером 1. Тогда значение Поставщики.ПС будет в данный момент имеет значение, равное 1, и система обрабатывает внутренний запрос
    (	SELECT	ПР
    FROM	Поставки
    WHERE	ПС = 1 );
    получая в результате множество (9, 11, 12, 15). Теперь система может завершить обработку для поставщика с номером 1. Выборка значений Название и Статус для ПС=1 (СЫТНЫЙ и рынок) будет проведена тогда и только тогда, когда ПР=11 будет принадлежать этому множеству, что, очевидно, справедливо.
  2. Далее система будет повторять обработку такого рода для следующего поставщика и т.д. до тех пор, пока не будут рассмотрены все строки таблицы Поставщики.

Подобные подзапросы называются коррелированными, так как их результат зависит от значений, определенных во внешнем подзапросе. Обработка коррелированного подзапроса, следовательно, должна повторяться для каждого значения извлекаемого из внешнего подзапроса, а не выполняться раз и навсегда.

Рассмотрим пример использования одной и той же таблицы во внешнем подзапросе и коррелированном вложенном подзапросе.

Выдать номера всех продуктов, поставляемых только одним по-ставщиком.

Результат:
SELECT	DISTINCT X.ПР
FROM	Поставки X
WHERE	X.ПР NOT IN
    (	SELECT	Y.ПР
        FROM	Поставки Y
        WHERE	Y.ПС <> X.ПС );
X.ПР
17

Действие этого запроса можно пояснить следующим образом: "Поочередно для каждой строки таблицы Поставки, скажем X, выделить значение номера продукта (ПР), если и только если это значение не входит в некоторую строку, скажем, Y, той же таблицы, а значение столбца номер поставщика (ПС) в строке Y не равно его значению в строке X".

Отметим, что в этой формулировке должен быть использован по крайней мере один псевдоним - либо X, либо Y.


Запросы, использующие EXISTS

3.3.6. Запросы, использующие EXISTS

Квантор EXISTS (существует) - понятие, заимствованное из формальной логики. В языке SQL предикат с квантором существования представляется выражением EXISTS (SELECT * FROM ...).

Такое выражение считается истинным только тогда, когда результат вычисления "SELECT * FROM ..." является непустым множеством, т.е. когда существует какая-либо запись в таблице, указанной во фразе FROM подзапроса, которая удовлетворяет условию WHERE подзапроса. (Практически этот подзапрос всегда будет коррелированным множеством.)

Рассмотрим примеры. Выдать названия поставщиков, поставляющих продукт с номером 11.

Результат:
  SELECT	Название
FROM	Поставщики
WHERE	EXISTS
    (	SELECT	*
        FROM	Поставки
        WHERE	ПС = Поставщики.ПС
        AND	ПР = 11 );
Название
СЫТНЫЙ
УРОЖАЙ
КОРЮШКА
ЛЕТО

Система последовательно выбирает строки таблицы Поставщики, выделяет из них значения столбцов Название и ПС, а затем проверяет, является ли истинным условие существования, т.е. су-ществует ли в таблице Поставки хотя бы одна строка со значением ПР=11 и значением ПС, равным значению ПС, выбранному из таблицы Поставщики. Если условие выполняется, то полученное значение столбца Название включается в результат.

Предположим, что первые значения полей Название и ПС равны, соответственно, 'СЫТНЫЙ' и 1. Так как в таблице Поставки есть строка с ПР=11 и ПС=1, то значение 'СЫТНЫЙ' должно быть включено в результат.

Хотя этот первый пример только показывает иной способ формулировки запроса для задачи, решаемой и другими путями (с помощью оператора IN или соединения), EXISTS представляет собой одну из наиболее важных возможностей SQL. Фактически любой запрос, который выражается через IN, может быть альтернативным образом сформулирован также с помощью EXISTS. Однако обратное высказывание несправедливо.

Выдать название и статус поставщиков, не поставляющих продукт с номером 11.

Результат:
  SELECT	Название, Статус
  FROM	Поставщики
  WHERE	NOT EXISTS
    (	SELECT	*
        FROM 	Поставки
        WHERE	ПС = Поставщики.ПС
        AND	ПР = 11 );

Название | Статус
ПОРТОС | кооператив
ШУШАРЫ | совхоз
ТУЛЬСКИЙ | универсам
ОГУРЕЧИК | ферма


Функции в подзапросе

3.3.7. Функции в подзапросе

Теперь, после знакомства с различными формулировками вложенных подзапросов и псевдонимами легче понять текст и алгоритм реализации запроса (п. 3.1) на получение тех поставщиков продуктов для Сырников, которые поставляют эти продукты за минимальную цену:

SELECT	Продукт, Цена, Название, Статус
FROM	Продукты, Состав, Блюда, Поставки, Поставщики
WHERE	Продукты.ПР = Состав.ПР
AND	Состав.БЛ = Блюда.БЛ
AND	Поставки.ПР = Состав.ПР
AND	Поставки.ПС = Поставщики.ПС
AND	Блюдо = 'Сырники'
AND	Цена =	(	SELECT MIN(Цена)
            FROM	Поставки X
            WHERE	X.ПР = Поставки.ПР );

Естественно, что это коррелированный подзапрос: здесь сначала определяется минимальная цена продукта, входящего в состав Сырников, и только затем выясняется его поставщик.

На этом примере мы закончим знакомство с вложенными подзапросами, предложив попробовать свои силы в составлении ряда запросов, с помощью механизма таких подзапросов:

  1. Выдать названия всех мясных блюд.
  2. Выдать количество всех блюд, в состав которых входят помидоры.
  3. Выдать блюда, продукты для которых поставляются агрофирмой ЛЕТО.


Объединение (UNION)

3.4. Объединение (UNION)

В литературе [2] рассматривалась реляционная операция "Объединение", позволяющая получить отношение, состоящее из всех строк, входящих в одно или оба объединяемых отношения. Но при этом исходные отношения или их объединяемые проекции должны быть совместимыми по объединению. Для SQL это означает, что две таблицы можно объединять тогда и только тогда, когда:

  1. они имеют одинаковое число столбцов, например, m;
  2. для всех i (i = 1, 2, ..., m) i-й столбец первой таблицы и i-й столбец второй таблицы имеют в точности одинаковый тип данных.

Например, выдать названия продуктов, в которых нет жиров, либо входящих в состав блюда с кодом БЛ = 1:

Результат: Продукт
SELECT	Продукт
FROM	Продукты
WHERE	Жиры = 0
UNION
SELECT	Продукт
FROM	Соста
WHERE	БЛ = 1
Майонез
Лук
Помидоры
Зелень
Яблоки
Сахар

Из этого простого примера видно, что избыточные дубликаты всегда исключаются из результата UNION. Поэтому, хотя в рассматриваемом примере Помидоры, Зелень и Яблоки выбираются обеими из двух составляющих предложения SELECT, в окончательном результате они появляются только один раз.

Предложением с UNION можно объединить любое число таблиц (проекций таблиц). Так, к предыдущему запросу можно добавить (перед точкой с запятой) конструкцию

UNION
SELECT	Продукт
FROM	Продукты
WHERE	Ca < 250

позволяющую добавить к списку продуктов Масло, Рис, Мука и Кофе. Однако тот же результат можно получить простым изменением фразы WHERE первой части исходного запроса

WHERE Жиры = 0  OR  Ca < 250


Реализация операций реляционной алгебры предложением SELECT

3.5. Реализация операций реляционной алгебры предложением SELECT

С помощью предложения SELECT можно реализовать любую операцию реляционной алгебры [2].

Селекция (горизонтальное подмножество) таблицы создается из тех ее строк, которые удовлетворяют заданным условиям. Пример:

SELECT	*
FROM	Блюда
WHER	Основа = 'Молоко'
AND	Выход > 200;

Проекция (вертикальное подмножество) таблицы создается из указанных ее столбцов (в заданном порядке) с последующим исключением избыточных дубликатов строк. Пример:

SELECT	DISTINCT Блюдо, Выход, Основа
FROM	Блюда;

Объединение двух таблиц содержит те строки, которые есть либо в первой, либо во второй, либо в обеих таблицах. Пример:

SELECT	Блюдо, Основа, Выход
FROM	Блюда
WHER	Основа = 'Овощи'
UNION
SELECT	Блюдо, Основа, Выход
FROM	Блюда
WHER	В = 'Г';

Пересечение двух таблиц содержит только те строки, которые есть и в первой, и во второй. Пример:

SELECT	БЛ
FROM	Состав
WHERE	БЛ IN
    (	SELECT БЛ
        FROM Меню);

Разность двух таблиц содержит только те строки, которые есть в первой, но отсутствуют во второй. Пример:

SELECT	БЛ
FROM	Состав
WHERE	БЛ NOT IN
    (	SELECT БЛ
        FROM Меню);

Декартово произведение таблиц и различные виды соединений были подробно рассмотрены в п. 3.2.1-3.2.6.

Здесь опущено лишь достаточно нудное описание редко встречаемой операция деления, которая также может быть реализована предложением SELECT с коррелированными вложенными подзапросами.


Резюме

3.6. Резюме

Краткое знакомство с возможностями предложения SELECT показало, что с его помощью можно реализовать все реляционные операции. Кроме того, в предложении SELECT выполняются разнообразные вычисления, агрегирование данных, их упорядочение и ряд других операций, позволяющих описать в одном предложении ту работу, для выполнения которой потребовалось бы написать несколько страниц программы на алгоритмических языках Си, Паскаль или на внутренних языках ряда распространенных СУБД.

Например, пусть требуется получить калорийность и стоимость тех блюд, для которых:

  • есть все составляющие их продукты;
  • калорийность не превышает 400 ккал;
  • стоимость не превышает 1.5 рубля, а результат надо упорядочить по возрастанию калорийности блюд в рамках их видов.

Для этого можно дать запрос, показанный на рис. 3.2, позволяющий получить искомый результат в виде таблицы

Вид | Блюдо
Горячее | Помидоры с луком | калорий - | 244.6 | 0.44 | руб
Горячее | Бефстроганов | калорий - | 321.3 | 0.53 | руб
Горячее | Драчена | калорий - | 333.9 | 0.33 | руб
Горячее | Каша рисовая | калорий - | 339.2 | 0.27 | руб
Горячее | Омлет с луком | калорий - | 354.9 | 0.36 | руб
Десерт | Яблоки печеные | калорий - | 170.2 | 0.30 | руб
Десерт | Крем творожный | калорий - | 394.3 | 0.27 | руб
Закуска | Салат летний | калорий - | 155.5 | 0.32 | руб
Закуска | Салат витаминный | калорий - | 217.4 | 0.37 | руб
Закуска | Творог | калорий - | 330.0 | 0.22 | руб
Закуска | Мясо с гарниром | калорий - | 378.7 | 0.62 | руб
Напиток | Кофе черный | калорий - | 7.1 | 0.05 | руб
Напиток | Компот | калорий - | 74.4 | 0.14 | руб
Напиток | Кофе на молоке | калорий - | 154.8 | 0.11 | руб
Напиток | Молочный напиток | калорий - | 264.9 | 0.34 | руб
Суп | Суп молочный | калорий - | 396.6 | 0.22 | руб
SELECT	Вид, Блюдо, 'калорий -',
    (SUM(INT((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000)),
    (SUM(Стоимость/К_во*Вес/1000)+MIN(Труд/100)),’руб’
FROM	Блюда, Вид_блюд, Состав, Продукты, Наличие
WHERE	Блюда.БЛ  = Состав.БЛ
AND	Состав.ПР = Продукты.ПР
AND	Состав.ПР = Наличие.ПР
AND	Блюда.В	= Вид_блюд.В
AND	БЛ NOT IN
    (	SELECT	БЛ
        FROM	Состав
        WHERE 	ПР IN
            (	SELECT	ПР
                FROM	Наличие
                WHERE	К_во = 0))
GROUP	BY Вид, Блюдо
HAVING	SUM(Стоимость/К_во*Вес/1000+MIN(Труд/100))< 1.5
    AND	SUM(((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000) < 400
ORDER	BY Вид, 4;

Рис. 3.2. Пример сложного запроса

Такой результат, нестрого говоря, строился следующим образом.

  1. FROM. Эта фраза инициирует создание в рабочей памяти таблицы, являющейся декартовым произведением таблиц Блюда, Вид_блюд, Состав, Продукты и Наличие.
  2. WHERE. Эта фраза нужна для преобразования полученного декартова произведения в естественное соединение и удаления из последнего строк с кодами блюд, не обеспеченных продуктами. Естественное соединение образуется путем вычеркивания строк, где не совпадают: код блюда из таблицы Блюда с кодом блюда из таблицы Состав, код продукта из таблицы Состав с кодом продукта из таблицы Продукты и т.д. Обеспеченность блюда всеми продуктами проверяется с помощью последовательности подзапросов. Внутренний подзапрос выдает перечень кодов продуктов, которых нет в кладовой пансионата. Следующий подзапрос выдает коды тех блюд, в состав которых должны входить "отсутствующие" продукты. И, наконец, из естественного соединения вычеркиваются строки с кодами полученных блюд (точнее оставляются строки "Где код блюда не принадлежит перечню кодов блюд, полученному в подзапросе".
  3. SELECT. Из полученного соединения удаляются столбцы, не используемые в выражениях SELECT или других фразах. Если в списке SELECT есть выражения (константы), то для хранения их значений формируются дополнительные столбцы и инициируются операции по их заполнению. В рассматриваемом примере будут сохранены столбцы Вид, Блюдо, Белки, Углев, Жиры, Вес, Стоимость, К_во и созданы дополнительные столбцы для формирования и хранения значений стоимости и калорийности составляющих каждого блюда, а также для хранения текстовых констант 'калорий -' и 'руб'. Обратите внимание на прием, использованный при суммировании стоимостей продуктов, входящих в состав блюда, и стоимости его приготовления (Труд): можно ли заменить MIN на MAX или AVG?
  4. GROUP BY. Отредактированное естественное соединение группируется по видам блюд и их названиям. Создаются группы горячих блюд, десертов и т.д., а внутри каждой группы создаются подгруппы строк со сведениями о продуктах, относящихся к конкретному блюду группы.
  5. SELECT. Каждая подгруппа строк, полученная на предыдущем шаге, преобразуется в единственную строку для результата. В нее заносится вид блюда (общий для всех подгрупп группы), название блюда (общее для всех строк подгруппы), две текстовых константы ('калорий -' и 'руб') и две суммы. Последние формируются путем суммирования тех значений дополнительных столбцов, которые принадлежат подгруппе.
  6. HAVING. Сформированные строки, не удовлетворяющие условиям фразы HAVING
    SUM(Стоимость/К_во*Вес/1000+MIN(Труд/100)) < 1.5 и
SUM(((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000) < 400
    исключаются из результата предыдущего шага.
  7. ORDER BY. Результат шага 6 упорядочивается в соответствии со списком фразы ORDER BY для получения окончательного результата. Сначала строки группируются по видам блюд (в алфавитном порядке), а затем – по значению элемента данных, указанного на четвертом месте фразы SELECT, т.е. по калорийности.

Конечно, рассмотренный запрос весьма сложен, но попробуйте написать на любом знакомом вам языке программу, реализующую те же действия, и оцените сложность ее написания и отладки.


Предложения модификации данных SQL

Глава 4. Предложения модификации данных SQL
4.1. Особенности и синтаксис предложений модификации

Модификация данных может выполняться с помощью предложений DELETE (удалить), INSERT (вставить) и UPDATE (обновить). Подобно предложению SELECT они могут оперировать как базовыми таблицами, так и представлениями. Однако по ряду причин не все представления являются обновляемыми. Пока зафиксируем этот факт, отложив описание представлений и особенностей их обновления до главы 5, но будем помнить, что термин "представление" относится только к обновляемым представлениям.

Предложение DELETE имеет формат

DELETE
FROM   базовая таблица | представление
[WHERE фраза];

и позволяет удалить содержимое всех строк указанной таблицы (при отсутствии WHERE фразы) или тех ее строк, которые выделяются WHERE фразой.

Предложение INSERT имеет один из следующих форматов:

INSERT
INTO    {базовая таблица | представление}
                [(столбец [,столбец] ...)]
VALUES ({константа | переменная}
           [,{константа | переменная}] ...);

или

INSERT
INTO {базовая таблица | представление}
             [(столбец [,столбец] ...)]
     подзапрос;

В первом формате в таблицу вставляется строка со значениями полей, указанными в перечне фразы VALUES (значения), причем i-е значение соответствует i-му столбцу в списке столбцов (столбцы, не указанные в списке, заполняются NULL-значениями). Если в списке VALUES фразы указаны все столбцы модифицируемой таблицы и порядок их перечисления соответствует порядку столбцов в описании таблицы, то список столбцов в фразе INTO можно опустить. Однако не советуем этого делать, так как при изменении описания таблицы (перестановка столбцов или изменение их числа) придется переписывать и INSERT предложение.

Во втором формате сначала выполняется подзапрос, т.е. по предложению SELECT в памяти формируется рабочая таблица, а потом строки рабочей таблицы загружаются в модифицируемую таблицу. При этом i-й столбец рабочей таблицы (i-й элемент списка SELECT) соответствует i-му столбцу в списке столбцов модифицируемой таблицы. Здесь также при выполнении указанных выше условий может быть опущен список столбцов фразы INTO.

Предложение UPDATE также имеет два формата. Первый из них:

UPDATE (базовая таблица | представление}
SET	столбец = значение [, столбец = значение] ...
[WHERE фраза]

где значение - это

столбец | выражение | константа | переменная

и может включать столбцы лишь из обновляемой таблицы, т.е. значение одного из столбцов модифицируемой таблицы может заменяться на значение ее другого столбца или выражения, содержащего значения нескольких ее столбцов, включая изменяемый.

При отсутствии WHERE фразы обновляются значения указанных столбцов во всех строках модифицируемой таблицы. WHERE фраза позволяет сократить число обновляемых строк, указывая условия их отбора.

Второй формат описывает предложение, позволяющее производить обновление значений модифицируемой таблицы по значениям столбцов из других таблиц. К сожалению в ряде СУБД эти форматы отличаются друг от друга и от стандарта. Для примера приведем один из таких форматов:

UPDATE  {базовая таблица | представление}
SET	столбец = значение [, столбец = значение] ...
FROM    {базовая таблица | представление} [псевдоним],
        {базовая таблица | представление} [псевдоним]
      [,{базовая таблица | представление}
                                [псевдоним]] ...
[WHERE  фраза]

Здесь перечень таблиц фразы FROM содержит имя модифицируемой таблицы и тех таблиц, значения столбцов которых используются для обновления. При этом, естественно, таблицы должны быть связаны между собой в WHERE фразе (см.п.3.2.3), которая, кроме того, служит для указания условий отбора обновляемых строк модифицируемой таблицы.

В значениях, находящихся в правых частях равенств фразы SET, следует уточнять имена используемых столбцов, предваряя их именем таблицы (псевдонима).


Предложение DELETE

4.2. Предложение DELETE
4.2.1. Удаление единственной записи

Удалить поставщика с ПС = 7.

DELETE
FROM    Поставщики
WHERE   ПС = 7;

Если таблица Поставки содержит в момент выполнения этого предложения какие-либо поставки для поставщика с ПС = 7, то такое удаление нарушит непротиворечивость базы данных. К сожалению нет операции удаления, одновременно воздействующей на несколько таблиц. Однако в некоторых СУБД реализованы механизмы поддержания целостности (см.п.2.5 в литературе [2]), позволяющие отменить некорректное удаление или каскадировать удаление на несколько таблиц.

4.2.2. Удаление множества записей

Удалить все поставки.

DELETE
FROM	Поставки;

Поставки - все еще известная таблица, но в ней теперь нет строк. Для уничтожения таблицы надо выполнить операцию DROP TABLE Поставки (см.п.5.2).

Удалить все мясные блюда.

DELETE FROM Блюда
WHERE  Основа = 'Мясо';
4.2.3. Удаление с вложенным подзапросом

Удалить все поставки для поставщика из Паневежиса.

DELETE
FROM	Поставки
WHERE	ПС IN
     (SELECT  ПС
      FROM    Поставщики
      WHERE   Город = 'Паневежис');


Предложение INSERT

4.3. Предложение INSERT
4.3.1. Вставка единственной записи в таблицу

Добавить в таблицу Блюда блюдо:

Шашлык (БЛ - 34, Блюдо - Шашлык, В - Г,
                      Основа - Мясо, Выход - 150)

при неизвестной пока трудоемкости приготовления этого блюда.

INSERT
INTO	Блюда (БЛ, Блюдо, В, Основа, Выход)
VALUES	(34, 'Шашлык', 'Г', 'Мясо', 150);

Создается новая запись для блюда с номером 34, с неопределенным значением в столбце Труд.

Порядок полей в INSERT не обязательно должен совпадать с порядком полей, в котором они определялись при создании таблицы. Вполне допустима и такая версия предыдущего предложения:

INSERT
INTO	Блюда (Основа, В, Блюдо, БЛ, Выход)
VALUES	('Мясо', 'Г', 'Шашлык', 34, 150);

При известной трудоемкости приготовления шашлыка (например, 5 коп) сведения о нем можно ввести с помощью укороченного предложения:

INSERT
INTO	Блюда
VALUES	(34, 'Шашлык', 'Г', 'Мясо', 150, 5);

в котором должен соблюдаться строгий порядок перечисления вводимых значений, так как, не имея перечня загружаемых столб-цов, СУБД может использовать лишь перечень, который определен при создании модифицируемой таблицы.

В предыдущих примерах проводилась модификация стержневой сущности, т.е. таблицы с первичным ключом БЛ (см.п.2.4 в литературе [2]). Почти все СУБД имеют механизмы для предотвращения ввода не уникального первичного ключа, например, ввода "Шашлыка" под номером, меньшим 34. А как быть с ассоциациями или другими таблицами, содержащими внешние ключи?

Пусть, например, потребовалось добавить в рецепт блюда Салат летний (БЛ = 1) немного (15 г) лука (ПР = 10), и мы воспользовались предложением

INSERT
INTO	Состав (БЛ, ПР, Вес)
VALUES	(1, 10, 15);

Подобно операции DELETE операция INSERT может нарушить непротиворечивость базы данных. Если не принять специальных мер, то СУБД не проверяет, имеется ли в таблице Блюда блюдо с первичным ключом БЛ = 1 и в таблице Продукты - продукт с первичным ключом ПР = 10. Отсутствие любого из этих значений породит противоречие: в базе появится ссылка на несуществующую запись. Проблемы, возникающие при использовании внешних ключей, подробно рассмотрены в литературе [2], а здесь отме-тим, что все "приличные" СУБД имеют механизмы для предотв-ращения ввода записей со значениями внешних ключей, отсутст-вующих среди значений соответствующих первичных ключей.


Вставка множества записей

4.3.2. Вставка множества записей

Создать временную таблицу К_меню, содержащую калорийность и стоимость всех блюд, которые можно приготовить из имеющихся продуктов. (Эта таблица будет использоваться шеф-поваром для составления меню на следующий день.)

Для создания описания временной таблицы можно, например, воспользоваться предложением CREATE TABLE (см.п.5.2)

CREATE TABLE К_меню
    (	Вид	CHAR (10),
        Блюдо	CHAR (60),
        Калор_блюда	INTEGER,
        Стоим_блюда	REAL);

а для ее загрузки данными – предложение INSERT с вложенным подзапросами:

INSERT
INTO	К_меню
SELECT	Вид, Блюдо,
    INT(SUM(((Белки+Углев)*4.1+Жиры*9.3) * Вес/1000)),
    (SUM(Стоимость/К_во*Вес/1000) + MIN(Труд/100))
FROM	Блюда, Вид_блюд, Состав, Продукты, Наличие
WHERE	Блюда.БЛ	= Состав.БЛ
AND	Состав.ПР	= Продукты.ПР
AND	Состав.ПР	= Наличие.ПР
AND	Блюда.В	= Вид_блюд.В
AND	БЛ NOT IN
    (	SELECT	БЛ
        FROM	Состав
        WHERE	ПР IN
            (	SELECT	ПР
                FROM	Наличие
                WHERE	К_во = 0))
GROUP BY Вид, Блюдо
ORDER BY Вид, 3;

В этом запросе предложение SELECT выполняется так же, как обычно (см. описание запроса в п.3.6), но результат не выводится на экран, а копируется в таблицу К_меню. Теперь с этой копией можно работать как с обычной базовой таблицей (Блюда, Про-дукты,...), т.е. выбирать из нее даннные на экран или принтер, обновлять в ней данные и т.п. Никакая из этих операций не будет оказывать влияния на исходные данные (например, изменение в ней названия блюда Салат летний на Салат весенний не приведет к подобному изменению в таблице Блюда, где сохранится старое название). Так как это может привести к противоречиям, то подобные временные таблицы уничтожают после их использования. Поэтому программа, обслуживающая шеф-повара, должна исполнять предложение DROP TABLE К_меню после того, как будет закончено составление меню.


Использование INSERT...SELECT для построения внешнего соединения

4.3.3. Использование INSERT...SELECT для построения внешнего соединения

Рассмотренное в п.3.2.3 естественное соединение двух таблиц не включает тех строк какой-либо из них, для которых нет соответствующих строк в другой таблице. Например, если в таблицу Блюда были занесены под номером 34 сведения о Шашлыке, а рецепт его приготовления не был занесен в таблицу Рецепты, то при загрузке их естественного соединения в таблицу Временная:

CREATE TABLE Временная
    (	Вид	CHAR (8),
        Блюдо	CHAR (60),
        Рецепт	CHAR (560));

INSERT
INTO	Временная
SELECT	Вид, Блюдо, Рецепт
FROM	Блюда, Рецепты, Вид_блюд
WHERE	Блюда.БЛ = Рецепты.БЛ
AND	Блюда.В = Вид_блюд.В;

в ней не окажется строки с Шашлыком (в таблице Рецепты не обнаружен код 34, и строка с этим кодом исключена из результата).

Следовательно, в некотором смысле можно считать, что при обычном соединении теряется информация для таких несоответствующих строк. Однако иногда (как и в приведенном примере) может потребоваться способность сохранить эту информацию. В этом случае можно воспользоваться так называемым внешним соединением:

INSERT
INTO	Временная
SELECT	Вид, Блюдо, Рецепт
FROM	Блюда, Рецепты, Вид_блюд
WHERE	Блюда.БЛ = Рецепты.БЛ
AND	Блюда.В = Вид_блюд.В;

INSERT
INTO	Временная
SELECT	Вид, Блюдо, “???”
FROM	Блюда, Вид_блюд
WHERE	Блюда.В = Вид_блюд.В
AND	БЛ NOT IN
    (	SELECT	БЛ
        FROM	Рецепты);

В результате будет создана базовая таблица

Вид | Блюдо | Рецепт
Закуска | Салат летний | Помидоры и яблоки нарезать...
Закуска | Салат мясной | Вареное охлажденное мясо, ...
. . .
Напиток | Кофе черный | Кофеварку или кастрюлю спо...
Напиток | Кофе на молоке | Сварить черный кофе, как ...
Горячее | Шашлык | ???

где первые 33 строки соответствуют первому INSERT и представляют собой проекцию естественного соединения таблиц Блюда и Рецепты по кодам блюд (БЛ), включающую три столбца. Последняя строка результата соответствует второму INSERT и сохраняет информацию о блюде Шашлык, рецепт котого пока не введен в таблицу Рецепты.

Заметим, что для внешнего соединения нужны два отдельных INSERT...SELECT. Однако тот же результат можно получить и одним INSERT...SELECT, используя фразу UNION, объединяющую предложения SELECT из двух INSERT:

INSERT
INTO	Временная
SELECT	Вид, Блюдо, Рецепт
FROM	Блюда, Рецепты, Вид_блюд
WHERE	Блюда.БЛ = Рецепты.БЛ
AND	Блюда.В = Вид_блюд.В
UNION
SELECT	Вид, Блюдо, “???”
FROM	Блюда, Вид_блюд
WHERE	Блюда.В = Вид_блюд.В
AND	БЛ NOT IN
    (	SELECT	БЛ
        FROM	Рецепты);


Предложение UPDATE

4.4. Предложение UPDATE
4.4.1. Обновление единственной записи

Изменить название блюда с кодом БЛ=5 на Форшмак, увеличить его выход на 30 г и установить NULL-значение в столбец Труд.

UPDATE Блюда
SET	Блюдо = 'Форшмак', Выход = (Выход+30), Труд  = NULL
WHERE  БЛ = 5;
4.4.2. Обновление множества записей

Утроить цену всех продуктов таблицы поставки (кроме цены кофе - ПР = 17).

UPDATE Поставки
SET	Цена = Цена * 3
WHERE  ПР <> 17;
4.4.3. Обновление с подзапросом

Установить равной нулю цену и К_во продуктов для поставщиков из Паневежиса и Резекне.

UPDATE Поставки
SET	Цена = 0, К_во = 0
WHERE  ПС IN
    (SELECT ПС
        FROM	Поставщики
        WHERE  Город IN ('Паневежис', 'Резекне'));
4.4.4. Обновление нескольких таблиц

Изменить номер продукта ПР = 13 на ПР = 20.

UPDATE	Продукты	UPDATE	Состав
SET	ПР = 20	SET	ПР = 20
WHERE	ПР = 13;	WHERE	ПР = 13;

UPDATE	Поставки	UPDATE	Наличие
SET	ПР = 20	SET	ПР = 20
WHERE	ПР = 13;	WHERE	ПР = 13;

К сожалению в единственным запросе невозможно обновить более одной таблицы, а так как код продукта входит в четыре таблицы, то пришлось выдать четыре сходных запроса. Это может привести к противоречию базы данных (нарушению целостности по ссылкам), поскольку после выполнения первого предложения таблицы Состав, Поставки и Наличие ссылаются на уже несуществующий продукт. База становится непротиворечивой только после выполнения четвертого запроса.


О конструировании предложений модификации

4.5. О конструировании предложений модификации

Для тех, кто достаточно хорошо понял предложение SELECT, несложно овладеть конструированием предложений DELETE, INSERT и UPDATE. Но в процессе такого конструирования следует учитывать, что:

  1. Если в WHERE фразе предложений DELETE и UPDATE используется вложенный подзапрос, то во фразе FROM этого подзапроса не должна упоминаться таблица, из которой удаляются (в которой обновляются) строки. Аналогично, в подзапросе предложения INSERT не должна упоминаться таблица, в которую загружаются данные.

    Так, SQL отвергнет предложение

    INSERT
INTO	Выбрано
SELECT	(33), Т, БЛ
FROM	Выбрано
WHERE	СМ = 17;

    позволяющее ввести информацию о том, что отдыхающий, сидящий на 33-м месте, выбирает тот же набор блюд, что и отдыхающий, сидящий на 17-м месте. Ввод придется осуществить через какую-либо промежуточную таблицу, например, таблицу Выбор:

    DELETE
FROM	Выбор;

INSERT
INTO	Выбор  (СМ, Т, БЛ)
SELECT	(33), Т, БЛ
FROM	Выбрано
WHERE	СМ = 17;

INSERT
INTO	Выбрано
SELECT	СМ, Т, БЛ
FROM	Выбор;

  2. Составляя предложения модификации данных, необходимо все время помнить о сохранении непротиворечивости базы данных. Об этом упоминалось ранее и подробно говорилось в литературе [2].


О предложениях определения данных и оптимизации запросов

Глава 5. О предложениях определения данных и оптимизации запросов
5.1. Системный каталог

Системный каталог - это набор таблиц, в которых содержится информация, необходимая для правильного функционирования СУБД: о поддерживаемых базах данных и их базовых таблицах, представлениях, курсорах, индексах, пользователях и их правах доступа к информации, правилах модификации данных и т.д. В разных СУБД, поддерживающих SQL, существует от десятка до нескольких десятков системных таблиц, структура которых ничем не отличается от уже знакомой нам структуры пользовательских таблиц.

Так, в каждой строке системной таблицы SYSTABLES хранится описание одной из таблиц пользовательских или системной баз данных. Для каждой из них указывается имя таблицы, имя пользователя, который создал эту таблицу, число столбцов в ней и ряд других элементов информации. В таблице SYSCOLUMNS содержится строка для каждого столбца каждой таблицы, в которой указано имя столбца, имя таблицы, частью которой является данный столбец, тип данных для этого столбца и много другой информации о столбце.

С помощью предложения SELECT пользователь может получить информацию из любой системной таблицы. Например, он может дать запрос на получение имен таблиц, числа их столбцов и строк, владельца и краткого описания (если таковое вводилось в базу данных):

SELRCT	Tab_name,N_col,N_row,Tab_owner,Comments
FROM	SYSTABLES;

и получить результат, показанный на рис. 5.1,а.

Для получения же некоторых данных о столбцах таблицы Блюда можно дать запрос

SELECT	Col_name, Type, Length, Comments
FROM	SYSCOLUMNS
WHERE	Tab_name = 'Блюда';

и получить результат, показанный на рис. 5.1,б.

а)

Tab_name | N_col | N_row | Tab_owner | Comments
...
SYS_TABLES | 11 |  | SYSTEM
SYS_COLUMNS | 14 |  | SYSTEM
...
Блюда | 6 | 33 | KIRILLOW | Перечень блюд, известных шеф-повару
Поставки | 4 | 37 | GROMOW | Данные о поставляемых продуктах
Вид_блюд | 2 | 5 | KIRILLOW | Перечень видов блюд
Трапезы | 2 | 3 | GROMOW | Перечень трапез в пансионате
Состав | 3 | 148 | KIRILLOW | Состав блюд
Продукты | 11 | 17 | KIRILLOW | Таблица продуктов
...

б)

Col_name | Type | Length | Comments
БЛ | INTEGER | 4 | Код блюда
Блюдо | TEXT | 16 | Название блюда
В | TEXT | 1 | Код вида блюда (З, С, ...)
Основа | TEXT | 6 | Основной продукт в блюде
Выход | REAL | 4 | Масса порции готового блюда
Труд | INTEGER | 4 | Стоимость приготовления блюда (коп)

Рис. 5.1. Результаты запросов по системным таблицам

Пользователь, не знакомый со структурой базы данных, может с помощью подобного рода запросов получить информацию о такой структуре. Для этого ему надо владеть языком SQL и немного подумать.

В заключение следует отметить, что СУБД не позволяет обновлять каталог с помощью предложений DELETE, INSERT и UPDATE. Обновление проводится только при создании, модификации или уничтожении таблиц, индексов, правил и т.п. с помощью предложений, рассматриваемых ниже.


Создание и уничтожение базовых таблиц

5.2. Создание и уничтожение базовых таблиц

Базовые таблицы описываются в SQL с помощью предложения CREATE TABLE (создать таблицу), синтаксис которого имеет небольшие различия в различных СУБД. Однако все они поддерживают следующую минимальную форму:

CREATE TABLE базовая_таблица (столбец тип_данных
                                   [NOT NULL]
                [,столбец тип_данных [NOT NULL]] ...);

где тип_данных должен принадлежать к одному из типов данных, поддерживаемых СУБД (например, одному из типов данных, перечисленных в п.1.2).

Так, описание таблицы Блюда может быть записано в виде

CREATE TABLE Блюда
    (	БЛ	SMALLINT NOT NULL,
        Блюда	CHAR (70) NOT NULL,
        В	CHAR  (1),
        Основа	CHAR (10),
        Выход	FLOAT,
        Труд	SMALLINT );

В результате создается пустая базовая таблица Блюда, а в системный каталог помещается строка, описывающая эту таблицу. Отметим, что в профессиональных СУБД имя таблицы дополняется именем пользователя, который издал предложение CREATE TABLE. Если этот пользователь зарегистрирован в системе под именем Kirillov, то в каталоге будет зарегистрирована таблица Kirillov.Блюда и указанный пользователь может обращаться к ней по имени Kirillov.Блюда или по сокращенному имени Блюда, которое использовалось во всех предшествующих примерах и будет использоваться далее.

Конструкция NOT NULL запрещает использование неопределенного значения, т.е. специального значения, которое вводится для представления "неизвестного значения" или "неприменимого значения". Например, строка поставки таблицы Поставки может содержать неопределенное значение в столбце Цена и (или) К_во (извесно, что поставщик поставляет указанный продукт, но на данный момент неизвестна цена этого продукта и (или) объем поставки).

Существующую базовую таблицу можно в любой момент уничтожить с помощью предложения DROP TABLE (уничтожить таблицу):

DROP TABLE базовая_таблица;

по которому удаляется описание таблицы, ее данные, связанные с ней представления и индексы, построенные для столбцов таблицы (см. п. 5.3).

В SQL существует также предложение ALTER TABLE (изменить таблицу), которое позволяет добавить справа к таблице новый столбец, т.е. модифицировать описание табицы. Так как без него "можно жить", а объем книги ограничен, то мы не будем здесь описывать это предложение.


О индексах и производительности

5.3. О индексах и производительности

Для ускорения поиска данных можно создавать индексы. Индекс - это системная таблица, построенная по значениям заданного столбца заданной таблицы. В нем размещается перечень уникальных значений указанного столбца таблицы со ссылками на те ее строки, где встречаются эти значения (структура, похожая на предметный указатель книги). Например, индекс, построенный для столбца Основа таблицы Блюда, будет содержать следующие сведения:

Значения столбца | Строки, в которых встречается такое значение
Кофе | 32 | 33
Крупа | 20 | 21
Молоко | 7 | 8 | 12 | 18 | 22 | 24 | 28 | 31
Мясо | 2 | 6 | 9 | 13 | 14
Овощи | 1 | 3 | 17 | 23 | 15
Рыба | 4 | 5 | 10 | 11
Фрукты | 25 | 26 | 27 | 29 | 30
Яйца | 16 | 19

Отметим, что такой индекс уже существовал (в несколько иной форме) в базе данных, хотя это обстоятельство никак не повлияло на текст иллюстрационных предложений SELECT, DELETE, INSERT и UPDATE. SQL намеренно не включает в свои конструкции ссылки на индексы. Решение о том, использовать или не использовать какой-либо индекс при обработке некоторого конкретного запроса принимается не пользователем, а оптимизатором СУБД, который учитывает множество факторов - размер таблиц, тип используемых структур хранения данных, статистическое распределение данных в таблицах и индексах и т.д. Однако чтобы оптимизатор смог использовать индексы, их нужно построить (чтобы выиграть в лотерею нужно, по крайней мере, иметь лотерейный билет).

Естественно, что поиск какого-либо значения путем последовательного перебора неупорядоченных данных будет во много раз медленнее, чем поиск с использованием упорядоченного списка (индекса). Ясно также, что таблицу можно упорядочить лишь по данным одного столбца, тогда как поиск часто приходится осуществлять по данным нескольких столбцов. По нескольким столб-цам производится и соединение таблиц. Поэтому, несмотря на то, что индексы увеличивают объем базы данных, их следует использовать как для отдельных столбцов таблицы, так и для комбинации нескольких ее столбцов (например, для комбинации: Фамилия, Имя, Отчество).

Для построения индекса в SQL существует предложение CREATE INDEX (создать индекс), имеющее формат

CREATE [UNIQUE] INDEX имя_индекса ON базовая_таблица
(столбец [[ASC] | DESC]
                  [, столбец [[ASC] | DESC]] ...);

где UNIQUE (уникальный) указывает, что никаким двум строкам в индексируемой базовой таблице не позволяется принимать одно и то же значение для индексируемого столбца (или комбинации столбцов) в одно и то же время.

Например, индексы для столбцов БЛ и Основа таблицы Блюда создаются с помощью предложений

CREATE UNIQUE INDEX Блюда_БЛ ON Блюда (БЛ);
CREATE INDEX Блюда_Основа ON Блюда (Основа);

а индекс для первичного ключа (столбцы БЛ и ПР) таблицы Состав - с помощью предложения

CREATE UNIQUE INDEX Состав_БЛ_ПР ON Состав (БЛ, ПР);

В больших (более 1000 строк) таблицах поиск индексированных значений выполняется на порядок быстрее, чем поиск неиндексированных, а в очень больших таблицах - на два-три порядка.

Так может быть, если позволяет память, следует построить индексы для всех столбцов всех таблиц базы данных?

Если база данных не должна модифицироваться, то на этот вопрос можно дать положительный ответ. Однако при удалении или добавлении строки таблицы должны быть перестроены все индексы, построенные для ее столбцов, а при изменении значения индексированного столбца - индекс этого столбца. Когда модифицируется много - несколько сотен (тысяч) строк - и после модификации каждой строки перестраиваются все ее индексы, время модификации может быть на порядок (несколько порядков) больше времени модификации строк с неиндексированными столб-цами. Поэтому перед модификацией множества строк таблицы целесообразно уничтожить индексы ее столбцов, что можно сделать с помощью предложения DROP INDEX (уничтожить индекс), имеющего следующий формат:

DROP INDEX имя_индекса;

Так как индексы могут создаваться или уничтожаться в любое время, то перед выполнением запросов целесообразно строить индексы лишь для тех столбцов, которые используются в WHERE и ORDER BY фразах запроса, а перед модификацией большого числа строк таблиц с индексированными столбцами эти индексы следует уничтожить.


Представления. Создание и уничтожение представлений

5.4. Представления
5.4.1. Создание и уничтожение представлений

В п.1.3 уже обсуждалось это понятие, а также приводились примеры его создания и использования. Напомним, что представление - это виртуальная таблица, которая сама по себе не существует, но для пользователя выглядит таким образом, как будто она существует. Представление не поддерживаются его собственными физическими хранимыми данными. Вместо этого в каталоге таблиц хранится определение, оговаривающее, из каких столбцов и строк других таблиц оно должно быть сформировано при реализации SQL-предложения на получение данных из представления или на модификацию таких данных.

Синтаксис предложения CREATE VIEW имеет вид

CREATE VIEW имя_представления
    [(столбец[,столбец] ...)]
    AS подзапрос
    [WITH CHECK OPTION];

где подзапрос, следующий за AS и являющийся определением данного представления, не исполняется, а просто сохраняется в каталоге;

необязательная фраза "WITH CHECK OPTION" (с проверкой) указывает, что для операций INSERT и UPDATE над этим пред-ставлением должна осуществляться проверка, обеспечивающая удовлетворение WHERE фразы подзапроса;

список имен столбцов должен быть обязательно определен лишь в тех случаях, когда:

а) хотя бы один из столбцов подзапроса не имеет имени (создается с помощью выражения, SQL-функции или константы);

б) два или более столбцов подзапроса имеют одно и то же имя;

если же список отсутствует, то представление наследует имена столбцов из подзапроса.

Например, создадим представление Мясные_блюда

CREATE VIEW Мясные_блюда
AS SELECT БЛ, Блюдо, В, Выход
FROM	Блюда
WHERE  Основа = 'Мясо';

которое может рассматриваться пользователем как новая таблица в базе данных.

Уничтожение ненужных представлений выполняется с помощью предложения DROP VIEW (уничтожить представление), имеющего следующий формат:

DROP VIEW представление;


Операции выборки из представлений

5.4.2. Операции выборки из представлений

Создав представление Мясные_блюда пользователь может считать, что в базе данных реально существует такая таблица и дать, например, запрос на получение из нее всех данных:

SELECT *
FROM	Мясные_блюда;

результат которого имеет вид

БЛ | Блюдо | В | Выход
2 | Салат мясной | З | 200
6 | Мясо с гарниром | З | 250
9 | Суп харчо | С | 500
13 | Бастурма | Г | 300
14 | Бефстроганов | Г | 210

Поскольку при определении представления может быть использован любой допустимый подзапрос, то выборка данных может осуществляться как из базовых таблиц, так и из представлений:

CREATE VIEW Горячие_мясные_блюда
    AS SELECT	Блюдо, Продукт, Вес
        FROM	Мясные_блюда, Состав, Продукты
        WHERE	Мясные_блюда.БЛ = Состав.БЛ
        AND	Продукты.ПР = Состав.ПР
        AND	В = 'Г';

Если теперь возникла необходимость получить сведения о горячих мясных блюдах, в состав которых входят помидоры, то можно сформировать запрос

SELECT	Блюдо, Продукт, Вес
FROM	Горячие_мясные_блюда
WHERE	Блюдо IN
    (	SELECT Блюдо
        FROM	Горячие_мясные_блюда
        WHERE  Продукт = 'Помидоры')

и получить:

Блюдо | Продукт | Вес
Бастурма | Говядина | 180
Бастурма | Помидоры | 100
Бастурма | Лук | 40
Бастурма | Зелень | 20
Бастурма | Масло | 5

Легко заметить, что данный запрос, осуществляющий выбор данных через два представления, выглядит для пользователя точно так же, как обычный SELECT, оперирующий обычной базовой таблицей. Однако СУБД преобразует его при выполнении в эквивалентную операцию над лежащими в основе базовыми таблицами (перед выполнением проводит слияние выданного пользователем SELECT с предложениями SELECT из описаний представлений, хранящихся в каталоге).


Обновление представлений

5.4.3. Обновление представлений

Рассмотренные в гл.4 операции DELETE, INSERT и UPDATE могут оперировать не только базовыми таблицами, но и представлениями. Однако, если из базовых таблиц можно удалять любые строки, обновлять значения любых их столбцов и вводить в такие таблицы новые строки, то этого нельзя сказать о представлениях, не все из которых являются обновляемыми.

Безусловно обновляемыми являются представления, полученные из единственной базовой таблицы простым исключением некоторых ее строк и (или) столбцов, обычно называемые "представление-подмножество строк и столбцов". Таким является представление Мясные_блюда, полученное из базовой таблицы Блюда исключением из нее столбца Труд и строк, не содержащих значение 'Мясо' в столбце Основа. Работая с ним, можно:

  1. вставить (операция INSERT) новую строку, например, строку (34, 'Шашлык', 'Г', 150), фактически вставляя соответствующую строку (34, 'Шашлык', 'Г', 150, NULL) в лежащую в основе базовую таблицу Блюда;

  2. удалить (операция DELETE) существующую строку из представления, например строку (13, 'Бастурма', 'Г', 300), фактически удаляя соответствующую строку (13, 'Бастурма', 'Г', 300, 5) из таблицы Блюда;

  3. обновить (операция UPDATE) какое-либо поле в существующей строке, например увеличить массу порции Бефстроганова с 210 до 250 граммов, фактически осуществляя то же самое изменение в соответствующем поле таблицы Блюда.

Однако если бы представление Мясные_блюда имело вместо столбца Выход столбец Вых_труд, полученный путем суммирования значений столбцов Выход и Труд таблицы Блюда, то указанные выше операции INSERT и UPDATE были бы отвергнуты системой. Действительно, как распределить вводимое значение столбца Вых_труд (153 или 254) между значениями столбцов Выход и Труд базовой таблицы Блюда? Была бы отвергнута и операция удаления масла из состава бастурмы, если бы ее попытались выполнить путем удаления строки ('Бастурма', 'Масло', 5) в представлении Горячие_мясные_блюда. Встает множество вопросов: надо ли удалять из базовой таблицы Блюда строку, содержащую значение 'Бастурма' в столбце Блюдо?; надо ли удалять из базовой таблицы Продукты строку, содержащую значение 'Масло' в столбце Продукт?; надо ли удалять из базовой таблицы Состав все строки, содержащие значение 5 в столбце Вес?. Последний вопрос возник потому, что при конструировании представления Горячие_мяс-ные_блюда в него не была включена информация о связях между лежащими в его основе базовыми таблицами Блюда, Состав и Продукты, и у системы нет прямых путей для поиска той единственной строки таблицы Состав, которая должна быть удалена.

Таким образом, некоторые представления по своей природе обновляемы, в то время как другие таковыми не являются. Здесь следует обратить внимание на слова "по своей природе". Дело заключается не просто в том, что некоторая СУБД не способна поддерживать определенные обновления, в то время как другие СУБД могут это делать. Никакая СУБД не может непротиворечивым образом поддерживать без дополнительной помощи обновление такого представления как Горячие_мясные_блюда. "Без дополнительной помощи" означает здесь "без помощи какого-либо человека - пользователя".

Как было указано выше, к теоретически обновляемым представ-лениям относятся представления-подмножества строк и столбцов. Однако существуют некоторые представления, которые не являются представлениями-подмножествами строк и столбцов, но также теоретически обновляемы. Хотя известно, что такие есть и можно привести их примеры, но невозможно дать их формального определения. Неверным является такое формальное определение некоторых авторов - "нельзя обновлять соединение". Во-первых, в некоторых соединениях с успехом выполняется операция UPDATE, а, во-вторых, как было показано выше, не обновляемы и некоторые представления, не являющиеся соединениями. Кроме того, не все СУБД поддерживают обновление любых теоретически обновляемых представлений. Поэтому пользователь должен сам оценивать возможность использования операций DELETE, INSERT или UPDATE в созданном им представлении.


Для чего нужны представления

5.4.4. Для чего нужны представления

Одна из основных задач, которую позволяют решать представления, - обеспечение независимости пользовательских программ от изменения логической структуры базы данных при ее расширении и (или) изменении размещения столбцов, возникающего, например, при расщеплении таблиц. В последнем случае можно создать представление-соединение с именем и структурой расщепленной таблицы, позволяющее сохранить программы, существовавшие до изменения структуры базы данных.

Кроме того, представления дают возможность раличным пользователям по-разному видеть одни и те же данные, возможно, даже в одно и то же время. Это особенно ценно при работе различных категорий пользователей с единой интегрированной базой данных. Пользователям предоставляют только интересующие их данные в наиболее удобной для них форме (окно в таблицу или в любое соединение любых таблиц).

Наконец, от определенных пользователей могут быть скрыты некоторые данные, невидимые через предложенное им представление. Таким образом, принуждение пользователя осуществлять доступ к базе данных через представления является простым, но эффективным механизмом для управления санкционированием доступа.


О других предложениях и конструкциях SQL

Глава 6. О других предложениях и конструкциях SQL
6.1. Безопасность и санкционирование доступа

В контексте баз данных термин безопасность означает защиту данных от несанкционированного раскрытия, изменения или уничтожения. SQL позволяет индивидуально защищать как целые таблицы, так и отдельные их поля. Для этого имеются две более или менее независимые возможности:

механизм представлений, рассмотреный в предыдущей главе и используемый для скрытия засекреченных данных от пользователей, не обладающих правом доступа;

подсистема санкционирования доступа, позволяющая предоставить указанным пользователям определенные привилегии на доступ к данным и дать им возможность избирательно и динамически передавать часть выделенных привилегий другим пользователям, отменяя впоследствии эти привилегии, если потребуется.

Обычно при установке СУБД в нее вводится какой-то идентификатор, который должен далее рассматриваться как идентификатор наиболее привилегированного пользователя - системного администратора. Каждый, кто может войти в систему с этим идентификатором (и может выдержать тесты на достоверность), будет считаться системным администратором до выхода из системы. Системный администратор может создавать базы данных и имеет все привилегии на их использование. Эти привилегии или их часть могут предоставляться другим пользователям (пользователям с другими идентификаторами). В свою очередь, пользователи, получившие привилегии от системного администратора, могут передать их (или их часть) другим пользователям, которые могут их передать следующим и т.д.

Привилегии предоставляются с помощью предложения GRANT (предоставить), общий формат которого имеет вид

GRANT привилегии ON объект TO пользователи;

В нем "привилегии" - список, состоящий из одной или нескольких привилегий, разделенных запятыми, либо фраза ALL PRIVILEGES (все привилегии); "объект" - имя и, если надо, тип объекта (база данных, таблица, представление, индекс и т.п.); "пользователи" - список, включающий один или более идентификаторов санкционирования, разделенных запятыми, либо специальное ключевое слово PUBLIC (общедоступный).

К таблицам (представлениям) относятся привилегии SELECT, DELETE, INSERT и UPDATE [(столбцы)], позволяющие соответственно считывать (выполнять любые операции, в которых используется SELECT), удалять, добавлять или изменять строки указанной таблицы (изменение можно ограничить конкретными столбцами). Например, предложение

GRANT SELECT, UPDATE (Труд) ON Блюда TO cook;

позволяет пользователю, который представился системе идентификатором cook, использовать информацию из таблицы Блюда, но изменять в ней он может только значения столбца Труд.

Если пользователь USER_1 предоставил какие-либо привилегии другому пользователю USER_2, то он может впоследствии отменить все или некоторые из этих привилегий. Отмена осуществляется с помощью предложения REVOKE (отменить), общий формат которого очень похож на формат предложения GRANT:

REVOKE привилегии ON объект FROM пользователи;

Например, можно отобрать у пользователя cook право изменения значений столбца Труд:

REVOKE UPDATE (Труд) ON Блюда FROM cook;


Обработка транзакций

6.2. Обработка транзакций

В п.4.4.4 рассматривался пример, в котором требовалось изменить номер продукта ПР = 13 на ПР = 20 и для этого пришлось проводить последовательное изменение четырех таблиц:

UPDATE	Продукты	UPDATE	Состав
SET	ПР = 20	SET	ПР = 20
WHERE	ПР = 13;	WHERE	ПР = 13;

UPDATE	Поставки	UPDATE	Наличие
SET	ПР = 20	SET	ПР = 20
WHERE	ПР = 13;	WHERE	ПР = 13;

Этот пример приведен здесь для иллюстрации того, что единственная, с точки зрения пользователя, операция может потребовать нескольких операций над базой данных. Более того, между этими операциями может даже нарушаться непротиворечивость базы данных. Например, в ней могут временно содержаться некоторые записи поставок, для которых не имеется соответствующих записей поставляемых продуктов. Положение не спасает и перестановка последовательности обновляемых таблиц. Противоречивость исчезнет только после выполнения всех обновлений, т.е. выполнения логической единицы работы - полной замены номера продукта в базе данных (независимо от количества таблиц, в которых встречается номер продукта).

Теперь можно дать определение транзакции. Транзакция или логическая единица работы, - это в общем случае последовательность ряда таких операций, которые преобразуют некоторое непротиворечивое состояние базы данных в другое непротиворечивое состояние, но не гарантируют сохранения непротиворечивости во все промежуточные моменты времени.

Никто кроме пользователя, генерирующего ту или иную последовательность SQL-предложений, не может знать о том, когда может возникнуть противоречивое состояние базы данных и после выполнения каких SQL-предложений оно исчезнет, т.е. база данных вновь станет актуальной. Поэтому в большинстве СУБД создается механизм обработки транзакций, при инициировании которого все изменения данных будут рассматриваются как предварительные до тех пор, пока пользователь (реже система) не выдаст предложения:

COMMIT (фиксировать), превращающее все предварительные обновления в окончательные ("зафиксированные");

ROLLBACK (откат), аннулирующее все предварительные обновления.

Таким образом, транзакцией можно назвать последовательность SQL-предложений, расположенных между "точками синхронизации", учреждаемых в начале выполнения программы и издании COMMIT или ROLLBACK и только в этих случаях. При этом следует иметь в виду, что возможен неявный COMMIT (существует режим AUTOCOMMIT, в котором система издает COMMIT после выполнения каждого SQL-предложения) и ROLLBACK (выполняемый при аварийном завершении программы).

Ясно теперь, что пользователь должен сам решать, включать ли механизм обработки транзакций и если включать, то где издавать COMMIT (ROLLEBACK), т.е. какие последовательности SQL-предложений являются транзакциями.

Теперь о проблемах, связанных с параллельным использованием базы данных множеством разнообразных пользователей.

Большинство СУБД позволяют любому числу транзакций одновременно осуществлять доступ к одной и той же базе данных и в них существуют те или иные механизмы управления параллельными процессами, предотвращающие нежелательные воздействия одних транзакций на другие. По сути это механизм блокирования, главная идея которого достаточно проста. Если транзакции нужны гарантии, что некоторый объект (база данных, таблица, строка или поле), в котором она заинтересована, не будет изменен каким-либо непредсказуемым образом в течение требуемого промежутка времени, она устанавливает блокировку этого объекта. Результат блокировки заключается в том, чтобы изолировать этот объект от других транзакций и, в частности, предотвратить его изменение средствами этих транзакций. Для первой транзакции, таким образом, имеется возможность выполнять предусмотренную в ней обработку, располагая определенными знаниями о том, что объект в запросе будет оставаться в стабильном состоянии до тех пор, пока данная транзакция этого пожелает.

Ограниченный объем книги заставляет нас завершить на самом интересном месте обсуждение чрезвычайно важного вопроса об управлении транзакциями и параллельном их исполнении. С этим материалом мы познакомим вас в следующей книге, посвященной прикладному программированию в среде СУБД. А о средствах SQL, используемых в прикладных программах, будет кратко рассказано далее.


Прикладное программирование

6.3. Прикладное программирование

В предыдущих главах, рассматривая предложения SQL, мы практически не оговаривали, откуда попадают в СУБД такие запросы. Молчаливо предполагалось, что язык используется в интерактивном режиме и запросы вводятся с клавиатуры. Однако все предложения SQL, которые можно ввести с терминала, можно использовать также в прикладной программе.

Многие современные СУБД имеют собственные языки программирования, ряд которых включает в себя SQL. Другие работают с программами, написанными на одном из распространенных алгоритмических языков (Си, Паскаль или Фортран), в которые включаются предложения SQL. Для обмена информацией с частями программы, написанными на любых из этих языков, существуют специальные конструкции SQL, позволяющие работать с переменными и (или) отдельными строками таблиц.

Переменные включающего языка:

  • могут появляться в предложениях манипулирования данными языка SQL только во фразах SELECT и INTO предложения SELECT, фразе WHERE предложений SELECT, UPDATE и DELETE, фразе SET предложения UPDATE, фразе VALUES предложения INSERT и, наконец, в качестве элемента выражения во фразах SELECT, WHERE или SET, но не VALUES;
  • должны иметь типы данных, совместимые с типами данных тех столбцов базы данных, с которыми они должны сравниваться, значения которых им должны быть присвоены или которым должны быть присвоены значения переменных;
  • могут иметь имена, совпадающие с именами столбцов базы данных (система различает их по месторасположению в предложениях SQL или по специальному символу, например, ".", устанавливаемому перед именем переменной, когда надо использовать ее значение).

После выполнения любого предложения SQL происходит обновление системной переменной SQLCODE (в нее заносится числовой индикатор состояния). Нулевое значение SQLCODE означает, что данное предложение выполнено успешно. Положительное значение означает, что предложение выполнено, но имела место некоторая исключительная ситуация. Например, значение +100 указывает, что не было найдено никаких данных, удовлетворяющих запросу. Наконец, отрицательное значение указывает, что имела место ошибка и предложение не выполнено. Поэтому за каждым предложением SQL в программе должна следовать проверка значения SQLCODE и должно предприниматься соответствующее действие, если это значение оказалось не таким, которое ожидалось. На практике же такую проверку осуществляют после тех предложений SQL, при выполнении которых возможна исключительная ситуация).

Основная проблема "встраивания" предложения SELECT в программу заключается в том, что SELECT, как правило, порождает таблицу с множеством строк и столбцов, а включающий язык не обладает хорошими средствами, позволяющими оперировать одновременно более чем одной записью (строкой). По этим причинам необходимо обеспечить своего рода мост между уровнем множеств языка SQL и уровнем записей включающего языка. Такой мост обеспечивают курсоры. Курсор состоит, по существу, из некоторого рода указателя, который может использоваться для просмотра множества записей. Поочередно указывая каждую запись в данном множестве, он обеспечивает возможность обращения к этим записям по одной одновременно.

Однако нередко программе требуются в каждый момент времени значения только из одной строки какой-либо таблицы, и для этого используется единичное SELECT, формат которого имеет вид

SELECT [[ALL] | DISTINCT]{ * | элемент_select
                                 [,элемент_select] ...}
INTO	переменная [[INDICATOR] индикаторная_переменная]
[,переменная [[INDICATOR] индикаторная_переменная]] ...
FROM	базовая_таблица | представление [псевдоним]
    [,базовая_таблица | представление [псевдоним]] ...
[WHERE	фраза]
[GROUP	BY фраза [HAVING фраза]];

где элемент_select - это одна из следующих конструкций:

[таблица.]* | [таблица.]столбец | SQL_функция |
 переменная | (выражение) | системная_переменная

Очевидно, что это описание отличается от описания подзапроса (п.2.1) наличием фразы INTO и включением в список элементов_select переменных. Переменные могут также включаться в выражения и фразы WHERE и HAVING.

Приведем несколько примеров. Получить общий вес продуктов в кладовой пансионата и занести его в переменную Общий_вес:

SELECT	SUM(К_во)
INTO	Общий_вес
FROM	Наличие;

Здесь определяется единственное значение (сумма данных в столб-це) и поэтому системная переменная SQLCODE устанавливается в нуль.

Однако в следующем примере

SELECT	Продукт, К_во
INTO	Продукт, К_продукта
FROM	Наличие, Продукты
WHERE	Наличие.ПР = Продукты.ПР
AND	ПР IN
    (	SELECT ПР
        FROM	Продукты
        WHERE  Продукт = 'Икра черная');

где требовалось узнать количество черной икры в кладовой пансионата и занести название этого продукта и его количество в переменные Продукт и К_продукта, соответственно, переменная SQLCODE примет значение +100, так как в кладовой нет икры.

Наконец, в примере

SELECT	Продукт, К_во
INTO	Продукт, К_продукта
FROM	Наличие, Продукты
WHERE	Наличие.ПР = Продукты.ПР;

где не указан конкретный продукт и, следовательно, SELECT спродуцирует вывод всей таблицы продуктов, значение SQLCODE будет отрицательным. При этом значения переменных Продукт и К_продукта останутся неизменными, т.е. такими, какими они были после последнего правильного выполнения команды.

В единичном SELECT можно ввести за каждой целевой переменной слово INDICATOR и имя индикаторной переменной. Значения индикаторных переменных не равны нулю только при нулевом значении SQLCODE и NULL-значениях элементов SELECT для соответствующих целевых переменных. Например, если в столбцах К_во и Стоимость продукта 9 хранится значение NULL, то после выполнения запроса

SELECT	ПР, К_во, Стоимость
INTO	ПР INDICATOR Инд1, К_прод INDICATOR Инд2,
    Стоим INDICATOR Инд3
FROM	Наличие
WHERE	ПР = 9;

будут получены следующие значения переменных: SQLCODE = 0, ПР = 9, Инд1 = 0, Инд2 = Инд3 = -1, а К_прод и Стоим имеют значение NULL.

Переменные можно использовать и в предложениях модификации данных. Приведем несколько примеров.

Изменить цены продуктов ленинградских поставщиков на величину, заданную переменной Измен:

UPDATE	Поставки
SET	Цена = Цена + .Измен
WHERE	ПС IN
    (SELECT	ПC
        FROM	Поставщики
        WHERE	Город = 'Ленинград');

Удалить все блюда, основа которых указана в переменной Осн:

DELETE
FROM	Блюда
WHERE	Основа = .Осн;

Добавить в таблицу Поставщики нового поставщика, атрибуты которого заданы соответствующими переменными ПС, Имя, Статус, Город, а Адрес и Телефон неизвестны:

INSERT
INTO 	Поставщики
    (ПС, Название, Статус, Город)
VALUES	(.ПС, .Имя, .Статус, .Город);

В тех же приложениях, где надо отыскивать и обрабатывать множество подходящих записей из одной таблицы или совокупности таблиц базы данных следует использовать курсоры, позволяющие организовать последовательный доступ к строкам какой-либо таблицы (соединению таблиц, представлению и т.п.).

Предложение

DECLARE имя_курсора CURSOR
FOR подзапрос

определяет имя курсора и связанный с ним подзапрос. С его помощью идентифицируется некоторое множество столбцов и строк указаной таблицы (совокупности таблиц), которое становится активным множеством для данного курсора. (Точнее говоря, определяется множество частей строк, в которые входят только значения из указанных столбцов.) Курсор идентифицирует также позицию в этом множестве (сначала это позиция его первой записи). Активные множества всегда рассматриваются как упорядоченные. При этом упорядочение определяется фразой ORDER BY, а при ее отсутствии – системой (в порядке загрузки строк в таблицу).

Описанные с помощью DECLARE CURSOR множества используются рядом предложений SQL для удаления отмеченных строк (DELETE), их модификации (UPDATE) или присвоения значений перечисленных в SELECT столбцов переменным, список которых указывается в предложении FETCH (вызвать). Однако перед выполнением этих команд необходимо активизировать курсор, который в этот момент не должен быть открыт. Для этого используется предложение OPEN (OPEN имя_курсора).

Предложение FETCH используется для выборки той записи активного множества, на которую указывает курсор, для присвоения значений столбцов этой записи переменным, перечисленным во фразе INTO, и для перемещения курсора на следующую строку активного множества. При перемещении за последнюю строку переменная SQLCODE примет значение +100.

FETCH	имя_курсора
INTO	переменная [[INDICATOR] индикаторная_переменная]
{,переменная [[INDICATOR] индикаторная_переменная]} ...

Команду FETCH обычно помещают в некоторый цикл, размещая непосредственно за ней команду анализа SQLCODE. Это позволяет обнаружить переход от значения 0 на +100 и организовать выход из цикла.

Наконец, следует упомянуть еще два предложения, связанные с курсорами. Это предложение для дезактивации курсора (CLOSE имя_курсора) и предложение для уничтожения курсора (DROP CURSOR имя_курсора).

Мы уже отмечали, что ограниченный объем книги не позволяет подробнее обсудить и должным образом проиллюстрировать использование курсоров. Не затронуты также предложения COMMENT ON (ввести описание таблицы или столбца), CONNECT (открыть базу данных), DISCONNECT (закрыть базу данных), WHENEVER (организовать обработку индикатора ошибки SQL) и несколько предложений, связанных с управлением транзакциями и параллельным их исполнением.


Основы проектирования реляционных баз данных

Структуризированный язык запросов (SQL)
Учебное пособие

Предисловие
Глава 1. Реляционные базы данных и язык SQL
1.1. Реляционная база данных
1.2. Почему SQL?
1.3. Таблицы SQL
Глава 2. Запросы с использованием единственной таблицы
2.1. О предложении SELECT
2.2. Выборка без использования фразы WHERE
2.2.1. Простая выборка
2.2.2. Исключение дубликатов
2.2.3. Выборка вычисляемых значений
2.3. Выборка c использованием фразы WHERE
2.3.1. Использование операторов сравнения
2.3.2. Использование BETWEEN
2.3.3. Использование IN
2.3.4. Использование LIKE
2.3.5. Вовлечение неопределенного значения
2.4. Выборка с упорядочением
2.5. Агрегирование данных
2.5.1. SQL-функции
2.5.2. Функции без использования фразы GROUP BY
2.5.3. Фраза GROUP BY
2.5.4. Использование фразы HAVING
Глава 3. Запросы с использованием нескольких таблиц
3.1. О средствах одновременной работы с множеством таблиц
3.2. Запросы, использующие соединения
3.2.1. Декартово произведение таблиц
3.2.2. Эквисоединение таблиц
3.2.3. Естественное соединение таблиц
3.2.4. Композиция таблиц
3.2.5. Тета-соединение таблиц
3.2.6. Соединение таблиц с дополнительным условием
3.2.7. Соединение таблицы со своей копией
3.3. Вложенные подзапросы
3.3.1. Виды вложенных подзапросов
3.3.2. Простые вложенные подзапросы
3.3.3. Использование одной и той же таблицы во внешнем и вложенном подзапросе
3.3.4. Вложенный подзапрос с оператором сравнения, отличным от IN
3.3.5. Коррелированные вложенные подзапросы
3.3.6. Запросы, использующие EXISTS
3.3.7. Функции в подзапросе
3.4. Объединение (UNION)
3.5. Реализация операций реляционной алгебры предложением SELECT
3.6. Резюме
Глава 4. Предложения модификации данных SQL
4.1. Особенности и синтаксис предложений модификации
4.2. Предложение DELETE
4.2.1. Удаление единственной записи
4.2.2. Удаление множества записей
4.2.3. Удаление с вложенным подзапросом
4.3. Предложение INSERT
4.3.1. Вставка единственной записи в таблицу
4.3.2. Вставка множества записей
4.3.3. Использование INSERT...SELECT для построения внешнего соединения
4.4. Предложение UPDATE
4.4.1. Обновление единственной записи
4.4.2. Обновление множества записей
4.4.3. Обновление с подзапросом
4.4.4. Обновление нескольких таблиц
4.5. О конструировании предложений модификации
Глава 5. О предложениях определения данных и оптимизации запросов
5.1. Системный каталог
5.2. Создание и уничтожение базовых таблиц
5.3. О индексах и производительности
5.4. Представления
5.4.1. Создание и уничтожение представлений
5.4.2. Операции выборки из представлений
5.4.3. Обновление представлений
5.4.4. Для чего нужны представления
Глава 6. О других предложениях и конструкциях SQL
6.1. Безопасность и санкционирование доступа
6.2. Обработка транзакций
6.3. Прикладное программирование
Литература


Предисловие

Основные идеи современной информационной технологии базируются на концепции, согласно которой данные должны быть организованы в базы данных с целью адекватного отображения изменяющегося реального мира и удовлетворения информационных потребностей пользователей. Эти базы данных создаются и функционируют под управлением специальных программных комплексов, называемых системами управления базами данных (СУБД).

Увеличение объема и структурной сложности хранимых данных, расширение круга пользователей информационных систем привели к широкому распространению наиболее удобных и сравнительно простых для понимания реляционных (табличных) СУБД. Для обеспечения одновременного доступа к данным множества пользователей, нередко расположенных достаточно далеко друг от друга и от места хранения баз данных, созданы сетевые мультипользовательские версии СУБД. В них тем или иным путем решаются специфические проблемы параллельных процессов, целостности (правильности) и безопасности данных, а также санкционирования доступа.

Ясно, что совместная работа пользователей в сетях с помощью унифицированных средств общения с базами данных возможна только при наличии стандартного языка манипулирования данными, обладающего средствами для реализации перечисленных выше возможностей. Таким языком стал SQL, разработанный в 1974 году фирмой IBM для экспериментальной реляционной СУБД System R. После появления на рынке двух пионерских СУБД этой фирмы - SQL/DS (1981 год) и DB2 (1983 год) - он приобрел статус стандарта де-факто для профессиональных реляционных СУБД. В 1987 году SQL стал международным стандартом языка баз данных, а в 1992 году вышла вторая версия этого стандарта.

В книгу включены наиболее важные предложения базового варианта SQL, позволяющие познакомиться с основными средствами манипулирования данными. Недостаток места не позволил подробно рассмотреть другие его конструкции. Однако и таких знаний достаточно для получения данных из баз, находящихся под управлением большинства современных СУБД (если, конечно, вам предоставят привилегии доступа к ним).

Эта книга писалась одновременно с книгой [2], поэтому и читать их целесообразно вместе, так как знакомство с одной из них облегчит понимание другой.


Литература
  1. Дейт К. Руководство по реляционной СУБД DB2. - М.: Финансы и статистика, 1988. - 320 с.
  2. Кириллов В.В. Основы проектирования реляционных баз данных. Учебное пособие. - СПб.: ИТМО, 1994. - 90 с.
  3. Мейер М. Теория реляционных баз данных. -М.: Мир, 1987. - 608 с.
  4. Ульман Дж. Базы данных на Паскале. -М.: Машиностроение, 1990. - 386 с.


Проверка переключателя

В этой программе мы научимся проверять включен ли переключатель, на практике.Теоретические сведения можно получить в разделе странички контрольные элементы.

Первым делом нужно понять, что же должна делать программа.В данном случае программа должна проверять - включен ли переключатель.То есть при нажатии на кнопу проверка, она должна говорить(выводить на экран) сообщение о состоянии переключателя.

Создаем новый проект.Ставим на форму объекты : кнопку со свойством Caption- проверить, переключатель со свойством Caption - Переключить, и кнопку с Caption'ом - выход.

Перейдите к коду кнопки - проверить.Впишите туда :

If Check1.Value = 1 then

Msgbox "Переключатель включен"

Else

Msgbox "Переключатель выключен"

End if

В коде кнопки - Выход введите: End

В результате у вас должно поучится вот это:

Private Sub Command1_Click()
If Check1.Value = 1 Then
MsgBox "Переключатель включен"
Else
MsgBox "Переключатель выключен"
End If
End Sub

Private Sub Command2_Click()
End
End Sub

Рассмотрим как работает программа:

If Check1.Value = 1 Then - программа проверяет - если свойство Value элемента Check1 равно 1, то есть флажок установлен то...

MsgBox "Переключатель включен" - вывести сообщение, говорящее о том что переключатель включен

Else - иначе ( то есть если свойство Value элемента Check1 не равно 1) то...

MsgBox "Переключатель выключен"-вывести сообщение, говорящее о том что переключатель выключен.

End If - закончить проверку.

Попробуйте запустить программу.Поставьте галочку на переключатель и на кнопку - проверка, потом уберите галочку и опять нажмите на кнопку проверка. Если все работает, то я вас поздравляю.Если же нет, то попробуйте взять программу и разобраться.

Переключатель вам скорее всего пригодится.Эта программа просто показывает как с ним работать, но потом можете делать многие вещс помощью переключателя. Например, в некоторых программах спрашивается, выводить ли это далее.


Числа

Кратные числа

Эта программа не научит вас пользоваться каким-либо элементом управления, но она покажет на примере как пользоваться типами данных, как пользоваться циклами. Вообще в этой программе лучше все-таки разобраться, ведь она показывает на наглядном примере алгоритм вычисления кратных чисел, а это очень важно для программиста.

Шаг №1 - Постановка задачи

По ходу использования этой программы пользователь будет вводить в текстовое поле число, а программа в специальном окне будет выводить, на какие числа это число делится без остатка и что при этом получится. Например, если пользователь введет в поле 10, то программа в поле результат должна написать :

10 : 1 = 10
10 : 2 = 5
10 : 5 = 2
10 : 10 = 1

Вроде бы тут все понятно.

Шаг №2 - Визуальное проектирование

Итак на форме у вас должны быть следующие элементы :

1) Кнопка ( Button ) : Name - Command1, Caption - Поситать
2) Кнопка ( Button ) : Name - Command2, Caption - Выход
3) Текстовое поле ( TextBox ) : Name - Text1, поле свойства Text должно быть пустым
4) Текстовое поле ( TextBox ) : Name - Text2, поле свойства Text также должно быть пустым
5) Этикетка ( Label ) : Caption - Делимое
6) Этикетка ( Label ) : Caption - Результат

В результате всего этого у вас должно получиться примерно так :

Все, с визуальным проектированием покончено.

Шаг №3 - Программирование в коде

В этой программе, как и во многих других этот шаг самый важный. Для начала пишем в разделе глобальных объявлений :

Option Explicit - как обычно объявляем переменные

Dim delimoe As Long - Объявляем переменную delimoe - которая будет обозначать в программе то число котрое мы делим. Long - потому, что вдруг кто - то ( а такие люди обязательно найдутся ) захочет найти числа на которые делится 1000000

Dim delitel As Long - Объявляем переменную delitel - которая будет обозначать в программе то чило на которое мы делим.

Dim resultat As Double - Объявляем переменную resultat которая будет обозначать результат деления, то есть делимое разделить на делитель

Далее вам нужно уяснить, как же будет проверять программа : делится ли число без остатка. То есть является ли переменная resultat целым числом. Войдите в код кнопки Посчитать и пишите там :

Text2.Text="" - Опустошаем свойство Text элемента Text2

If Not IsNumeric (Text1.Text) Then - Если свойство Text элемента Text1 не число, то..ю

Msgbox "Это не число", vbCritical - Сообщить об этом

Exit Sub - Выйти из кода кнопки Посчитать

End If - Закончить проверку свойства Text элемента Text1

delimoe = Text1.Text - Присваиваем переменной delimoe значение свойства Text элемента Text1

Form1.Caption = "Считаю"- Изменяем заголовок программы на "Считаю", это нужно для больших чисел, пока компьютер считает заголовок должен быть таким, это будет заметно при действительно больших числах, например при числе 1000 вы наверное и не заметите как изменится заголовок, а вот при 1000000 это будет достаточно заметно

For delitel = 1 to delimoe - Задаем цикл. Программа просо подставляет в значение переменной delitel все числа от 1 до значения переменной delimoe. Например если вы хотите разделить число 5, то программа проверит , делится ли 5 на 1;2;3;4;5. И естественно, она должна показать на какие из этих чисел, delimoe делится без остатка.

resultat = delimoe / delitel - Тут вроде все ясно, получаем переменную resultat деля delimoe на delitel

If resultat = Int(resultat) then - А вот тут то программа и проверяет : является ли переменная resultat целым числом. Если resultat равен округленному resultat то ...

Text2.Text = Str(delimoe) + " : "+str( delitel) +" = " +str( resultat )+vbCrLf - Свойство Text элемента Text2 равно текстовому эквиваленту переменной delimoe плюс значок деления ( : ) плюс текстовый эквивалент переменной delitel плюс значок равно плюс текстовый эквивалент переменной resultat и плюс переход на другую строчку. В общем просто выводим результат в виде выражения.

End If - заканчиваем проверку целочисленности переменной resultat

Next - Завершаем цикл

Form1.Caption = "Числа"- Возвращаем заголовок программы

А теперь осталось только в код кнопки Выход ввести:

End - заканчиваем работу программы

В результате всего этого у вас должен получится такой код :

Option Explicit
Dim delimoe As Long
Dim delitel As Long
Dim resultat As Double

Private Sub Command1_Click()
Text2.Text = ""

If Not IsNumeric(Text1.Text) Then
MsgBox "Это не число", vbCritical
Exit Sub
Else
delimoe = Text1.Text
End If

Form1.Caption = "Считаю"

For delitel = 1 To delimoe
resultat = delimoe / delitel
If resultat = Int(resultat) Then
Text2 = Text2 + Str(delimoe) + " : " + Str(delitel) + " = " + Str(resultat) + vbCrLf
End If
Next

Form1.Caption = "Числа"
End Sub

Private Sub Command2_Click()
End
End Sub

Шаг №4 - Подведение итогов :

Все программа готова. Запутите и проверьте ее. Попробуйте найти числа на которые число 5 делиться без остатка, потом попробуйте какое-нибудь свое число. Если все получилось, то поздравляю, если же нет, то можете посмотреть исходники .


Выбор цвета

В этой программе я попробую научить вас работать с таким элементом управления как выпадающее меню (ComboBox). Этот элемент зачастую бывает очень нужен как начинающим программистам, так и профессионалам. Например на моей страничке он используется в опросе.

Шаг №1 - Постановка задачи

Как всегда определяем - что же будет делать наша программа. Наша программа будет изменять цвет формы в зависимости от того, который вы выбрали в выпадающем меню.

Шаг №2 - Визуальное проектирование

Приступаем к визуальному проектированию. На форме должны быть такие элементы :

Кнопка (Button) : Caption - Поменять цвет, Name - Command1
Кнопка (Button) : Caption - Выход, Name - Command2

Теперь разберемся с самим Выпадающим меню:
То что можно будет выбрать надо записывать в параметре List :
Нажмите на этот параметр и введите туда - Черный
Потом нажмите на любое место проекта, опять нажмите на List и введите туда Белый
Ну а теперь так же добавьте - Серый. Самое трудное в визуальном проектировании ;) вы уже сделали. Теперь в поле параметра Text элемента ComboBox1 впшите - Цвет. После всех этих ухищрений у вас должно получиться примерно так :

Шаг №3 - Программирование в коде

Теперь приступаем к программированию в коде, здесь можно пойти двумя путями : в одном легче,и код получится меньше, но второй путь грамотнее с точки зрения "правильного" программирования.Я опишу оба.
В обоих случаях вам придется в раздел глобальных объявлений вписать :

Option Explicit - как всегда объявляем переменные.

Dim Col As String - объявляем текстовую переменную Col -она будет обозначать текстовое значение цвета в программе ( например белый)

Dim FCol As Long - объявляем числовую (я пишу Long, а не Integer потому, что в интервал Integer'a не поместятся числа которые нам нужны) переменную FCol - она будет означать числовое значение цвета формы в программе.

Перейдите к коду кнопки Выход и напишите там

End

А теперь перейдите к коду кнопки Поменять и напишите там :

Col = Combo1.Text - присваиваем переменной Col значение параметра Text элемента Combo1. Если выбран Белый, то Col будет равен белый. Если вы не выбирали цвет, то Col будет равен Цвет.

Теперь пути расходятся, но пишется все только в коде кнопки Поменять, сначала описываем первый путь :

If Col = "Белый" Then FCol = Qbcolor(15) - Если текстовая переменная Col равна тексту "Белый" то числовая переменная FCol равна числовому эквиваленту цвета номер 15 (то есть белому)

If Col = "Черный" Then FCol = Qbcolor(0) - Если текстовая переменная Col равна тексту "Черный" то числовая переменная FCol равна числовому эквиваленту цвета номер 0 (то есть черному)

If Col = "Серый" Then FCol = Qbcolor(7) - Если текстовая переменная Col равна тексту "Серый" то числовая переменная FCol равна числовому эквиваленту цвета номер 7 (то есть серому)

If Col = "Цвет" Then Msgbox "Сначала выберите цвет",vbCritical - Если текстовая переменная Col равна тексту "Цвет" то есть изначальному тексту элемента TextBox то выдать сообщение об этом со значком ошибки.

Form1.BackColor = FCol - числовое значение заднего цвета элемента Form1 равно числовому значению переменной FCol.

В результате у вас должна получится такая программа :

Option Explicit
Dim Col as String
Dim FCol as Long

Private Sub Command1_Click( )
Col=Combo1.Text
If Col = "Белый" Then FCol = Qbcolor(15)
If Col = "Черный" Then FCol = Qbcolor(0)
If Col = "Серый" Then FCol = Qbcolor(7)
If Col = "Цвет" Then Msgbox "Сначала выберите цвет",vbCritical
Form1.BackColor = FCol
End Sub

Private Sub Command2_Click( )
End
End Sub

Все, первый путь - легкий, но неграмотный закончен, теперь можете проверить программу. Но я советую прочитать второй путь и разобраться в нем, при написании больших программ он будет короче. Извините, но для этого пути я не делал исходников, здесь все слишком легко.


Выбор цвета (другой путь)

Я очень советую освоить этот метод. На форме оставляем все то что было.Меняем только код кнопки Поменять.Пишите :

Col = Combo1.Text-присваиваем переменной Col значение параметра Text элемента Combo1.

Select Case Col - проверяем значение переменной Col

присваиваем переменной Col значение параметра Text элемента Combo1. Если выбран Белый, то Col будет равен белый. Если вы не выбирали цвет, то Col будет равен Цвет.

Case "Белый" - если текстовая переменная Col равна тексту Белый, то ...

FCol = Qbcolor(15) - ...числовая переменная FCol равна числовому эквиваленту цвета номер 15 (то есть белому)

Case "Черный" - если текстовая переменная Col равна тексту Черный, то ...

FCol = Qbcolor(0) - ...числовая переменная FCol равна числовому эквиваленту цвета номер 0 (то есть черному)

Case "Серый" - если текстовая переменная Col равна тексту Серый, то ...

FCol = Qbcolor(7) - ...числовая переменная FCol равна числовому эквиваленту цвета номер 7 (то есть серому)

Case "Цвет" - если текстовая переменная Col равна тексту Цвет, то ...

Msgbox "Вы не выбрали цвет", vbCritical - сообщить о том что цвет не выбран.

End Select - заканчиваем проверку переменной Col

Form1.BackColor = FCol - придаем параметру BackColor элемента Form1 значение переменной FCol

В результате у вас должна получиться вот такая программа :

Option Explicit
Dim Col As String
Dim FCol As Long

Private Sub Command1_Click()
Col = Combo1.Text
Select Case Col
Case "Белый"
FCol = QBColor(15)
Case "Черный"
FCol = QBColor(0)
Case "Серый"
FCol = QBColor(7)
Case "Цвет"
MsgBox "Выберите цвет", vbCritical, "Ошибка"
End Select
Form1.BackColor = FCol
End Sub

Private Sub Command2_Click()
End
End Sub

Шаг №4 - Подведение итогов

Проверьте программу. Если все работает, то поздравляю, это очень хорошо, а еще лучше если вы поняли что вы сделали , а не просто скопировали программный код. Если же программа не работает, то вы можете посмотреть исходники .


Управление объектом

Эта программа может понадобиться в качестве примера тем, кто собирается писать аркады, и вообще программы с клавиатурой.

Шаг №1 - Постановка задачи

Как всегда первым делом определяем, что же будет делать наша программа. Программа должна по нажатию одной из стрелок передвигать объект по экрану в напрвлении соответствующем стрелке.То есть если вы нажали на стрелку вверх, то объект должен передвинуться вверх и т.п.

Особенности

Во вторых вы должны уяснить некоторые особенности. Система координат в VB имеет начало в левом верхнем углу, то есть там координата (0;0). Соответственно, если вы хотите чтобы объект сместился вниз на n твипов (величина обозначающая координаты в VB), то вам нужно прибавить к его вертикали ( Top ) значение n, то же и с горизонталью. Вторая особенность, если на форме находятся управляющие элементы (кнопки и т.д.) то программа не будет работать, из-за того, что фокус находиться не на форме, а на одном из этих элементов. Конечно можно сделать и так чтобы все работало и с управляющими элементами, но эта статья для новичков, поэтому я не буду тут описывать, как это сделать.

Шаг №2 - Визуальное проектирование

Как всегда следующим шагом является визуальное проектирование. То есть вы должны разместить на форме некоторые объекты. На этот раз на форме должен быть только один объект - фигура (shape). Не меняйте его название,, пусть оно останется Shape1 (если хотите можете поменять, но тогда вы не сможете прото вставить мой код, вам придется делать поправки).Можете сделать эту фигуру чем угодно, хоть квадратом, хоть кругом, это не имеет значения , например в моей программе это - круг.Всё, с визуальным проектированием покончено.

Шаг №3 - Программирвание в коде

Теперь приступим к программированию в коде:

Для начала объявляем переменные пишем

Option Explicit ' Объявляем переменные
Dim x as Integer ' Объявляем числовую переменную х
Dim y as Integer ' Объявляем числовую переменную y

Теперь войдите в код формы и выберите событие KeyDown, в нем пишите

x = Shape1.Left 'Присваиваем переменной х значение положения объекта по горизонтали

y = Shape1.Top 'Присваиваем переменной х значение положения объекта по вертикали

If KeyCode = 37 Then x = x - 100 'Если код нажатой клавиши равен 37 (стрелка влево) то от значения переменной x отнять 100

If KeyCode = 39 Then x = x + 100 'Если код нажатой клавиши равен 39 (стрелка вправо) то к значению переменной x прибавить 100

If KeyCode = 38 Then y = y - 100 'Если код нажатой клавиши равен 38 (стрелка вверх) то от значения переменной y отнять 100

If KeyCode = 40 Then y = y + 100 'Если код нажатой клавиши равен 40 (стрелка вниз) то к значению переменной y прибавить 100

Shape1.Left = x 'Присваиваем значению горизонтального положения объекта значение переменной x

Shape1.Top = y 'Присваиваем значению вертикального положения объекта значение переменной y

В результате всего этого у вас должо получиться следующее :

Option Explicit

Dim x As Integer
Dim y As
Integer

Private Sub Form_KeyDown(KeyCode As Integer, Shift As
Integer)
x = Shape1.Left
y = Shape1.Top
If KeyCode = 37 Then x = x -
100
If KeyCode = 39 Then x = x + 100
If KeyCode = 38 Then y = y - 100
If KeyCode = 40 Then y = y + 100
Shape1.Left = x
Shape1.Top = y
End Sub

Шаг №4 - Подведение итогов

Вот и всё, теперь порпробуйте запустить программу, и понажимать на стрелки, получилось ? Если да, то поздравляю, если не то можете взять исходники и попробовать разобраться в них.


Ваша первая программа

Первая программа

Здесь мы расскажем вам о том как сделать первый шаг в освоении Visual Basic (VB) - вашу первую программу.

Установка :

1.Перед началом программирования вам нужно установить VB на свой компьютер.

2.После этого вам нужно запустить VB

3.В окне выбора - выберите Standart exe (если у вас на картинке гораздо больше иконок то не пугайтесь что VB у вас не тот, просто у вас установленна полная версия, а у нас - нет.

1.Шаг №1 - Поставновка задачи

Первым делом вам нужно понять - чего вы хотите от программы, что она будет делать - поставить задачу.В данном случае мы хотим чтобы программа при нажатии на кнопку выдавала сообщение - "Привет всем".

2.Шаг №2 - Визуальное проектирование:

Программирование в VB делится на 2 этапа - визуального программирования и программирования в исходном коде.Во втором шаге мы будем визуально программировать в VB. Изначально все должно выглядеть вот так

Если это не так, или вы просто не можете чего то разобрать кликните на картинке и увидите ее в полный рост.Итак-слева расположенна панель инструметнов (если ее нет зайдите в меню view и нажмите на Toolbox) на этой панели расположенны все элементы которые чаще всего используются на этапе программирования.Правее расположенна форма-это как бы окно вашей программы, на ней все и располагается.Еще правее находятся свойства, если вы ничего не меняли то это должны быть свойства текущей формы.У каждого элемента свои свойства, но основные свойства это: name-имя элемента в программе и caption-надпись на элементе.Ну и совсем справа находятся список файлов используемых в проекте и - под ним - расположение формы на экране при запуске программы.

Первым делом вам нужно нажать на кнопку на панели инструментов и растянуть ее на форме расположив ее так чтобы вам было удобно.Теперь нажмите на форму (она должна выделиться по краям и в окне свойств-properties должно появиться имя-name-Form1), измените у формы значение caption - на название вашей программы.Видите результат ? Полоска над программой должна изменить свой текст на ваш.Теперь сами дайте название кнопке. Теперь создайте вторую кнопку с caption-Выход.Теперь создайте TextBox.У TextBox-а нет свойств caption, но у него есть свойство - text - он определяет какой текст будет в TextBox.Задайте TextBox-у параметр name-Text1.

Дальше


Ваша первая программа

Теперь ваша форма должна выглядеть так:

И последнее.Очистите текст элемента TextBox.

3.Шаг №3 - Программирование в коде

Теперь приступим к этапу программирования в программном коде.Первым делом сделаем так чтобы при нажтии на кнопку выход программа заканчивала свою работу.Для этого 2 раза нажмите на кнопку Выход, теперь на месте формы перед вами должен появиться белый экран и такое выражение:

Private Sub Command2_Click ( )

End sub

Если немного не так то ничего страшного, просто у нас название кнопки-Command2 а у вас оно может быть другим.Теперь между первой и последней строчкой выражения введите - End.У вас должно получиться:

Private Sub Command2_Click ( )

End -Выход

End sub

Для того чтобы попасть обратно на форму вам нужно нажать на кнопку - [image] -расположенную над окном файлов.После того как вы попали на форму 2 раза нажмите на кнопку -Привет, у вас должен появиться все тот же белый экран с тем что вы уже ввели и новыми строками:

Private Sub Command1_Click ( )

End Sub

Private Sub Command2_Click ( )

End

End Sub

Введите между Private Sub Command1_Click ( ) и End Sub строку -

Text1.Text = "Привет"-Тут мы присваиваем свойству Text элемента Text1 значение Привет.

В конце концов у вас должна получиться программа

Private Sub Command1_Click ( )
Text1.Text = "Привет"
End Sub
Private Sub Command2_Click ( )
End
End Sub

Шаг №4 - Подведение итогов

Если у вас все так и получилось, то запустите программу клавишей F5.Нажмите на кнопку Привет, теперь в окне ввода текста-TextBox должен появиться текст - Привет.Если все так и есть, то попробуйте нажать на кнопку Выход, программа должна закончить свою работу.Если вы хотите сделать исполняемый файл-exe то сделайте так : войдите в меню файл и нажмите Make project1.exe. VB должен откомпилировать программу, то есть сделать exe'шный файл..Если у вас все получилось-то поздравляю - вы написали свою первую программу на VB.

Шаг №5 - Усовершенствование

Теперь давайте усовершенствуем программу.Сделаем так чтобы этот TextBox можно было очистить.Для начала, создайте кнопку с надписью-Очистка.Теперь войдите в ее программный код, нажав на нее 2 раза.Напишите в ее коде - Text1.Text = "". Теперь у вас должно получиться :

Private Sub Command1_Click ( )

Text1.Text = "Привет"

End Sub

Private Sub Command2_Click ( )

End

End Sub

Private Sub Command1_Click( )

Text1.Text = "" -Тут мы присваиваем свойству Text элемента Text1 значение ""

End Sub

Запустите программу и нажмите сначала на кнопку-Привет, а потом на кнопку Очистка.Сначала в текстовом окне должна появиться надпись Привет, а потом оно должно оказаться пустым.Если все так и есть, то я вас еще раз поздравляю - вы улучшили свою первую программу.

Если все работает, то я вас поздравляю.Если же нет, то попробуйте взять программу и разобраться.


В Visual Basic я могу назвать себя новичком (поставил полную версию только 2 недели назад), хотя имеется опыт написания несложных программ на Quick Basic и Visual Basic for Application

Напишем игру сами!

В Visual Basic я могу назвать себя новичком (поставил полную версию только 2 недели назад), хотя имеется опыт написания несложных программ на Quick Basic и Visual Basic for Application. Для того чтобы более полно познакомится с возможностями этого языка решил написать какую-либо несложную программу. Выбор был остановлен на игровой программе, т.к. для игр характерна четкая постановка задачи и довольно показательный результат. Результаты моих изысканий в области Visual Basic изложены в этой статье.

Попробуем сами написать игру Collumns. Для тех кто не знает это тетрисоподобная игра, в которой в стакан падают прямоугольники состоящие из 3 разноцветных квадратных сегментов. При совпадении цветов у трех и более сегментов они уничтожаются. Наверно, это самый простой вариант игр подобного вида, т.к. все управление сводится к смещению фигуры вправо и влево, а также перемене цветов (главное, что нет никаких поворотов). Тем не менее, игра довольно интересная.

Сразу оговорюсь, приведенный код далек от идеала, но я думаю, даже его изучение и повторение будет довольно полезно начинающим программистам, а полученный результат довольно осязаем и Вы сможете похвастаться, что сами написали игру (по крайней мере поняли, как она работает). Продвинутым программистам пример также может быть полезен, на его основе они могут написать свою более качественную и красивую игру в которую мы с большим удовольствием сыграем.

Начнем разработку игры как обычно с создания формы (имя по умолчанию Form1). Здесь вы можете полностью положиться на свои дизайнерские способности. Единственное что необходимо это наличие на форме элемента рисунок размером 3000х6000 твипов с именем Picture1, выполняющего роль игрового поля, и элемента метка с именем MyPoint, на которую мы будем выводить очки. Для тех, кто не желает сам работать над дизайном могу предложить свой вариант см. рис.1.

Рис.1. Вид формы для игры

В обработчик события Picture1_KeyDown необходимо занести следующий код.

Private Sub Picture1_KeyDown(KeyCode As Integer, Shift As Integer)

If Bloking = 1 Then Exit Sub

Select Case KeyCode

Case vbKeyUp: ChangeColor

Case vbKeyLeft: MoveLeft

Case vbKeyRight: MoveRight

Case vbKeyDown: notPause = 1

Case vbKeyEscape: pause

End Select

End Sub

Так же необходимо создать кнопку (или метку) для запуска игры, которая должна вызывать процедуру Main и кнопку, вызывающую процедуру Pause. Например так:

Private Sub Метка2_Click()

Main

End Sub

Private Sub Метка3_Click()

pause

End Sub

На этом подготовку формы можно считать законченной.

Приступим к самой программе. На самом деле программа очень простая. Единственный сложный момент в ней это процедура, которая будет искать и удалять одноцветные сегменты.

Первоначально необходимо описать все переменные, которые мы планируем использовать в любом программном модуле. Их в данном случае довольно много:

Option Explicit

Public nxt(3) As Integer ' текущие цвета фигуры

Public clm1 As Integer, rd1 As Integer

' в этих переменных хранятся текущие номера строки и столбца

Public matrnm(13, 7) As String, matr(13, 7) As Integer

' массивы для хранения игрового поля и имен элементов на поле

Public klip As Integer, och As Long

Public notPause As Integer

Public Bloking As Integer

Public nameid As Long

Public nm(3) As String

Public tcsp1 As Label 'три ссылки на объект

Public tcsp2 As Label

Public tcsp3 As Label

Затем переходим к первой процедуре. Эта процедура вызывается при запуске игры. Она должна подготовить игровое поле, очистить все используемые массивы, подготовить датчик случайных чисел, а затем вызывать все другие процедуры для организации игры. Код процедуры следующий:

Sub Main()

Dim fff As Control

Erase matr, matrnm

' очистка используемых массивов

matr(13, 1) = 1: matr(13, 2) = 1: matr(13, 3) = 1 matr(13, 4) = 1: matr(13, 5) = 5: matr(13, 6) = 1:

For Each fff In Form1.Controls

If Left$(fff.Name, 2) = "lb" Then

Form1.Controls.Remove fff.Name

End If

Next: ' подготовка игрового поля

och = 0: Randomize (Timer)

' инициализация генератора случ. чисел

Do ' основной цикл программы

init ' добавление фигуры

If matr(3, clm1) > 0 Then Exit Do

Dropping ' движение фигуры вниз

Do

klip = 0: Bloking = 1

' блокировка клавиатуры на время удаления

udal

Bloking = 0: notPause = 0

Loop Until klip = 0

Loop

MsgBox "ВСЕ!!!"

End Sub

Следующая процедура вызывается при создании каждой новой фигуры. Заметим, что фигура состоит из трех разноцветных меток квадратной формы. Здесь же присваивается значение переменным содержащие ссылки на эти метки. Это позволяет потом обращаться к ним из любого места программы.

Sub init()

' создание на экране новой фигуры

Dim i As Integer

clm1 = Int(Rnd * 6) + 1

nxt(1) = Int(Rnd * 5) + 10

nxt(2) = Int(Rnd * 5) + 10

nxt(3) = Int(Rnd * 5) + 10

' цвета сегментов фигуры

nameid = nameid + 1: nm(3) = "lbl" & CStr(nameid)

Set tcsp3 = Form1.Controls.Add("VB.Label", nm(3), _ Form1.Picture1)

' добавление сегмента

SetObj tcsp3, (clm1 - 1) * 500, 1000, nxt(3)

' установка параметров сегмента

nameid = nameid + 1: nm(2) = "lbl" & CStr(nameid)

Set tcsp2 = Form1.Controls.Add("VB.Label", nm(2), _ Form1.Picture1)

SetObj tcsp2, (clm1 - 1) * 500, 500, nxt(2)

nameid = nameid + 1: nm(1) = "lbl" & CStr(nameid)

Set tcsp1 = Form1.Controls.Add("VB.Label", nm(1), _ Form1.Picture1)

SetObj tcsp1, (clm1 - 1) * 500, 0, nxt(1)

End Sub

Так как при добавлении новой метки необходимо установить ряд ее свойств, причем эти свойства для всех меток одинаковы, то чтобы избежать повторения кода используется процедура SetObj, которой в качестве параметров передается ссылка на объект и значения необходимых свойств.

Sub SetObj(NmObj As Label, objLeft As Integer, _

objTop As Integer, ByVal objColor As Integer)

With NmObj

.BorderStyle = 1

.BackColor = QBColor(objColor)

.Left = objLeft: .Top = objTop

.Height = 500: .Width = 500

.Visible = True

End With

End Sub

Следующая процедура предназначена для организации падения фигуры. Падение выполняется до тех пор, пока не выполнится одно из двух условий - следующая клетка занята или достигнуто дно стакана. В любом случае после этого в соответствующий элемент массива matr заносится цвет оказавшегося там сегмента, а в массив matrnm его имя.

Sub Dropping()

Dim i As Integer, d As Integer

' фигура опускается пока не достигнет дна или другой фигуры

For i = 4 To 13

d = 0

If matr(i, clm1) = 0 Then

d = 1: rd1 = I

Else: matr(i - 3, clm1) = nxt(1):

matrnm(i - 3, clm1) = nm(1)

matr(i - 2, clm1) = nxt(2): matrnm(i - 2, clm1) = nm(2)

matr(i - 1, clm1) = nxt(3): matrnm(i - 1, clm1) = nm(3)

End If

If d = 0 Then Exit For

Call MoveDown

Next i

End Sub

Процедура MoveDown просто медленно двигает нашу фигуру вниз. В принципе здесь можно использовать метод Move.

Sub MoveDown()

Dim i As Integer

For i = 1 To 20 ' за раз опускаемся на 25 твипов

tcsp1.Top = tcsp1.Top + 25

tcsp2.Top = tcsp2.Top + 25

tcsp3.Top = tcsp3.Top + 25

sleep (0.005)

Next i

End Sub

Процедура ChangeColor вызывается в ответ на нажатие клавиши “стрелка вверх” оно циклический меняет цвет сегментов прямоугольника.

Public Sub ChangeColor()

Dim sd As Integer

sd = nxt(1): nxt(1) = nxt(3): nxt(3) = nxt(2): nxt(2) = sd

' просто циклически меняем цвета в массиве

tcsp1.BackColor = QBColor(nxt(1))

tcsp2.BackColor = QBColor(nxt(2))

tcsp3.BackColor = QBColor(nxt(3))

' и устанавливаем соответствующее свойство объектов

End Sub

Две процедуры MoveRight и MoveLeft очень похожи по своей реализации. Они организуют движение фигуры вправо и влево. Движение возможно, если три позиции справа и слева от текущей фигуры пусты, и она не выходит за границы стакана. Само движение реализуется изменением свойства Left.

Public Sub MoveRight()

If matr(rd1, clm1 + 1) + matr(rd1 - 1, clm1 + 1) _

+ matr(rd1 - 2, clm1 + 1) = 0 And clm1 < 6 Then

tcsp1.Left = tcsp1.Left + 500

tcsp2.Left = tcsp2.Left + 500

tcsp3.Left = tcsp3.Left + 500

clm1 = clm1 + 1

End If

End Sub

Public Sub MoveLeft()

If matr(rd1, clm1 - 1) + matr(rd1 - 1, clm1 - 1) _

+ matr(rd1 - 2, clm1 - 1) = 0 And clm1 > 1 Then

tcsp1.Left = tcsp1.Left - 500

tcsp2.Left = tcsp2.Left - 500

tcsp3.Left = tcsp3.Left - 500

clm1 = clm1 - 1

End If

End Sub

Простая процедура Sleep реализует небольшую задержку в программе, в качестве параметра ее передается величина задержки в секундах. Оператор DoEvents внутри позволяет выполнять любые события во время задержки.

Sub sleep(tm)

Dim tm1 As Single

If notPause = 1 Then Exit Sub

‘ если была нажата клавиша Down, то падение без задержки

tm1 = Timer

Do: DoEvents: Loop While tm1 + tm > Timer

End Sub

Остановимся и переведем дух. Уже на этом этапе, если все сделано правильно, наше приложение вполне работоспособно. Добавьте пустую процедуру с именем udal () и запускайте. Если возникли ошибки, то сразу отлаживайте. В общем фигуры уже должны падать и быть управляемыми. Не работает только удаление одноцветных сегментов.

Это самый трудный, но и самый интересный с точки зрения программирования участок программы. Отмечу, что данный код можно было бы значительно упростить, если не реализовывать два эффекта. Первый это плавное падение всех (а не одного) сегментов сразу. Второй постепенное удаление с визуальным уменьшением фигуры. Но если уж взялись программировать игру, то на эффектах не стоит экономить.

Sub udal()

Dim udd(12, 6) As Integer

Dim dsg(12, 6) As Integer

' описываем вспомогательные масивы

Dim i As Integer, j As Integer, kol As Integer

Dim k As Integer, s As Integer, p As Integer

Dim clr As Integer, t As Integer, l As Integer

Dim kl As Integer, clp As Integer, g As Integer

kol = 0

' в этом блоке обрабатываем матрицу с цветами

' и находим все сегменты подлежащие удалению

' затем заносим их в матрицу udd()

For i = 1 To 12

For j = 1 To 6

If matr(i, j) > 0 Then

For k = 1 To 4:

s = 0

Select Case k

Case 1: p = i: clr = matr(i, j)

Do: s = s + 1: p = p + 1

If p > 12 Then Exit Do

Loop While matr(p, j) = clr

Case 2: p = i: t = j: clr = matr(i, j)

Do: s = s + 1: p = p + 1: t = t + 1

If p > 12 Or t > 6 Then Exit Do

Loop While matr(p, t) = clr

Case 3: p = i: t = j: clr = matr(i, j)

Do: s = s + 1: p = p + 1: t = t - 1

If p > 12 Or t < 1 Then Exit Do

Loop While matr(p, t) = clr

Case 4: p = i: t = j: clr = matr(i, j)

Do: s = s + 1: t = t + 1

If t > 6 Then Exit Do

Loop While matr(p, t) = clr

End Select

If s >= 3 Then

kol = s: Beep

Select Case k

Case 1: p = i: For l = 1 To s:udd(p, j) = 1: p = p + 1: Next l

Case 2: p = i: t = j: For l = 1 To s: udd(p, t) = 1:

p = p + 1: t = t + 1: Next l

Case 3: p = i: t = j: For l = 1 To s: udd(p, t) = 1:

p = p + 1: t = t - 1: Next l

Case 4: p = i: t = j: For l = 1 To s: udd(p, t) = 1:

t = t + 1: Next l

End Select

End If

Next k

End If

Next j, i

kl = 0

' плавно уменьшаем элементы, подлежащие удалению

For k = 1 To 5

For i = 1 To 12

For j = 1 To 6

If udd(i, j) Then

Form1.Controls(matrnm(i, j)).Width = _

Form1.Controls(matrnm(i, j)).Width - 50

Form1.Controls(matrnm(i, j)).Height = _

Form1.Controls(matrnm(i, j)).Height - 50

Form1.Controls(matrnm(i, j)).Top = _

Form1.Controls(matrnm(i, j)).Top + 25

Form1.Controls(matrnm(i, j)).Left = _

Form1.Controls(matrnm(i, j)).Left + 25

sleep (0.005)

End If

Next j, i

Next k

'удаляем их

For i = 1 To 12

For j = 1 To 6

If udd(i, j) Then

Form1.Controls.Remove (matrnm(i, j)): matr(i, j) = 0

matrnm(i, j) = ""

kl = kl + 1

End If

Next j, i

If kl = 0 Then Exit Sub

' в зависимости от количества удаленных сегментов

' подсчитываем очки

Select Case kl

Case 3: och = och + 9

Case 4, 5, 6: och = och + kl * 2

Case Is > 6: och = och + kl * 3

End Select

Form1.MyPoint = Right("000000" & CStr(och), 6)

Do

clp = 0

Erase dsg

For i = 1 To 11

For j = 1 To 6

If matr(i, j) > 0 Then

For k = i + 1 To 12

If matr(k, j) = 0 Then dsg(i, j) = dsg(i, g) + 1: clp = 1

Next k

End If

Next j, i

If clp = 0 Then Exit Do

' довольно непонятный блок

' здесь мы сдвигаем вниз те сегменты под которыми

' образовалась пустота

' любознательные могут выполнять это место пошагово

' чтобы разобраться, но главное это работает!

For l = 1 To 5

For i = 1 To 11

For j = 1 To 6

If dsg(i, j) > 0 Then

Form1.Controls(matrnm(i, j)).Top = Form1.Controls(matrnm(i, j)).Top + 100

End If

Next j, i

sleep (0.05)

Next l

For i = 12 To 2 Step -1

For j = 1 To 6

If dsg(i, j) > 0 Or matr(i, j) = 0 Then

matr(i, j) = matr(i - 1, j)

matrnm(i, j) = matrnm(i - 1, j)

End If

Next j, i

For j = 1 To 6

If dsg(1, j) > 0 Or matr(1, j) = 0 Then

matr(1, j) = 0

matrnm(1, j) = ""

End If

Next j

Loop While clp = 1

klip = kl

End Sub

Нам осталось реализовать паузу. Ну а это вообще очень легко.

Sub pause()

MsgBox " Пауза! ", , " Пауза !"

End Sub

Вот и все!

В заключении дать советы по модернизации этой игры, которые вы можете воспринимать в качестве домашнего задания. Советы приведены в порядке возрастания их сложности.

    1. Добавьте предпросмотр следующей фигуры.

    2. Добавьте меню, помощь, форму About.

    3. Разработайте таблицу рекордов.

    4. Используя элемент Image вместо Label сделайте, чтобы падающие сегменты были в виде картинок.

    5. Придумайте что-нибудь интересное сами.


Visual Basic с самого начала

Примеры
В этом разделе представлены программы, которые сами по себе особой ценности не несут, но эти статьи очень желательно прочитать новичкам, так как из этих примеров можно выудить массу полезной информации.На этих примерах можно постигнуть азы программирования.К тому же эти примеры можно использовать в своих программах, как например программу - пароль. Написав с моими инструкциями какую-нибудь программу, не останавливайтесь на достигнутом, пытайтесь улучшить программы, можете попробовать комбинировать их.

Первая программа - "Привет всем"

Программа пароль

Проверка переключателя - пример работы с переключателем

Купля-продажа - пример работы с переменными

Управление объектом - пример работы с клавиатурой

Отсчет - пример работы с таймером

Запись данных в файл

Считывание данных из файла

Меню под заголовком

Выбор цвета - пример работы с выпадающим меню (ComboBox) и цветом формы.

Просмотрщик графических файлов-статья JetWorm'а. Рекомендую просмотреть всем, но в особенности тем, кто уже хоть немного знаком с VB.

Печать - статья прислана JetWorm'ом, в ней описываются очень важные моменты программирования, в том числе и вывод данных на печать.

Игра - угадай число

Кратные числа - пример работы с циклам, целыми числами и т.д.

Музыка в проекте

Регистрация программы - защитите ваше творение

Напишем игру сами


Загрузка данных из файла

Считывание данных из файла

Этот пример описывает как можно получить данные из файла, его желательно употреблять вместе с предыдущим примером- "Запись данных в файл" .

Шаг №1 - Постановка задачи

Как всегда для начала мы будем ставить задачу, что же должна делать эта программа ? Программа должна считать все данные из файла autoexec.bat который расположен в корневом каталоге C:\ и выводить их в текстовое окно.

Шаг №2 - Визуальное проектирование

Вторым шагом, как обычно будет визуальное проектирование, то есть расстновка объектов на форму. У вас на форме должны быть такие объекты :

Текстовое окно (TextBo) : Name-Text1, Caption-ничего Multiline-True
Кнопка (Button) : Name-Button1, Caption-"Загрузить"
Кнопка (Button) : Name-Button2, Caption-"Выход"

В результате должно получиться примерно так :

Все, с визуальным проектированием покончено.

Шаг №3 - Программирование в коде

Теперь как всегда самая сложная, но и самая нужная и интересная часть : программирование в коде.Войдите в раздел глобальных объявлений и напишите там :

Option Explicit- этим мы даем понять программе что объявляем переменные

Dim st as String - мы объявляем текстовую переменную st, она нам нужна для обозначения очередной строки, то есть в нее мы будем записывать данные из очередной строки

Dim AllText as String - мы объявляем текстовую переменную AllText, я думаю что из названия вы уже поняли для чего нам нужна эта переменная. Если это не так то объясняю : эта переменная нужна нам для обозначения всего текста, то есть всех данных вытащеных из файла.

Теперь зайдите в код кнопки Считать.Напишите там :

Open "C:\autoexec.bat" for Input as #1 - этой строкой мы открываем файл autoexec.bat, под номером 1, для чтения из него данных по сторкам.

Do While Not EOF (1) - для понимания смысла этого выражения можно перевести его дословно : делать пока нет конца файл (Eon of File) номер 1

Input #1, st - Считываем из файла номер один очередную строку ( в начале цикла это будет первая, потом вторая и так пока не кончится файл)

AllText = AllText + st + chr(13)+chr(10)- То есть весь тект равен весь тект плюс очередная строка ( в начале цикла весь текст - пустой, то есть в этой переменной ничего не записано, дальше весь текст - это первая строка, потом первая и вторая строка и т.д. пока не кончится файл) и плюс клавиша Enter ( сейчас я не буду описывать почему Enter - Это Chr(10) +chr(13) это будет описанно в соответствующей статье).Клавиша Enter тут нужна для перехода на другую строчку, если ее не нажать, то переменная AllText будет сплошным слиянием строк, то есть все строки будут записанны в одну большую строку.К тому моменту когда он кончится переменная AllText будет равна всему тексту файла.

Loop - заканчиваем цикл, то есть если еще нет конца файла то программа идет к началу цикла, а если конец файла достигнут, то программа идет на свою следующую строчку.

Close #1 - закрываем файл номер 1

Text1.Text = AllText - Придаем параметру Text элемента Text1 значение переменной AllText.

Все ! Программа окончена.

Шаг №4 - Подведение итогов

Проверьте программу.Если все работает то попробуйте открыть другие файлы, наример файл data.dat который (я надеюсь) вы сделали с программой запись в файл.Если вы не сделали его, то пожалуйста, сделайте, это очень легко, но если вы хотите стать програмистом то это вам обязательно поможет.Если же программа не работает то, можете попробовать разобраться в исходниках.


Меню

В этой статье описывается создание меню вверху формы. Примеры такого меню можно встретить где угодно если вы работаете в Windows. Например сейчас поднимите глаза к верху экрана, что вы видите ? Правильно, заголовок окна. А что вы видите под ним ? Это и есть меню. Если вы хотите чтобы ваша программа достойно выглядела среди других такая менюшка обязательно должна присутствовать в ней. Тем более что в Visual Basic они делаются легко и непринужденно. Эта статья как раз об этом.

Шаг №1 - Постановка задачи

Итак, что же будет делать наша программа. Я задался этим вопросом когда начал писать эту статью. Ведь для демонстрации работы меню, гужно только оно само. Так сказать меню ради меню. Но так как надо еще и показать какие-нибудь действия с меню я решил что оно будет при нажатии на соответствующий пункт меню, выдавать какое-нибудь сообщение. Например хорошее, и плохое.И еще пусть будет кнопка выход, так как она там очень нужна и вообще она везде есть.

Шаг №2 - Визуальное проектирование

Следующий пункт - визуальное проектирование. Объектов на форме не будет, на ней будет тоько меню. Как его сделать : зайдите в верхнее меню Visual Basic, в пункт Tools (Инструменты), найдите там пункт Menu Editor и войдите в него. Перед вами должно быть вот такое окно :

Введите в поле Caption - Сообщения, а в меню Name - mnuMes.Нажмите Ок.Попробуйте щелкнуть на появившемся меню Сообщения. Вас должног выкинуть в программный код. Дело в том, что сейчас пункт меню Сообщения является кнопкой. Опять зайдите в редактор меню. Нажмите Next и стрелочку вправо. Теперь напишите в поле Caption - Хорошее, а в поле Name - mnuGood. Нажмите Ок. Опять попробуйте нажать на Сообщения. Теперь вас уже не должно выкинуть в код, а должно появиться выплывающее меню с надписью Хорошее. Теперь сами попробуйте сделать пункт Плохое с параметром Name - mnuBad прямом под пунктом Хорошее. Получилось ? Если да, то продолжайте если же нет то попробуйте еще раз, вы должны разобраться в этом редакторе меню. После этого сделайте пункт с Caption'ом - тире, то есть не слово тире, а просто тире, сепаратор, и именем sep. И наконец сделайте пункт с Caption'ом Выход, и именем - mnuExit.После всего этого у вас должна получиться такое окно :

Шаг №3 - Программирование в коде

Теперь перейдите к коду пункта Хорошее, просто нажав на него. Введите туда:

Msgbox "Это хорошее сообщение",vbExclamation,"Хорошее" - выдаем сообщение с заголовком - Хорошее , тектом : "Это хорошее сообщение" и значком восклицательного знака.

Войдите в код пункта Плохое и введите туда

Msgbox "Это плохое сообщение",vbCritical ,"Плохое" - выдаем сообщение с заголовком - Плохое, тектом :"Это плохое сообщение" и значком ошибки Windows.

Войдите в код пункта Выход и введите туда :

End - Выход

Теперь программа должна выглядеть так :

Private Sub mnuBad_Click()
MsgBox "Это плохое сообщение", vbCritical, "Плохое"
End Sub

Private Sub mnuExit_Click()
End
End Sub

Private Sub mnuGood_Click()
MsgBox "Это хорошее сообщение", vbExclamation, "Хорошее"
End Sub

Шаг №4 - Подведение итогов

Все! Программа готова.Проверьте ее. Попробуйте добавить свои пункты и все такое. Если все работает то поздравляю - вы нучились работать с меню, если же нет то вы можете взять исходники и разобраться в них.


Музыка

Эта програмкка покажет вам как вставить музыку в свои программы. Тут я думаю комментарии излишни так как многие новички , которые пишут программы, особенно игры, хотят вставить в них музыку и звук.Я думаю добавить стоит только то, что эта программа будет использовать ActiveX, который длжен поставляться с VB. Только не забудьте - если вы поставляете программу на другой компьютер ( на котором нет VB )- то вам придется поместить этот ActiveX в ее каталог на том компьютере. И еще - если картинки при компиляции встраиваются в программу, то все остальное - музыка, звук и все такое вам придется также поставлять с программой.

Шаг №1 - Постановка задачи

Наша программа будет при запуске программы проигрывать файл gen.mid, котрый будет находится в каталоге с программой и при его окончании переводить на начало и вновь запускать.

Вам надо усвоить одно свойство VB. Если вы напишете "gen.mid" - в том месте где должно быть имя файла, то VB будет искать его в рабочем каталоге. Если вы запускаете программу непосредственно из VB - не откомпилировав ее, то рабочим каталогом будет или рабочий каталог, или каталог к которому вы в последний раз обращались, например открывая или сохраняя проект. Довольно муторно определять какой каталог будет рабочим, поэтому я советую проверять программу откомпилировав ее, и запустив екзешный файл.

Прежде всего создайте каталог программы, куда вы будете сохранять все файлы этой программы ( в том числе и екзешный файл) и поместите туда midi - файл ( у меня в программе - это gen.mid, но у вас он может быть другой).

Шаг №2 - Визуальная реализация

Для начала вам нужно поместить на форму тот ActiveX , о котором я говорил вам в самом начале. Нажмите в панели объектов на правую кнопку мышки и в выплывающем меню нажмите - Components. Выберите там объект MCIWndX Control и нажмите Ок. Поместите на форму этот элемент и придайте ему размеры которые вам нужны. В поле свойства FileName этого объекта введите - gen.mid. Теперь откомпилируйте программу и запустите ее екзешный файл, Там должно быть примерно такое окно.

Нажмите на кнопку "Play" и прислушайтесь, если вы слышите звук, то поздравляю - половину программы вы уже сделали. Елси - же нет, то может быть несколько причин.

1) Кнопка "Play" - серая ? Это значит что файл midi не найден. Проверьте - есть ли он в каталоге, если есть то проверьте что вы напислали в свойстве FileName

2)У вас выключенны колонки или их вообще нет. Если это так, то если они выключенны - включите их, а если их нет - то тут я ничем не могу помочь

3) Файл mid - испорчен или не содержит ничего. Поменяйте его.

4)У вас на колонках поставлен низкий уровень звука - повысьте его

5)Вы - глухой. Тут я также ничем не могу помочь.

Если же ни одна из причин не подходит, пожалуйста - напишите мне.

Если все получилось, то сделайте этот элемент невидимым ( нам же надо чтобы музыка включалась без участия пользователя )

Шаг №3 - Программирование в коде

К этому шагу нужно приступать лишь в том случае если у вас все успешно прошло на предыдущем. Впишите в код формы, событие Load :

Mciwnd1.Command = "Play"- просто даем команду элементу Mciwnd1 - проигрывать файл.

В общем - то теперь программа будет при запуске проигрывать музыку. Но при ее окончании она будет замолкать и все. Нам же надо чтобы при окончании музыки программа вновь проигрывала ее сначала.

Войдите в код элемента Mciwnd1 выберите событие PositionChange и там пишите

If Mciwnd1.Position = Mciwnd1.Lenght Then - если текущая позиция "ползунка" равна общей длинне всего файла, то есть файл подошел к концу то ...

Mciwnd1.Position = 0 - Переводим "ползунок"на начало

Mciwnd1.Command = "Play" - даем команду элементу Mciwnd1 - проигрывать файл.

После всего этого у вас должен получиться такой код :

Private Sub Form_Load()
MCIWnd1.Command = "Play"
End Sub

Private Sub MCIWnd1_PositionChange(ByVal Position As Long)
If MCIWnd1.Position = MCIWnd1.Length Then
MCIWnd1.Position = 0
MCIWnd1.Command = "Play"
End If
End Sub

Все!!!

Шаг №4 - Подведение итогов

Откомпилируйте программу и проверьте ее. Если музыка играет и при подходе мелодии к концу переходит на ее начало то я вас поздравляю. Если же нет то вы можете взять (вместе с mid файлом) исходники.

P.S.:Вместо файла mid вы также можете подставить файлы avi и wav


Вторая программа

Вторая программа-получение пароля

Внимание:в этом тексте уже не описываются некоторые принципы работы с VB, все они описаны в первой программе .

Иногда случается так, что вам нужно поставить на программу пароль.Для этого вы конечно можете закодировать сам файл - exe программами типа Cryto, но с этим много мороки : сначала нужно закодировать файл, потом если хотите его запустить придется его раскодировать. Гораздо лучше встроить пароль прямо в программу.Тем более что это совсе нетрудно.

Шаг №1 - Постановка задачи

Наша программа будет спрашивать пароль. Если юзер введет правильный пароль то она сообщит ему что пароль правилен. Если же нет, то она сообщит ему, что пароль неправилен. Как видите тут все легко и просто.

Шаг №2 - Визуальное проектирование

На форме разместите акие элементы с такими настройками :

TextBox (Поле свойства text должно быть пустым)
Command1 (в поле свойства Caption введите - Вход )
Command2 (В поле свойства Caption введите - Выход)
У формы свойство Caption должно быть -получение пароля.Теперь ваша форма должна выглядеть вот так :

Шаг №3 - Программирование в коде

Теперь приступим к программированию в коде.Нажмите два раза на кнопку - Выход - в коде этой кнопки введите End. Вернитесь к форме. Нажмите два раза на кнопку Вход и напишите :

If Text1.Text = "Ваш пароль" then msgbox "Пароль правильный" else msgbox "Неправильный пароль"

Вместо фразы Ваш пароль поставьте тот пароль который вам нужен.
Теперь разберем все по порядку:

If Text1.Text="Ваш пароль"- В этой фразе мы проверяем : равно ли свойство Text элемента Text1 какому-либо паролю.

Then Msgbox "Пароль правильный" - Если равно, то программа выводит собщение говорящее о том, что пароль правильный.

Else Msgbox "Пароль неправильный"-Иначе, выводит сообщение о том, что пароль неправильный.

Запустите программу и проверьте ее

Шаг №4 - Подведение итогов

Готово ! Теперь запустите программу.Проверьте все.Введите пароль который вы ввели вмсто значения Ваш пароль и нажмите на Вход.Если сообщение гласит о том, что пароль правильный то попробуйте изментиь его на неправильный и нажмите на Вход, если вы получили сообщение, что пароль неправильный, то попробуйте нажать на Выход и если программа вышла то поздравляю вас, вы написали программу пароля на VB!!! Если, что то не работает, то вы можете взять программу попробовать разобраться в ней.

P.S.:Если все работает, то усовершенствуйте программу.Поставьте у TextBox свойство PassworChar равным звездочке - *.Проверьте программу.


Купля - продажа

Купля/продажа

В этой программе мы описываем работу с переменными на основе действия купли продажи. Это действие вы можете использовать когда будете писать игры, но главное задание этой статьи - научить вас работать с переменными на практике.

Шаг №1 - Постановка задачи

Для начала, как и во всех программах надо определить - что же будет делать программа.Эта программа должна при нажатии соответстующей клавиши отнимать какое-то количество денег и прибавлять какое-то количество товара.

Шаг №2 - Визуальное проектирование

Второе - это визуальное проектирование. На форме вы должны расставить такие объекты :

1) Кнопку с Caption'ом - Покупка ;

2) Кнопку с Caption'ом - Продажа ;

3) Надпись (Label) с Caption'ом - Деньги ;

4) Надпись (Label) с Caption'ом - 100 и Name'ом - money

5) Надпись (Label) с Caption'ом - Товар

6) Надпись (Label) с Caption'ом - 0 и Name'ом - goods

7) Надпись (Label) с Caption'ом - Цена товара

8) Надпись (Label) с Caption'ом - 10 и Name'ом - price

В результате у вас должно получиться так :

Шаг № - Программирование в коде

Далее приступим к программированию в коде :

Для начала объявим переменные (объяснм программе какие переменные будут у нас в программе. Для этого напишем :

Option Explicit - тут мы как бы говорим, что будем объявлять переменные

Dim mon - объявляем переменную с именем mon - деньги.

Dim g - объявляем переменную с именем g - товар.

Dim pr - объявляем переменную с именем pr - цена товара.
Все, переменные мы объявили.

Далее войдем в код формы (кликнув на неё 2 раза) - это будет код формы с действием Load ( действие которое исполняется при загрузке формы), и придадим переменным mon и g значения 100 и 0 соответственно :

g = 0 - придаем переменной g значение 0

mon = 100 - придаем переменной mon значение 100

pr = 10 - придаем переменной pr значение 10

Ну а сейчас создадим сами действие покупки.

Войдите в код кнопки - Покупка и введите :

if mon - pr => 0 then - тут идет проверка : если деньги оставшиеся после покупки будут больше или равны нулю то ...

mon = mon - pr - придаем переменной деньги значение деньги минус цена товара, то есть от денег отнимаем цену товара.

g = g + 1 - придаем переменной товар значение товар плюс 1 (увеличиваем значение переменной товар на 1)

else - иначе (то есть если деньги оставшиеся после покупки будут меньше нуля то ...

Msgbox "У вас не хватает денег на покупку" , vbCritical - выдать сообщение о том, что денег на покупку не хватит со значком ошибки.

end if - завешаем процедуру проверки.

money.Caption = mon - придаем свойству Caption элемента money значение переменной mon.

goods.Caption = g - придаем свойству Caption элемента goods значение переменной g.

А теперь создадим действие продажи. Для этого войдите в код кнопки Продажа и напишите там :

If g - 1 >= 0 then - проверям : будет ли g минус 1 (то есть кол-во товара после того, как от него отнимут 1 больше или равно нулю) и если да то...

mon = mon + pr - придаем переменной mon значение mon плюс 1 (то есть прибавляем к деньгам цену товара)

g = g - 1 - придаем переменной g значение g минус 1 ( то есть отнимаем от кол-ва товара 1)

Else - иначе, то есть если g минус 1 будет меньше нуля то ...

Msgbox "У вас нет товара на продажу", vbCritical - выводим сообщение о том, что нет товара на продажу со значком ошибки.

End If - заканчиваем процедуру проверки

money.Caption = mon - придаем свойству Caption элемента money значение переменной mon.

goods.Caption = g - придаем свойству Caption элемента goods значение переменной g.

После всего этого у вас должен быть такой код :

Option Explicit
Dim mon
Dim g
Dim pr

Private Sub Command1_Click()
If mon - pr >= 0 Then
mon = mon - pr
g = g + 1
Else
MsgBox "У вас не хватает денег на покупку", vbCritical
End If
money.Caption = mon
goods.Caption = g
End Sub

Private Sub Command2_Click()
If g - 1 >= 0 Then
mon = mon + pr
g = g - 1
Else
MsgBox "У вас нет товара на продажу", vbCritical
End If
money.Caption = mon
goods.Caption = g
End Sub

Private Sub Form_Load()
g = 0
mon = 100
pr = 10
End Sub

Шаг №4 - Подведение итогов

Проверьте программу. Попробуйте купить товар и продать его.Если все работает, то я вас поздравляю.Если же нет, то попробуйте взять программу и разобраться.


Программа "Печать"

Шаг №1 - Постановка задачи

С помощью этой программы Вы научитесь работать с полем ввода текста, с панелями сообщений (MessageBox), а также научитесь распечатывать форму со всей информацией, расположенной на этой форме. Но самое главное - я раскажу Вам, какие преимущества даст использование массивов элементов в ваших программах. При выполнении программы Вы сможете ввести в соответствующие поля нужную информацию, а затем распечатать ее на принтере. Из-за удивительной простоты кода, распечатывание будет проводиться самым простым образом: форма, сохраняя все свои параметры (цвет, размер, помещенные на ней элементы и т.д.) будет вырисовываться в верхнем левом углу листа. Кстати, программа будет работать только с той машиной, на которой установлен принтер... Так что, перед использованием программы советую включить/подсоединить/проинсталлировать принтер, если таковой имеется...

Шаг №2 - визуальная реализация программы.

В этом разделе будут описаны элементы, помещаемые на форму с теми свойствами, которые требуют своего изменения. Для избежания непредвиденных ошибок, просьба указывать свойства так же, как это сделал автор. Итак, поехали...

Форма
Name = frmPrint
Caption = Information for printing
BorderStyle = Fixed Single
MaxButton = False
MinButton = True

Кнопка #1
Name = cmdPrint
Caption = Print
Default = True

Свойство Default = True указывает, что программный код кнопки будет исполняться при нажатии Enter'а.

Кнопка #2
Name = cmdExit
Caption = Exit
Cancel = True

Свойство Cancel = True указывает, что программный код кнопки будет исполняться при нажатии Escape'а.

Тут необходимо кое-что пояснить. При выполнении следующего действия программа скажет, что элемент с таким именем уже существует, и спросит, хотите ли Вы создать массив элементов. Ответьте "Да". Итак, поместите на форму элемент TextBox со следующими свойствами:

Name = txtInform
Text = <пустой>

Затем, поместите на форму еще 6 (шесть) текстбоксов с таким же именем (может просто скопировать первый и вставлять их на форму). Затем рядом с каждым текстовым полем поместите Label'ы с названиями, объясняющими назначения полей ввода: First name, Last name, Nick, Date of birth, E-mail, Phone.

На этом этап визуальной разработки Вашего приложения закончен. Теперь ваша форма должна выглядеть примерно вот так:

Шаг №3 - Программирование в коде

Ну вот. Теперь можно переходить к наисанию кода. Как я уже говорил, код программы будет несложнным. Вообще есть программы с простейшей визуальной реализацией и очень запутанным кодом, а наша программа, наоборот, заставила Вас повозиться с установкой на форме различных элементов, но зато код ее будет элементарным. Во-первых, в коде кнопки выхода напишите команду выхода из программы. Но это мы сделаем интереснее, используя панели сообщения (Message Box). Итак, пишите:

If MsgBox("Do you really wanna leave us?", vbYesNo + vbQuestion, "Exit?") = vbYes Then
End
End If

В данном случае, при нажатии кнопки выхода появится панель с вопросом, хотите ли вы выйти, и двумя кнопками: "Да" и "Нет". Подробно об использовании MessageBox я рассказывать не буду - всю информацию можно найти в хелпе.

Теперь давайте напишем код для кнопки "Print". Ключевые моменты я выделил синим цветом.

'b - индекс поля ввода
'Проверяем все поля с индексом
'от 0 до 5
For b = 0 To 5
'Если какое-то поле пустое
If txtInform(b).Text = "" Then
'Выдаем сообщение
'Если ответ - Да, то иди к метке 1
If MsgBox("One of the fields is clear. Continue?", vbQuestion + vbYesNo, "Printing") = vbYes Then
GoTo 1
'Если ответ - Нет, то иди к метке 2
Else
GoTo 2
End If
End If
Next
'Метка 1
1
frmPrint.PrintForm
Exit Sub
'Метка 2
2
MsgBox "Please, fill the fields."
Exit Sub

Шаг №4 - Подведение итогов

В принципе, все действия объяснены в комментариях. Но кое-что стоит объяснить.
Во-первых, индекс элементов в массиве начинается с нуля. Это надо запомнить, потому что понять это невозможно. Это - ошибка многих, только начавших работать с массивами элементов.
Во-вторых, мы не случайно использовали метки. Дело в том, что при использовании такого цикла, действие будет повторяться столько раз, сколько раз выполняется условие. Т.е. в нашем случае, если Вы не заполнили несколько полей, то программа выдаст Вам несколько сообщений подряд. Но нам-то нужно только одно сообщение! Поэтому мы прерываем процедуру с помощью ссылки на метку. В принципе, это можно было сразу делать с помощью команды Exit Sub (выйти из процедуры), но мне хотелось показать еще и работу с метками.
Кстати, мы использовали эту команду для того, чтобы разделить две метки: первую и вторую, а иначе вторая метка принадлежала бы первой.

На этом и второй этап создания программы завершен. Вы можете откомпилировать Вашу программу и посмотреть ее в действии.
Эту программу можно значительно усовершенствовать, добавив другие поля ввода. Для большей красоты отпечатанной формы ее размеры можно увеличить, а шрифт я настоятельно рекомендую сделать побольше.

Прим. Редактора.: Если у вас что - то не работает, можете посмотреть исходники.


Запись в файл

Файл

Эта статья очень нужна тем, кто собирается работать с файлами, а это в конце концов становится нужно любому программисту.Ведь очень часто нужно сохранить что-нибудь, какие-нибудь переменные например имя пользователя или что-нибудь в этом роде.

Шаг №1 - Постановка задачи

Как всегда сначала в программе определяем, что же программа будет делать. Данная программа будет добавлять в файл с именем Data.dat имя и фамилию пользователя, которые он напишет в текстовых полях.

Шаг №2 - Визуальное проектирование

Вторым шагом мы приступаем к визуальному проектированию.Разместите на форме такие элементы :

Наклейка (Label1) - с параметром Caption - Имя
Наклейка (Label2) - с параметром Caption - Фамилия
Текстовое окно (Text1) - в окне свойства Text удалите всё
Текстовое окно (Text2) - в окне свойства Text удалите всё
Кнопка (Button1) - с Caption'oм - Записать
Кнопка (Button2) - с Caption'oм - Выйти

В результате у вас должно получиться так :

Шаг №3 - Программирование в коде

Теперь как всегда приступаем к программированию в коде. Для начала мне нужно объяснить некоторые положения.Vb может открывать файлы для нескольких действий : запись данных в файл по срокам (с удалением всего что было до этого)-оператор Output, загрузка данных из файла по строкам - оператор Input, добавление данных в файл по строкам (без удаление всего что было до этого)-оператор Append. Есть ещё несколько видов открытия файла, но они нам пока не нужны.В этом примере мы будем пользоваться оператором Append. И ещё одна особенность, если вы запустите программу из VB, то Data.dat будет записываться в рабочий каталог VB, если же вы запустите уже откомпилированную программу, то есть файл с расширением exe, то файл Data.dat будет записываться в тот каталог где находится этот exe'шный файл.Если файла Data.dat не будет, то будет создан новый файл с этим именем.Приступаем к программированию.

Войдите в раздел глобальных объявлеий ( в самом верху) и напишите там

Option Explicit 'Объявляем переменные

Dim fn As String 'Объявляем текстовую переменную fn - Имя
Dim ln As String 'Объявляем текстовую переменную ln - Фамилия

Войдите в программный код кнопки Записать и напишите там

fn = Text1.Text 'Придаем переменной fn значение Text элемента Text1

ln = Text2.Text 'Переменная ln равна значению Text элемента Text2

Open "data.dat" For Append As #1 'Открываем файл data.dat , под номером 1, для добавления в него данных

Print #1, fn ;" "; ln 'Записать в файл номер 1 переменную fn,пробел и переменную ln

Close #1 'Закрываем файл номер 1

Войдите в код кнопки Выход и напишите там

End 'Выход

В результате всего этого у вас должно получиться

Option Explicit

Dim fn As String
Dim ln As String

Private Sub Command1_Click()
fn = Text1.Text
ln = Text2.Text
Open "data.dat" For Append As #1
Print #1, fn;" ";ln
Close #1
End Sub

Private Sub Command2_Click()
End
End Sub

Шаг №4 - Подведение итогов

Все, программа готова, теперь надо проверить её.Желательно перед проверкой программы, откомпилировать ее.Теперь запустите exe'шный файл и введя свое имя и фамилию нажмите Записать.Теперь введите ещё чьи-нибудь имя и фамилию и опять запишиите их.Выйдите из программы и зайдите в тот каталог, откуда вы запускали ее. Найдите там файл Data.dat и просмотрите его любым текстовым редактором, там должны быть те данные котрые вы вписали, если же их нет, то напишите мне, я постараюсь вам помочь.Можете также взять исходники и попробовать разобраться в них.


Серийный номер

Предположим вы написали супер-крутую программу (если это действительно так, то я удивлен что вы это читаете). И вы не хотите чтобы кто-то ей пользовался не заплатив за нее, или просто не зарегистрировавшись. Можно просто выдавать программы только тем, кто заплатит, но тогда они в свою очередь один раз заплатив за нее могут сами распространять ее, да и вообще путь этот крайне неэффективный. Или например вы хотите чтобы программа была только демоверсией, а за полную нужно зарегистрироваться. Так поступают многие разработчики, и даже всеми любимый Win95 использует для регистрации сочетание серийного номера и регистрационного кода. В этой статье мы разберем как это сделать. Конечно - эта программа не очень хорошая защита, обойти ее очень легко, но она показывает как это можно сделать.

Шаг №1 - Постановка задачи

Вообще в этой программе можно сделать очень много. Например сделать так, чтобы какая то программа работала, но через каждую минуту просила зарегистрироваться, или чтобы не были возможны некоторые функции программы. Но мы сделаем так, чтобы без регистрации нельзя было просто зайти в программу. В качестве самой программы мы возьме самую крутую нашу прогу - Hello World :)

Надо сделать так, чтобы программа при входе в нее проверяла, зарегистрировался ли пользователь, и если нет то просила его об этом, а если да, то выводила его на программу -Hello World. Самое простое решение - записывать сведения о регистрации в файл, к которому у пользователя не будет доступа ,он его просто не должен найти (этого можно достичь спрятав файл куда-нибудь в Windows, но наша программа будет записывать его в корневой каталог диска C). В файле r.txt будет записанно - зарегистрированна ли программа.

Нам нужно чтобы у каждой программы был свой серийный номер, то есть на каждом компьютере он был разным, но при каждом запуске программы на одном компьютере - одинаковым. Опять же самое простое решение - генерировать код и записывать его в файл. При этом нужно не перезаписывать старый номер, а записывать его только тогда, когда старого нет - то есть при первом запуске программы на компьютере.

Далее - процесс регистрации. Ну это уже зависит не от программы. Вы просто должны каким-либо образом отдать юзеру код - который соответствует его серийному номеру. Например по e-mail, по телефону или как-нибудь еще. Юзер сообщает вам серийный номер, а вы сообщаете код (например после того как он заплатит).

Шаг №2 - Визуальное проектирование

У вас в проекте должно быть 2 формы : одна - программа Hello World, а вторая - регистрация. Сначала сделаем самое легкое - программу Hello World , в этой статье я не буду говорить как это делать, это все описанно в первой программе только скажу, что имя формы у меня в программе - prog

Далее делаем форму с именем frmSn. На ней должны быть такие элементы :

Этикетка (Label) : Caption - Серийный номер
Этикетка (Label) : Caption - Регистрационный код
Текстовое поле (TextBox) : Свойство Text должно быть пустым, имя - txtSn
Текстовое поле (TextBox) : Свойство Text должно быть пустым, имя - txtRc
Кнопка (CommandButton) : Имя - Command1, Caption - Регистрировать
Кнопка (CommandButton) : Имя - Command2, Caption - О регистрации
Кнопка (CommandButton) : Имя - Command2, Caption - Выход

В результате всего этого у вас должно получится примерно так :

Шаг №3- Программирование в коде

Это самый трудный шаг во всей программе. Но отсюда можно получить очень много интересных сведений для себя. В разделе глобальных объявлений пишите :

Option Explicit - заставляем объявлять все переменные

Dim sn as Long - Объявляем "большую" переменнубю sn - это серийный номер

Dim rc as Long - Объявляем "большую" переменнубю rc - это регистрационный код

Dim result as Long - это "служебная" переменная потом поймете для чего она нам нужна

Dim result2 as Single - это также "служебная" переменная

Dim reg As String - Это регистрация - если зарегистрированно то она равна 1 если нет - то 0

При начале работы программа первым делом должна проверить - зарегистрированна ли она. Пишем в разделе Form_Load( )

If Dir("c:\r.txt") = "" Then - Проверяем - есть ли файл r.txt на диске С и если нету тогда...

reg = 0 - Придаем регистрации значение - 0 (незарегистрированно)

Open "c:\r.txt" For Output As #2 -Открываем этот файл для записи

Print #2, reg -Записываем в него переменную reg

Close #2 -Закрываем его

Else -Если на диске С есть файл r.txt

Open "c:\r.txt" For Input As #2 -Открываем его для вытаскивания из него данных

Input #2, reg -Вытаскиваем из него переменную reg

Close #2 -Закрываем его

If reg = "1" Then -Если зарегистрированно то

prog.Show -Показываем форму prog

frmsn.Hide - Скрываем эту форму

End If - Заканчиваем проверку регистрации

End If - Заканчиваем проверку наличия файла

Дальше


Серийный номер - продолжение

Дальше программа должна проверить : а в первый ли раз ее запускают, и если в первый, то сгенерировать серийный номер , показать его и записать в свою директорию в файл с именем sn.

If Dir("sn") = "" Then -Если нет файла sn - то есть программа запущена в первый раз то...

sn = Int(Rnd * 2000000) -Генерируем слуйчайный серийный номер

txtSN = sn -Записываем его в TextBox

Open "sn" For Output As #1 -Открываем файл sn для записи

Print #1, sn -Записываем в него переменную sn

Close #1 -Закрываем файл

Else -Иначе - то есть если файл sn - есть

Open "sn" For Input As #1 -Открываем файл sn для вынимания из него данных

Input #1, sn -Вытаскиваем из него переменную sn

Close #1 -Закрываем файл

txtSN = sn -Записываем серийный номер в TextBox

End If

Поздравляю половину программы вы уже сделали. Осталось совсем чуть-чуть. Но теперь и начинается самое интересное. Дело в том, что нужно проверит соответствие серийного номера - регистрационному коду. А так как номера у нас все время разные - то для их взаимосвязи нужна какая-либо формула. Например код должен быть равен номер поделить на 2. Но такой код быстро раскусят, найдут эту формулу. Итак : формула будет такая , делим номер на 2, округляем результат , и прибавляем 19 -это и будет код. Конечно такой код раскустить очень легко. Но вы можете придумать свой код, который не раскусят так быстро ( любую защиту можно обойти ). Итак, пишем в коде кнопки регистрация :

On Error GoTo 1 -При ошибке - идти на метку 1

rc = txtRC -Придаем значение переменной rc

result = Int (sn / 2) - Делим серийный номер на 2

result = result + 19 - Прибавляем к результату 19

If rc = result Then - Если получившееся число - целое то...

MsgBox "Спасибо за регистрацию", vbExclamation

reg = "1" - Регистрация равна 1 - зарегистрированно

Open "c:\r.txt" For Output As #2 -Открываем файл r.txt для записи

Print #2, reg -Записываем в него переменную reg

Close #2 -Закрываем его

prog.Show - Показываем форму prog

frmsn.Hide - Прячем форму frmsn

Else 'Иначе, то есть если результат не целое число

MsgBox "Извините, но код неверен", vbCritical

End If- Заканчиваем проверку

Exit Sub -Нужно для того, чтобы программа не пошла на метку 1

1 - Метка 1

MsgBox "Неправильно введен регистрационный код", vbCritical

Все.Программа готова, но нужно еще сделать несколько мелочей. В коде кнопки "О регистрации" напишите :

MsgBox "Для регистрации вы должны написать по адресу timurkar@mail.ru и указать ваш серийный номер", vbInformation - вместо timurkar@mail.ru напишите свой адрес или что то типа этого

В коде кнопки "Выход" - напишите

End

Шаг №4 - Подведение итогов

Вы написали программу, но теперь вы сами должны знать как подобрать код к номеру. В общем то формула у вас есть, если хотите напишите программку подбирающую коды к серийным номерам (но никому не давайте), а если хотите сами подбирайте. Если вам уж очень лень писать подборщик - можете взять его отсюда . Если программа не работает, то вы можете взять исходники и покопаться в них. Желаю удачи.


Отсчет

Эта программа называется отсчет потому что она будет отсчитывать числа от 10 до 0. В ее описании я попробую продемонстрировать вам работу с таймером в Visual Basic. Уметь работать с этим элементом очень важно любому программисту.

Шаг №1 - Постановка задачи

Итак, что же будет делать наша программа? Она будет отсчитывать от 10 до 0, а потом заканчивать свою работу Иначе говоря на этикетке (Label) с самого начала будет цифра 10 и через каждую секунду это число будет уменьшаться на 1. Приступим к работе.

Шаг №2 - Визуальное проектирование

Теперь как всегда приступим к визуальному проектированию.

У вас на форме должны быть всего 2 элемента : этикетка и , естественно, таймер.
Можете еще поместить кнопку выход, но я думаю она вам не понадобится, так как программа должна выходить сама а можно просто нажать на крестик вверху формы.

У этикетки в параметре Caption сотрите все, там должнно быть пусто.

Теперь приступим к работе с таймером:
Для того чтобы таймер работал, ему нужен программный код, то есть что он будет делать, но об этом далее. Еще таймеру нужны такие параметры, как Интервал(Interval) и состояние(Enabled) - включен(True) или выключен(False), если состояние - включен, то таймер будет через каждый интервальный отрезок времени выполнять свой программный код.Интервал указывается в миллисекундах , то есть если вы хотите чтобы таймер выполнял свой код через каждую секунду, то вам нужно будет поставить интервал равным 1000.

Это нам и надо, поэтому поставьте у таймера Состояние-включен, а интервал 1000. Все, с визуальным проектированием покончено.

Шаг №3 -Программирование в коде

Теперь приступим к программированию в коде. Напишите в разделе глобальных объявлений :

Option explicit-объявляем переменные

Dim s As Integer-объявляем переменную s - котрая будет обозначать в программе количество оставщихся секунд

Напишите в коде формы, событие-Load, то есть действие при загрузке формы которое у нас происходит с загрузкой программы:

s=10 -придаем переменной s значение 10

Label1.Caption=Str(s)-придаем параметру Caption элемента Label1 текстовое значение переменной s (так как параметру Caption можно придать только текстовое значение, а переменная s у нас - Integer - целое число)

А теперь перейдите к коду таймера (заметьте, что у таймера есть только одно событие- таймер) и напишите там :

s=s-1-отнимаем от значения переменной s единичку, то есть уменьшаем количество оставшихся секунд на 1

Label1.Caption=Str(s)-опять придаем параметру Caption элемента Label1 текстовое значение переменной s

If s=0 Then End-если переменная s равна 0 (то есть секунд больше не осталось), то заканчиваем программу.

Шаг №4 - Подведение итогов

Запустите и проверьте программу. Если все работает то поэкспериментируйте с программой, попробуйте поменять начальное значение переменной s. Если же что то не работает то вы можете посмотреть исходники.


Угадай число

Наверное каждый, кто хочет научиться программировать мечтает написать свою игру. С графикой, с 3d, со звуком и все такое. В этой статье мы с вами напишем игру, без всего этого, но зато с четкой логикой и достаточной увлекательностью.

Шаг №1 - Постановка задачи

Что же должна делать эта программа, иначе говоря, какова же цель игры ? Она состоит в том, что компьютер загадывает число от 1 до 100, вы должны его угадать, вводя число в текстовое поле, а копьютер должен говорить, больше это число чем загаданное, или меньше и вести счет попыткам.Если же вы угадаете число то компьютер должен сказать вам об этом и сказать, за сколько попыток вы выиграли.

Шаг №2 - Визуальное проектирование

Вторым шагом будет визуальное проектирование программы.На форме должны быть такие элементы :

Кнопка(Button)-с Caption'oм - Загадать и Name - Command1
Кнопка(Button)-с Caption'oм - Проверить и Name - Command2
Кнопка(Button)-с Caption'oм - Выход и Name - Command3
Текстовое поле (TextBox) - без текста и Name - Text1

В результате должно получиться вот так :

Все, с визуальным проектированием покончено.

Шаг №3 - Программирование в коде

Дальше идет самое интересное, но и самое сложное место в нашей программе - программирование в коде.
Войдите в раздел глобальных объявлений и напишите там :

Option Explicit - объявляем переменные

Dim a as Integer - Объявляем числовую переменную a - эта переменная будет числом которое загадает компьютер.

Dim b as Integer - Объявляем числовую переменную zc - эта переменная будет числом которое введет пользователь.

Dim c as Integer - Объявляем числовую переменную c - это будет число попыток

Теперь войдите в код кнопки Загадать и напишите там :

Randomize Timer - это выражение нужно для того , чтобы включить генератор случайных чисел. Вы можете его не писать, но тогда числа будут не совсем случайными. То есть при каждом запуске программы это будет хоть и случайное , но одно и тоже число.

a = Int(Rnd*100) - Переменная а равна целому числу от 1 до 100 (Int -целое, Rnd*100-от 1 до 100)

MsgBox "Число от 1 до 100 загаданно",vbExclamation,"Готово"-Выдаем сообщение с заголовком -"Готово", текстом -"Число от 1 до 100 загаданно", и значком с восклицательным знаком.

А теперь войдите в код кнопки Проверить и напишите там :

If a=0 then Msgbox "Сначала нужно загадать число",vbCritical,"Ошибка" : Exit Sub - Если загаданное число равно 0, то есть оно не загаданно, то выдаем сообщение об этом и выходим из процедуры ( то есть дальнейший код не выполняется)

If Not IsNumeric (Text1.Text) Then - если параметр Text элемента Text1 не число, то ...

Msgbox "Это не число",vbCritical,"Ошибка"-...Выдаем сообщение об этом

Exit Sub - Выходим из процедуры

Else - Иначе, то есть если параметр Text элемента Text1 число, то ...

b=Text1.Text - Переменная b равна параметру Text элемента Text1

If b>100 or b<1 then Msgbox "Это не число от 1 до 100" : Exit Sub- если переменная b больше 100 или меньше 1 то выдать сообщение об этом и выйти из процедуры.

If a>b then msgbox "Загаданное число больше" : c=c+1 - если переменная a (загаданное число) больше переменной b (введенное число) то сообщить об этом и к переменной c (число попыток) прибавить 1.

If a<b then msgbox "Загаданное число меньше" : c=c+1 - если переменная a (загаданное число) меньше переменной b (введенное число) то сообщить об этом и к переменной c (число попыток) прибавить 1.

If a=b then Msgbox "Вы угадали число за "+Str(c+1)+ " попыток" ,vbExclamation,"Победа":c=0:a=0 - Если переменная а равна переменной b то сообщить об этом , обнулить число попыток, обнулить загаданное число.С+1 здесь сделано потому, что отсчет числа попыток начинается не с одного, а с нуля)

End if - Закончить условие которое началось в строке "If Not IsNumeric..."

Ну и в код кнопки выход впишите :

End

В результате всего этого у вас должна получится вот такая программа :

Option Explicit
Dim a As Integer
Dim b As Integer
Dim c As Integer

Private Sub Command1_Click()
Randomize Timer
a = Int(Rnd * 100)
MsgBox "Число от 1 до 100 загаданно", vbExclamation, "Готово"
End Sub

Private Sub Command2_Click()
If a = 0 Then MsgBox "Сначала загадайте число", vbCritical, "Ошибка": Exit Sub
If Not IsNumeric(Text1.Text) Then
MsgBox "Это не число", vbCritical, "Ошибка"
Exit Sub
Else
b = Text1.Text
If b > a Then MsgBox "Загаданное число меньше", vbInformation, "Нет": c = c + 1
If b < a Then MsgBox "Загаданное число больше", vbInformation, "Нет": c = c + 1
If b = a Then MsgBox "Ура.Вы угадали число за " + Str(c+1) + " попыток", vbExclamation, "Победа!": a = 0: c = 0
End If
End Sub

Private Sub Command3_Click()
End
End Sub

Шаг №4 - Подведение итогов

Проверьте программу. Попробуйте выиграть. Ну как , нравиться ? Если у вас все получилось то поздравляю - вы можете с гордостью говорить друзьям, что написали игру.Если же что - то не работает то можете попробовать разобраться в исходниках.


Просмотрщик рисунков

Программа “Просмотр рисунков”

Программа “Просмотр рисунков” предназначена для просматривания графических изображений форматов BMP, DIB, JPG, GIF(не анимированных), ICO, CUR, WMF, EMF. Выбор рисунка будет осуществляться с помощью листа с именами файлов этих типов. При просмотре изображение будет растягиваться или сжиматься до размеров поля просмотра.
Итак, начнем…

Шаг №1. Визуальная реализация программы.
Поместите на форму следующие элементы с данными свойствами:

Форма
Name = frmViewer
BorderStyle = Fixed Single
MinButton = True
Caption = “Просмотр рисунков”

ImageBox.
Name = imgImage
BorderStyle = Fixed Single
Stretch = True

DriveListBox.
Name = lstDrive

DirListBox.
Name = lstDir

FileListBox.
Name = lstFile
Pattern = *.bmp;*.dib;*.gif;*.jpg;*.ico;*.cur;*.wmf;*.emf

Ну вот. Все. Ваша форма должна выглядеть примерно вот так:

Теперь немного объясню. В свойствах элемента Image Вы указали Stretch = True. Этим Вы сказали, что Image будет подгонять рисунок под границы поля. Если сможете, сделайте так, чтобы можно было просматривать рисунок в отдельном окне в полный рост. В свойствах элемента FileListBox Вы указали Pattern = *.bmp;*.dib;*.gif;*.jpg;*.ico;*.cur;*.wmf;*.emf. Так Вы указали, какие типы файлов будут отображаться в этом листе. Внимание! Типы файлов записываются через точку с запятой и БЕЗ пробела!

Шаг №2. Написание программного кода
Напишем программный код для этой программы. Я не буду слово в слово пояснять смысл. Просто дам программный код, а потом объясню самые сложные места.
Итак вуаля:

Private Sub Form_Load() - Делаем так, чтобы при загрузке формы
‘устанавливалась маска для листа с файлами
lstFile.Pattern = "*.BMP;*.DIB;*.GIF;*.jpg;*.ico;*.cur;*.wmf;*.emf"
End Sub

Private Sub lstDir_Change()
‘Путь листа с файлами равен пути
‘листа с директориями
lstFile.Path = lstDir.Path
End Sub

Private Sub lstDrive_Change()
‘Диск листа с директориями равен диску
‘листа с дисками
lstDir.Path = lstDrive.Drive
End Sub

Private Sub lstFile_Click()
'b = Пути к файлам
b = lstDir.Path
‘Если путь заканчивается слэшем тогда
If Right(b, 1) = "\" Then
a = lstDir.Path + lstFile.filename
‘А если нет тогда
Else
a = lstDir.Path + "\" + lstFile.filename
End If
‘Загружаем картинку из файла с
‘путем “a”
imgImage = LoadPicture(a)
End Sub

Private Sub lstFile_KeyPress(KeyAscii As Integer)
‘То же самое для нажатия клавиатуры
b = lstDir.Path
If Right(b, 1) = "\" Then
a = lstDir.Path + lstFile.filename
Else
a = lstDir.Path + "\" + lstFile.filename
End If
imgImage = LoadPicture(a)
End Sub

Я не буду объяснять весь код, лишь самые непонятные части.
Свойство Pattern y lstFile мы меняли, чтобы были видны только файлы с графическими изображениями.
Так, как часто Вы пользуетесь клавиатурой для выбора файла, то мы поместили одинаковый программный код в два события листа с файлами: для нажатия мыши и для нажатий клавиатуры. Этот код можно было записать только однажды, используя вызов процедуры, но о процедурах будет рассказано позже.
Оператор Right мы использовали для определения правого символа пути к директории с изображениями. В скобках сначала стоит переменная, содержащая текст, а за ней стоит число, указывающее количество проверяемых символов. Так, если переменная содержит такой текст:
Test “Right”
То оператор
Right(переменная, 7)
Возвратит Вам слово “Right”.
Мы использовали такую процедуру потому, что если Ваш путь - корневой каталог, то последний символ его будет слэш (C:\). А если не корневой каталог, то слэша не будет и может возникнуть ошибка (C:\DOS). Поэтому важно присоединять слэш к пути, если его там нет.
Оператор LoadPicture(путь) загружает картинку в элемент Image или Picture. Внимание! Ошибочно думать, что загрузить картинку можно подобным образом:
Image1.Picture = C:\Windows\Колечки.bmp
Так ничего не получится.
На этом создание программы подходит к концу. Вы можете ее усовершенствовать: добавить лейбл, в котором будет отображаться путь к файлу; добавить панель “О программе…”, в которой будет написано, что Вы создали эту программу; можете дополнить программу MDI-формой, чтобы открывать рисунок во весь экран. Много чего можно сделать с этой программой.