Text
                    Программные
решения
для

Инженеров
и

Ученых
•••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••
43021_FM.indd 1
9/10/07 10:10:34 УТРА
43021_FM.indd 2
9/10/07 10:10:34 УТРА
CRC Press является подразделением
Taylor & Francis Group, занимающейся информационным бизнесом
Бока-Ратон, Лондон, Нью-Йорк

Программные
решения
для


Инженеров и Ученых Хулио Санчес Мария П. Кантон ••••••••••••••••••••••••••••••• ••••••••••••••••••••••••••••••• 43021_FM.indd 3 9/10/07 10:10:34 УТРА CRC Press Группа Тейлора и Фрэнсиса 6000 Broken Sound Parkway NW, люкс 300 Бока-Ратон, Флорида 33487-2742 © 2008 Taylor & Francis Group, LLC CRC Press является отпечатком Taylor & Francis Group, информационного бизнеса, не претендующего на оригинальные работы правительства США Напечатано в Соединенных Штатах Америки на бескислотной бумаге 10 9 8 7 6 5 4 3 2 1 Международный стандартный номер книги-13: 978-1-4200-4302-0 (Твердый переплет) Эта книга содержит информацию, полученную из достоверных и высоко оцененных источников. Перепечатанный материал цитируется с разрешения автора и указываются источники. Перечислены самые разнообразные ссылки. Были предприняты разумные усилия для публикации надежных данных и информации, но автор и издатель не могут взять на себя ответственность за действительность всех материалов или за последствия их использования. Никакая часть этой книги не может быть перепечатана, воспроизведена, передана или использована в любой форме любым электронным, механическим или другими способами, известными в настоящее время или изобретенными в будущем, включая фотокопирование, микрофильмирование и запись, или в любой информационной системе хранения или поиска информации, без письменного разрешения издателей. Для получения разрешения на копирование или электронное использование материалов из этой работы, пожалуйста, зайдите на www.copyright.com (http:// www.copyright.com/) или свяжитесь с Центром защиты авторских прав, Inc. (CCC) 222 Розвуд Драйв, Дэнверс, Массачусетс, 01923, 978-750-8400. CCC - это некоммерческая организация, которая предоставляет лицензии и регистрацию для различных пользователей. Для организаций, которые получили лицензию на ксерокопирование от CCC, была организована отдельная система оплаты. Уведомление о товарном знаке: Названия продуктов или корпораций могут быть товарными знаками или зарегистрированными товарными
знаками и используются только для идентификации и пояснения без намерения нарушить. Каталогизация данных Библиотеки Конгресса при публикации Sanchez, Julio, 1938Программные решения для инженеров и ученых / авторы Хулио Санчес и Мария П. Кантон. п. см. Включает библиографические ссылки и указатель. ISBN 978-1-4200-4302-0 (др. бумага) 1. Инженерия - обработка данных. 2. Наука - обработка данных. И. Кантон, Мария П. II. Название. TA345.S31535 2008 620’.0285--dc22 2007034749 Посетите веб-сайт Taylor & Francis по адресу http://www.taylorandfrancis.com и веб-сайт CRC Press по адресу http://www.crcpress.com 43021_FM.indd 4 9/10/07 10:10:34 УТРА Содержание Предисловие xxiii ЧАСТЬ I — МЕТОДЫ И КОД Глава 1 — Компьютерные системы счисления Краткое содержание главы 3 1.0 Подсчет 3 1.0.1 Система подсчета 3 1.0.2 Римские цифры 4 1.1 Истоки нашей системы счисления 5 1.1.1 Системы счисления для цифровой электроники 6 1.1.2 Позиционные характеристики 7 1.1.3 Основание 7 1.2 Типы чисел 8 1.2.1 Целые числа 8 1.2.2 Числа со знаком 8 1.2.3 Рациональные и иррациональные числа 8 1.2.4 Действительные и комплексные числа 9 1.3 Представления радиусов 10 1.3.1 Десятичные и двоичные числа 10 1.3.2 Восьмеричные и шестнадцатеричные числа 11 1.4 Общая теория счета 12 1.5 Процедуры преобразования языка ассемблера 14 1.5.1 Преобразование двоичного кода в ASCII-десятичный 15
1.5.2 Преобразование двоичного кода в шестнадцатеричный 18 1.5.3 Преобразование десятичной системы счисления в двоичную 19 1.6 Процедуры преобразования C ++ 23 1.6.1 Преобразование двоичных файлов C ++ в ASCII 23 1.6.2 Преобразования C ++ ASCII в двоичный код 25 Глава 2 — Числовые данные в памяти Краткое содержание главы 31 2.0 Электронно-цифровые машины 31 2.1 Хранение числовых данных 32 2.1.1. Размер слова 32 2.2 Целочисленные кодировки 33 2.2.1 Представление знаковой величины 34 2.2.2 Представление дополнения к основанию 35 2.3 Кодирование дробных чисел 39 2.3.1 Представления с фиксированной точкой 40 v 2.3.2 Представления с плавающей запятой 41 2.4 Стандартизированные кодировки с плавающей запятой 42 2.4.1 Единый формат ANSI/IEEE 754 42 2.4.2 Показатель степени с плавающей запятой в едином формате ANSI/IEEE 754 43 2.4.3 Значение с плавающей запятой в едином формате ANSI/ IEEE 754 44 2.4.4 Декодирование чисел с плавающей запятой 45 2.5 Десятичные дроби в двоичном коде (BCD) 49 2.5.1 Двоичный код с плавающей запятой 49 2.6 Преобразования BCD 51 2.6.1 Функции преобразования BCD 51 Глава 3 — Машинная арифметика Краткое содержание главы 55 3.0 Микропроцессоры Intel 55 3.0.1 Флаги процессора 56 3.1 Логические инструкции 57 3.1.1 Логично И 58 3.1.2 Логическое ИЛИ 59 3.1.3 Логический XOR
59 3.1.4 Логически НЕ 60 3.2 Инструкции по арифметике 60 3.2.1 Арифметика со знаком и без знака 60 3.2.2 Операции с десятичными числами 61 3.3 Вспомогательные инструкции и инструкции по обработке битов 63 3.3.1 Инструкции по битовому сдвигу и повороту 63 Инструкции по сдвигу битов 63 Инструкции по повороту бита 66 Инструкции по переключению передач двойной точности 67 Режимы адресации сдвига и поворота 68 3.3.2 Инструкции по сравнению, битовому сканированию и тестированию битов 68 Условные переходы со знаком и без знака 70 3.3.3 Инструкции по увеличению, уменьшению и расширению знака 71 3.3.4 Фирменные инструкции 486 и Pentium 72 BSWAP 72 XADD 73 CMPXCHG и CMPXCHG8B 74 3.4 Идентификация процессора 74 Глава 4 — Высокоточная арифметика Краткое содержание главы 79 4.0 Приложения BCD-арифметики 79 4.0.1 Стандарт ANSI/IEEE 854 80 4.1 Алгоритмы для BCD-арифметики 81 4.2 Сложение BCD с плавающей запятой 82 4.3 Вычитание BCD с плавающей запятой 82 4.4 Умножение BCD с плавающей запятой 83 4.5 Деление BCD с плавающей запятой 85 4.6 Арифметические функции C ++ BCD 86 4.6 Высокоточная BCD-арифметика 90 vi Оглавление Глава 5 — Аппаратные средства с плавающей запятой Краткое содержание главы 95 5.0 Математический сопроцессор
95 5.1 Математические единицы Intel 96 5.1.1 Приложения к математическим единицам 97 5.1.2 Ограничения математических единиц 98 5.1.3 Интерфейс процессора/сопроцессора 99 5.1.4 Версии математических единиц 100 8087 101 80287 101 80387 102 5.1.5 Числовая единица в 486 и процессоре Pentium 102 5.2 Обнаружение и идентификация математической единицы 102 5.3 Стандарт ANSI/IEEE 754 106 5.3.1 Числовое кодирование данных 107 5.3.2 Округление 109 5.3.3 Интервальная арифметика 110 5.3.4 Обращение с бесконечностью 110 5.3.5 Не число (NaN) 112 Сигнализация и тихие NaNs 112 5.3.6 Исключения 113 Исключение недопустимой операции 113 Исключение деления на ноль 114 Исключение переполнения 114




b b = × = ∑ 0 10 для 0 9( целое число) n я я я я я d d d = × ≤ ≤ ∑ Рациональные числа - это числа, выраженные в виде отношения двух целых чисел, например 1/2, 2/3, 5/248. Обратите внимание, что такое использование слова рациональный относится к математическому понятию отношения, а не к разуму. Знаменатель рационального числа выражает количество потенциальных частей. В этом смысле 2/5 указывает на две из пяти возможных частей. Нет никаких причин, по которым число ber 1 нельзя использовать для указания количества потенциальных деталей, например 2/1, 128/1. В этом случае соотношение x/1 указывает на x элементов неделимой части. Следовательно, из этого следует, что x/1 = x. Подразумевается, что набор рациональных чисел включает в себя целые числа, поскольку целое число может быть выражено в виде отношения с использованием единицы, обозначаемой тором. Но не все нецелые числа могут быть записаны как точное соотношение двух целых чисел. Открытие первого иррационального числа обычно связывают с исследованием прямоугольного треугольника греческим математиком Пифагором (приблизительно 600 год до н.э.). Здесь опять же иррациональное относится к не-соотношению, а не к “неразумному”. Теорема Питагора утверждает, что в любом прямоугольном треугольнике квадрат самой длинной стороны (hyпотенциуза) равен сумме квадратов двух других сторон. Для этого треугольника теорема Пифагора утверждает, что a 2 +b 2 =c 2 2=c 2 2=c*c c= √2 Следовательно, длина гипотенузы в прямоугольном треугольнике с единичными сторонами является числом, которое при умножении само на себя дает 2. Это число (приблизительно 1,414213562) не может быть выражено как точное отношение двух целых чисел. Другими иррациональными числами являются
√3, √5, а также математические константы π и e . 1.2.4 Действительные и комплексные числа Набор чисел, включающий в себя натуральные числа, целые числа, а также рациональные и иррациональные числа, называется действительными числами........... Большинство распространенных математических задач решаются с использованием действительных чисел. Однако при исследовании квадратов и корней мы замечаем следующую интересную особенность Компьютерные системы счисления 9 a=1 b=1 c +2 * +2 = 4 -2 * -2 = 4 Поскольку квадрат положительного числа положителен, а квадрат отрицательного числа также положителен, не может быть действительного числа, квадрат которого отрицателен. Следовательно, √-2 и √-4 не существуют в системе вещественных чисел. Но математики 18 века расширили систему счисления, включив в нее операции с корнями отрицательных чисел. Они определили воображаемую единицу как я 2 = -1 или, я = √-1 Это приводит к возникновению нового набора чисел, называемых комплексными числами, которые состоят из действительной части и мнимой части. Одно из применений комплексных чисел заключается в нахождении решения квадратного уравнения. Комплексные числа также полезны в векторном анализе, графике и при решении многих инженерных, научных и математических задач. 1.3 Представления по радиусам Радиус системы счисления - это количество символов в наборе, включая ноль. Таким образом, основание десятичной системы равно 10, а основание двоичной системы равно 2. Для некоторых приложений десятичная система не идеальна. Вычислительные машины основаны на электронных схемах, которые могут находиться в одном из двух стабильных состояний. Следовательно, система счисления, основанная на двух символах, лучше подходит для работы с компьютером, поскольку каждое состояние может быть представлено цифрой. 1.3.1 Десятичные и двоичные числа В двоичной системе счисления используются только два символа: 1 и 0. Это простейший из возможных наборов символов, с помощью которых мы можем считать и выполнять арифметические действия. Большинство трудностей в изучении и использовании двоичной системы является результатом этой простоты. На рис. уре 1.1 показаны шестнадцать групп по четыре электронные ячейки в каждой во всех возможных комбинациях двух состояний. Рисунок 1.1 Электронные ячейки и двоичные числа 10 Глава 1 0000 0111 1000 1001 1011 1100 1101 1110 1111
1010 0100 0101 0110 0001 0010 0011 Обратите внимание, что представление в двоичных числах соответствует физическому состоянию каждой электронной ячейки. Если мы представим каждый элемент в виде миниатюрной лампочки, то двоичное число 1 можно использовать для представления состояния заряженного элемента (горит), а двоичное конечное число 0 - для представления состояния незаряженного элемента (свет ВЫКЛЮЧЕН). 1.3.2 Восьмеричные и шестнадцатеричные числа Двоичные числа удобны в цифровой электронике; однако одним из их недостатков является количество символов, необходимых для кодирования большого значения. Например, число 9134 представлено четырьмя десятичными разрядами. Однако двоичный эквивалент 10001110101110 требует 14 цифр. По той же причине большие двоичные числа трудно запомнить. Одним из возможных способов компенсации этих ограничений двоичных чисел является использование отдельных символов для представления групп двоичных цифр. Например, группа из трех двоичных чисел допускает восемь возможных комбинаций. В этом случае мы можем использовать десятичные цифры от 0 до 7 для представления каждой возможной комбинации из трех двоичных цифр. Эта группировка из трех двоичных цифр приводит к восьмеричной системе счисления, показанной в следующей таблице: двоичная система счисления восьмеричная 000 0 001 1 010 2 011 3 100 4 101 5 110 6 111 7 Восьмеричная кодировка служит сокращенным представлением для групп из 3-значных двузначных чисел. Представьте себе цифро-электронное устройство, построенное из элементов с двумя состояниями, сгруппированных в количестве, кратном 3. В этом случае восьмеричные числа обеспечивают удобный способ представления двоичных данных, хранящихся в этом устройстве. Например, если память устройства состояла из групп по двенадцать ячеек, для представления любого сохраненного значения можно было использовать четыре восьмеричные цифры. Шестнадцатеричные числа (основание 16) используются для представления значений, закодированных в четырех двоичных разрядах. Поскольку десятичных разрядов всего десять, шестнадцатеричная система содержит в рядах первые шесть букв алфавита (A, B, C, D, E и F). В результате получается набор из шестнадцати символов, следующий: 0123456789ABCDEF Компьютерные системы счисления 11 Большинство современных компьютеров спроектированы с ячейками памяти, регистрами и путями передачи данных, кратными четырем двоичным разрядам. В таблице 1.2 перечислены некоторые распространенные единицы хранения в памяти. Таблица 1.2 Единицы хранения в памяти ЕДИНИЦА ИЗМЕРЕНИЯ БИТЫ ШЕСТНАДЦАТЕРИЧНЫЕ ЦИФРЫ ДИАПАЗОН ШЕСТНАДЦАТЕРИЧНЫХ
Откусывать 4 1 от 0 до F Байт 8 2 от 0 до FF Слово 16 4 от 0 до FFFF Двойное слово 32 8 от 0 до FFFFFFFF Четырехсловие 64 16 от 0 до FFFFFFFFFFFFFFF В большинстве цифровых электронных устройств адресация памяти организована в виде чисел, кратных четырем двоичным разрядам. И здесь шестнадцатеричная система счисления обеспечивает удобный способ представления адресов. В таблице 1.3 перечислены некоторые распространенные единицы обработки памяти и их шестнадцатеричный и десятичный диапазоны. Таблица 1.3 Единицы адресации памяти ЕДИНИЦА ПУТЬ ПЕРЕДАЧИ ДАННЫХ ДИАПАЗОН АДРЕСОВ В БИТАХ ДЕСЯТИЧНЫЙ ШЕСТНАДЦАТЕРИЧНЫЙ 1 абзац 4 от 0 до 15 0-F 1 страница 8 от 0 до 255 0-FF 1 килобайт 16 от 0 до 65 535 0-FFFF 1 мегабайт 20 от 0 до 1 048 575 0-FFFFF 4 гигабайта 32 от 0 до 4 294 967 295 0-FFFFFFFF 1.4 Общая теория счета Математика берет свое начало в нашем интуитивном чувстве единства. Если вы не смогли Дистенразличают от одного объекта и два объекта, как характеристика отношения к имусвойствам самих объектов, вы не могли рассчитывать. Без счета не могло бы быть арифметики, алгебры или исчисления. Счет - это абстрактный процесс, который предполагает следующие допущения: 1 Счетчик использует набор символов, которые последовательно присваиваются подсчитываемым единицам . 2. В этом наборе должен быть первый символ. Процесс подсчета всегда начинается с этого
символа. 3. Остальные символы в наборе должны быть упорядочены; 2 следует за 1, 3 следует за 2 и так далее. 4. Набор символов должен быть конечным: в наборе есть последний символ. 12 Глава 1 Обычно подсчет продолжается после последнего символа набора. В противном случае мы могли бы насчитать только до девяти единиц в десятичной системе. Подсчет после последнего символа в наборе требует дополнительных правил. Одна схема подсчета, известная как позиционная система , выравнивает цифры в столбцах, которым присвоены разные веса. В этой схеме в качестве заменителя используется специальная цифра, называемая нулем . Подсчет в позиции, основанной на нуле, подразумевает следующее дополнительное правило: 5. Существует специальный символ, называемый нулем, который сам по себе не обозначает единиц измерения. Подсчет начинается с первого символа набора и продолжается по набору символов до последнего символа набора. В десятичной системе мы считаем 1 2 3 4 5 6 7 8 9 Мы можем продолжить отсчет, пройдя мимо последнего символа набора, используя свойства позиционной системы, основанной на нуле. Если вес столбца увеличивается справа налево (как это имеет место в наших общих системах), мы считаем дальше последнего символа в наборе, устанавливая самый правый столбец равным нулю, затем сдвигая столбец влево до следующего символа набора. Согласно этой схеме, число, следующее за 9, равно 10. Поскольку мы так хорошо знакомы с десятичным счетом, эти правила могут показаться тривиальными. Однако правила применимы к любой позиционной системе, основанной на нуле, такой как двоичная, восьмеричная или шестнадцатеричная. Подсчет в двоичном формате имеет ту особенность, что первый символ набора (1) также является последним символом набора. Правила подсчета могут быть применены к любой системе счисления, например: Первая единица представлена первым символом набора двоичным кодом десятичным кодом восьмеричным кодом шестнадцатеричным кодом 1 1 1 1 Мы продвигаемся по набору символов, пока не дойдем до последнего символа набора двоичный десятичный восьмеричный шестнадцатеричный 1 9 7 F На этом этапе мы поворачиваем крайний правый столбец к нулю и сдвигаем столбец влево до следующего или первого символа набора двоичный код десятичный код восьмеричный шестнадцатеричный 10 10 10 10 Обратите внимание, что в разных числовых базах одни и те же символы представляют разные
суммы. По этой причине бессмысленно говорить "десять двоичных, или десять восьмеричных, или десять шестнадцатеричных". Десять - это название, данное комбинации цифр 10 в десятичной системе счисления . Поскольку комбинации цифр в системах с другими основаниями не имеют имен, мы вынуждены использовать двоичную систему счисления "один-ноль", восьмеричную систему счисления "один-ноль" или шестнадцатеричную систему счисления "один-ноль". То же самое относится к десятичным обозначениям сотен, тысяч и их комбинаций. Компьютерные системы счисления 13 Точно так же неверно говорить "тысяча шестнадцатеричных чисел", поскольку слово "тысяча" подразумевает десятичную систему счисления. Подводя итог: подсчет в позиционной системе счисления, основанной на нуле, продолжается в самом правом столбце путем индексации по набору символов в соответствующем наборе. Когда крайний правый столбец находится на последнем символе набора, он изменяется на 0, а следующий столбец слева перемещается на следующий символ набора. Эта последовательность сброса и увеличения, которая выполняется, когда столбец достигает последнего символа набора, продолжается до тех пор, пока мы не достигнем столбца, который не находится на последнем символе набора. Если мы представим выпуклость знаком #, то процесс подсчета в десятичных числах можно описать следующим образом 1 .... первый символ набора 2 .... следующий символ набора 3 .... продолжайте движение по набору символов . . 9 .... до последнего символа набора # 10 .... установите в крайнем правом столбце значение нуля и увеличьте его В двоичной системе отсчет выполняется следующим образом 1 .... первый символ набора также является последним # 10 .... установите крайний правый столбец равным нулю и нажмите 11 .... продолжайте движение по набору символов до последнего # 0 .... сбросить самый правый столбец и выпуклость # 00 .... сбросьте следующий столбец и поднимите 100 .... столбец не на последнем символе набора 101 .... перейдите к самому правому столбцу # 0 .... сбросить самый правый и нажать 110 .... столбец не в последнем символе набора Обратите внимание, что когда все столбцы содержат последний символ из набора, подсчет может продолжаться только при открытии нового столбца слева. Также обратите внимание, что комбинация символов, которая следует за этим условием, одинакова во всех базисах, например, после двоичной системы счисления десятичной системы счисления восьмеричной системы счисления шестнадцатеричной системы счисления 111 999 777 FFF следующее число двоичное десятичное восьмеричное шестнадцатеричное 1000 1000 1000 1000 1.5 Процедуры преобразования языка ассемблера Мы используем десятичные числа в нашей повседневной жизни, потому что они осмысленно представляют обычные единицы, используемые в реальном мире. Утверждение о том, что определенное историческое событие
произошло в шестнадцатеричном году 7C6, дало бы мало информации среднему человеку. Однако в компьютерных системах, основанных на электронных ячейках с двумя состояниями, двоичные представления более удобны. Также обратите внимание, что шестнадцатеричные и восьмеричные числа являются удобным сокращением для представления групп двоичных цифр. 14 Глава 1 Рисунок 1.2 Пример преобразования двоичного кода в десятичный код ASCII Числовые преобразования между позиционными системами разных радиусов основаны на количестве символов в соответствующих наборах и на позиционном значении (весе) каждого столбца. Но методы, используемые для ручных преобразований, не всегда подходят для машинных преобразований, как мы увидим в следующих разделах. 1.5.1 Преобразование двоичного кода в ASCII-десятичный Чтобы вручную преобразовать двоичное число в его десятичный эквивалент, мы учитываем позиционный вес каждой двоичной цифры, как показано на рисунке 1.2. Таблица позиционных весов на рисунке 1.2 содержит десятичное значение каждого двоичного столбца. Эти веса являются степенями основания системы (2 в двоичной системе). В таблице значений цифр, также представленной на рис. 1.2, суммируются десятичные значения двоичных столбцов, содержащих цифру 1. Сумма весов всех однозначных чисел в операциии является десятичным эквивалентом двоичного числа. В этом случае 10010101 двоичный код = 149 десятичным. Метод, показанный на рис. 1.2, хотя и полезен при ручных преобразованиях, не является хорошим алгоритмом для компьютерных преобразований. Современные цифровые компьютеры содержат множество инструкций для выполнения целочисленного деления. Использование целочисленного деления на 10 обеспечивает более простое и эффективное преобразование. Остаток от деления решемал цифры результата, пока частное станет новое значение, которое будет разделено. Частное, равное 0, указывает на завершение преобразования. На рисунке 1.3 приведена блок-схема низкоуровневого преобразования двоичной системы счисления в десятичную. Алгоритм обработки, приведенный на рис. ур 1.3, может быть записан следующим образом: Компьютерные системы счисления 15 10010101 ТАБЛИЦА ЗНАЧЕНИЙ ЦИФР (цифра х вес) 1x1 = 1 1x4 = 4 1 x 16 = 16 1 x 128 = итого 128 149 ТАБЛИЦА ПОЗИЦИОННОГО ВЕСА (десятичные значения) 2 = 128 2 = 64 2 = 32 2
= 16 2 = 8 2 = 4 2 = 2 2 = 1 7 6 5 4 3 2 1 0 Рисунок 1.3 Блок-схема преобразования двоичного кода в десятичный код ASCII 1. Настройте и инициализируйте буфер для хранения десятичных цифр результата в формате ASCII. Установите указатель буфера на крайнюю правую позицию цифры результата. 2. Получите остаток от значения, разделенный на 10. 3. Добавьте 30H к оставшейся цифре, чтобы преобразовать в представление ASCII. 4. Сохраните оставшуюся цифру в буфере и проиндексируйте указатель буфера на предыдущую цифру. 5. Частное от деления на 10 становится новым двоичным значением. 6. Завершите процедуру преобразования, если коэффициент равен 0. В противном случае перейдите к шагу 2. ЗАПИСНАЯ КНИЖКА ПРОГРАММИСТА Набор символов ASCII был разработан таким образом, что десятичные цифры находятся в диапазоне от 30 до 39 часов. Преобразование двоичного кода в ASCII и ASCII-кода в двоичный код - это простой вопрос добавления или вычитания 30H. 16 Глава 1 НАЧАЛО КОНЕЦ ДА НЕТ УСТАНОВОЧНЫЙ БУФЕР ИНИЦИАЛИЗИРОВАТЬ УКАЗАТЕЛЬ БУФЕРА ДВОИЧНЫЙ / 10 ОСТАТОК + 30 ЧАСОВ = ЦИФРА ASCII ЦИФРА ASCII В БУФЕР УКАЗАТЕЛЯ НА СЛЕДУЮЩУЮ ЦИФРУ ЧАСТНОЕ = ДВОИЧНОЕ ЧАСТНОЕ = 0 ? I n c o d i n g a n 8 0 x 8 6 a s s e m b l y l a n g u a g e c o n v e r s i o n r o u t i n e f o r b inary-to-ASCII-decimal мы начинаем с создания буфера для хранения результата. Размер буфера зависит от максимального ожидаемого количества десятичных цифр ASCII. Например, если двоичный файл может содержать до 8 бит, то десятичный буфер ASCII должен быть способен хранить три символа, поскольку наибольшее значение будет равно 255 десятичным символам. По тому же принципу, если двоичный файл может содержать до 16 бит, то десятичный буфер ASCII должен быть способен хранить пять символов, поскольку в этом случае максимальное десятичное значение будет равно 65 535. Код сначала очищает буфер ASCII, вводя необходимые пустые символы. Этот шаг необходим, потому что преобразование заканчивается, как только появляется нулевое частное. Следовательно, если буфер не был предварительно очищен, результат может быть загрязнен мусором из предыдущего преобразования. ;************************************** ; определения для 32-разрядной плоской модели ;*************************************** .486
.МОДЕЛЬ плоская .КОД _BIN_TO_ASC10 Процедура ; Преобразования 16-разрядного двоичного числа в 5 десятичных ; цифр в формате ASCII; При вводе: ; AX = двоичное число ; EDI --> 5-байтовый буфер ASCII ; При выходе: ; Буфер содержит десятичные цифры ASCII ;***********************| ; очистить буфер ASCII | ;***********************| MOV ECX, 5 ; Пять цифр для очистки CLEAR_5: MOV БАЙТ PTR [EDI], 20 ЧАСОВ ; Четкая цифра ВКЛ EDI ; Указатель поворота ЦИКЛ CLEAR_5 ; Повторите для 5 цифр ДЕКАБРЬ EDI ; Настроить указатель буфера на цифру MOV ECX,10 ; Десятичный делитель в CX ;***********************| ; получаем десятичный код ASCII | ; цифра | ;***********************| ПОЛУЧАЕМ_ASC10: MOV DX, 0 ; Очистить для разделения слов РАЗДЕЛ CX ; Выполнить деление AX/CX ; Частное в AX, а остаток в DL ; Преобразовать десятичную дробь в десятичную в формате ASCII Добавить Продолжительность, 30 часов ; Добавьте 30 часов, чтобы привести к диапазону ASCII ;***********************| ; цифра магазина | ; указатель буфера перехода | ;***********************| MOV БАЙТ PTR [EDI], DL ; Сохранить цифру в буфере ДЕКАБРЬ EDI ; Указатель буфера на следующую цифру ; Примечание: двоичное частное оставляется в AX инструкцией DIV CX ;***********************| ; является ли частное = 0 ? |
;***********************| Компьютерные системы счисления 17 CMP AX,0 ; Проверка на окончание двоичного файла JNZ ПОЛУЧИТЬ_ASC10 ; Продолжить, если не 0 ;***********************| ; окончание преобразования | ;***********************| CLD RET _BIN_TO_ASC10 ENDP Рисунок 1.4 Пример преобразования двоичного кода в шестнадцатеричный ASCII 1.5.2 Преобразование двоичной системы в шестнадцатеричную Метод, описанный в разделе 1.5.1 для преобразования двоичного кода в десятичный ASCII, может быть адаптирован к другим радиусам путем представления позиционного веса каждой двоичной цифры в выбранной системе счисления. В случае преобразования двоичного кода в шестнадцатеричный код ASCII позиционный вес каждой двоичной цифры является шестнадцатеричным значением. На рисунке 1.4 показано преобразование двоичного значения 10010101 в шестнадцатеричное с использованием соответствующих позиционных весов. Процедура преобразования на языке ассемблера 80x86 из двоичного кода в шестнадцатеричныйASCIIдесятичный аналогична процедуре преобразования двоичного кода в десятичный ASCII-код (BIN_TO_ASC10), перечисленной в разделе 1.5.1. В случае преобразования в шестнадцатеричные цифры ASCII буфер должен содержать только четыре символа ASCII, поскольку 16-разрядный двоичный код не может превышать значения FFFFH. В любом случае делитель для получения цифр является основанием системы счисления. Следовательно, процедура преобразования двоичного кода в шестнадцатеричный использует значение 16. Следующая процедура преобразует двоичное число в шестнадцатеричное в формате ASCII. _BIN_TO_ASC16 ПРОДОЛЖЕНИЕ ; Процедура преобразования 16-разрядного двоичного числа в 4 ASCII ; шестнадцатеричные цифры 18 Глава 1 10010101 10010101 ТАБЛИЦА ЗНАЧЕНИЙ ЦИФР (цифра вес) 1 1Ч = 1Ч 1 4 ЧАСА = 4 ЧАСА 1 10Ч = 10Ч 1 80Ч = 80Ч всего 95Ч ТАБЛИЦА ПОЗИЦИОННОГО ВЕСА (шестнадцатеричные значения)
2 = 80 часов 2 = 40 часов 2 = 20 часов 2 = 10 часов 2 = 8 часов 2 = 4 часа 2 = 2 часа 2 = 1 час 7 6 5 4 3 2 1 0 ; ; При вводе: ; AX = двоичное число ; EDI --> 4-байтовый буфер ASCII ; При выходе: ; Буфер содержит десятичные цифры ASCII ;***********************| ; очистить буфер ASCII | ;***********************| MOV ECX, 4 ; Четыре цифры для очистки CLEAR_4: MOV БАЙТ PTR [EDI], 20 ЧАСОВ ; Четкая цифра ВКЛ EDI ; Указатель поворота ЦИКЛ CLEAR_4 ; Повторите для 4 цифр ДЕКАБРЬ EDI ; Установить указатель буфера на последнюю ; цифру MOV ECX,16 ; Шестнадцатеричный делитель в CX ;***********************| ; получить шестнадцатеричный код ASCII | ; цифра | ;***********************| ПОЛУЧАЕМ_ASC16: MOV DX, 0 ; Очистить для разделения слов РАЗДЕЛ CX ; Выполните деление AX / CX ; Частное в AX, остаток в DL ; Преобразуйте десятичную дробь в ASCII
; Проверьте диапазон цифр от 0 до 9 CMP DL,9 JA IS_LETTER ; Цифра - это шестнадцатеричная буква Добавить DL, 30 часов ; Добавьте 30 часов, чтобы привести к диапазону ASCII JMP ЦИФРА в ХРАНИЛИЩЕ ЭТО_ ПИСЬМО: Добавить ДЛ,55 ; Преобразовать в букву ASCII ;***********************| ; цифра магазина | ; указатель буфера перехода | ;***********************| ЦИФРА в ХРАНИЛИЩЕ: MOV БАЙТ PTR [EDI], DL ; Сохранить цифру в буфере ДЕКАБРЬ EDI ; Указатель буфера на следующую цифру ; Примечание: двоичное частное оставляется в AX инструкцией DIV CX ;***********************| ; является ли частное = 0 ? | ;***********************| CMP AX,0 ; Проверка на окончание двоичного файла JNZ GET_ASC16 ; Продолжить, если не 0 ;***********************| ; окончание преобразования | ;***********************| CLD RET _BIN_TO_ASC16 ENDP 1.5.3 Преобразование десятичной системы счисления в двоичную Преобразование десятичной системы счисления в двоичную может быть выполнено путем использования позиционных весов для нахождения двоичных 1-разрядных чисел, а затем вычитания этого позиционного веса из десятичного значения. Процесс показан на рисунке 1.5. Компьютерные системы счисления 19 Рисунок 1.5 Пример преобразования десятичной системы счисления в двоичную В примере с рисунком 1.5 мы начинаем с десятичного значения 149. Поскольку наивысшая степень 2, меньшая 149, равна 128, что соответствует биту 7, мы устанавливаем бит 7 и выполняем вычитание: 149 - 128 = 21 На данный момент наибольший позиционный вес, меньший 21, равен 16, что соответствует биту 4. Поэтому мы устанавливаем бит 4 и выполняем вычитание: 21 - 16 = 5 Оставшиеся шаги преобразования можно увидеть на рис. 1.5. Преобразование обработка завершается, когда результат вычитания равен 0. Предположим, что пользователь вводит числовое значение в виде строки из ASCII десятичных, восьмеричных или шестнадцатеричных цифр. Для того, чтобы процессор для выполнения симдям арифметические операции над этими числами, загрузить пользователя значения в машину РЭГ-
министрами или выделенных ячейках памяти. Однако методы, подходящие для ручного преобразования, не всегда являются хорошим компьютерным алгоритмом. На рисунке 1.6 показаны два алгоритма преобразования десятичной системы счисления в двоичную, которые подходят для машинного кодирования. Используя первый метод, показанный на рисунке 1.6, отдельные десятичные цифры умножаются на соответствующие им позиционные значения. Конечный результат получается путем сложения всех частичных произведений. Этот метод используется часто, однако его недостаток заключается в том, что во время каждой итерации используется разный множитель (1, 10, 100, 1000 и так далее). Второй метод на рисунке 1.6 начинается с десятичной цифры в формате ASCII старшего порядка. Вычисления состоят из умножения накопленного значения на 10. Первоначально это накопленное значение устанавливается равным 0. После умножения на 10 значение цифры добавляется к накопленному значению. Следующий алгоритм основан на втором методе на рисунке 1.6, показанном на следующей странице. 20 Глава 1 10010101 149 - 128 = 21 10000000 21 - 16 = 5 00010000 5-4=1 00000100 1-1=0 000000001 двоичный результат 10010101 ТАБЛИЦА ПОЗИЦИОННОГО ВЕСА (десятичные значения) 2 = 128 2 = 64 2 = 32 2 = 16 2 =8 2 =4 2 =2 2 =1 7 6 5 4 3 2 1 0 Рисунок 1.6 Машинное преобразование десятичного кода ASCII в двоичный. На рисунке 1.7 приведена блок-схема алгоритма преобразования. Рисунок 1.7 Блок-схема для преобразования ASCII в машинный регистр 1. Настройте и инициализируйте нулевым значением ячейку хранения для хранения значения, накопленного во время преобразования. Установите указатель на цифру ASCII самого высокого порядка. Компьютерные системы счисления 21 НАЧАЛО КОНЕЦ ДА НЕТ НАСТРОИТЬ НАКОПИТЕЛЬ
ИНИЦИАЛИЗИРОВАТЬ УКАЗАТЕЛЬ БУФЕРА ЦИФРА ASCII - 30 часов УКАЗАТЕЛЬ НА СЛЕДУЮЩУЮ ЦИФРУ НАКОПИТЕЛЬ X 10 АККУМУЛЯТОР + ЦИФРА ДЕЙСТВИТЕЛЬНАЯ ЦИФРА ? 3459 3459 9 1 = 9 5 10 = 50 4 100 = 400 3 1000 = 3000 двоичный код = 3459 0 10 + 3 = 3 3 10 + 4 = 34 34 10 + 5 = 345 345 10 + 9 = 3459 СПОСОБ № 1 СПОСОБ № 2 ДЕСЯТИЧНЫЕ ЦИФРЫ В формате ASCII ДЕСЯТИЧНЫЕ ЦИФРЫ ASCII 2. Проверить цифры ASCII значения в диапазоне от 0 до 9. Завершение процедуры, если цифра ASCII находится не в этом диапазоне. 3 Вычтите 30H из десятичной цифры ASCII. 4. Умножьте накопленную стоимость на 10. 5. Добавьте цифру к накопленному значению. 6. Увеличьте указатель до следующей цифры и перейдите к шагу 2. Указанная процедура, названная ASCII_TO_EDX, выполняет необходимый процесс для загрузки 5-значного ASCII-номера в регистр EDX. Процедура преобразования основана на блок-схеме, показанной на рис. 1.7. _ASCII_TO_EDX ПРОЦЕДУРА ; Преобразует ASCII-число в двоичное и сохраняет в регистре EDX ; ; При вводе: ; ESI > начало 10-значного буфера ASCII, содержащего десятичную дробь ; строка в диапазоне от 0 до 4 294 967 295 ; При выходе:
; EDX = двоичное число ; ; ;***********************| ; инициализировать регистры | ;***********************| MOV EDX, 0 ; Очистить регистр накопителя MOV ECX, 10 ; Введите множитель в ECX ;***********************| ; контрольная цифра для диапазона | ; от 0 до 9 | ;***********************| ЦИФРА_TO_ACC32: ДВИЖЕНИЕ AL,[ESI] ; Получить цифру ASCII CMP AL, ’0’ ; Тест на нижний предел JB ВЫХОД_ASC32 ; Выход, если меньше 0 CMP AL, ’9’ ; Тест на более высокий предел JA ВЫХОД_ASC32 ; Выход, если больше 9 ;***********************| ; Цифра ASCII в двоичном формате | ;***********************| SUB AL, 30 часов ; ASCII в десятичном формате ;***********************| ; аккумулятор x 10 | ; + цифра | ;***********************| MOV AH, 0 ; Очистить старший байт в AX толкать AX ; Сохранить цифру в стеке MOV EAX, EDX ; Предыдущий продукт для EAX MUL ECX ; Выполнить EAX = EAX * 10 ПЕРЕМЕЩЕНИЕ EDX, EAX ; Переместить изделие в аккумулятор MOV EAX, 0 ; Очистить 32 бита POP AX ; Восстановить десятичную цифру Добавить EDX, EAX ; Добавить цифру в накопитель ;***********************|
; указатель на следующую цифру | ;***********************| INC ESI ; Указатель поворота 22 Глава 1 JMP DIGIT_TO_ACC32 ; Продолжить ВЫХОД_ASC32: ПОВТОРНО _ASCII_TO_EDX ENDP Процедуры преобразования 1.6 C ++ Процедуры преобразования в языках высокого уровня более компактны, но менее эффективны, чем в ассемблере. Программисты на C и C ++ часто полагаются на библиотечные процедуры для выполнения этих преобразований. Однако иногда нам приходится выполнять преобразования вручную. В следующих разделах мы разрабатываем процедуры преобразования целых чисел C ++, эквивалентные ранее перечисленным низкоуровневым . Рассматриваются два типа подпрограмм: те, которые преобразуют двоичное конечное значение, хранящееся в примитивной переменной, в строку цифр ASCII, и те, которые преобразуют строку цифр ASCII в двоичное значение. 1.6.1 Преобразования двоичных файлов C ++ в ASCII Библиотечные функции C ++ часто выполняют преобразования. Например, когда вы используете один из классов stream для отображения примитива данных, соответствующая функция выполняет преобразование автоматически. Например: int aNumber = 12345; ... cout << "\n Значение равно: " << aNumber; В этом случае поток cout преобразует двоичное значение, хранящееся в переменной aNumber, в отображаемую строку цифр ASCII и отправляет эту строку на стандартное устройство вывода. Но бывают моменты, когда нам нужно выполнить преобразование самим. Логика высокоуровневой процедуры преобразования двоичного значения в строку цифр ASCII аналогична той, которая разработана в разделе 1.5.1. В программировании на C ++ логику обработки можно описать следующим образом: 1. Объявите и инициализируйте буфер типа char для хранения десятичных цифр результата в формате ASCII. 2. Получите остаток от двоичного значения, разделенного на 10. 3. Добавьте 0x30 к оставшейся цифре для преобразования в ASCII. 4. Сохраните оставшуюся цифру в буфере в самой правой позиции. 5. Проиндексируйте указатель буфера на предыдущую цифру. 6. Получите новое двоичное значение, разделив предыдущее двоичное значение на 10. 7. Продолжайте обработку на шаге 2, пока двоичное значение больше нуля. Следующая функция, названная Bin2AscDec(), преобразует значение, хранящееся в примитиве C ++ типа unsigned long, в строку цифр ASCII. Цифры помещаются в буфер, передаваемый вызывающим устройством. Процедура преобразования выглядит следующим образом: void Bin2AscDec( char digits[], длинное значение без знака) // Функция для преобразования двоичного значения в строку // из десятичных цифр ASCII // Pre: // 1. digits[] - это массив символов для хранения ASCII // десятичные цифры // 2. значение - это двоичное число, хранящееся в переменной // типа unsigned long Компьютерные системы счисления 23
// 3. Массив имеет достаточный размер для хранения всех // Цифры в формате ASCII. Если нет, то возвращаемый результат является // недействительным. // Post: // Возвращает строку десятичных цифр ASCII в // массив аргументов { int size = 0; // Вычислить размер массива while(цифры[размер]) size++; в то времякак(значение > 0 && size > 0) { цифры[size-1] = (значение % 10) + 0x30;// Получить и сохранить // младшая цифра // Примечание: // размер - это количество элементов в цифрах // массив. Смещение хранилища равно size - 1 значение = value / 10; // Новый операнд размер --; // Обновить положение цифры } возвращение; } С небольшими изменениями мы можем изменить функцию Bin2AscDec() на ту , которая выдает строку шестнадцатеричных цифр ASCII. Чтобы адаптировать функцию к шестнадцатеричной системе счисления, мы должны использовать базу шестнадцатеричной системы счисления (16) для вычисления остатка и нового двоичного значения. Кроме того, программа преобразования двоичного кода в шестнадцатеричный код ASCII должна проверить значение результирующей цифры: если значение меньше 10, тогда шестнадцатеричная цифра ASCII получается путем добавления 0x30, как в случае преобразования ASCII в десятичный код. Если значение цифры равно 10 или больше, то мы должны добавить другую константу, чтобы получить одну из шестнадцатеричных цифр alpha в диапазоне от A до F. Чтобы получить шестнадцатеричную цифру в верхнем регистре, добавляемая константа равна 0x37. Константа равна 0x57 для шестнадцатеричных цифр в нижнем регистре. Следующая функция с именем Bin2AscHex() преобразует двоичное значение в примитиве типа unsigned long в строку шестнадцатеричных цифр ASCII. void Bin2AscHex( char digits[], длинное значение без знака) // Функция для преобразования двоичного значения в строку // из шестнадцатеричных цифр ASCII // Pre: // 1. digits[] - это массив символов для хранения ASCII // шестнадцатеричные цифры // 2. значение - это двоичное число, хранящееся в переменной // типа unsigned long // 3. Массив имеет достаточный размер для хранения всех // Шестнадцатеричные цифры ASCII. Если нет, то возвращаемый результат является // недействительным. // // Post: // Возвращает строку шестнадцатеричных цифр ASCII в // массив аргументов { размер int = 0;
// Элементы в массиве беззнаковый int hexDigit; беззнаковый int factor = 0x37; // Измените на 0x57, чтобы получить // цифры в нижнем регистре в 24 Глава 1 // диапазон от a до f // Вычислить размер массива while(цифры[размер]) size++; в товремякак(значение > 0 && size > 0) { Шестнадцатеричная цифра = значение % 16; // Преобразуйте и сохраните цифры в диапазоне от 0 до 9 , если (шестнадцатеричная цифра < 10) цифры [размер-1] = шестнадцатеричная цифра + 0x30; // Получить и сохранить // цифра младшего порядка остальное цифры[размер-1] = шестнадцатеричная цифра + коэффициент; // Примечание: // Если размер - это количество элементов в цифрах // массив, тогда смещение хранилища равно (размер - 1) значение = значение / 16; // Новый операнд размер --; // Обновить положение цифры } возврат; } 1.6.2 Преобразования C ++ ASCII в двоичный код Другой набор процедур преобразования необходим для того, чтобы преобразовать целое число, представленное в строке цифр ASCII, в соответствующее двоичное значение. Для каждой системы счисления требуется своя процедура преобразования. Наиболее распространенными случаями являются строки, состоящие из двоичных, десятичных и шестнадцатеричных цифр. Высокоуровневая процедура преобразования строки двоичных цифр ASCII в двоичное значение основана на весе каждой двоичной цифры. Вес первых восьми двоичных цифр показан на рисунке 1.2. Фактически позиционный вес вычисляется путем возведения 2 в соответствующую позицию цифры. Например, двоичное число 13 имеет десятичный вес 2 13 . Разумный алгоритм преобразования двоичного кода ASCII в двоичный состоит в том, чтобы проверять строку двоичных цифр справа налево, то есть начиная с цифры младшего порядка. Если двоичная цифра равна 1, то в сумматоре добавляется вес цифры. Вес младшего бита равен 1. Вес следующей цифры вычисляется путем удвоения предыдущей единицы. Программа продолжает проверять цифру за цифрой, пока не дойдет до конца двухзначной строки. Алгоритм показан на блок-схеме на рисунке 1.8. Следующая функция с именем AscBin2Bin() получает в качестве аргумента строку двоичных цифр ASCII, хранящуюся в массиве типа char. Строка цифр преобразуется в двоичный код и возвращается вызывающей стороне в переменной типа unsigned long. Процедура разрешает использование пробелов и тире в качестве разделителей в строке двоичных цифр. беззнаковый длинный AscBin2Bin( char digits[]) // Функция для преобразования строки двоичных цифр в двоичное // значение // // Pre: // digits[] - это массив символов, состоящий из двоичных цифр. // Массив может содержать тире, используемые в качестве // разделителей. Компьютерные системы счисления 25
Рисунок 1.8 Преобразование двоичной строки ASCII в двоичный код // Сообщение: // Возвращает двоичное значение цифр // в переменной типа unsigned long { размер int = 0; // Размер массива unsigned long binWeight = 1; // Вес двоичного разряда беззнаковый длинный накопитель = 0; // Значение времени беззнаковое int bitOffset; // Смещение в битовом массиве unsigned int digitPos = 0; // Относительное положение цифры // Вычислить размер массива while(цифры[размер]) size++; // Процедура начинается с наименьшей значащей цифры, которая // имеет вес 1. Вес других цифр // слева от первой равен 2 * весу // предыдущего бита для(int counter = 0; counter < size; counter++) { bitOffset = (size - 1) - digitPos; если(цифры[bitOffset] == ' ' || цифры[bitOffset] == '-') { digitPos++; продолжить; } // Проверьте наличие 1 цифры и добавьте вес к аккумулятору, если (digits[bitOffset] == '1') аккумулятор = аккумулятор + вес контейнера; // Обновить вес цифры 26 Глава 1 НАЧАТЬ КОНЕЦ ДА ДА НЕТ НЕТ НАКОПИТЕЛЬ = 0 ИНИЦИАЛИЗИРОВАТЬ УКАЗАТЕЛЬ БУФЕРА УКАЗАТЕЛЬ НА СЛЕДУЮЩУЮ ЦИФРУ НАКОПИТЕЛЬ = АККУМУЛЯТОР + ВЕС КОНЕЦ СТРОКИ ? ЦИФРА = 1 ? Вес ячейки *= 2; // Обновить позицию цифры в строке digitPos++; } вернуть аккумулятор; } Другой полезной высокоуровневой процедурой является процедура преобразования строки десятичных цифр в двоичное значение. В C ++ строка цифр обычно хранится в массиве типа char. Процедура обработки получает цифры одну за другой из массива, начиная с наименее значащей цифры. Цифра ASCII преобразуется в двоичную путем вычитания 0x30 из ее значения. Результат умножается на вес позиции цифры и добавляется к сумматору. Когда процедура завершается, накопитель содержит двукратный эквивалент строки цифр. Алгоритм показан на блок-схеме на рисунке 1.9. Рисунок 1.9 Преобразование десятичной строки ASCII в двоичную
Следующая функция с именем AscDec2Bin() получает в качестве аргумента строку из десятичных цифр ASCII, хранящихся в массиве типа char. Строка десятичных цифр преобразуется в двоичную и возвращается вызывающей стороне в переменной типа unsigned long. Процедура позволяет использовать запятые в качестве разделителей в строке десятичных цифр ASCII. длинный AscDec2Bin без знака ( символьные цифры[]) // Функция для преобразования строки десятичных цифр в двоичную // значение // Предварительно: // digits[] - это массив символов, состоящий из десятичных цифр. Компьютерные системы счисления 27 НАЧАЛО КОНЕЦ ДА НЕТ АККУМУЛЯТОР = 0 ВЕС = 1 ИНИЦИАЛИЗИРОВАТЬ УКАЗАТЕЛЬ БУФЕРА УКАЗАТЕЛЬ НА СЛЕДУЮЩУЮ ЦИФРУ ВЕС = ВЕС * 10 АККУМУЛЯТОР = АККУМУЛЯТОР + (ЦИФРА * ВЕС) КОНЕЦ СТРОКИ ? // Массив может содержать запятые в качестве разделителей. // Post: // Возвращает двоичное значение цифр // в переменной типа unsigned long // { размер int = 0; // Размер массива беззнаковый long decWeight = 1; // Вес десятичной цифры беззнаковый длинный накопитель = 0; // Значение времени беззнаковое int bitOffset; // Смещение в битовом массиве unsigned int digitPos = 0; // Относительное положение цифры обозначьте эту цифру символом; // Хранилище для цифр // Вычислить размер массива while(цифры[размер]) размер++; // Процедура начинается с наименее значащей цифры, которая // имеет вес 1. Вес других цифр // слева от первой равен 10 * вес // предыдущей для(int counter = 0; counter < size; counter++) { bitOffset = (size - 1) - digitPos; если(цифры[bitOffset] == ',') { digitPos++; продолжить; } // УТВЕРЖДАТЬ: // Допустимая десятичная цифра в диапазоне от 0 до 9 // Преобразовать цифру ASCII в двоичную Эта цифра = digits[bitOffset] - 0x30; // Накопить вес цифры accumulator = аккумулятор + (эта цифра * decWeight); // Обновить вес цифры
decWeight *= 10; // Обновить положение цифры в строке digitPos ++; } вернуть аккумулятор; } Аналогичная процедура позволяет преобразовать строку шестнадцатеричных цифр ASCII в binary и сохранить результаты в примитивной переменной типа unsigned long. Логика требует незначительных изменений, поскольку мы должны преобразовать каждую шестнадцатеричную цифру ASCII в двоичную перед умножением на соответствующий вес цифры. Эта часть процедуры должна учитывать, что двоичное значение числовой шестнадцатеричной цифры (диапазон от 0 до 9) получается путем вычитания 0x30 из значения символа ASCII. Для всех фабетических шестнадцатеричных цифр (диапазон от A до F) преобразование требует вычитания 0x37 , если цифра в верхнем регистре, или 0x57, если цифра в нижнем регистре. В процедуре шестнадцатеричной конверсии вес цифры вычисляется путем умножения на 16, а не на 10. Обработку выполняет функция AscHex2Bin(). беззнаковый длинный AscHex2Bin( char digits[]) // Функция для преобразования строки шестнадцатеричных цифр в двоичный код // значение // Pre: 28 Глава 1 // digits[] - это массив символов, состоящий из шестнадцатеричных цифр. // Сообщение: // Возвращает двоичное значение цифр // в переменной типа unsigned long // Примечание: // Функция допускает стандартный шестнадцатеричный код C ++ // отформатируйте символы (0x) в цифровой строке. // Шестнадцатеричные альфа-символы (от A до F) могут быть // в верхнем или нижнем регистре. // { размер int = 0; // Размер массива // беззнаковый длинный шестнадцатеричный вес = 1; // Вес цифры аккумулятор длительного хранения без знака = 0; // Значение температуры смещение без знака int = 0; // Смещение в массиве unsigned int digitPos = 0; // Относительное положение цифры беззнаковое значение int digitValue; // Значение двоичной цифры // Получаем размер массива в то времякак(цифры[размер]) размер++; // Процедура начинается с младшей значащей цифры, которая // имеет вес 1. Вес других цифр до // слева от первой равен 16 * вес // предыдущего бита для(int counter = 0; счетчик < размер; счетчик++) { смещение = (размер - 1) - digitPos; if(цифры[смещение] == 'x') { digitPos++; продолжить; } // УТВЕРЖДАТЬ: // Допустимая шестнадцатеричная цифра в диапазоне от 0 до F // Преобразовать цифру ASCII в двоичную если (цифры [смещение] < 'A')
Значение цифры = цифры [смещение] - 0x30; ещё { // Проверка шестнадцатеричной цифры в верхнем регистре если (цифры [смещение] < 'G') Значение цифры = цифры [смещение] - 0x37; else digitValue = цифры[смещение] - 0x57; } // Накапливать вес цифры аккумулятор = аккумулятор + (значение цифры * шестнадцатеричный вес); // Обновить вес цифры Шестнадцатеричный вес *= 16; // Обновить позицию цифры в строке digitPos++; } вернуть аккумулятор; } Компьютерные системы счисления 29 Функция AscHex2Bin(), перечисленная ранее, допускает наличие стандартных символов шестнадцатеричного форматирования C ++ (0x) в строке цифр.. Кроме того, шестнадцатеричные буквенные символы (от A до F) могут отображаться в верхнем или нижнем регистре. ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Исходный код для процедур преобразования C ++, разработанных в этой главе, можно найти в файле INTCOV.H, содержащийся в папке проекта ГЛАВА 1\ ЦЕЛОЧИСЛЕННЫЕ ПРЕОБРАЗОВАНИЯ в онлайн-программном обеспечении книги. Код для процедур преобразования на языке ассемблера находится в модуле UN32_1.ASM библиотеки MATH32. 30 Глава 1 Глава 2 Числовые данные в памяти Краткое содержание главы В этой главе мы обсудим несколько кодировок и форматов хранения, используемых в компьютерном представлении числовых данных. Мы также продолжаем разрабатывать процедуры для преобразования числовых данных в различные кодировки. Здесь процедуры преобразования предназначены для манипулирования десятичными числами в двоичном коде. Процедуры преобразования числовых данных являются одним из строительных блоков, необходимых для разработки математического программного обеспечения. Арифметические процедуры BCD являются темой главы 4. Материал, рассмотренный в этой главе, не включает манипулирование двоичными представлениями с плавающей запятой, такими как те, которые используются в Pentium math unit. Это одна из тем глав 5 и 6. 2.0 Электронно-цифровые машины Усилия по механизации арифметики можно проследить от абака, логарифмической линейки, механических калькуляторов и перфокарт до современных цифро-электронных калькуляторов и компьютеров. Работа Джона фон Неймана из Принстонского института перспективных исследований знаменует собой первый крупный прорыв в проектировании и конструировании цифро-электронной счетной машины. Усилия Фон Неймана, начавшиеся в 1945 году, привели в 1951 году к созданию первой машины, которая использовала электронные схемы для хранения данных, выполнения вычислений и хранения собственных инструкций. Согласно указанию фон Неймана, данные и инструкции хранятся в общей области памяти. Следствием такой архитектуры является то, что программа может легко изменять хранимые данные и даже свои собственные структуры. Вычислительная мощность первого компьютера фон Неймана составляла приблизительно 2000 операций в секунду, в то время как предыдущие электромеханические предшественники были
способны выполнять только 3-4 операции в секунду. Современные машины могут выполнять около 1 миллиарда инструкций в секунду. Первый коммерческий цифрово-электронный компьютер был изготовлен компанией Remington Rand Incorporated и получил название UNIVAC I (Универсальный автоматический компьютер). UNIVAC I использовал вакуумные трубки, ртутные линии задержки , магнитное запоминающее устройство и технологию перфоленты. Компьютеры своего поколения заполняли целые здания и потребляли достаточно электроэнергии, чтобы обеспечить питанием небольшой округ. Технологические достижения и методы миниатюризации снизили стоимость и размеры вычислительного оборудования. Современный КОМПЬЮТЕР стоимостью менее 1500 долларов легко купить 31 превосходит емкость хранилища и скорость вычислений многомиллионного основного фрейма несколько лет назад. 2.1 Хранение числовых данных Для выполнения арифметических операций вычислительная машина должна быть способна хранить и извлекать числовые данные. Элементы числовых данных хранятся в стандартных форматах, предназначенных для минимизации занимаемого места и оптимизации эффективности обработки. Исторически числовые данные хранились в структурах данных, разработанных с учетом особенностей конкретной машины или предпочтений ее разработчиков. . istics конкретной машины или предпочтений ее разработчиков. В 1985 году Институт инженеров электротехники и электроники (IEEE) и Американский национальный институт стандартов (ANSI) официально утвердили математические стандарты для цифровых компьютеров. Электронные и физические механизмы, используемые для хранения компьютерных данных, эволюционировали вместе с технологией. Одной из общих особенностей многих микроэлектронных устройств, от транзистора до микросхемы СБИС, является то, что они основаны на свойствах материала, называемого полупроводником. Технология интегральных схем сделала возможным объединение транзисторов, резисторов и конденсаторов, а также их электронных соединений на одном куске полупроводникового материала. Электронная технология основана на бистабильных компонентах, что объясняет, почему двоичная система счисления получила почти повсеместное распространение. Данные, хранящиеся в регистрах процессора, на магнитных носителях, в оптических устройствах или на перфоленте, обычно кодируются в двоичном формате. По этой причине программист и оператор часто могут игнорировать физические характеристики носителя информации. Другими словами, битовый шаблон 10010011 может быть закодирован как отверстия в полоске бумажной ленты, как магнитные заряды на диске, покрытом майларом, как положительные напряжения в ячейке памяти интегральной схемы или как крошечные кратеры на поверхности компакт-диска. Во всех случаях этот битовый шаблон представляет десятичное число 147. Двоичные кодировки должны учитывать не только числовые значения, но и символы и форматы, используемые в математической нотации. Например, для представления чисел со знаком требуется соглашение о кодировании знака плюс и минус. Другим случаем является представление десятичных дробей, для которого требуются специальные протоколы и схемы кодирования, облегчающие хранение и ускоряющие вычисления и другие операции по обработке данных. Во всех случаях числовые кодировки данных были отменены , чтобы минимизировать требования к хранению и оптимизировать обработку. 2.1.1. Размер слова В электронных устройствах бистабильные состояния представлены двоичной цифрой, или битом. Разработчики Circuit группируют несколько отдельных ячеек, образуя единицу памяти, содержащую несколько бит. На конкретном компьютере базовая единица хранения данных называется размером слова . В таблице 2.1 приведен список размеров слов некоторых исторических компьютерных систем. 32 Глава 2 Таблица 2.1 Размер компьютерного слова в исторических системах СЕМЕЙСТВО КОМПЬЮТЕРОВ РАЗМЕР СЛОВА Микрокомпьютеры TRS 80 (процессор Z80) 8-разрядные микрокомпьютеры Apple (процессор 6502
IBM PC, XT, AT, PCjr, модели 30, 50, 60 и 16 бит другие микрокомпьютеры, оснащенные процессорами Intel 8086, 8088 или 80286 DEC PDP 11 IBM PS/2 Model 55 SX, Model 70, Model 80 32-разрядные и другие микрокомпьютеры, оснащенные процессорами Intel 80386, 486 или PENTIUM IBM 360/370 серии IBM 303X, 308X серии DEC VAX 11 Prime computers DEC 10 36 бит UNIVAC Honeywell CDC 6000 60 бит CDC 7000 CDC CYBER series В ПК наименьшая индивидуально адресуемая единица памяти составляет 8 бит (один байт). Отдельные биты не могут быть адресованы напрямую и должны обрабатываться как часть более крупных единиц хранения данных. Размер слова машины определяет единицы хранения данных, размер структуры машины и единицы адресации памяти. ПК, оснащенные процессорами Intel 8086, 8088 или 80286, имеют 16-разрядные регистры, передают данные в 8- и 16-разрядных единицах в память и порты и адресуют память, используя 16-разрядную базу (регистр сегмента ) и 16-разрядные указатели (регистр смещения). Поскольку регистры данных в этих процессорах имеют ширину 16 бит, наибольшее значение, которое может храниться в регистре, равно 11111111 11111111 двоичным числам, или 65 535 десятичным. ПК, использующие процессоры Intel 80386, 486 и Pentium, имеют 32-разрядные внутренние регистры и плоское адресное пространство шириной 32 бита. В этих машинах размер слова составляет 32 бита. По соображениям совместимости некоторые операционные системы и программный код используют микропроцессоры 80386, 486 и Pentium в режиме, совместимом с их 16-разрядными предшественниками. 2.2 Целочисленные кодировки Целые числа представляют собой набор целых чисел, которые могут быть положительными или отрицательными. Интегер цифры расположены одна единица хранения друг от друга и не имеют десятичной точки. Компьютерное хранилище целых чисел без знака находится в простой двоичной кодировке. Поскольку наименьшая адресуемая единица памяти на ПК равна одному байту, логика процессора дополняется начальными нулями, которые меньше одного байта. Рисунок 2.1 представляет целое число, хранящееся в электронном виде в компьютерной ячейке. Числовые данные в памяти 33 Рисунок 2.1 Представление целого числа без знака В главе 1 мы обсудили преобразование двоичных чисел без знака в десятичные и шестнадцатеричные значения в формате ASCII и разработали соответствующие низкоуровневые и высокоуровневые подпрограммы. 2.2.1 Представление знаковой величины Представление чисел со знаком требует специального соглашения, чтобы отличать положительные величины от отрицательных. Наиболее общепринятая схема заключается в том, чтобы отменить голосование на один бит для представления знака. Обычный формат хранения чисел со знаком устанавливает бит старшего порядка для обозначения отрицательных величин и очищает его для обозначения положительных
величин и нуля. В этой схеме десятичные числа 93 и – 93 представлены следующим образом: 01011101 двоичный = 93 десятичный 11011101 двоичный = -93 десятичный | |---------- знаковый знак Обратите внимание, что крайняя левая цифра установлена для отрицательного числа и очищена для положительного ? . Этот способ обозначения отрицательных чисел, называемый представлением величины ? по ? знаку, соответствует обычному способу, с помощью которого мы записываем числа со знаком от руки. То есть мы ? предваряем число его знаком. Представление Знак-величина IDIV обладает следующими характеристиками: ? ? ? ? ? ? IMUL М ? ? ? ? M INC X М М М М М МУЛ М ? ? ? ? М ОТРИЦАНИЕ M М М М М М НЕ ? ? ? ? ? ? ИЛИ 0 M ? M M 0 RCL/RCR 1
Т/М X X X X M Количество RCL / RCR Т/М X X X X M РОЛЬ /ROR 1 M X X X X M Количество РОЛЕЙ M X X X X ? SAL/SAR/SHL/SHR 1 M М ? М М M Количество SAL/ SAR/SHL/SHR M М ? М М ? SBB T/M M M M M M STC 1 ? ? ? ? ? SUB М М М М
М М ТЕСТ 0 М ? M M 0 XOR 0 М ? M M 0 Условные обозначения: T = инструкция проверяет бит состояния M = инструкция изменяет бит состояния 0 = инструкция очищает бит состояния 1 = инструкция устанавливает бит состояния ? = действие инструкции на бит состояния не определено X = инструкция не влияет на бит состояния 3.1.1 Логическое И Код операции AND выполняет логическое И из двух операндов. Это действие определяет , что бит в результате устанавливается тогда и только тогда, когда соответствующие биты установлены в обоих операндах. Часто инструкция AND используется для очистки одного или нескольких битов, не затрагивая остальные. Это возможно, потому что при вводе бита 0 всегда очищается бит результата, в то время как при вводе бита 1 сохраняется значение первой команды операции. Мы можем использовать операцию AND для выделения 4 младших бита следующим образом: 58 Глава 3 шестнадцатеричный двоичный 34 0011 0100 И 0F 0000 1111 ______________ _________ 04 0000 0100 Второй операнд, в данном случае 0FH, называется маской. Операция AND предварительно обслуживает в другом операнде биты, равные 1 в маске, одновременно очищая биты маски, которые равны 0. Следовательно, маска 00001111B очищает четыре бита старшего порядка и сохраняет исходное значение четырех битов младшего порядка. 3.1.2 Логическое ИЛИ Код операции OR выполняет логическое включающее ИЛИ из двух операндов. Действие операции OR заключается в том, что бит в результате устанавливается тогда и только тогда, когда установлен один или оба соответствующих бита в операндах. Часто используется ИЛИ для выборочной установки одного или более битов в ячейку памяти или машинный регистр. Действие происходит потому, что при вводе бита 1 всегда устанавливается бит результата, в то время как при вводе бита 0 сохраняется исходное значение в другом операнде. Например, чтобы установить бит старшего порядка (бит номер 7), мы можем использовать бит OR с 1, как следует шестнадцатеричный двоичный код 34
0011 0100 ИЛИ 80 1000 0000 --- —————B4 1011 0100 Операция OR устанавливает биты, равные 1 в маске, и сохраняет биты, равные 0. 3.1.3 Логический XOR Код операции XOR выполняет логическое исключающее ИЛИ из двух операндов. Действие кода операции XOR заключается в том, что бит в результате устанавливается тогда и только тогда, когда соответствующие биты в операндах имеют противоположные значения. Это объясняет, почему XORing значения с самим собой всегда приводит к нулевому результату, поскольку в этом случае все биты обязательно имеют одинаковое значение. С другой стороны, преобразование XOR в 1 бит инвертирует значение другого операнда, поскольку 0 XOR 1 равно 1, а 1 XOR 1 равно 0. Это действие переключения XORing с помощью 1 бита генерирует те же побитовые результаты, что и операция NOT. Выбирая значение маски XOR , программист может управлять тем, какие биты операнда инвертируются, а какие - предварительно обслуживаются. Вы можете инвертировать четыре бита старшего порядка операнда путем XORing с маской, в которой установлены эти биты. Если четыре младших бита маски понятны, то в результате сохраняются исходные значения этих битов в исходном операнде. Например: шестнадцатеричный двоичный 55 0101 0101 XOR F0 1111 0000 --------------------A5 1010 0101 Машинная арифметика 59 В этом примере операция XOR инвертировала биты, равные 1 в маске, и сохранила биты, равные 0. Другими словами, маска XOR 11110000B инвертирует четыре бита старшего порядка и сохраняет значение битов младшего порядка в исходном операнде. 3.1.4 Логически НЕ В то время как операции AND, OR и XOR требуют двух операндов, команда NOT воздействует на одно значение. Его действие согласуется с логической функцией NOT, которая преобразует все 1-биты в 0, а все 0-биты в 1. Арифметически результатом является дополнение единицы к исходному значению (см. Раздел 2.2.2). Инструкция NOT полезна для получения представления двух дополняющих друг друга значений путем выполнения логического NOT, а затем добавления единицы к результатам. В 80x86 манипуляции с NOT и increment не нужны, поскольку дополнение двух может быть получено непосредственно с помощью инструкции NEG. 3.2 Инструкции по арифметике 80x86 не обладает арифметическими возможностями. Разработчики чипа не сочли важным предоставлять обширные арифметические функции в 8086, поскольку центральный процессор мог быть дополнительно дополнен чипом математического сопроцессора, первоначально называвшимся 8087. Микросхема математического сопроцессора позже была интегрирована в блок с плавающей запятой 486 DX и Pentium. Математический сопроцессор 80x87 и математический модуль Pentium подробно обсуждаются, начиная с главы 5. Математические инструкции в процессорах 80x86 могут работать с 8-, 16-, и 32-разрядными операндами, хотя 32-разрядные операции доступны только на 80386, 486 и Pentium. Наибольшее десятичное число без знака, которое можно представить в 8 битах, равно 255, в 16 битах - 65 535, а в 32 битах - 4 294 967 295. Поскольку представления со знаком
требуют одного бита для кодирования знака, числовой диапазон чисел со знаком составляет примерно половину диапазона чисел без знака. Предел, определяемый наибольшим значением, которое может храниться в одном регистре , не означает, что машинная арифметика ограничена этим диапазоном. Можно разработать многозначные арифметические процедуры, которые расширяют числовой диапазон до любого желаемого значения . Фактически, арифметические инструкции 80x86 были специально разработаны для поддержки многозначных операций. 3.2.1 Арифметика со знаком и без знака Некоторые арифметические инструкции размером 80x86 могут использоваться как со знаковыми, так и беззнаковыми операндами. Так обстоит дело со сложением и вычитанием, в которых операционные коды ADD, ADC, SUB и SBB могут использоваться с целыми числами со знаком или без знака. Это возможно, поскольку инструкции обновляют различные флаги в регистре состояния способом, который позволяет интерпретировать результаты либо как подписанные, либо как неподписанные. Например, флаг переноса (CF) и флаг нуля (ZF) используются в операциях с целыми числами без знака. В этом случае код может протестировать CF, чтобы определить, превысила ли операция сложения или вычитания пределы целевого операнда. В 60 Глава 3 одноразрядные процедуры это условие (установлен флаг переноса) обычно указывает на то, что результат недействителен, поскольку он не помещается в указанное хранилище. В многозначных подпрограммах для беззнаковых операндов флаг переноса указывает бит, который должен быть перенесен на следующую цифру. Набор команд 80x86 включает в себя два кода операций, ADC (сложение с переносом) и SBB (вычитание с заимствованием), которые учитывают состояние бита переноса. операционные коды сложения и вычитания 80x86 используются как для операндов со знаком, так и без знака . Мнемоника Intel для умножения и деления со знаком и без знака несколько сбивает с толку, поскольку имена кодов операций не точно представляют текущую операцию, выполняемую инструкцией. Инструкции, помеченные как IMUL (целочисленное умножение) и IDIV (целочисленное деление), используются для операций со знаковыми числами, в то время как MUL (умножение) и DIV (деление) используются для операций без знака. По назннации IMUL и исключением idiv, в котором приставка буква “Я” обычно связывают со словом "целое", кажется, подразумевает, что дробное умножение и деление также доступны, что это не так. Программист 80x86 должен перевести это использование слова integer в значение signed. Вкратце: процессоры 80x86 поддерживают сложение, вычитание, умножение и деление целых чисел со знаком и без знака. В случае сложения и вычитания одни и те же коды операций используются со знаковыми и беззнаковыми операндами. Код должен определить, является ли результат подписанным или беззнаковым, путем интерпретации бита старшего порядка и оценки флагов status. В случае умножения и деления существуют отдельные коды операций для операций со знаком и без знака. Буква I используется в качестве префикса (IMUL и IDIV) в кодах операций со знаком. 3.2.2 Операции с десятичными числами Хотя процессоры семейства Intel 80x86 являются двоичными машинами, их набор команд включает в себя коды операций для двоично-десятичной арифметики. В главе 2 вы видели, что номера BCD могут храниться в упакованном или неупакованном виде. В упакованном виде на каждый байт хранятся две цифры BCD. Разряд BCD младшего порядка занимает биты от 0 до 3, а разряд BCD старшего порядка занимает биты от 4 до 7. С другой стороны, неупакованные цифры BCD хранятся по одной цифре на байт. В случае неупакованных чисел BCD разрядность старшего порядка не используется. Упакованный и распакованный двоично-десятичный форматы показаны на рис. раздел 2.7. Здесь опять же терминология Intel может быть несколько запутанной, поскольку она относится к упакованным числам BCD как к десятичным числам. Слово decimal, используемое в кодах операций DAA (десятичная корректировка после сложения) и DAS (десятичная корректировка после вычитания ), фактически относится к операциям с упакованными цифрами BCD. DAA используется для корректировки результата после добавления двух действительных упакованных цифр BCD в регистр AL. Эта инструкция корректирует результат таким образом, чтобы сумма также сохранялась в упакованной форме BCD. При использовании
кодов операций DAS и DAA флаг переноса (CF) устанавливается, если выполняется перенос старшей цифры, то есть если сумма больше значения 99H. Следующий фрагмент кода ilиллюстрирует работу инструкции DAA. ; Фрагмент кода, иллюстрирующий настройку упакованного BCD, выполняемую ; инструкцией DAA MOV AL,27H ; AL теперь содержит цифры BCD 27 MOV AH, 35H ; AL содержит 35 цифр BCD Машинная арифметика 61 Добавить AL,AH ; AL = AL + AH ; На данный момент сумма в AL равна значению 5CH DAA ; После регулировки AL = 62H При интерпретации приведенного выше фрагмента кода помните, что цифры BCD - это значения, основанные на nibble . Таким образом, они могут быть представлены шестнадцатеричными цифрами в диапазоне от 0 до 9. Инструкция DAA корректирует содержимое AL до двух упакованных цифр BCD после операции сложения. Неупакованные двоично-десятичные цифры иногда называются числами ASCII в литературе Intel. Это нетрадиционная интерпретация термина ASCII, который на самом деле является стандартной кодировкой символов. Инструкция AAA (корректировка ASCII после добавления) корректирует содержимое регистра AL после добавления двух не упакованных цифр BCD. Корректировка всегда подразумевает обнуление 4 битов старшего порядка результата, поскольку этот битовый шаблон необходим в распакованном представлении BCD. Если сумма превышает значение 9, устанавливается флаг переноса и увеличивается значение AH. Следующий фрагмент кода иллюстрирует действие инструкции AAA. ; Действие инструкции AAA при добавлении двух распакованных ; BCD-чисел MOV AH, 0 ; Очистить AH MOV AL,7H ; AL = распакованный BCD 7 MOV CL, 3H ; CL = распакованный BCD 3 Добавить AL, CL ; Значение AL теперь равно 0AH AAA ; Скорректировать результат ; AH = 01H и AL = 00H. Установлен флаг переноса Как видно из приведенного выше фрагмента, инструкция AAA (ASCII adjust after addition) изменяет регистр AL после операции сложения двух неупакованных цифр BCD, чтобы результат был представлен правильно. Четыре бита старшего порядка результата обнуляются. Если потребовалось заимствование, AH увеличивается и устанавливается флаг переноса . Инструкция AAM (корректировка ASCII после умножения) корректирует произведение после умножения двух распакованных BCD-чисел. Цифра старшего порядка находится в AH, а цифра младшего порядка - в AL. Если цифры, используемые при умножении, являются действительными, неупакованными числами BCD, то переполнения быть не может, поскольку количество двух неупакованных цифр BCD не может превышать объема хранилища назначения...........". Следующий фрагмент иллюстрирует действие инструкции AAM. ; Действие инструкции AAM при умножении двух ; распакованные числа BCD MOV AH, 0 ; Очистить AH MOV AL,9H
; AL = распакованный BCD 9 MOV CL, 8H ; CL = распакованный BCD 8 КОЛИЧЕСТВО CL ; AL теперь длится 48 часов AAM ; Скорректировать результат ; AH = 07H и AL = 02H AAD (настройка ASCII перед делением) изменяет содержимое делимого в AL таким образом, чтобы результатом деления была действительная, неупакованная десятичная цифра в двоичном коде. 62 Глава 3 Обратите внимание, что для деления распакованных BCD-чисел на 80x86 требуется, чтобы регистр AH был равен нулю перед выполнением инструкции DIV. После выполнения AAD регистр AL содержит частную цифру, а AH - остаток. Значения AH и AL с большим или меньшим значением равны нулю. Также обратите внимание, что команда AAD должна быть выполнена перед выполнением арифметической операции, в отличие от трех других инструкций настройки, используемых в распакованной BCD-арифметике. Следующий фрагмент иллюстрирует действие инструкции AAD. ; Действие инструкции AAD при разделении двух распакованных ; BCD-чисел MOV AH, 0 ; Очистить AH MOV AL,9H ; AL = распакованный BCD 9 MOV CL, 4 ЧАСА ; CL = распакованный BCD 4 Добавить ; Скорректировать дивиденд для деления DIV CL ; Разделить 9/4 = 2,1 остатка ; AH = 01H (остаток) и AL = 02H (частное) 3.3 Вспомогательные инструкции и инструкции по обработке битов Мы включаем в эту группу все операционные коды 80x86, которые связаны с логическими и математическими инструкциями или служат для их поддержки. В классификации Intel эти структуры содержатся в группе арифметических операций и в группе битовых манипуляций. Группа команд может быть подразделена следующим образом: 1. Инструкции сдвига и поворота битов, включая вводные коды операций сдвига двойной точности , созданные с помощью 80386. 2. Инструкции по сравнению. В эту подгруппу входят операционные коды CMP (сравнение) и TEST а также операционные коды bit scan и bit test, представленные в процессоре 80386. 3. Инструкции по преобразованию типов и обработке указателей. В эту группу входят коды операций для выполнения операций расширения знака, а также коды операций INC (приращение) и DEC (уменьшение ). INC и DEC обычно используются в арифметике указателей. 4. Новые арифметические коды операций, введенные с 486 и Pentium, а именно: BSWAP (байт swap), XADD (обмен и добавление), CMPXCHG (сравнение и обмен) и CMPXCHG8B (сравнение и обмен 8 байтами). 3.3.1 Инструкции по битовому сдвигу и повороту Коды операций в группах shift и rotate выполняют битовые манипуляции, которые часто используются в BCD и двоичных арифметических процедурах. Эти операции обработки битов также могут быть использованы для реализации умножения и деления путем сдвига битов. Хотя при программировании на процессорах 80x86 обычно предпочтительны операционные коды умножения и деления, упомянутые ранее в этой главе. Инструкции сдвига битов Инструкции сдвига перемещают влево или вправо все биты в операнде. В системах 80x86 операндом может быть регистр процессора или переменная памяти. Например, после операции сдвига вправо все биты в значении 01110101B (75H) перемещаются на одну позицию вправо, в результате чего получается значение 00111010B (3AH). Обратите внимание, что при сдвиге вправо
самый правый бит исчезает. Код операции SHR размером 80x86 (логический сдвиг вправо) выполняет операцию- Машинная арифметика 63 операция, описанная выше. В этом коде операции самый правый бит перемещается во флаг переноса . На рисунке 3.2 показано действие инструкций сдвига 80x86. Операционными кодами 80x86 для выполнения битового сдвига влево являются SHL (логический сдвиг влево) и SAL (арифметический сдвиг влево). Обратите внимание, что SHL и SAL - это разные mneмоники для одной и той же операции (см. рис. 3.2). В SHL и SAL это самый левый бит операнда, который перемещается во флаг переноса. Термины логический и арифметический, используемые в операционных кодах SHL и SAL, отражают потенциальную проблему, связанную со сдвигом битов в знаковом представлении. Проблема заключается в том, что отрицательные числа в форме дополнения two всегда имеют старший бит. Следовательно, когда биты дополнительного числа двойки сдвигаются, знаковый бит может измениться непредсказуемым образом. По этой причине при операциях сдвига влево знаковых операндов знаковый бит перемещается во флаг переноса. После выполнения переноса программное обеспечение может протестировать флаг переноса и внести необходимые корректировки. С другой стороны, при операции сдвига вправо знаковый бит перемещается с бита номер 7 на бит номер 6, а нулевой бит вводится в позицию знакового бита. Это действие делает все числа со знаком положительными. Чтобы сделать возможными операции сдвига чисел со знаком, набор команд 80x86 имеет отдельный код операции для сдвига чисел со знаком вправо. Код операции SAR (арифметический сдвиг вправо) сохраняет знаковый бит (бит номер 7) при сдвиге всех остальных битов вправо. Это действие можно увидеть на схеме инструкции SAR на рисунке 3.2. Обратите внимание, что в инструкции SAR самый левый бит (знаковый бит) одновременно сохраняется и сдвигается. Например, значение 10000000B становится 11000000B после выполнения операции SAR. Это действие иногда называют операцией расширения знака. Рисунок 3.2 Инструкции по сдвигу 80x86 бит 64 Глава 3 76543210 76543210 76543210 76543210 76543210 76543210 0 0 CF CF CF SHL - сдвиг логический влево SAL - сдвиг арифметический влево SHR - логический сдвиг вправо SAR - арифметический сдвиг вправо 8-разрядные микропроцессоры, предшествовавшие семейству 80x86 (такие как Intel 8080, Zilog Z80 и Motorola 6502), не включали в себя структуры умножения и деления. В этих чипах умножение и деление должно было выполняться программным обеспечением. Одним из подходов к умножению было повторное сложение. Иногда этот подход все еще полезен. Следующий фрагмент кода иллюстрирует умножение путем повторного сложения с использованием кода 80x86. ; Умножение AL * CX с использованием повторного сложения MOV АХ, 0 ; Очистить регистр, используемый для ; накопления суммы MOV AL, 10 ; Загрузить мультипликатор MOV CX, 6 ; Множитель нагрузки УМНОЖИТЬ: Добавить AH,AL
; Добавить AL к сумме в AH ЦИКЛ УМНОЖИТЬ ; AH теперь содержит произведение 10 * 6 Часто используемый метод для выполнения операций быстрого умножения и деления заключается в сдвиге битов операнда. Этот метод основан на позиционных свойствах двоичной системы счисления. В схеме двоичного счисления значение каждой цифры является последовательной степенью 2 (см. Главу 1). Следовательно, при сдвиге всех цифр влево значение 0001B (1 десятичный знак) последовательно становится 0010B (2 десятичных знака), 0100B (4 десятичных знака) и 1000B (8 десятичных знаков). Ограничение двоичного умножения с помощью операций битового сдвига заключается в том, что множитель должен быть в степени 2. Если нет, то программное обеспечение должно сдвинуться в степени 2, которая меньше множителя, и добавлять множитель столько раз, сколько необходимо для завершения продукта. Например, чтобы умножить на 5, мы можем дважды сдвинуть влево и один раз добавить значение множимого. Более практичный подход может быть основан на том же алгоритме, который используется при умножении от руки . Например, умножение 00101101B (45 десятичных знаков) на 01101101B (109 десятичных знаков) может быть выражено как последовательность произведений и сдвигов следующим образом: 0 0 1 0 1 1 0 1 B = 45 десятичных раз 0 1 1 0 1 1 0 1 B = 109 десятичных знаков ------------------------------00101101 00000000 00101101 00101101 00000000 00101101 00101101 00000000 ----------------------------------------------0 0 1 0 0 1 1 0 0 1 0 1 0 0 1 B = 4905 знаков после запятой Фактические вычисления с использованием этого метода двоичного умножения довольно просты , поскольку произведение на цифру 0 равно нулю, а произведение на цифру 1 является самим умножителем . Процедура умножения просто проверяет каждую цифру в множителе. Если Машинная арифметика 65 цифра равна 1, множитель сдвигается влево и суммируется в сумматор. Если цифра равна 0, то биты сдвигаются, но сложение пропускается. Процедуры умножения на основе сдвига были довольно популярны в процессорах, которые не были оснащены инструкцией умножения. В случае 80x86 кажется, что от процедур умножения, основанных на битовых сдвигах, мало пользы, поскольку процессор способен выполнять эффективные умножения внутри системы. По этой причине программисты 80x86 находят мало практического применения кодам операций SAR и SAL при разработке арифметических процедур, хотя эти коды операций все еще полезны для других битовых манипуляций. Инструкции по повороту бита Команды поворота 80x86 также сдвигают биты в операнде влево или вправо. Разница между сдвигом и поворотом заключается в том, что при повороте смещенный бит либо повторно вводится на другом конце операнда, либо сохраняется во флаге переноса. ЛР операции (поворот влево) сдвигает биты влево, а старший бит-это ТИЦcled обратно в младший бит положении, а также хранить в нести флаг. Код операции ROR работает аналогичным образом, за исключением того, что действие выполняется слева направо. В обеих инструкциях, ROL и ROR, флаг переноса используется для хранения переработанного бита, который может быть удобно протестирован программным обеспечением. На рисунке 3.3 показано действие инструкций поворота 80x86. Рисунок 3.3 Инструкции по повороту размером 80x86 бит 66 Глава 3 76543210 76543210 76543210 76543210
76543210 76543210 76543210 76543210 CF CF CF CF Поворот влево RCL - поворот через перенос влево ROR - поворот вправо RCR - поворот с помощью переноски вправо Две команды поворота, RCL (поворот через перенос влево) и RCR (поворот через перенос вправо), используют флаг переноса в качестве временного хранилища для бита, который смещен. Это действие можно увидеть на диаграммах рисунка 3.3. Обратите внимание, что смещенный бит не восстанавливается на другом конце операнда до тех пор, пока команда не будет выполнена повторно. Также интересно, что, повторяя вращение столько раз, сколько бит в целевом операнде, инструкции rotate сохраняют исходное значение. Для этого требуется повернуть операнд размером в байт 8 раз, операнд размером в слово 16 раз и так далее. Инструкции сдвига двойной точности В 386-м были введены два новых кода операций для выполнения побитовых операций над длинными битовыми строками. Эти коды операций имеют мнемонические SHLD (сдвиг двойной точности влево) и SHRD (сдвиг двойной точности вправо). Инструкции также доступны в моделях 486 и Pentium. Команды сдвига двойной точности SHLD и SHRD требуют 3 операнда. Для например: ШЛ AX,BX,12 Крайний левый операнд (AX) является местом назначения сдвига. Самый правый операнд (12) - это количество битов. Средний операнд (BX) является исходным. Биты в исходном операнде перемещаются в операнд назначения, начиная с старших или младших битов источников. Источник и назначение должны иметь одинаковый размер, например, если конечная нация представляет собой регистр размером со слово, то источником должен быть регистр размером со слово или переменная памяти. По тому же принципу, если адресатом является регистр двойного слова или ячейка памяти, то источник также должен иметь ширину 32 бита. Либо источник, либо конечная нация могут быть операндом памяти, но по крайней мере один из них должен быть машинным регистратором . Операндом count может быть непосредственный байт или значение в регистре CL. Предел количества сдвигов составляет 31 бит. Следующий фрагмент кода показывает сдвиг битов двойной точности . ; Демонстрация действия, выполненного методом двойной точности ; сдвиг влево (SHLD) MOV EAX,3456H ; Один операнд к месту назначения MOV EBX, 10000000H ; Исходный операнд SHLD EAX, EBX,4 ; Сдвинуть влево цифры EAX на 4 бита ; и ввести биты EBX в ; Биты EAX, освобожденные сдвигом ; На данный момент: ; EAX = 34561 ; EBX = 10000000 (без изменений) Наиболее часто инструкции SHLD и SHRD используются для манипулирования длинными битовыми строками. Например, вы можете наложить на переменную памяти значение регистра , как в следующем фрагменте кода, используя встроенную сборку: int var1; main() { _asm { MOV EBX,12300000H ; Исходный операнд
Машинная арифметика 67 SHLD var1,EBX,12 // УТВЕРЖДАТЬ: // VAR1 = 123H } } В приведенных выше фрагментах кода обратите внимание, что команда SHLD использовалась для сдвига 4 упакованных цифр BCD. Сдвиг цифр выполняется путем выбора количества разрядов , кратного 4, поскольку каждая цифра занимает 4 бита. Таким образом, количество битов, равное 8, сдвинуло бы 2 упакованные цифры BCD. Также обратите внимание, что исходный регистр не изменился из-за сдвига двойной точности. Режимы адресации сдвига и поворота Режимы адресации для кодов операций сдвига и поворота претерпели несколько изменений в различных микропроцессорах линейки 80x86. В 8086 и 8088 shift и rotate могут использовать счетчик в регистре CL или число 1 в качестве непосредственного операнда. Более поздние процессоры допускают 8-разрядный непосредственный операнд. Следующий фрагмент кода иллюстрирует допустимые режимы адресации в каждом конкретном случае. ; Режимы адресации сдвига и поворота в микросхемах 8086 и 8088 SHL AL,1 ; Сдвиг влево на 1 битную позицию MOV CL, 4 ; Сдвинуть счетчик на CL SHL AL, CL ; Сдвиг влево на 5 разрядных позиций . . ; Режимы адресации сдвига и поворота в 80286, 80386, 486, ; и Pentium, в которых 8-разрядный непосредственный операнд может быть задан ; напрямую SHR AX,3 ; Сдвиг вправо на 3 бита . . ; В 80386, 486 и Pentium операционные коды shift и rotate допускают ; например, 32-разрядный регистровый операнд в качестве назначения SHL EBX,4 ; Сдвиг EBX на 4 бита . . 3.3.2 Инструкции по сравнению, битовому сканированию и тестированию битов Команда CMP (compare) изменяет флаги, как если бы имело место вычитание , но не изменяет значение операндов. Действие может быть описано как установка регистра состояния так, как если бы исходный операнд был вычтен из конечной нации. За инструкцией обычно следует условный переход. Следующий фрагмент кода показывает использование CMP при определении относительного значения операнда в машинном регистре. ; Использование CMP для определения, является ли BX > AX, BX < AX или BX = AX ; Код предполагает, что значения в AX и BX являются двоичными без знака CMP AX,BX ; Имитировать AX минус BX JA AX_ABOVE ; Перейти, если AX > BX JB AX_BELOW ; Перейти, если AX < BX ; В этот момент AX = BX . 68 Глава 3
. . ; Точка входа для AX > BX AX_ABOVE: . . . ; Точка входа для AX < BX AX_BELOW: . . . ТЕСТОВАЯ инструкция выполняет логическое И и обновляет флаги без изменения операндов. Если за ТЕСТОВОЙ инструкцией следует JNZ, переход выполняется, если в обоих операндах совпадают 1-биты. Следующий фрагмент кода показывает использование ТЕСТОВОГО кода операции. ; Использование теста для определения, установлен ли бит 7 регистра AL ТЕСТ AL, 10000000B ; И AL, и бинарная маска JNZ HIGH_BIT_SET ; Перейти, если бит 7 = 1 ; В этот момент бит 7 = 0 . . ; Точка входа для всех бит 7, установленных HIGH_BIT_SET: . . . Процессор 80386 ввел несколько новых инструкций по управлению битами, которые позволяют более тщательно выполнять сканирование и тестирование битов. Код операции BSF (bit scan forward) сканирует исходный операнд от низкого к высокому и сохраняет в целевом операнде битовую позицию первого найденного 1 бита. Если все биты исходного операнда равны 0, то устанавливается нулевой флаг , в противном случае нулевой флаг сбрасывается. BSR (bit scan reverse) выполняет тот же тест, но начиная с позиции старшего бита. Обе инструкции требуют словесных или двухсловных операндов; байтовые операнды не допускаются. Следующий фрагмент кода показывает работу BSF. ; Использование инструкций BSF и BSR для определения количества ; первого бита, установленного в исходном операнде. ПЕРЕМЕЩЕНИЕ AX,10001000B ; Первый бит справа налево ; установлен номер 3 BSF BX,AX ; номер бита AX в BX ; В этот момент BX = 03, поскольку первый установленный бит находится в бите ; позиция номер 3 при чтении от низкого к высокому. Нулевой флаг снят BSR CX, AX ; номер бита AX в CX ; считывание от высокого к низкому ; В этот момент CX = 07, поскольку бит номер 7 AX является первым ; битом, установленным при чтении от высокого к низкому. Нулевой флаг снят Операционные коды BT (bit test), BTS (bit test и set), BTR (bit test и reset), и BTC (bit test и complement) также были введены с процессором 386. Все из этих кодов операций копируют значение указанного бита во флаг переноса. Код может Машинная арифметика 69 позже включите инструкцию JC или JNC для прямого выполнения в соответствии с состоянием флага переноса. Кроме того, тестируемый бит может быть изменен в целевой операции и: BTS устанавливает тестируемый бит, BTR очищает тестируемый бит, а BTC дополняет тестируемый бит. Следующий фрагмент кода показывает действие этих кодов операций. ; Использование кодов операций BT, BTS, BTR и BTC для тестирования и манипулирования ; битами в соответствии с их положением MOV AX,10001000B ; Установить значение в операнде
BT AX, 3 ; Тестовый бит AX 3 ; Флаг переноса устанавливается с момента установки бита AX 3. AX не изменяется BTS AX,0 ; Тестовый бит AX 0 ; Флаг переноса снят, поскольку бит AX 0 не установлен ; AX = 10001001B, поскольку инструкция устанавливает указанный бит BTR AX,7 ; Тестовый бит AX 7 ; Флаг переноса установлен с тех пор, как установлен AX бит 7 ; AX = 00001001B с тех пор, как бит 7 сброшен (очищен) BTR BTC AX, 1 ; Тестовый бит AX 1 ; Флаг переноса снят, поскольку бит 1 очищен ; AX = 00001011B, поскольку бит 1 переключается (дополняется) BTC Условные переходы со знаком и без знака 80x86 предоставляет две категории кодов операций условного перехода: одну для работы с целыми числами и одну для работы с числами со знаком в форме дополнения two. Например, JA (переход, если выше) и JB (переход, если ниже) предполагают, что операнды являются целыми числами без знака, в то время как JG (переход, если больше) и JL (переход, если меньше) предполагают, что операнды представляют собой числа со знаком в формате дополнения two. В таблице 3.2 показаны инструкции условного перехода размером 80x86 в соответствии с их интерпретацией со знаком или без знака . Обратите внимание в таблице 3.2, что инструкции условного перехода, которые предполагают подписанные операнды, используют знак и флаг переполнения для определения своего действия. Флаг sign является четким, когда результатом операции является двоичное положительное число, то есть такое, в котором старший бит равен 0. Флаг sign устанавливается, если результатом предыдущей операции является двоичное отрицательное число, то есть число, в котором установлен старший бит. С другой стороны, арифметические процедуры без знака обычно игнорируют флаг sign, поскольку старший бит двоичных чисел без знака интерпретируется как значение. Флаг переполнения указывает на положительное число со знаком, которое слишком велико для представления в формате, или на отрицательное число со знаком, которое слишком мало. В знаковой арифметике этот флаг указывает на переполнение, однако обычно он игнорируется при работе с беззнаковыми двоичными числами. Несколько инструкций перехода в таблице 3.2 основаны на флаге четности, а именно: JNP (переход, если четности нет), JPO (переход, если четность нечетная), JP (переход, если четность четная) и JPE (переход, если четность четная). Этот флаг устанавливается, если восемь битов младшего порядка результата содержат четное количество 1-бит (четность четная), и снимается в противном случае. Этот флаг был предоставлен для совместимости с процессорами Intel 8080 и 8005. Хотя флаг четности может использоваться для обеспечения целостности передачи данных, он не имеет применения в арифметических или логических процедурах. 70 Глава 3 Таблица 3.2 условные переходы x86 МНЕМОНИКА ДЕЙСТВИЕ ФЛАГА Описание УСЛОВНЫЕ ПЕРЕХОДЫ, КОТОРЫЕ ПРЕДПОЛАГАЮТ ОПЕРАНДЫ БЕЗ ЗНАКА JA (CF или ZF) = 0 прыгай, если выше JNBE прыгайте, если не ниже или на равных ДЖЕ CF = 0 прыгайте, если выше или равны JNB
прыгайте, если не ниже JNC прыгай, если нет переноса JB CF = 1 прыгай, если ниже ДЖНЕЙ прыгайте, если не выше или равны JC прыжок, если установлен перенос JBE (CF или ZF) = 1 прыгайте, если ниже или равны ЮНА прыгайте, если не выше JE ZF = 1 прыжок при равенстве JZ прыгать, если ноль JNE ZF = 0 перейти, если не равно JNZ скачок, если не равен нулю JNP PF = 0 перейти, если нет четности JPO перейти, если четность нечетная JP PF = 1 перейти к четности JPE перейти, если четность четная УСЛОВНЫЕ ПЕРЕХОДЫ, КОТОРЫЕ ПРЕДПОЛАГАЮТ ЗНАКОВЫЕ ОПЕРАНДЫ JG ((SF xor OF) или ZF) = 0 прыгай, если больше НЕМНОГО перейти, если не меньше или равно JGE (SF xor OF) = 0 прыгайте, если больше или равно JNL прыгайте, если не меньше JL (SF XOR OF) = 1 прыгайте, если меньше JNGE перейти, если не больше или равно JLE ((SF xor OF) или ZF) = 1 прыгайте, если меньше или равно JNG прыгайте, если не больше JNO OF = 0 перейти, если переполнения нет JNS
SF = 0 прыжок, если положительный (без знака) ДЖО ИЗ = 1 переход при переполнении JS SF = 1 переход при отрицательном значении (установлен знак) Условные обозначения: CF = флаг переноса ZF = нулевой флаг PF = флаг четности SF = флаг знака OF = флаг переполнения 3.3.3 Инструкции по увеличению, уменьшению и расширению знака Команда INC (increment) добавляет 1 к значению назначения, в то время как команда DEC (decrement) вычитает 1. INC и DEC часто используются при манипулировании указателями, хотя они иногда находят применение в арифметических процедурах, в основном при регулировке условий переполнения или недостаточного потока. Обе инструкции предполагают, что операнд является целым числом без знака, поэтому они не влияют на флаг переноса. По этой причине при работе со знаковыми величинами предпочтительнее использовать инструкции ADD и SUB. Набор команд 80x86 также включает в себя несколько кодов операций, действие которых часто описывается как выполнение знакового расширения исходного операнда. CBW (преобразовать байт в слово) преобразует байт со знаком в форме дополнения two в слово со знаком, также в Машинной арифметике 71 дополнение двух. Источником всегда является регистр AL, а местом назначения - AX. Преобразование выполняется путем копирования старшего бита AL во все биты AH . Следовательно, знаковое значение 0083H преобразуется в FF83H, отсюда и использование термина расширение знака для описания его действия. Код операции CWD (преобразовать слово в двойное слово) выполняет то же преобразование слова в AX в двойное слово в DX:AX. Процессор 80386 представил две новые инструкции расширения sign, разработанные для работы с 32-разрядными и 64-разрядными операндами. CWDE (например, преобразовать word в двойное слово tendent) преобразует 16-разрядное число со знаком в AX в 32-разрядное число со знаком в EAX. В СТК (преобразовать двойное слово для четверных) предполагает два дополнения клавиши numBER в eax и преобразует его в 64-разрядное целое число в регистр edx:еах. Коды операций со знаковым выражением напряжения полезны при выполнении знакового умножения и деления, когда один из операндов имеет формат, отличный от формата назначения. Следующий фрагмент кода является демонстрацией использования инструкции CBW. ; Использование CBW для умножения словесного операнда со знаком в BX на ; байт со знаком в AL MOV BX,-1234 ; Множитель загрузки в байтах MOV AL,-104 ; Кратность загрузки (98H) CBW ; Преобразовать в word ; На данный момент AX содержит FF98H (байт со знаком, преобразованный в word) IMUL BX ; -1234 * -104 ; Результат -1234 * -104 равен 128 336. Продукт хранится ; в формате DX: AX как 0001:F550H 3.3.4 Фирменные инструкции 486 и Pentium Процессоры 486 и Pentium ввели 4 новые инструкции, связанные с арифметической обработкой; это: BSWAP (обмен байтами), XADD (обмен и добавление), CHPXCHG (сравнение и обмен) и CMPXCHG8B (сравнение и обмен 8 байтами).
BSWAP Инструкция BSWAP изменяет порядок байтов в 32-разрядном машинном регистре на обратный. Одно из применений BSWAP заключается в преобразовании данных между форматами little endian и big endian. В этом смысле можно использовать BSWAP для изменения порядка распакованных десятичных цифр, загруженных из операнда памяти в 32-разрядный машинный регистр, на обратный. Например: предположим, что четыре распакованных десятичных разряда сохранены в операнде памяти с наименьшей значащей цифрой в расположении самого низкого порядка, как это было бы в обычном формате BCD. Когда эти цифры загружаются в машинный регистр с помощью инструкции MOV, их порядок будет обратным. Следующий код имитирует эту ситуацию. ДАННЫЕ СЕГМЕНТ ЧЕТЫРЕ_ИГРЫ БД 01Ч, 02Ч, 03Ч, 04Ч ДАННЫЕ ЗАКАНЧИВАЕТСЯ Если бы эти цифры теперь загружались в 32-разрядный машинный регистр, обычно с помощью регистра указателя, их порядок был бы обратным, как показано в следующем фрагменте. 72 Глава 3 LEA SI,FOUR_DIGITS ; Указатель на распакованный BCD MOV EAX,DWORD PTR [SI] ; Загрузить EAX с помощью указателя ; EAX = 04030201H На этом этапе неупакованные цифры BCD меняются местами в регистре EAX. В машине Pentium ситуация может быть легко исправлена с помощью инструкции BSWAP . Инструкция перевернула бы байты в EAX следующим образом BSWAP EAX ; Поменять местами байты в EAX ; EAX = 01020304H На рисунке 3.4 показано действие инструкции BSWAP. Рисунок 3.4 Действие инструкции 486 BSWAP В процессоре 386 для изменения порядка байтов в 32-разрядном регистре требуется несколько операций XCHG (обмена). Следующая процедура имитирует BSWAP на машине 80386. BSWAP_EAX ПРОДОЛЖЕНИЕ РЯДОМ ; Имитируйте инструкцию 486 BSWAP EAX на машине 386 ; Комментарии предполагают, что при вводе EAX = 0403 0201H ; После инверсии байта EAX будет содержать 0102 0304H ; толкать EBX ; Сохранить EBX в стеке MOV EBX, EAX ; Скопировать EAX в EBX SHR EBX,16 ; Замените старшее слово на младшее ; На этом этапе: ; EAX = 0403 0201H ; EBX = 0000 0403H XCHG АХ, AL ; EAX = 0403 0102H SHL EAX,16 ; EAX = 0102 0000H XCHG
BH, BL ; EBX = 0000 0304H ИЛИ EAX,EBX ; EAX = 0102 0304H Всплывающее EBX ; Восстановить EBX RET BSWAP_EAX ENDP XADD Для инструкции 486 XADD (exchange and add) требуется исходный операнд в машинном регистре и операнд назначения, который может быть регистром или переменной памяти. При выполнении XADD исходный операнд заменяется целевым и Машинной арифметикой 73 23 16 23 16 31 24 31 24 15 8 15 8 7 0 7 0
назначение заменяется суммой обоих исходных операндов. Основная цель этой инструкции - предоставить многопроцессорный механизм, посредством которого несколько процессоров могут выполнять один и тот же цикл. CMPXCHG и CMPXCHG8B Код операции 486/Pentium CMPXCHG (сравнение и обмен) требует трех операндов. Источником должен быть машинный регистр. Адресатом может быть либо машинный регистр, либо переменная памяти. Третьим операндом является накопитель, который может быть либо AL, AX, либо EAX. Если значение в пункте назначения и накопителе равно , то CMPXCHG заменяет операнд назначения исходным. В этом случае устанавливается нулевой флаг (ZF). В противном случае целевой операнд загружается в накопитель. В каждом этом случае флаги устанавливаются так, как если бы целевой операнд был вычтен из кумулятора. В документации Intel указано, что CMPXCHG в первую очередь предназначен для манипулирования семафорами. Процессор Pentium включает версию кода операции сравнения и обмена с мнемоническим кодом CMPXCHG8B (сравнить и обменять 8 байт). Как и CMPXCHG, CMPXCHG8B требует трех операндов. Местом назначения должна быть переменная памяти. Два других операнда представляют собой 64-разрядное (8 байт) значение в EDX: EAX и 64-разрядное значение в ECX:EBX. Когда команда выполняется, значение в EDX:EAX сравнивается с целевым операндом. Если они равны, значение в ECX:EBX затем сохраняется в пункте назначения. В этом случае устанавливается нулевой флаг. Если они не равны, то пункт назначения загружается в EDX:EAX. В этом случае нулевой флаг снимается. В документации Intel указано, что CMPXCHG8B также предназначен для управления семафорами. 3.4 Идентификация процессора Программному обеспечению часто требуется определить, на какой версии процессора запущена программа , чтобы использовать или обойти одну или несколько инструкций или выбрать одну из доступных функций. Например, ранее мы разработали процедуру BSWAP_EAX, которых длатес на 486/Pentium с BSWAP регистра eax на 386 машину. Чтобы разработать код, который может выполняться в любой машинной среде, можно создать несколько альтернативных собственных маршрутов обработки. Можно вызвать тестовую функцию процессора, чтобы определить, какая ветвь обработки требуется . В более поздних версиях процессора 486 Intel представила инструкцию с именем CPUID. Эта инструкция может быть использована для получения информации о поставщике, а также о семействе процессоров, модели и пошаговом режиме. Информация, возвращаемая инструкцией , зависит от значения, переданного в регистре EAX. Если CPUID выполняется с 0 в EAX, тогда инструкция возвращает в EAX самый высокий входной параметр, который она может понять . Для процессоров семейства Pentium наименьшее значение, возвращаемое в EAX, равно 1. Также в этом случае регистры EBX, EDX и ECX могут содержать строку, идентифицирующую поставщика процессора. Если Pentium произведен корпорацией Intel, строка будет “GenuineIntel”. Другие производители могут предоставить другую идентификационную строку. Если команда CPUID выполняется со значением 1 в EAX, то она возвращает дополнительную информацию о процессоре. Другие значения также могут быть загружены в EAX в соответствии с семейством процессоров CPU. В таблице 3.3 перечислены значения, возвращаемые несколькими реализациями инструкции CPUID. 74 Глава 3 Таблица 3.3 Информация, возвращаемая инструкцией CPUID EAX ЦЕННОСТЬ ПРЕДОСТАВЛЕННАЯ ИНФОРМАЦИЯ 0Ч EAX = максимальный входной сигнал, воспринимаемый CPUID EBX = “Genu” (756E6547H) EDX = “ineI” (49656E69H) ECX = “ntel” (6C65746EH) 1Ч EAX = версия (тип, семейство, модель и идентификатор шага)
EBX = индекс бренда EDX = информация о характеристиках: Немного: Описание 0 математическая единица на чипе 1 Усовершенствования виртуального режима 8086 2 расширения для отладки 3 расширения размера страницы ... другая информация в зависимости от версии процессора 2Ч EAX-EBX-ECX-EDX = информация о кэше и TLB 3 ЧАСА ECX-EDX = серийный номер процессора Следующая функция, названная IdCpu(), проверяет пять различных вариантов процессора, используемых в микрокомпьютерах IBM: 8086/8088, 80286, 80386, 486 и Pentium. Если процессором является Pentium, то команда CPUID выполняется со значением 0 в EAX для проверки на подпись ”GenuineIntel”. Если сигнатурой является “GenuineIntel”, то ввод CPUIDструктуры выполняется второй раз со значением 1 в EAX. Когда выполнение повторно переходит к вызывающей стороне, переменные, передаваемые в качестве аргумента, содержат идентификационный код процессора . Если процессором был Pentium производства Intel, то вторая переменная содержит информацию о версии. аннулирует IdCpu(int *CPUtype, int *Cid) { _asm { // Функция для определения процессора на ПК // Post: // Параметр CPUtype следующим образом: // 1, если процессор равен 8086 или 8088 // 2, если процессор равен 80286 // 3, если процессор равен 80386 // 4, если процессор равен 486 // 5 если процессор - Pentium // Параметр Cid содержит идентификационный код процессора // если строка идентификатора процессора - ‘GenuineIntel’ // Биты следующие: // xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxxxx <= биты // |<-- игнорируется -------->|--| |---|--| // | | | |___[3-0] идентификатор шага // | |____[7-4] номер модели // |______[11-8] семейство // В противном случае Cid остается неизменным ;***********************| ; тест на 8086/8088 | ;***********************| Машинная арифметика 75
; Биты с 12 по 15 в регистре флагов всегда устанавливаются в процессорах 8086 ; и 8088 PUSHF ; Флаговый регистр для стекирования POP AX ; Хранить флаги в AX И AX, 0FFFH ; Очистить биты с 12 по 15 толкать ТОПОР ; ТОПОР для укладки POPF ; и для регистрации флагов PUSHF ; Флаги для стекирования POP AX ; и ТОПОРУ для чтения И AX, 0F000H ; Сохранить биты с 12 по 15 CMP AX,0F000H ; Проверка набора битов JNE TEST_286 ; Перейти, если биты не установлены ; На данный момент процессором является процессор 8086 или 8088 MOV AX,1 ; Код возврата MOV DX, 0 JMP ID_EXIT ; Выход ;***********************| ; тест для 80286 | ;***********************| ; Биты с 12 по 15 в регистре флагов всегда понятны в процессоре Intel ; 80286 TEST_286: PUSHF ; Флаговый регистр для стекирования POP BX ; Хранить флаги в BX ИЛИ BX, 0F000H ; Убедитесь, что установлено битовое поле НАЖМИТЕ BX ; Для складывания POPF ; И отметить регистр PUSHF ; Флаги для суммирования ХЛОП ТОПОР ; И к ТОПОРУ И AX,0F000H ; Очистить все остальные биты JNZ TEST_386 ; Перейти, если биты не понятны ; На данный момент процессор является 80286 MOV AX,2 ; Код возврата MOV DX, 0 JMP
ID_EXIT ; Выход ;***********************| ; тест на 80386 | ;***********************| ; Бит 18 регистра E flags был введен в процессоре 486 ; Этот бит не может быть установлен в 80386 TEST_386: PUSHFD ; 32-разрядные флаги E для стекирования ВСПЛЫВАЮЩИЙ EAX ; Флаги для EAX ИЛИ EAX, 40000H ; Убедитесь, что установлен бит 18 НАЖМИТЕ EAX ; Новые флаги для суммирования POPFD ; Регистр флагов to E PUSHFD ; Обратно в стек ХЛОП EAX ; И к EAX И EAX, 40000H ; Очистить все, кроме бита 18 JNZ TEST_486 ; Выполнить, если бит 18 чист ; На данный момент процессором является 80386 MOV AX, 3 ; Код возврата MOV DX,0 JMP ID_EXIT ; Выход ;***********************| ; тест на 486 | ;***********************| ; Бит 21 (флаг идентификатора) регистра E flags не может быть установлен в ; 486 TEST_486: 76 Глава 3 PUSHFD ; 32-разрядные флаги E для стекирования ВСПЛЫВАЮЩИЙ EAX ; Флаги для EAX ИЛИ EAX, 200000H ; Убедитесь, что установлен бит 21 НАЖМИТЕ EAX ; Новые флаги для суммирования POPFD ; Регистр флагов to E PUSHFD ; Обратно в стек ХЛОП EAX ; И к EAX И EAX, 200000 Ч ; Очистить все, кроме бита 21 JNZ IS_PENTIUM
; Перейти, если бит 21 чист ; На данный момент процессором является 486 MOV EAX, 4 ; Код возврата MOV EDX,0 JMP ID_EXIT ; Выход ;***********************| ; процессор PENTIUM | ;***********************| IS_PENTIUM: ;***********************| ; используйте CPUID | ;***********************| ПЕРЕМЕЩЕНИЕ EAX,0 CPUID CMP EBX, ’uneG’ Я ЭТО_ИНТЕЛ MOV EAX, 5 ; Относится к типу Pentium JMP ID_EXIT ; но не Intel IS_INTEL: ДВИЖЕНИЕ EAX,1 CPUID ; На данный момент: ; EAX = содержит следующую информацию // xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxxxx <= биты // |<-- игнорируется -------->|--| |---|--| // | | | |___[3-0] идентификатор шага // | |____[7-4] номер модели // |______[11-8] семья ; ДВИЖЕНИЕ EDI, Cid ДВИЖЕНИЕ [EDI], EAX MOV EAX,5 ; Код Pentium ID_EXIT: И EAX,0FH ; Очистить все остальные биты MOV EDI,тип процессора MOV [EDI],EAX } } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функция Id CPU() находится в файле Id CPU.h, расположенном в папке Sample Code\Chapter02\Id CPU в онлайн-программном обеспечении книги. Идентификатор программы CPU.cpp , также находящийся в этой папке, вызывает функцию Id CPU() и интерпретирует результаты.
Машинная арифметика 77 Глава 4 Высокоточная арифметика Краткое содержание главы Эта глава посвящена алгоритмам и функциям, используемым для выполнения фундаментальных арифметических операций над упакованными числами BCD. Мы разрабатываем интерфейсные функции C ++ для сложения, вычитания, умножения и деления многозначных BCD. Глава завершается разработкой высокоточных функций BCD-арифметики, которые позволяют легко манипулировать числами с 34 значащими цифрами. 4.0 Приложения BCD-арифметики Математический сопроцессор Intel и математические модули действительно являются мощными вычислительными инструментами. Эти устройства хранят числа с плавающей запятой и обрабатывают их в соответствии с форматами, определенными в стандарте ANSI/IEEE 754. C и C ++ используют эти стандарты при представлении чисел с плавающей запятой. Тип C/C ++ float соответствует формату ANSI/IEEE singlegle, а тип C / C ++ double соответствует формату ANSI / IEEE double. Таблица 2.2 показывает, что мантисса в стандарте ANSI/IEEE 754 с двойной формат 53 Биичных цифр широкий, к которому мы должны добавить неявное 1-бит. Наибольшее десятичное значение, допустимое в 54 битах, равно 720 575 940 379 277 743, что позволяет представлять до 18 значащих цифр. Такой точности достаточно для многих математических приложений; однако в науке, бизнесе и технике нам иногда необходимо представлять числа, состоящие более чем из 18 значащих цифр. В этом случае программист должен взять на себя задачу кодирования числовых значений и выполнения необходимых вычислений. Одним из вариантов представления числовых значений и выполнения вычислений с более высокой точностью, чем ANSI / IEEE 754, является BCD-арифметика. Основными недостатками BCD-арифметики на главном процессоре по сравнению с вычислениями с плавающей запятой с использованием math unit является то, что BCD-код выполняется намного медленнее и что кодировки занимают больше места. Одним из основных преимуществ разработки арифметических процедур BCD является то, что точность вычислений не ограничена конструкцией аппаратных средств Intel с плавающей запятой . Числовые операции с единицами измерения с плавающей запятой, такими как математические единицы Pentium и MMX, должны выполняться в определенных форматах числовых данных, которые 79 встроены в аппаратное обеспечение. Мы видели, что при использовании современного оборудования с плавающей запятой максимальная числовая точность результата составляет 18 значащих цифр. Использование BCD-арифметики с плавающей запятой является опцией при разработке подпрограмм, которые способны выполнять математические вычисления с любой желаемой точностью. Другое соображение, которое иногда благоприятствует использованию BCD-арифметики, относится к ошибкам округления. Математическая единица является двоичной машиной, и десятичные числа должны быть преобразованы в двоичные перед обработкой. После завершения вычислений результаты должны быть преобразованы обратно в десятичные числа для вывода. Преобразования из двух десятичных чисел в десятичные и из десятичных в двоичные часто приводят к ошибкам, поскольку многие десятичные числа не могут быть точно представлены в двоичном формате. BCD-арифметика, с другой стороны, является десятичной арифметикой. В BCD-арифметике ошибки преобразования не вводятся .



∞. 4. Деление на 0/0 или ∞/∞. 5. Операция с остатком в форме x REM y, в которой x = 0 или y = ∞. Аппаратные средства с плавающей запятой 113 6. Квадратный корень из отрицательного числа. 7. Операции преобразования из двоичных форматов с плавающей запятой в целочисленные или десятичные дляматы, которые приводят к результату, который не может быть точно представлен. 8. Операции сравнения, в которых один или оба операнда являются NaNs. Исключение деления на ноль Это исключение возникает, когда делитель равен нулю, а делимое ненулевое. В соответствии со стандартом результат кодируется как бесконечность. Обратите внимание, что операция 0/0, которая генерирует недопустимое исключение, не считается делением на ноль. Исключение переполнения В таблице 5.2 показаны кодировки экспоненты в базовом едином формате ANSI/IEEE 754, которые варьируются от -126 до +127. Поскольку этот показатель равен смещению 127, максимальный абсолютный показатель равен десятичному значению 254 (11111110B), а минимальный абсолютный показатель равен десятичному значению 1 (00000001B). Кодировки экспоненты 0 (00000000B) и -127 (11111111B) не используются при представлении действительных чисел в базовом едином формате. Анализ допустимых показателей в других форматах подтверждает , что значение цифры экспоненты 00..00B и 11..11B также не входит в допустимый диапазон, назначенный для представления действительных чисел. Этот подход основан на том факте, что любое компьютерное представление действительных чисел обязательно ограничено определенным диапазоном. Числа приближаются к границам этого диапазона по мере того, как они становятся очень большими или очень маленькими. Условие переполнения имеет место всякий раз, когда действительное число превышает представимый диапазон, становясь слишком большим. В базовых и расширенных форматах ANSI / IEEE 754 максимальные представимые значения имеют показатель степени в виде 11..10B и значение 11..11B. Что касается положительных вещественных чисел, добавление наименьшего возможного значения к этой кодировке создает число, превышающее представимый диапазон (переполнение). Стандарт требует, чтобы при обнаружении условия переполнения подавался сигнал об исключении и в результате операции вводилась специальная кодировка. Существует четыре возможных варианта фактического результата, в зависимости от выбранного режима округления, а именно: 1. Если выбрано округление до ближайшего, результат переполнения кодируется как бесконечность с знаком промежуточного результата. 2. Если выбран режим усечения, результат переполнения представляется с помощью кодировки forкодировка mat для наибольшего представимого числа. 3. Если выбрано округление до отрицательной бесконечности, результат положительного переполнения представлен с кодировкой формата для наибольшего представимого значения и отрицательного переполнения с кодировкой для – ∞. 4. Если выбрано округление до положительной бесконечности, результат отрицательного переполнения будет представлен отображается с кодировкой формата для наименьшего представимого значения и положительным переполнением с кодировкой для + ∞. Обратите внимание, что в ANSI / IEEE 754 переполнение всегда происходит внезапно (также называемое внезапным переполнением). Из-за ограничений в представлении действительных чисел там 114 Глава 5 отсутствуют положения для постепенного переполнения. Результат переполнения положительного числаber приводит к +
∞ или в большем представимом положительном действительном, в то время как переполнение отрицательного числа приводит к – ∞ или в наименьшем представимом отрицательном действительном значении. Какое выполняется действие, зависит от выбранного режима округления. Исключение переполнения Условия переполнения возникают, когда абсолютное значение числа становится очень большим. С другой стороны, недостаточный поток имеет место, когда абсолютное значение числа становится очень маленьким, другими словами, когда его значение приближается к нулю. Один из методов обработки чисел, приближающихся к нулю, заключается в том, чтобы сделать их равными нулю. Эта операция, иногда называемая сбросом до нуля, часто использовалась как простое решение проблемы отключения потока. Но это внезапное уменьшение потока создает некоторые специфические проблемы. Например, в уравнении (x–y)+ y = x если y – достаточно большое число, то часть (x-y) может внезапно уменьшиться до нуля, поэтому (0)+y = y и y=x вместо (x–y)+y = x что является ожидаемым результатом. Вы видели, что, согласно положениям стандарта ANSI/IEEE 754 с, переполнение конусловий, обрабатываются резко преобразования результата в бесконечность, или наибольшее представимое реально. Какой метод будет принят, зависит от режима округления в effect. Чтобы избежать опасностей внезапного недофинансирования, стандарт требует использования специального ненормализованного представления действительных чисел, называемого денормальными. Чтобы понять постепенное уменьшение потока, вы должны вспомнить, что представление с плавающей запятой считается нормализованным, когда первая цифра значения отлична от нуля. Нормализация предназначена для сохранения максимального количества значащих цифр и, следовательно, точности сохраняемого значения. Можно сделать вывод , что наименьшее представимое число в любом формате кодируется с ЭКСПОнент узор 00..01B и в значащей части числа от 00..00В. Постепенное уменьшение основано на использовании специальной кодировки для вещественных чисел (так называемых денормальных), которые характеризуются показателем степени в виде 00..00B и денормализованным значением. Это представление, легко идентифицируемое показателем, содержащим все нулевые биты, позволяет представлять числа, меньшие, чем наименьшее значение, которое могло бы быть закодировано с использованием нормализованного значения. Постепенное уменьшение объема становится возможным за счет точности . По мере того, как значение становится денормализованным, количество его значащих цифр уменьшается. ANSI/IEEE 754 требует использования денормализованных представлений, а также постепенного уменьшения количества очень маленьких чисел. Стандарт описывает два соответствующих Аппаратные средства с плавающей запятой 115 отложенные события, которые могут способствовать недостаточному потоку. Одним из них является создание представимого числа, которое все же настолько мало, что может сгенерировать исключение ошибки. Примером является условие переполнения, которое может возникнуть в результате деления на очень маленький операнд. Второе событие - это потеря точности, возникающая в результате представления очень маленьких чисел путем денормализации значимого. Исключение с неточным результатом Неточные результаты могут быть получены в результате многих арифметических операций, выполняемых с допустимыми операндами. Например, операции деления 1/3, 1/7 и 1/9 не могут быть точно представлены в двоичной форме. Это исключение, иногда называемое исключением точности , предназначено как предупреждение о том, что округленный результат предыдущей операции может не быть точно представлен. В большинстве вычислительных ситуаций это наиболее частое исключение, а также условие ошибки, которое чаще всего игнорируется. 116 Глава 5
Глава 6 Данные и преобразования с плавающей запятой Краткое содержание главы В этой главе представлены форматы и стандарты, используемые для хранения числовых данных в устройствах Intel math units. Поддерживаемые типы данных включают целые и действительные двоичные числа и двоичные десятичные дроби с конечным кодом. Глава также включает в себя разработку процедур преобразования для ввода пользовательских данных в математическую единицу и для преобразования числовых данных математической единицы в строки удобочитаемых ASCII-цифр. ..........., 6.0 Форматы данных математических единиц В главе 5 мы рассмотрели форматы хранения данных с плавающей запятой, предписанные стандартом ANSI / IEEE 754 для двоичной арифметики с плавающей запятой. В следующих разделах мы ссылаемся на кодирование числовых данных, выполняемое математическими блоками Intel. Мы упоминали, что 80387 и математический модуль 486 и Pentium являются единственными процессорами Intel для обработки числовых данных, которые полностью соответствуют положениям ANSI / IEEE 754. Хотя все математические модули Intel хранят числовые данные и обрабатывают их в идентичной форме, в этом обсуждении мы используем терминологию, которая содержится в документации Intel для 80387, 486 и Pentium. Следует отметить следующие различия: 1 Числовой тип данных, обозначенный как short real в документации 8087 и 80287 соответствует типу данных одиночной точности 80387, 486 и Pentium. 2. Числовой тип данных, обозначенный как long real в документации 8087 и 80287 соответствует формату двойной точности 80387, 486 и Pentium. 3. Упакованный десятичный тип данных, который фигурирует в документации 8087 и 80287, соответствует упакованному типу данных BCD в 80387, 486 и Pentium. 4. Временный реальный тип данных в 8087 и 80287 соответствует расширенному прецизионному типу данных 80387, 486 и Pentium. Математические модули Intel работают с тремя классами чисел: двоичными целыми числами, десятичными целыми числами и двоичными вещественными числами. В таблице 6.1 перечислены основные характеристики и доступные кодировки для каждого из этих классов чисел. 117 Таблица 6.1 Форматы математических единиц для числовых данных Имя БИТЫ ДИАПАЗОН КОДИРОВАНИЕ ДВОИЧНЫЕ ЦЕЛЫЕ ЧИСЛА: слово целое 16 10 4 ПОЛОЖИТЕЛЬНЫЕ КАК ДВОИЧНЫЕ ЦЕЛЫЕ ЧИСЛА, ОТРИЦАТЕЛЬНЫЕ В ДОПОЛНЕНИИ К ДВУМ короткое целое число 32 10 9 ПОЛОЖИТЕЛЬНЫЕ КАК ДВОИЧНЫЕ ЦЕЛЫЕ ЧИСЛА, ОТРИЦАТЕЛЬНЫЕ В ДОПОЛНЕНИИ К ДВУМ длинное целое число 64 10 18 ПОЛОЖИТЕЛЬНЫЕ КАК ДВОИЧНЫЕ ЦЕЛЫЕ ЧИСЛА, ОТРИЦАТЕЛЬНЫЕ В ДОПОЛНЕНИИ К ДВУМ ДЕСЯТИЧНЫЕ ЦЕЛЫЕ ЧИСЛА: знак экспонента
значение биты биты биты упакованный BCD 80 10 18 1 НЕТ 72 ДВОИЧНЫЕ ВЕЩЕСТВЕННЫЕ ЧИСЛА: знак экспонента значение биты биты биты одинарной точности 32 10 ±38 1 8 23 двойная точность 64 10 ±308 1 11 52 повышенная точность 80 10 ±4932 1 15 64 6.0.1 Двоичные целые числа В модуле Intel math двоичные целые числа могут храниться в трех форматах. Все три имеют идентичную структуру, но разную емкость. Целочисленный формат word (см. таблицу 6.1) занимает два байта, короткий целочисленный формат занимает двойное слово, а длинный интегрированный формат - четырехсловный. Во всех трех форматах старший значащий бит кодирует знак числа. В соответствии с Генеральной конвенцией, знаковый бит 1 представляет собой негродной количество и знаковый бит от 0 положительное число. Положительные числа хранятся в чистом двоичном виде. Отрицательные числа представлены в форме дополнения к двум. На рисунке 6.1 показана общая структура двоичных целых форматов. Разработчики математического модуля обеспечили некоторую совместимость между типами двоичных данных integer и типами данных, используемыми семейством процессоров 80x86. Значение, сохраненное в виде целого числа в виде математической единицы (16 бит), может быть загружено в 16-разрядный регистр 80x86 непосредственно из памяти. Все арифметические команды 80x86 могут работать с числами, закодированными в этом формате. Короткий целочисленный формат может быть загружен непосредственно в 32-разрядный регистр в 80386, 486 и Pentium. Однако значение, сохраненное в формате короткого целого числа, не может быть загружено непосредственно в регистр 8086/8088 или 80286. Программы, работающие с небольшими целыми числами, могут эффективно использовать эту совместимость. Например, графическое приложение, которое работает с адресами пикселей в диапазоне от 0 до ±32 767, может хранить пиксельные данные экрана в целочисленном формате word. Затем пиксельные объявления могут быть загружены непосредственно в математический модуль для необходимых математических вычислений, а затем сохранены обратно в тех же переменных памяти. Поскольку математический модуль включает инструкции по загрузке и сохранению целых чисел в этих форматах, никаких
операций преобразования данных не требуется. В то же время графические маршрутизаторы отображения могут обращаться к данным в переменных памяти для загрузки из них и сохранения в 118 Глава 6 регистры 80x86. Такое совместное использование данных приводит к значительному увеличению производительности, поскольку облегчает вычисления, которые легче выполнять на математическом модуле, в то время как процедуры отображения и другие графические манипуляции могут по-прежнему выполняться с помощью кода 80x86. Рисунок 6.1 Форматы двоичных целых чисел в математических единицах 6.0.2 Десятичные целые числа Тип данных decimal integer содержит единый формат хранения, обозначенный как packed BCD в таблице 6.1. В упакованном формате BCD бит старшего порядка кодирует знак числа. 1-бит кодирует отрицательное число, а 0-бит - положительное число обычным образом. Это контрастирует с двоичными целочисленными форматами, в которых отрицательные числа кодируются с использованием формы дополнения двух. В десятичном целочисленном формате нет поля экспоненты, поскольку формат ограничен целочисленными значениями. Значение состоит из 2 двоичных десятичных цифр, упакованных в каждый байт. Всего в 72-разрядном значении представлено 18 значащих цифр. На рисунке 6.2 показано отображение разрядности десятичной целочисленной кодировки. Рисунок 6.2 Формат математических единиц - десятичных целых Данные и преобразования с плавающей запятой 119 15 31 63 0 0 0 двойки дополняют форму двойки дополняют форму двойки дополняют форму ЦЕЛОЕ СЛОВО КОРОТКОЕ ЦЕЛОЕ ЧИСЛО ДЛИННОЕ ЦЕЛОЕ ЧИСЛО 79 72 78 0 18 упакованных цифр BCD неиспользуемые биты знак числа (0 = положительный, 1 = отрицательный) ДЕСЯТИЧНОЕ ЦЕЛОЕ ЧИСЛО (УПАКОВАННЫЙ BCD) Запоминаемый образ числа, закодированного в виде десятичного целого числа, состоит из 10 байт (80 бит), причем старший байт расположен по адресу памяти с наименьшим номером. Байт старшего порядка кодирует знак числа в его наиболее значимом косяке. Остальные 7 бит в знаковом байте не используются. Следующие 9 байтов содержат 18 упакованных двоично-десятичных цифр, из расчета две цифры на байт. 80-битное упакованное значение BCD может быть удобно сохранено в десятибайтной переменной памяти. Большинство программ на ассемблере, включая MASM от Microsoft, Borland's Turbo Assembler и Intel ASMx86, кодируют десятибайтную переменную, объявленную с помощью директивы DT, как число в десятичном целочисленном формате. Мы должны подчеркнуть, что, в отличие от форматов BCD12 и BCD20, разработанных в предыдущих главах, десятичный целочисленный формат математических единиц не является кодировкой с плавающей точкой. Десятичное целочисленное представление ограничено целыми числами. Возможно, наиболее важным применением этого формата является преобразование двоичных чисел с плавающей запятой в десятичные знаки ASCII. Использование десятичной Интегер формат показано далее в этой главе. 6.0.3 Двоичные числа Строго говоря, термин "действительные числа" - это математическое обозначение множества, включающего все рациональные и иррациональные числа; однако этот термин также используется
для обозначения числа, которое может быть представлено в форме знака с плавающей запятой. Три двоичных вещественных кодировки математических единиц представлены в таблице 6.1. На рисунке 6.3 показана общая структура форматов вещественных чисел. Все три формата вещественных чисел имеют следующие поля: 1. Поле знаковых битов, которое является наиболее значащим битом в кодировке, представляет знак числа. Один бит в поле знака указывает на отрицательное число, а нулевой бит указывает на положительное число. 2. Поле экспоненты кодирует положение двоичной точки значимого. Экспоненциальное кодирование осуществляется в форме смещения. Кодировка экспоненты выполняется в форме смещения. Следовательно, если значение показателя меньше, чем смещение, то показатель отрицательный. Если показатель больше, чем смещение, то он положительный. Если показатель равен смещению, то он равен нулю. 3. Поле significand кодирует значащие цифры числа в виде двоичной дроби. Обычные числа (см. Главу 5) имеют показатель степени в диапазоне от 11..10 до 00..01, а значение представляет собой двоичную дробь в виде 1.xx..xx. Количество цифр в дробной части значимого значения изменяется в разных форматах (см. таблицу 6.2). Целочисленная цифра означаемого неявно указана в формулах одинарной и двойной точностиmats, но явно закодирована в формате расширенной точности. Внутренне математическое устройство хранит все числа в формате расширенной точности двоичной вещественной кодировки. Числа, закодированные в остальных шести форматах (см. таблицу 6.1), включая вещественные с одинарной и двойной точностью, существуют только в памяти. Когда число загружается из переменной памяти в математический модуль, оно автоматически преобразуется в формат расширенной точности. Таким образом, все внутренние вычисления, выполняемые сопроцессором, выполняются с повышенной точностью. Дополнительные биты экспоненты и значащих в этом формате обеспечивают защиту от вычислительных ошибок, которые могут возникнуть в результате операций преобразования и округления 120 Глава 6 во время переполнения и недостаточного расхода. Этот формат расширенной точности также может использоваться для хранения констант и промежуточных результатов. Однако, следуя рекомендациям ANSI / IEEE 754, в литературе Intel рекомендуется не использовать расширенный формат для повышения точности вычислений, поскольку такая практика ставит под угрозу запас прочности, для обеспечения которого он был разработан. В таблице 6.2 приведены параметры формата для двоичных вещественных чисел размером 80x87. Рисунок 6.3 Вещественные форматы математических единиц 6.1 Специальные кодировки для вещественных чисел В главе 5 мы упоминали, что ANSI / IEEE 754 обрабатывает определенные типы чисел отдельно; это: бесконечности, NAN, нули, нормальные и денормальные значения. Модули Intel math реализуют и распознают эти специальные кодировки, а также другие, не упомянутые в стандарте. Обратите внимание, что специальные кодировки применяются только к реальным формулам с именами single, double и extended precision, а не к двоичным или десятичным тегам. Данные и преобразования с плавающей запятой 121 31 63 79 22 51 63 30 62 78 0 0 0 значение (неявный бит целого числа) значение (неявный бит целого числа) значение (явный бит целого числа)
предвзятый показатель предвзятый показатель предвзятый показатель знаковый бит (0 = положительный, 1 = отрицательный) знаковый бит (0 = положительный, 1 = отрицательный) знаковый бит (0 = положительный, 1 = отрицательный) ДВОИЧНАЯ ДЕЙСТВИТЕЛЬНОСТЬ ОДИНАРНОЙ ТОЧНОСТИ ДВОИЧНАЯ ДЕЙСТВИТЕЛЬНОСТЬ ДВОЙНОЙ ТОЧНОСТИ ПОВЫШЕННАЯ ТОЧНОСТЬ БИНАРНЫХ РЕАЛ Таблица 6.2 Двоичные форматы с плавающей запятой ОДИНОЧНЫЙ ДВОЙНОЙ РАСШИРЕННЫЙ ТОЧНОСТЬ ТОЧНОСТЬ ТОЧНОСТЬ знаковый бит 1 1 1 биты экспоненты 8 11 15 максимальный показатель +127 +1023 +16383 минимальный показатель –126 –1022 нормальных числа) смещение экспоненты +127 +1023 +16383 значимые биты 23 52 64 значимые дробные разряды 23 52 -16382 63 (точность) явная двоичная точка НЕТ НЕТ ДА общее количество бит 32 64 80 Мы упоминали, что в форматах одинарной и двойной точности значение кодируется подразумеваемым 1 битом. Эта схема, требуемая ANSI / IEEE 754, позволяет получить точность в одну значащую цифру. Однако стандарт не определяет точный формат для представлений с расширенной точностью. В случае повышенной точности разработчик волен использовать или не использовать неявный встроенный
бит в значимом значении. В таблице 6.3 показаны битовые поля для специальных кодировок математического модуля в реальных форматах одинарной точности и двойной точности. Таблица 6.3 Специальные кодировки одинарной и двойной точности БИТОВЫЕ ШАБЛОНЫ ОБОЗНАЧЕНИЕ ЗНАК ПОКАЗАТЕЛЬ ЗНАЧЕНИЕ НОЛЬ положительный ноль 0 00..00 00..00 отрицательный ноль 1 00..00 00..00 БЕСКОНЕЧНОСТЬ позитивная бесконечность 0 11..11 00..00 отрицательная бесконечность 1 11..11 00..00 НЕНОРМАЛЬНЫЕ положительные ненормальные 0 00..00 с 11..11 до 00..01 отрицательные денормальности 1 00..00 с 11..11 до 00..01 NANS бессрочно 1 11..11 10..00 положительный сигнал NaNs 0 11..11 с 01..11 до 00..01 отрицательный сигнал NANS 1 11..11 с 01..11 до 00..01 позитивные спокойные НаНы 0 11..11 с 11..11 до 10..00 отрицательные тихие NANS 1 11..11 с 11..11 до 10..00 НОРМАЛИ положительные нормальные числа 0 с 11..10 до 00..01 с 11..11 до 00..00
отрицательные нормальные числа 1 с 11..10 до 00..01 с 11..11 до 00..00 одинарная точность 1 бит 8 бит 23 бита двойная точность 1 бит 11 бит 52 бита 122 Глава 6 Разработчики оригинального математического сопроцессора 8087 выбрали явный 1 бит в значении в формате расширенной точности. Эта практика была предварительно применена во всех последующих версиях math unit. Одна из причин наличия явного 1-разрядного значения в формате расширенной точности заключается в том, что это облегчает процедуры математических вычислений . Неявная 1-битная схема, используемая в одинарном и двойном форматах, не применяется к денормальным и нулевым кодировкам, которые всегда должны иметь начальный нулевой бит в значимом значении. Следовательно, при использовании неявной 1-разрядной схемы хранения программное обеспечение должно выполнять дополнительные тесты для показателей, чтобы исключить особые случаи, в которых неявный бит должен быть равен нулю. В таблице 6.4 показаны битовые поля для специальных кодировок математической единицы в реальных форматах расширенной точности. 6.2 Низкоуровневые числовые данные в памяти Мы упоминали, что большинство популярных программ на ассемблере, включая MASM от Microsoft, Borland Turbo Assembler и Intel ASMx86, предоставляют помощь для хранения числовых данных в форматах математических единиц. Это удобный способ создания и инициализации переменных и констант памяти, которые затем могут быть считаны непосредственно в математические сопроцессоры. К счастью, все основные программы на ассемблере, упомянутые выше, работают почти одинаково в отношении хранения числовых данных в памяти. В таблице 6.5 перечислены директивы ассемблера и соответствующие им форматы данных математических единиц. Таблица 6.4 Специальные кодировки повышенной точности БИТОВЫЕ ШАБЛОНЫ ОБОЗНАЧЕНИЕ ЗНАК ПОКАЗАТЕЛЬ ЗНАЧЕНИЕ НОЛЬ положительный ноль 0 00..00 0.00..00 отрицательный ноль 1 00..00 0.00..00 БЕСКОНЕЧНОСТЬ позитивная бесконечность 0 11..11 1.00..00 отрицательная бесконечность 1 11..11 1.00..00 НЕНОРМАЛЬНЫЕ
положительные ненормальные 0 00..00 с 0.11..11 по 0.00..01 отрицательные денормальности 1 00..00 от 0,11..11 до 0,00..01 NANS неопределенный 1 11..11 1.10..00 положительный сигнал NaNs 0 11..11 с 1.01..11 по 1.00..01 отрицательный сигнал NANS 1 11..11 с 1.01..11 по 1.00..01 позитивные спокойные НаНы 0 11..11 с 1.11..11 по 1.00..01 отрицательные тихие NANS 1 11..11 с 1.11..11 по 1.00..01 НОРМАЛИ положительные нормальные числа 0 с 11..10 до 00..01 1.11..с 11 до 1.00..00 отрицательные нормальные числа 1 с 11..10 до 00..01 1.11..с 11 до 1.00..00 Размер поля ..... 1 бит 15 бит 64 бита Данные и преобразования с плавающей запятой 123
6.2.1 Инициализация данных с помощью директивы DW Мы упоминали, что целочисленный тип данных math unit word является единственным числовым форматом, который полностью совместим со всеми процессорами 80x86, и что формат short integer совместим с 80386, 486 и Pentium, но не с его предшественниками. Директиву DW assembler можно использовать для выделения и инициализации двух байтов памяти, таким образом создавая переменную, совместимую с целочисленным типом данных math unit word. Этот comсовместимость между ЦП Слово и математике слово целочисленных форматах могут быть весьма полезны в приложениях, выполнять числовые операции на небольшие целые чисБерс, как упоминалось ранее. Следующий фрагмент кода инструктирует ассемблер инициализировать именованные переменные в формате word integer: ; Инициализировать переменные word числовыми значениями ДЕСЯТЬ DW 10 ; 000AH ДВА ДВ 2 ; 0002H МИНУС 2 DW -2 ; FFFEH (дополнение двойки к 2) МИНУС_10 DW -10 ; FFF6H (дополнение двух к 10) ДЕСЯТЬ ТЫСЯЧ ДВ 10000 ; 2710H MAX_INT DW 0FFFFH ; FFFFH Таблица 6.5 Директивы ассемблера для инициализации числовых данных в памяти ДВОИЧНЫЕ ДИРЕКТИВА ВЕРБАЛЬНЫЙ ЭКВИВАЛЕНТ ЦЕЛОЕ ДЕСЯТИЧНОЕ реальный DW определите слово слово НЕТ НЕТ целое число DD определите двойное слово короткое НЕТ единичный целое число точность DQ определите четырехсловие длинное НЕТ двойное целое точность DT
определение десятибайта НЕТ упакованный расширенный BCD точность 6.2.2 Инициализация данных директивами DD и DQ В таблице 6.5 мы видим, что директивы DD (define double word) и DQ (define quadword) могут генерировать числа, закодированные как двоичное целое число или как вещественное значение с одинарной точностью. Это делает необходимым, чтобы код сообщал ассемблеру, какой формат должен быть создан. Самый простой способ определить два возможных формата - ввести значения с плавающей запятой одинарной точности с явной десятичной запятой. Следующие строки кода иллюстрируют эту операцию. ; Инициализировать переменные с двойным и четырехсловным значением в числовые значения SHORT_INT DD 12345 ; Инициализировать короткое целое число LONG_INT DQ 56789 ; Инициализировать длинное целое число SINGLE_REAL DD 123.45 ; Инициализировать одинарную точность SINGLE_IN_HEX DD 4332200H ; Ввод шестнадцатеричных цифр ДВОЙНАЯ РЕАЛЬНОСТЬ DQ 56789.0 ; Инициализировать двойную точность DOUBLE_EXP DQ -1.3E12 ; Ввод в экспоненциальной системе счисления 124 Глава 6 Обратите внимание, что наличие десятичной точки определяет, в каком формате инициализируется переменная памяти. Например, значение 56789 хранится как длинное целое число, в то время как значение 56789.0 определяет создание вещественного значения двойной точности. Разница в формате хранилища должна учитываться при загрузке переменной в регистр сопроцессора, поскольку код операции FLD используется для загрузки реальных значений, а структура FILD - для загрузки целых чисел. Также обратите внимание, что входные данные могут вводиться в экспоненциальной нотации, как в случае переменной DOUBLE_EXP в предыдущем примере кода. ..........., 6.2.3 Инициализация данных с помощью директивы DT В таблице 6.5 мы видим, что директива DT (define tenbyte) может генерировать число, закодированное как десятичное или как вещественное с расширенной точностью. Также в этом случае мы инструктируем ассемблер сгенерировать действительное число, введя явную десятичную точку или используя экспоненциальную нотацию. Если десятичная точка отсутствует, ассемблер кодирует число в упакованном формате BCD. Создание десятибайтных переменных показано в следующем фрагменте кода. ; Инициализировать десятибайтные переменные числовыми значениями PACKED_DEC DT 123456789 ; Упакованный BCD EXTENDED_PREC DT 123456789.0 ; Действительное с увеличенной точностью РАСШИРЕННОЕ выражение DT
1.23E8 ; Экспоненциальный ввод EXTENDED_IN_HEX DT 7F6ABC749318049EFF3FH ; Шестнадцатеричный ввод Здесь снова программа должна учитывать формат переменной при загрузке ее в сопроцессор. Код операции FLD используется для загрузки вещественного значения расширенной точности , а код операции FBLD - для загрузки упакованной переменной BCD. 6.2.4 Изображение в памяти специальных кодировок В кодировках математических единиц для двоичных вещественных чисел одинарной точности и двойной точности первая цифра означаемого является неявным 1-разрядным значением (см. рис. 6.3)...........). действительные числа. Эта схема кодирования, которая требуется в соответствии с ANSI / IEEE 754, имеет преимущество удвоения числового диапазона представления. Однако, подразумеваемая 1-битных затрудняет КРРсъел памяти образ некоторых специальных кодировок, так как оборудование будет автоматически интерпретировать, что мантисса предшествует 1-бит. С другой стороны, в формате расширенной точности все значащие цифры явно кодируются. Поэтому создать любую из специальных кодировок в формате расширенной точности относительно просто, как показано в следующем фрагменте кода. ; Специальные кодировки в формате повышенной точности ZERO_POS DT 00000000000000000000H НУЛЕВОЕ ЗНАЧЕНИЕ DT 800000000000000000H DENORMAL_POS DT 000000000000000000FFH DENORMAL_NEG DT 8000000000000000FFH NAN_POS DT 7FFF80000000000000FFH NAN_NEG DT 0FFFF80000000000000FFH INFINITY_POS DT 7FFF8000000000000000H INFINITY_NEG DT 0FFFF8000000000000000H ; | | | ; |--|---------------| ; знак и показатель степени -^ ^------- значение Данные и преобразования с плавающей запятой 125 Обратите внимание, что некоторые из приведенных выше кодировок представляют собой произвольный выбор одной из многих допустимых комбинаций знака / экспоненты / значения, которые являются допустимыми в формате. Например, достаточно, поле значения денормального числа в формате расширенной точности может находиться в диапазоне от 7F..FFH до 00..01H. Значение, выбранное в предыдущем фрагменте кода (00..FFH), является лишь одним из многих допустимых значений. Программное обеспечение, которое должно сохранять специальные кодировки в переменных памяти, должно использовать десятибайтные единицы формата расширенной точности, как показано в этом разделе. Это связано с тем фактом, что меньшие поля и неявный 1 бит других реальных кодировок могут привести к изменению представления, поскольку оно хранится в памяти NDP. 6.2.5 Работа с переменными памяти
Программы на ассемблере автоматически отслеживают формат хранения, назначенный каждой переменной с плавающей запятой. Например, команда FLD может использоваться для загрузки переменных одинарной, двойной и расширенной точности, а код операции FILD - для загрузки целых чисел word, short и long. В каждом случае ассемблер проверяет допустимый тип данных и генерирует соответствующий код операции. Таким образом, инструкция: FLD REAL_VAR выполняется сборка без ошибок и выполняется правильно, если переменная REAL_VAR является вещественной с одинарной, двойной или расширенной точностью. По тому же принципу, инструкция: ФИЛД INTEGER_VAR выполняется корректно, если переменная INTEGER_VAR представляет собой слово, короткое или длинное целое число. В порядке исключения может оказаться желательным использовать операнд, который явно не указывает его тип. Например, если регистр ESI указывает на вещественную переменную единственной точности, простое кодирование FADD [ESI] может работать некорректно, поскольку ассемблер не имеет способа определить, является ли переменная, на которую указывает ESI, одинарной или двойной точностью. Решение состоит в том, чтобы сообщить ассемблеру тип переменной с помощью операции переопределения типа переменной . Если, как в приведенном выше примере, регистр ESI указывает на единственную переменную точности, хранящуюся в памяти с двойным словом, команда может быть закодирована в виде: FADD QWORD PTR [ESI] В таблице 6.6 перечислены операторы типа для различных числовых переменных данных. Таблица 6.6 Операторы переопределения типов для числовых данных ПЕРЕМЕННАЯ ДАННЫХ Хранение ПЕРЕОПРЕДЕЛИТЬ ОПЕРАТОР целое слово DW СЛОВО PTR короткое целое DD одинарной точности длинное целое число DQ DWORD PTR QWORD PTR двойной точности упакованный BCD DT ТБАЙТ PTR повышенная точность 126 Глава 6 6.3 Высокоуровневые числовые данные Форматы данных в конкретной реализации языка высокого уровня должны соответствовать форматам данных машинного уровня. Но язык высокого уровня не обязан реализовывать все форматы низкого уровня. В C / C ++ форматы числовых данных зависят от компилятора. Visual C ++ версии 6.0 определяет типы данных, показанные в таблице 6.7. Таблица 6.7 Числовые типы данных Visual C++ ИМЯ ТИПА БАЙТЫ ДРУГИЕ ИМЕНА ДИАПАЗОН ЗНАЧЕНИЙ int 2/4 подписанный, зависящий от системы
подписанный int беззнаковый int 2/4 беззнаковый зависит от системы __int8 1 символ, символ со подписью с -128 по 127 __int16 2 short, short int, подписанный short int с 32 768 по 32 767 __int32 4 подписано, подписано int с 2,147,483,648 по 2,147,483,647 __int64 8 нет -9,223,372,036,854,775,808 по 9,223,372,036,854,775,807 символ 1 символ со знаком с 128 по 127 символ без знака 1 нет от 0 до 255 короткое 2 short int, подписанный short int с 32 768 по 32 767 короткий без знака 2 беззнаковый short int от 0 до 65 535 длинный 4 long int, подписанный long int от 2,147,483,648 до 2,147,483,647 длинный без знака 4 беззнаковый long int от 0 до 4,294,967,295 float 4 нет
3.4E +/– 38 (7 цифр) двойной 8 нет 1.7E +/– 308 (15 цифр) длинный двойной 10 нет 1.2E +/– 4932 (19 цифр) Обратите внимание, что тип данных long double (80-разрядный, точность 10 байт) реализован как double (64-разрядный, точность 8 байт) в Windows 95 / NT и более поздних версиях. Также, что типы int и unsigned int имеют размер системного слова. В MS DOS и Win16 это 2 байта, в 32-разрядных операционных системах размер равен 4 байтам. Код, который должен обеспечивать переносимость, должен полагаться на целочисленные типы размера: __int8, __int16, __int32 и __int64. Также примечательно, что Visual C ++ не предоставляет эквивалентного типа данных для 10-байтового упакованного формата BCD. 6.4 Преобразование числовых данных При разработке числового программного обеспечения обычно необходимо иметь какой-либо способ ввода данных в математический модуль и отображения результатов вычислений. Высокий уровень Данные и преобразования с плавающей запятой 127 языковые программисты могут использовать функции ввода и вывода языков для этой цели. Это возможно, потому что функции C и C ++ заботятся о преобразовании пользовательского ввода в целочисленные форматы ANSI / IEEE или форматы с плавающей запятой. В тестовых программах, разработанных для этой книги и содержащихся на компакт-диске с книгой, мы часто используем функцию C ++ cin() для получения данных с клавиатуры и функцию cout() для отображения сообщений и результатов вычислений. Однако cin и cout, а также соответствующие функции ввода и вывода на языке Си являются программными черными ящиками. Программист практически не контролирует, как выполняется преобразование, и не имеет обратной связи относительно возможных ошибок или потери точности. В следующих разделах мы разрабатываем процедуры для ввода данных в регистры математических единиц измерения и для восстановления данных из регистров математических единиц измерения в удобочитаемые строки ASCII. Мы также разрабатываем низкоуровневые и высокоуровневые интерфейсные подпрограммы для этих основных процедур. 6.4.1 Преобразование данных в ANSI/IEEE 754 Стандарты ANSI / IEEE 754 и 854 требуют, чтобы реализация обеспечивала преобразования между десятичными строками и закодированными числами с плавающей запятой. Однако математический модуль не содержит средств преобразования, и процедуры преобразования обычно не поставляются с аппаратным обеспечением. Отсутствие процедур преобразования является одной из причин, по которой Intel math unit нельзя рассматривать как автономную реализацию стандарта ANSI / IEEE 754. Хотя отсутствие полноценных средств аппаратного преобразования само по себе не должно рассматриваться как нарушение стандарта, поскольку ANSI/ IEEE 754 и 854 предусматривают, что реализация может быть реализована на аппаратном обеспечении, в программном обеспечении или в комбинации того и другого. Стандарты гласят, что “Это среда, которую видит программист или пользователь системы, которая соответствует или не соответствует этому стандарту”. Однако в обоих стандартах также указано, что аппаратные компоненты, которые требуют поддержки программного обеспечения, “не должны считаться соответствующими, кроме этого программного обеспечения”. В разделе 6.2 мы видели, что коду на ассемблере относительно легко инициализировать переменные и определить константы памяти и целочисленные значения, которые могут быть загружены в математический модуль. Но не существует простого или удобного способа преобразования десятичных чисел ASCII, введенных пользователем, в реальные форматы математических единиц и наоборот. На протяжении многих лет Intel предоставляла пакеты поддержки программного обеспечения для каждого из своих математических подразделений. Эти продукты, названные библиотеками поддержки чисел 80x87, включают в себя процедуры преобразования, программные эмуляторы, библиотеки общих
функций и функций с комплексными числами, а также обработчики ошибок....... Основная проблема с библиотеками числовой поддержки 80x87 заключается в том, что они предназначены для использования с линейкой ассемблеров Intel ASMx86. Код нелегко перенести в другие системы разработки высокого или низкого уровня. 6.4.2 Требования к преобразованию в ANSI/IEEE 754 В стандарте ANSI /IEEE 754 перечислены требования к выполнению двоичных преобразований в десятичные и десятичных преобразований в двоичные. Их можно резюмировать следующим образом: 1. Реализация должна обеспечивать преобразования между всеми поддерживаемыми внутренними форматами и по крайней мере одним десятичным форматом. Если число, подлежащее преобразованию, выражается как 128 Глава 6 ±M × 10 ±N где M и N - целые числа, тогда диапазоны десятичного преобразования для одинарного и двойного форматов с плавающей запятой соответствуют приведенным в таблице 6.8. Таблица 6.8 Диапазоны преобразования в стандарте ANSI/IEEE 754 ФОРМАТ Из ДЕСЯТИЧНОЙ В ДВОИЧНУЮ ИЗ ДВОИЧНОЙ В ДЕСЯТИЧНУЮ МАКСИМАЛЬНОЕ M МАКС. N МАКС. M МАКС. N Один 10 9 –1 99 10 9 –1 53 Двойной 10 17 –1 999 10 17 –1 340 2. Когда целое число M в таблице 6.7 выходит за пределы указанного диапазона, разработчик может изменить значащие цифры после девятой в одинарном формате и после семидесятой в двойном формате. Это изменение обычно заключается в замене соответствующей цифры на 0. 3. Преобразования должны быть округлены в соответствии с активным режимом округления, как указано в Стандарте. При округлении до ближайшего четного ошибка не должна превышать 0,47 единиц младшей значащей цифры. Во всех других режимах округления ошибка не должна превышать 0,47 ulps (единицы измерения на последнем месте). В таблице 6.9 приведен правильный диапазон округления для десятичных конверсий. Таблица 6.9 Правильные диапазоны округления в десятичной системе счисления в стандарте ANSI/IEEE 754 ФОРМАТ ИЗ ДЕСЯТИЧНОЙ системы СЧИСЛЕНИЯ В ДВОИЧНУЮ Из ДВОИЧНОЙ В ДЕСЯТИЧНУЮ МАКС M МАКС N МАКС M МАКСИМАЛЬНОЕ Число Один
10 9 –1 13 10 9 –1 13 Двойной 10 17 –1 27 10 17 –1 27 4. Преобразования должны быть монотонными, то есть увеличение значения операнда должно не уменьшать значение преобразованной строки. Монотонность обычно связана с точностью процедуры преобразования. 5. Когда включен режим округления до ближайшего, преобразование из двоичной системы в десятичную и возврат к двоичному коду всегда должен приводить к одному и тому же числу, при условии, что десятичная строка выполняется с максимальной точностью, показанной в таблице 6.7. 6. При преобразовании десятичного числа в двоичный формат процедура преобразования должна определять переполнение и недостаточный поток. Если это так, то код должен выполнять действия, указанные стандартом. Требуемое действие состоит в обращении к обработчику ошибок, возможно, с скорректированным показателем степени, и результат округляется до точности назначения. Если результат слишком выходит за пределы диапазона, чтобы можно было выполнить такую настройку, то вместо этого следует использовать тихий NaN. . Разработчик процедуры преобразования, целью которой является соответствие стандарту ANSI / IEEE 754 , должен уделять пристальное внимание некоторым из вышеуказанных требований. Что касается кода, который выполняется в Intel математика единиц, использование расширенной точности формате помощники в обеспечении требуемой точности, так как стандарт не требуют конверсий превышать двойной точности формат для чисел. Данные и преобразования с плавающей запятой 129 Библиотеки MATH16 и MATH32 на компакт-диске с книгой содержат низкоуровневые процедуры преобразования, разработанные авторами. FPU_INPUT преобразует введенную пользователем десятичную строку ASCII в двоичную с плавающей запятой и загружает число в верхний регистр стека математических единиц. FPU_OUTPUT сохраняет верхний регистр стека FPU в виде десятичного числа ASCII. Также предусмотрена процедура поддержки с именем ASCII_TO_EXP . Эта последняя процедура преобразует десятичное число ASCII в экспоненциальную (научную) нотацию. Операция полезна, потому что FPU_INPUT asпредполагает, что десятичное число ASCII уже находится в экспоненциальной форме. Все три подпрограммы содержатся в модуле UN16_4 библиотеки MATH16 и модуле UN32_4 библиотеки MATH32 на компакт-диске книги. Обратите внимание, что 32-разрядные версии этих процедур имеют начальное подчеркивание в именах процедур, чтобы сделать их доступными для кода C ++. Обращаясь к этим процедурам, мы исключаем начальный символ подчеркивания. 6.4.3 Процедура FPU_INPUT Эта процедура загружает десятичное число с плавающей запятой в экспоненциальной записи в верхний регистр стека математических единиц. Это также косвенное средство для преобразования десятичных входных данных в экспоненциальной форме в любой из целочисленных математических единиц или в вещественный формат с плавающей запятой . Это возможно благодаря тому факту, что содержимое вершины стека математических единиц может храниться в памяти в виде целого числа (коды операций FIST, FISTP, FBSTP) или в виде вещественного числа с плавающей запятой (коды операций FST и FSTP). Следующий фрагмент кода показывает
эту операцию. .ДАННЫЕ ASCII_DEC DB 28 DUP(00H); Хранилище для десятичных чисел ; в экспоненциальной форме SINGLE_PREC DD ? ; Хранилище для единичной точности ; действительное число .КОД . . . LEA EDX, ASCII_DEC ; Указатель на сохраненную десятичную дробь ВЫЗОВ FPU_INPUT ; Процедура преобразования и загрузки ; Верхний регистр стека FPU теперь имеет входной номер в расширенном ; прецизионном реальном формате FST SINGLE_PREC ; Хранить верхнюю часть стека как единственную ; действительная точность . . . Процедура FPU_INPUT сообщает вызывающей стороне, что операция прошла успешно, снимая флаг переноса и загружая значение 0 в регистр AL. Если установлен флаг переноса, то AL содержит один из следующих кодов ошибки: AL = 1, если входные данные не были отформатированы в экспоненциальной системе счисления. В этом случае в ST(0) загружается специальная кодировка NAN. 130 Глава 6 AL = 2, если показатель превышает допустимую ошибку диапазона. Если превышение представляет собой переполнение, в ST(0) загружается специальная кодировка для положительной бесконечности. Если недостаточный поток, то специальная кодировка для отрицательной бесконечности находится в ST(0). AL = 3, если строка вызывающего объекта содержит один или более недопустимых символов. В этом случае в ST(0) загружается специальная кодировка для NAN. Алгоритм преобразования может быть описан следующим образом: 1. Число, представленное строкой цифр ASCII в экспоненциальной системе счисления, сначала разделяется по его полям exponent и significand. Показатель степени хранится в виде двоичного значения , а целочисленное значение - в виде строки упакованных цифр BCD. 2. Код проверяет величину экспоненты на наличие значений, которые превышают диапазон длямат. Если это так, загружается специальная кодировка (+ или – бесконечность), и вызывающему абоненту сообщается об ошибке ввода. Недопустимые символы в строке вызывающего объекта определяют, что кодировка для NaN загружена в FPU, и вызывающему объекту помечается недопустимый ввод. 3. Значение целого числа BCD затем загружается в математическую единицу и делится на 10 17 в илинажмите, чтобы масштабировать его до числа в диапазоне 0 > значение >10 4. Константа 10 возведена в степень целого показателя без знака (y). Значение умножается на 10 y , если показатель положительный, и делится на 10 y , если он отрицательный тивный. Результат остается в ST(0). Вычисление 10 y
Алгоритм загрузки строки цифр ASCII в верхний регистр стека математических единиц требует вычисления 10 y , где y - показатель степени числа, подлежащего преобразованию. В литературе встречается несколько подходов к этому вычислению. Возможно, наиболее распространенным подходом является использование логарифмов. Проблемы, возникающие в результате логарифмической аппроксимации и других методов вычисления экспоненциальных функций, обсуждаются в главе 8. Что касается процедур преобразования, то использование логарифмической аппроксимации для вычисления 10 y вносит значительную ошибку. При работе, близкой к пределам временного реального формата математической единицы, ошибка может распространиться на 12-й значащий бит результата. Например, преобразование значения ASCII 1.0 E+4922 с использованием логарифмического приближения mic для вычисления 10 4922 выдает число 9.99999999999999502 E+4921. В этом случае ошибка в логарифмическом приближении равна 10 y испортила результат до такой степени, что он превышает ошибку, допускаемую стандартом ANSI / IEEE 754. Проблему можно устранить, используя более точное приближение значения 10 y . Процедура преобразования, используемая процедурой _FPU_INPUT, которая основана на наборе констант, вычисляет значение 10 y с точностью до 63-го значащего бита. В этом случае строка ASCII 1.0 E+4921 выдает число 1.00000000000000000 E+4921 который является точным с точностью до десятичной цифры младшего порядка. Данные и преобразования с плавающей запятой 131 Операции после преобразования FPU_INPUT выполняет все вычисления с использованием формата расширенной точности math unit для действительных чисел. Поскольку стандарт ANSI /IEEE 754 не требует преобразований, превышающих точность двойного формата, использование повышенной точности может показаться расточительным и ненужным. В действительности использование повышенной точности обеспечивает простой и эффективный способ гарантировать, что преобразование соответствует требованиям стандарта ANSI/IEEE 754 . Все, что необходимо, - это чтобы код сохранил значение, возвращаемое процедурой FPU_INPUT, в переменной двойной точности, а затем перезагрузил ST(0) из этого хранилища. Последовательность сохранения / перезагрузки автоматически гарантирует выполнение следующих требований стандарта ANSI / IEEE 754. 1. Округление до реального формата двойной точности будет выполнено в соответствии с настройкойнастройка поля управления округлением управляющего слова математической единицы. 2. Исключения переполнения и недостаточного потока будут правильно закодированы, и обработчик ловушек получит управление, если биты ответа на исключение управляющего слова установлены соответствующим образом. 3. Более высокая точность расширенного формата обеспечивает монотонность результата, сохраняемого в переменной двойной точности. Кроме того, использование повышенной точности в процедурах преобразования гарантирует, что во время обработки не будет превышен допуск ошибки 0,47 ulps для округления до ближайшего и 1,47 ulps для всех других режимов округления. 6.4.4 Процедура FPU_OUTPUT Процедура FPU_OUTPUT преобразует значение в верхнем регистре стека математических единиц в десятичное число ASCII, отформатированное в экспоненциальной системе счисления. _FPU_OUTPUT также предоставляет косвенное средство для преобразования двоичного числа, хранящегося в памяти в одном из форматов FPU, в десятичное число ASCII в экспоненциальной форме. Следующий фрагмент кода показывает необходимые операции. .ДАННЫЕ ; ХРАНЯЩИЙСЯ в ЕДИНСТВЕННОМ ЭКЗЕМПЛЯРЕ DD
4332200H ; Действительный с одинарной точностью OUTPUT_BUF DB 25 DUP (00H); Хранилище для десятичных чисел DB 0H .CODE . . FLD STORED_SINGLE ; Загрузить вещественное значение одинарной точности ; в стек FPU LEA EDX,OUTPUT_BUF ВЫЗОВ _FPU_OUTPUT ; Буфер OUTPUT_BUF теперь содержит десятичную строку цифр ASCII ; которые представляют значение сохраненного реального значения с одинарной точностью В _FPU_OUTPUT процедура возвращает нести флаг ясно, если конверсией был успешным, и число в St(0) относится к категории нормальных. Если нет, то устанавливается флаг переноса и регистр AL выглядит следующим образом: AL = 1, если число в ST(0) является положительным или отрицательным денормальным значением или если его формат не поддерживается. В этом случае буфер вызывающего объекта содержит строку: “denormal”. 132 Глава 6 AL = 2, если число в ST(0) является положительной бесконечностью. В этом случае буфер вызывающего объекта содержит строку: “+бесконечность”. AL = 3, если число в ST(0) равно отрицательной бесконечности. В этом случае буфер вызывающего объекта содержит строку: “–бесконечность”. AL = 4, если число в ST(0) является положительным или отрицательным NAN. В этом случае буфер вызывающего абонента содержит строку: “NAN (not-a-number)”. AL = 5, если ST(0) помечен как пустой. В этом случае буфер вызывающего объекта содержит строку: “ПУСТО”. Нули со знаком отображаются следующим образом: 0.00000000000000000 E + 0 представляет положительный ноль -0.00000000000000000 E + 0 представляет отрицательный ноль. ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Низкоуровневые процедуры преобразования FPU_INTPUT и FPU_OUTPUT, описанные в предыдущих разделах, находятся в модуле Un32_4 MATH32.LIB, в онлайновом программном обеспечении книги. Процедуры поддержки расположены в модуле Un32_1. 6.4.5 Преобразование ASCII в экспоненциальный Процедура преобразования входных данных FPU_INPUT требует, чтобы входное значение было в экспоненциальной форме. Десятичная строка ASCII должна быть введена в формате, аналогичном научной нотации. Например, значение -128.765 должно быть введено в форме -1.28765 E+2. Процедура с именем ASCII_TO_EXP преобразует строку десятичных чисел ASCII в числовую систему счисления. Процедура ASCII_TO_EXP также обнаруживает и обходит число, которое уже находится в экспоненциальной форме. Это позволяет передавать строки либо в ASCII десятичной, либо в экспоненциальной форме. Если строка не в экспоненциальной записи, она преобразуется. Если он уже находится в экспоненциальной форме, он копируется в выходной буфер. 6.5 Высокоуровневые функции интерфейса Папка Sample Code\Chapter6\Mu Conversations на компакт-диске с книгой содержит файл Un32_4.h. Этот заголовок содержит три функции C ++, которые обеспечивают доступ к процедурам преобразования, упомянутым в предыдущих разделах. В рамках проекта Mu Conversions демонстрируются и реализуются следующие функции преобразования: ...........: 1. FpuInput() предоставляет доступ к процедуре _FPU_INPUT в MATH32.lib. 2. FpuOutput() предоставляет доступ к процедуре _FPU_OUTPUT в MATH32.lib. 3. AsciiToExp() предоставляет доступ к процедуре _ASCII_TO_EXP в MATH32.lib.
Ниже приведен список функций. Данные и преобразования с плавающей запятой 133 int FpuInput(номер символа[]) { // При вводе: // параметр number[] - это определяемый пользователем массив, содержащий // Десятичное число в формате ASCII, отформатированное в экспоненциальном или // десятичная система счисления. // // Экспоненциальное обозначение следующим образом: // // см.mmmmmmmmmmmmmmmmmm ESeeee // где // s = знак числа (пробел = +) // m = до 18 значащих цифр. Дополнительные цифры игнорируются // . = десятичная точка, следующая за первой значащей цифрой. // Если в строке нет десятичной точки, предполагается, что // E = явная буква E (или e) для обозначения начала экспоненты // S = знак + или - экспоненты. Если знака нет, предполагается положительное значение // e = до четырех цифр экспоненты. Числовое значение // показатель степени не может превышать +/- 4932 // Для разделения последней цифры // значения и начала показателя степени можно использовать один или несколько пробелов // Примеры ввода: // 1.781252345E-1 // -3.14163397 E+0 // 1.2233445566778899 e1387 // 11.2233 // -0.004455 // При выходе: // Значение пользователя загружается в регистр математической единицы ST(0) // Предыдущие значения в стеке сдвигаются на единицу вниз // зарегистрировать // возвращаемое значение = 0 (ошибки нет) // // b. Если возвращаемое значение не равно нулю, возвращаемое значение указывает на // следующие ошибки // 1 если в строке вызывающего абонента нет символа E или e // Кодировка NAN загружена в ST(0) // 2 если показатель превышает допустимый диапазон // Переполнение - положительная бесконечность загружается в ST(0) // Недостаточный поток - отрицательная бесконечность загружается в ST(0) // 3 если в имени вызывающего абонента присутствует недопустимый символ // строка. Кодировка NAN загружается в ST(0) //
// ТОПОР уничтожен. Все остальные регистры сохранены. // Среда NDP вызывающего абонента сохраняется, за исключением // верхний регистр стека. int rtnError = 0; _asm { ПЕРЕМЕЩЕНИЕ EDX, номер ПОЗВОНИТЬ FPU_INPUT ; Код ошибки захвата MOV Ошибка rtnError, EAX } возвращает rtnError; } аннулирует AsciiToExp(символ ascStr[], символ expStr[]) { 134 Глава 6 // Проверяет наличие входной строки ASCII и преобразует ее в // экспоненциальную форму // При вводе: // ascStr[] представляет собой десятичную строку в формате ASCII, заканчивающуюся нулем // байт, который содержит десятичный код вызывающего объекта в формате ASCII // ввод // Примечание: Буфер вызывающего объекта может содержать знак $ // и запятые в качестве символов форматирования. Строка // терминатору (00H) может предшествовать CR // управляющие коды (0DH) или LF (0AH). Если // в строке присутствуют символы E или e, то // процедура предполагает, что строка уже // в экспоненциальной системе счисления // // expStr[] представляет собой 27-байтовый неформатированный выходной буфер, который будет // заполняется выходной строкой, отформатированной в // экспоненциальной форме // // При выходе: // expStr[] является выходным буфером вызывающего устройства с номером ввода // переформатирован в экспоненциальную форму следующим образом: // // см.mmmmmmmmmmmmmmmmmm ESeeee // где // s = знак числа (пробел = +) // m = 18 значащих цифр // . = десятичная точка, следующая за первой значащей цифрой // E = буква E сигнализирует о начале поля экспоненты // S = + или - знак экспоненты // e = до четырех цифр экспоненты // // ПРИМЕЧАНИЕ: //
Эту функцию можно использовать, чтобы убедиться, что входная строка // передаваемый в функцию FpuInput() форматируется в экспоненциальном // обозначение. _asm { ДВИЖЕНИЕ ESI, ascStr ПЕРЕМЕЩЕНИЕ EDI, expStr ВЫЗОВ ASCII_TO_EXP } возврат; } int FpuOutput(символ userBuf[]) { // Преобразует значение в верхнем регистре FPU // складывается в десятичное число ASCII по-научному // обозначения // // При вводе: // userBuf[] - это неформатированная буферная область вызывающего объекта // с объемом памяти не менее 25 байт // // ST(0) содержит значение, подлежащее преобразованию // // При выходе: // Возвращаемое значение = 0 // ST(0) принимает значение преобразованного Данные и преобразования с плавающей запятой 135 // Буфер вызывающего объекта будет содержать десятичный код в формате ASCII // представление числа в верхней части стека FPU // регистр, отформатированный в научной нотации следующим образом: // // sm.mmmmmmmmmmmmmmmm ESeeee // где // s = знак числа (пробел = +) // m = 18 значащих цифр // . = десятичная точка, следующая за первой значащей цифрой // E = явная буква E для обозначения начала экспоненты // S = + или - знак экспоненты // e = до четырех цифр экспоненты // Примеры вывода: // 1.78125234500000000 E-12 // -3.14163397000000000 E+0 // 1.22334455667788998 E+1388 // Возвращаемое значение = 1 // Недопустимое значение в верхней части стека // // Другие типы номеров возвращают следующие текстовые строки из 12 символов // в буфере вызывающего абонента // + ненормальный // - ненормальный
// + NAN // - NAN // + бесконечность // - бесконечность // не поддерживается // ПУСТО // Нули со знаком отображаются следующим образом: // 0.00000000000000000 E+0 (положительный ноль) // -0.00000000000000000 E+0 (отрицательный ноль) // // Математическая среда вызывающего устройства сохраняется //******************************************************************** int rtnError = 0; _asm { ПЕРЕМЕЩЕНИЕ EDX, пользовательский интерфейс ВЫЗОВ FPU_OUTPUT JNC FPU_EXIT // Ошибка ПЕРЕМЕЩЕНИЯ rtnError,1 FPU_EXIT: } возвращает rtnError; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Программный проект FP Conversations демонстрирует функции интерфейса C ++. Функции. Файлы проекта находятся в папке Sample Code\Chapter06\FP Conверсии, все в онлайновом программном обеспечении книги. 136 Глава 6 Глава 7 Архитектура математического модуля и набор инструкций




числовые функции от 0 до 2 63 радиана. Начиная с 2 63 равно приблизительно 9,22 × 10 18 , многие процедуры обработки чисел могут выполнять вычисления без какого-либо предварительного тестирования предельного диапазона или уменьшения аргумента. Intel задокументировала, что в 80387 и математическом модуле 486 и Pentium приведение аргумента к первому октанту выполняется внутренне, используя константу более высокой точности для модуля p/4, чем может быть представлено внешне . По этой причине нежелательно использовать процедуры сокращения аргументов, не подписанные для 8087 и 80287, при разработке кода, который будет использоваться исключительно в 80387 или математическом модуле 486 и Pentium. Вычисление тригонометрических функций обсуждается в главе 8. Логарифмическими трансцендентными примитивами являются FYL2X (y умножить на логарифмическую базу 2 x) и FYL2XP1 (y умножить на логарифмическую базу 2 x плюс 1). Обе инструкции используют двоичный код. Логарифмы по другим основаниям вычисляются с помощью формулы log b (x) = log b (2) · log 2 (x) Поскольку этого требует приведенная выше формула, операция умножения встроена в коды операций математического модуля FYL2X и FYL2XP1. Вычисление логарифмов обсуждается в главе 8. В таблице 7.10 перечислены и описаны трансцендентные наставления. Т е и н т е л м а т у н и т с к о н т а и н а с и н г л е т р а н с к е н д е н т а л и н с т р у к т и о н для возведения в степень, названного F2XM1 (2 к x минус 1), хотя инструкция FSCALE может быть использована для возведения 2 в целочисленную степень. В 8087 и 80287 аргумент для команды F2XM1 должен находиться в диапазоне от 0 до 1/2. В 80387 и математическом модуле 486 и Pentium аргумент был расширен до диапазона от -1 до +1. Фундаментальной функцией возведения в степень, необходимой в высокоуровневых языках программирования и общей обработке чисел, является операция y x . Процедуры возведения в степень зубцы, включая один для получения y x , описаны в главе 8. Все трансцендентные инструкции предполагают, что аргументы являются действительными и находятся в диапазоне. Денормальные, ненормальные значения, бесконечности и NAN считаются недопустимыми. Некоторые функции принимают нулевой операнд, в то время как для других функций ноль находится вне диапазона. Важно, чтобы код удостоверял действительность и диапазон операнда, поскольку допустимые или выходящие за пределы диапазона значения приводят к неопределенному результату, не сигнализируя об исключении ception. Трансцендентные алгоритмы Вплоть до 1993 года корпорация Intel не публиковала много информации об алгоритмах, используемых математическим подразделением внутри компании при вычислении трансцендентных значений или других примитивов и функций. Палмер и Морс в своей книге The 8087 Primer (см. Библиографию) упоминают, что в оригинале 8087 трансцендентные значения были получены с использованием вариации CORDIC (цифровой компьютер с координированным вращением) алгоритма, впервые опубликованного в 1971 году (см. Библиографию). Модификация CORDIC заключалась в уменьшении размера таблицы констант, необходимых для вычислений, и использовании рационального приближения к концу обработки. 174 Глава 7 Таблица 7.10
Трансцендентные инструкции по математическим единицам МНЕМОТЕХНИКА ОПЕРАЦИЯ ПРИМЕРЫ FCO Вычисляет косинус вершины стека и FCOS (80387) возвращает значение в ST. |ST| < 2 63 . Входные данные в радианах FSIN Вычисляет синус вершины стека и ФСИН (80387) возвращает значение в ST. |ST| < 2 63 . Ввод в радианах FSINCOS (80387) Вычисляет синус и косинус ST. FSINCOS Синус появляется в ST, а косинус - в ST(1). |ST| < 2 63 . Ввод в радианах. Тангенс = синус/косинус FPATAN Частичный арктангенс. Вычисляет ARCTAN m= (Y/X), X - это ST, а Y - это ST(1). X и Y должны соблюдать 0<Y<X<+ ∞. Стек выскакивает. X и Y уничтожены. 1 в радианах. Результат имеет знак ST(1) и должен быть < B FPTAN Частичная касательная. Вычисляет Y/X = TAN m в точке ST должен находиться в диапазоне 0 ≤ m< p/4. Y возвращается в ST, а X в ST(1). ошибка уничтожена. Ввод в радианах. Результат находится в диапазоне |0| < 2 FPATAN FPTAN 63 FYL2X Вычисляет Z = логарифмическая база 2 из X. X - это значение в ST и Z в ST(1). Извлекается стек, и Y находится в ST. Операнды должны быть в диапазоне 0 < X < ∞ и–∞<Y <+ ∞ ФАЙЛ2XP1 FYL2X
Вычисляет Z = логарифмическая база 2 из (X+1). FYL2XP1 X находится в ST и должен находиться в диапазоне 0 < | X | < (1– √2/2). Y находится в ST(1) и должна находиться в диапазоне – ∞ < Y < ∞. Стек извлекается, и Z находится в ST F2XM1 Вычисляет Z = 2 x – 1. F2XM1 X находится в ST и должен находиться в диапазоне 0 ≤ x ≥ 0,5 радиана. Результат заменяет x в ST Математика архитектура блока и набора команд 175 В 1993 году Intel опубликовала Руководство пользователя процессора Pentium (см. Библиографию phy). Том 3 этой работы, озаглавленный "Руководство по архитектуре и программированию ", содержит приложение G, Отчет о трансцендентных функциях. Это приложение включает в себя summarydiscussionon трансцендентные понятия. По этому вопросу в книге Intel упоминается альтернатива CORDIC, которая называется алгоритмом на основе полиномов и описана Коди и Уэйтом в их книге Software Manual for the Elementary Functions (см. Библиогрописание). Трансцендентные алгоритмы, используемые Pentium, описываются как нечто среднее между CORDIC и методом, основанным на полиномиалах. В случае Pentium таблица функций, хранящаяся в ПЗУ, используется для сокращения вычислений, требуемых методом, основанным на полиномиалах. В прошлом табличные полиномиальные алгоритмы использовались в математических пакетах программного обеспечения. Метод хорошо описан Таном в двух статьях, опубликованных в ACM Transactions on Mathematical Software (см. Библиографию). Инновация Pentium заключается в реализации этих алгоритмов в аппаратном обеспечении. Преимущества, упомянутые Intel, относятся к следующим элементам: Точность. Этот элемент измеряется в единицах ошибки последнего места или ulps. Ошибка в ulps определяется формулой где f(x) - точное значение функции, F(x) - вычисленное значение, а k целое число, такое, что 1 ≤2 -к f(x) < 2 По данным Intel, наихудшая ошибка при вычислении трансцендентных функций в процессоре Pentium составляет 1 ulp при округлении до ближайшего режима и 1,5 ulp во всех других режимах округления. Эта степень точности представляет собой улучшение на 2-3 ulps по сравнению с математическим модулем 486. Intel не предоставила никакой информации относительно сравнительной точности других математических единиц. Монотонность. Этот атрибут ссылается на функцию, значение которой всегда изменяется в том же направлении, что и аргумент. Другими словами, если аргумент больше, функция также больше, и наоборот. В этом случае монотонность является результатом точности вычислений. Документация Pentium гарантирует, что переходные стандартные функции монотонны во всей своей области. Доказательство правильности. Алгоритм, используемый при вычислении функций , делает возможным строгий и простой анализ ошибок. Документ Intel, упомянутый в начале раздела, содержит сводку проверки для каждой из
функций, вычисляемых Pentium. 176 Глава 7 63 () () 2 k fx Fx u − − = Производительность. В документации Intel говорится, что трансцендентные алгоритмы, используемые в Pentium, приводят к повышению производительности. Типичные значения варьируются от 54 до 115 тактовых циклов . 7.2.5 Постоянные инструкции Постоянные инструкции математического модуля используются для загрузки числовых значений, которые обычно необходимы в математических вычислениях. Все постоянные инструкции работают с верхним регистром стека. Инструкции в этой группе удобны, поскольку эти и другие константы могут быть созданы и загружены из переменных памяти, как описано в главе 6. Преимущества использования внутренних констант заключаются в том, что они упрощают программирование и повышают скорость выполнения. Константы загружаются так, как если бы они были определены в формате расширенной точности. Это гарантирует, что они будут с точностью примерно до 19 знаков после запятой. В таблице 7.11 перечислены и описаны постоянные инструкции математической единицы измерения. Таблица 7.11 Постоянные инструкции математической единицы МНЕМОТЕХНИКА ОПЕРАЦИЯ ПРИМЕРЫ FLDLG2 Загрузите логарифмическое основание 10 из 2 на Сверху стека FLDLG2 . Константа с точностью до 64 бит (приблизительно 19 цифр) Log 10 2 = 0.30102... FLDLN2 Загрузите логарифмическое основание e, равное 2, на Сверху стека FLDLN2 . Константа с точностью до 64 бит (приблизительно 19 цифр) Log e 2 = 0.69315... FLDL2E Загрузите логарифмическое основание 2 из e на FLDL2E сверху стека. Константа с точностью до 64 бит (приблизительно 19 цифр) Log 2 e = 1.44268... FLDL2T Загрузите основание логарифма 2 из 10 на . Константа с точностью до Сверху стека FLDL2T
64 бит (приблизительно 19 цифр) Log 2 10 = 3.32192... FLDPI Загрузите p в верхнюю часть стека. Константа FLDPI с точностью до 64 бит (приблизительно 19 цифр) имеет значение 3,14159 ... FLDZ Загрузите ноль в верхнюю часть стека. Константа FLDZ имеет точность до 64 бит (приблизительно 19 цифр) FLD1 Загрузите +1.0 на вершину стека. Константа FLD1 имеет точность до 64 бит (приблизительно 19 цифр) Архитектура математического модуля и набор команд 177 7.2.6 Инструкции по управлению процессором Подобно постоянным инструкциям, инструкции управления процессором не выполняют никаких числовых вычислений. Их назначение - настроить процессор на желаемый режим работы, считывать его состояние во время вычислений и вносить коррективы в стековые регистры. Альтернативная мнемоническая форма (БЕЗ ОЖИДАНИЯ) предусмотрена для использования в подпрограммах, которые должны выполняться при обстоятельствах, когда время может быть критическим фактором. Используя форму "БЕЗ ожидания", программист заставляет ассемблер не ставить перед кодом операции управления процессом префикс "обычное ожидание". Специальная мнемоника обозначается буквой N, например, FINIT и FNINIT. Кроме того, в igформе NO WAIT отсутствуют замаскированные числовые исключения. Форма "Без ожидания" также требуется в коде , который не может предполагать, что математическая единица доступна в системе. При отсутствии математического модуля мнемоника ожидания может привести к зависанию компьютера. Этот метод кодирования показан в процедуре ID_FPU, перечисленной в главе 5. Инструкции по управлению процессором приведены в таблице 7.12. Таблица 7.12 Команды управления процессором математического блока МНЕМОТЕХНИКА УПРАВЛЕНИЕ ПРИМЕРЫ FCLEX Очистить флаги исключений, исключение FCLEX FNCLEX статус и флаг занятости в статусе FNCLEX слово FDECSTP Уменьшите значение поля верхнего указателя стека FDECSTP в слове состояния. Если поле = 0 , то оно изменится на 7. В результате происходит поворот стека FDISI Отключите прерывания, установив mask. FDISI FNDISI Никаких действий в 80287 и 80387 не предпринято
ФНДИСИ (8087) FENI Включите прерывания, сняв флажок FENI FNENI маска в регистре управления. ФНЕНИ (8087) Никаких действий в 80287 и 80387 FFREE Измените тег регистра назначения FFREE ST(2) на ПУСТОЙ FINCSTP Добавьте единицу в поле stack top в FINCSTP слово состояния. Если поле = 7, то оно изменится на 0. В результате происходит поворот стека FINIT Инициализировать процессор. Управляющее слово FINIT (продолжает) 178 Глава 7 Таблица 7.12 Инструкции по управлению процессором математического блока (продолжение ) МНЕМОТЕХНИКА ОПЕРАЦИЯ ПРИМЕРЫ FNINIT имеет значение 3FFH. Регистры стека FNINIT помечены как ПУСТЫЕ. Флаги исключений сняты. Все исключения замаскированы. Округление установлено до ближайшего четного. Точность установлена на 64 бита. Номер регистра 0 является верхним в стеке FLDCW Загрузить переменную памяти (word) в FLDCW CTRL_WORD регистр управления FLDENV Загрузите 14-байтовую среду из Область хранения памяти FLDENV MEM_14 . Среда должна была быть ранее сохранена с помощью FSTENV FNOP Операция с плавающей запятой не выполняется. FNOP ПЕРВЫЙ Восстановить состояние из 94-байтовой области памяти FRSTOR MEM_94 , ранее записанной с помощью FSAVE или FNSAVE СОХРАНИТЬ Сохранить состояние (среду и стек Сохранить MEM_94
FNSAVE записывается) в 94-байтовую область в памяти FSETPM Устанавливает адресацию в защищенном режиме FSETPM (80287) для систем 80287. Интерпретируется как FNOP в 80387 FSTCW Сохранить регистр управления в памяти FSTCW CTRL_WORD FNSTCW переменная (слово) FSTENV Сохраните 14-байтовое окружение в FSTENV MEM_14 FNSTENV область хранения памяти. Смотрите FLDENV FSTSW Сохранить регистр состояния в памяти FSTSW STAT_WORD FNSTSW переменную (word) В 80387, 486 и Pentium можно закодировать FSTSW AX ОЖИДАНИЕ Альтернативная мнемоника для ОЖИДАНИЯ. FWAIT Должен использоваться с эмуляторами Intel Архитектура математического модуля и набор инструкций 179 Глава 8 Трансцендентные примитивы Краткое содержание главы В этой главе мы обсуждаем проектирование и разработку примитивных подпрограмм для вычисления экспоненциальных, тригонометрических и логарифмических функций в модуле Intel math unit. В этой главе мы обсуждаем проектирование и разработку примитивных подпрограмм для вычисления экспоненциальных, тригонометрических и логарифмических функций. Эти подпрограммы будут выполнять фундаментальное вычисление трансцендентных функций, требуемых в типичном пакете с плавающей запятой, математическом приложении или языке высокого уровня. 8.0 Разработка программного обеспечения Math Unit Программирование Intel math unit не всегда является простой или интуитивно понятной задачей. В дополнение к трудностям преобразования данных, упомянутым в главе 6, необходимо принимать во внимание следующие возможные источники проблем: 1. Тригонометрические функции доступны не во всех реализациях math unit напрямую . На 8087 и 80287 может быть получена только частичная касательная (FPTAN), и ее диапазон ограничен углом от 0 до p/4 радиан. В этих математических единицах все другие тригонометрические
функции должны быть выведены из этой частичной касательной. Программное обеспечение также должно уменьшить входной аргумент до допустимого диапазона. Сопроцессор 80387 ввел новые тригонометрические команды для непосредственного вычисления синуса и косинуса и расширил диапазон операндов команды частичного касательного. Однако код не будет выполняться в системах 8087 и 80287, если будут использоваться эти новые коды операций. 2. Для вычисления обратных тригонометрических функций предусмотрена только одна инструкция, FPATAN. АРК-синус, АРК-Косинус, дуги по касательной, а также дуги функции их получrocals, должны быть рассчитаны с частичной дуги-функция тангенс. 3. Два логарифмических кода операций работают с двоичным основанием. Необходима дополнительная обработка для получения логарифмов по другим основаниям, таким как натуральные и обычные логарифмы. Аналогичные манипуляции необходимы при вычислении антилогарифмов. 4. Команда F2XM1 увеличивает 2 до степени x и вычитает единицу. В 8087 и 80287 диапазон экспоненты (x) должен быть положительным числом от 0 до 0,5. Хотя 181 диапазон экспонент был увеличен в 80387, нет единой инструкции по математическим единицам, которая возводила бы основание в произвольную степень. 5. Программа, содержащая инструкции по математическим единицам, почти наверняка зависнет, если она будет выполнена на компьютере, который не оснащен аппаратными средствами с плавающей запятой. Хотя в наши дни ПК без математического модуля - редкое явление, проблему нельзя полностью игнорировать. Решением является продукт, называемый эмулятором с плавающей запятой. Идеальный эмулятор состоит из набора программных процедур, которые в точности имитируют аппаратный компонент в системах, не оснащенных чипом. Однако в 8087 программное обеспечение эмулятора не может работать с теми же кодами операций, которые используются аппаратным компонентом. Операционные коды математических единиц должны быть заменены или исправлены операционными кодами, распознанными emuлатором. Эмуляторы программного обеспечения Math unit и процедуры поддержки обычно не входят в состав ассемблеров и пакетов разработки. 6. Математическая единица представляет собой двоичную машину. Хотя он оперирует целыми числами с плавающей запятой, и типы данных BCD, типичное числовое приложение использует в основном представления с плавающей запятой . Это означает, что программам часто требуется ввод и вывод в той или иной форме удобочитаемых ASCII-кодировок. Процедуры преобразования во внутренние форматы math unit и из них не являются тривиальными........... При неправильном кодировании они могут повлиять на результат вычислений . Некоторые из этих проблем уже были решены. В главе 5 мы разработали функцию IdMathUnit(), которая может быть использована для идентификации различных реализаций математического сопроцессора. В главе 6 мы представили процедуры, которые позволяют преобразовывать числовые данные в ASCII в форматы математических единиц и наоборот. В этой главе мы разрабатываем процедуры для выполнения фундаментальных операций при вычислении экспоненциальных, тригонометрических и логарифмических функций. 8.1 Экспоненциальные функции Экспоненциальные функции, такие как вычисление числа 10 y ,e y
; Показатель степени - 100 для вычисления Трансцендентные примитивы 195 ; остаток ЦИКЛ MUL_STAGE_2 FSTP ST(1) ; S2 | S3 | ПУСТОЙ | ; DX удерживает REM (y MOD 100) MOV CX,DX ; Остальное скопировать в CX ; Тест на показатель степени, кратный 100 (без остатка) CMP CX,0 ; В этом случае CX = 0 JNE ЭТАП_1 ; Уходи, если нет ;*************************| ; быстрый выход, если REM = 0 | ;***************************************************| ; умножьте коэффициенты экспоненты: | ; (y **F3)**I3 ----> S3 -----> СТ(3) | ; (y**F2)**I2 ----> S2 -----> СТ(2) | ;***************************************************| ; | S2 | S3 | ПУСТОЙ | FMUL СТ,СТ(1) FSTP СТ(1) ; x**y | ПУСТО | JMP EXIT_XTOYF ; Показатель степени в точности кратен 100 ; ;*****************| ; ЭТАП 1 | ;********************************| ; вычислить (x**F1)**I1 | ;********************************| ; Тест на EXP > 10 ; Сначала загрузите 1 для случая, когда exp < 10 ; На этом этапе: |
ST(0) | СТ(1) | СТ(2) | ; | S2 | S3 | ПУСТОЙ | ; | или 1 | или 1 ; CX = REM (y MOD 100) ; Тест на EXP > 10 ; Первая загрузка 1 для случая, когда exp < 10 STAGE_1: FLD1 ; 1 | S2 | S3 | CMP CX, 10 ; Имеет опыт > 10 JNA ЭТАП_0 ; Перейти, если не превышает 10 FSTP ST(0) ; S2 | S3 | ПУСТОЙ | ; На данный момент CX равен > 10 FLD X_TO_10 ; x**10 | S2 | S3 | FLD1 ; 1 | x**10 | S2 |.. ; ;*******************************| ; вычислить (x**F1)**I1 | ;*******************************| ; CX = REM (y MOD 100) MOV DX, CX ; Сохранить остаток в DX MOV AX, CX ; Новый показатель для AX ДВИЖЕНИЕ BX,10 ; Делитель
DIV BL ; Показатель степени / 25 MOV Ах, 0 ; Четкое напоминание MOV CX, AX ; Скопировать делитель в CX ; CX = INT(y/100) MUL_STAGE_1: FMUL СТ,СТ(1) ; продолжение | x**10 | S2 |.. SUB DX,BX ; Показатель степени - 10 для вычисления ; остаток ЦИКЛ MUL_STAGE_1 FSTP ST(1) ; S1 | S2 | S3 | ; DX содержит остаток 196 Глава 8 MOV CX, DX ; CX - новый счетчик итераций ; Тест на показатель степени, кратный 10 CMP CX, 0 ; В этом случае CX = 0 JNE STAGE_0 ; Уходи, если нет ;*************************| ; быстрый выход, если REM = 0 | ;***************************************************| ; умножьте коэффициенты экспоненты: | ; (y **F3)**I3 ----> S3 -----> СТ(3) | ; (y**F2)**I2 ----> S2 -----> СТ(2) | ; (y**F1)**I1 ----> S1 -----> СТ(1) | ;***************************************************| ; | S1 | S2 | S3 |.. FMUL
СТ,СТ(1) FSTP СТ(1) ; РР | S3 | ПУСТОЙ | FMUL СТ,СТ(1) FSTP СТ(1) ; x**y | ПУСТО | JMP EXIT_XTOYF ; ;*****************| ; ЭТАП 0 | ;********************************| ; вычислить (y**F0)**I0 | ;********************************| ; На данный момент: | ST(0) | СТ(1) | УЛИЦА(2) | ; | S1 | S2 | S3 | ; | или 1 | или 1 | или 1 | ; CX = INT(y/10) ЭТАП_0: ПОЛЕ X_VALUE ; x | S1 | S2 |.. FLD1 ; 1 | x | S2 |.. MUL_STAGE_0: FMUL СТ,СТ(1) ; S0 | x |
S1 |.. ЦИКЛ MUL_STAGE_0 FSTP ST(1) ; S0 | S1 | S2 |.. ;***************************************************| ; умножьте коэффициенты экспоненты: | ; (y **F3)**I3 ----> S3 -----> СТ(3) | ; (y**F2)**I2 ----> S2 -----> СТ(2) | ; (y**F1)**I1 ----> S1 -----> СТ(1) | ; (y**F0)**I0 ----> S0 -----> ST(0) | ;***************************************************| ; | S0 | S1 | S2 |.. FMUL СТ,СТ(1) FSTP СТ(1) ; РР | S2 | S3 | FMUL СТ,СТ(1) FSTP СТ(1) ; РР | S3 | ПУСТОЙ | FMUL СТ,СТ(1) FSTP СТ(1) ; x**y | ПУСТО | EXIT_XTOYF: CLD ПОВТОРНО ; _X_TO_Y_BYFAC ENDP
Трансцендентные примитивы 197 Приложения Каждый из методов вычисления экспоненциальных функций, описанных ранее, имеет преимущества и недостатки: 1. Логарифмические аппроксимации относительно компактны и эффективны. Кроме того, та же процедура может быть непосредственно применена к вычислению степеней и корней. Их основным недостатком является низкая точность результата. 2. Двоичное питание обеспечивает высокую точность и производительность, но метод применим применяется только к вычислению целых степеней. 3. Методы факторизации экспоненты обеспечивают очень высокую точность для случая C y а также приемлемую производительность. Однако затраты на внедрение выше, чем при использовании других методов. Для случая x y разложение экспоненты на множители дает меньше преимуществ по сравнению с двоичным потоком , чем для C y . Смешанные методы Можно спроектировать реализации, которые сочетают в себе два различных метода получения экспоненциальных функций. Например, подпрограмма может использовать двоичное включение или разложение экспоненты на множители для оценки целочисленной части экспоненты и логарифмическую аппроксимацию для оценки дробной части. Этот способ реализации улучшает точность, ограничивая ошибку логарифмической аппроксимации дробной составляющей. Следующая процедура использует смешанный подход: _X_TO_Y_MIXED ПРОЦЕДУРА ИСПОЛЬЗУЕТ esi edi ebx ebp ; Смешанный метод получения x**y ; Целая часть вычисляется с помощью двоичного питания ; Дробная часть вычисляется логарифмическим приближением ; При вводе: ; ST(0) = показатель степени ; ST(1) = base (должно быть целым числом) ; При выходе: ; ST(0) = x**y ; ; Эта подпрограмма вызывает процедуры с именами X_TO_Y_BYBP и ; X_TO_Y_BYLOG ; ; Сначала вычислите целую часть, используя двоичное питание ; exp | База | FSTP EXP_ELEMENT ; база | ПУСТО | FSTP БАЗОВЫЙ ЭЛЕМЕНТ ; ПУСТО | FLD EXP_ELEMENT ; exp
| База | FRNDINT ; int(exp) | База | FLD БАЗОВЫЙ ЭЛЕМЕНТ ; база | int(exp) | FXCH ; int(exp) | База | ПОЗВОНИ _X_TO_Y_BYBP ; x** int y| ПУСТО | ; Теперь вычислите дробную часть, используя логарифмическую аппроксимацию FLD EXP_ELEMENT ; exp | x**int y| FLD ST(0) ; опыт | опыт | x**int y |... FRNDINT ; int(exp) | опыт | x**int y |... FSUBP ST(1),ST ; frc(exp) | x**int y| ПУСТО | FLD БАЗОВЫЙ ЭЛЕМЕНТ ; база | frc(exp)| x**int y |... FXCH ; frc(exp) | База | x**int y |... ПОЗВОНИТЬ _X_TO_Y_BYLOG 198 Глава 8 ;x**(frc y)| x**int y| ПУСТО | FMUL СТ,СТ(1) ; x**y | x**int y| ПУСТО | FSTP СТ(1) ; x**y | ПУСТО | ; ST(0) имеет значение x**y CLD RET _X_TO_Y_MIXED ENDP
Процедура _X_TO_Y_MIXED используется при вычислении экспонент для финансовых примитивов, разработанных в главе 10. 8.2 Тригонометрия математических единиц В оригинальных математических сопроцессорах 8087 и 80287 все тригонометрические функции были реализованы с помощью двух элементарных команд: FPTAN для вычисления частичного касательного и FPATAN для вычисления частичного дугового касательного. С 80387 Intel внесла серьезные изменения в тригонометрические инструкции. Эти изменения, которые были подробно обсуждены, начиная с раздела 7.3.4, можно резюмировать следующим образом: 1. Команда FPTAN (частичная касательная) была изменена, чтобы принимать входной угол (r) с абсолютным значением в диапазоне 0 £r£2 63 . В 8087 и 80287 входной угол должен находиться в диапазоне 0 £ r £ p / 4 радиана. Обратите внимание, что 2 63 составляет приблизительно 9,22 E +18, что означает, что FPTAN в 80387 можно считать практически неограниченным диапазоном для большинства технических применений. 2. В набор команд math unit были добавлены три новых тригонометрических кода операций: FSIN, FCOS и FSINCOS . Эти инструкции, которые не имеют аналогов в аппаратных средствах 8087 и 80287, предназначены для облегчения прямого вычисления функций синуса и косинуса. 3. Команда FPATAN (partial arc-tangent), используемая при вычислении обратных тригонометрических функций ric, имеет неограниченный диапазон операндов в 80387. В 8087 и 80287 абсолютное значение в ST (0) должно быть меньше, чем в ST(1). Модификации и дополнения, внесенные в сопроцессор 80387 и предварительно обслуживаемые в математическом блоке 486 и Pentium, могут значительно упростить кодирование и повысить производительность тригонометрических процедур, как показано далее в этой главе. Однако программы, использующие новые функции, работают некорректно в системах, оснащенных 80287 или 8087. Тот факт, что центральный процессор 80386 совместим с 80287 и 80387, вносит дополнительную сложность . В следующих разделах мы представляем тригонометрические примитивы, совместимые со всеми процессорами Intel 80387 или математическим модулем 486 и Pentium. Библиотека MATH16, поставляемая на компакт-диске с книгой, содержит тригонометрические процедуры, которые используют набор команд и вычислительные возможности оригинальных сопроцессоров Intel . 8.2.1 Угловые преобразования Инструкции по вычислению тригонометрических функций и дуговых функций требуют измерения в радианах . Хотя радианы являются стандартным представлением во многих технических, инженерных и научных областях, нетехнические приложения часто выражают углы в десятичных градациях. Процедуры, перечисленные в этом разделе, выполняют преобразования из радиан в де- Трансцендентные примитивы 199 grees и наоборот. В обеих формулах преобразования используется константа 180. Это значение сохраняется в памяти в расширенном формате для обеспечения максимальной точности. Из-за двоичной архитектуры математического модуля использование десятичных констант создает возможность ошибок преобразования, которые иногда могут приводить к неожиданным результатам. Например, тригонометрические функции углов, которые лежат точно на границах октанта или квадранта, принимают особые значения. Таким образом, тангенс угла, равного p или 2p радианам, равен 0, а тангенс угла равен p/2 или 2 p/3 радиана - это бесконечность. Теперь предположим, что вы должны были использовать процедуру преобразования, чтобы получить радиан-меру угла в 270 десятичных градусов, а затем вычислить тангенс этого значения. В этом случае небольшие ошибки в преобразовании десятичных разрядов grees в радианы могут привести к тому, что ожидаемое значение (бесконечность) не будет получено. Если бы это было так, ошибка заключалась бы не в вычислении касательной, а в преобразовании десятичных градусов в радианы.
Следующие процедуры низкого уровня преобразуют градусы в радианы, а радианы в градусы. .ДАННЫЕ ; Константа, используемая при преобразовании ONE_80 DT 180.0 .КОД ;************************************ ; градусы в радианах ;************************************ ; _DEG_TO_RAD ПРОЦЕДУРА ; Преобразуйте входное значение в ST (0) в радиан ; ; При вводе: ; ST(0) = значение в градусах ; На выходе: ; ST(0) = значение, переведенное в радианы ; ; Формула: ; r = d * (pi/180) ; ; где r = угол в радианах, d = угол в градусах ; ; ; ST(0) | СТ(1) | УЛИЦА(2) ; d | ПУСТО | ПУСТО | FLD ОДИН_80 ; 180 | d | FLDPI ; число пи | 180 | d | FDIV ST,ST(1); числопи/180 | 180 | d | FSTP СТ(1) ; числопи/180 | d | FMUL ST,ST(1);
r | d | FSTP УЛИЦА (1) ; r | ПУСТО | ; ST(0) теперь содержит начальное значение в радианах CLD RET _DEG_TO_RAD ENDP 200 Глава 8 ;************************************ ; радианы в градусах ;************************************ _RAD_TO_DEG ПРОДОЛЖЕНИЕ ; Преобразовать начальное значение в ST (0) в градусы ; ; При вводе: ; ST(0) = значение в радианах ; На выходе: ; ST(0) = значение, переведенное в градусы ; ; Формула: ; d = r * (180/пи) ; ; где r = угол в радианах, d = угол в градусах ; ; ; ST(0) | СТ(1) | УЛИЦА(2) ; d | ПУСТО | FLDPI ; r | pi | ПУСТО | FLD ОДИН_80 ; 180 | pi | r |... FDIV ST,ST(1); 180/пи | pi | r |...
FSTP СТ(1) ; 180/число | r | ПУСТО | FMUL ST,ST(1); d | r | ПУСТО | FSTP СТ(1) ; d | ПУСТО | ; ST(0) теперь содержит начальное значение в градусах CLD RET _RAD_TO_DEG ENDP 8.2.2 Операции масштабирования диапазона В главе 7 мы видели, что FPTAN вычисляет частичный тангенс угла в ST(0), выраженный в радианах. В 8087 и 80287 входной угол должен быть меньше, чем p/4 радиана. Это ограничение входного аргумента делает необходимым, чтобы процедура общего назначения для вычисления функций любого угла выполняла некоторые предварительные операции тестирования, масштабирования и уменьшения. В положении для вычисления функций любого угла Сведение к единичному кругу Первая операция редукции основана на тождествах периодичности, которые утверждают, что любая тригонометрическая функция угла > 2 p радиан (360 градусов) равно функции этого угла минус 2B радиан. Угол в диапазоне от 0 до 2 часто говорят, что p радианов находится в единичной окружности. Арифметическое уменьшение до единичного круга состоит из последовательных вычитаний константы 2B до тех пор, пока результат не станет меньше 2B. В разделе 7.3.2 мы обсудили, как инструкции FPREM и FPREM1 используются для выполнения модульной арифметики. Следующая процедура с именем CIRCLE_SCALE выполняет уменьшение аргумента до 2 p радианов. Процедура найдена в модуле Un32_6 библиотеки MATH32 библиотека на компакт-диске с книгой. .ДАННЫЕ STATUS_WW DW 0 ; Хранилище для слова состояния .CODE _CIRCLE_SCALE ПРОЦЕСС ; Уменьшите угол в ST(0) (r) до значения, меньшего, чем 2 радиана на дюйм Трансцендентные примитивы 201 ; путем вычисления n = r (MOD 2pi) ; В коде предполагается, что переменная размером с слово с именем STATUS_WW ; была ранее определена в сегменте кода. ; ;
ST(0) | СТ(1) | УЛИЦА(2) ; r | ПУСТОЙ | FLDPI ; pi | r | ПУСТО | УВЛЕЧЕНИЕ ST,ST(0); 2pi | r | ПУСТО | FXCH ; r | 2pi | ПУСТО | UNIT_CIRC: ЕДИНИЦА ИЗМЕРЕНИЯ ФПРЕМ ; REM (r/2pi) | 2pi | ПУСТО | FSTSW STATUS_WW ; Сохранять биты состояния в памяти ОЖИДАНИЕ ; Задержка до окончания сохранения MOV АХ, БАЙТ PTR STATUS_WW+1 ; Переместить биты состояния в AH И AH,00000100B ; Замаскировать все, кроме C2 JNZ UNIT_CIRC ; Повторите, если C2 неясен ; ; ST(0) | СТ(1) | СТ(2) FSTP СТ(1) ; n | ПУСТО | ; ST(0) теперь содержит угол в единичной окружности (n) CLD RET _CIRCLE_SCALE ENDP Редукция к первому октанту Причина, по которой аргумент исходной инструкции FPTAN ограничен
числами p/4 ra, заключается в том, что все тригонометрические функции являются периодическими. Если известно местоположение октанта исходного угла, программное обеспечение может использовать фундаментальные идентификаторы для определения, какая функция требуется в каждом конкретном случае. В таблице 8.5 перечислены четыре формулы, которые применимы для вычисления тангенса угла в соответствии с его положением октанта. Таблица 8.5 Формулы октантов для касательной функции угла r



_asm { FLD x FLD y ВЫЗОВ МАКСИМУМ FSTP значение } возвращаемое значение; } double Min(двойной x, двойной y) // Возвращает меньшее значение // При вводе: // параметры x и y содержат значения // При выходе: // возвращает меньшее { двойное значение; _asm { ПОЛЕ x ПОЛЕ y ВЫЗОВ МИН FSTP значение } возвращаемое значение; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функции с именами Order(), Max(), Min() содержатся в файле Calc Primitives ..h, который находится в папке Sample Code\Chapter09\Calc Primitives в онлайновом программном обеспечении книги. 9.0.4 Вычисление модуля упругости Фундаментальная операция конечной алгебры, называемая модулем, состоит в нахождении остатка, полученного в результате деления двух аргументов. Некоторая путаница в терминологии существует из-за того, что аргумент делителя иногда также называют модулем. Чтобы избежать этого затруднения, в следующем обсуждении мы используем слово модуль для обозначения результата операции. Следовательно, если r = остаток от x / y тогда также r = x (MOD y). Следующая низкоуровневая процедура вычисляет функцию модуля двух аргументов аргументы. .КОД _МОДУЛЬ ПРОЦЕСС ; Найдите x (MOD y), который является остатком от x / y ; ; При вводе: ; ST(0) = x (аргумент) 224 Глава 9 ; ST(1) = y (модуль упругости) ; ; На выходе: ; ST(0) = x (MOD y) ; ; ST(0)
| СТ(1) | УЛИЦА (2) | ; x | y | ПУСТО | REPEAT_MOD: ФПРЕМ ; REM(x/y) | y | ПУСТО | FSTSW STATUS_WW ; Сохранять биты состояния в памяти ОЖИДАНИЕ ; Задержка до окончания хранения ПЕРЕМЕЩЕНИЕ AH,БАЙТ PTR STATUS_WW+1 ; Переместить биты состояния в AH И AH, 00000100B ; Снять маску со всех, кроме C2 JNZ REPEAT_MOD ; Повторить, если C2 неясен FSTP СТ(1) ; x (MOD y) | ПУСТОЙ | CLD RET _МОДУЛЬ ENDP Функция интерфейса C ++ к процедуре _MODULUS выглядит следующим образом: int Modulus(int x, int y) // Возвращает значение x(MOD y) // При вводе: // параметры x и y содержат значения // При выходе: // возвращает x по модулю y { int по модулю; _asm { ФИЛД y ФИЛД x ПОЗВОНИ МОДУЛЬ FISTP по модулю } возвращает по модулю; } Функция Modulus() находится в файле Un_32.h, который находится в папке CHAPTER9\TEST UN32_7 на компакт-диске с книгой. 9.0.5 Целые и дробные части Рациональные и иррациональные числа часто приходится разделять на их целочисленную и дробную части. По-видимому, эта операция может быть легко выполнена с использованием кода операции математической единицы FRNDINT для получения целочисленной части, которую затем можно вычесть из исходного числа для получения дробной части. Однако FRNDINT округляет значение до целого числа в соответствии с текущей настройкой поля управления округлением для контрольного слова. Следовательно, если элемент управления округлением находится в режиме по умолчанию (округление до
ближайшего четного), код операции может не обеспечить корректного получения целочисленной части. Например, если ST(0) содержит число 125.66 и управление округлением выполняется до ближайшего четного, то FRNDINT выдает значение 126, которое не является целой частью 125.66. Проблема легко решается, если заставить математическую единицу округлять в меньшую сторону или сокращать. В этом случае также необходимо сохранить управляющее слово вызывающего абонента, чтобы его можно было корректировать Общие математические функции 225 полностью восстановлен. Следующая процедура разрешает целую и дробную части числа в ST(0). .ДАННЫЕ РАУНД ВНИЗ DW 177FH ; Управляющее слово для округления в меньшую сторону ; операции CONTROL_WW DW 0 ; Память для управляющего слова вызывающего абонента .КОД _INT_FRAC ПРОЦЕДУРА ; Разделять целые и дробные части числа в ST(0) ; ; При вводе: ; ST(0) = n ; ; На выходе: ; ST(0) = целая часть n ; ST(1) = дробная часть n ;**********************| ; установите значение округления в меньшую сторону | ;**********************| FSTCW CONTROL_WW ; Сохранить управляющее слово в памяти FLDCW РАУНД ВНИЗ ; Установите новое управляющее слово ; На этом этапе математическая единица устанавливается в положение округления в меньшую сторону ; | ST(0) | СТ(1) | УЛИЦА(2) ; n | ПУСТО - | FLD ST(0) ; n | n | ПУСТО | FRNDINT ; я | n | ПУСТО |
FLDCW CONTROL_WW ; Восстановить исходное управляющее слово FSUB ST(1),ST ; i f | ПУСТОЙ | CLD RET _INT_FRAC КОНЕЦ Поскольку _INT_FRAC создает как целочисленную, так и дробную части операционной команды, могут быть разработаны высокоуровневые интерфейсные процедуры, которые используют _INT_FRAC для повторного поворота любого из этих элементов. Функции C ++ IntPart() и FracPart() выполняют эти операции. Код выглядит следующим образом: double FracPart(двойное число) // Возвращает дробную часть числа с плавающей запятой // При вводе: // параметр num содержит операнд // При выходе: // возвращает дробную часть числа // { double frac; _asm { ПОЛЕ номер ВЫЗОВ INT_FRAC FXCH FSTP frac } возвращает frac; } int IntPart(двойное число) 226 Глава 9 // Возвращает целую часть числа с плавающей запятой // При вводе: // параметр num содержит операнд // При выходе: // возвращает целую часть num // { int part; _asm { FLD номер ВЫЗОВ INT_FRAC FISTP часть } вернуть часть; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функции IntPart() и FracPart() содержатся в файловых примитивах Calc.h который находится в папке Sample Code\Chapter09\Calc Primitives в онлайн-программном обеспечении книги. 9.0.6 Решение треугольников
Одна из теорем евклидовой геометрии, обычно приписываемая греческому математику тику Пифагору, утверждает соотношение между сторонами прямоугольного треугольника, как показано на рисунке 9.1. Рисунок 9.1 Теорема Пифагора Следующая процедура позволяет решить для гипотенузы прямоугольного треугольника, когда известны размеры двух сторон. _ПИФАГОР ПРОЦЕСС ; Решите уравнение Пифагора: c = SQRT (x ^ 2 + y ^ 2) ; ; При вводе: ; ST(0) = x ; ST(1) = y ; ; На выходе: ; ST(0) = SQRT (x ^2 + y ^ 2) ; ; ; ST(0) | СТ(1) | УЛИЦА (2) | ; x | y | ПУСТО | Общие математические функции 227 a+b 2 2 c c= a b FMUL ST(0),ST ; x^2 | y | ПУСТО | FXCH ; y | x^2 | ПУСТО | FMUL ST(0),ST ; y^2 | x^2 | ПУСТО | FADDP ST(1),ST ; y^2 + y ^ 2 | ПУСТО
| FSQRT ; c | ПУСТО | CLD RET _ПИФАГОР КОНЕЦ Функция интерфейса C ++ выглядит следующим образом: double Pythagoras(двойное a, двойное b) // Возвращает квадратный корень из суммы 2 квадратов // При вводе: // входные значения параметров a и b // При выходе: // возвращает квадратный корень из a^2 + b^2 { double c; _asm { FLD a FLD b ВЫЗОВ ПИФАГОР FSTP c } возвращает c; } Решение прямоугольного треугольника часто требуется в терминах одной стороны и соответствующего угла. Касательная функция может быть применена к этому случаю, как показано на рисунке 9.2. Рисунок 9.2 Формула бокового угла для прямоугольного треугольника Следующая процедура находит противоположную сторону прямоугольного треугольника с точки зрения стороны и смежного угла. При обработке предполагается, что угол введен в градусах. Преобразование из градусов в радианы и вычисление функции tangent выполняется путем вызова процедур, разработанных в главе 8. _SOLVE_FOR_A ПРОЦЕСС ; Решите прямоугольный треугольник в терминах одной стороны и смежного ; угла (A), используя формулу ; ; a = b * Загар A ; ; При входе: 228 Глава 9 b a Загар (A) = a = b Загар (A) A a b ; ST(0) = угол A (в градусах) ; ST(1) = сторона b ; ; На выходе: ; ST(0) = сторона a ;
; ST(0) | СТ(1) | УЛИЦА(2) | ; A | b | ПУСТО | ; Перевести градусы в радианы ВЫЗОВИТЕ _DEG_TO_RAD ; A (rads)| b | ПУСТО | ВЫЗОВ _ТАНГЕНС ; Загар A | b | ПУСТО | FMULP ST(1),ST ; b * Загар A| ПУСТО | CLD RET _SOLVE_FOR_A ENDP Функция интерфейса C ++ кодируется следующим образом: double SolveForA(двойной угол, двойная сторона) // Решите прямоугольный треугольник в терминах одной стороны и смежного // угла, используя формулу // a = b * Загар A // При входе: // параметры angle и sideb содержат данные треугольника // При выходе: // возвращает сторону a { двойная сторона a; _азм { FLD sideb Поле угол наклона ВЫЗОВ SOLVE_FOR_A FSTP сайда } обратная сторона; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функции Pythagoras() и SolveForA() содержатся в файле Calc Primitives.h который находится в папке Sample Code\Chapter09\Calc Primitives в онлайновом программном обеспечении книги. 9.1 Квадратные уравнения Общее квадратное уравнение выражается формулой: ax
2 + bx + c = 0 где коэффициенты a, b и c являются константами. Можно попытаться решить квадратное уравнение, применив стандартную квадратную формулу: Общие математические функции 229 Набор решений может иметь два равных действительных корня, два разных действительных корня или не иметь действительных корней. Следующая процедура, названная QUAD_FORM_REAL, вычисляет действительные корни, если они существуют, квадратного уравнения путем решения квадратной формулы. .ДАННЫЕ ; Для квадратичных процедур CONST_A DT 0 ; Хранилище для константы a CONST_B DT 0 ; Хранение для константы b CONST_C DT 0 ; Память для константы c ДИСКРИМИНАНТ DT 0 ; Хранилище для дискриминанта .КОД _QUAD_FORM_REAL ПРОЦЕСС ; Решение квадратного уравнения в виде: ; ; ax^2 + bx + c = 0 ; ; с помощью формулы ; ; -b + SQRT [(b^2-4ac)] ; x(1) = ПУСТО ПУСТО ПУСТО ---; _ 2а ; ; -b - [SQRT (b^2-4ac)] ; x(2) = ПУСТО ПУСТО ПУСТО ---; 2a ; ; где x(1) и x (2) являются двумя возможными корнями, а a, b и c являются ; константами ; При вводе: ; ST(0) = c ; ST(1) = b ; ST(2) = a ; На выходе: ; ST(0) = x (1) ; ST(1) = x(2) ;
; | ST(0) | УЛИЦА(1) | УЛИЦА (2) ; c | b | a | ;***************************| ; хранить константы | ;***************************| FSTP CONST_C ; b | a | ПУСТО | FSTP CONST_B ; a | ПУСТО | FSTP CONST_A ; ПУСТО - | ;****************************| ; оценить дискриминант | ; SQRT (b^2 - 4ac) | ;****************************| FLD CONST_C ; c | ПУСТО | FLD CONST_A ; a | c | ПУСТО | FMULP ST(1),ST ; ac | ПУСТО | FLD1 ; 1 | ac | ПУСТО | 230
Глава 9 2 4 2 b b ac x a −± − = FADD ST(0),ST ; 2 | ac | ПУСТО | УВЛЕЧЕНИЕ ST(0),ST ; 4 | ac | ПУСТО | FMULP ST(1),ST ; 4ac | ПУСТО | FLD CONST_B ; b | 4ac | ПУСТО | FMUL ST(0),ST ; b^2 | 4ac | ПУСТО | FXCH ; 4ac | b^2 | ПУСТО | FSUB ; b^2-4ac | ПУСТО | FSQRT ; dscr | ПУСТО |
; Сохранить дискриминант FSTP ДИСКРИМИНАНТНЫЙ ; ПУСТО - | ;****************************| ; вычислите корень x(1) | ;****************************| FLD CONST_A ; a | ПУСТО | FLD1 ; 1 | a | ПУСТО | УВЛЕЧЕНИЕ ST(0),ST ; 2 | a | ПУСТО | FMULP Улица (1),улица ; 2a | ПУСТО | FLD CONST_B ; b | 2a | ПУСТО | FCHS ; -b | 2a | ПУСТО | FLD ДИСКРИМИНАНТНЫЙ ; dscr | -б | 2a | FADDP ST(1),ST ; -b + dscr| 2a | ПУСТО | FDIVR ; x(1)
| ПУСТО | ; ST(0) содержит корень x(1) ;****************************| ; вычислите корень x(2) | ;****************************| FLD CONST_A ; a | x(1) | FLD1 ; 1 | a | x(1) | FADD ST(0),ST ; 2 | a | x(1) | FMULP ST(1),ST ; 2a | x(1) | ПУСТО | FLD CONST_B ; b | 2a | x(1) | FCHS ; -b | 2a | x(1) | FLD ДИСКРИМИНАНТНЫЙ ; dscr | -б | 2a | FSUBP ST(1),ST ; -b - dscr| 2a | x(1) | FDIVR ; x(2) | x(1) | ПУСТО
| FXCH ; x(1) | x(2) | ПУСТО | CLD RET _QUAD_FORM_REAL ENDP Описанная выше процедура была разработана для того, чтобы показать обработку настолько четко, насколько это возможно, и минимально использовать стек математических единиц. Можно повысить эффективность вычислений, сохранив все элементы данных в математическом модуле вместо использования переменных памяти. Значения 4 и 2 получаются программой путем манипулирования константой 1. Производительность может быть дополнительно улучшена путем сохранения этих значений в виде целых констант. Функция интерфейса C ++ к процедуре _QUAD_FORM_REAL выглядит следующим образом: пустота квадроформального (двойной a, двойной b, двойной c,\ double*x1, double*x2) // Решение квадратного уравнения в виде: // // ax^2 + bx + c = 0 // // с помощью формулы // // Общие математические функции 231 // -b + SQRT[(b^2-4ac)] // x(1) = ---------------------// 2a // // -b - [SQRT(b^2-4ac)] // x(2) = ---------------------// 2a // // где x(1) и x (2) - два возможных корня, а a, b и c // константы // // При вводе: // параметры a, b и c содержат коэффициенты уравнения // На выходе: // параметры x1 и x2 содержат корни, если // уравнение имеет действительные корни // { double r1, r2; _asm { ОБЛАСТЬ a ОБЛАСТЬ b ОБЛАСТЬ c ВЫЗОВ QUAD_FORM_REAL FSTP r1 FSTP r2
} *x1 = r1; *x2 = r2; возврат; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функция QuadFormReal() находится в файле Calc Primitives.h, расположенном в папке Sample Code\Chapter09\Calc Primitives в онлайнпрограммном обеспечении книги. 9.2 Мнимые и комплексные числа Математические модули Intel не предусматривают обработку мнимых или сложных чисел . Сложная арифметика должна быть реализована программным обеспечением. Поскольку не существует явного способа представления мнимой единицы измерения (i), вызывающая подпрограмма должна быть осведомлена о том, какой регистр стека содержит неявное мнимое число. В целях последующего обсуждения читателю следует вспомнить, что мнимая единица определяется как следовательно, Также напомним, что комплексные числа состоят из действительной и мнимой частей, объединенных знаком плюс или минус. Обычно они представляются как 232 Глава 9 x яx −= 1 i=− a + bi a – bi где a - действительная часть, а bi - мнимая часть. Чисто мнимое число (bi) может быть представлено как комплексное число в виде 0 + bi. Это представление позволяет унифицировать обработку как мнимых, так и комплексных чисел. 9.2.1 Операции в сложной арифметике Для комплексных чисел определены арифметические операции сложения, вычитания, умножения, деления, степени, корни и абсолютное значение. Следующие формулы имеют отношение к сложной арифметике. 1. Сложение и вычитание сложений (a + bi) + (c + di) = (a + c) + i(b + d) 2. Комплексное умножение (a + bi)(c + di) = (ac – bd) + i(ad + bc) 3. Сложное деление Величина или абсолютное значение комплексного числа представляет собой расстояние между точкой, определяющей комплексное число, и началом координат плоскости, как показано на диаграмме Арганда на рис. 9.3. Рисунок 9.3 Абсолютное значение (величина) комплексного числа 4. Формулой для определения абсолютной величины является выражение Пифагора Общие математические функции 233 2 2 ( ) ( ) a bi ac bd i до н. э. c di c
d + + + − = + + мнимая ось действительная ось (a + ib) b a |(a + ib )| 2 2 x a b = + Следующие процедуры низкого уровня выполняют фундаментальные арифметические операции вычисления над комплексными числами. .ДАННЫЕ ; Данные для сложной арифметики PART_A DT 0 ; Хранилище для a ЧАСТЬ B DT 0 ; Хранилище для bi PART_C DT 0 ; Хранилище для c ЧАСТЬ D DT 0 ; Хранилище для di .КОД _ADD_CMPLX ПРОЦЕСС ; Сложения двух комплексных чисел (a + bi и c + di) с помощью ; формулы: ; ; (a + bi) + (c + di) = (a + c) + i (b + d) ; ; При вводе: ; ST(3) = a ; ST(2) = b ; ST(1) = c ; ST(0) = d ; На выходе: ; ST(0) = (a + c) = действительная часть ; ST(1) = (b + d) = мнимая часть ;
; | ST (0) | ST(1) | ST(2) | ST(3) | ; | a | b | c | d | ;***************************| ; сохраняйте входные данные | ;***************************| FSTP ЧАСТЬ D ; bi | c | di | ПУСТОЙ | FSTP PART_C ; c | di | ПУСТОЙ | FSTP ЧАСТЬ B ; d | ПУСТОЙ | FSTP PART_A ; ПУСТО | ;***************************| ; вычислить (a + c) | ;***************************| FLD ЧАСТЬ_A ; a |ПУСТО- | FLD PART_C ; c | a | ПУСТО | УВЛЕЧЕНИЕ ; a + c |ПУСТОЙ- | ;***************************| ; вычислить i(b + d) | ;***************************| FLD ЧАСТЬ B ; b | a + c | ПУСТОЙ | FLD ЧАСТЬ D ; d | b | a + c | ПУСТОЙ |
УВЛЕЧЕНИЕ ; b + d | a + c | ПУСТОЙ | FXCH ; a + c | b + d | ПУСТОЙ | ; | |___ мнимая часть ; |___________ реальная часть CLD RET _ADD_CMPLX ENDP ; ; _SUB_CMPLX ПРОЦЕСС _MUL_CMPLX ПРОЦЕСС ; Умножение двух комплексных чисел (a + bi и c + di) на ; с помощью формулы: 234 Глава 9 ; ; (a + bi)(c + di) = (ac - bd) + i(ad + bc) ; ; При вводе: ; ST(3) = a ; ST(2) = b ; ST(1) = c ; ST(0) = d ; На выходе: ; ST(0) = (ac - bd) = действительная часть ; ST(1) = (ad + bc) = мнимая часть ; ; | ST (0) | ST(1) | ST(2) | ST(3) | ; | a | bi | c | di | ;***************************| ; сохраняйте входные данные | ;***************************| FSTP ЧАСТЬ D ; bi | c | di | ПУСТОЙ | FSTP PART_C ; c | di | ПУСТОЙ | EMPTY | FSTP
PART_B ; di | ПУСТОЙ | EMPTY | ПУСТОЙ | FSTP PART_A ; ПУСТО | EMPTY | ПУСТОЕ | EMPTY | ;***************************| ; вычислить (ac - bd) | ;***************************| ; ; ПУСТОЙ | FLD PART_A ; a | ПУСТОЙ | FLD PART_C ; c | a | ПУСТО | FMULP ST(1),ST ; ac | ПУСТОЙ | FLD PART_B
; -1 | ПУСТО | ;***************************| Финансовые расчеты 263 ( ) 1 1 n i FV pm i − + = ; вычислить: | ; (i + 1)^(n - 1) | ; вечера * ----------------| ; i | ;***************************| ; число = (i + 1)^(n - 1) ; ; число | ПУСТО | FLD ИНТЕРЕС ; i | число | ПУСТОЙ | FDIV ; число /i | ПУСТОЕ | FLD ПЛАТЕЖ ; pm | i/num | ПУСТОЙ | FMULP ST(1),ST ; PV | ПУСТОЙ | CLD RET _ ANNUITY_FV ENDP Функция интерфейса C ++ к процедуре _ANNUITY_FV выглядит следующим образом: double AnnuityFv(двойной срок, двойной i, двойной pm, int ppy) { // Финансовая процедура для расчета будущей стоимости // инвестиций по формуле: // // (i + 1)^(n - 1) // FV = премьер-министр * --------------//
я // где: // FV = будущая стоимость инвестиций // pm = периодический платеж // i = периодическая процентная ставка // срок = годы // // При поступлении: // параметр term = годы // параметр i = годовая процентная ставка // параметр pm = периодический платеж // параметр ppy = периоды платежей в год // При выходе: // возвращает будущую стоимость инвестиций двойное значение F; // Преобразовать проценты в процентную форму i /= 100; _asm { ; Процентная ставка (i) и срок (n) преобразуются в одинаковые ; единицы времени перед вызовом функции ANNUITY_FV FLD pm ; Загрузить периодический платеж FILD ppy ; Единицы измерения времени FLD i ; Интерес FDIV ST, ST(1) ; ввод-вывод FLD срок ; годы FMUL СТ,СТ(2) ;n*t FSTP ST(2) FXCH ; ST0 ST1 ST2 ; УТВЕРЖДАТЬ: | n | я | вечера | ПОЗВОНИТЕ ANNUITY_FV FSTP fValue } возвращает fValue; } 264 Глава 10 10.2.2 Приведенная стоимость аннуитета Иногда нам нужно знать, какова начальная сумма денег, эквивалентная определенной ренте
. Другими словами, во что бы нам пришлось вложить сегодня единовременную сумму, чтобы получить будущую стоимость, эквивалентную той, которая была бы получена в результате периодических выплат данного аннуитета. Эта величина, известная как приведенная стоимость аннуитета, рассчитывается по следующей формуле: где pm - периодический платеж аннуитета, i - ставка, n - срок в годах, а PV - приведенная стоимость. Следующая низкоуровневая процедура вычисляет текущее значение. _ ANNUITY_PV ПРОЦЕДУРА ; Финансовая процедура для расчета приведенной стоимости ; аннуитета. ; При вступлении: ; ST(0) = n (член) ; ST(1) = i (процентная ставка) ; ST(2) = pm (периодический платеж) ; При выходе: ; ST(0) = PV (приведенная стоимость инвестиций) ;***************************| ; хранить входные данные | ;***************************| ; | ST(0) | ST(1) | ST(2) | ST(3) | ; | n | я | pm | ПУСТОЙ | FSTP СРОК ; я | pm | ПУСТОЙ | FSTP ИНТЕРЕС ; pm | ПУСТО | FSTP ОПЛАТА ; ПУСТО | ;***************************| ; вычислить 1-(i + 1)^-n | ;***************************| ПОЛЕ СРОК ; n | ПУСТО | FLD ИНТЕРЕС ; i | n | ПУСТОЙ | FLD1 ; 1 | i | n | ПУСТОЙ | FADD
;я+1| n | ПУСТО | FXCH ; n | i+1 | ПУСТОЙ | ПОЗВОНИ _X_TO_Y_MIXED ;(i+1)^n| ПУСТОЙ | ; x^-n = 1/x^n FLD1 ; 1 |(i+1)^n| ПУСТО | FDIVR ; 1/ | ;(i+1)^n| ПУСТОЙ | FLD1 ; 1 |1/(я+..| ПУСТОЙ | ФСУБР ;1-(1/..| ПУСТО | ;***************************| ; вычислить: | ; 1-(i + 1)^-n | ; вечера * --------------| ; i | ;***************************| ; число = 1-(i + 1)^-n ; ; число | ПУСТОЙ | FLD ИНТЕРЕС ; i | число | ПУСТОЙ | Финансовые расчеты 265 ( ) 1 1 n i PV pm i − − + = FDIV ; число /i | ПУСТОЕ |
FLD ОПЛАТА ; pm | i/num | ПУСТОЙ | FMULP ST(1),ST ; PV | ПУСТОЙ | CLD RET _ ANNUITY_PV ENDP Функция C ++ для доступа к процедуре _ANNUITY_PV выглядит следующим образом: double AnnuityPv(двойной срок, двойной i, двойной pm, int ppy) { // Финансовая процедура для расчета приведенной стоимости // инвестиций по формуле: // // 1-(i + 1)^-n // PV = вечера * ------------// я // // где: // PV = текущая стоимость инвестиций // pm = периодический платеж // i = периодическая процентная ставка // n = срок в годах // // При поступлении: // параметр term = годы // параметр i = процентная ставка // параметр pm = периодический платеж // параметр ppy = периоды платежей в год // При выходе: // возвращает приведенную стоимость инвестиций двойная стоимость; // Преобразовать проценты в процентную форму i /= 100; _asm { ; Процентная ставка (i) и срок (n) преобразуются в одинаковые ; единицы времени перед вызовом функции ANNUITY_PV FLD pm ; Загрузить периодический платеж FILD ppy ; Единицы измерения времени FLD i ; Интерес FDIV ST, ST(1) ; ввод-вывод FLD срок ; годы FMUL СТ,СТ(2) ;n*t
FSTP СТ(2) FXCH ; ST0 ST1 ST2 ; УТВЕРЖДАТЬ: | n | я | вечера | ПОЗВОНИТЕ ANNUITY_PV FSTP pValue } возвращает pValue; } 10.2.3 Причитающаяся аннуитет В обычных аннуитетах, таких как те, которые обсуждались ранее, периодические выплаты производятся в конце каждого периода. Причитающийся аннуитет - это такой, при котором платежи производятся в начале каждого периода. Результат осуществления платежей в be266 Глава 10 особенностью каждого периода является то, что показатель для формулы аннуитета должен быть увеличен на единицу. Результирующая формула выглядит следующим образом: где pm - периодический платеж, i - процентная ставка, n - количество периодов платежа и A - аннуитет, подлежащий выплате в начале каждого периода. Следующая низкоуровневая процедура рассчитывает причитающийся аннуитет. _ ЕЖЕГОДНЫЙ_ДЕНЬ ПРОЦЕДУРА ; Финансовая процедура для расчета будущей стоимости ; причитающейся ренты. ; При вступлении: ; ST(0) = n (член) ; ST(1) = i (процентная ставка) ; ST(2) = pm (периодический платеж) ; При выходе: ; ST(0) = A (причитающаяся рента) ; ; ;***************************| ; хранить входные данные | ;***************************| ; | ST(0) | ST(1) | ST(2) | ST(3) | ; | n | я | pm | ПУСТОЙ | FSTP СРОК ; я | pm | ПУСТОЙ | FSTP ИНТЕРЕС
; pm | ПУСТО | FSTP ОПЛАТА ; ПУСТО | ;***************************| ; вычислить (i + 1)^(n + 1) | ;***************************| ПОЛЕ СРОК ; n | ПУСТО | FLD1 ; 1 | n | ПУСТО | FADD ;

ошибка, возникающая при хранении непредставимых чисел, часто усугубляется ошибкой десятичного / двоичного преобразования. Таким образом, Visual C ++ 6.0 представляет часть 1/3 следующим образом: 0.33333333333333331 Использование дробных приближений в численных расчетах часто приводит к неожиданным результатам, особенно когда ошибка представления усугубляется преобразованием или вычислительной ошибкой. 10.3.3 Точность и ошибки вычислений Деловые, финансовые и экономические проблемы часто связаны с очень большими или очень маленькими цифрами, например, с государственным долгом, валовым доходом крупной корпорации или потреблением мяса осьминога на душу населения в регионе центральной Сахары. Представление чисел с плавающей запятой в математическом модуле соответствует формулам mats, установленным в ANSI / IEEE 754. Напомним, что формат двойной точности содержит 52 явных значащих бита и один неявный бит. Наибольшее десятичное значение, которое может быть представлено 53 двоичными цифрами, равно: 9007199254740991 Это значение соответствует 53-битному значению всех цифр 1. Это означает, что 53-разрядный двоичный знак позволяет представлять десятичные числа до 15 или 16 значащих цифр. Независимо от конверсии и представительства ошибок, упомянутых ранееподразделений, максимальное количество значащих цифр, которые могут быть закодированы в различных подразделениях форматы с плавающей точкой часто является причиной ошибок в расчетах. Например, число 988.888.888.888.88888 содержит 18 значащих цифр. В двойном формате этот номер хранится в виде 16 значащих цифр следующим образом: ...........: 988,888,888,888,888,830 Аналогично, небольшое число подвержено ошибке кодирования из-за количества значимых цифр, которые поддерживаются в формате, например, значение: icant digits, например, значение: 0,4444444444444444444444 представляется как 0,4444444444444444 В любом случае представление страдает потерей точности. Если бы вы загрузили приведенные выше значения в математический модуль и выполнили операцию умножения, результат был бы неточным, а именно: 988,888,888,888,888,888 * 0.444444444444444444 = 43950617283906110.0 Сравните этот результат с результатом, полученным путем кодирования этих чисел в Формат BCD20 и выполнение умножения BCD результат BCD: 43950617283906172.0049382716049382 результат математической единицы: 43950617283906110.0000000000000000 274 Глава 10 разница: 62.0049382716049382 Ошибка отмены Особенно интересная ошибка, иногда называемая ошибкой отмены, имеет место , когда операнды арифметической операции имеют очень большое и очень малое число. Например, предположим, что программа на C ++ определяет следующие числа двойной точности: двойное число 1 = 512000000000000; // 5.12e+14 двойное число 2 = 0,0075; Если бы код теперь добавил переменные num1 и num2, результатом было бы: 512000000000000 которое имеет то же значение, что и операнд num1. Обратите внимание, что в этом примере оба операнда находятся в пределах формата двойной точности. Однако операция сложения завершается неудачей, поскольку сумма превышает емкость двойного формата. Однако операция сложения завершается неудачей, поскольку сумма превышает емкость двойного формата. 10.4 Финансовое программное обеспечение
Финансовые функции низкого уровня, упомянутые в этой главе, расположены в модуле Un32_8 библиотеки MATH32. Функции интерфейса C ++ находятся в файле Un32_8.h , который находится в тестовом проекте Un32_8, расположенном в папке Chapter10. Также в этом проекте есть программа на C ++, которая использует библиотеку и интерфейсные процедуры и демонстрирует , как получить к ним доступ с C ++. Исходные и исполняемые файлы находятся на компакт-диске с книгой. Финансовые расчеты 275 Глава 11 Статистические расчеты Краткое содержание главы Статистика была описана как универсальный язык науки и техники. Деscriptive статистике понимается сбор, представление и описание данных, а inferenтиал статистик позволяет интерпретировать описательные статистики для того, чтобы делать выводы и принимать решения. Эта глава посвящена фундаментальным вычислениям и формулам, используемым в описательной статистике и статистике вывода. В ней мы разрабатываем минимальный набор подпрограмм, которые решают общие статистические формулы. Формулы относятся к базовым манипуляциям с данными, мерам центральной тенденции, мерам дисперсии, вероятности, нормального распределения и линейному регрессионному анализу. 11.0 О статистических данных Компьютерные данные, используемые в статистических расчетах, могут поступать из многих мыслимых источников и форматироваться практически неограниченными способами. Исходные данные могут поступать непосредственно от датчиков и приборов. Первичные данные могут храниться на устройствах любого типа и в нескольких форматах файлов. Обработанные данные могут быть в стандартных или запатентованных форматах и типах данных. Например, Гибкая система передачи изображений (FITS) была разработана Научным управлением стандартов и технологий НАСА (NOST) для обеспечения обмена и хранения наборов астрономических данных. Объем базового документа, описывающего стандарт FITS, составляет более 70 страниц, и данные могут быть представлены в пяти различных типах данных. С другой стороны, Лаборатория реактивного движения Калифорнийского технологического института разработала Систему планетарных данных (PDS) для дальнейшего уточнения астрономических данных, которые конкретно относятся к планетологии.. Документация по этому стандарту насчитывает более двухсот страниц. Это показывает, что было бы тщетно пытаться предвидеть, как вводятся, обрабатываются и форматируются данные для статистического вычисления . По практическим соображениям функции и подпрограммы, разработанные в этой главе, предполагают, что данные уже готовы к хранению в массивах. Программист должен знать, что часто это не так, и что подпрограммы и функции часто нужно будет модифицировать в order для адаптации к определенному источнику данных или формату. 277 11.0.1 Кодирование с гибким типом данных Мы попытались сделать наши подпрограммы гибкими в отношении типов данных в рамках ограничений языка программирования. В определенной степени гибкость типов может быть достигнута с помощью шаблонов C++. Механизм шаблонов позволяет создавать универсальные функции, которые работают с любым совместимым типом данных. В C и C ++ та же цель может быть достигнута с помощью указателей на void, но шаблоны проще разрабатывать и использовать. Например, следующий шаблон определяет функцию с именем Sort, которая может принимать в качестве параметра массив любого типа данных C++. шаблон <класс A> сортировка по пустоте(A * arrS, размер int ) {
... В функции *arrS - это указатель на массив любого типа данных. Функция эффективно отсортирует массив данных типа int, double, float или любого другого совместимого типа данных. 11.1 Примитивы для манипулирования данными Статистические формулы, а также численные методы, представленные в главе 12, из десяти требуют арифметических операций над массивными данными. Одной из простейших функций является получение суммы всех элементов в массиве. В математике греческая заглавная буква сигма используется для представления суммирования набора слагаемых, например, давления обозначает сумму всех значений x i , от i = 1 до i = 10. Если включены все возможные значения x , то обозначение часто сокращается до Однако программы-редакторы, используемые при создании компьютерного исходного кода, обычно не способны генерировать специальные символы. По этой причине в списках кодов мы используем слово SIG для обозначения греческой заглавной буквы sigma, например: SIG(x) = 11.1.1 Общие суммирования При статистических вычислениях часто требуется несколько стандартных суммирований. Удобно иметь доступные функции для выполнения этих базовых функций. Поскольку символы bol не допускаются в разрешенных в C ++ именах функций, нам пришлось придумать некоторые эквивалентные текстовые обозначения. В таблице 11.1 приведены математическое выражение и имена функций. 278 Глава 11 10 1 i i x = ∑ x ∑ x ∑ Таблица 11.1 Имена функций C ++ для операций суммирования МАТЕМАТИЧЕСКОЕ ВЫРАЖЕНИЕ НАЗВАНИЕ ФУНКЦИИ Описание = SIGx() Суммируйте значения x = SIGxSq() Возведите значения x в квадрат, затем суммируйте = SIGxToSq() Суммируем значения x, затем возводим в квадрат = SIGxy() Умножьте значения x * y, затем суммируйте = SIGxyByn() Умножьте n на SIGxy = SIGxBySIGy() Умножьте SIGx на SIGy = SIGxSkipCnt() Добавьте значения x из i = a в b Функции, перечисленные в таблице 11.1, находятся в файле Un32_9.h в папке Chapter11 на компакт-диске книги. Код для функции SIGxSkipCnt() выглядит следующим образом: шаблон <класс A> двойной SIGxSkipCnt(A * arr, пропуск целых чисел, количество целых чисел, размер целых чисел) { // Функция для нахождения суммы значений в массиве любого
// числового типа, начиная с заданного смещения от первого // элемент, для ряда элементов // // При входе: // arr[] - это числовой массив // skip - начальное смещение // count - количество используемых элементов // размер, если количество элементов в массиве // вычисляется вызывающим абонентом следующим образом: // size = размер массива / sizeof arr[0]; // // При выходе: // Возвращает сумму элементов массива // Возвращает 0, если данные неверны // Первая проверка правильности входных данных // если((пропустить + количество) > размер) возврат 0.0; double aSum = 0; для(int x = пропустить; x < (count + пропустить); x++) aSum = arr[x] + aSum; вернуть aSum; } Статистические расчеты 279 2 x ∑ x ∑ ( ) 2 x ∑ xy ∑ n xy ∑ ( )( ) x y ∑ ∑ b i ia x = ∑ ЗАПИСНАЯ КНИЖКА ПРОГРАММИСТА В C ++, когда массив передается в качестве аргумента функции, количество элементов в массиве недоступно коду
функции. Если этот параметр требуется , он также должен быть передан вызывающим объектом в качестве аргумента. Общепринятым способом получения количества элементов в массиве является выражение: размер массива / sizeof array[0] Здесь оператор sizeof сначала применяется к имени массива, которое возвращает количество байтов в массиве. Затем это значение делится на размер в байтах каждого элемента массива, который получается путем применения оператора sizeof к элементу массива со смещением 0. Эта проблема была решена в Java, в которой вызываемый метод может напрямую определять размер аргумента массива. 11.1.2 Сортировка Некоторые статистические функции легче реализовать, если данные сначала упорядочены нульмерно, другими словами, если они были отсортированы. Дональд Кнут определяет сортировку как перестановку элементов в порядке возрастания или убывания. Том III его серии, озаглавленной "Искусство компьютерного программирования" (см. Библиографию), озаглавлен "Сортировка и поиск" . Тот факт, что Кнут посвящает этой теме большую часть целого тома, дает представление о сложности и глубине этого предмета. Было разработано множество алгоритмов сортировки, некоторые из которых довольно сложны. Вероятно, самым простым для понимания и внедрения является печально известная пузырьковая сортировка, которая также является одной из наименее эффективных. Так называемая сортировка по выбору проста в реализации и обеспечивает приемлемую производительность для данной цели. Следующая функция шаблона реализует сортировку данных массива в порядке возрастания. шаблон <класс A> // пустая сортировка(A *arrS, размер int ) { // Сортировка массива любого числового типа с использованием алгоритма выбора // сортировки int maxInd; // Индекс наибольшего значения за каждый проход int bottom; // Ложный нижний индекс для каждого прохода int i; // Локальный счетчик A temp; // Временное хранилище для (bottom = size-1; bottom >= 1; bottom—) { maxInd = 0; для (i = 1; i <= снизу; i++) если (arrS[i] > arrS[maxInd]) maxInd = i; temp = arrS[внизу]; arrS[внизу] = arrS[maxInd]; arrS[maxInd] = temp; } } 280 Глава 11 ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функция Sort() содержится в файле Un32_9.h, который находится в папке CHAPTER11 онлайнового программного обеспечения книги. Также в этом файле есть функция SortCopy(), которая сортирует данные массива во второй массив, передаваемый вызывающим объектом в качестве аргумента. 11.2 Методы подсчета Частая статистическая проблема состоит в определении количества различных способов, с помощью которых можно подсчитать связанные наборы. Например, предположим, что есть четыре кандидата на пост президента профессиональной ассоциации и три кандидата на должность секретаря . Также предположим, что должностные лица избираются раздельно и что одно и то же лицо не может занимать обе должности. Вопрос в том, сколько комбинаций президента и секретаря может быть избрано? График на рис. 11.1 показывает все возможные варианты .
Рисунок 11.1 Древовидная диаграмма для проблемы выборов Поскольку существует три возможных секретаря для каждого из четырех возможных президентов, общее число комбинаций возможных пар равно 3, умноженным на 4, или 12. Это приводит к фундаментальному принципу подсчета, который гласит, что если любая задача A может быть выполнена n различными способами, а другая независимая задача B - m различными способами, то задача A, за которой следует задача B, может быть выполнена n * m различными способами. Уточнение принципа подсчета, описанного в предыдущем параграфе, относится к порядку элементов в наборе. Когда порядок элементов важен , мы ссылаемся на перестановку, когда порядок не важен, мы ссылаемся на Статистические вычисления 281 комбинация. Например, любые два элемента из набора из 3 (помеченных a, b и c) могут быть связаны следующим образом: (a,c), (c, a), (a,b), (b,a), (b, c) и (c,b) что приводит к шести перестановкам. Когда порядок элементов не имеет значения, тогда возможны следующие подмножества: {a,c}, {a,b} и {b,c} что приводит к трем комбинациям. Обратите внимание, что в обычной статистической записи используются круглые скобки для обозначения перестановок и фигурные скобки для обозначения комбинаций . 11.2.1 Перестановки В случае перестановки важен порядок, в котором отображаются элементы. Например, три элемента (обозначенные a, b и c) могут быть взяты в следующем порядке: (a,b,c), (a,c,b), (b,a,c), (b,c,a), (c, a,b) и (c,b, a) В этом случае число перестановок равно 6. Формальное определение перестановки r элементов из набора S , состоящего из n элементов, представляет собой упорядоченное расположение этих r элементов без повторений. Общепринятым обозначением для количества перестановок является: где n - общее количество элементов в наборе, а r - количество элементов, выбранных из набора. Формула перестановок такова: В случае предыдущего примера, состоящего из всех возможных комбинаций трех элементов, значение n = 3 и значение r = 3. Следовательно: Напомним, что 0! = 1. Следующая низкоуровневая процедура вычисляет количество перестановок. .ДАННЫЕ ; Этот раздел содержит элементы данных для всех низкоуровневых ; процедур в модуле Un32_9 библиотеки MATH32 NELS DD 0 ; Хранение для n RELS DD 0 ; Память для r TEMPV DD 0 ; Временное хранилище ; Для кривой нормального распределения X ДД 0 ; координата x графика U DD 0 ; Среднее 282 Глава 11 n r P !
( )! n r n P nr = − 3! 6 (3 3)! n r P= = − SD ДД 0 ; Стандартное отклонение ; Определение константы e E DT 4000ADF85458A2BB4A9BH .КОД _ ИЗМЕНЕНИЯ ПРОЦЕДУРА ; Упорядоченная перестановка без повторений n элементов, в наборе S, ; берется по r за раз, согласно формуле: ; ; n! ; nPr = ---------; (n - r)! ; ; где: ; nPr = количество перестановок r элементов в наборе из ; n элементов ; n = количество элементов в наборе S ; r = количество элементов в перестановке (r за раз) ; ; Примечание: код предполагает, что n и r являются целыми числами ; Использует процедуру _FACTORIAL в модуле Un32_7 ; библиотека math32. ; При вводе: ; ST(0) = n (количество элементов) ; ST(1) = r (принимается r за раз) ; На выходе: ;




Точки на кривой Точки, не расположенные на кривой Рисунок 12.4 Линейная интерполяция подобными треугольниками Линейная интерполяция может быть основана на похожих треугольниках, как показано на рисунке 12.4. Сходство между треугольниками приводит к тождествам: Если координаты точки P i равны x i ,y i , и координаты точки P j являются x j ,y j , тогда P K может быть рассчитан следующим образом: Применяя приведенный выше результат из аналогичных треугольников, мы получаем: Учитывая, что координата y точки P k равна 314 Глава 12 a x i x k y k y i x j y j P i P k P j c d b a c b d ad c b = =.... j i j i k i k i a y
y b x x c y у d x x = − = − = − = − ( )( ) j i k i j я y y x x c x x − − = − k i y c y =+ затем, путем линейной интерполяции Данные, необходимые для линейной интерполяции, включают координаты обеих смежных центральных точек (P i иP j на рис. 12.4) и координата x интерполируемой точки (P k на рис. 12.4). Мы разрабатываем две функции, которые выполняют линейную интерполяцию. В первом используются координаты точек P j иP
i , а также координату x точки P k , чтобы определить координату y точки P k : . Эта функция, называемая InterpLinPxy(), кодируется следующим образом double InterpLinPky(двойной pxi, двойной pyi, двойной pxj, двойной pyj, двойной pxk) { // Найдите координату y точки Pk, которая, как предполагается, находится // вдоль прямой, соединяющей точки Pi и Pj, согласно // следующему уравнению: // // Pyk = ((Pyj-Pyi)(Pxk-Pxi)/(Pxj-Pxi))+Pyi // // где: // // Pyj - координата y точки Pj // Pxj - координата x точки Pj // Pyi - координата y точки Pi // Pxi - координата x точки Pi // Pxk - координата x желаемой точки // Pyk - это значение y, найденное путем интерполяции // // При вводе: // параметрами pxj, pyj, pxi, pyi, pxk являются // возвращает координаты соответствующих точек //: // Координата y точки Pk, найденная с помощью линейной // интерполяции, как значение типа double. возврат (((pyj - pyi)*(pxk - pxi))/(pxj - pxi)) + pyi; } Вторая функция использует табличные данные, содержащие координаты x и y нескольких точек на кривой. Функция пытается найти две точки, смежные с желаемой точкой. Если они существуют, то координата y точки P k вычисляется методом линейной интерполяции с использованием функции InterpLinPxk(). Функция, названная InterpLinFromTable(), является следующей: double InterpLinFromTable(двойной *DATx, // x массив двойной * datY, // y массив Интерполяция, дифференцирование и интегрирование 315 ( )( ) j i k i k i j i y
y x x y y x x − − = + − двойной pxk, // координата x внутренний размер) // размер массива { // Найдите координату y точки Pk, которая, как предполагается, находится // на прямой, соединяющей две точки, Pi и Pj, // на основе данных координат, содержащихся в двух массивах // точек на кривой. Использует функцию InterpLinPky(). // При вводе: // массив DATx[] содержит координаты точек x // массив datY[] содержит y-координаты точек // параметр pxk - это координата x точки Pk // size - это количество элементов в обоих массивах // вычисляется вызывающим абонентом следующим образом: // size = размер данных / sizeof DATx[0]; // Допущения: // Координатные точки в массивах DATx[] и datY[] // сортируются в порядке возрастания. // Обработка: // Процедура выполняет поиск в массиве координат x // до тех пор, пока не будет найдена единица, превышающая указанное значение // Pxk. Эта точка помечена как Pj. Если есть // от x до точки Pj, то она обозначается как Pi. // Координаты Pi и Pj и значение Pxk // затем передаются в InterpLinPky() // функция. // Возвращает: // Координата y точки Pk, найденная с помощью линейной // интерполяции, как значение типа double. // Возвращает 0, если нет смежных точек // в наборе данных. // Найти точку Pj в массиве DATx[] double pyj, pyi, pxj, pxi; double pyk = 0; int found = 0; for(int w = 0; w < размер; w++) { если(DATx[w] > pxk || w != 0) {
pxj = DATx[w]; pxi = DATx[w-1]; pyj = данные [w]; pyi = данные [w-1]; найдено = 1; разрыв; } } если(найдено == 0) возвращает 0; // Вычислить Pyk с помощью функции InterpLinPky() pyk = InterpLinPky(pxi, pyi, pxj, pyj, pxk); вернуть pyk; } 12.0.2 Интерполяция Лагранжа Теоретически, если в наборе данных имеется n точек данных, можно найти полиномиальное выражение степени n - 1, которое определяет кривую, проходящую через каждую точку данных. В 316 Глава 12 эта смысловая линейная интерполяция, как обсуждалось в разделе 12.1.1, является формой полиномиальной интерполяции, когда многочлен имеет первую степень. Общая проблема может быть выражена следующим образом: задан набор из n точек, (x 1 ,y 1 ), (x y ,y 2 )... (x n ,y n ), найдите уникальный многочлен степени n – 1, который определяет кривую. Французский математик Жозеф Луи Лагранж определил ставшее классическим решение задачи интерполяции с помощью формулы, которая приведена здесь без доказательства: Формула Лагранжа основана на наборе простых множителей. Например, кубический полиномиал (третьей степени) может быть выражен как сумма четырех членов следующим образом: где x i иy i - значения независимых и зависимых переменных соответственно. Каждый член суммы следует простой схеме: числитель представляет собой произведение линейных факторов в форме (x - x i ), где один x i в каждом термине пропущено. Например, в расширении P 3 (x) в приведенном ранее первом члене отсутствует x 1 , во втором члене отсутствует x 2 и так далее. Пропущенный x i используется при построении знаменателя каждого члена путем замены x на пропущенный x i . Затем каждый член умножается на значение зависимой переменной (y i ), соответствующий x i опущен в термине. Компьютерный алгоритм интерполяции Лагранжа состоит из вычисления каждого
члена разложения и накопления суммы. Внешний цикл посещает каждую пару в наборе данных. Внутренний цикл вычисляет числитель и знаменатель каждого члена, умножает на значение y для каждого члена в разложении Лагранжа и накапливает сумму. Логика может быть выражена в псевдокоде следующим образом: ШАГ 1: Определите следующие переменные и элементы управления: i (счетчик внешнего цикла) j (счетчик внутреннего цикла) pxk (значение x в точке интерполяции) Ax[] (массив значений x) Ay[] (массив значений y) n (количество пар данных в наборе) Term (переменная для хранения членов Лагранжа) Интерполяция, дифференцирование и интегрирование 317 1 1 () я Дж Дж Н ян Дж я яJ х х пх г х х ≤≤ ≤≤ ≠ − = − ∑∏ 2 3 4 1 3 4 3 1 2 1 2 1 3 1 4 2 1 2 3 2 4 1 2 4 1 2 3 3 4 3 1 3 2 3 4 4 1 4 2 4 3
( )( )( ) ( )( )( ) () ( )( )( ) ( )( )( ) ( )( )( ) ( )( )( ) . ( )( )( ) ( )( )( ) х х х х х х х х х х х х Пх г г х х х
х х х х х х х х х х х х х х х х х х х х х г г х х х х х х х х х х х х − − − − − − = + + − − − − − − − − −
− − − + − − − − − − Sum = 0 (переменная для хранения суммы членов Лагранжа) ШАГ 2: Если i < n; Термин = Ay[i], Шаг 3 Если i = n, шаг 6 ШАГ 3: если j < n, шаг 4 если j = n, шаг 5 ШАГ 4: Если j я Термин = Термин * (pxk – Ax[j]) / (Ax[i] – Ax[j]); j = j + 1; Шаг 3. ШАГ 5: Сумма = Sum + Term; i = i + 1; Шаг 2. ШАГ 6: Сумма = значение y для pxk. Выполнено. Следующая функция вычисляет переменную зависимой переменной из набора данных с использованием интерполяции Лагранжа. Код реализует алгоритм, описанный в предыдущих параграфах. двойное взаимодействие (двойной * DATx, // x массив двойной * datY, // y массив двойной pxk, // координата x внутренний размер) // размер массива { // Найдите координату y точки Pk с помощью Лагранжа // интерполяция // // При вводе: // массив DATx[] содержит координаты точек x // массив datY[] содержит y-координаты точек // параметр pxk - это координата x точки Pk // size - это количество элементов в обоих массивах // вычисляется вызывающим абонентом следующим образом: // size = размер данных / sizeof DATx[0]; // // Возвращает: // Координата y точки Pk, найденная Лагранжем // интерполяция в виде значения типа double. // Возвращает 0, если в наборе данных нет смежных точек // . двойной lTerm = 0; // Каждый член Лагранжа двойной pyk = 0; // Значение зависимой переменной // для точки pxk для(int i = 0; i < size; i++) {
lTerm = Дата[i]; for(int j = 0; j < size; j++) { если(j != i) lTerm = lTerm * (pxk - DATx[j])/(DATx[i]-DATx[j]); 318 Глава 12 } pyk = pyk + lTerm; } вернуть pyk; } 12.0.3 Интерполяция методом наименьших квадратов В разделе 12.1 мы обсудили два общих типа методов, применяемых в операциях подгонки кривой . Первый тип методов предполагает, что кривая проходит через все точки данных. Линейная интерполяция и интерполяция Лагранжа, рассмотренные до сих пор, принадлежат к этой группе. Метод второго типа не предполагает, что кривая проходит через точки данных. На рисунке 12.2 показаны оба случая. В науке, инженерии и финансах существует много случаев, когда экспериментальные или измеренные данные должны рассматриваться как приблизительные. Это означает, что мы не можем предполагать, что данные точно определяют точки на кривой. Для таких ситуаций нам требуются методы интерполяции, которые обеспечивают наилучшее соответствие данным, в предположении, что данные моделируют конкретную функцию с хорошим поведением. В главе 11, раздел 11.7.2, мы обсудили регрессионный анализ в контексте статистики и разработали методологии для линейной регрессии. Теперь мы вернемся к теме линейной регрессии в более общих терминах и обобщим методы подгонки кривой методом наименьших квадратов на полиномы более высокого порядка. Линейные модели Линейная корреляция основана на взаимосвязи между переменными, выражаемой уравнением : где b называется точкой пересечения y, а m - наклон кривой, а y - значение зависимой переменной. Чтобы построить линейное уравнение, определяющее взаимосвязь между независимой и зависимой переменными, нам нужно определить значения m и b.... Требуемая функция - это та, которая обеспечивает наилучшее соответствие данным. Другими словами, мы хотим минимизировать расстояние по вертикали между точками данных и кривой. Эти расстояния, иногда называемые остатками от точек данных, показаны на рисунке 12.5. Рисунок 12.5 Подгонка кривой путем минимизации остатков Интерполяция, дифференцирование и интегрирование 319 y mx b = + d 1 x 1 d 2 x 2 d 3 x 3 f(x) d 4 x 4 На рисунке 12.5 открытые точки представляют точки данных, в то время как расстояния от данных до кривой для каждого x представляют собой значения d1, d2, d3 и d4. Чтобы найти наилучшее соответствие кривой, обозначенное как f (x), мы должны минимизировать сумму расстояний от данных до соответствующих точек кривой, поскольку остатки представляют ошибку интерполяции. Однако, если мы используем фактические значения расстояний , положительные и отрицательные значения имеют тенденцию к нейтрализации. Это приводит к
правильному измерению погрешности. Решение состоит в том, чтобы возвести в квадрат остатки перед их суммированием, отсюда и название метода наименьших квадратов. После возведения в квадрат значение E, называемое суммой ошибок, выражается следующими формулами: В терминах m и b уравнение суммы ошибок может быть выражено следующим образом: Теперь мы можем применить дифференциальное исчисление для получения частных производных от E по неизвестным m и b. Поскольку минимум m и b имеет место, когда производные равны нулю, мы можем использовать уравнения: Переставляя члены и решая для наклона m и y-пересечения b, уравнения становятся: Процедура вычисления координаты y точки с использованием интерполяции методом наименьших квадратов сначала определяет значения m и b в форме пересечения с наклоном уравнения с помощью приведенных выше формул. Тогда значение координаты y для точки P k можно легко получить, подставив в исходное уравнение: 320 Глава 12 2 1 n k k E d = = ∑ 2 1 ( ) n k k k E y mx b = = − − ∑ 1 1 2 ( ) 0 2 ( ) 0 Н к к к к Н к к к Е хг
МХ б м Е г МХ б б = = ∂ =− − − = ∂ ∂ =− − − = ∂ ∑ ∑ 2 2 ( )( )( ) ( ) ( )( ) n xy x y y m x m b n x x n − − = = − ∑
∑∑ ∑ ∑ ∑ ∑ () fx mx b = + Вычисление суммы ошибок Одним из преимуществ интерполяции методом наименьших квадратов является то, что мы можем измерить погрешность между точками данных и уравнением, определяющим кривую. Сумма ошибок - это квадрат остатков для каждой точки в наборе данных. Вычисление является алгоритмически простым, поскольку все, что нам нужно сделать, это посетить каждую координатную точку в наборе данных, вычислить квадрат значения невязки для точки и накопить это значение. Эта сумма ошибок затем может быть использована для обнаружения расхождений между данными и кривой , которая их моделирует. Нулевое значение суммы ошибок указывает на то, что кривая точно соответствует данным. Линейная интерполяционная функция методом наименьших квадратов Следующая функция вычисляет координату y точки P k используя линейную интерполяцию методом наименьших квадратов. Функция возвращает координату y в переменной типа double, а сумма ошибок возвращается в параметре, переданном вызывающим объектом по ссылке. шаблон <класс A> double LinearLSInterp(A *DATx, // Массив координат x A *datY, // Массив координат y A pxk, // координата x точки Pk двойная * eSum, // Переменная вызывающего объекта для // сумма ошибок размер int) // Размер массива { // // Функция для нахождения координаты y точки Pk с помощью // методов наименьших квадратов // При вводе: // При вводе: // массив DATx[] содержит координаты точек x // массив datY[] содержит y-координаты точек // параметр pkx - это координата x точки Pk // size - это количество элементов в обоих массивах // вычисляется вызывающим абонентом следующим образом: // size = sizeof DATx / размер datXY[0]; // ШАГ 1: // Вычисляем уклон (м) по формуле: // // y = mx + b // // n(SIGxy) - (SIGx)(СИГи) // м = ----------------------------------// n(SIGx^2) - (SIGx)^2 // где: //
x являются элементами данных массива DATx // y являются элементами данных массива dayY // n - количество элементов // m - это y-перехват y' = mx + b // // ШАГ 2: // Вычислить y-перехват (b) по формуле: // // y = mx + b // // SIGy - m (SIGx) // b = ----------------// n Интерполяция, дифференцирование и интегрирование 321 // где: // x являются элементами данных массива DATx // y являются элементами данных массива dayY // n - количество элементов // m - наклон y = mx + b // // ШАГ 3: // Решите для точки y, применяя // pyk = mx + b // двойное m = 0; // наклон двойное число; // Память для числителя удвоить d1, d2; // Память для знаменателя A sumXY = 0; // SIGxy A sumX = 0; // SIGx A sumY = 0; // SIGy A Сумма XSQ = 0; // SIGx^2 // Переменные для вычисления y-перехвата double b = 0; // y-перехват двойной пик; // Координата Y точки Pk // Переменные для вычисления суммы ошибок double res = 0; // Остаточный накопитель double newY = 0; двойная ошибка = 0; // Накопитель суммы ошибок // Вычислить суммы, требуемые в формуле для(int i = 0; i < size; i++) { sumXY = sumXY + (DATx[i] * datY[i]); sumX = sumX + DATx[i]; sumY = сумЫ + данные [i]; sumXSq = sumXSq + (DATx[i] * DATx[i]); } // Вычислить наклон (м) // // n(SIGxy) - (SIGx)(СИГи)
// м = ----------------------------------// n(SIGx^2) - (SIGx)^2 // // Начинать с числителя: nSIGxy - (SIGx)(SIGy) nume = (двойной) размер * sumXY - (sumX * сумЫ); // Вычисляем левый и правый элементы знаменателя // d1 = n (SIGx ^ 2) // d2 = (SIGx) ^ 2 d1 = size * sumXSq; d2 = sumX * sumX ; // Теперь получаем наклон m = nume / (d1 - d2); // Вычислить y-перехват (b) // // SIGy - m (SIGx) // b = ----------------// n // // Начнем с числителя: SIGy - m(SIGx) nume = (double) sumY - (m * sumX); // Теперь получим b 322 Глава 12 b = число / размер; // На этом этапе: // переменная b содержит y-перехват // переменная m содержит наклон // параметр pxk содержит значение x // Вычислить координату y с помощью // y' = mx + b pyk = (m * pxk) + b; // Вычислить сумму ошибок, используя остатки для(int k = 0; k < size; k++) { newY = (m * DATx[k]) + b; res = datY[k] - newY; ошибка = err + (res * res); } // Сохранить сумму ошибок в переменной вызывающего объекта *eSum = err; // Вернуть координату y для точки Pk вернуть pyk; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функции интерполяции, перечисленные в предыдущих разделах, находятся в файле Un32_10.h, расположенном в папке CHAPTER12 онлайнового программного обеспечения книги. Нелинейные модели Если функциональное уравнение является нелинейным, то наименьших квадратов метод, описанный ранееранее в этом разделе не может быть использована в Кривой-сторона. Однако некоторые нелинейные уравнения могут быть легко преобразованы в линейную функцию. Обычная линейная регрессия затем может быть применена к преобразованной функции, чтобы получить соответствие методом наименьших квадратов прямой линии. Общей функцией, которую можно легко преобразовать в линейную форму, является общая степенная функция: где m и b являются константами. Если взять натуральный логарифм обеих сторон этого уравнения, то это приводит к: ......... Теперь можно определить следующие уравнения преобразования: Интерполяция, дифференцирование и интегрирование 323 ()
m fx bx = ln ( ) ln ln fx m x b = + () ' ln ' ln ' ln y fx b b x x y y = = = =
Пожалуйста, обратите внимание, что простые числа в формулах используются для удобства, чтобы использовать одни и те же константы в списках кода. Это не следует путать с использованием простых чисел для обозначения производных. Значения могут быть подставлены в форму пересечения с наклоном следующим образом: Уравнения наклона и пересечения y, перечисленные ранее, могут быть изменены следующим образом минимумы: Компьютерный алгоритм аналогичен тому, который используется в линейной модели интерполяции методом наименьших квадратов. Сначала координаты x и y точек данных преобразуются в их натуральные логарифмы и сохраняются в локальных массивах. Эти данные используются для получения значения y-пересечения b и наклона m путем подстановки в предварительно полученные уравнения. Естественный антилогарифм b дает значение b, которое подставляется в исходную формулу кривой для получения значения зависимой переменной в точке P к . Обработка выполняется с помощью следующей процедуры. двойной NonLinLSInterp(двойной *DATx, // Массив координат x double *datY, // Массив координат y double pxk, // x координата точки Pk двойная * eSum, // Переменная вызывающего объекта для // суммы ошибок размер int) // Размер массива { // Функция для нахождения координаты y точки Pk с использованием // нелинейного метода наименьших квадратов, предполагая, что функция // имеет вид: // // f(x) = bx^m // // путем выполнения следующего преобразования: // // ln f(x) = m * ln x + ln b // // где: // y = f(x) // b' = ln b // y' = ln y // x' = ln x // // При вводе: // массив DATx[] содержит координаты точек x // массив datY[] содержит y-координаты точек // параметр pkxk - это координата x точки Pk // size - это количество элементов в обоих массивах // вычисляется вызывающим абонентом следующим образом: // size = размер DATx / размер datXY[0]; // 324 Глава 12 ' ' '
y mx b = + 2 2 ( ' ') ( ')( ') ' ( ') ' ( ')( '') n xy x y y m x m b n x x n − − = = − ∑ ∑ ∑ ∑ ∑ ∑ ∑ // ОБРАБОТКА: // ШАГ 1: // Преобразуйте значения в массивах DATx[] и dat[Y] // взяв естественные журналы значений, хранящихся там. // // ШАГ 2: // Используйте эти значения для расчета наклона (м) // // ШАГ 3: // Вычислить y-перехватить b' и преобразовать в b. //
// ШАГ 4: // Решите для точки pxyk, применив исходное // уравнение: // // y = bx^m // // ШАГ 5: // Вычислите и сохраните сумму ошибок в вызывающей // переменная // Примечание: // Обработка использует содержащиеся в ней логарифмические примитивы // в модулях Un32_5 библиотеки MATH32. // Интерфейс C ++ функционирует на основе этих примитивов // включены в этот модуль. // // двойное m = 0; // наклон двойное число; // Память для числителя двойные d1, d2; // Память для знаменателя двойная сумма XY = 0; // SIGxy двойная сумма x = 0; // SIGx двойная сумма = 0; // SIGy двойная сумма XSQ = 0; // SIGx^2 двойной xTom; // x в степени m // // Переменные для вычисления y-перехвата double b = 0; // y-перехват двойной lnb = 0; // ln of y-перехват двойной пик; // Координата Y точки Pk // // Переменные для вычисления суммы ошибок double res = 0; // Остаточный накопитель double err = 0; double newY = 0; // Массивы для натуральных журналов x и y double *lnX = new double[size]; double *lnY = new double[size]; // // Преобразовать данные в ln для (int l = 0; l < size; l ++) { lnX[l] = Лог (DATx[l]); lnY[l] = Лог(datY[l]); } // Вычислить суммы, требуемые в формуле для(int i = 0; i < size; i++) { sumXY = sumXY + (lnX[i] * lnY[i]); Интерполяция, дифференцирование и интегрирование 325 sumX = sumX + lnX[i]; сумЫ = сумЫ + lnY[i]; sumXSq = sumXSq + (lnX[i] * lnX[i]); }
// // Вычислить наклон (м) // // n(SIGxy) - (SIGx)(СИГи) // м = -------------------------// n(SIGx^2) - (SIGx)^2 // // Начнем с числителя: nSIGxy - (SIGx)(SIGy) nume = (двойной) размер * sumXY - (sumX * sumY); // // Вычисляем знаменатель левого и правого элементов // d1 = n (SIGx ^ 2) // d2 = (SIGx) ^2 d1 = size * sumXSq; d2 = sumX * sumX; // Теперь получаем наклон m = nume / (d1 - d2); // // Вычисляем ln из y-перехвата (b') // // SIGy - m (SIGx) // b' = ----------------// n // // Начнем с числителя: SIGy - m (SIGx) // nume = (double) sumY - (m * sumX); // Теперь получим b lnb = nume / size; // Решаем для b b = AlogE (lnb); // // Вычисляем координату y, используя // y' = bx ^ m // xTom = XToYMixed(pxk, m); pyk = b * xTom; // // Вычислить сумму ошибок, используя уравнение и остатки для(int k = 0; k < size; k ++) { newY = b * (XToYMixed(DATx[k], m)); res = datY[k] - newY; ошибка = err + (res * res); } // Сохранить сумму ошибок в переменной вызывающего абонента *eSum = err; // вернуть pyk; } Функция NonLinLSInterp(), перечисленная ранее, предполагает, что все данные расположены в первом квадранте. Это необходимо, поскольку логарифмические функции не существуют для отрицательных значений. 326 Глава 12 ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функция NonLinLSInterp() содержится в файле Un32_10.h, расположенном в Папка Chapter12 в онлайн-программном обеспечении книги. 12.1 Числовое дифференцирование Первая производная функции в точке P k может быть описана графически как наклон касательной в этой точке, как показано на рисунке 12.6. Рисунок 12.6
Графическая интерпретация производной На рисунке 12.6 наклон прямой, касательной к кривой в точке P k (линия точка-пунктир) представляет производную от f(x) в точке P k . Если f(x) неизвестна, производная может быть аппроксимирована путем вычисления наклона секущей линии, которая пересекает кривую в двух точках, близких к P k . Это приближение показано на рисунке 12.7. Рисунок 12.7 Производная, аппроксимированная наклоном секущей Интерполяция, дифференцирование и интегрирование 327 f(x) P k dy dx f (x) P k P i P j y k y i x j y j x k h h x i На рисунке 12.7 производная в точке P k может быть приблизительно вычислена, сначала проведя секущую линию через точки P i иP j , которые считаются близкими к точке P k . Наклон этой секущей является нашим приближением производной при P k этом случае уравнение для производной имеет вид: где h = (x j –x i )/2, как показано на рисунке 12.7. Поскольку данные указывают на P i иP j сближаются, точность производной увеличивается . Другими словами, когда h приближается к нулю, наклон секущей между точками P i иP j аппроксимирует наклон касательной в точке P k . .В
В приведенных выше уравнениях для первой производной используются значения данных с обеих сторон оцениваемой точки. Они называются выражениями центрального различия. Часто мы вынуждены оценивать производную от точки, расположенной на пределе вершины набора данных. В этих случаях другая доступная точка отсчета находится либо слева, либо справа от точки, производная которой желательна. Когда опорная точка находится слева от точки желаемой производной, говорят, что формула является выражением обратной разности. Когда опорная точка находится справа от точки искомой производной, то формула описывается как прямое разностное выражение. На рис. 12.8 графически показаны прямые и обратные случаи разности производной. В этом случае производная может отличаться от производной в обратном направлении. Рисунок 12.8 Случаи обратной и прямой разности производной 328 Глава 12 j i k j i y y x x − ≈ − f(x) f(x) P k P k P j P j y k y k x j x j y j y j x k x k h h Обратная разница Обратная разница Прямая разница Прямая разница f(x) f(x) P k P k P i P я y k y k y я y я x k x k h h x я
x я Рисунок 12.9 Графическая интерпретация второй производной Формулы прямой и обратной разности для производной представляют собой разности координат y в обеих точках. Поскольку производная представляет собой наклон касательной в точке кривой, она выражает скорость изменения кривой в этой точке. Аналогичным образом, скорость изменения наклона, называемая второй производной , измеряет скорость изменения первой производной, как показано на рисунке 12.9. На рисунке 12.9 мы проводим секущие линии между соседними точками, P i и P и бытьмежду точками P и P j . Деля разницу в наклоне двух линий на расстояние между их средними точками, мы получаем скорость изменения наклона, или вторую онд производную. Другими словами, применяя метод вычисления производной к формуле для первой производной и используя две секущие линии на рис. 12.9, мы можем получить скорость изменения первой производной, которая является второй определяющей . Вторая производная выражается следующей формулой: Числовое дифференцирование следует использовать с осторожностью. Шум или неточности в данных могут легко привести к большим ошибкам в вычислении первой производной. Еще большие ошибки могут привести к оценке вторых производных. Ошибки при вычислении производной графически показаны на рисунке 12.10. Обратите внимание, что точки P i иP j используются при определении секущей. Однако, поскольку эти точки не расположены на кривой, они не дают достаточно хорошей оценки наклона касательной в точке P k . Интерполяция, дифференцирование и интегрирование 329 f(x) f(x) П к П к П я П я П Дж П Дж г к г к г я г я х Дж х Дж г Дж г Дж
х к х к ч. ч. ч. ч. х я х я ч. ч. " Дж к я Дж я г г г г х х х х г
// cols - количество столбцов в матрице // скалярное значение - это значение, которое будет добавлено // При выходе: // Скаляр добавляется ко всем элементам в строке матрицы i int rowStart = i * cols; for(int j = 0;j < cols ;j++) matx[rowStart + j] += скалярный; } шаблон <класс A> аннулирует RowMinusScalar(A *matx, int i, int cols, скаляр) { // Вычитает скаляр из каждого элемента в строке матрицы // При вводе: // * matx - это матрица вызывающего абонента // i - номер строки // cols - количество столбцов в матрице // скалярное значение - это значение, подлежащее вычитанию // При выходе: // Скаляр вычитается из всех элементов в строке матрицы i int rowStart = i * cols; for(int j = 0;j < cols ;j++) matx[rowStart + j] -= скаляр; } шаблон <класс A> аннулирует RowDivScalar(A *matx, int i, int cols, скаляр) { // Разделите все элементы в строке матрицы на скаляр // При вводе: // * matx - это матрица вызывающего абонента // i - номер строки // cols - количество столбцов в матрице // скаляр - это значение, которое нужно разделить на // При выходе: // Все элементы в строке матрицы i делятся на // скалярный int rowStart = i * cols; for(int j = 0;j < cols ;j++) matx[rowStart + j] /= скаляр; } шаблон <класс A> аннулирует ColMulScalar(A * matx, int j, int rows, int cols, скаляр) { // Умножьте столбец матрицы на скаляр // При вводе: // * matx - это матрица вызывающего абонента // j - номер столбца // строки - это количество строк в матрице // cols - количество столбцов в матрице // скалярное значение - это значение, которое нужно умножить на // При выходе: // элементы в столбце матрицы j умножаются на скаляр for(int i = 0;i < строки ;i++) { matx[(cols * i) + j] *= скалярный;
} } Линейные системы 351 Поскольку операции на уровне столбцов не так распространены при обработке массивов, как операции со строками , мы привели единственный пример, которым является функция ColMulScalar() . Программист должен быть в состоянии использовать его для разработки любых других операций с колонками, которые могут потребоваться. ........... ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Код находится в файле Un32_11.h, расположенном в папке Chapter13 в онлайновом программном обеспечении книги. 13.2.3 Низкоуровневые векторно-скалярные операции Процессоры массивов являются приложениями, требующими больших вычислительных ресурсов. Кодирование этих операций в языках высокого уровня является удобным и простым, но жертвы контроль, перфорМанс, и, возможно, точность. При низкоуровневых вычислениях процедура получает адрес первой многомерной записи, а также параметры строки и столбца, необходимые для выполнения операции. В вычислениях низкого уровня процедура получает адрес первой многомерной записи, а также параметры строки и столбца, необходимые для выполнения операции. Например, для выполнения операции на уровне строк низкоуровневая процедура должна знать адрес матрицы, количество элементов в каждом столбце, номер желаемой строки. Кроме того, низкоуровневая программа должна иметь в наличии горизонтальный коэффициент пропуска. Используя этот информационный код, можно посетить каждую запись матрицы и для каждой формы выполнить требуемую операцию. ;************************************************************* ; процедуры низкого уровня для векторной арифметики ;************************************************************* .КОД _ROW_TIMES_SCALAR ПРОЦЕДУРА ИСПОЛЬЗУЕТ esi edi ebx ebp ; Процедуру для умножения вектора строки матрицы на скаляр ; При вводе: ; ST(0) = скалярный множитель ; ESI —> матрица, содержащая вектор строк ; EAX = количество векторов строк (на основе 0) ; ECX = количество столбцов в матрице ; EDX = коэффициент пропуска по горизонтали ; При выходе: ; записи указанного вектора строк, умноженные на ST(0) ; Формула для смещения начала вектора равна ; смещение = [ ((i-1) * N * s) ] ; где i представляет строку матрицы, а s - столбец ; AL содержит номер желаемого вектора строк на основе 0 ; CL содержит количество записей в строке (N) ; DL содержит коэффициент пропуска MOV AH, 0 ; Очистить байт старшего порядка МУЛ CL ; AX = AL * CL ; При втором умножении предполагается, что произведение будет меньше ; 65535. Это предположение разумно, поскольку пространство матрицы ; назначено равным 400 с толкать DX
; Сохранять перед умножением MOV DH, 0 ; Очистить байт старшего порядка MUL DX ; AX = AX * DL POP DX ; Восстановить DX 352 Глава 13 Добавить ESI, EAX ; Добавить смещение к указателю MOV DH, 0 ; Очистить байт старшего порядка ; На этом этапе: ; ESI —> первая запись в строке матрицы ; ST(0) содержит скалярный множитель ; ECX = количество записей в строке ; EDX = длина в байтах каждой записи матрицы ЗАПИСИ: ВЫЗОВ FETCH_ENTRY FMUL СТ,СТ(1) ; Умножить на ST(1) ПОЗВОНИ STORE_ENTRY Добавить ESI, EDX ; Указатель на следующую запись ЦИКЛ ЗАПИСИ ПОВТОРНЫЙ _ROW_TIMES_СКАЛЯРНЫЙ КОНЕЦ P ;**************************************************************** _ROW_PLUS_SCALAR ПРОЦЕДУРА ИСПОЛЬЗУЕТ процедуру esi edi ebx ebp ; для добавления скаляра в строку матрицы ; При вводе: ; ST(0) = скалярный множитель ; ESI —> матрица, содержащая вектор строк ; EAX = количество векторов строк (на основе 0) ; ECX = количество столбцов в матрице ; EDX = коэффициент пропуска по горизонтали ; На выходе: ; элементы вектора строк, умноженные на ST(0) ; ; Формула для смещения начала вектора равна ; смещение = [ ((i-1) * N * s) ] ; AL содержит номер желаемого вектора строк на основе 0 ; CL содержит количество записей в строке (N) ; DL содержит коэффициент пропуска (8 для двойной точности) MOV AH, 0 ; Очистить байт старшего порядка МУЛ CL ; AX = AL * CL ; При втором умножении предполагается, что произведение будет меньше
; 65535. Это предположение разумно, поскольку пространство матрицы ; назначено равным 400 с толкать DX ; Сохранять перед умножением MOV DH, 0 ; Очистить байт старшего порядка MUL DX ; AX = AX * DL POP DX ; Восстановить DX Добавить ESI, EAX ; Добавить смещение к указателю MOV DH, 0 ; Очистить байт старшего порядка ; На этом этапе: ; ESI —> первая запись в строке матрицы ; ST(0) содержит скалярный множитель ; ECX = количество записей в строке ; EDX = длина в байтах каждой записи матрицы ЗАПИСИ_A: ВЫЗОВ FETCH_ENTRY УВЛЕЧЕНИЕ СТ,СТ(1) ; Добавить скаляр ПОЗВОНИТЕ STORE_ENTRY Добавить ESI, EDX ; Указатель на следующую запись ЦИКЛ ENTRIES_A ПОВТОРНЫЙ _ROW_PLUS_SCALAR ENDP ;**************************************************************** ; _ROW_DIV_SCALAR ПРОЦЕСС Линейные системы 353 ; Процедура деления вектора строки матрицы на скаляр ; При вводе: ; ST(0) = скалярный делитель ; ESI —> матрица, содержащая вектор строк ; EAX = количество векторов строк (на основе 0) ; ECX = количество столбцов в матрице ; EDX = коэффициент пропуска по горизонтали ; При выходе: ; Записи вектора строк, деленные на ST(0) ; ST(0) сохраняется ; Алгоритм: ; Деление выполняется путем получения величины, обратной ; делителю и с использованием процедуры умножения ; | ST(0)
| СТ(1) | СТ(2) ; делитель | ? | ? FLD ST(0) ; делитель | делитель | ? FLD1 ; 1 | делитель | делитель FDIV ST,ST(1); 1/делитель | 1 | делитель FSTP ST(1) ; 1/делитель | делитель | ? ПОЗВОНИ _ROW_TIMES_SCALAR FSTP ST(0) ; делитель | ? | ? CLD RET _ROW_DIV_SCALAR ENDP ;*************************************************************** _ROW_MINUS_SCALAR Прока ; процедура вычесть скалярной записи в матрицу ; подряд ; ; на входе: ; ST(0) = скаляр для вычитания ; ESI —> матрица, содержащая вектор строк ; EAX = количество векторов строк (на основе 0) ; ECX = количество столбцов в матрице ; EDX = коэффициент пропуска по горизонтали ; ; При выходе: ; Скаляр, вычитаемый из элементов вектора строк ; Алгоритм: ;
Вычитание выполняется путем изменения знака ; вычитания и с использованием процедуры сложения ; | ST(0) | СТ(1) | СТ(2) ; | # | ? FCHS ; -# | ? ВЫЗОВ _ROW_PLUS_SCALAR FCHS ; # | ? CLD RET _ROW_MINUS_SCALAR ENDP ЗАПИСНАЯ КНИЖКА ПРОГРАММИСТА В предыдущих процедурах скалярное вычитание выполняется путем изменения знака скалярного слагаемого, в то время как деление выполняется путем умножения на повторный код делителя. Также обратите внимание, что инверсия знака вектора строки может быть получена с помощью – 1 в качестве скалярного множителя. 354 Глава 13 Обратите внимание, что процедуры операций со строками, перечисленные ранее, получают коэффициент пропуска по горизонталиtal в регистре EDX. Основные процедуры _ROW_TIMES_SCALAR и _ROW_PLUS_SCALAR затем вызывают вспомогательные процедуры FETCH_ENTRY и STORE_ENTRY для доступа и сохранения записей матрицы. FETCH_ENTRY и STORE_ENTRY определяют тип требуемого доступа к данным в соответствии со значением в регистре EDX. Если значение в EDX равно 4, то данные кодируются в формате одинарной точности . Если значение равно 8, то данные имеют двойную точность. Если значение равно 10, то данные имеют повышенную точность. Этот механизм позволяет создавать низкоуровневый код, который может использоваться с любым из трех типов с плавающей запятой в ANSI / IEEE 754. Процедуры интерфейса C ++, которые закодированы как шаблонные функции, используют оператор sizeof в записи матрицы для определения типа данных, передаваемых вызывающим объектом. Visual C ++ версии 6 в операционных системах Win32 определяет размер типов данных int, long, unsigned long и float как 4 байта. Поэтому невозможно использовать размер переменной данных, чтобы определить, имеет ли аргумент тип integer или float. По этой причине процедуры интерфейса, перечисленные в этом разделе, могут использоваться только с аргументами типа float. Попытка передать целочисленные матрицы или скаляры приведет к необнаруженным вычислительным ошибкам. Функции интерфейса C ++ для низкоуровневых процедур работы со строками следующие: //************************************************************** // Функции интерфейса C ++ к модулю Un32_13 //************************************************************** шаблон <класс A> аннулирует RowTimesScalarLL(A *matx, int i, int cols, скаляр) { // Умножьте строку матрицы на скаляр, используя низкоуровневый код // в модуле Un32_13 // При вводе: // При вводе: //
* matx - это матрица вызывающего абонента (тип с плавающей запятой) // i - номер строки // cols - количество столбцов в матрице // скалярное значение - это добавляемое значение (тип с плавающей запятой) // Подпрограмма ожидает: // ST(0) содержит скаляр // ESI —> матрица // EAX = номер вектора строки // ECX = количество столбцов в матрице // EDX = коэффициент пропуска по горизонтали // При выходе: // элементы в строке матрицы i умножаются на скаляр int eSize = sizeof(matx[0]); _asm { MOV ECX, cols // Столбцы в ECX MOV EAX, i // Номер строки в EAX MOV ESI, matx // Адрес в ESI FLD скалярный // Скаляр к ST(0) ПЕРЕМЕЩЕНИЕ EDX, eSize // Пропуск по горизонтали ВЫЗОВ ROW_TIMES_SCALAR } возвращает; } Линейные системы 355 шаблон <класс A> void RowPlusScalarLL(A *matx, int i, int cols, скаляр) { // Добавьте скаляр к каждой записи в строке матрицы // используя низкоуровневый код // При вводе: // * matx - это матрица вызывающего абонента (тип с плавающей запятой) // i - номер строки // cols - количество столбцов в матрице // скалярное значение - это добавляемое значение (тип с плавающей запятой) // Подпрограмма ожидает: // ST(0) содержит скаляр // ESI —> матрица // EAX = номер вектора строки // ECX = количество столбцов в матрице // EDX = коэффициент пропуска по горизонтали // При выходе: // скаляр добавляется к элементам в строке I матрицы int eSize = sizeof(matx[0]); _азм
{ ПЕРЕМЕЩЕНИЕ ECX, cols // Столбцы в ECX MOV EAX, i // Номер строки в EAX MOV ESI, matx // Адрес в ESI FLD скалярный // Скаляр к ST(0) ПЕРЕМЕЩЕНИЕ EDX, eSize // Пропуск по горизонтали ВЫЗОВ ROW_PLUS_SCALAR } возвращает; } шаблон <класс A> аннулирует RowDivScalarLL(A *matx, int i, int cols, скаляр) { // Разделите строку матрицы на скаляр, используя низкоуровневый код // в модуле Un32_13 // При вводе: // *matx - это матрица вызывающего абонента (тип float) // i - номер строки // cols - количество столбцов в матрице // скаляр - это значение, на которое нужно разделить (тип с плавающей запятой) // Подпрограмма ожидает: // ST(0) содержит скаляр // ESI —> матрица // EAX = номер вектора строки // ECX = количество столбцов в матрице // EDX = коэффициент пропуска по горизонтали // При выходе: // элементы в строке матрицы i разделяются скалярным значением eSize = sizeof(matx[0]); _asm { MOV ECX,cols // Столбцы в ECX MOV EAX,i // Номер строки для EAX MOV ESI, matx // Адрес для ESI FLD скалярный // Скалярный в ST(0) 356 Глава 13 ПЕРЕМЕЩЕНИЕ EDX, eSize // Горизонтальный пропуск ВЫЗОВ ROW_DIV_SCALAR } возврат; } шаблон <класс A> аннулирует RowMinusScalarLL(A *matx, int i, int cols, скаляр)
{ // Вычитает скаляр из строки матрицы, используя низкоуровневый код // в модуле Un32_13 // // При вводе: // *matx - это матрица вызывающего абонента (тип float) // i - номер строки // cols - количество столбцов в матрице // скалярное значение - это значение для вычитания (тип с плавающей запятой), которое // // Подпрограмма ожидает: // ST(0) содержит скаляр // ESI —> матрица // EAX = номер вектора строки // ECX = количество столбцов в матрице // EDX = коэффициент пропуска по горизонтали // При выходе: // Скаляр вычитается из элементов в строке матрицы I int eSize = sizeof(matx[0]); _asm { MOV ECX,cols // Столбцы в ECX MOV EAX,i // Номер строки для EAX MOV ESI, matx // Адрес для ESI FLD скалярный // Скалярный в ST(0) ПЕРЕМЕЩЕНИЕ EDX, eSize // Пропуск по горизонтали ВЫЗОВ ROW_MINUS_SCALAR } возвращает; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Процедуры интерфейса C ++ находятся в файле Un32_12.h, расположенном в папке Chapter13\Test Un32_12.h в онлайновом программном обеспечении книги. 13.2.4 Матрично-скалярные операции Часто нам нужно выполнять скалярные операции над всеми элементами матрицы, причем более полезными операциями являются скалярное умножение, деление, сложение и вычитание. В этом разделе мы представляем процедуры для выполнения этих матрично-скалярных вычислений. Поскольку матрично-скалярные манипуляции требуют больших вычислительных затрат, мы разрабатываем подпрограммы в низкоуровневом коде и предоставляем функции интерфейса C ++ для процедур на языке ассемблера . Здесь мы перечисляем низкоуровневый код и интерфейсные процедуры для выполнения умножения матрицы на скаляр. Низкоуровневый код выглядит следующим образом: Линейные системы 357 .КОД _MAT_TIMES_SCALAR ПРОЦЕДУРА ИСПОЛЬЗУЕТ esi edi ebx ebp ; Процедуру для умножения матрицы на скаляр ; При вводе: ; ST(0) = скалярный множитель
; ESI —> матрица, содержащая вектор строк ; EAX = количество строк ; ECX = количество столбцов ; EDX = коэффициент пропуска по горизонтали ; При выходе: ; элементы матрицы, умноженные на ST(0) ; Общее количество записей - M * N КОЛИЧЕСТВО страниц AH, 0 ; Очистить байт старшего порядка MUL CL ; AX = AL * CL MOV ECX, EAX ; Сделать счетчик в CX ; На этом этапе: ; ESI —> первая запись в матрице ; ST(0) содержит скалярный множитель ; ECX = количество записей в матрице ; EDX = длина в байтах каждой записи матрицы (4, 8 или 10 байт) MAT_MUL: ВЫЗОВ FETCH_ENTRY FMUL ST,ST(1) ; Умножить на ST(1) ПОЗВОНИТЕ STORE_ENTRY Добавить ESI, EDX ; Указатель к следующей записи ЦИКЛ MAT_MUL CLD RET _MAT_TIMES_SCALAR ENDP : Функция интерфейса C ++ называется MatTimesScalarLL(). Код выглядит следующим образом шаблон <класс A> аннулирует MatTimesScalarLL(A *matx, int rows, int cols, скаляр) { // Умножьте матрицу на скаляр, используя низкоуровневый код // в модуле Un32_13 // При вводе: // *matx - это матрица вызывающего абонента (тип double) // rows - количество строк в матрице // cols - количество столбцов в матрице // скалярное значение - это значение, на которое нужно умножить (тип с плавающей запятой) // Подпрограмма ожидает: // ST(0) содержит скаляр // ESI —> матрица // EAX = номер вектора строки // ECX = количество столбцов в матрице // EDX = коэффициент пропуска по горизонтали // При выходе: //
элементы в матрице умножаются на скаляр int eSize = sizeof(matx[0]); _asm { MOV ECX,cols // Столбцы в ECX ПЕРЕМЕЩЕНИЕ EAX,строки // Строки в EAX MOV ESI, matx // Адрес для ESI FLD скалярный // Скалярный в ST(0) 358 Глава 13 ПЕРЕМЕЩЕНИЕ EDX, eSize // Горизонтальный пропуск ВЫЗОВ MAT_TIMES_SCALAR } возврат; } ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Процедуры для матрицы на скаляр, сложение, умножение, деление и subtracции находятся в UN32_13.Модуль ASM библиотеки MATH32. Процедуры взаимодействия с C ++ face находятся в файле Un32_12.h, расположенном в папке Chapter13/Test Un32_12 в онлайновом программном обеспечении книги. 13.2.5 Операции от матрицы к матрице Две матричные операции, определенные в линейной алгебре, - это сложение матриц и умножение. Сложение матриц - это процесс сложения соответствующих элементов двух матриц. Это подразумевает, что операция допустима только в том случае, если матрицы имеют одинаковый размер. Процесс сложения в случае C = A + B состоит в том, чтобы найти каждую соответствующую запись в матрицах A и B и сохранить их сумму в том же месте в матрице C. Матричное умножение C = A * B довольно нелогично. Вместо умножения соответствующих элементов двух матриц, матричное умножение состоит в умножении каждой из записей в строке матрицы A на каждую из соответствующих записей в столбце матрицы B и сложении этих произведений для получения записи, равной три раза C. Например Записи в продуктовой матрице C получаются следующим образом: Первая строка матрицы: C 11 = (A 11 *B 11 )+(A

Глава 14 Решение и синтаксический анализ уравнений Краткое содержание главы В этой главе представлены манипуляции и методы обработки, необходимые для вычисления алгебраических уравнений, обычно называемых синтаксическим анализом уравнений. Операции численного синтаксического анализа полезны при разработке многих типов математических процедур, часто встречающихся в графических, научных, финансовых и инженерных приложениях, а также при обработке, выполняемой языковыми компиляторами и интерпретаторами. Анализ числовых выражений считается одной из распространенных процедур, которые наиболее сложно реализовать в коде . . 14.0 Отображение функций При решении числового выражения в строковой форме код должен анализировать текстовую строку, содержащую уравнение. Обычно это уравнение определяет функциональное соотношение в виде: где f(x) представляет функциональное выражение, например: Чтобы решить уравнение, код должен применить фундаментальные правила алгебры, чтобы определить порядок, в котором должны оцениваться его лексические элементы, а затем приступить к вычислению одной переменной в терминах другой. При разработке подпрограммы , которая выполняет эти операции, необходимо учитывать следующие элементы: 1. Уравнение должно быть выражено таким образом, чтобы не оставалось неопределенностей относительно констант, переменных и операций, представленных в строке. В то же время уравнение должно состоять из стандартных символов ASCII, доступных на клавиатуре компьютера. Грамматика уравнений относится к правилам и соглашениям, которые гарантируют, что уравнение 375 () y fx = ( 1) y x TANx = + строка интерпретируется особым образом. Например, конкретная версия уравнения грамммар может требовать, чтобы математическое выражение: быть представленным в текстовой форме как y = 4*x^2 В этом случае символ * явно представляет умножение, в то время как символ ^ допускает представление степеней без надстрочного знака. При определении грамматики уравнений может быть принята любая общепринятая символика, при условии, что результирующее выражение имеет алгебраический смысл, а правила и символы непротиворечивы. 2. Компьютерные вычисления обычно требуют, чтобы числовые значения в строке уравнения были преобразованы в двоичные кодировки и сохранены в структурах данных, к которым может быть легко обращен код. В случае компилятора эта операция включает в себя поиск текущего значения идентификаторов, представляющих переменные и константы. В любом случае код должен быть способен заменить представление независимой переменной
able (x) ее числовым значением. 3. Обработка должна соответствовать правилам алгебры при выполнении различных операцийоперации, указанные в уравнении. Это означает, что элементы, заключенные в скобки, должны быть повторно решены в соответствии с их уровнем вложенности, и что операции должны читаться слева направо и выполняться в обычном порядке, то есть: сначала возведение в степень, затем умножение и деление, при этом сложения и вычитания выполняются в последнюю очередь. 4. По мере выполнения обработкой последовательности шагов, требуемых для вычисления числового выражения, представление выражения соответствующим образом обновляется. Это означает, что при выполнении вычисления его выражение в строке уравнения заменяется частичным результатом. Процесс продолжается до достижения окончательного сингулярного значения. В дополнение к более или менее стандартным операциям, перечисленным выше, сложная программа решения уравнений может выполнять и другие функции. Например: проверка строки уравнения на наличие синтаксических ошибок, проверка того, что константы и переменные имеют допустимое время жизни и область видимости, и подтверждение того, что заключенные в скобки термины правильно спарены. Многие подходы были успешно использованы при разборе уравнений и математических выражений. В литературе упоминается обычное арифметическое представление, в котором операторы помещаются между операндами в виде инфиксных обозначений. Например, выражение , как говорят, находится в инфиксной форме. В 1951 году Ян Лукасевич, польский логик, разработал обозначение, которое не требовало использования круглых скобок для указания приоритета. Эта схема представления, известная как польская нотация, позже была распространена на алгебраические выражения. В дальнейшем она была расширена до алгебраических выражений. 376 Глава 14 2 4 y x = 3 y x =+ Польская нотация устраняет необходимость в круглых скобках, помещая операторы перед операндами. Например, приведенное выше предыдущее выражение может быть записано в польской форме как Поскольку в обычной польской системе счисления оператор предшествует операндам, его иногда называют префиксной формой. Вариант польской нотации, называемый обратной польской нотацией, или RPN, помещает оператор после операнда следующим образом: RPN иногда называют постфиксной (или суффиксальной) формой. В Соединенных Штатах обратная Польская нотация была популяризирована благодаря ее использованию в некоторых калькуляторах Hewlett-Packard. Если выражение в префиксной форме польской нотации вычисляется справа налево, операторы могут быть обработаны по мере их появления в текстовой строке. То же самое применяется к вычислению выражения слева направо на обратном польском языке (RPN). Польская методология обозначения особенно подходит для реализаций на основе стека , поскольку числовые элементы могут быть извлечены из стека во время оценки в том порядке, в котором они требуются. По этой причине многие языковые процессоры и приложения для решения уравнений начинаются с переформатирования введенного пользователем алгебраического выражения в польскую или обратную польскую форму. В этой главе мы представляем процедуру решения уравнений, которая использует FPU при формировании всех численных вычислений. Поскольку FPU не может удобно получить доступ к внешней структуре стека, и поскольку его внутренний стек ограничен восемью элементами, мы приняли подход, основанный на массиве. Алгоритм основан на длинном алгебраическом
методе решения уравнений, с элементами в скобках или без них. Этот прямолинейный алгебраический подход подходит для реализаций FPU. 14.1 Разработка синтаксического анализатора Одной из наиболее сложных задач программирования при разработке языкового компилятора или интерпретатора является кодирование синтаксического анализатора. Синтаксический анализатор можно рассматривать как экспертную систему по грамматике и синтаксису конкретного языка. Мак (1991) утверждает, что “мозгом компилятора является синтаксический анализатор”. В динамике компиляции и интерпретации языка анализатор получает данные от программного элемента, называемого сканером. Именно сканер разбивает исходные инструкции на составные части, а именно: токены, идентификаторы, константы и операторы. Анализатор использует свои знания семантики языка для выполнения операций, указанных в сканируемом выражении. На рисунке 14.1 на следующей странице показаны шаги, выполняемые при вычислении простого математического выражения. Решение и синтаксический анализ уравнений 377 3 y x =+ 3 y x = + Рисунок 14.1 Вычисляем выражение y = 5,33 * x^ 2 На рисунке 14.1 первый шаг, выполняемый сканером, состоит в определении числовых значений в строке уравнения. Как только значения определены, они преобразуются из ASCII в двоичный код и сохраняются в массиве с именем C. В этом примере C0 - это значение независимой переменной (x), в то время как C1 и C2 являются постоянными членами в исходной строке уравнения. На этом этапе числовые значения в исходном выражении pression могут быть заменены ссылками на их местоположения в массиве C, поскольку в массиве C хранятся двойные конечные значения. Анализатор работает с этой упрощенной версией математического выражения. В случае языкового компилятора анализатор обычно вызывает службы генератора кода, который создает исходный текст на сборном языке. В приложении для решения уравнений или на интерпретируемом языке анализатор использует “исполнитель” или процедуру решателя , которая непосредственно вычисляет значение функции или выражения. Читатель должен учитывать, что пример на рис. 14.1 чрезвычайно прост. В реальном мире дело, выражения должны быть решены может содержать ключевые слова конкретного языка, идентификаторы, обозначающие переменные и константы, операторс, специальные символы, такие как пробелы и табуляции коды, жестко констанц, а также скобкой или другой приоритет-указывает на символы, которые, возможно, несколько уровней вложенности. В дополнение к интерпретации ключевых слов и арифметических операторов конкретного языка компилятор или интерпретатор должен быть способен 378 Глава 14 СКАНЕР выражение: данные в массиве Cj: для x = 3, решаем: y = 5,33 x ^ 2 y = C1 C0^C2 *
* * C3 = C0^C2 C4 = C1 C3 C0 = 3,0 C1 = 5,33 C2 = 2,0 C3 = 9,0 C4 = 47,97 FLD C0 FLD C2 ВЫЗОВ EXP_RT N ПЕРВЫЙ P C3 ; FLD C3 FLD C1 FMUL FST P C4 АНАЛИЗАТОР ГЕНЕРАТОР КОДА Или РЕШАТЕЛЬ выполнение логических функций над операндами, а также реализация структур принятия решений и итразработки в соответствии с синтаксисом. Поскольку эта книга не посвящена компиляторам или интерпретаторам языка, наше обсуждение синтаксических анализаторов ограничено операциями, необходимыми для вычисления функции, выраженной в алгебраическом уравнении. Тем не менее, процедура вычисления уравнения должна выполнять, с меньшей степенью сложности, все функции полностью реализованного языкового анализатора. Логика обработки, содержащаяся в подпрограммах, перечисленных в следующих разделах, может быть использована в качестве упрощенной модели для разработки полноценного анализатора, как требуется компилятору или программе-интерпретатору. 14.2 Оценка пользовательских уравнений Подход к разработке процедуры синтаксического анализа, как того требует приложение для решения уравнений или отображения функций, может основываться на требованиях к обработке, перечисленных в разделе 14.0. В следующих разделах мы рассмотрим каждое из этих требований отдельно . 14.2.1 Грамматика уравнений Первое требование заключается в том, чтобы уравнение было выражено таким образом, чтобы не оставалось никакой неопределенности в отношении закодированных операций. Возможной отправной точкой является идентификация всех распознанных арифметических операторов. Можно идентифицировать два типа арифметических операторов . Унарные операторы принимают один операнд, например, когда символы + и – используются для обозначения знака значения. Тригонометрические функции являются унарными. Обычные операторы, называемые двоичными операторами, требуют двух операндов. Это относится к операторам, которые указывают возведение в степень, умножение, деление, сложение и вычитание. В Ниже перечислены операторы, используемые процедурами обработки, разработанными позже в этой главе: операция символ или ключевое слово
Тригонометрия: синус SIN косинус COS касательная ЗАГАР дугообразная ASIN аркосинус ACOS арктангенс ATAN Арифметика: возведение в степень ^ умножение * деление / дополнение + вычитание – Специальные символы: назначение = принудительный приоритет (, ) переменная x x, X variable y y, Y Обратите внимание, что функция возведения в степень может быть использована для вычисления корней, например , квадратный корень из x будет представлен как x ^ 0.5. Несмотря на этот факт, большинство языков и приложений получат выгоду от отдельной функции квадратного корня. Также не означает, что арифметические операторы + и - служат двойной цели: их можно использовать Решение и синтаксический анализ уравнений 379 для выражения операции, а также знака числовой величины. Обработка должна предпринимать специальные шаги, чтобы различать унарные и бинарные действия знаков + и –. Другие символы могут включать знак =, который служит для обозначения начала разрешимого члена, и буквы x и y для представления переменных. Круглые скобки используются для группировки операций с принудительным приоритетом. Логика обработки сначала выполняет операции, заключенные в круглые скобки, как это согласуется с правилами algebra. Поскольку термины, заключенные в скобки, могут быть вложенными, логика обработки должна определять уровень вложенности, чтобы решить выражение от самого внутреннего к самому внешнему компоненту. 14.2.2 Синтаксис уравнения Выражение уравнения должно быть осмысленным и последовательным. Это определяется принятыми правилами синтаксиса. Например, синтаксис уравнения может требовать, чтобы двоичные арифметические операторы имели числовое выражение непосредственно слева и справа от них. Следовательно, выражение y = 3,0 * x имеет смысл. В этом случае константа 3.0 и переменная x представляют собой числовые значения . С другой стороны, выражение y = / * 4.2 не согласуется с правилом, поскольку оператор / слева от оператора * не преобразуется в числовое значение. Точно так же правила синтаксиса часто требуют, чтобы унарные арифметические операторы и тригонометрические операторы имели числовое значение непосредственно справа от них, например: y = SiNx является допустимым выражением, но y = SIN * x это не так. В этом случае оператор *, расположенный справа от оператора SIN, не преобразуется в числовое значение.
Что касается круглых скобок, синтаксис уравнения обычно требует, чтобы они были сопоставлены и чтобы они заключали в себя разрешимые выражения, например: y = 2 * (x^2 - 5) это юридическое выражение, но y = 2 * () не имеет смысла. Тем не менее, логика обработки программы вычисления уравнений может быть способна корректно разрешать некоторые выражения с несовершенным синтаксисом. Например, круглые скобки являются излишними в уравнении y = 2 * (x^2) Однако процедура оценки может быть способна правильно оценить выражение несмотря на его синтаксический недостаток. 380 Глава 14 Процедура разрешения может принимать несколько вариантов. Наиболее желательным методом обработки была бы проверка того, что выражение соответствует всем принятым грамматическим и синтаксическим требованиям. Это подразумевает поиск в строке уравнения недопустимых или неправильно используемых символов, выражений или ключевых слов, проверку числовых терминов, связанных с операторами, подтверждение сопряжения и вложенности вводимых в скобки выражений и проверку числовых операндов на предмет диапазона и недопустимых символов. С другой стороны, код может предполагать, что синтаксис уравнения правильный, и стремиться решить его практически без предварительной проверки. Этот второй подход используется в процедурах решения и синтаксического анализа, разработанных далее в этой главе. Мы оставляем на усмотрение программиста выполнение операций проверки грамматики и синтаксиса, которые обеспечивают корректность и согласованность выражения. pression. 14.2.3 Таблица символов и числовые данные Обычно полноценный анализатор, используемый в компиляторе или интерпретаторе языка, создает структуру данных и управляет ею, которая собирает и хранит всю необходимую информацию относительно идентификаторов, присутствующих в программе. Эта структура обычно называется таблицей символов bol. Таблица символов используется для определения типа данных и фактического числового значения идентификаторов. Например, при решении выражения: y = x * const_1 процедура обработки выполняет поиск идентификаторов x и const_1 в таблице символов. Таблица типовых символов содержит типы данных x и const_1, а также ссылку, которая позволяет получить доступ к значению идентификатора из соответствующего хранилища памяти. Создание таблицы символов и управление ею - одна из основных функций механизма компиляции . Простая процедура вычисления уравнения, подобная разработанной в этой главе, не требует использования идентификаторов, за исключением тех, которые представляют независимые и зависимые переменные. Таким образом, таблица символов сведена к элементарной структуре. С другой стороны, процедура обработки должна находить способы определения местоположения, кодирования и сохранения числовых значений, присутствующих в строке уравнения. Например, в строке уравнения y = 3,33 * x / 1,5 код должен быть способен идентифицировать переменную x, а также определять и сохранять ее значение. Кроме того, константы 3.33 и 1.5 должны быть преобразованы из ASCII в двоичный формат с плавающей запятой, а полученные значения сохранены для дальнейшего использования. Эти манипуляции обычно выполняются модулем сканера, как показано на рисунке 14.1. Процедура вычисления уравнения, перечисленная далее в этой главе, выполняет все эти операции. Во-первых, значение независимой переменной (x) сохраняется со смещением 0 массива значений с плавающей запятой в формате расширенной точности. Затем значения для жестко закодированных констант, которые появляются в строке уравнения, преобразуются в двоичный код с плавающей запятой (в формате расширенной точности) и сохраняются в последовательных элементах массива. В строке уравнения переменная x и числовые строки ASCII повторно помещаются со ссылкой на элемент массива, который содержит двоичную кодировку, как на рисунке 14.1. Решение и синтаксический анализ уравнений 381
В процедурах обработки, разработанных далее в этой главе, ссылка на массив кодируется в однобайтовой схеме, которая обозначается как формат Cj. Десятибайтовая кодировка удобна, поскольку она позволяет заменить однозначную константу или переменную, не расширяя строку уравнения......... Элемент j в этом обозначении является номером записи, или индексом, в массиве. Значение Cj вычисляется путем добавления 80H к смещению записи. Следовательно, смещение под номером 3 в массиве представлено значением 83H. Избыточный формат 80H для ссылок на элементы массива делает возможным для программного обеспечения идентифицировать эти ссылки в строке уравнения. Это возможно, поскольку все остальные текстовые символы в строке уравнения находятся в диапазоне от 0H до 7FH. 14.3 Алгоритм решения уравнений Анализатор, содержащийся в процедуре с именем _CALCULATE_Y, перечисленной далее в этой главе, получает от вызывающего объекта строку ASCII, которая представляет уравнение, где зависимая переменная (y) выражается как функция независимой переменной (x). Синтаксис требует, чтобы элемент слева от знака = не содержал никакого выражения, кроме зависимой переменной. " ". Поэтому выражение 2*y=x^3 недопустимо. Вместо этого уравнение должно быть введено в форме y = x^3/2. Это совместимо с ограничениями большинства языков высокого уровня. Например, язык C требует, чтобы элемент слева от знака = (называемый значением lvalue) был именем переменной или конкретным элементом массива. 14.3.1 Процедура _ОЦЕНКИ Строка уравнения может содержать любой из операторов, перечисленных в разделе 14.2.1, в соответствии с правилами синтаксиса, упомянутыми в разделе 14.2.2. Ядром нашего движка для решения уравнений является низкоуровневая программа с именем _EVALUATE, которая способна применять правила алгебры для численного вычисления выражения, не содержащего парных элементов. Процедура _EVALUATE основана на следующей логике: 1. Он получает от вызывающего абонента текстовую строку, содержащую математическое выражение, которое не включает элементы в скобках. Это требование делает возможным, чтобы выражение разрешалось слева направо в соответствии с основными правилами алгебры. 2. Код предполагает, что верхний регистр стека FPU содержит значение независимой переменной. 3. Элементы, которые можно найти в строке выражения, являются юридическими ключевыми словами и операторы, константы в десятичном или экспоненциальном формате, независимая переменная, представленная буквами x или X, а также однобайтовые ссылки (в формате Cj) на числовые данные, хранящиеся в рабочем массиве. 4. Процедуры _EVALUATE пытаются найти числовое решение математического cal выражение, полученное от вызывающего, путем применения правил элементарной алгебры. 382 Глава 14 Обработка предполагает, что синтаксис выражения согласован и что он соответствует грамматическим и синтаксическим требованиям. Процедура практически не проверяет согласованность или корректность математического выражения. Коммерческое применение этой процедуры, безусловно, потребует предварительной проверки строки выражения на правильность грамматики и синтаксиса. Поскольку _EVALUATE вычисляет значение выражения вызывающего объекта, это также упрощает его. Упрощение состоит в замене части текстовой строки, которая представляет операцию, частичными результатами, полученными во время обработки, аналогично тому, как это делается в методе решения уравнений от руки. Например, предположим, что выражение является x^2 + 7 * x - TANx + 4,33 должен быть оценен для значения x = 10. Первым этапом обработки этапа сканирования процедуры _EVALUATE является сохранение значения независимой переменной со смещением 0 внутреннего массива и замена буквы “x” в исходном выражении ссылкой на ence в формате Cj. Поскольку ссылка Cj для x всегда равна 80H, что соответствует индексу C0, выражение становится C0^2 + 7 * C0 - ТАНК0 + 4.33. Обратите внимание, что для упрощения этой иллюстрации мы не использовали постфикс H
для ссылок в формате Cj. Читатель должен понимать, что значение C0 в приведенном выше выражении на самом деле является смещением C0H в рабочем массиве. Второй шаг заключается в замене ключевых слов в строке выражения на синусоидальные шестибайтовые токены. Допустимыми ключевыми словами и токенами являются следующие: ключевое слово: токен: ГРЕХ 01Ч ПОТОМУ ЧТО 02Ч ЗАГАР 03Ч ASIN 04Ч ACOS 05Ч ATAN 06Ч Токенизация ключевых слов является удобным упрощением, поскольку синтаксическому анализатору легче работать с однобайтовыми токенами, чем с многосимвольными ключевыми словами. После того, как ключевое слово TAN обозначается символом 03H, строка выражения выглядит следующим образом: C0^2 + 7 * C0 - 03C0 + 4.33. Следующий шаг - найти и сохранить жестко запрограммированные константы в строке выражения. Сканирование выполняется слева направо от начала выражения. Встречающиеся константы - это 2, 7 и 4,33. Они преобразуются в формат с плавающей запятой и сохраняются в рабочем массиве. Затем уравнение редактируется таким образом, чтобы константы были повторно размещены по ссылкам на их массивы в формате Cj следующим образом: C0^C1 + C2 * C0 - 03C0 + C3 На данный момент рабочий массив выглядит следующим образом: Ссылка на Cj данные C0 10.0 (значение x) C1 2.0 C2 7.0 Решение и синтаксический анализ уравнений 383 C3 4.33 Процедура _EVALUATE теперь выполняет операции, указанные в строке expression, слева направо, в соответствии со следующими правилами: сначала выполняются тригонометрические операции , затем возведение в степень, затем умножение и деление, наконец, сложение и вычитание. По мере выполнения каждой операции частичный результат сохраняется в рабочем массиве, а текст, представляющий разрешенный элемент в строке выражения, заменяется соответствующей ссылкой на массив. Отсюда следует выражение C0 ^ C1 + C2 * C0 - 03C0 + C3 сначала упрощается путем вычисления касательной, обозначаемой символом 03, следующим образом: C0 ^ C1 + C2 * C0 - C4 + C3 C4 теперь содержит TAN 10. Следующее упрощение состоит в выполнении операции возведения в степень, указанной оператором ^. Теперь уравнение принимает вид: C5 + C2 * C0 - C4 + C3 C5 содержит 100, что является значением C0 ^ C1 или 10^2. Далее выполняется операция умножения , указанная оператором *. Теперь уравнение принимает вид: C5 + C6 - C4 + C3 На этом этапе переменные принимают следующие значения: C5 = 100 C6 = 7 * 10 = 70 C4 = TAN 10 = 0,176326 C3 = 4,33 Затем выполняются операции сложения и вычитания слева направо, как следующим образом: 100 + 70 = 170 170 - 0.176326 = 169.8236 169.8236 + 4.33 = 174.1536
Это значение хранится со смещением 9 рабочего массива, которое представлено символом C9H в формате Cj. ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Процедура _EVALUATE, найденная в модуле Un32_14.asm библиотеки MATH32 , реализует логику, описанную ранее. Файл находится в онлайновом источнике программного обеспечения книги. Функция интерфейса C ++ для процедуры _EVALUATE выглядит следующим образом: двойная оценка(char * equation, двойной varX) { // Функция для вычисления уравнения или части уравнения, // которая не содержит компонентов в скобках, согласно // следующим правилам грамматики уравнений: 384 Глава 14 // // SIN (sine) 01Ч // COS (косинус) 02Ч // ЗАГАР (касательная) 03Ч // ASIN (арксин) 04Ч // ACOS (аркозин) 05Ч // ATAN (арктангенс) 06H // // // Распознанные символы: // ^ = возведение в степень // * = умножение // / = разделение // + = сложение (или унарный +) // - = вычитание (или унарное -) // // // Другие элементы грамматики уравнения: // 1. Константы ASCII, например: // 1, 2,33 или -1,2E+233 // 2. Независимая переменная, представленная // буквой x или X. // 3. Двоичные константы, представленные одной цифрой // запись в формате, превышающем 80H // // // Правила приоритета: // 1. Уравнение вычисляется слева направо // 2. Сначала вычисляются тригонометрические функции // 3. затем операции возведения в степень // 4. затем умножение и деление //
5. сложение и вычитание выполняются последними // // При вводе: // equation[] - строка уравнения, оканчивающаяся на 0H // Знак равенства и элементы слева от него должны быть удалены // вызывающей процедурой // Переменная varX является независимой переменной // Использует: // Процедура _EVALUATE в модуле Un32_14 // библиотека MATH32 // // При выходе: // возвращает решение уравнения или математического // выражение, передаваемое вызывающим объектом в виде типа double // двойной результат; _asm { ДВИЖЕНИЕ ESI, уравнение // Адрес в ESI FLD varX ПОЗВОНИТЬ ОЦЕНИТЬ FSTP результат } возвращает результат; } Решение и синтаксический анализ уравнений 385 ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Функция C ++ Evaluate() находится в файле Un32_13.h, расположенном в папке проекта Chapter14/Test Un33_13 на компакт-диске с онлайновым программным обеспечением книги. 14.3.2 Процедура _CALCULATE_Y После оснащения процедурой, которая вычисляет зависимую переменную в простом математическом выражении, можно решить более сложную задачу вычисления выражения, которое может включать термины, заключенные в скобки. Простейший подход заключается в выделении элементов в скобках, которые можно рассматривать как простые выражения, и использовании _EVALUATE для нахождения численного решения этих элементов. Процедура _CALCULATE_Y основана на следующих программных элементах: 1 Он получает от вызывающего абонента текстовую строку, содержащую уравнение в виде y=w где w - математическое выражение, которое может включать элементы, заключенные в скобки. 2. Программа предполагает, что верхний регистр стека FPU содержит значение независимой зависящей переменной. 3. Элементы, которые можно найти в строке формул являются правовым запросам и опоператоры, константы в десятичной или экспоненциальном формате, независимой переменной рэпвозмущался буквы X или X, зависимой переменной (y), а знак=. 4. Процедуры _CALCULATE_Y пытаются найти числовое решение уравнения получено от вызывающего с применением правил элементарной алгебры. В этом случае обработка предполагает, что синтаксис выражения согласован и что оно соответствует грамматическим требованиям приложения или языка. Логика обработки для процедуры CALCULATE_Y состоит из подготовительных шагов и этапов обработки. Подготовительные шаги (называемые предварительными шагами в листинге кода) состоят из копирования правого члена уравнения вызывающей системы из буфера вызывающей системы в рабочий буфер. Затем он сбрасывает переменные и указатели подпрограммы и очищает рабочие буферы.
Этап обработки состоит из решения уравнения путем выполнения следующих шагов: ШАГ 1. В строке уравнения выполняется поиск символа “(”. Если символ “(” не найден, то уравнение не содержит терминов в скобках и может быть полностью передано ВЫЧИСЛИТЕЛЮ. В этом случае выполнение продолжается на ШАГЕ 6. ШАГ 2. Если найден символ “(”, его смещение в строке уравнения сохраняется в рабочей переменной (с именем LEFT_PARS). ШАГ 3. Затем в строке уравнения выполняется поиск символа “(” или “)” справа от символа “(” ранее найденный символ. ШАГ 4. Если найден символ “(”, то уравнение содержит вложенное выражение в скобках нажатие. Выполнение продолжается на ШАГЕ 2. 386 Глава 14 ШАГ 5. Если в строке уравнения найден символ “)”, то вводится элементарная скобка выражение расположено между смещением, сохраненным в LEFT_PARS, и текущим символом “)” . Выражение, заключенное в круглые скобки, копируется в рабочий буфер и передается процедуре EVALUATE. Когда выполнение возвращается, заключенное в скобки выражение стирается из исходной строки уравнения и заменяется числовым результатом, повторно преобразованным процедурой _EVALUATE. Этот результат сохраняется в буфере рабочего массива в избыточной форме C0H. Затем выполнение продолжается на ШАГЕ 1. ШАГ 6. На этом этапе либо исходное уравнение не содержало элементов в скобках, либо все элементы в скобках были разрешены. Теперь уравнение передается в процедуру _EVALUATE, которая возвращает окончательное решение. Логика завершения процедуры _CALCULATE_Y основана на предположении, что процедура _EVALUATE возвращает Cj > 1, если последняя итерация процедуры завершила выполненные вычисления. В этом случае результат находится в ST(0). Если Cj = 1, то последняя итерация _EVALUATE не выполняла никаких вычислений. В этом случае результатом является переменная x, если в верхнем диапазоне рабочего массива не сохранены значения, или запись с самым высоким номером в верхнем диапазоне рабочего массива. Это связано с тем, что значения в верхнем диапазоне рабочего массива вводятся процедурой _CALCULATE_Y. Ниже приведена функция интерфейса C ++ к процедуре _CALCULATE_Y. двойное вычисление (char * уравнение, double varX) { // Функция для вычисления зависимой переменной в математическом // выражении, которое может включать термины в скобках, возможно вложенные. // Эта функция использует процедуру _EVALUATE для решения // выражений, заключенных в круглые скобки, или для решения выражения // без круглых скобок. // // При вводе: // equation[] представляет собой массив символов, содержащий уравнение // текст в формате: // y=w // где w - выражение, которое может включать // термины в скобках, независимые // переменная, числовые константы и ключевые слова // и операторы, перечисленные в заголовке // _ Процедура ОЦЕНКИ // Уравнение должно заканчиваться через 0H или 0DH // ST(0) = значение независимой переменной (x) // // Использует: // Процедура _EVALUATE в модуле Un32_14 // библиотека MATH32 // При выходе:
// возвращает решение уравнения или математического // выражение, переданное вызывающим объектом, в виде double // двойной результат; _asm { Решение и синтаксический анализ уравнений 387 ДВИЖЕНИЕ ESI, уравнение // Адрес для ESI FLD


ориентированные объекты. Усовершенствование закончилось включением классов, виртуальных функций, стиля однострочных комментариев, перегрузки операторов, улучшенной проверки типов, множественного наследования, функций шаблонов и некоторой степени обработки исключений. Программирование на C ++ было стандартизировано в 1998 году как ISO/IEC 14882:1998. Текущая версия датируется 2003 годом. За время своего развития в язык C ++ было добавлено множество библиотек. Одна из первых библиотек (названная iostream) предоставляла столь необходимые функции ввода и вывода на уровне потока. Новые операторы с именами cin() и cout() заменили более неудобные функции C с именами printf() и scanf(). 16.0.2 Преимущества языка C++ Ниже приведены наиболее часто упоминаемые преимущества языка C: 1. C++ не является специализированным языком программирования, поэтому он подходит для разработки широкого спектра приложений, от основных системных программ до второстепенных утилит. 2. Хотя C ++ является относительно небольшим языком, он содержит все необходимые операторы, типы данных и управляющие структуры, чтобы сделать его в целом полезным. 3. Язык включает в себя богатый набор библиотечных функций для работы с ввод/вывод, манипуляции с данными и хранилищем, системный интерфейс и другие примитивы, не реализованные непосредственно в языке. 4. Типы данных и операторы C++ в точности соответствуют характеристикам компьютера аппаратное обеспечение. Это делает программы на C ++ эффективными, а также простыми в интерфейсе с программами на языке assembly. 5. Язык C ++ не привязан к какой-либо конкретной среде или операционной системе. Язык доступен на машинах, которые варьируются от микрокомпьютеров до мэйнфреймов. По этой причине программы на C ++ переносимы, то есть их относительно легко адаптировать к другим компьютерным системам. 6. Ориентация объекта в C ++ необязательна. Приложения, которым не требуется объектная ориентация эту функцию можно отключить . 16.0.3 Недостатки языка C++ Ниже приведены наиболее часто отмечаемые недостатки C++: 1. Поскольку C ++ не является языком очень высокого уровня, его не так-то просто выучить. Начатьразработчики считают, что некоторые конструкции на C ++ сложны и их трудно понять. 2. Правила языка C ++ не очень строгие, и компилятор часто допускает значительные изменения в кодировании. Это допускает некоторую небрежность в стиле, которая часто приводит к неправильным или неэлегантным привычкам программирования. 3. Вместо того, чтобы быть line C, маленьким и компактным языком, C ++ стал большим и сложным. 4. Библиотечные функции C ++ предназначены для работы на конкретной машине. Иногда это усложняет преобразование программного обеспечения C ++ в другие реализации или системы. 5. Ненадежность C не была устранена в C ++. 424 Глава 16 16.1 Реализации C и C++ для ПК Несколько компаний-разработчиков программного обеспечения разработали компиляторы C и C ++ для ПК. Некоторые из этих продуктов завоевывали и теряли популярность по мере появления на рынке других новых версий или реализаций. Исторически одними из наиболее известных реализаций C для ПК были компиляторы Microsoft C и Quick C, IBM C 2 compiler (который является версией Microsoft C, лицензированной IBM), компиляторы Intel iC 86 и iC 286, Borland Turbo C, Turbo C Professional, Lattice C, Aztec C, Zortech C и Metaware High C. Исторически одними из наиболее известных реализаций C для ПК были компиляторы Microsoft C и Quick C, IBM C 2 (которая является версией Microsoft C, лицензированной IBM). В настоящее время системы разработки на C ++ почти полностью заменили старые компиляторы C . Наиболее популярными из них являются версии Microsoft, поставляемые как часть студии разработчика, обычно называемой Visual C ++. Последняя версия Visual C ++, которая не включала .Поддержка NET была версии 6. После версии 6 суффикс .NET был добавлен в версии компилятора C ++, таким образом, в Visual C ++ .NET в версии
Studio разработчика 7, Visual C++ .NET 2003 и Visual C++ .NET 2005. Однако в версии 2005 .Обозначение NET было удалено, и появилась версия Visual C ++ 2005. Объяснение Microsoft изменения названия заключается в том, что .Обозначение NET привело пользователей к мысли, что компиляторы 7.0 и 7.1 предназначены исключительно для разработки на .NET , что не так. Корпорация Borland за эти годы разработала и выпустила на рынок несколько систем C ++ для ПК. Текущая версия - Borland C ++ Builder 2006 для Microsoft Windows. В продукте Borland доступны две версии: профессиональная и корпоративная версии. 16.2 Блок-схемы и проектирование программного обеспечения Набор логических инструкций, предназначенных для выполнения конкретной задачи, называется программой. Документ, содержащий набор инструкций для запуска компьютерной системы, может быть описан как программа включения питания для человека-оператора. Точно так же компьютерная программа - это набор логических инструкций, которые заставят компьютер выполнить определенную и специфическую задачу. Обратите внимание, что в обоих случаях концепция программы требует набора структур, которые следуют логическому шаблону и предсказуемому результату. Набор бессистемных инструкций , которые не приводят к предсказуемому концу, вряд ли можно считать программой. Программисты разработали логические средства, которые помогают им убедиться, что компьютерные программы следуют неизменной последовательности опций и связанных с ними действий . Одно из наиболее полезных вспомогательных средств называется блок-схемой. Блок-схема - это графическое представление опций и действий, которые формируют программу. Блок-схемы используют графические символы для обозначения различных типов программных операций. Наиболее распространенными из них являются: 1. Прямоугольник. Используется для представления операций обработки, таких как арифметические вычисления и манипуляции с данными. 2. Параллелограмм. Представляет функции ввода и вывода, такие как ввод с клавиатуры, отображение воспроизведение и операции печати. 3. Бриллиант. Указывает на решение или логическое сравнение, результатом которого является "да" или "нет". Язык C++ на ПК 425 4. Круг. Используется для обозначения точки завершения программы, такой как завершение выполнения или выхода с ошибкой. 5. Поточные линии. Используются для соединения других символов блок-схемы, указывающих направление потока выполнения программы. Использование блок-схемы лучше всего проиллюстрировать на примере. Рисунок 16.1 представляет собой блок-схему программы для включения компьютерной системы, в которой компоненты могут быть подключены к линии электропередачи тремя возможными способами: Рисунок 16.1 Блок-схема запуска машины 1. Все компоненты компьютера напрямую подключены к отдельным настенным розеткам. 2. Все компоненты компьютера подключены к шнуру питания, а шнур питания подключен к сетевой розетке. 3. Некоторые компоненты подключены к шнуру питания, а некоторые - напрямую к настенным розеткам. Обратите внимание на блок-схему на рисунке 16.1, что символы блок-схемы в виде ромба используются для представления программных решений. Эти решения соответствуют принципам аристотелевской логики, следовательно, на этот вопрос должно быть два и не более двух ответов . Эти возможные ответы обычно обозначены как "ДА" и "НЕТ" в 426 Глава 16 НАЧАЛО КОНЕЦ ДА НЕТ НЕТ ДА Все ли компоненты подключены по отдельности ? Все ли компоненты подключены
к блоку питания ? Включение отдельных переключателей компонентов Включение блока питания и отдельных компонентных выключателей Полосовой выключательвключения питания блок-схема. Решения являются ключевыми моментами в логике программы. Программа, которая не требует решений или сравнений, состоит из такой простой логики, что блок-схема была бы тривиальной и ненужной. Например, блок-схема, которая состоит из трех этапов обработки: запуска, решения проблемы и завершения, логически бессмысленна. Вычислительные машины нынешнего поколения не оснащены человекоподобным интеллектом. Поэтому некоторые предположения, которые очевидны при работе с человеческими существами, неверны в отношении компьютеров. Компьютерные программы не могут оставлять без внимания концы и не делать предположений о разумном поведении. Вы не можете сказать компьютеру : “Ну, ты знаешь, что я имею в виду”. Программист использует блок-схемы, чтобы убедиться, что каждый шаг обработки четко определен. Что касается графики блок-схемы, следует отметить, что стрелки являются необязательными на блок-схемах, если выполнение выполняется сверху вниз, как на рисунке 16.1. Однако, если выполнение выполняется вверх, на более высокий уровень блок-схемы, то соединительные линии должны иметь стрелки, указывающие направление потока программы. 16.3 Консольное приложение на C ++ Microsoft Visual C ++ предоставляет текстовый тип программы, который полезен при разработке программ на C ++, не требующих графического пользовательского интерфейса. Например, инженеру требуется небольшая программа для выполнения набора числовых вычислений. Если приложение не должно распространяться среди других пользователей и не требует вывода графики, то консольное приложение предоставляет простой способ разработки программы на C ++. Чтобы создать консольное приложение, вы должны сначала запустить Visual C ++ в Studio разработчика. Как только отобразится среда разработки, выберите Новую команду в меню Файл. Эта команда отображает окно выбора, показанное на рисунке 16.2. Рисунок 16.2 Команда создания нового файла на Visual C++ Язык C++ на ПК 427 Как только отобразится окно "Новый файл", вы должны убедиться, что вкладка "Проект" активна, и выбрать опцию "Консольное приложение Win32" в нижней части экрана "Проект". Далее вы вводите название вашего проекта и его местоположение в файловой системе компьютера. В этом примере мы назвали наш проект demo1. Кнопка справа от поля ввода с надписью “местоположение:” позволяет просматривать файловую систему компьютера. Как только вы нажмете кнопку OK, на следующем экране появится несколько радиооборудований, которые позволяют выбирать между пустым проектом, простым приложением, приложением “Привет, мир” или приложением, поддерживающим MFC (базовые классы Microsoft). В этом примере мы выберем “Простое приложение”, как показано на рисунке 16.3. Рисунок 16.3 Выбор опции “Простое приложение” Нажимая кнопку “Готово”, производит оповещение экран, который информирует пользователя о том, что скелет проект был создан, его название, и преЧии двух предварительно скомпилированные заголовки с названием “файл stdafx.H” и “StdAfx.cpp”.Рисунок 16.4 - это захват экрана уведомления. При нажатии кнопки OK выполнение возвращается на главный экран Visual Studio . Этот экран разделен на три панели. Та, что справа, - это редактор. Та, что внизу, показывает операции разработки и называется панелью сборки. Панель слева, называемая панелью рабочей области, показывает компоненты программы. На панели "Рабочее пространство" теперь отображаются две кнопки на нижнем поле. В данный момент выбрана одна из них отображаемый вид класса. Этот режим панели Workspace используется в объектно-ориентированной разработке. В это время вы должны нажать кнопку с надписью
Просмотр файла, а затем элемент управления со знаком плюс (+) слева от записи файлы проекта. 428 Глава 16 Это действие отобразит исходный файл, заголовок и файлы ресурсов, находящиеся в данный момент в проекте . Также ReamMe.txt файл, содержащий информацию о компонентах консольного приложения, которое вы только что создали. Рисунок 16.4 Экран информации о новом проекте Visual C ++ Если вы теперь развернете запись "Исходные файлы", нажав на ее кнопку со знаком плюс, отобразится список из двух текстовых файлов. Первый файл с именем demo.cpp (предполагая, что вы назвали свою программу “demo”) будет исходным файлом, созданным Visual Studio. Вы можете использовать этот файл в качестве отправной точки для вашей программы. Файл с именем StdAfc.cpp содержит единственную инструкцию для включения stdafx.h в ваш исходный код. Вы можете просмотреть stdafx.h, открыв группу Файлов заголовков и дважды щелкнув по имени файла. На рисунке 16.5 на следующей странице показан экран Visual Studio в этот момент. Теперь вам следует отредактировать исходный файл в соответствии с вашим собственным стилем программирования и начать кодировать консольное приложение. Меню Сборки содержит команду для сборки программы (с именем Rebuild All) и выполнения кода в среде разработки . Например, следующий список кода показывает модификацию простого файла приложения в программу Hello, World. Язык C++ на ПК 429 Рисунок 16.5 Экран Visual C++ для нового проекта // demo.cpp // Первая демонстрационная программа для консольного приложения // Автор кода: Хулио Санчес // Дата: 21 марта 2007 // #включить “stdafx.h” #Включить <iostream.h> int main(){ cout <“Весь мир \n\n”; возвращает 0; } Обратите внимание, что мы исключили параметры, переданные функции main(), поскольку наша программа их не использует. Также мы добавили строку включения для библиотеки iostream. Это позволит нам использовать мощные функции ввода и вывода этой библиотеки (cin() и cout()). Когда эта программа выполняется, отображается экран "Привет, мир". 430 Глава 16 Глава 17 Программирование на основе событий Краткое содержание главы В этой главе рассматриваются принципы программирования Windows. В нем мы объясняем различия между программами DOS и Windows и предоставляем модель, называемую программированием, управляемым событиями, которая подходит для приложений Windows. Мы также описываем основные компоненты программы Windows, ее файловую структуру и функциональные возможности. 17.0 Графические операционные системы Операционные системы, использовавшиеся в первом поколении цифровых компьютеров, требовали, чтобы пользователь переключал серию двоичных переключателей, к каждому из которых был прикреплен индикатор, для ввода данных в машину. В этих системах последовательность начальной загрузки состояла из предопределенной последовательности действий по переключению и нажатию кнопок, которые оператор запоминал и выполнял ритуально. Чтобы управлять одной из этих машин , нужно было принадлежать к избранному классу специалистов, говорящих на двоичном языке и придерживающихся шестнадцатеричных чисел,
которые посвятили большую часть своей жизни расшифровке тайн аппаратного и программного обеспечения. В последующие годы несколько изобретений и адаптаций упростили ввод и вывод данных с компьютера . Корпорация IBM, разработавшая перфокарты для своих бизнес-машин, адаптировала эту технологию к своей первой линейке компьютеров. Телетайпные машины (называемые TTYS) были изобретены для использования в индустрии связи , но вскоре были адаптированы для использования в качестве компьютерных устройств. С появлением TTY клавиатура пишущей машинки нашла свое применение в вычислительной технике. Затем появилось использование электронно-лучевых трубок коммерческого телевидения как способа отображения текста без необходимости печатать его на бумаге. Дополнительным бонусом было то, что ЭЛТ также можно было использовать для отображения изображений. Другие устройства последовали его примеру. Некоторые из них стали стандартными элементами технологии, в то время как другие исчезли. К ним относятся световое перо, сенсорный экран, графический планшет, джойстик и мышь. Общий элемент во всех этих 431 устройства заключаются в том, что они позволяют пользователю визуально взаимодействовать с машиной. Идея для интерактивного устройства ввода пришел с работы Алан Кей в компании Xerox в Пало - Альто, исследовательский центр в начале 1970-х годов. Доктора Кей пытается разработать компьютер, которые могут использоваться детьми дошкольного возраста, которые были слишком молоды, чтобы читать или вводить команды в текстовом виде. Возможный подход состоял в использовании небольших экранных объектов, называемых значками, которые представляли знакомую вещь. Механическое устройство (мышь) позволяло перемещать эти графические объекты по экрану для взаимодействия с системой. Результатом стали интерактивная графика и графический пользовательский интерфейс. Стив Возняк, один из основателей Apple computers, рассказывает, что он посетил Xerox PARC и увидел мышь и графический пользовательский интерфейс. Он сразу же пришел к выводу, что интерактивный способ управления компьютером - это путь будущего. Вернувшись в Apple, Возняк начал разработку операционной системы , которая поддерживала графическое взаимодействие с пользователем, управляемое мышью, основанное на значках. После одной или двух неудачных попыток компьютер Macintosh был выпущен. Вскоре после этого Microsoft выпустила графическую операционную систему для ПК под названием Windows. 17.1 Войдите в Windows Многие осознали преимущества более разумного и физического взаимодействия с вычислительной машиной. Однако потребовалось много лет для реальной реализации этой идеи в эффективной операционной системе, которая была бы предпочтительна большинству пользователей. Дуглас Энгельбарт продемонстрировал жизнеспособный интерфейс мыши в 1968 году, но потребовалось пятнадцать лет, чтобы мышь стала стандартным компьютерным компонентом. Для того чтобы реализовать графический пользовательский интерфейс (GUI), необходимо было иметь не только работающее указывающее устройство, но и видеотерминал с поддержкой графики. Кроме того, программное обеспечение должно было бы предоставлять набор графических сервисов независимо от устройства . В мире ПК эволюция аппаратных и программных компонентов в графическую операционную систему заняла примерно одно десятилетие. Первые версии были довольно примитивными и не получили широкого признания. Только после Windows95 графическая операционная система Microsoft для ПК стала стандартом . Первоначально разработка графической операционной системы для ПК была совместной работой Microsoft и IBM. Но вскоре у двух компаний возникли серьезные стратегические и тактические разногласия. Результатом усилий IBM стал продукт под названием Operating ating System/2, или OS/2. Казалось, что Windows и OS / 2 разделят рынок операционных систем для ПК. Однако по причинам, не связанным с ее техническими достоинствами, OS / 2 быстро уступила позиции Windows. OS/2 Warp версии 4.0 была на рынке до 2005 года, но IBM больше не продается. 17.1.1 Текстовые и графические программы Для программиста DOS и Windows находятся в разных мирах. Программы DOS имеют полный и неограниченный доступ к аппаратному обеспечению компьютера. Как только код получает контроль, он может делать все, что ему заблагорассудится. Его единственными ограничениями являются аппаратные возможности-
432 Глава 17 связи и навыки программиста. Хотя разработчики операционных систем, похожих на DOS, часто перечисляют правила, которым должны следовать “программы с хорошим поведением”, не существует способа обеспечить соблюдение этих правил. Намеренно или по ошибке программа DOS может внести хаос в систему, удалив файлы из хранилища и даже попытавшись физически уничтожить аппаратное устройство. Каждое приложение DOS имеет полный контроль над всеми системными ресурсами. Оно может выделить всю память для себя, установить любой удобный видеорежим, управлять принтером и линиями связи, а также мышью и клавиатурой. Повторные исходные коды не обязательно использовать совместно, поскольку операционная система находится в состоянии покоя, пока приложение находится на переднем плане. В этой среде более одного приложения редко выполняются одновременно. Приложения Windows должны совместно использовать ресурсы между собой и с операционной системой. Память, центральный процессор, аппаратное обеспечение дисплея, линии связи и устройства, мышь, клавиатура и дисковое хранилище являются общими. Каждая программа работает в своем собственном адресном пространстве и имеет ограниченный доступ к другим областям памяти. Код не может переменного токасесс устройств напрямую, но должны сделать это посредством операционной системы услуг называется прикладной программный интерфейс (ППИ). Этот механизм гарантирует хорошее поведение всех программ. Графическое программирование в Windows и DOS сильно отличается. Поскольку все исходные ресурсы являются общими, Windows должна контролировать доступ ко всем устройствам и ресурсам, включая память, видеосистему, устройства связи, а также компоненты ввода-вывода . На рисунке 17.1 показано, как программы DOS и Windows получают доступ к системным исходным кодам. Рисунок 17.1 Доступ приложений DOS и Windows к системным ресурсам Программирование на основе событий 433 память память дисковое хранилище дисковое хранилище видеосистема видеосистема Устройства ввода-вывода Устройства ввода-вывода DOS Приложение Windows Приложение Windows API сервисы 17.1.2 Графические службы Windows - это графическая операционная система; следовательно, приложение Windows - это графическая программа. Поскольку приложения Windows используют графический пользовательский интерфейс и учитывая, что доступ приложения к ресурсам должен контролироваться, Windows вынуждена предоставлять приложениям множество графических сервисов. В среде защищенного режима приложения не могут напрямую обращаться к памяти или устройствам. Если приложение пытается это сделать, центральный процессор уведомляет операционную систему (в данном случае Windows), которая сначала останавливает, а в конечном итоге уничтожает программу-нарушитель. Общее правило в Windows - веди себя прилично, иначе ты будешь уничтожен. Программист Windows не имеет прямого контроля над ресурсами компьютера, но операционная система предоставляет службы, которые обеспечивают контролируемый доступ. 17.2 Модели программирования Характер среды операционной системы определяет модели программирования
. В DOS программа является “богом машины”. Работающий код имеет неограниченный доступ и контроль. Он работает на своих собственных мощностях, реализует свою собственную функциональность и вызывает операционную систему только для запроса определенной службы. Он не использует компьютер совместно ни с какой другой программой. Следовательно, модель программирования для программы DOS представляет собой набор последовательных инструкций и программных конструкций. Управление не возвращается в операционную систему до завершения работы приложения. Эта форма взаимодействия между приложением и операционной системой приводит к появлению модели последовательного программирования . Концептуальная модель для программы Windows совершенно иная. В этом случае код не имеет прямого доступа к устройствам и ресурсам и должен совместно использовать машину с другими приложениями и с самой операционной системой. В текущих версиях Windows реализована упреждающая многозадачность. Это означает, что операционная система может переключать передний план (доступ к процессору) с одного приложения на другое. Если приложение ведет себя неправильно, Windows может просто отключить его. Следовательно, “богом машины” является операционная система, а не работающая программа. 17.2.1 Программы, управляемые событиями Для обеспечения такого способа взаимодействия между приложением и системным кодом необходима новая модель программирования. Эту модель иногда называют программированием, основанным на событиях. В программировании, основанном на событиях, синхронизация между операционной системой и приложением осуществляется в форме программных событий. Например, когда пользователь изменяет размер окна приложения (пользовательское событие), операционная система выполняет соответствующее действие, а затем уведомляет код приложения (системное событие). Приложение, в свою очередь, может решить предпринять собственные действия по обновлению своей области отображения, или оно может решить вообще не предпринимать никаких действий. В любом случае это возвращает управление операционной системе. Управляемая событиями модель, хотя и простая и эффективная, может показаться странной программисту, привыкшему работать в DOS. В случае программы Windows код приложения больше не является последовательным набором инструкций , а серией блоков кода, которые выполняются при получении соответствующего сообщения . 434 Глава 17 Событийно-ориентированная модель реализуется посредством сообщений, передаваемых между участниками. В этом примере мы можем сказать, что в ответ на событие изменения размера пользовательского окна устройство мыши отправило сообщение операционной системе, которая, в свою очередь, отправила сообщение приложению. Однако эти сообщения не похожи на вторжения, которые так часто используются в программировании DOS. Прерывания DOS могут выполняться одновременно; то есть новое прерывание может произойти до завершения предыдущего. С другой стороны, сообщения Windows помещаются в очередь и должны ждать, пока не придет их очередь на обработку. Программистам DOS приходится менять свое мышление при работе в Windows. На рис. 17.2 показаны две модели программирования: последовательная модель в программе DOS и управляемая событиями модель в программах Windows. В DOS приложение восстанавливает управление во время загрузки и сохраняет его в течение всего срока службы. В программе Windows событие пользовательского ввода отправляет сообщение операционной системе. Операционная система выполняет соответствующее действие и отправляет сообщение в соответствующее приложение . Приложение просматривает сообщение и решает, требуется ли какое-либо действие или сообщение следует проигнорировать. В любом случае оно возвращает управление Windows. Во время завершения работы приложение отправляет в Windows сообщение с запросом на завершение работы. Рисунок 17.2 Модели последовательного и событийно-ориентированного программирования Событийно-ориентированное программирование 435 DOS
(сообщение) (сообщение) ПРИЛОЖЕНИЕ ДЛЯ DOS ПРИЛОЖЕНИЕ ДЛЯ WINDOWS [обработчики событий] СОБЫТИЕ ПОЛЬЗОВАТЕЛЬСКОГО ВВОДА Windows ОПЕРАЦИОННАЯ СИСТЕМА [менеджер мероприятий] // программный код инициализирует get_user_input get_user_input: если требуется действие выполните действие X при завершении программы вернитесь в DOS else get_user_input действие X: действие (X=1) get_user_input действие (X=2) get_user_input . . // окончание программы // переключатель обработки сообщений (msg) пример 1: возвратсобытия приложения 1 случай 2: возвратсобытия приложения 2 . . значениепоумолчанию: Возврат // отправить сообщение сообщение об окончании выполнения возврат Менеджер событий Принципиальное различие между последовательными программами и программами, управляемыми событиями, заключается в том, что одна активна, а другая пассивна. Управляемому событиями коду не нужно предоставлять циклы для отслеживания пользовательского ввода, поскольку эта функция выполняется Windows. Приложение остается бездействующим до тех пор, пока операционная система не уведомит его о том, что произошло событие . Это код операционной системы, который должен отслеживать аппаратные устройства, чтобы обнаружить действия, которые должны быть обработаны кодом. Эта функция называется управление событиями, а Windows является менеджером событий. В обязанности менеджера событий входит обнаружение событий и обеспечение своевременного уведомления приложений, которые должны реагировать на события. Обычно это называется операцией отправки событий. Диспетчер событий отслеживает аппаратные устройства, которые могут генерировать события, такие как клавиатура и мышь. Подпрограмма программного обеспечения, которая проверяет действие на устройствах, генерирующих события, иногда называется циклом событий. Обработчик событий В модели, управляемой событиями, обработчиком событий является приложение. Его функция состоит в том, чтобы дождаться наступления события, а затем выполнить соответствующее действие. Обработчик событий не отслеживает устройства, генерирующие события напрямую, и часто не предоставляет ответ первого уровня на событие. Он просто выполняет действие или группы действий, которые входят в его обязанности, и возвращает управление менеджеру событий. В Windows все усложнения интерфейса устройства выполняются на системном уровне. В этом смысле программы, управляемые событиями, легче кодировать, поскольку “грязную работу” выполняет операционная система.
17.2.2 Типы событий События могут быть сгруппированы в несколько типов, хотя классификация не очень строгая . Часто одно событие может быть отнесено к той или иной группе в соответствии с нашими собственными определениями. Однако три общие группы могут быть разграничены без особого совпадения: • Системные события • Управляющие события • События программы Одно событие часто запускает другое того же или другого типа. Например, сгенерированное системой событие может сгенерировать управляющее событие. Или программное событие может быть причиной системного события, которое, в свою очередь, порождает другое системное событие и так далее. Это взаимодействие между событиями создает цепочку событий, и именно эта взаимосвязь между связанными событиями иногда затрудняет точное определение конкретного типа события. Системные события В эту группу входят те события, которые происходят в программном обеспечении операционной системы. Например, если пользователь нажимает левую кнопку мыши, когда курсор находится внутри области client, Windows отправляет приложению сообщение WM_LBOTTONDOWN. Это 436 Глава 17 сообщение указывает коду приложения, что произошло (или вот-вот произойдет) определенное действие, на которое, возможно, программа должна отреагировать. Возможно множество других системных событий, генерируемых системой, включая ввод текста пользователем на клавиатуре, выбор пункта меню, перемещение курсора мыши, перетаскивание объекта с помощью мыши, изменение размера окна или управление элементом управления полосой прокрутки. Управляющие события Управляющие события относятся к объектам графического управления, которых так много в среде Windows. Среди них кнопки, списки, поля со списком, полосы прокрутки, элементы управления вверх-вниз и многие другие. Событие элемента управления происходит, когда взаимодействие пользователя с элементом управления требует реакции от операционной системы или приложения. В этом случае элемент управления отправил сообщение операционной системе. При необходимости операционная система может затем сгенерировать событие для уведомления приложения о действии пользователя. Большая часть программирования, необходимого для реализации пользовательского интерфейса, состоит из поддержки событий управления. Причина в том, что в программах Windows доступно множество стандартных элементов управления. Программистам обычно удобнее использовать один из этих готовых компонентов, чем создавать настраиваемый. Элементы управления Windows более подробно обсуждаются далее в этой главе. Мероприятия программы На рисунке 17.1 мы можем видеть, что завершение работы приложения Windows требует, чтобы программный код отправил соответствующее сообщение операционной системе. В этом случае приложение инициирует событие в форме запроса на прекращение выполнения . Этот тип события называется созданным программистом или программным событием. 17.2.3 Моделирование событий Программное событие генерируется, когда код приложения отправляет сообщение операционной системе, требующее предоставления услуги или действия. Однако программные события редко находятся в начале цепочки событий. Например, программное событие, запрашивающее завершение работы программы, часто возникает в управляющем событии, когда пользователь указал на желание завершить программу. На рисунке 17.3 на следующей странице показана цепочка событий, которая происходит, когда пользователь нажимает на кнопку закрыть в строке заголовка окна. На рисунке 17.3 сообщение, отправляемое операционной системой приложению, называется "WM_DESTROY". Это одно из многих стандартных сообщений, которые используются Windowsdows. Сообщение WM_DESTROY указывает приложению, что операционная
система уже уничтожила окно. Следовательно, сообщение WM_DESTROY получено постфактум. Другое сообщение Windows с именем WM_CLOSE отправляется приложению, чтобы уведомить его о том, что пришло время выполнить очистку и завершить работу. WM_CLOSE - это своего рода предупреждение. Это означает, что рисунок 17.3 на самом деле представляет собой грубый набросок процесса, который на самом деле включает в себя несколько других этапов. Система обмена сообщениями Windows, которая является результатом механизмов генерации и обработки событий, может стать довольно сложной. Программисту или разработчику программ часто требуется создать модель цепочки событий. Было разработано несколько инструментов моделирования для графического отображения процессов и данных. Диаграммы потоков данных и сущностных связей являются хорошо известными инструментами, используемыми разработчиками программ. Однако эти условности- Программирование, управляемое событиями 437 традиционные инструменты не очень хорошо подходят для моделирования систем, управляемых событиями. Уорд и Меллор , а также Хатли и Прибхай представили вариации диаграмм потоков данных, которые лучше подходят для представления систем, в которых события происходят в реальном времени. На рис. 17.3 мы использовали символы из обычных диаграмм потока данных и расширений Уорда и Меллора для моделирования цепочки событий. Более сложные и детализированные представления могли бы включать символы для хранилищ данных, хранилищ элементов управления и для процессов. Рисунок 17.3 Цепочка событий 17.3 Файловая структура программы Windows В дополнение к принятию модели программирования, управляемой событиями, взамен традиционной последовательной модели, программисту Windows также необходимо привыкнуть к файловой структуре приложения Windows. Многие программы DOS имеют простые файловые структуры. Во многих случаях единственными файлами, внешними по отношению к источнику, являются различные включаемые файлы с долговыми расписками, перечисленные в заголовке программы. Внутренне компилятор DOS устанавливает необходимые параметры компоновщика, ссылается на соответствующие библиотеки и создает исполняемый файл почти прозрачно для программиста. Компиляторы Windows также предназначены для автоматического управления многими сложностями создания программ ; однако программам Windows обычно требуется больше файлов, чем их аналогам DOS . Некоторые из этих файлов не имеют аналогов в системах DOS. 17.3.1 Исходные файлы Исходные файлы в Windows имеют тот же тип, что и в программировании DOS. В любом случае они содержат языковые инструкции и конструкции программы на C или C++. Определенные расширения имени файла связаны с различными типами исходных файлов, это 438 Глава 17 сообщение. = кнопка закрытия вниз сообщение. = WM_DESTROY сообщение. = готово! управление событие система событие программа событие закрывающее окно завершение программы уборка являются: .C, .CPP и .H. Расширение .C изображает программу на C. Наличие этого расширения обычно указывает на то, что программа закодирована на обычном C; следовательно, она не включает в себя никаких ключевых слов и конструкций, являющихся частью C ++. The .Расширение CPP указывает
исходный файл, который может содержать ключевые слова и конструкции, специфичные для C ++. Выражение .H соответствует заголовочному файлу, на который обычно ссылаются в начале программы с помощью инструкции #include. 17.3.2 Библиотечные файлы C и C++ - это маленькие языки: они не предоставляют функций для выполнения операций ввода и вывода. По этой причине немногие полезные программы на C или C ++ не содержат никаких других функций, кроме тех, которые явно закодированы в исходном коде. Большинству программ DOS и практически всем программам Windows требуется поддержка библиотечных функций. Файлы библиотеки отличаются от исходных файлов. Все программные файлы, содержащие исходный код , объединяются во время компиляции. Библиотечные функции включаются во время компоновки, все хотя внутренние механизмы систем разработки C и C ++ иногда не позволяют нам заметить разницу. Часто случается так, что оператор #include в исходном файле ссылается на файл заголовка, который, в свою очередь, содержит ссылки на другие источники или на объектный код в библиотеке. Эти ссылки обычно создаются с помощью внешнего декларатора, который служит для указания того, что переменная или функция имеет внешнюю связь. На рис. 17.4 показаны различные исходные, объектные и библиотечные файлы, которые могут быть частью программы на C или C++. Рисунок 17.4 Файловая структура программы Windows Программирование, управляемое событиями 439 VENDOR.H VENDOR.OBJ ПРИЛОЖЕНИЕ ДЛЯ WINDOWS ПРИЛОЖЕНИЯ ДЛЯ DOS И WINDOWS WIN_LIB.DLL USER2.CPP USER1.CPP USER.OBJ MY_PROG.EXE КОМПИЛЯТОР C или C ++ КОМПОНОВЩИК ПОЛЬЗОВАТЕЛЬ.H VENDOR.LIB USER.LIB 17.3.3 Файлы ресурсов Программы Windows имеют доступ к элементам данных, хранящимся в исполняемом файле или в библиотечном файле, называемом resources. Хотя ресурсы - это данные, и хотя они часто связаны с файлом программы .EXE, они уникальны. Во-первых, повторные исходные тексты не могут быть изменены во время выполнения. На самом деле это файлы, доступные только для чтения, и они не доступны программному коду напрямую. Более того, ресурсы не находятся в области данных программы. Во время загрузки ресурсы программы обычно остаются в файле на диске до тех пор, пока они не понадобятся. Типичными ресурсами являются значки, курсоры, диалоговые окна, меню, растровые изображения и символьные строки.Текущее поколение программных средств для разработки операционных приложений Windows, таких как Microsoft Visual C++ и Borland C++, содержит специальные средства для работы с ресурсами. В системе Microsoft существует несколько специализированных редакторов ресурсов. Ресурсы, не имеющие эквивалента в программировании DOS, предоставляют несколько преимуществ приложениям Windows: • Компоненты пользовательского интерфейса изолированы от остальной части кода. • Приложения могут быть изменены без изменения ресурсов. • Данные могут совместно использоваться приложениями путем повторного использования одних и тех же ресурсов. • Графические элементы в пользовательском интерфейсе могут быть легко созданы. • Графические элементы пользовательского интерфейса могут быть изменены без изменения кода приложения. Несколько специальных типов файлов используются Windows для управления ресурсами. Файл сценария с
повторным исходным кодом (расширение .RC) представляет собой текстовый файл ASCII, из которого создаются двоичные повторные исходники. Файлы сценариев ресурсов могут быть созданы программистом вручную или они могут быть автоматически сгенерированы средствами ресурсов в среде разработки. Программа компилятора ресурсов, которая является частью системы разработки, создает двоичные файлы ресурсов (расширение .RES) из файлов сценариев ASCII (расширение .RC). В Microsoft Visual C ++ версия компилятора ресурсов для командной строки называется RC.EXE ; в Borland C ++ она называется BRCC.EXE или BRCC32.EXE. До Windows 95 компилятор ресурсов также имел задачу добавления двух конечных ресурсов в исполняемый файл, созданный компоновщиком. Компоновщик Microsoft для Windows 95 и более поздних версий может напрямую управлять файлами в обоих форматах .OBJ и .RES. 17.3.4 Создавать файлы В ходе эволюции систем разработки программного обеспечения составные программы становились все более мощными. Следовательно, количество переключателей, опций и режимов, которые можно было выбирать в компиляторах, компоновщиках и других инструментах, также быстро росло . Программисту становилось все труднее запоминать и вводить все эти параметры каждый раз, когда проект нужно было компилировать или обновлять . Для исправления этой ситуации были разработаны различные формы так называемых make-программ и утилит. С помощью make-программы программист записывал 440 Глава 17 все переключатели и опции в текстовом файле и утилите make будут автоматически применяться ими в конкретном проекте. В программировании DOS простейшей реализацией утилиты make является пакетный файл. Дополнительным усовершенствованием является программа, которая считывает переключатели и опции, выбранные в среде разработки, и применяет их к сборщику, компилятору, компоновщику или другим программным средствам. В большинстве сред использование программы make необязательно; однако на платформе Windows операции и элементы управления настолько сложны, что большинству программистов не пришло бы в голову вводить их вручную. Все основные системы разработки Windows C и C ++ для ПК имеют утилиту make . В Microsoft Visual C ++ она называется NMAKE.EXE. В системе Borland это MAKE.EXE. Текстовый файл для любой системы имеет расширение .MAK. Файлы Make состоят из инструкций в текстовом формате ASCII. Они могут быть созданы вручную или сгенерированы автоматически средой разработки. Программы создания можно ввести из командной строки или активировать из среды разработки, обычно выбрав определенную опцию меню. 17.3.5 Объектные файлы Программа-компилятор генерирует файл, называемый объектным файлом, который имеет расширение . sion .OBJ. Система разработки Microsoft Visual C ++ использует спецификацию Common Objectject File Format (COFF), которая возникла в операционной системе UNIX. Версия COFF от Microsoft добавляет дополнительные данные заголовка, чтобы сделать формат совместимым с DOS и 16-разрядными версиями Windows. Стекомпилерсинсковсдевелопментскийстемскавеманикиферент переключатели, опции и режимы работы выбираются пользователем. В Visual C++ параметры компилятора выбираются с помощью команды Settings меню Build . Диалоговое окно Настройки проекта содержит вкладку C/C ++, которая активирует переключатели и элементы управления компилятора, как показано на рисунке 17.5 на следующей странице. Как только в настройках проекта активируется вкладка C / C ++, отображается выпадающий список, соответствующий категории. Когда поле Категория раскрывается (см. рисунок 17.5), появляются восемь опций. Выбор любой из этих опций активирует диалоговое окно с новым набором выбираемых вариантов. Раскрывающиеся списки "Уровень предупреждения" и "Оптимизация " также показаны на рисунке 17.5. Уровень предупреждения компилятора определяет серьезность предупреждений, для которых компилятор генерирует сообщение
. Оптимизация состоит из четырех предопределенных режимов: по умолчанию, отключить, максимально увеличить скорость и минимизировать размер. Кроме того, запись с надписью Настроить (не показана на рис. 17.5) также находится в списке. При выборе этого параметра активируетсядругое поле списка настраиваемых оптимизаций, когда в поле списка категорий выбраны параметры оптимизации в .----------- Освоение всех опций компилятора и переключателей требует значительного опыта работы со средой разработки. Программисты часто используют настройки по умолчанию и при необходимости вносят в эти настройки изменения. Программирование, управляемое событиями 441 Рисунок 17.5 Visual C++ Параметры компилятора 17.3.6 Исполняемые файлы Как в DOS, так и в Windows исполняемый файл генерируется программой компоновщика. Команду link можно ввести вручную из командной строки, с помощью пакетного файла или программы make, или автоматически с помощью среды разработки. Ссылка операции принимает объект файла, сгенерированного компилятора или ассемблера (расширения .OBJ), библиотечные файлы, сгенерированные менеджером библиотек (расширение .LIB), файлы в обычном формате объектных файлов (расширение .COFF) и двоичные файлы ресурсов, созданные компилятором ресурсов (расширение .RES), и создает исполняемый файл с расширением .EXE. Кроме того, компоновщик Microsoft автоматически преобразует файлы формата 32-разрядного объектного модуля (.OMF) в COFF. Результатом компоновщика также может быть динамическая библиотека ссылок (extension .DLL). Компоновщик - это инструмент, который генерирует исполняемый файл; однако среды разработки Windows могут активировать функции компоновщика косвенно. В 442 Глава 17 Microsoft Visual C ++, программа CL может использоваться для компиляции исходных файлов или для компиляции, а затем связывания объектных файлов с исполняемыми файлами. Файлы Make также могут ссылаться на операцию компоновщика. Как и в случае с компилятором, существует значительное количество переключателей и опций, которые могут быть выбраны во время компоновки. В Visual C++ опции компоновщика активируются с помощью команды Настройки меню Сборки. Диалоговое окно "Настройки проекта" содержит вкладку "Ссылка", как показано на рисунке 17.6. Рисунок 17.6 Параметры компоновщика Visual C++ Когда в настройках проекта активна вкладка Ссылка, отображается выпадающий список с надписью Категория. При раскрытии поля Категория отображаются пять параметров (см. Рисунок 17.6). Выбор любого из этих параметров активирует диалоговое окно с новым набором возможных вариантов. Изучение всех переключателей и опций компоновщика требует владения системой разработки, а также глубоких знаний операционной системы Windows . И здесь программисты часто меняют параметры компоновщика по умолчанию только при необходимости, но такое отношение часто приводит к потере функциональности и манче. Программирование, управляемое событиями 443 17.3.7 Динамическое связывание Одной из уникальных характеристик Windows является динамическое связывание. В DOS компоновщик работает статически: он берет один или несколько объектных и библиотечных файлов и объединяет их в исполняемый файл, который физически включает весь код как в исходных текстах, так и в библиотечных модулях. Программы Windows используют обычные библиотеки и исходные файлы во время подключения, но также и особый тип файлов, называемый библиотекой динамических ссылок или DLL. В дытермодинамические связей, библиотеки файлов ссылаются на связь, но код не физиЧески включаются в исполняемый файл. Когда программа запускается, необходимые библиотеки времени выполнения загружаются в память и ссылки разрешаются. Библиотеки динамических ссылок - это двоичные файлы, которые имеют функции, которые могут совместно
использоваться несколькими приложениями. В дополнение к совместному использованию кода между приложениями, библиотеки DLL могут использоваться для разделения приложения на отдельные компоненты, которые более управляемы и их проще обновлять. Динамическое связывание имеет несколько преимуществ: • Несколько процессов могут совместно использовать один и тот же код, что позволяет экономить место в памяти и сокращать время доступа. • Библиотеки DLL могут быть изменены и обновлены независимо от приложений, которые их используют. Программы, использующие статические библиотеки, должны быть перекомпилированы при изменении библиотек. fied. Этот механизм может быть эффективно использован для обеспечения послепродажной поддержки программы . • Доступ к библиотекам DLL возможен из разных языков программирования или сред, если приложения соответствуют соглашениям о вызовах. Существуют также недостатки, связанные с библиотеками DLL. Один из них заключается в том, что код не является автономным, поскольку он зависит от файлов DLL, присутствующих в целевой системе. Если процесс, использующий динамическое связывание во время загрузки, ссылается на библиотеку DLL, которая недоступна на главном компьютере, Windows немедленно завершает программу. В случае приложений, которые зависят от динамической компоновки во время выполнения, программа не завершается, если библиотека DLL не загружена, но функциональные возможности, связанные с отсутствующей библиотекой DLL, недоступны для кода. В случае приложений, которые зависят от динамической компоновки во время выполнения, программа не завершается, если библиотека DLL не загружена, но функциональные возможности, связанные с отсутствующей библиотекой DLL, недоступны для кода.,, Библиотеки динамических ссылок хранятся в различных форматах и связаны с различными расширениями имен файлов. Стандартным расширением является .DLL, но файлы с расширением .EXE, .DRV, .FON и другие также могут работать как библиотеки динамических ссылок. Файлы DLL системного уровня, такие как KERNEL32.DLL, USER32.DLL, и GDI32.DLL, находятся в каталоге WinXX\SYSTEM. Файлы с расширением .DLL автоматически загружаются Windows. Другие должны быть явно загружены программным модулем с использованием функций API LoadLibary() или LoadLibaryEx(). В дополнение к библиотекам DLL, в программировании Windows используются два других типа библиотек и связанных с ними файлов: • Объектные библиотеки (расширение .LIB) содержат объектный код, который добавляется в приложение во время компоновки. Объектные библиотеки используются при статической компоновке. Стандартные библиотеки C с именами LIBC.LIB и LIBCMT.LIB входят в эту группу. • Библиотеки импорта (расширение .LIB) - это особая форма объектной библиотеки, которая не содержит кода. Библиотеки импорта предоставляют компоновщику информацию о dy444 Глава 17 библиотеки с пользовательскими ссылками. Доступ к библиотеке динамических ссылок GDI32.DLL осуществляется с помощью библиотеки импорта с именем GDI32.LIB. Программирование, управляемое событиями 445 Глава 18 Компоненты оконной программы Краткое содержание главы
Приложения Windows обладают уникальными характеристиками. В этой главе мы обсудим некоторые из наиболее оригинальных функций: соглашения об именовании Windows, числовые константы и дескрипторы Windows. Программы Windows также визуально отличаются, поскольку типичное приложение Windowsdows имеет графическое окно и интерактивные элементы управления. Во введении к главе рассказывается об использовании этих графических компонентов, а также элементов стиля программирования, таких как комментарии к программе и обозначения утверждений. Шаблоны программирования описываются как средство упрощения процесса кодирования. 18.0 “Привет, мир” Опытных и талантливых программистов DOS часто пугает их первое знакомство с программами Windows. Программирование под Windows описывается как странное, запутанное и неуклюжее. На самом деле программирование под Windows не более сложно, чем программирование под DOS, хотя программы под Windows на первый взгляд кажутся более сложными. Во-первых, текстовые программы Windows могут быть закодированы с использованием средств консольных приложений, описанных в главе 16. Что касается программ, которым требуется графика или графический пользовательский интерфейс, то программы Windows определенно легче разрабатывать и кодировать, чем их аналоги DOS. Поскольку Windows - это графическая среда, она содержит множество служб, стандартных подпрограмм и вспомогательных средств для кодирования, которые недоступны в DOS. Любой, кто когда-либо реализован графический пользовательский Ининтерфейсной для DOS приложение, буду очень признателен графических объектов в winДау. Керниган и Ричи в своей книге The C Programming Language (изданной Прентис-Холлом в 1978 году) перечислили код короткой программы, которая выводит на экран сообщение “привет, мир!”. С тех пор эта программа известна как программа Hello World, и многие другие авторы книг по программированию последовали ее примеру. Версия программы Hello World на C ++ выглядит следующим образом: 447 #включить <iostream.h> аннулировать main() { cout < “Привет, мир!”; } В текстовой операционной системе, такой как DOS или UNIX, программа Hello World довольно проста. Сообщение отображается в текущем положении текстового курсора с любыми активными атрибутами. Предполагается, что видеосистема представляет собой “стеклянный телетайп”, который автоматически прокручивается, когда текст доходит до конца экрана. В этой текстовой парадигме не предусмотрено графического пользовательского интерфейса или многозадачности. Программа Windows, выполняющая те же функции, должна иметь дело со многими другими уровнями сложности. Во-первых, она должна сосуществовать и совместно использовать ресурсы с операционной системой и другими приложениями. В главе 17 мы обсуждали модель программы, управляемой событиями, которая необходима в данном случае. Кроме того, приложение Windows - это графическая, а не текстовая программа. Наконец, программа выполняется в виде окна, которое можно перекрывать, перекрывать, сворачивать, разворачивать, в которое можно вводить данные с клавиатуры и мыши или с помощью рисования, и многих других функций. Как и следовало ожидать, код более объемный, хотя он относительно прост, учитывая достигнутую функциональность. Ниже приведен список версии программы Hello World для Windows: /******************************************************** WIN_HELLO.CPP Отображает сообщение “Привет, мир от Windows!" в клиентской области. *********************************************************/ #включить <windows.h> ОБРАТНЫЙ ВЫЗОВ LRESULT WndProc (HWND, UINT, WPARAM, LPARAM) ; // WinMain() int WINAPI WinMain (ПОМЕХА, ПОМЕХА hPrevInstance, PSTR szCmdLine, int iCmdShow) { статический символ szAppName[] = “WinHello” ; HWND
hwnd ; Сообщение сообщение ; // Определение структуры типа WNDCLASSEX WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra =0; wndclass.cbWndExtra =0; внеклассный.Помеха = Помеха ; внеклассный.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) Получить stockobject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; // Регистрация структуры wmdclass RegisterClassEx (&wndclass) ; // CreateWindow() hwnd = CreateWindow (szAppName, 448 Глава 18 “Программа WIN_HELLO”, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // начальная позиция x CW_USEDEFAULT, // начальная позиция y 500, // начальный размер x 300, // начальный размер y NULL, // дескриптор родительского окна NULL, // дескриптор меню окна Препятствие, // дескриптор экземпляра программы NULL) ; // параметры создания ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; // Цикл сообщений пока (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } верните сообщение.wParam ; } // Оконная процедура LRESULT ОБРАТНОГО ВЫЗОВА WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; переключатель (iMsg)
{ регистр WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, “Привет, мир из Windows!”, 1, &прямоугольник, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; возвращает 0 ; пример WM_DESTROY : PostQuitMessage (0) ; возвращает 0 ; } возвращает DefWindowProc (hwnd, iMsg, wParam, lParam) ; } Рисунок 18.1 на следующей странице представляет собой снимок экрана программы WIN_HELLO gram. 18.1 Соглашения об именовании Программист, привыкший работать в среде, отличной от Windows, сразу не заметит, что программа Windows содержит определенные незнакомые элементы. Одним из них являются не обычные имена, присваиваемые переменным. Например, в программе WIN_HELLO мы находим следующую строку: статический символ szAppName[] = “WinHello” ; Если вы программист на C или C ++, то сама инструкция довольно знакома: это объявление и инициализация статического массива типа char. Что может показаться вам необычным, так это имя массива szAppName. Идентификатор “szAppName” кажется странным Компоненты программы Window 449 потому что он соответствует соглашениям, рекомендованным Корпорацией Майкрософт для имен переменных . Считается, что этот стиль именования был придуман программистом Microsoft венгерского происхождения по имени Чарльз Симони по имени Чарльз Симони и в его честь назван венгерской нотацией. Рисунок 18.1 Экран программы WIN_HELLO В венгерской нотации имя переменной должно обеспечивать способ идентификации типа данных. В szAppName префикс sz обозначает строку, заканчивающуюся нулем. Слово или слова, следующие за префиксом, описывают содержимое переменной, в данном случае название приложения. В таблице 18.1 перечислены стандартные префиксы имен переменных, рекомендованные Корпорацией Майкрософт. Достоинства венгерской нотации горячо обсуждались. Те, кто поддерживает его, утверждают, что это помогает избежать ошибок несоответствия типов данных. Недоброжелатели упоминают, что связывание имени и типа данных вынуждает программиста изменять имя всякий раз, когда изменяется тип данных, что может привести к ненужному редактированию программы . Также то, что сама Microsoft не соблюдает венгерскую нотацию постоянно, во многих случаях этот стиль приводит к путанице. ..........., Возможно, наиболее известным источником путаницы, вызванной венгерской нотацией, является параметр wParam, передаваемый процедуре Windows (см. WinProc в списке программ WIN_HELLO ). В этом случае wParam указывает слово в соответствии с соглашением об именовании, но когда был разработан Win32 API, wParam был изменен на 32-разрядное целое число без знака. Однако для обеспечения согласованности со старым кодом в документации Windows он по-прежнему упоминается как wParam. В принципе, мы согласны с тем, что венгерская нотация создает больше проблем, чем она решает. Однако отклонение от него только ухудшает ситуацию, поскольку Microsoft официально внедрила его в Windows и в документации Visual C ++. В этой книге мы используем венгерскую нотацию в примерах программ и перечне кода, 450 Глава 18 особенно при обращении к типам и структурам данных Windows. В наших собственных переменных мы используем его довольно свободно и только ради простоты и единообразия. Таблица 18.1 Стандартные префиксы, рекомендованные корпорацией Майкрософт
ПРЕФИКС ТИП ДАННЫХ ИНТЕРПРЕТАЦИЯ b/f BOOL ноль = false, ненулевое значение = true (f означает флаг) по БАЙТУ 8-разрядное целое число без знака ch CHAR один символ ASCII cx/cy INT количество значений x и количество значений y d DOUBLE Реальное значение двойной точности IEEE dw DWORD 32-разрядное целое число h РУЧКА 32-разрядный дескриптор целого числа без знака hwnd HWND дескриптор окна i INT 16-разрядное целое значение l ДЛИННЫЙ 32-разрядное целое число без знака Lp/np ДАЛЕКО* 32-разрядный дальний указатель n КОРОТКИЙ 16-разрядное целое число без знака p указатель любой указатель, обычно далекий pt ТОЧКА 32-разрядные координаты, помеченные x и y, упакованные в структуру rgb RGB значения красного, зеленого и синего цветов упакованы в 32 бита следующим образом: 00BB GGRR x/ y INT x-координата и y-координата s массив символов строка sz массив символов строка, заканчивающаяся нулем w
WORD 16-разрядное целое число без знака Что касается имен функций, то здесь меньше причин для споров. Корпорация Майкрософт предлагает программистам использовать глагольно-именную модель, описывающую, что делает функция и с чем она работает: например, функции Windows API BeginPaint(), GetClientRect(), DrawText() и EndPaint(), на которые ссылаются в программе WIN_HELLO, указанной ранее в этой главе. Название функции начинается с заглавной буквы, как и все остальные слова в имени функции. Подчеркивание не используется в качестве разделителя . Поскольку этот стиль знаком большинству программистов на C и C ++, нет причин использовать другой для наших собственных функций. 18.2 Константы и дескрипторы Заголовочные файлы Windows определяют множество числовых констант, которые могут быть источником недоумения для непосвященных. Например, в программе WIN_HELLO, перечисленной ранее, о у с л у, ш е ф и н д ч е в ы е п р о в а с е и д е н т и ф и р с К С _ Ч Р е д О В А, C S _ V R E D R AW, WS_OVERLAPPEDWINDOW и CW_USEDEFAULT, среди прочих. Эти и многие другие идентификаторы используются в качестве заполнителей для числовых констант. Обоснование заключается в том, что символьные имена более значимы, чем числовые значения, и что они могут быть прозрачно изменены в коде и документации. В таблице 18.2 перечислены значения некоторых распространенных префиксов, используемых для числовых констант. Многие другие также используются функциями Windows. Компоненты программы Window 451 Таблица 18.2 Обычные префиксы Windows для числовых констант ПРЕФИКС ТИП BS стиль кнопки CBS стиль поля со списком CS стиль класса CW создать окно, связанное DT нарисовать текст, связанный DS стиль диалогового окна ES редактировать стиль класса IDI ИДЕНТИФИКАЦИОННЫЙ номер значка IDC Идентификационный номер курсора LBS стиль списка SBS стиль полосы прокрутки SS стиль статического класса WS стиль окна WM оконное сообщение 18.2.1 Дескрипторы Windows В контексте программирования Windows дескриптор - это маркер, представляющий элемент, объект или ресурс. Например, следующая строка в программе WIN_HELLO hwnd = CreateWindw (szAppName, ... является вызовом функции CreateWindows(), которая возвращает значение, присвоенное переменной hwnd.
способный hwnd. В свою очередь, hwnd определяется как переменная типа HWND ранее в списке кода следующим образом: HWND hwnd ; В этом случае значение, возвращаемое вызовом CreateWindow(), является дескриптором окна типа HWND, как определено в заголовочном файле windows.h. После того, как дескриптор был получен, его можно использовать для ссылки на объект или элемент данных, который он представляет. Например, достаточно, далее в списке программы WIN_HELLO мы видим, что дескриптор окна программы используется для получения системной информации. Вызов GetClientRect (hwnd, &rect) ; передает дескриптор функции Windows API для извлечения координат клиентской области Windows, которые хранятся функцией в переменной структуры с именем rect. Кроме того, Windows использует этот же дескриптор для идентификации конкретного окна вашей программы. Если в вашем приложении одновременно открыто несколько окон , оно может определить, к какому из них относится сообщение, изучив дескриптор. Программисты DOS сначала узнают о дескрипторах применительно к дисковым файлам. В DOS дескрипторы за пределами дисковых файлов и стандартных устройств мало используются . Дескрипторы Windows, с другой стороны, относятся ко многим типам объектов и ресурсов в среде, включая дескрипторы окон, строк, значков, меню, курсоров, экземпляров программы, графических объектов (включая перья, кисти, шрифты и растровые изображения), динамически выделяемой памяти, областей и цветовых палитр. В таблице 18.3 перечислены некоторые из наиболее часто используемых дескрипторов в программировании Windows. 452 Глава 18 Таблица 18.3 Распространенные типы дескрипторов Windows ИДЕНТИФИКАТОР ДЕСКРИПТОРА ОТНОСИТСЯ К HACCEL ускорителю HBITMAP растровое изображение HBRUSH кисть HCURSOR курсор HDC контекст устройства Шрифт шрифт ИКОНА значок ПРЕПЯТСТВИЕ экземпляр программы HKEY раздел реестра HMENU меню HPALETTE палитра GDI HPEN ручка GDI HRGN регион GDI HWND окно 18.3 Визуальные элементы Визуально приложение Windows имеет характерный внешний вид, который отличает его
от обычной текстовой программы и даже от других программ в графических, многозадачных средах. Большинство элементов, отличающих программу Windows от других, - это компоненты главного окна и элементы управления вводом /выводом, используемые при реализации графического пользовательского интерфейса. 18.3.1 Главное окно Хотя программа Windows может выполняться без вывода на устройство отображения, большинство приложений Windows будут иметь главное окно. Главное окно является основным средством ввода и вывода данных программы и ее единственным доступом к экрану. На рисунке 18.2 на следующей странице показаны основные компоненты главного окна программы. Некоторые элементы окна на рис. 18.2 присутствуют во всех окнах программы, другие должны присутствовать, но могут быть сконфигурированы по-разному, а третья группа необязательна........ ........... Ниже приведены основные строительные блоки программного окна: • На дисплее главного окна есть строка заголовка, хотя заголовок можно оставить пустым. • Кнопки управления справа от строки заголовка используются для сворачивания, разворачивания и закрытия окна программы. Программист может выбрать, какие из этих кнопок управления будут отображаться. • Значок в левой части строки заголовка активирует системное меню. Программы могут использовать значки Windows по умолчанию или один из своих собственных. • Строка меню необязательна. Обычная строка меню содержит одно или несколько выпадающих меню. Каждое выпадающее меню состоит из команд, которые активируются щелчком мыши или с помощью клавиши Alt и кода из подчеркнутых букв. Команды меню, которые расширяются в подменю, обычно обозначаются завершающими многоточиями. Компоненты программы Window 453 Рисунок 18.2 Элементы окна программы • Главное окно программы, а также многие элементы управления вводом / выводом могут иметь вертикальные или горизонтальные полосы прокрутки. Windows уведомляет приложение о действиях пользователя с помощью полос прокрутки, но приложение должно обеспечить требуемую обработку. • Программы Windows могут иметь строку состояния в нижней части экрана, которая может содержать единственную область отображения текста (как показано на рис. 18.2) или быть разделена на несколько областей . Строка состояния, как следует из ее названия, используется для отображения сообщений, которые информируют пользователя о состоянии программы. Строка состояния включает в себя ручки изменения размера с правой стороны, которые используются для изменения размера окна. • Зона экрана, назначенная каждому программному окну, называется клиентской областью. Размеры и графические атрибуты клиентской области могут быть получены из операционной системы с помощью дескриптора окна. 18.3.2 Элементы управления Окно на рис. 18.2 содержит несколько элементов управления: кнопки в строке заголовка, пункты меню и команды, регулятор изменения размера, полосы прокрутки и кнопку, которая приводит в действие системное меню. Кнопки, полосы прокрутки, команды меню и ручки изменения размеров - это лишь некоторые из многих компонентов управления, доступных в Windows. Эти 454 Глава 18 значок программы и системное меню строка меню вертикальная полоса прокрутки выпадающее меню
строка состояния ручка для определения размера горизонтальная полоса прокрутки строка заголовка кнопки управления графические компоненты, которые используются при реализации операций ввода-вывода, обычно называются элементами управления. Некоторые элементы управления существуют со времен оригинальной операционной системы Windows ; другие были добавлены с различными версиями и доработками; до сих пор другие элементы управления поставляются в качестве дополнений или послепродажных продуктов. Элементы управления подразделяются на две группы: предопределенные элементы управления и общие элементы управления. Различия между ними в основном связаны с типом сообщения, используемого для взаимодействия с приложением, и историческими соображениями. Программисты могут создавать свои собственные настраиваемые элементы управления и предоставлять процедуры обработки для работы с ними. В этом разделе мы ссылаемся на стандартные элементы управления, являющиеся частью Windows; к ним относятся: кнопки, флажки, поля редактирования, списки, поля со списком, полосы прокрутки, окна журнала, панели инструментов, всплывающие подсказки, строки состояния, древовидные представления и древовидные списки, индикаторы выполнения, элементы управления вверх-вниз, панели отслеживания и многие другие. На рис. 18.3 показаны некоторые из функций, часто используемых приложениями Windows. Рисунок 18.3 Элементы управления программами Windows 18.3.3 Другие визуальные компоненты В дополнение к элементам управления вводом /выводом, таким как те, что показаны на рис. 18.3, в приложениях Windows доступен богатый выбор дополнительных программных элементов. Каждый пользователь Windows знаком с диалоговыми окнами, списками и древовидными представлениями файлов, элементами управления вкладками, панелями инструментов и всплывающими подсказками, а также таблицами свойств. Мастера используются для оказания помощи пользователю в выполнении сложных задач. Большая часть программирования под Windows заключается в том, чтобы научиться внедрять эти готовые программные компоненты в ваш собственный код и адаптировать их для выполнения задач, которые требуются вашей программе. Компоненты программы Window 455 18.4 Стиль программирования О стилях кодирования были написаны книги, большинство из которых не очень полезны для работающего программиста. Более того, обсуждение стиля может показаться противоречием само по себе. Если стиль - это отличительная особенность, которая отличает человека, то мы должны позволить ему быть результатом индивидуальности каждого и не пытаться влиять на него с помощью наших мнений и рекомендаций. Однако отчасти это верно, в контексте программирования с помощью Windows плохой стиль кодирования легко приводит к программам, которые не поддаются расшифровке, и к пресловутому “спагетти-коду”. Как мы упоминали ранее в этой главе, программирование для Windows не является сложным или глубоким, но многие программы для Windows в конечном итоге становятся чрезвычайно сложными. Хорошо организованные и прокомментированные источники позволяют вам быстро найти основные процедуры и усвоить основы операций обработки. Хороший стиль способствует организации кода в исполняемые блоки и использованию комментариев для описания механизма, с помощью которого программа выполняет свои основные задачи. С другой стороны, комментарии к программе не должны использоваться для объяснения основ языка программирования или тривиальных или очевидных манипуляций.. Иногда мы видим строку кода, которая содержит несколько недостатков, например: int var1, var2 = 22; // Создание и инициализация // Переменных В этой строке мы можем обнаружить несколько проблем со стилем. Во-первых, современные методы программирования рекомендуют объявлять каждую переменную отдельно. В C и C ++ возможны множественные объявления и инициализации, но код становится понятнее,
если каждой переменной выделить по одной текстовой строке. Другим недостатком в строке примера является то, что имена переменных не указывают на их функцию в программе. Наконец, комментарий бесполезен, поскольку любой программист на C знает, что оператор является объявлением переменной. 18.4.1 Заголовки с комментариями Один из способов убедиться, что необходимая информация включена в источник, использовать предварительно разработанный блок комментариев. Компании-разработчики программного обеспечения часто снабжают своих программистов стандартными заголовками, которые должны быть прикреплены ко всем исходным файлам. Эти заголовки служат напоминанием программисту обо всех элементах информации, которые должны быть включены в каждый исходный файл. Следующее может быть использовано в качестве контрольного списка для получения информации, которая может быть включена в блок заголовка программы с комментариями: ...........: • Уведомление об авторских правах разработчиков • Название программы • Программист или имя программиста и другая личная информация • Имя исходного файла • Список связанных источников, программных модулей и файлов поддержки, включая библиотеки, DLL-файлы и необходимые дополнения • Описание системы разработки и ресурсов • Требования к программному оборудованию и программному обеспечению 456 Глава 18 • Хронология обновления программы, список дат и модификаций • История тестирования программы и исправления ошибок Ниже приведен заголовок комментария, используемый компанией-разработчиком программного обеспечения автора: //************************************************************ //************************************************************ // PROGNAME.CPP // Авторское право (c) 20?? от Skipanon Software Associates // ВСЕ ПРАВА ЗАЩИЩЕНЫ //************************************************************ //************************************************************ // Дата: Кодируется: // Имя файла: Название модуля: // Исходный файл: // Описание: // //************************************************************ // Библиотеки и поддержка программного обеспечения: // //************************************************************ // Среда разработки: // //************************************************************ // Системные требования: // //************************************************************ // Дата начала: // История обновлений:
// Дата МОДИФИКАЦИЯ // // //************************************************************ // История испытаний: // ПРОТОКОЛ ИСПЫТАНИЙ Дата РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ // //************************************************************ // Комментарии программиста: // // //************************************************************ //************************************************************ 18.4.2 Обозначения утверждений В течение последних нескольких лет в сообществе программистов наблюдалось движение , которое отдает предпочтение более формальным методам составления спецификаций программ. Утверждается, что программы , заданные математически, могут быть легко проверены на согласованность, тем самым избегая многих проблем проектирования и дефектов. Более того, эти формальные спецификации в конечном итоге приведут к автоматизированному кодированию, то есть к машинам, которые могут писать программы без вмешательства программиста-человека. Мнения жюри по-прежнему расходятся относительно практической жизнеспособности формальных методов спецификации программ, но большинство программистов согласны с тем, что это хорошая идея - уменьшить неоднозначность и непоследовательность в кодировании. Полуформальный метод спецификаций, называемый обозначением утверждений, постепенно завоевывает популярность. Он состоит из определенного Компоненты программы Window 457 стиль комментариев, которые вставляются в код для определения состояния ввода. В качестве заголовков комментариев используются следующие типы утверждений: УТВЕРЖДАТЬ Заголовок комментария ASSERT описывает состояние вычислений в конкретной точке кода. Идея состоит в том, чтобы указать то, что наверняка известно о переменных и других программных объектах, чтобы эту информацию можно было использовать при разработке и тестировании следующего кода. Специальные символы и термины связаны с заголовком ASSERT. Термин Assigned используется для представления переменных и констант, инициализированных значением. Символ && используется для представления логического И, в то время как символ || представляет логическое ИЛИ относительно условий утверждения. Символ == используется для документирования определенного значения, а символ ?? указывает, что значение неизвестно или не поддается определению в данный момент. Наконец, символ —>, или слово Implies, используется для представления отправленного логического вывода. Программист также может свободно использовать другие символы и термины из математики, логики или из конкретного контекста проекта, при условии, что они понятны и непротиворечивы. INV Заголовок комментария INV представляет инвариант цикла. Он используется для описания состояния вычислений в определенной точке цикла. Целью инварианта цикла является документирование основных задач, которые должны выполняться циклом. Поскольку инвариант вставляется внутри тела цикла, его утверждение должно быть истинным перед выполнением тела, во время каждой итерации цикла и сразу после того, как тело цикла выполнено в последний раз. Инварианты цикла также используются в методах оптимизации кода. ДО и ПОСЛЕ Заголовки комментариев ДО и ПОСЛЕ публикации связаны с функциями. Они устанавливают условия, которые ожидаются в точках входа и выхода функции. Предварительная серия описывает элементы (обычно переменные и константы), которые должны быть предоставлены вызывающей стороной. Утверждение POST: описывает состояние вычислений в момент, когда функция завершает свое выполнение. Взятые вместе, они представляют
условия контракта между вызывающим объектом и функцией. Если вызывающий объект гарантирует, что утверждение PRE: истинно во время вызова, то функция гарантирует, что утверждение POST: удовлетворено во время возврата. FCTVAL Утверждение FCTVAL представляет значение, возвращаемое функцией. В C это значение связано с именем функции. Поэтому его не следует использовать для представления переменных, измененных функцией. 18.4.3 Шаблоны программирования Одним из возражений, часто выдвигаемых против программирования на уровне API для Windows, являются сложности кодирования. В программе с именем WIN_HELLO, перечисленной ранее в этой главе, вы видели, что код для простой программы hello, world в Windows может быть довольно сложным . 458 Глава 18 Одним из способов уменьшить трудности программирования под Windows является использование шаблонов кода, которые обеспечивают стандартную структуру операций. Еще симplification-это “фантики”, позволяющие получить доступ к API служб косвенно, так кодирования трудности. Среды разработки, такие как MFC, Visual Basic, Power Builder и Delphi, основаны на этих двух упрощениях , хотя и реализованы по-разному на каждом языке. Наш подход исключает использование “оболочек” для доступа к службам API; однако всегда можно упростить прямое программирование на уровне API с помощью шаблонов исходного кода. Шаблон - это программа или часть программы, из которой были удалены все “особенности”. Метод шаблонов напоминает то, как скульптор использует каркас из дерева и проволоки для создания статуи, добавляя пластилин для лепки и манипулируя с ним. В случае скульптуры каркас имеет тело, голову, руки и ноги; однако у него нет ни пола, ни возраста, ни принадлежности к одежде. Одна и та же рама использовалась для изготовления статуй обнаженного пещерного человека и полностью одетой дамы при дворе Людовика XIV. Программируемая версия каркаса скульптора из дерева и проволоки представляет собой временную табличку для кодирования. Если мы возьмем идею стандартного блока заголовков и добавим к нему общие части программы WIN_HELLO, то получим следующий шаблон исходного кода для программы Windows: ...........: //************************************************************ //************************************************************ // TEMPL01.CPP // Авторское право (c) 20?? Автор : // ВСЕ ПРАВА ЗАЩИЩЕНЫ //************************************************************ //************************************************************ // Дата: Кодируется: // Имя файла: Название модуля: // Исходный файл: // Описание программы: // //************************************************************ // Библиотеки и поддержка программного обеспечения: // //************************************************************ // Среда разработки: // //************************************************************ // Системные требования: // //************************************************************
// Дата начала: // История обновлений: // Дата МОДИФИКАЦИЯ // //************************************************************ // История испытаний: // ПРОТОКОЛ ИСПЫТАНИЙ Дата РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ // //************************************************************ // Комментарии программиста: Компоненты программы Window 459 // // //************************************************************ //************************************************************ #включить <windows.h> // // Предварительное разъяснение процедуры Windows LRESULT ОБРАТНОГО ВЫЗОВА WndProc (HWND, UINT, WPARAM, LPARAM) ; // //**************************** // WinMain //**************************** int WINAPI WinMain (ПОМЕХА, ПОМЕХА hPrevInstance, PSTR szCmdLine, int iCmdShow) { статический символ szAppName[] = “AppName” ; // Имя приложения HWND hwnd ; Сообщение сообщение ; // Определение структуры типа WNDCLASSEX WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra =0; wndclass.cbWndExtra =0; внеклассный.Помеха = Помеха ; внеклассный.hIcon = LoadIcon (NULL,IDI_APPLICATION); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) Получить stockobject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL,IDI_APPLICATION); // Регистрируем структуру wmdclass RegisterClassEx (&wndclass) ; // CreateWindow() hwnd = CreateWindow (szAppName, “Заголовок окна”, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // начальная позиция x CW_USEDEFAULT,
// начальная позиция y CW_USEDEFAULT, // начальный размер x CW_USEDEFAULT, // начальный размер y NULL, // дескриптор родительского окна NULL, // дескриптор меню окна Препятствие, // экземпляр программы // дескриптор NULL) ; // параметры создания ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; // Цикл сообщений while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } верните сообщение.wParam ; } //**************************** 460 Глава 18 // Процедура Windows //**************************** LRESULT ОБРАТНЫЙ ВЫЗОВ WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; переключатель (iMsg) { // Обработка сообщений Windows обращение WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; // Начальные операции отображения здесь EndPaint (hwnd, &ps) ; возвращает 0 ; // Завершение выполнения программы в случае WM_DESTROY : PostQuitMessage (0) ; возвращает 0 ; } возвращает DefWindowProc (hwnd, iMsg, wParam, lParam) ; } Вы можете использовать этот шаблон в качестве фрейма для создания приложения Windows. Важно помнить, что программный шаблон - это не образец программы, в той мере, в какой рама скульптора - это не статуя. Некоторые шаблоны могут компилироваться правильно , в то время как другие - нет. Каждый шаблон содержит инструкции по его использованию. Обычно программист удаляет эти примечания, когда в них больше нет необходимости. ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Справочник Chapter_18\TEMPLATES в онлайновом программном обеспечении книги содержит электронный источник для всех шаблонов, перечисленных в тексте. Компоненты программы Window 461 Глава 19
Первая программа для Windows Краткое содержание главы В этой главе рассказывается о программировании API в Windows. Нельзя отрицать, что программирование Windows сложное: независимо от того, с чего мы начинаем или как мы к этому подходим, обсуждение вскоре переходит к деталям многих опций, режимов, элементов управления, переключателей и альтернатив. Существует более тысячи сервисов API, многие из которых принимают дюжину или более параметров. Управляемый событиями механизм, лежащий в основе программы Windows, часто становится сложным: системному и прикладному коду может потребоваться, например, изменить восемь или десять сообщений для выполнения простой задачи. В этой главе мы рассмотрим основные компоненты программы Windows и рассмотрим процесс создания программы Windows на основе шаблона. 19.0 Предварительные шаги Наш подход к программированию для Windows заключается в том, чтобы избегать библиотек классов или других оболочек, таких как Microsoft Foundation Classes (MFC). На начальном уровне окон пропрограммирования использовании заранее заготовленных интерфейсов может есть какое-то притяжение, однако в высокопроизводительные графические пакеты, в лучшем случае раздражают, а чаще всего главным препятствием. С другой стороны, мы пользуемся возможностями редактирования и создания кода, предоставляемыми Developer Studio, и используем wizards для создания программ, поскольку в этом случае не нужно платить за контроль или производительность. Прежде чем мы сможем создать крупное графическое приложение, мы должны быть в состоянии сконструировать платформу Windows code framework, которая его поддерживает. Изготовление программы требует не только знания языка программирования, но и навыков использования среды разработки. Например, чтобы создать значок для строки заголовка вашей программы, вам необходимо знать о службах API, которые используются для определения и загрузки значка, но вам также необходимо обладать навыками использования редактора значков, который является частью программы разработчика dio. Даже после того, как значок создан и сохранен в файле, вам необходимо выполнить ряд шагов, которые сделают этот ресурс доступным для программы. 463 19.1 Программный проект Мы предполагаем, что вы уже установили один из поддерживаемых программ разработки . Текст совместим с Microsoft Visual C ++ версии 5.0 и более поздних версий. При создании примеров программ для этой книги мы использовали Visual C ++ версии 6.0. В следующем разделе описаны шаги по созданию нового проекта в Microsoft Developer Studio, вставке шаблона исходного кода в проект, изменению и сохранению шаблона с новым именем и компиляции результирующего файла в исполняемый файл Windows dows. 19.1.1 Создание проекта Вы запускаете Developer Studio, дважды щелкнув значок программы на рабочем столе или выбрав его в группе программ Microsoft Visual C ++. Начальный экран отличается версией программы, конфигурацией Windows, параметрами, выбранными при последнем запуске Developer Studio, и разрабатываемым проектом. В версии 5.0 введено понятие рабочей области проекта, также называемой workspace, как контейнера для нескольких связанных проектов. В версии 5 расширение .mdp, использовавшееся ранее обычно для файлов проекта, было изменено на .dsw, которое теперь относится к рабочей области. Также были изменены диалоговые окна для создания рабочих областей, проектов и файлов. Структура рабочей области / проекта и базовый интерфейс также используются в Visual C++ версии 6.0. Мы начинаем с создания проекта на основе файла шаблона. Пошаговое руководство предназначено для ознакомления читателя со средой Developer Studio. Позже в этой главе вы узнаете о различных частях программы Windows и разработаете пример приложения. Мы называем этот первый проект Program Zero Demo, за отсутствием лучшего названия. Файлы проекта находятся в папке Program Zero Project в онлайн-пакете программного обеспечения книги. Проект расположен в рабочей области, которая может включать в себя несколько проектов. Проект
и рабочая область могут располагаться в одной папке или подпапке или в разных и могут иметь одинаковые или разные имена. В примерах и демонстрационных программах, используемых в этой книге, мы используем одну и ту же папку для проекта и рабочей области. Результатом такого подхода является то, что рабочее пространство исчезает как отдельная сущность, упрощая процесс создания. Новый проект запускается путем выбора команды New в меню Developer Studio File. Как только отобразится новое диалоговое окно, нажмите на вкладку проекта и выберите тип проекта из отображаемого списка. В данном случае нашим проектом является приложение Win32. Убедитесь, что запись "Местоположение проекта" соответствует желаемому диску и папке. Если нет, нажмите кнопку справа от текстового поля "Местоположение" и выберите другое. Затем введите название проекта в соответствующее текстовое поле в правом верхнем углу формы. Название проекта совпадает с тем, которое использовалось Development Studio для создания папки проекта. В этом примере мы создаем проектjectnamedProgramZeroDemowhichislocatedinafoldernamed 3DB_PROJECTS. Вы можете использовать эти же имена или создать другие по своему вкусу. Обратите внимание, что при вводе названия проекта оно добавляется к пути, указанному в текстовом поле locationtion. На этом этапе появится Новое диалоговое окно, как показано на рисунке 19.1. 464 Глава 19 Рисунок 19.1 Использование новой команды в меню "Файл" студии разработчика Убедитесь, что переключатель с надписью Создать новое рабочее пространство установлен таким образом, чтобы при нажатии кнопки OK в диалоговом окне создавались как проект, так и рабочее пространство. На данный момент вы создали проект, а также рабочее пространство с тем же именем, но в нем еще нет программных файлов. То, как вы будете действовать дальше, зависит от того, используете ли вы другой исходный файл в качестве основы или шаблона или начинаете с нуля. Если вы хотите создать исходный файл с нуля, нажмите на меню Developer Studio Project и выберите Добавить в проект и Новые команды. Это действие отображает то же диалоговое окно, что и при создании проекта, но теперь открыта вкладка Файлы. В случае исходного файла выберите опцию Исходный файл C ++ из отображаемого списка и введите имя файла в соответствующее текстовое поле. Диалоговое окно появится, как показано на рисунке 19.2, на следующей странице. Метод разработки, который мы используем в этой книге, основан на использовании шаблонов исходного кода . Чтобы использовать шаблон в качестве основы или другой исходный файл, вы должны выполнить различную серию шагов. Предполагая, что вы создали проект, следующим шагом будет выбор и загрузка шаблона программы или исходного файла. Мы используем шаблон с именем Templ01.cpp. Если вы установили программное обеспечение книги в своей системе, файл шаблона находится по пути 3DB/Templates. Чтобы загрузить исходный файл в ваш текущий проект, откройте меню Developer Studio Project и выберите пункт Добавить в проект, а затем команды Files. Это действие вызывает диалоговое окно Вставки файлов в проект. Используйте кнопки справа от текстового поля Посмотреть в, чтобы перейти к нужному диску и папке, пока не будет найден нужный файл. Первая программа для Windows 465 прочитал лекцию. На рисунке 16-3 показан файл Templ01.cpp выделен и готов к вставке в проект. Рисунок 19.2 Создание нового исходного файла в Developer Studio При использовании файла шаблона для запуска нового проекта вы должны быть осторожны, чтобы не разрушить структуру или не изменить исходный код. Файл шаблона обычно переименовывается после того, как он вставлен в проект. Можно вставить файл шаблона в проект, переименовать его, удалить из проекта, а затем повторно вставить переименованный файл. Однако проще переименовать копию файла шаблона перед его вставкой в проект. Используются следующие последовательности операций: 1. Щелкните меню Файл и выберите команду Открыть. Перейдите по каталогу структура, чтобы найти файл, который будет использоваться в качестве шаблона. В этом случае файл Templ01.cpp находится в папке 3DB/Templates.
2. По-прежнему удерживая курсор на панели редактора Developer Studio, откройте меню Файл и нажмите команду Сохранить как. Снова перемещайтесь по структуре каталогов, пока не дойдете до папки PROJECTS\Program Zero Demo. Сохраните файл, используя имя Prog_zero.cpp. 3. Нажмите на меню Проекта и выберите команды Добавить в проект и Файлы. Найдите файл с именем Prog_Zero.cpp в диалоговом окне Вставка файлов в проект выберите его и нажмите кнопку ОК. 466 Глава 19 Рисунок 19.3 Вставка существующего исходного файла в проект Файл Prog_zero.cpp теперь отображается в списке демонстрационных файлов Program Zero в области разработкирабочая область oper Studio. Он также отображается в окне редактора. Главный экран Developer Studio настраивается пользователем. Кроме того, размер областей отображения определяется системным разрешением. По этой причине невозможно изобразить отображение экрана Developer Studio, соответствующее тому, которое увидит каждый пользователь. На иллюстрациях и снимках экрана в этой книге мы использовали разрешение 1152 на 854 пикселя в 16-битном цвете с крупными шрифтами. Однако наш формат экрана может не совсем соответствовать вашему. На рисунке 19.4 на следующей странице показано полноэкранное отображение Developer Studio с файлом Prog_zero.cpp , загруженным в область редактора. Панель Project Workspace в Developer Studio была представлена в версии 4.0. Она имеет четыре возможных вида: вид класса, вид файла, Вид информации и вид ресурса. Представление ресурсов не отображается на рисунке 19.4. Для того , чтобы отобразить исходный файл в e d i t o r p a n e , вы должны указать м у с т ф и р с т с е л е к т ф и л е ви е в т а б а н д о б л е - c l i c k o n t h e Prog_zero.cpp имя файла. На этом этапе вы можете приступить к разработке нового проекта, используя переименованный файл tempplate в качестве основного источника. Первый шаг - убедиться, что разрабатываемое программное обеспечение работает правильно. Для этого откройте меню сборки Developer Studio и нажмите команду Перестроить все. Developer Studio компилирует и создает вашу программу , которая на данном этапе представляет собой не что иное, как переименованный файл шаблона. Результаты отображаются в области вывода. Если компиляция и компоновка прошли без ошибок, снова откройте меню Сборки и выберите команду Выполнить Prog_zero.exe команда нотонна. Если все в порядке, в вашей системе выполняется программа бездействия. Первая программа для Windows 467 Рисунок 19.4 Рабочее пространство проекта Developer Studio, редактор и панели вывода Теперь нажмите команду Сохранить в меню Файл, чтобы убедиться, что все файлы проекта сохранены на вашем жестком диске. 19.2 Элементы программы Windows Файл шаблона Templ01.cpp, который мы использовали и переименовали в предыдущем примере , представляет собой простую программу для Windows, не имеющую никаких функциональных возможностей, кроме отображения на экране window. Прежде чем приступить к редактированию этого шаблона в полезную программу, вам следует ознакомиться с его основными элементами. В этом разделе мы разбираем файл шаблона Templ01.cpp для подробного изучения каждого из его компонентов . Программа содержит два основных компонента: WinMain() и процедуру Windows. 19.2.1 WinMain() Все приложения с графическим интерфейсом Windows должны иметь функцию WinMain(). WinMain() для программы с графическим интерфейсом Windows является тем же, чем main() для приложения DOS. Обычно говорят, что WinMain() является точкой входа в программу, но это не совсем так. Компиляторы C / C ++ генерируют код запуска, который вызывает WinMain(), так что на самом деле Windows вызывает именно код запуска, а не WinMain(). Строка заголовка WinMain() выглядит следующим образом : 468
Глава 19 |------------------- Возвращаемый тип | |-------------- Одно из стандартных соглашений о вызовах | | определено в windows.h | | |-------- Имя функции | | | | | | [ список параметров .... --- ------ --------------------------------------------------int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { WINAPI - это макрос, определенный в заголовочном файле windows.h, который преобразует вызов функции в соответствующее соглашение о вызове. Напомним, что соглашения о вызовах относятся к тому, как аргументы функции размещаются в стеке во время вызова, и отвечает ли вызывающий объект или вызываемая процедура за восстановление целостности стека после вызова. Microsoft Basic, FORTRAN и Pascal помещают параметры в стек в том же порядке, в котором они объявлены. В этих языках стек должен быть повторно сохранен вызывающей стороной. В C и C ++ параметры передаются в обратном порядке, и стек восстанавливается автоматически после возврата вызова. По историческим причинам (и для использования преимуществ аппаратных возможностей процессоров Intel) Windows требует соглашения о вызовах Pascal. В предыдущих версиях Windows стандартом вызова WinMain() был PASCAL или FAR PASCAL. Вы все еще можете заменить WINAPI на FAR PASCAL, и программа будет правильно компилироваться и компоноваться, но использование макроса WINAPI делает вашу программу более переносимой. Параметры Чаще всего параметры передаются в WinMain() Windows, но некоторые могут быть переданы любой программой, выполняющей ваше приложение. Ваш код может проверять эти параметры , чтобы получить информацию об условиях, в которых выполняется программа. В WinMain() передаются четыре параметра: • HINSTANCE - это идентификатор типа дескриптора. Переменная hInstance - это целое число, которое идентифицирует экземпляр программы. Учтите, что в многозадачной среде может одновременно выполняться несколько копий (экземпляров) одной и той же программы. Windows устанавливает это значение и передает его вашему коду. Вашей программе необходимо получить доступ к этому параметру , чтобы ввести его в структуру WNDCLASSEX; также при вызове функции CreateWindow() . Поскольку дескриптор экземпляра требуется вне WinMain() для многих функций Windows API, файл шаблона сохраняет его в общедоступной переменной с именем pInstance. Как правило, использование общедоступных переменных нежелательно в программировании Windows , но этот случай является одним из допустимых исключений из правила. • Переменная hPrevInstance также имеет тип HINSTANCE. Этот параметр включен в запрос на совместимость с предыдущими версиями Windows, в которых использовалась одна копия кода для запуска более чем одного экземпляра программы. В 16-разрядной версии Windows первому экземпляру отводилась особая роль в управлении ресурсами. Поэтому приложению необходимо было знать, был ли он первым экземпляром. В hPrevInstance сохранялся дескриптор предыдущей позиции. В Windows 95 / NT и более поздних версиях этот параметр не используется, и его значение равно НУЛЮ. • PSTR szCmdLine. Это указатель на строку, содержащую команду tail, введенную пользователем при выполнении программы. Это работает только тогда, когда имя программы является en-
Первая программа для Windows 469 вводится из командной строки DOS или из диалогового окна "Выполнить". По этой причине он редко используется code. • int iCmdShow. Этот параметр определяет способ первоначального воспроизведения окна. Программа, которая выполняет ваше приложение (обычно Windows), присваивает этому параметру значение, как показано в таблице 19.1. Таблица 19.1 Параметры режима отображения WinMain() ЗНАЧЕНИЕ ЗНАЧЕНИЕ SW_HIDE Скрывает окно и активирует другое окно SW_MINIMIZE Сворачивает указанное окно и активирует окно верхнего уровня в системном списке SW_RESTORE Активирует и отображает окно. Если окно свернуто или развернуто, Windows восстанавливает его первоначальный размер и положение (такие же, как у SW_SHOWNORMAL) SW_SHOW Активирует окно и отображает его в его текущем размере и положении SW_SHOWMAXIMIZED Активирует окно и отображает его как развернутое окно SW_SHOWMINIMIZED Активирует окно и отображает его в виде значка SW_SHOWMINNOACTIVE Отображает окно в виде значка. Активное окно остается активным SW_SHOWNA Отображает окно в его текущем состоянии. Активное окно остается активным SW_SHOWNOACTIVATE Отображает окно в его последнем размере и положении. Активное окно остается активным SW_SHOWNORMAL Активирует и отображает окно. Если окно свернуто или развернуто, Windows восстанавливает его первоначальный размер и положение (такие же, как в SW_RESTORE) 19.2.2 Переменные данных Программный файл Templ01.cpp определяет несколько переменных. Одна из них, дескриптор главного окна программы, определяется глобально. Другие являются локальными для WinMain() или процедуры Windows. Переменная, определенная глобально, является: HWND hwnd; HWND - это 16-разрядное целое число без знака, которое служит дескриптором окна. Переменная hwnd ссылается на фактическое окно программы. Переменная инициализируется , когда мы вызываем службу CreateWindow(), описанную далее в этом разделе. Переменные, определенные в WinMain(), следующие: статический символ szClassName[] = "MainClass" ; // Имя класса СООБЩЕНИЕ сообщение ; 470 Глава 19 Первый - это массив символов, который показывает имя класса приложения. В шаблоне ему присвоено название MainClass, которое вы можете заменить на более значимоеfulone.Теаприкатионкласснамемуствеческамеонеский структура WNDCLASSEX. MSG - это структура типа сообщения, для которой msg является возможностью изменения. Структура MSG определяется в заголовочных файлах Windows следующим образом: структура typedef tagMSG { // сообщение HWND hwnd;
// Дескриптор окна, принимающего сообщение UINT сообщение; // номер сообщения WPARAM wParam; // Контекстно-зависимая дополнительная информация LPARAM lParam; // о сообщении DWORD время; // Время, в которое было отправлено сообщение ТОЧКА pt; // Положение курсора на момент публикации сообщения } MSG; Комментарии к элементам структуры показывают, что переменная содержит информацию , которая важна для исполняемого кода. Значения переменной message перезагружаются каждый раз при получении нового сообщения. 19.2.3 Структура WNDCLASSEX Эта структура определена в заголовочных файлах Windows следующим образом: структура typedef tagWNDCLASSEX { UINT cbSize; UINT стиль; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; ПОМЕХА Помеха; ИКОН hIcon; КУРСОР КУРСОР; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; HICON hIconSm; } WNDCLASSEX; Структура WNDCLASSEX содержит информацию о классе window. Она используется с функциями RegisterClassEx() и GetClassInfoEx(). Структура аналогична структуре WNDCLASS, используемой в 16-разрядной Windows. Различия между двумя структурами заключаются в том, что WNDCLASSEX имеет элемент cbSize, который определяет размер структуры, и элемент hIconSm, который содержит дескриптор маленького значка, связанного с классом window. В файле шаблона Templ01.cpp раскрыта структура и переменная инициализирована следующим образом: // Создание структуры WNDCLASSEX WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (WNDCLASSEX) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra =0; wndclass.cbWndExtra =0; внеклассный.Помеха = Помеха ; внеклассный.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject
Первая программа для Windows 471 wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szClassName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; Класс window - это шаблон, который определяет характеристики конкретного окна, такие как тип курсора и цвет фона. Класс также определяет адрес процедуры Windows, которая выполняет работу для window. Переменные structures определяют класс window следующим образом: cbSize задает размер структуры в байтах. Элемент устанавливается с помощью оператора sizeof в инструкции: sizeof(WNDCLASSEX); style определяет стиль или styles класса. Два или более стилей могут быть объединены с помощью побитового оператора C ИЛИ (|). Этим элементом может быть любая комбинация значений из таблицы 19.2. Таблица 19.2 Краткое описание стилей класса Window СИМВОЛИЧЕСКАЯ КОНСТАНТА Экшен CS_BYTEALIGNCLIENT Выравнивает клиентскую область окна по границе байта (в направлении x) для повышения производительности во время операций рисования. Этот стиль влияет на ширину окна и его горизонтальное положение на дисплее. CS_BYTEALIGNWINDOW Выравнивает окно по границе байта (в направлении) для повышения производительности во время операций, связанных с перемещением окна или изменением его размеров. Этот стиль влияет на ширину окна и его горизонтальное положение на дисплее. CS_CLASSDC Выделяет один контекст устройства для совместного использования всеми окнами в классе. Классы окон зависят от процесса; следовательно, разные потоки могут создавать окна одного и того же класса. CS_DBLCLKS Отправляет сообщения о двойном щелчке в оконную процедуру, когда пользователь дважды щелкает мышью, когда курсор находится в окне, принадлежащем классу. CS_GLOBALCLASS Позволяет приложению создавать окно класса независимо от значения параметра hInstance , переданного функции CreateWindowEx(). Если вы не укажете этот стиль, параметр hInstance, переданный функции CreateWindowEx(), должен совпадать с параметром, переданным функции RegisterClass(). CS_HREDRAW Перерисовывает все окно, если перемещение или изменение размера изменяет ширину клиентской области. CS_NOCLOSE Отключает команду Закрыть в системном меню. (продолжает) 472 Глава 19 Таблица 19.2 Краткое описание стилей классов окон (продолжение) СИМВОЛИЧЕСКАЯ КОНСТАНТА Экшен CS_OWNDC Выделяет уникальный контекст устройства для каждого окна в классе. CS_PARENTDC
Указывает, что дочерние окна наследуют контекст устройства своего родительского окна. Указание CS_PARENTDC повышает производительность приложения. CS_SAVEBITS Сохраняет в виде растрового изображения часть изображения экрана, затемненную окном. Windows использует сохраненное растровое изображение для воссоздания изображения экрана при удалении окна. Этот стиль полезен для небольших окон (таких как меню или диалоговые окна), которые отображаются ненадолго, а затем удаляются перед выполнением других действий на экране. CS_VREDRAW Перерисовывает все окно, если перемещение или изменение размера изменяет высоту клиентской области. Из них стили CS_HREDRAW и CS_VREDRAW являются наиболее часто используемыми. Они могут быть использованы для создания окна, которое автоматически перерисовывается, если его размер изменяется по вертикали или горизонтали, как реализовано в коде Templ01.cpp. lpfnWndProc является указателем на оконную процедуру, описанную далее в этой главе . В шаблоне Templ01.cpp он инициализируется именем процедуры Windows следующим образом: wndclass.lpfnWndProc = WndProc; cbClsExtra - это количество дополнительных байтов, которые должны быть выделены в соответствии со структурой класса window. Операционная система инициализирует байты равными нулю. В шаблоне этому элементу присвоено нулевое значение. cbWndExtra - это количество дополнительных байтов, которые необходимо выделить после создания экземпляра window. Операционная система инициализирует байты равными нулю. В шаблоне этому элементу присвоено нулевое значение. Препятствие является дескриптором экземпляра оконной процедуры. hIcon - это дескриптор значка класса. Если этот элемент равен НУЛЮ, приложение должно отображать значок всякий раз, когда пользователь сворачивает окно приложения. В шаблоне этот элемент инициализируется вызовом функции LoadIcon(). hCursor является дескриптором курсора класса. Если этот элемент равен NULL, приложение должно явно задавать форму курсора всякий раз, когда мышь перемещается в окно приложения . В шаблоне этот элемент инициализируется путем вызова функции LoadCursor(). Первая программа для Windows 473
hbrBackground фон кистью. Этот элемент может быть дескриптор физикоческих кисть используется для рисования фона, или она может быть значение цвета. Если это значение цвета, то это должен быть один из стандартных системных цветов, перечисленных в таблице 19.3. Таблица 19.3 Общие стандартные системные цвета Windows СИМВОЛЬНАЯ КОНСТАНТА ЗНАЧЕНИЕ COLOR_ACTIVEBORDER Цвет границы активного окна COLOR_ACTIVECAPTION Цвет подписи активного окна COLOR_APPWORKSPACE Фон окна MDI-клиентов COLOR_BACKGROUND Цвет рабочего стола COLOR_BTNFACE Цвет лицевой стороны кнопок COLOR_BTNSHADOW Цвет тени для кнопок COLOR_BTNTEXT Цвет текста на кнопках COLOR_CAPTION_TEXT Цвет текста для подписей, полей размера и полей прокрутки COLOR_GRAYTEXT Цвет для выделенного текста COLOR_HIGHLIGHT Цвет выбранного элемента COLOR_HIGHLIGHTTEXT Цвет текста выбранного элемента COLOR_INACTIVEBORDER Цвет границы неактивного окна COLOR_INACTIVECAPTION Цвет заголовка неактивного окна COLOR_MENU Цвет фона меню COLOR_MENUTEXT Цвет текста меню Цветная полоса прокрутки Цвет серой области полосы прокрутки COLOR_WINDOW Цвет фона окна COLOR_WINDOWFRAME Цвет рамки окна COLOR_WINDOWTEXT Цвет текста окна Когда этот элемент равен НУЛЮ, приложение должно раскрасить свой собственный фон, когда когда-либо потребуется раскрасить свою клиентскую область. В шаблоне этот элемент инициализируется вызовом функции GetStockObject(). lpszMenuName является указателем на символьную строку, заканчивающуюся нулем, которая задает имя ресурса меню класса, как оно отображается в файле ресурсов. Если вы используете целое число для идентификации меню, то вы должны использовать макрос MAKEINTRESOURCE. Если этот элемент равен NULL, окна, принадлежащие этому классу, не имеют меню по умолчанию, как в случае с файлом шаблона. lpszClassName является указателем на строку, заканчивающуюся нулем, или это атом. Если этот параметр является атомом, это должен быть глобальный атом, созданный предыдущим вызовом функции GlobalAddAtom(). Atom, 16-разрядное значение, должно быть в слове младшего порядка lpszClassName; слово старшего порядка должно быть равно нулю. Если lpszClassName является строкой, это указывает имя класса window. В Templ01.cpp этому элементу присваивается значение szClassName[] array. В Windows 95 / NT и более поздних версиях hIconSm является дескриптором маленького значка, который ассоциируется с классом window. Это значок, отображаемый в диалоговых окнах со списком имен файлов и проводником Windows. В этом случае приложение Windows 95/98 может использовать предопределенную иконку, используя функцию LoadIcon с теми же параметрами , что и для элемента hIcon. В Windows NT этот элемент не используется и должен быть установлен в значение NULL. Приложения Windows 95 / NT и более поздних версий, для которых маленькому значку присвоено значение NULL
, по-прежнему имеют маленький значок по умолчанию, отображаемый на панели задач. 474 Глава 19 В большинстве случаев лучше создать как большой, так и маленький значок, чем позволять Windows создавать маленький значок из большого растрового изображения. Далее в этой главе мы опишем, как создать оба значка в качестве программного ресурса и как сделать эти ресурсы доступными для приложения. Вопреки тому, что иногда заявлялось, функция LoadIcon() не может использоваться для загрузки как больших, так и маленьких значков с одного и того же ресурса. Например, если повторный источник значка называется IDI_ICON1, и мы действуем следующим образом: wndclass.hicon = LoadIcon (помеха, СОЗДАТЬ ИСХОДНЫЙ код(IDI_ICON1); . . . wndclass.hiconSm = LoadIcon (помеха, СОЗДАТЬ ИСХОДНЫЙ код(IDI_ICON1); в результате из файла ресурсов загружается большой значок, но не маленький. Это происходит, даже если файл ресурсов содержит оба изображения. Вместо этого вы должны использовать функцию LoadImage() следующим образом: wndclass.hIcon = (HICON)Загружаемое изображение(помеха, СОЗДАТЬ ИСХОДНЫЙ КОД(IDI_ICON1), IMAGE_ICON, // Тип 32, 32, // Размер пикселя LR_DEFAULTCOLOR) ; . . wndclass.hIconSm = (HICON)Загружаемое изображение(помеха, СОЗДАТЬ ИСХОДНЫЙ КОД(IDI_ICON1), IMAGE_ICON, // Введите 16, 16, // Размер в пикселях LR_DEFAULTCOLOR) ; Теперь ресурсы больших и маленьких значков загружены правильно и используются по необходимости. Также обратите внимание, что значение, возвращаемое LoadImage(), приведено к типу в HICON. Эта манипуляция стала необходимой, начиная с версии 6 Microsoft Visual C ++ из-за изменений, внесенных в компилятор с целью улучшения совместимости со стандартом ANSI C ++. 19.2.4 Регистрация класса Windows Как только ваш код объявил структуру WNDCLASSEX и инициализировал ее возможности-члены, он определил класс window, который охватывает все атрибуты структуры. Наиболее важными из них являются стиль окна (wndclass.style), указатель на процедуру Windows (wndclass.lpfnWndProc) и имя класса window (wndclass. lpszClassName). Функция RegisterClassEx() используется для уведомления Windows о существовании определенного класса window, как определено в структурной переменной WNDCLASSEX. Оператор address-of используется для ссылки на местоположение конкретной структурной переменной, как в следующем изменении состояния: RegisterClassEx (&wndclass) ; Первая программа для Windows 475 Функция RegisterClassEx() возвращает атом (16-разрядное целое число). Это значение ненулевое, если класс успешно зарегистрирован. Код должен проверять успешную регистрацию, поскольку в противном случае вы не сможете создать Windows. Следующая конструкция гарантирует, что выполнение не продолжится в случае сбоя функции. if(!RegisterClassEx (&wndclass)) return(0); Этот стиль кодирования используется в шаблоне Templ01.cpp.
19.2.5 Создание окна Класс окна - это общая классификация. Другие данные должны быть предоставлены во время фактического создания окон. Функция CreateWindowEx() получает дополнительную информацию в качестве параметров. CreateWindowEx() - это версия функции CreateWindow() для Windows 95. Единственное различие между ними заключается в том, что новая версия поддерживает расширенный стиль окна, передаваемый в качестве первого параметра. Функция CreateWindowEx() очень богата аргументами, многие из которых применимы только к специальным стилям Windows. Например, кнопки, поля со списком, списки , поля редактирования и статические элементы управления могут быть созданы с помощью вызова CreateWindowEx() . На данный момент мы обращаемся только к наиболее важным параметрам функции, которые появляются с опозданием в главном окне программы. В файле Templ01.cpp вызов CreateWindowEx кодируется следующим образом: hwnd = создать windowex ( WS_EX_LEFT, // выровнено по левому краю (по умолчанию) szClassName, // указатель на имя класса "Заголовок окна", // заголовок окна (строка заголовка) WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // начальная позиция x CW_USEDEFAULT, // начальная позиция y CW_USEDEFAULT, // начальный размер x CW_USEDEFAULT, // начальный размер y NULL, // дескриптор родительского окна NULL, // дескриптор меню окна Препятствие, // дескриптор экземпляра программы NULL) ; // параметры создания Первым параметром, передаваемым функции CreateWindowEx(), является расширенный стиль окна, представленный в Win32 API. Тот, который используется в файле Templ01.cpp, WS_EX_LEFT, действует как заполнитель для других, которые вы, возможно, захотите выбрать, поскольку это фактически значение по умолчанию. В таблице 19.4 перечислены некоторые из наиболее распространенных расширенных стилей. Второй параметр, передаваемый вызову функции CreateWindowEx(), является либо указателем на строку с именем типа window, либо строкой, заключенной в двойные кавычки, либо предопределенным именем для класса управления. В файле шаблона szClassName является указателем на строку, определенную в начале WinMain(), с текстом "MainClass". Вы можете отредактировать эту строку в своих собственных приложениях , чтобы имя класса было более значимым. Например, если вы разрабатывали программу-редактор, вы можете переименовать класс приложения в "TextEdClass". Однако это всего лишь имя, используемое Windows для связи окна со своим классом; оно не отображается в виде заголовка и не используется иным образом. 476 Глава 19 Первая программа для Windows 477 Таблица 19.4 Распространенные расширенные стили Windows СИМВОЛИЧЕСКАЯ КОНСТАНТА ЗНАЧЕНИЕ WS_EX_ACCEPTFILES Окно, созданное с использованием этого стиля, допускает перетаскивание файлов. WS_EX_APPWINDOW Окно верхнего уровня принудительно выводится на панель задач приложения, когда окно свернуто. WS_EX_CLIENTEDGE
Окно имеет границу с загнутым краем. WS_EX_CONTEXTHELP Строка заголовка содержит вопросительный знак. Когда пользователь нажимает на метку, курсор меняется на вопросительный знак с указателем. Если пользователь затем нажимает на дочернее окно, он получает сообщение WM_HELP. WS_EX_CONTROLPARENT Позволяет пользователю перемещаться между дочерними окнами window с помощью клавиши TAB. WS_EX_DLGMODALFRAME Окно, имеющее двойную границу. При необходимости окно может быть создано со строкой заголовка, указав стиль WS_CAPTION в параметре dwStyle. WS_EX_LEFT Окно имеет общие свойства "выровнено по левому краю". Это значение по умолчанию. WS_EX_MDICHILD Создает дочернее окно MDI. WS_EX_NOPARENTNOTIFY Указывает, что дочернее окно, созданное с помощью этого стиля, не отправляет сообщение WM_PARENTNOTIFY своему родительскому окну при создании или уничтожении. WS_EX_OVERLAPPEDWINDOW Сочетает стили WS_EX_CLIENTEDGE и WS_EX_WINDOWEDGE. WS_EX_PALETTEWINDOW Сочетает в себе стили WS_EX_WINDOWEDGE, WS_EX_TOOLWINDOW и WS_EX_TOPMOST. WS_EX_RIGHTSCROLLBAR Указывает, что вертикальная полоса прокрутки (если она присутствует) находится справа от клиентской области. Это значение используется по умолчанию. WS_EX_STATICEDGE Создает окно со стилем трехмерной границы, предназначенный для использования для элементов, которые не принимают пользовательский ввод. WS_EX_TOOLWINDOW Создает окно инструментов. Этот тип окон предназначен для использования в качестве плавающей панели инструментов. WS_EX_TOPMOST Создает окно, которое размещается, то есть окно, созданное с использованием этого стиля, должно размещаться над всеми не самыми верхними окнами и должно оставаться над ними, даже когда окно деактивировано. WS_EX_TRANSPARENT Создает windows, то есть окно, созданное с использованием этого стиля, должно быть прозрачным. То есть любые окна, которые находятся под ним, не затемняются им. WS_EX_WINDOWEDGE Задает окно с границей с приподнятым краем. Классы управления также могут использоваться в качестве имени класса окна. Этими классами являются КНОПКА символьных констант, поле со списком, РЕДАКТИРОВАНИЕ, поле списка, MDICLIENT, полоса прокрутки и STATIC. Третьим параметром может быть указатель на строку или строка, заключенная в двойные кавычки, вводимая непосредственно в качестве параметра. В любом случае, эта строка используется в качестве заголовка к окну программы и отображается в строке заголовка программы. Часто этот заголовок совпадает с названием программы. Вам следует отредактировать эту строку в соответствии с вашей собственной программой. Четвертый параметр - это стиль окна. В качестве символьных констант определено более 25 стилей
. Наиболее используемые из них перечислены в таблице 19.5. Таблица 19.5 Стили окон СИМВОЛЬНАЯ КОНСТАНТА ЗНАЧЕНИЕ WS_BORDER Окно, имеющее границу в виде тонкой линии. WS_CAPTION Окно, имеющее строку заголовка (включает в себя стиль WS_BORDER). WS_CHILD Дочернее окно. Этот стиль нельзя использовать со стилем WS_POPUP. WS_CLIPCHILDREN Исключает область, занимаемую дочерними окнами, когда рисование происходит в родительском окне. WS_CLIPSIBLINGS Закрепляет дочерние окна относительно друг друга. Когда конкретное дочернее окно получает сообщение WM_PAINT , этот стиль удаляет все другие перекрывающиеся дочерние окна из области дочернего окна, подлежащего обновлению. Если параметр WS_CLIPSIBLINGS не указан и дочерние окна перекрываются, можно рисовать в клиентской области соседнего дочернего окна. WS_DISABLED Окно изначально отключено. Отключенное окно не может получать входные данные от пользователя. WS_DLGFRAME Окно имеет границу в стиле, обычно используемом для диалоговых окон. В окне нет строки заголовка. WS_HSCROLL Окно с горизонтальной полосой прокрутки. WS_ICONIC Окно изначально свернуто. Такой же, как стиль WS_MINIMIZE. WS_MAXIMIZE Окно изначально развернуто. WS_MAXIMIZEBOX Окно, в котором есть кнопка Развернуть. Не может быть объединен со стилем WS_EX_CONTEXTHELP. WS_MINIMIZE Окно изначально свернуто. То же, что и в стиле WS_ICONIC. WS_MINIMIZEBOX Окно имеет кнопку сворачивания. Не может быть объединено со стилем WS_EX_CONTEXTHELP. WS_OVERLAPPED Перекрывающееся окно. Имеет строку заголовка и границу. (продолжает) 478 Глава 19 Таблица 19.5 Стили окон (продолжение) СИМВОЛИЧЕСКАЯ КОНСТАНТА ЗНАЧЕНИЕ WS_OVERLAPPEDWINDOW Наложенное окно со стилями WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX. Такой же, как стиль WS_TILEDWINDOW. WS_POPUP Всплывающее окно. Не может использоваться с стилем WS_CHILD.
WS_POPUPWINDOW Всплывающее окно со стилями WS_BORDER, WS_POPUP и WS_SYSMENU. Стили WS_CAPTION и WS_POPUPWINDOW должны быть объединены, чтобы сделать Системное меню видимым. WS_SIZEBOX Окно с рамкой размера. То же, что и в стиле WS_THICKFRAME. WS_SYSMENU Окно, в строке заголовка которого есть окно системного меню. Также должен быть указан стиль WS_CAPTION. WS_TILED Перекрывающееся окно. Имеет строку заголовка и границу. Такой же, как стиль WS_OVERLAPPED. WS_TILEDWINDOW Наложенное окно со стилями WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX. То же, что и в стиле WS_OVERLAPPEDWINDOW WS_VISIBLE Окно изначально отображается. WS_VSCROLL Окно с вертикальной полосой прокрутки. Т е с т ы л е д е ф и н е д и н т е т е м п л а т е ф и л е те м п л 0 1 . c c p i s W S _ O V E RЗАКРЫТОЕ ОКНО. Этот стиль создает окно, имеющее стили WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX. Это наиболее распространенный стиль оформления окон. Пятый параметр службы CreateWindowEx() определяет начальное горизонтальное положение окна. Значение CS_USERDEFAULT (0x80000000) определяет использование позиции по умолчанию. Файл шаблона использует ту же символическую константу CS_USERDEFAULT для позиции y и размера Windows x и y. Параметрам девять и десять присвоено значение NULL, поскольку у этого окна нет родительского элемента и нет меню по умолчанию. Одиннадцатый параметр, hInstance, является дескриптором экземпляра, который был передан WinMain() Windows. Последняя запись, называемая параметрами создания, может быть использована для передачи данных в программу . Структура типа CREATESTRUCT используется для хранения параметров инициализации, переданных процедуре Windows приложения. Данные могут включать в себя дескриптор экземпляра, новое меню, размер и расположение окна, стиль, имя окна dow и название класса, а также расширенный стиль. Поскольку параметры создания не передаются , полю присваивается значение NULL. Первая программа для Windows 479 Функция CreateWindowEx() возвращает дескриптор окна типа HWND. Файл шаблона Templ01.cpp сохраняет этот дескриптор в глобальной переменной с именем hwnd. Причина этого в том, что многим функциям Windows API требуется этот дескриптор. Сохраняя его в глобальной переменной, мы делаем его видимым во всем коде. Если CreateWindowsEx() завершается с ошибкой, она возвращает NULL. Код в WinMain() можно проверить на наличие этого условия ошибки с помощью инструкции: if(!hwnd) return(0); Мы не используем этот тест в файле шаблона Templ01.cpp потому что обычно в нем нет необходимости. Если WinMain() завершается с ошибкой, вы можете использовать отладчик для проверки значения hwnd после CreateWindowEx(), чтобы убедиться, что был повторно включен действительный дескриптор. 19.2.6 Отображение окна CreateWindowEx() создает окно внутри, но не отображает его. Чтобы отобразить окно, ваш код должен вызвать две другие функции: ShowWindow() и U p d a t e Wi n d o w ( ) . S h o w Wi n d o w ( ) s e t s h e w i n d o w s s h o w s t a t e a n d UpdateWindow() обновляет клиентскую область окна. В случае
главного окна программы ShowWindow() должна быть вызвана один раз, используя в качестве параметра значение iCmdShow, переданное Windows в WinMain(). В файле шаблона вызов кодируется следующим образом: ShowWindow (hwnd, iCmdShow) ; Первым параметром ShowWindow() является дескриптор окна, возвращаемый CreateWindowEx(). Второй параметр - это параметр режима отображения окна , который определяет, как окно должно быть первоначально отображено. Параметры режима отображения перечислены в таблице 19.1, но при этом первом вызове ShowWindow() вы должны использовать значение, полученное WinMain(). UpdateWindow() фактически инструктирует окно нарисовать само себя , отправляя W M _ PA I N T m e s s a g e t o t h e w i n d o w s p r o c e d u r e . Ч е р р о к е с т и н г о сообщение WM_PAINT описано далее в этой главе. Фактический код в файле template выглядит следующим образом: UpdateWindow (hwnd) ; Если все прошло хорошо, на этом этапе на экране отображается ваша программа. Теперь пришло время внедрить механизмы передачи сообщений, которые лежат в основе событийно-ориентированного программирования. 19.2.7 Цикл обмена сообщениями В среде, управляемой событиями, не может быть никакой гарантии, что сообщения обрабатываются быстрее, чем они исходят. По этой причине Windows поддерживает две очереди сообщений . Первый тип очереди, называемый системной очередью, используется для хранения сообщений , которые исходят от аппаратных устройств, таких как клавиатура и мышь. Кроме того, каждый поток выполнения имеет свою собственную очередь сообщений. Механизм обработки сообщений480 Глава 19 nism можно описать на упрощенном примере: когда происходит событие клавиатуры, программное обеспечение драйвера устройства помещает сообщение в системную очередь. Windows использует информацию о фокусе ввода, чтобы решить, какой поток должен обработать сообщение. Затем она перемещает сообщение из системной очереди в соответствующую очередь потоков. Простой блок кода, называемый циклом сообщений, удаляет сообщение из очереди потоков и отправляет его функции или подпрограмме, которая должна его обработать. Когда принимается специальное сообщение, цикл сообщений завершается, как и поток. Цикл сообщений в Templ01.cpp кодируется следующим образом: while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } возвращает сообщение.wParam ; Оператор while вызывает функцию GetMessage(). Первым параметром для GetMessage() является переменная структурного типа MSG, описанная в Приложении A. Структурная переменная заполняется информацией о сообщении в очереди, если таковое имеется . Если ни одно сообщение не ожидает в очереди, Windows приостанавливает работу приложения и назначает его временной интервал другим потокам выполнения. В среде, управляемой событиями, программы действуют только в ответ на события. Нет события, нет сообщения, нет действия. Второй параметр GetMessage() - это дескриптор окна, для которого нужно повторно выполнить проверку сообщения. Большинство приложений устанавливают для этого параметра значение NULL, что сигнализирует о том, что все сообщения для Windows, принадлежащие приложению, выполняющему вызов, должны быть извлечены. Третий и четвертый параметры GetMessage() - это наименьший и наибольший номера сообщений, которые должны быть извлечены. Потоки, которые извлекают сообщения только в пределах определенного диапазона, могут использовать эти параметры в качестве фильтра. Когда обоим этим параметрам присваивается специальное значение 0 (как в нашем цикле обмена сообщениями), то фильтрация не выполняется и все сообщения передаются приложению. Внутри цикла обмена сообщениями есть две функции. TranslateMessage() - это функция обработки на панели управления, которая преобразует нажатия клавиш в символы. Символы затем помещаются в очередь сообщений. Если сообщение не является нажатием клавиши, которое требует перевода, то никаких специальных действий не предпринимается. Функция DispatchMessage() отправляет сообщение в процедуру Windows, где оно дополнительно обрабатывается и либо
обрабатывается, либо игнорируется. Процедура Windows обсуждается в следующем разделе . GetMessage() возвращает 0 при получении сообщения с пометкой WM_QUIT. Это сигнализирует об окончании цикла передачи сообщений; в этот момент выполнение возвращается из WinMain(), и приложение завершается. 19.3 Оконная процедура На данный момент выполнения программы класс window зарегистрирован, окно создано и отображено, и все сообщения перенаправляются в ваш код. Процедура Windows, иногда называемая оконной функцией, заключается в том, что вы пишете код для обработки сообщений, полученных из цикла обмена сообщениями. Именно в процедуре windows вы реагируете на события, относящиеся к вашей программе. Первая программа для Windows 481 Каждое окно должно иметь оконную процедуру. Хотя обычно используется имя WinProc(), вы можете использовать любое другое имя для процедуры Windows при условии, что оно отображается в заголовке процедуры, прототипе, в соответствующей записи структуры WNDCLASSEX и что оно не конфликтует с другим именем в вашем приложении. Кроме того, программа Windows может иметь более одной программы Windows. Главное окно программы обычно регистрируется в WinMain(), но другие окна могут быть зарегистрированы в другом месте приложения. Здесь опять же каждая процедура Windows соответствует классу window, имеет свою собственную структуру WNDCLASSEX, а также уникальное имя. В шаблоне процедура Windows закодирована следующим образом: |------------------------ Возвращаемый тип, эквивалентный длинному типу | |---------------- То же самое, что и в соглашении о вызовах FAR PASCAL. | | Используется в окнах и диалоговых процедурах. | | |-------- Имя процедуры | | | [ список параметров ... ] ------- -------- ----------------------------------------------LРЕЗУЛЬТАТ ОБРАТНОГО ВЫЗОВА WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { Процедура Windows имеет тип обратного вызова. Символ ОБРАТНОГО ВЫЗОВА был впервые введен в Windows 3.1 и эквивалентен FAR PASCAL, а также WINAPI, поскольку все они в настоящее время соответствуют соглашению о вызовах __stdcall. Хотя в заголовке функции можно заменить __stdcall на ОБРАТНЫЙ вызов, это не рекомендуется, поскольку это может поставить под угрозу переносимость приложения на другие платформы или будущие версии операционной системы. Возвращаемое значение процедуры Windows имеет тип LRESULT, который представляет собой 32-разрядное целое число. Фактическое значение зависит от сообщения, но оно редко используется кодом приложения. Однако есть несколько сообщений, для которых ожидается, что процедура Windows вернет определенное значение. Если вы сомневаетесь, рекомендуется проверить документацию Windows . 19.3.1 Параметры процедуры Windows Четыре параметра процедуры Windows являются первыми четырьмя полями в структуре MSG-сообщения. Структура MSG обсуждалась ранее в этой главе. Поскольку процедура Windows вызывается Windows, параметры предоставляются операционной системой во время вызова следующим образом: • hwnd - это дескриптор окна, принимающего сообщение. Это тот же самый дескриптор, который был повторно изменен с помощью CreateWindow(). • iMsg - это 32-разрядное целое число без знака (UINT), которое идентифицирует каждое конкретное сообщение. Константы для различных сообщений определены в заголовочных файлах Windows. Все они
начинаются с букв WM_, которые обозначают сообщение окна. • wParam и lParam называются параметрами сообщения. Они предоставляют дополнительную информацию о сообщении. Оба значения специфичны для каждого сообщения. 482 Глава 19 Последние два элемента структуры сообщения, которые соответствуют времени публикации сообщения sage и положению курсора, не передаются в процедуру Windows. Однако код приложения может использовать функции GetMessageTime() и GetMessagePos() для извлечения этих значений. 19.3.2 Переменные процедуры Windows Реализация процедуры Windows в Templ01.cpp начинается с объявления скаляра типа HDC и двух структурных переменных типа HWND и MSG соответственно. Переменные следующие: • hdc - это дескриптор контекста устройства. Контекст устройства - это структура данных, поддерживаемая Windows, которая используется при определении графических объектов и их атрибутов, а также связанных с ними графических режимов. Доступ к таким устройствам, как видеодисплей, принтеры и графические устройства, должен осуществляться через дескриптор контекстов их устройств, который получается из Windows. • ps - переменная PAINTSTRUCT. Структура определяется Windows следующим образом: структура определения типа tagPAINTSTRUCT { HDC hdc; // определяет устройство отображения BOOL


// Создать контур для обводки текста // Перевести фоновое смешивание в ПРОЗРАЧНЫЙ режим beginPath (hdc); SetBkMode (hdc, ПРОЗРАЧНЫЙ); // смешивание фона Вывод текста (hdc, 20, 20, "Этот текст ОБВОДИТСЯ", 20); Конечный путь (hdc); // Создайте пользовательское черное перо шириной 2 пикселя ApEn = CreatePen(PS_SOLID, 2, 0); SelectObject(hdc, ApEn); // выберите его в DC StrokePath (hdc); // Проведите пальцем по контуру // Второй путь для обводки и заливки текста beginPath (hdc); SetBkMode(hdc, ПРОЗРАЧНЫЙ); TextOut(hdc, 20, 110, "Обводка и заливка", 18); EndPath(hdc); // Получаем и выбираем стандартное перо и кисть ApEn = GetStockObject(BLACK_PEN); aBrush = GetStockObject(LTGRAY_BRUSH); SelectObject(hdc, ApEn); SelectObject(hdc, Прерывание); Траектория штриха и заполнения (hdc); // Контур обводки и заливки // Очистка и завершение обработки WM_PAINT Удалить object(hFont); EndPaint (hwnd, &ps); Рисунок 20.8 представляет собой снимок экрана программы TEXTDEM3. Рисунок 20.8 Снимок экрана программы TEXTDEM3 524 Глава 20 Глава 21 Программирование клавиатуры и мыши Краткое содержание главы Большинство приложений требуют пользовательского ввода и операций управления. Наиболее распространенными устройствами ввода являются клавиатура и мышь. В этой главе мы обсудим программирование клавиатуры и мыши в Windows. 21.0 Ввод с клавиатуры С первых дней появления компьютеров набор текста на клавиатуре, похожей на пишущую машинку, был эффективным способом взаимодействия с системой.. Хотя типичные программы Windows в значительной степени полагаются на устройство мыши, клавиатура является наиболее распространенным способом ввода текстовых символов в приложение . Механизмы, с помощью которых Windows отслеживает и обрабатывает ввод с клавиатуры, основаны на ее архитектуре, управляемой сообщениями. Когда пользователь нажимает или отпускает клавишу, низкоуровневый драйвер генерирует прерывание, чтобы проинформировать Windows об этом действии. Затем Windows извлекает нажатие клавиши из аппаратного регистра, сохраняет его в системной очереди сообщений и переходит к его проверке. Действие, выполняемое операционной системой , зависит от типа нажатия клавиши и от того, какое приложение в данный момент удерживает клавиатуру на переднем плане, называемом фокусом ввода. Нажатие клавиши отправляется соответствующему отвечающему приложению посредством сообщения для его процедуры Windows. Конкретный способ, с помощью которого Windows обрабатывает нажатия клавиш, определяется ее многозадачным характером. В любой момент времени одновременно может выполняться несколько программ , и любая из этих программ может иметь более одного потока выполнения. Одним из возможных результатов нажатия клавиши (или последовательности нажатий клавиш) является изменение потока, который удерживает фокус ввода, возможно, на другое приложение. Это причина, по которой Windows не может напрямую отправлять ввод с клавиатуры какому-либо определенному потоку. 525
Это цикл обмена сообщениями в функции WinMain() приложения, которая извлекает сообщения с клавиатуры из системной очереди. Фактически, все системные сообщения помещаются в очередь сообщений приложения. Процесс делает следующие предположения: во-первых, очередь потока пуста; во-вторых, поток удерживает фокус ввода; и в-третьих, нажатие клавиши доступно на системном уровне. Другими словами, это приложение запрашивает у Windows нажатия клавиш; Windows не отправляет нежелательные данные о нажатиях клавиш. Обилие функций клавиатуры и сообщений с клавиатуры создает впечатление , что программирование клавиатуры Windows затруднено. Дело в том, что приложениям не нужно обрабатывать все сообщения с клавиатуры, и вряд ли они когда-либо делают это. Два сообщения, WM_CHAR и WM_KEYDOWN, обычно предоставляют код со всеми необходимыми данными, касающимися пользовательского ввода с клавиатуры. Многие нажатия клавиш можно игнорировать, поскольку Windows генерирует другие сообщения, которые легче обрабатывать. Например, приложения обычно могут игнорировать тот факт, что пользователь выбрал пункт меню с помощью нажатия клавиши, поскольку Windows отправляет приложению сообщение, как если бы пункт меню был выбран щелчком мыши. Если код приложения продолжает обработку выбора меню щелчками мыши, то эквивалентное действие на панели управления выполняется автоматически. 21.1 Фокус ввода Приложение, которое удерживает фокус ввода, получает уведомления о нажатиях клавиш пользователя. Пользователь может визуально определить, в каком окне находится фокус ввода, поскольку это то окно, строка заголовка которого подсвечена. Это относится как к родительскому окну, так и к дочерним окнам, таким как окно ввода или диалоговое окно. Приложение может определить, имеет ли окно фокус ввода , вызвав функцию GetFocus(), которая возвращает дескриптор окну с фокусом ввода. Сообщение Windows WM_SETFOCUS отправляется окну в тот момент, когда оно получает фокус ввода, а WM_KILLFOCUS - в тот момент, когда оно его теряет. Приложения могут перехватывать эти сообщения, чтобы заметить любое изменение фокуса ввода. Однако эти сообщения являются простыми уведомлениями; код приложения не может перехватывать эти сообщения, чтобы предотвратить потерю фокуса ввода. Данные клавиатуры доступны для кода, удерживающего фокус ввода, на двух уровнях. Нижний уровень, обычно называемый данными нажатия клавиши, содержит необработанную информацию о нажатой клавише . Данные о нажатиях клавиш позволяют коду определить, было ли сгенерировано сообщение о нажатии клавиши пользователем нажатием клавиши или ее отпусканием, и было ли нажатие клавиши результатом обычного действия по нажатию и отпусканию или от удерживания клавиши нажатой (так называемое типематическое действие). Данные клавиатуры более высокого уровня относятся к коду символа, связанному с клавишей. Приложение может перехватывать сообщения о нажатиях клавиш низкого уровня или на уровне символов, генерируемые Windows. 526 Глава 21 21.1.1 Обработка нажатий клавиш Четыре сообщения Windows информируют код приложения о данных нажатия клавиши: WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP и WM_SYSKEYUP. Сообщения типа keydown генерируются при нажатии клавиши, иногда называемой действием make. Сообщения типа keyup генерируются при отпускании клавиши, что называется действием break. Приложения обычно игнорируют сообщения типа keyup. Сообщения "системного типа", WM_SYSKEYDOWN и WM_SYSKEYUP, относятся к системным ключам. Системное нажатие клавиши - это нажатие, генерируемое при удерживании клавиши Alt нажатой. Когда появляется любое из этих четырех сообщений, Windows помещает данные о нажатии клавиши в параметры lParam и wParam, переданные оконной процедуре. lParam содержит битовую информацию о нажатии клавиши, как показано в таблице 21.1. Таблица 21.1 Разрядность и битовые поля в lParam сообщения о нажатии клавиши БИТЫ
ЗНАЧЕНИЕ 0-15 Поле количества повторений. Значение представляет собой количество повторений нажатия клавиши в результате того, что пользователь удерживает клавишу нажатой (типографское действие). 16-23 Код сканирования OEM. Значение зависит от производителя оригинального оборудования. 24 Расширенный ключ. Бит устанавливается, когда нажатая клавиша является одной, дублируемой на улучшенных 101- и 102-клавишных клавиатурах IBM, таких как правые клавиши ALT и CTRL , клавиши / и Enter на цифровой клавиатуре или клавиши Insert, Delete, Home, PageUp, PageDown и End. 25-28 Сдержанный. 29 Контекстный код. Бит устанавливается, если клавиша Alt нажата во время нажатия клавиши. Бит понятен, если сообщение WM_SYSKEYDOWN отправлено в активное окно, потому что ни одно окно не имеет фокуса клавиатуры. 30 Предыдущее состояние клавиши. Клавиша устанавливается, если клавиша нажата перед отправкой сообщения. Бит чист, если клавиша поднята. Эта клавиша позволяет коду определить, было ли нажатие клавиши результатом действия make или break. 31 Переходное состояние. Всегда 0 для сообщения WM_SYSKEYDOWN. Параметр wParam содержит код виртуального ключа, который является аппаратно-независимым значением, идентифицирующим каждый ключ. Windows использует коды виртуального ключа вместо кода сканирования, зависящего от vice. Обычно коды виртуальных ключей обрабатываются, когда приложению необходимо распознать ключи, с которыми не связано значение ASCII, такие как управляющие клавиши. В таблице 21.2 на следующей странице перечислены некоторые из наиболее используемых кодов виртуальных ключей. Обратите внимание, что первоначально "w" в wParam означало "word", поскольку в 16-разрядных Windows wParam был значением размера слова. Win32 API расширил wParam с 16 до 32 бит. Однако в случае символьных кодов виртуального ключа wParam определяется как тип int. Код может вводить тип wParam следующим образом: Программирование клавиатуры и мыши 527 Таблица 21.2 Коды виртуальных ключей СИМВОЛИЧЕСКОЕ ИМЯ ШЕСТНАДЦАТЕРИЧНОЕ ЗНАЧЕНИЕ Клавиша VK_CANCEL 0x01 Ctrl + Break VK_BACK 0x08 Обратный пробел VK_TAB 0x09 Вкладка VK_RETURN 0x0D Введите VK_SHIFT 0x10 Сдвиг VK_CONTROL 0x11 Ctrl
VK_MENU 0x12 Alt VK_PAUSE 0x13 Пауза VK_CAPITAL 0x14 Caps Lock VK_ESCAPE 0x1B Esc VK_SPACE 0x20 Пробел VK_PRIOR 0x21 Открыть страницу VK_NEXT 0x22 Страница вниз VK_END 0x23 Конец VK_HOME 0x24 Главная VK_LEFT 0x25 Стрелка влево VK_UP 0x26 Стрелка вверх VK_RIGHT 0x27 Стрелка вправо VK_DOWN 0x28 Стрелка вниз VK_SNAPSHOT 0x2C Экран печати VK_INSERT 0x2D Вставить VK_DELETE 0x2E Удалить VK_MULTIPLY 0x6A Цифровая клавиатура * VK_ADD 0x6B Цифровая клавиатура + VK_SUBTRACT 0x6D Цифровая клавиатура VK_DIVIDE 0x6F Цифровая клавиатура /
VK_F1..VK_F12 0x70..0x7B F1 .. F12 ввод aKeystroke; символ Символ; . . aKeystroke = (int) wParam; aCharacter = (char) wParam; Симплекейстрокепрокессингканбеимплементедбуинтеркептинг WM_KEYDOWN. Иногда приложению необходимо знать, когда генерируется сообщение на системном уровне . В этом случае код может перехватить WM_SYSKEYDOWN. Первая операция, выполняемая в обычном WM_KEYDOWN или WM_SYSKEYDOWN 528 Глава 21 обработчик предназначен для хранения в локальных переменных lParam, wParam или обоих. В случае код wParam может при необходимости преобразовать 32-разрядное значение в тип int или char (см. предыдущее техническое примечание). Обработка нажатия клавиши обычно состоит из выполнения побитовых операций в order для выделения требуемых битов или битовых полей. Например, чтобы определить, установлен ли флаг extended key, код может логически И с маской, в которой установлен бит 24, а затем протестировать ненулевой результат, как в следующем фрагменте кода: длинный без знака код ключа; . . WM_KEYDOWN: код ключа = lParam; // сохранить lParam если(код ключа и 0x01000000) { // тестовый бит 24 // УТВЕРЖДЕНИЕ: // нажатая клавиша является расширенной Обработка кода виртуального ключа, который передается вашей процедуре перехвата в lParam, состоит из сравнения его значения с ключом или ключами, которые вы хотите обнаружить. Например, чтобы узнать, была ли нажата клавиша Backspace, вы можете действовать, как в следующем фрагменте кода: int virtkey; . . WM_KEYDOWN: виртуальный ключ = (int) lParam; // приведите и сохраните lParam если(virtkey == VK_BACK) { // проверка на пробел // УТВЕРЖДЕНИЕ: // Нажата клавиша Backspace 21.1.2 Определение состояния ключа Приложение может определить состояние любого виртуального ключа с помощью сервиса GetKeyState(). Общая форма функции выглядит следующим образом: КОРОТКИЙ GetKeyState(nVirtKey); GetKeyState() возвращает КОРОТКОЕ целое число с установленным битом старшего порядка, если клавиша нажата, и битом младшего порядка, если она переключена. Переключающие клавиши - это те, которые имеют на клавиатуре индикатор, указывающий на их состояние: блокировка цифр, блокировка заглавными буквами и блокировка прокрутки. Светодиод для соответствующей клавиши загорается, когда она включена, и не загорается в противном случае. Некоторые константы виртуального ключа могут быть использованы в качестве параметра nVirtKey GetKeyState(). В таблице 21.3 на следующей странице приведен список виртуальных ключей. Обратите внимание, что при тестировании условия набора старших разрядов, возвращаемого GetKeyState() у вас может возникнуть соблазн использовать побитовую И двоичную маску следующим образом:
if(0x8000 & (GetKeyState(VK_SHIFT))) { Программирование клавиатуры и мыши 529 Таблица 21.3 Виртуальные ключи, используемые в GetKeyState() ЗАРАНЕЕ ОПРЕДЕЛЕННЫЙ СИМВОЛ Клавиша ВОЗВРАЩАЕТ VK_SHIFT Сдвиг Состояние клавиш сдвига влево или вправо VK_CONTROL Ctrl Состояние левой или правой клавиш Ctrl VK_MENU Alt Состояние левой или правой клавиш Alt VK_LSHIFT Shift Состояние левой клавиши Shift VK_RSHIFT Shift Состояние правой клавиши Shift VK_LCONTROL Ctrl Состояние левой клавиши Ctrl VK_RCONTROL Ctrl Состояние правой клавиши Ctrl VK_LMENU Alt Состояние левой клавиши Alt VK_RMENU Alt Состояние правой клавиши Alt Следующая инструкция является проверкой для нажатой левой клавиши Shift. if(GetKeyState(VK_LSHIFT) < 0) { // УТВЕРЖДАТЬ: // Нажата левая клавиша shift Хотя во многих случаях такие операции приводят к ожидаемым результатам, их успех зависит от размера типа данных, что ставит под угрозу переносимость. Другими словами, если GetKeyState() возвращает 16-разрядное целое число, то маска 0x8000 эффективно проверяет бит старшего порядка. Однако, если возвращаемое значение хранится в 32 битах, то маской должно быть значение 0x80000000. Поскольку любое целое число со знаком и набором старших разрядов представляет собой отрицательное число, можно проверить условие разрядности следующим образом: if(GetKeyState(VK_SHIFT) < 0) { Этот тест не зависит от размера бита операнда. 21.1.3 Обработка символьного кода Приложения часто обрабатывают ввод с клавиатуры в виде символьных кодов. Можно получить код символа из кода виртуального ключа, поскольку он закодирован в wParam сообщений WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP и WM_SYSKEYUP . Коды буквенно-цифровых клавиш не перечислены в таблице 21.1, однако для каждой из них также имеется код виртуальной клавиши. Коды виртуальных клавиш для цифровых клавиш от 0 до 9 - это VK_0 - VK_9, а коды буквенных символов от A до Z - от VK_A до VK_Z. Этот тип обработки не лишен осложнений. Например, код виртуальной клавиши для буквенных символов не указывает, является ли символ заглавным или строчным. Следовательно, приложению пришлось бы вызвать GetKeyState() , чтобы определить, была ли нажата клавиша <Shift> или клавиша Caps Lock переключена
при нажатии символьной клавиши. Кроме того, коды виртуальных клавиш для некоторых символьных клавиш, таких как ;, =, +, <, не определены в заголовке Windows 530 Глава 21 Файлы. Приложения должны использовать числовые значения, присвоенные этим ключам, или определять свои собственные символьные константы. К счастью, обработка символьного кода в Windows намного проще. Функция TranslateMessage() преобразует код виртуального ключа для каждого символа в его эквивалент ANSI (или Unicode) и помещает его в очередь сообщений потока. TranslateMessage() обычно включается в цикл обмена сообщениями программы. После TranslateMessage() сообщение извлекается из очереди, обычно с помощью GetMessage() или PeekMessage(). Конечным результатом является то, что приложение может перехватывать WM_CHAR, WM_DEADCHAR, WM_SYSCHAR и WM_SYSDEADCHAR для того, чтобы создать сообщение WM_KEYDOWN, содержащее о б т а я н т е а Н С и к а р а к т е р к о д е с т а т о р р е с п о н д о т е в и р т у л - к е й, для того, чтобы создать сообщение WM_KEYDOWN.,,, Символьные сообщения мертвого типа относятся к диакритическим символам, используемым в некоторых клавиатурах на иностранных языках. Это знаки, добавленные к символам, чтобы отличать их от других, таких как острое ударение (á) или окружность (â). При обработке на английском языке WM_DEADCHAR и WM_SYSDEADCHAR обычно имеют значения ig. Сообщение WM_SYSCHAR соответствует виртуальному ключу, который является результатом WM_SYSKEYDOWN. WM_SYSCHAR публикуется при нажатии символьной клавиши при удерживании нажатой клавиши Alt. Поскольку Windows также отправляет сообщение, соответствующее спондстоамоусекликонтческийстемитем,асполикатионсофтенигноре WM_SYSCHAR. Это оставляет нам WM_CHAR для обработки символов общего назначения. Когда сообщение WM_CHAR отправляется вашей процедуре Windows, параметр lParam тот же , что и для WM_KEYDOWN. Однако wParam содержит код ANSI для символа вместо кода виртуального ключа. Этот стандарт ANSI код, который приблизительно соответствуетляет получить ASCII-код, можно сразу обрабатываются и отображаются без дополнительных манипуляций. Обработка заключается в следующем: символ ачар; // хранилище для символа . . регистр WM_CHAR: aChar = (char) wParam; // УТВЕРЖДАТЬ: // aChar содержит код символа ANSI 21.1.4 Демонстрационная программа клавиатуры Программа KBR_DEMO.CCP, расположенная в папке "Демонстрация клавиатуры" в онлайновом программном пакете книги, представляет собой демонстрацию процедур обработки клавиатуры, описанных ранее. Программа использует контекст частного устройства; поэтому шрифт выбирается один раз, во время обработки WM_CREATE. KBR_DEMO использует шрифт TrueType, похожий на пишущую машинку, с именем Courier. Courier - это моноширинный шрифт (все символы имеют одинаковую ширину). Это делает возможным использование стандартных символьных обозначений для создания графика растровых изображений. Рисунок 21.1 на следующей странице представляет собой снимок экрана программы KBD_DEMO. Программирование клавиатуры и мыши 531 Рисунок 21.1 Экран программы KBR_DEMO На рисунке 21.1 показан случай, в котором пользователь ввел клавишу Alt. Обратите внимание, что значение wParam 00010010 эквивалентно 0x12, которое является кодом виртуальной клавиши для клавиши Alt (см. Таблицу 21.1). Критическая обработка в программе KBD_DEMO заключается в следующем: LРЕЗУЛЬТАТ ОБРАТНОГО ВЫЗОВА WndProc (HWND hwnd, UINT iMsg, WPARAM wParam,
LPARAM lParam) { статический int cxChar, cyChar ; // Символьные размеры статический int cxClient, cyClient; // Параметры клиентской области статический HDC hdc ; // дескриптор для частного DC длинный без знака код ключа; // хранилище для нажатий клавиш длительное время без знака ключевая маска; // битовая маска unsigned int виртуальный ключ; // виртуальный ключ int i, j; // счетчики символ aChar; // код символа // Структуры PAINTSTRUCT ps; TEXTMETRIC tm; RECT Текстовая правка; // RECT-type HFONT hFont; . . . регистр WM_PAINT : // Обработка состоит из отображения текстовых сообщений BeginPaint (hwnd, &ps) ; // Инициализировать прямоугольную структуру SetRect (&textRect,



Что диалоговые окна делают для программиста, так это подготавливают ряд функций, которые часто необходимы. Кроме того, диалоговые окна выполняют за вас большую часть операций по обработке и ведению домашнего хозяйства. Они управляют фокусировкой клавиатуры, передавая ввод с клавиатуры от одного элемента управления к другому, они отслеживают движения мыши и предоставляют специальную процедуру для отслеживания действий с элементами управления, содержащимися в диалоговом окне. При использовании в сочетании с редактором диалоговых окон в Developer Studio диалоговые окна легко создавать и реализовывать в коде. Windows 3.1 представила расширение концепции диалоговых окон, обычно называемых общими диалоговыми окнами. Общие диалоговые окна представляют собой набор предварительно упакованных служб для операций, которые обычно требуются во многих приложениях. К ним относятся открытие и сохранение файлов, выбор шрифта, выбор или изменение цветовых атрибутов, поиск и замена текстовых строк и управление принтером. Общие диалоговые окна обсуждаются далее в этом разделе. 22.4.1 Модальные и немодальные Существует два основных типа диалоговых окон: те, которые приостанавливают работу приложения до тех пор, пока пользователь не начнет взаимодействовать с диалоговым окном, и те, которые этого не делают. Первый тип, который является наиболее распространенным, называется модальными диалоговыми окнами. Второй тип, который часто можно увидеть на плавающих панелях инструментов, называется немодальными диалоговыми окнами. Модальные диалоговые окна не препятствуют пользователю переключаться на другое приложение. Хотя при возврате к исходному потоку именно модальное диалоговое окно сохраняет передний план. Интерфейс Windows руководящие принципы для программ-депризнак (см. Библиографию) рекомендует модальные диалоговые окна должны иметь кнопку OK, чтобы принять и обработать входной сигнал, и "отмена", чтобы прервать выполнение и откажитесь от пользователей асской диалоговом окне. 22.4.2 Окно сообщения Самое простое из всех диалоговых окон используется для отображения на экране сообщения, о котором пользователь узнает, прочитав его нажатием кнопки. Специальная функция в Windows API позволяет создавать окна сообщений напрямую, без необходимости использовать редактор диалоговых окон или манипулировать программным ресурсом. Окно сообщения содержит заголовок, сообщение, любой из нескольких предварительно выделенных значков и одну или несколько кнопок. Общая форма вызова функции выглядит следующим образом: int MessageBox(hwnd, lpText, lpCaption, uType); где hwnd - это дескриптор окна, которому принадлежит окно сообщения, lpText - это указатель на отображаемое текстовое сообщение (или саму строку сообщения), lpCaption - это указатель на заголовок (или саму строку заголовка), а uType - это один из нескольких битовых флагов, которые управляют поведением окна сообщения. В таблице 22.6 перечислены наиболее полезные битовые флаги, используемые в функции MessageBox(). Таблица 22.6 Часто используемые битовые флаги окна сообщения СИМВОЛИЧЕСКАЯ КОНСТАНТА ЗНАЧЕНИЕ MB_ABORTRETRYIGNORE Содержит три кнопки: прервать, повторить попытку и игнорировать. MB_OK Содержит одну нажимную кнопку: OK. Это значение по умолчанию. MB_OKCANCEL Содержит две кнопки: OK и Отмена. MB_RETRYCANCEL В окне сообщения есть две кнопки: Повторить попытку и Отменить. MB_YESNO Содержит две кнопки: Yes и No. MB_YESNOCANCEL Содержит три кнопки: Да, Нет и Отмена. (продолжает) 574 Глава 22 Таблица 22.6 Часто используемые битовые флаги окна сообщения (продолжение) СИМВОЛИЧЕСКАЯ КОНСТАНТА ЗНАЧЕНИЕ
Флаги значков: MB_ICONEXCLAMATION Значок с восклицательным знаком. MB_ICONWARNING Значок восклицательного знака. MB_ICONINFORMATION Значок вопросительного знака. MB_ICONASTERISK Значок строчной буквы i в круге. MB_ICONQUESTION Значок вопросительного знака. MB_ICONSTOP Значок знака остановки. MB_ICONERROR Значок руки. MB_ICONHAND Значок руки. Флаги кнопок по умолчанию: MB_DEFBUTTON1 Первая кнопка - это кнопка по умолчанию. MB_DEFBUTTON2 Вторая кнопка является кнопкой по умолчанию. MB_DEFBUTTON3 Третья кнопка является кнопкой по умолчанию. MB_DEFBUTTON4 Четвертая кнопка является кнопкой по умолчанию. Флаги модальности: MB_APPLMODAL Пользователь должен ответить на окно сообщения, прежде чем продолжить работу в окне. Однако пользователь может перейти к окну другого приложения и работать в этих окнах. MB_SYSTEMMODAL То же, что и MB_APPLMODAL, за исключением того, что окно сообщения имеет стиль WS_EX_TOPMOST. Используйте системные окна сообщений для уведомления пользователя о серьезных ошибках, которые требуют немедленного внимания. MB_TASKMODAL То же, что и MB_APPLMODAL, за исключением того, что все окна верхнего уровня, принадлежащие текущей задаче, отключаются, если параметр hwnd равен NULL. Другие флаги: MB_HELP Добавляет кнопку справки в окно сообщения. Нажатие кнопки Справка или нажатие F1 генерирует событие Справки. MB_RIGHT Текст выровнен по правому краю. MB_SETFOREGROUND Окно сообщения становится окном переднего плана. Внутренне Windows вызывает функцию SetForegroundWindow для окна сообщения. MB_TOPMOST Окно сообщения создается в стиле окна WS_EX_TOPMOST. Например, следующая инструкция создает окно сообщения с надписью "Действие меню" с текстовой строкой "Запрошено закрытие файла", которая содержит значок восклицательного знака и кнопку с надписью OK: ...........: MessageBox (hwnd, "Требуется закрыть файл", "Действие меню", MB_ICONEXCLAMATION | MB_OK); На рисунке 22.8 показано результирующее окно сообщения. Рисунок 22.8 Простое окно сообщения Элементы графического пользовательского интерфейса 575 22.4.3 Создание модального диалогового окна Developer Studio предоставляет редактор диалоговых окон, который является инструментом для создания диалоговых окон. Как только диалоговое окно создано, оно становится другим программным ресурсом , на который можно ссылаться в коде. Диалоговое окно редактор может использоваться для создания симдям сообщение коробок; впрочем, в этом случае проще использовать функции MessageBox() Функции, описанной в предыдущем разделе. Диалоговые окна полезны, когда они используются
для получения пользовательских данных. Уникальной особенностью диалоговых окон является то, что они содержат свою собственную обработку. В смысле процедура диалогового окна похожа на вашу оконную процедуру. Вы создаете модальное диалоговое окно с помощью функции DialogBox() со следующей стандартной формой: диалоговое окно int (hInstance, lpTemplate, hwndParent, lpDiaProc); где hInstance - это дескриптор экземпляра программы, который содержит диалоговое окно, lpTemplate идентифицирует шаблон или ресурс диалогового окна, hwndParent - это дескриптор окна владельца, а lpDiaProc - это имя процедуры диалогового окна. Именно эта процедура получает управление при создании диалогового окна. Следующий фрагмент кода показывает создание диалогового окна во время перехвата команды меню с идентификатором ID_DIALOG_ABOUT: Идентификатор DIALOG_ABOUT: Диалоговое окно (pInstance, СОЗДАТЬ ИСХОДНЫЙ код (IDD_DIALOG1), hwnd, (DLGPROC) О DLGPROC); В этом случае ресурс диалогового окна называется IDD_DIALOG1, а процедура диалогового окна, которая получает управление, называется AboutDlgProc(). Общая форма процедуры диалогового окна выглядит следующим образом: BOOL DialogProc (hwndDlg, uMsg, wParam, lParam); где DialogProc - это имя процедуры, определенной в поле lpDiaProc функции DialogBox(). Первый параметр, передаваемый диалоговой процедуре (hwndDlg), является дескриптором диалогового окна. Второй параметр - это сообщение Windows. Значения wParam и lParam содержат специфичную для сообщения информацию, как и в случае с оконной процедурой. Как только диалоговое окно создано и перед его отображением Windows отправляет сообщение WM_INITDIALOG в процедуру диалогового окна. Как правило, процедура диалогового окна перехватывает сообщение для инициализации элементов управления и выполнения других служебных функций. В WM_INITDIALOG параметр wParam содержит дескриптор элемента управления с фокусом, который является первым видимым и не отключенным элементом управления в поле. Приложение возвращает значение TRUE, чтобы принять этот фокус по умолчанию. В качестве альтернативы приложение может установить фокус на другой элемент управления, и в этом случае оно возвращает значение FALSE. Процедура диалогового окна получает сообщения для элементов управления в диалоговом окне. Эти сообщения могут быть перехвачены тем же способом, что и сообщения, отправленные в win576 Глава 22 процедура dow. Следующий фрагмент кода представляет собой процедуру диалогового окна для диалогового окна, которое содержит одну кнопку: ОБРАТНЫЙ ВЫЗОВ BOOL AboutDlgProc (HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) { переключить (iMsg) { регистр WM_INITDIALOG : возвращает TRUE ; // Диалоговое окно управляет перехватом сообщений в случае WM_COMMAND : switch (LOWORD (wParam)) { // Получить идентификатор элемента управления идентификатор регистра : EndDialog (hDlg, 0) ; возвращает TRUE ; } разрыв ; } возвращает FALSE ; } Обратите внимание, что, в отличие от оконной функции, AboutDlgProc() не возвращает управление с помощью оконной процедуры по умолчанию. Как правило, процедура диалогового окна возвращает значение FALSE, чтобы указать, что обработка по умолчанию должна быть предоставлена Windows, и значение TRUE, когда дальнейшая обработка не требуется. Исключением является сообщение WM_INITDIALOG, в котором значение повторного поворота относится к принятию или отклонению фокуса по умолчанию, как обсуждалось ранее ously. Обратите внимание, что диалоговые процедуры, как и все оконные процедуры, должны иметь тип ОБРАТНОГО ВЫЗОВА. Неспособность объявить оконную процедуру или процедуру обратного вызова с этим типом может быть источником непредсказуемых ошибок, таких как ошибка общей защиты. Вы можете создать диалоговое окно с помощью следующих шагов:
1. Выберите Ресурс в меню Вставка студии разработчика. Выберите тип ресурса диалога в диалоговом окне и нажмите Создать. 2. Редактор диалогового окна запускается путем отображения пустой формы и плавающей панели инструментов, содержащейэлементы управления, которые можно вставить в диалоговое окно. Если панель инструментов не видна, вы можете отобразить ее на экране редактора, открыв меню Сервис, затем выбрав пункт Настроить и установив флажок Элементы управления на вкладке Панели инструментов. Элементы управления включают все те, которые уже были описаны, и некоторые другие. Чтобы добавить элемент управления в диалоговое окно, вы перетаскиваете его на форму, а затем с помощью маркеров изменяете его размер. Двойной щелчок по форме или по одному из элементов управления открывает диалоговое окно свойств, которое позволяет определить атрибуты этого конкретного элемента. На рисунке 22.9 на следующей странице показан редактор диалоговых окон с окнами свойств формы и панели инструментов Controls. 3. Как только вы закончите создание диалогового окна, нажмите кнопку Закрыть окна редактора меню . Если в приложении уже есть файл сценария, диалоговое окно будет добавлено к нему. Если нет, Деveloper Studio предложит вам сохранить новый файл сценария. 4. Пропустите этот шаг, если файл сценария уже был вставлен в проект. Если нет, откройте Proменю ject, выберите Добавить в проект, а затем выберите Files. В поле Вставить файлы в журнал проекта выберите файл сценария и затем нажмите OK. Файл сценария теперь отображается в рабочей области проекта Developer Studio. Элементы графического пользовательского интерфейса 577 Рисунок 22.9 Редактор диалогов студии разработчика 5. Выберите кнопку Просмотр ресурсов на панели рабочей области проекта и нажмите + на перезапуск сценария исходные тексты. Нажмите + на диалоговое окно. Обратите внимание на имя идентификатора ресурса диалогового окна (обычно IDD_DIALOG1), если это первое созданное диалоговое окно. 6. Теперь ваш код должен создать диалоговое окно, обычно путем перехвата соответствующей команды меню и вызова DialogBox(). Кроме того, процедура диалогового окна должна перехватывать сообщение WM_INITDIALOG и предоставлять обработчики для элементов управления, содержащихся в окне, как описано ранее. 22.4.4 Общие диалоговые окна Windows 3.1 представила общие диалоговые окна как набор предварительно упакованных служб для выполнения рутинных операций, необходимых во многих приложениях. Идея, лежащая в их основе, заключается в стандартизации часто используемых функций ввода, чтобы они выглядели одинаково в различных функциях программы и даже в разных приложениях. Например, диалоговое окно common, используемое для выбора имени файла и просмотра системы дискового хранилища , является тем же самым, если вы открываете или сохраняете файл. Более того, два приложения, которые работают с файлами, могут использовать одно и то же общее диалоговое окно, предоставляя пользователю знакомый интерфейс. С помощью обычных диалоговых окон можно выполнять следующие операции: открытие и сохранение файлов, выбор шрифтов, выбор или изменение цветовых атрибутов , поиск и замена текстовых строк и управление принтером. Обычные диалоговые окна dialog имеют модальное поведение, то есть программа приостанавливается до тех пор, пока пользователь не закроет диалоговое окно. Обычные диалоговые окна обрабатываются внутри Windows; следовательно, они не имеют процедуры создания диалогового окна. 578 Глава 22 Идентификация пункта меню или другого ресурса, который активирует общее диалоговое окно, обычно служит для перехвата сообщения. Обработка выполняется непосредственно в процедуре перехвата. Каждое общее диалоговое окно связано со структурой, которая используется для передачи в него информации и получения результатов действий пользователя. Программы , использующие общие диалоговые окна, должны включать заголовочный файл commdlg.h. Например, пункт меню ID_DIALOG_COLORSELECTOR может перехватить действие пользователя при обработке сообщения WM_COMMAND и затем перейти к заполнению переменной
структурного типа CHOOSECOLOR (см. Приложение F), как в следующем фрагменте кода : LРЕЗУЛЬТАТ ОБРАТНОГО ВЫЗОВА WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; ТЕКСТОМЕТРИЧЕСКИЙ tm ; HBRUSH hBrush; // Ручка для кисти // Переменные для общего диалога цвета и шрифта статический ВЫБОР цвета cc; // Структура статическая ссылка на цвет Пользовательские цвета[16] ; // Массив для пользовательских // цвета int i; // счетчик для пользовательского цветного дисплея переключатель (iMsg) { ... случай WM_COMMAND: переключатель (НИЖНИЙ УРОВЕНЬ (wParam)) { // Общий диалог выбора цвета идентификатор РЕГИСТРА_DIALOG_COLORSELECTOR: cc.lStructSize = размер (ВЫБЕРИТЕ ЦВЕТ) ; cc.hwndOwner = hwnd ; cc.Препятствие = NULL ; результат cc.rgbResult = RGB (0x80, 0x80, 0x80) ; cc.lpCustColors = Пользовательские цвета ; cc.Флаги = CC_RGBINIT | CC_FULLOPEN ; cc.lCustData = 0L ; cc.lpfnHook = NULL ; cc.lpTemplateName = NULL ; ... Как только структурная переменная заполнена необходимыми данными, приложение может вызвать функцию ChooseColor() для отображения общего диалогового окна. Для функции ChooseColor() требуется единственный параметр: адрес ранее упомянутой структуры. Большинство элементов структуры очевидны. Элемент lpCustColors представляет собой массив из 16 значений типа COLORREF, который содержит значения RGB для пользовательских цветов в окне журнала. Элементы Flags - это битовые флаги, которые определяют работу диалогового окна. В предыдущем примере мы установили бит CC_RGBINIT, чтобы элемент rgbREsult сохранял начальный выбор цвета. Значения 0x80 для каждого из красных, зеленых и синих компонентов создают средний серый цвет. Последние три элемента структуры используются для настройки диалогового окна. Константа CC_FULLOPEN приводит к тому, что окно dialog открывается в режиме полного отображения, то есть с элементами управления, необходимыми пользователю для создания пользовательских цветов. Функция ChooseColor() возвращает значение TRUE, если пользователь нажимает кнопку OK в диалоговом окне. Таким образом, кодирование продолжается следующим образом: Элементы графического пользовательского интерфейса 579 if (ChooseColor (&cc) == TRUE) { // УТВЕРЖДАТЬ: // элементы структуры имеют данные о цвете, выбранные пользователем // Очистите окно клиента hdc = GetDC(hwnd); InvalidateRect (hwnd, NULL, TRUE) ; UpdateWindow (hwnd) ; Выберите ЦВЕТОВУЮ структуру: rbgResult содержит сплошную цветовую рамку, а в массиве custColor с возможностью изменения custColor содержится 16 пользовательских цветов. Теперь код создает сплошную кисть,
используя цвет, сохраненный в элементе rgbResult, и отображает прямоугольник, заполненный этим цветом. Затем цикл отображает первые восемь из 16 пользовательских цветов: hBrush = CreateSolidBrush(cc.rgbResult); // Выберите кисть в DC SelectObject (hdc, hBrush) ; // Нарисуйте прямоугольник с помощью кисти Rectangle (hdc, 20, 20, 100, 100) ; // Отобразить первые восемь пользовательских цветов, используя // триплеты цветов, сохраненные в массиве custColors для (i = 0; i < 8; i++) { hBrush = CreateSolidBrush(пользовательские цвета[i]); Выберите объект (hdc, hBrush) ; Прямоугольник (hdc, 20+ (20 * i), 120, 40+ (20 * i), 140) ; // Очистить и выйти Удалить объект (SelectObject (hdc, hBrush)) ; ReleaseDC (hwnd, hdc); } возвращает 0 ; ... На рисунке 22.10 показано диалоговое окно цвета, отображаемое этим кодом. Рисунок 22.10 Общее диалоговое окно выбора цвета 580 Глава 22 22.4.5 Демонстрационная программа диалогового окна Программа с именем DIA_DEMO, содержащаяся в папке проекта Dialog Box Demo в онлайновом программном пакете книги, представляет собой тривиальную демонстрацию нескольких диалоговых окон. Диалоговое меню содержит команды для создания немодального диалогового окна, трех различных модальных диалоговых окон (одно из них с растровым изображением), а также для общих диалоговых окон цвета и шрифта . Код демонстрирует, как информация, полученная с помощью модальных и общих диалоговых окон, передается приложению. 22.5 Общие средства управления В Windows 95 представлен новый набор элементов управления, дополняющих те, которые существовали ранее. Они также доступны в Windows NT версии 3.51 и более поздних версиях. Эти элементы управления, иногда называемые новыми общими элементами управления, позволяют реализовать строки состояния, панели инструментов, трекбары, индикаторы выполнения, элементы управления анимацией, списки изображений, элементы управления видом списка , элементы управления древовидным представлением, таблицы свойств, вкладки, мастера и расширенные возможности редактирования . Из этого списка видно, что их обсуждению можно было бы посвятить целый том. Таблица 22.7 представляет собой список общих элементов управления Windows, впервые реализованных в Windows 95. Таблица 22.7 Оригинальный набор общих элементов управления УПРАВЛЕНИЕ Описание Элементы управления фреймовым окном: панель инструментов Отображает окно с кнопками для генерации команд. Всплывающая подсказка Небольшое всплывающее окно, описывающее назначение кнопки на панели инструментов или другого инструмента. строка состояния Отображает информацию о состоянии в нижней строке экрана. Элементы управления типа проводника: просмотр списка Отображает список текста со значками. древовидное представление Отображает иерархический список элементов. Разные элементы управления: анимация Отображает последовательные кадры видеоклипа AVI. заголовок Отображается над столбцом текста. Управляет шириной отображаемого текста.
горячая клавиша Позволяет пользователю быстро выполнить действие. список изображений Коллекция изображений, используемая для управления большими наборами значков или растровых изображений. На самом деле это не элемент управления, но поддерживает списки, используемые другими элементами управления. индикатор выполнения Указывает на выполнение длительной операции. расширенное редактирование Позволяет пользователю редактировать с помощью форматирования символов и абзацев. слайдер Отображает ползунок управления с дополнительными отметками. кнопка вращения Отображает пару кнопок со стрелками, которые пользователь может нажать, чтобы увеличить или уменьшить значение. вкладка Отображает элементы, похожие на разделители, используемые в диалоговом окне с вкладками. поля или таблицы свойств. Прежде чем мы сможем внедрить новые общие элементы управления, требуются некоторые предварительные шаги. Причина в том, что на библиотеку common controls не осуществляется автоматическая ссылка во время соединения и она не инициализируется для работы. Необходимы следующие операции: 1. Библиотека common controls с именем Comctl32.lib должна быть включена в список библиотек, на которые ссылается программа компоновщика. Это достигается путем открытия проекта Элементы графического пользовательского интерфейса 581 меню и выбор команды Настройки. В диалоговом окне "Настройки проекта" откройте вкладку "Ссылка". Поле редактирования "Объект /библиотечные модули" содержит список всех ссылочных библиотек, разделенных друг от друга пробелом. Поместите курсор между двумя записями библиотеки и введите "Comctl32.lib". Нажмите кнопку OK. 2. Программный код должен включать заголовочный файл common controls. Это сопровождается дополняется инструкцией: #включить <commctrl.h> 3. Функция initcommon Controls() должна быть вызвана до использования обычных элементов управления . Эта функция не принимает параметров и ничего не возвращает. Инициализация может быть выполнена в WinMain() следующим образом: initcommon controls(); 4. Элементы управления расширенного редактирования находятся в их собственной библиотеке с именем Riched32.dll и имеют свой собственный файл заголовка с именем richedit.h. Чтобы использовать элементы управления библиотеки, ваша программа должна содержать инструкцию: LoadLibrary ("RICHED32.DLL"); На этом этапе приложение может реализовать общие элементы управления. В этом разделе мы рассмотрим некоторые из распространенных элементов управления, которые чаще всего встречаются в графических приложениях ics, а именно панели инструментов и элементы управления всплывающими подсказками. Их вместе с элементами управления строки состояния иногда называют элементами управления фреймового окна. Некоторые из распространенных элементов управления доступны на панели инструментов редактора диалогового окна Developer Studio. Редактор ресурсов содержит специальный редактор панели инструментов для создания этого типа общего элемента управления. Большинство распространенных элементов управления также могут быть созданы с помощью функций CreateWindow() или CreateWindowEx(). Другие имеют специальную функцию , такую как CreateToolbarEx(). 22.5.1 Общие элементы управления обработкой сообщений Наиболее распространенные элементы управления отправляют сообщения WM_NOTIFY. Одним заметным исключением являются элементы управления панели инструментов, которые отправляют WM_COMMAND. При обработке сообщений обычных элементов управления мы используем те же методы, что и при обработке пунктов меню. Сообщение WM_NOTIFY содержит идентификатор элемента управления в wParam и указатель на структуру в lParam. Структура представляет собой либо структуру NMHDR, либо, чаще, более крупную структуру, которая имеет структуру NMHDR в качестве своего первого элемента
. Общие уведомления (имена которых начинаются с NM_) и уведомления TTN_SHOW и TTN_POP элементов управления ToolTip - единственные случаи, в которых структура NMHDR фактически используется сама по себе. Формат структуры NMHDR следующий: структура typedef tagNMHDR { HWND hwndFrom; UINT idFrom; UINT код; } НМПЧ; где hwndFrom - дескриптор элементов управления, отправляющих сообщение, idFrom идентификатор элемента управления, а code - одно из значений в таблице 22.8. 582 Глава 22 Таблица 22.8 Общие коды уведомлений о контроле код ДЕЙСТВИЕ В КОНТРОЛЕ Или РЕЗУЛЬТАТАХ NM_CLICK Пользователь щелкнул левой кнопкой мыши. NM_DBLCLK Пользователь дважды щелкнул левой кнопкой мыши. NM_RCLICK Пользователь щелкнул правой кнопкой мыши. NM_RDBLCLK Пользователь дважды щелкнул правой кнопкой мыши. NM_RETURN Пользователь нажал клавишу ввода. NM_SETFOCUS Элементу управления присвоен фокус ввода. NM_KILLFOCUS Элемент управления потерял фокус ввода. NM_OUTOFMEMORY Элементу управления не удалось завершить операцию, поскольку было недостаточно доступной памяти. Чаще всего уведомления передают указатель на более крупную структуру, которая содержит структуру NMHDR в качестве своего первого члена. Например, элемент управления просмотром списка использует уведомление LVN_KEYDOWN, которое отправляется при нажатии клавиши. В этом случае указатель находится на структуру LV_KEYDOWN, определенную следующим образом: typedef struct tagLV_KEYDOWN { NMHDR hdr; СЛОВО wVKey; Флаги UINT; } LV_KEYDOWN; Поскольку элемент NMHDR является первым в этой структуре, указатель в сообщении об уведомлении может быть приведен либо к указателю на NMHDR, либо к указателю на LV_KEYDOWN. 22.5.2 Панели инструментов и всплывающие подсказки Панель инструментов - это окно, содержащее графические кнопки или другие элементы управления. Обычно он расположен между клиентской областью и строкой меню. Хотя приложения Windows используют панели инструментов в течение длительного времени, системная поддержка панелей инструментов отсутствовала до выпуска API WIN-32. Чаще всего панели инструментов используются для обеспечения быстрого доступа к командам меню. Панели инструментов часто содержат разделители, представляющие собой пробелы на панели инструментов, позволяющие группировать связанные кнопки. Всплывающая подсказка - это небольшое всплывающее окно, которое отображается, когда мышь удерживается на кнопке панели инструментов более чем на полсекунды дольше. Всплывающие подсказки обычно состоят из короткого текстового сообщения, объясняющего функцию кнопки или элемента управления панели инструментов. На рисунке 22.11 показана программа, содержащая панель инструментов с девятью кнопками. Рисунок 22.11 Панель инструментов Элементы графического пользовательского интерфейса
583 Обратите внимание на рис. 22.11, что разделители используются для группировки кнопок панели инструментов. В этом случае первая группа кнопок соответствует функциям в меню Файл, вторая группа - функциям в меню Правка и так далее. Обычно не все команды меню имеют кнопку на панели инструментов, а только те, которые используются чаще всего. 22.5.3 Создание панели инструментов Существует несколько способов создания панели инструментов. Вы можете определить панель инструментов в коде, используя стандартные кнопки, предоставленные в Visual C ++. Вы можете использовать предварительно созданное растровое изображение кнопок панели инструментов, которое можно преобразовать в ресурс панели инструментов и затем отредактировать. Вы можете создавать пользовательские кнопки с помощью редактора панели инструментов. Или вы можете использовать комбинацию этих методов. В этом разделе мы придерживаемся самого простого метода, но даже в этом случае вы должны быть осторожны и выполнять шаги в том же порядке, в котором мы их перечислили. Инструменты создания панели инструментов в Developer Studio были разработаны для использования при программировании MFC ; поэтому система делает предположения относительно порядка, в котором выполняются шаги. Если вы будете небрежны в этом отношении, вам может в конечном итоге потребоваться выполнить некоторое ручное редактирование файлов ресурсов. Мы должны признать, что существуют сложности при создании панели инструментов за пределами MFC, однако больших страданий можно избежать, если панель инструментов не будет создана до того, как будет определено меню программы. Идея состоит в том, чтобы использовать те же идентификационные коды для панели инструментов, что и для соответствующих пунктов меню, чтобы обработка сообщений выполнялась с той же процедурой перехвата. Например, если первая кнопка на панели инструментов на рисунке 22.11 соответствует команде New в меню File, то и кнопка, и пункт меню могут иметь имя ID_FILE_NEW. То же самое относится и к другим кнопкам на панели инструментов. В следующем описании создания панели инструментов мы предполагаем, что идентификационные строки были определены для соответствующих пунктов меню: Меню "Файл": ID_FILE_NEW ID_FILE_OPEN' ОТКРЫТЬ ФАЙЛ ID_FILE_SAVE Меню редактирования: ID_EDIT_CUT ID_EDIT_COPY ID_EDIT_PASTE Меню печати: ID_PRT_PRINT Меню справки: ID_HLP_ ПО ПОВОДУ ID_HLP_HELP Все кнопки панели инструментов на рис. 22.11 соответствуют стандартным кнопкам, содержащимся в Developer Studio. Эти кнопки можно загрузить на панель инструментов, указав их системные названия или загрузив растровое изображение, содержащее их. В текущем примере мы используем растровый подход. Панели инструментов требуют, чтобы каждая из кнопок была определена в структуре типа TBBUTTON (см. Приложение F). Ваша программа, обычно в оконной процедуре, создает массив структур с одной записью для каждой кнопки на панели инструментов. В 584 Глава 22 разделители кнопок должны быть включены. В случае экрана на рис. 22.11, array структуры TBBUTTON выглядит следующим образом: // Массив атрибутов для кнопок панели инструментов TBBUTTON tbb[] = { 0, ID_FILE_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 1, ID_FILE_OPEN, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 2, ID_FILE_SAVE ФАЙЛА, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP,
0, 0, 0, 0, 3, ID_EDIT_CUT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 4, ID_EDIT_COPY, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 5, ID_EDIT_PASTE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 0, 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, 6, ID_PRT_PRINT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, 7, ID_HLP_ABOUT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, 8, ID_HLP_HELP, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0, }; /* |------------| |-------------| |------------|| смотрите примечание | | | | | ниже | | | |--- Один или более | | | стили кнопок | | |----------- Один или несколько государственных флагов | |--------------------------- Идентификатор команды, сопоставленный с | кнопка |------------------- Нулевой индекс к изображению кнопки в | растровое изображение (без разделителей) Примечание: 0, 0, 0, 0 | | | |------------ указатель строки кнопки | |--|--------------- значение, определяемое приложением |--------------------- Зарезервировано */ В таблице 22.9 перечислены флаги стилей, используемые с панелями инструментов. Таблица 22.9 Панель инструментов и флаги стиля кнопок панели инструментов Стиль Описание Стили панели инструментов: BSTYLE_ALTDRAG Позволяет пользователю изменять положение кнопки на панели инструментов, перетаскивая ее, удерживая нажатой клавишу Alt. Если этот стиль не указан, пользователь должен удерживать нажатой клавишу Shift во время перетаскивания кнопки. Обратите внимание, что необходимо указать стиль CCS_ADJUSTABLE, чтобы разрешить перетаскивание кнопок панели инструментов. TBSTYLE_TOOLTIPS Создает элемент управления всплывающей подсказкой, который приложение может использовать для отображения описательного текста для кнопок на панели инструментов. TBSTYLE_WRAPABLE Создает панель инструментов, которая может содержать несколько строк кнопок. Кнопки панели инструментов могут "переноситься" на следующую строку, когда панель инструментов становится слишком узкой, чтобы вместить все кнопки в одной строке. Перенос происходит по разделительным и негрупповым границам. Стили кнопки инструментов:
TBSTYLE_BUTTON Создает стандартную нажимную кнопку. TBSTYLE_CHECK Кнопка переключается между нажатым и не нажатым состояниями каждый раз, когда пользователь нажимает на нее. В нажатом состоянии кнопка имеет другой цвет фона. TBSTYLE_CHECKGROUP Создает кнопку проверки, которая остается нажатой до тех пор, пока не будет нажата другая кнопка в группе. (продолжает) Элементы графического пользовательского интерфейса 585 Таблица 22.9 Панель инструментов и флаги стиля кнопок панели инструментов (продолжение) Стиль Описание Стили кнопок инструментов: TBSTYLE_GROUP Создает кнопку, которая остается нажатой до тех пор, пока не будет нажата другая кнопка в группе. TBSTYLE_SEP Создает разделитель. Кнопка, имеющая этот стиль, не получает пользовательский ввод и ей не присваивается номер кнопки. В таблице 22.10 перечислены состояния панели инструментов Таблица 22.10 Состояния панели инструментов СОСТОЯНИЕ ПАНЕЛИ ИНСТРУМЕНТОВ Описание TBSTATE_CHECKED Кнопка имеет стиль TBSTYLE_CHECKED и нажимается. TBSTATE_ENABLED Кнопка принимает пользовательский ввод. Кнопка, не имеющая этого состояния, не принимает пользовательский ввод и отображается серым цветом. TBSTATE_HIDDEN Кнопка не видна и не может принимать вводимые пользователем данные. TBSTATE_INDETERMINATE Кнопка выделена серым цветом. TBSTATE_PRESSED Кнопка нажата. TBSTATE_WRAP После кнопки следует разрыв строки. Кнопка также должна иметь состояние TBSTATE_ENABLED. Растровое изображение для панели инструментов на рисунке 22.11 предоставлено Developer Studio. Мы сделали копию этого растрового изображения, и вы можете найти его в каталоге ресурсов в онлайновом программном пакете книги. Растровое изображение называется toolbar.bmp. Процесс создания панели инструментов из растрового изображения панели инструментов требует, чтобы вы следовали определенной последовательности. Цена, которую приходится платить за изменение порядка операций, заключается в том, что вы можете в конечном итоге получить неправильные файлы ресурсов, которые необходимо отредактировать вручную. Результатом следующих операций является ресурс панели инструментов: 1. Выберите Ресурс в меню Вставка. Выберите тип растрового ресурса и нажмите Кнопку Импортировать. 2. В диалоговом окне "Редактор импорта ресурсов" измените поле "Имя файла" на поле "имя файла растрового изображения". Это достигается путем ввода "*.bmp". Теперь вы можете выполнять поиск по файловой системе до тех пор, пока не найдете растровое изображение панели инструментов. В этом случае желаемое растровое изображение имеет название "toolbar.bmp". Выберите растровое изображение и нажмите на кнопку с надписью Импортировать. 3. Растровое изображение панели инструментов теперь загружено в редактор растровых изображений. Растровое изображение панели инструментов показано на рисунке 22.12. Кнопки помечены в соответствии с идентификаторами, подписанными какв элементах структуры TBBUTTON, перечисленных ранее. 4. Теперь вы должны преобразовать растровое изображение в ресурс панели инструментов. Для этого откройте меню Изображение и щелкните по команде Редактор панели инструментов. Новая панель ресурсов, диалоговое окно с размером пикселя нормальных кнопок на панели инструментов является Дисиграли, что на 16 пикселей в ширину и пикселей в высоту 15. Нажмите "ОК", и
появится редактор панели инструментов с растровым изображением, преобразованным в панель инструментов. 586 Глава 22 Рисунок 22.12 Идентификационные коды кнопок ”Toolbar.bmp" 5. Теперь вы можете перейти к редактированию панели инструментов и присвоить идентификационные коды каждой из кнопок. Обратите внимание, что в конце панели инструментов есть пустая кнопка, которая используется для создания пользовательских кнопок. Вы можете нажать на пустую кнопку и использовать редактор для создания нового изображения кнопки. Чтобы удалить кнопку, щелкните по ней и перетащите с панели инструментов. Чтобы переместить кнопку, нажмите на нее и перетащите в новое место. Чтобы создать пространство на панели инструментов , перетащите кнопку так, чтобы она перекрывала половину ширины соседней кнопки. Чтобы присвоить идентификационный код кнопке панели инструментов, дважды щелкните по кнопке и введите новый идентификатор в поле ИДЕНТИФИКАТОР: редактирование диалогового окна Свойства кнопки панели инструментов. На этом этапе вы можете ввести соответствующие идентификационные коды для всех кнопок на панели инструментов. На рисунке 22.13 показан редактор панели инструментов после вставки разделителей. Рисунок 22.13 Редактор панели инструментов Developer Studio Элементы графического пользовательского интерфейса 587 ID_FILE_NEW ID_FILE_OPEN ID_FILE_SAVE

Arc (hdc, 150, 150, // верхний левый угол прямоугольника 350, 250, // справа внизу 250, 260, // начальная точка 200, 140; // конечная точка На рисунке 23.7 показано расположение каждой из точек в предыдущем вызове функции Arc() и результирующий эллипс. Рисунок 23.7 Координаты эллиптической дуги в примере кода 23.4.7 Рисование с помощью arcTo() arcTo() - это версия функции Arc(), которая обновляет текущее положение пера до конечной точки эллиптической дуги. Для этой функции требуется Windows NT версии 3.1 или более поздней. Она недоступна в Windows 95 или более поздней версии. Параметры функции идентичны параметрам функции Arc(). 23.4.8 Рисование с помощью AngleArc() Функция AngleArc() рисует отрезок прямой линии и дугу окружности. Отрезок прямой линии проходит от текущего положения пера до начальной точки дуги. Дуга определяется радиусом окружности и двумя углами: начальным положением в градусах относительно оси x и угловой разверткой, также в градусах, относительно начального положения. Дуга рисуется в направлении против часовой стрелки. Общий вид функции выглядит следующим образом: BOOL УГЛОВАЯ дуга( HDC hdc, // 1 int X, // 2 int Y, // 3 DWORD двРадиус, // 4 ПОПЛАВОК eStartAngle, // 5 ПОПЛАВОК eSweepAngle // 6 ); 624 Глава 23 x = 150 y = 150 x = 350 y = 250 ограничивающий прямоугольник x = 200 y = 140 (начальная точка) (конечная точка) направление рисования x = 250 y = 260 Второй и третий параметры - это координаты центра окружности, которая определяет дугу, в логических единицах. Четвертый параметр - это радиус окружности, также в логических единицах. Пятый параметр - это начальный угол в градусах относительно оси x. Последний параметр - это угол развертки, также в градусах, относительно начального положения угла. На рисунке 23.8 показаны различные элементы в функции AngleArc(). Рисунок 23.8 Функциональные элементы AngleArc() Функция AngleArc() недоступна в Windows 95 или более поздней версии; однако ее можно
эмулировать в коде. Microsoft Developers Network содержит следующий список, который позволяет реализовать функцию AngleArc() в программном обеспечении: BOOL AngleArc2(HDC hdc, int X, int Y, DWORD dwRadius, float fStartDegrees, float fSweepDegrees) { int iXStart, iYStart; // Конечная точка начальной радиальной линии int iXEnd, iYEnd; // Конечная точка конечной радиальной линии плавающие значения fStartRadians; // Начальный угол в радианах плавающие радианы; // Конечный угол в радианах BOOL результат; // Результат функции float fTwoPi = 2,0 f * 3,141592f; /* Получаем начальный и конечный углы в радианах */ if (fSweepDegrees > 0,0f) { fStartRadians = ((fStartDegrees / 360.0f) * fTwoPi); fEndRadians = (((fStartDegrees + fSweepDegrees) / 360.0f) * fTwoPi); } else { Первые радианы = (((Первые стартовые градусы + первые средние градусы) / 360.0f) * fTwoPi); Радианы = ((Начальные градусы / 360.0f) * fTwoPi); } /* Вычислить точку на начальной радиальной линии с помощью */ /* полярно -> декартова преобразования */ iXStart = X + (int)((float)dwRadius * (float)cos(fStartRadians)); iYStart = Y - (int)((float)dwRadius * (float)sin(fStartRadians)); /* Вычислить точку на конечной радиальной линии с помощью */ /* полярно -> декартова преобразования */ iXEnd = X + (int)((float)dwRadius * (float)cos(fEndRadians)); iYEnd = Y - (int)((float)dwRadius * (float)sin(fEndRadians)); Рисование линий и кривых 625 центр окружности и положение пера сегмент и дуга радиус начальный угол угол развертки /* Нарисуйте линию до начальной точки */ lineTo(hdc, iXStart, iYStart); /* Нарисуйте дугу */ bResult = Arc(hdc, X - dwRadius, Y - dwRadius, X + dwRadius, Y + dwRadius, iXStart, iYStart, iXEnd, iYEnd); // Переместить в конечную точку - Arc() не будет этого делать, а arcTo() // не будет работать на Win32s или Win16 */ MoveToEx(hdc, iXEnd, iYEnd, NULL); вернуть результат; } Обратите внимание, что единственное документированное различие между предыдущим списком AngleArc2() и функцией GDI AngleArc() заключается в том, что если значение, введенное в шестом параметре, превышает 360 градусов, версия программного обеспечения не будет многократно развертывать угол gle. В большинстве случаев это не проблема. Программа с именем PXL_DEMO, находящаяся в папке проекта Pixel and Line Demo в онлайновом программном пакете книги, использует функцию AngleArc2() для отображения кривой, подобной той, что показана на рисунке 23.8. 23.4.9 Рисование с помощью Полибезье() В механическом черчении сплайн - это гибкая кромка, которая используется для соединения нескольких точек на нерегулярной кривой. Два французских инженера, Пьер Безье и Пол де Кастельжо, почти одновременно обнаружили математическое выражение для сплайновой кривой, которое можно легко адаптировать к компьютерным представлениям. Эта кривая
известна как сплайн Безье или кривая, поскольку именно Безье впервые опубликовал свои находки . Кривая Безье определяется своими конечными точками, называемыми узлами, и одной или несколькими контрольными точками. Контрольные точки служат магнитами или аттракторами, которые "тянут" кривую в своем направлении, но никогда не настолько, чтобы кривая пересекла контрольную точку. На рисунке 23.9 показаны элементы простой кривой Безье. Рисунок 23.9 Сплайн Безье Кривая Безье на рис. 23.9 может быть сгенерирована геометрическим методом, который заключается в создании серии постепенно уменьшающихся отрезков. Процесс, 626 Глава 23 запуск узла Кривая Безье конечный узел контрольная точка (аттрактор) иногда называемый методом "разделяй и властвуй", начинается с соединения промежуточных точек между узлами и аттрактором, создавая таким образом новый набор узлов и новый аттрактор. Процесс продолжается до тех пор, пока не будет достигнута достаточно точная аппроксимация сплайна. На рисунке 23.10 показаны последовательные этапы создания сплайна Безье с помощью этого метода. Рисунок 23.10 Метод построения кривой Безье "разделяй и властвуй" На шаге 1 на рисунке 23.10 мы видим начальный узел S1, конечный узел E1 и привлекательтор A1. Сначала мы находим точку на полпути между S1 и A1 и помечаем ее как P1. Другая точка на полпути между A1 и E1 помечается как P2. Точки P1 и P2 соединяются отрезком прямой , средняя точка которого обозначена как P3. На шаге 2 мы можем увидеть две новые фигуры. Первый рисунок имеет узлы в точках S2 и E2, а аттрактор - в точке A2. Второй рисунок имеет узлы в точках S3 и E3, а аттрактор - в точке A3. На шаге 3 мы соединили средние точки между узлами и аттракторами отрезком прямой, продолжая таким образом процесс . Две новые фигуры имеют свои новые соответствующие узлы и аттракторы, так что процесс можно снова повторить. На шаге 3 мы можем видеть, как результирующие отрезки линии начинают приближаться к кривой Безье на рисунке 23.9. Процесс "разделяй и властвуй" делает очевидным фундаментальное допущение сплайна Безье: кривая проходит в одном направлении и касается прямых линий от узлов к аттракторам. Второе предположение состоит в том, что кривая никогда не пересекает аттракторы. Формулы Безье основаны на этих предположениях. Кривая Безье, сгенерированная методом "разделяй и властвуй", известна как качественная Безье. В компьютерной графике наиболее полезной формой Безье является кубическая форма. В кубической форме кривая Безье определяется двумя узлами и двумя аттракторами. Развитие кубической кривой Безье почти идентично развитию квадратичной. На рисунке 23.11, на следующей странице, показаны элементы кубической кривой Безье. Рисование линий и кривых 627 ШАГ 1: ШАГ 2: ШАГ 3: S 1 S 2 S 4 S 5 A 5 E 5 А 4 E 4 E 2 A 2 A 3 S 3 E 3 P 3 P 2 P 1 A
1 E 1 Рисунок 23.11 Элементы кубической системы Безье Функция PolyBezier(), представленная в Windows 95, рисует одну или несколько кубических кривых Безье, каждая из которых определяется своими узлами и двумя аттракторами. Функция может вызываться для рисования нескольких кривых Безье. В этом случае для первой кривой требуется четыре параметра, а для всех остальных кривых требуется три параметра. Это происходит потому, что конечный узел предыдущей кривой Безье служит начальным узлом для следующей. PolyBezier() не изменяет текущее положение пера. Кривая Безье рисуется с помощью пера, выбранного в контексте устройства. Общая форма функции выглядит следующим образом : БУЛ Полибезье( HDC hdc, // 1 ПОСТОЯННАЯ ТОЧКА * lppt, // 2 DWORD CPOINTS // 3 ); Первый параметр - это дескриптор контекста устройства. Второй параметр - это адрес массива точек, который содержит пары координат x и y для узлов и контрольных точек. Третий параметр - это количество точек в массиве. Это значение должно более чем в три раза превышать количество кривых, которые будут нарисованы. Например, если функция PolyBezier() вызывается для рисования четырех кривых, в массиве должно быть 13 пар координат (1 + (3 * 4)). Функция возвращает TRUE, если она выполнена успешно, и FALSE в противном случае. Данные Безье хранятся в массиве точек в определенном порядке. В первой кривой Безье первая и четвертая записи являются узлами, а вторая и третья - аттракторами. Обратите внимание, что в массиве первая и четвертая записи находятся со смещением 0 и 3 соответственно, а вторая и третья записи находятся со смещением 1 и 2. Если в массиве есть другие кривые Безье, первый узел не является явным в данных, поскольку он совпадает с конечным узлом предыдущей кривой. Следовательно, после первой кривой следующие два элемента являются аттракторами, а третий элемент является конечным узлом. В таблице 23.6 показана последовательность узлов и контрольных точек для массива с многослойными кривыми Безье. 628 Глава 23 начальный узел первый элементуправления точка вторая точка управления конечный узел Сплайн Безье Таблица 23.6 Узлы и контрольные точки для функции PolyBezier() ЧИСЛО СМЕЩЕНИЕ Тип 1 0 Начальный узел кривой 1 2 1 Первый аттрактор кривой 1 3 2 Второй аттрактор кривой 1
4 3 Конечный узел кривой 1 5 4 Первый аттрактор кривой 2 6 5 Второй аттрактор кривой 2 7 6 Конечный узел кривой 2 8 7 Первый аттрактор кривой 3 9 8 Второй аттрактор кривой 3 10 9 Конечный узел кривой 3 Следующий фрагмент кода показывает построение кривой Безье с использованием функции PolyBezier(): ТОЧКИ Набор точек[4]; // Массив координат x/y ... // Заполнить массив точек для сплайна Безье // Записи 0 и 3 являются узлами // Записи 1 и 2 являются аттракторами pointsArray[0].x = 150; pointsArray[0].y = 150; pointsArray[1].x = 200; pointsArray[1].y = 75; pointsArray[2].x = 280; pointsArray[2].y = 190; pointsArray[3].x = 350; pointsArray[ 3].y = 150; // Нарисуйте сплайн Безье PolyBezier (hdc, pointsArray, 4); Результирующая кривая Безье аналогична кривой на рис. 23.9. 23.4.10 Рисование с помощью PolyBezierTo() Функция PolyBezierTo() очень похожа на функцию PolyBezier(), за исключением того, что начальным узлом для первой кривой является текущее положение пера, а текущее положение пера обновляется до конечного узла последней кривой. Возвращаемое значение и параметры одинаковы для обеих функций. В случае PolyBezierTo() каждая кривая определяется тремя точками: двумя контрольными точками и конечным узлом. В таблице 23.7 на следующей странице показана последовательность баллов, сохраненных в массиве points для PolyBezierTo(). 23.4.11 Рисование с помощью PolyDraw() PolyDraw() - самая сложная из функций рисования линий Windows. Это создает возможность рисования серии отрезков и кривых Безье, которые могут быть объединенными или непересекающимися. PolyDraw() может использоваться вместо нескольких вызовов функций moveTo(), lineTo() и PolyBezierTo(). Все рисунки нарисованы с помощью пера, выбранного в данный момент в контексте устройства. Общая форма функции выглядит следующим образом: BOOL Полидрайв( HDC hdc, // 1 ПОСТОЯННАЯ ТОЧКА * lppt, // 2 КОНСТАНТНЫЙ БАЙТ *lpbTypes, // 3 int cCount // 4 ); Рисование линий и кривых 629 Таблица 23.7 Узлы и контрольные точки для функции PolyBezierTo()
ЧИСЛО СМЕЩЕНИЕ Тип 1 0 Первый аттрактор кривой 1 2 1 Второй аттрактор кривой 1 3 2 Конечный узел кривой 1 4 3 Первый аттрактор кривой 2 5 4 Второй аттрактор кривой 2 6 5 Конечный узел кривой 2 7 6 Первый аттрактор кривой 3 8 7 Второй аттрактор кривой 3 9 7 Конечный узел кривой 3 Второй параметр - это адрес массива точек, который содержит пары координат x и y. Третий параметр представляет собой массив типа BYTE, который содержит идентификаторы, определяющие назначение каждой из точек в массиве. Четвертый параметр - это подсчет количества точек в массиве точек. Функция повторно принимает значение TRUE, если она выполнена успешно, и FALSE в противном случае. В таблице 23.8 перечислены константы, используемые для представления идентификаторов, введенных в третьем параметре функции. Таблица 23.8 Константы для спецификаторов точек PolyDraw() Тип ЗНАЧЕНИЕ PT_MOVETO С этой точки начинается непересекающаяся фигура. Точка становится новым текущим положением пера. PT_LINETO Необходимо провести линию от текущей позиции до этой точки, которая затем становится новой текущей позицией пера. PT_BEZIERTO Это контрольная точка или конечный узел для кривой Безье . Эта константа всегда встречается в наборах из трех. Текущее положение определяет начальный узел для кривой Безье. Две другие координаты являются контрольными точками. Третья запись является конечным узлом. PT_CLOSEFIGURE Фигура автоматически закрывается после выполнения типа PT_LINETO или PT_BEZIERTO для этой точки . Линия рисуется от конечной точки до самой последней точки PT_MOVETO или moveTo(). Константа PT_CLOSEFIGURE объединяется посредством побитового оператора OR с константой PT_LINETO
или PT_BEZIERTO. Это указывает на то, что соответствующая точка является последней на рисунке и что рисунок должен быть закрыт. Функция PolyDraw() недоступна в Windows 95 или более поздней версии. Корпорация Майкрософт опубликовала следующий код для реализации этой функции в программном обеспечении: //***************************** // Версия PolyDraw() для Win95 // как опубликовано корпорацией Майкрософт) //***************************** BOOL PolyDraw95(HDC hdc, // дескриптор контекста устройства CONST LPPOINT lppt, // массив точек 630 Глава 23 CONST LPBYTE lpbTypes, // идентификаторы линий и кривых int cCount) // количество точек { int i; for (i=0; i<cCount; i++) switch (lpbTypes[i]) { случай PT_MOVETO : MoveToEx(hdc, lppt[i].x, lppt[i].y, NULL); разрыв; регистр PT_LINETO | PT_CLOSEFIGURE: регистр PT_LINETO : lineTo(hdc, lppt[i].x, lppt[i].y); разрыв; случай PT_BEZIERTO | PT_CLOSEFIGURE: случай PT_BEZIERTO : ПолиБезьерТо(hdc, &lppt[i], 3); i+=2; разрыв; } возвращает TRUE; } Обратите внимание, что в функции PolyDraw95() обработка закрытых и открытых фигур выполняется в одних и тех же перехватах. Следовательно, закрывающее действие не выполняется mented.ЖнениЮСПОЛЬНУЮсофтшареяМПЛЕМЕНТАЦия,насЛУДиНГТИЧЕ PT_CLOSEFIGURE константа не влияет на чертеже. Мы закодировали следующую модификацию, названную PolyDraw95A(), которая закрывает открытые фигуры: //***************************** // Версия PolyDraw() для Win95 // улучшено! //***************************** BOOL PolyDraw95A (HDC hdc, // дескриптор контекста устройства CONST LPPOINT lppt, // массив точек , CONST LPBYTE lpbTypes, // массив идентификаторов int cCount) // количество точек { int i; статический длинный lastPenx, lastPeny; // Память для последнего положения пера ТОЧКА Текущие точки [1]; // Сохраняем начальное положение пера для рисования Получаем currentpositionex (hdc, currentPoints); lastPenx = currentPoints[0].x; lastPeny = currentPoints[0].y; для (i=0; i<cCount; i++) переключить (lpbTypes[i]) { регистр PT_MOVETO :
MoveToEx(hdc, lppt[i].x, lppt[i].y, NULL); // Сохранить позицию для закрытых фигур lastPenx = lppt[i].x; lastPeny = lppt[i].y; перерыв; пример PT_LINETO | PT_CLOSEFIGURE: Рисование линий и кривых


области, определенной в вызове, путем выполнения логического И между двумя областями RGN_COPY Результирующая область отсечения является копией области, определенной в вызове. Результат идентичен вызову SelectClipRgn(). Если регион, указанный в вызове, равен нулю, новый регион отсечения является регионом отсечения по умолчанию RGN_DIFF Результирующая область отсечения представляет собой разницу между текущей областью отсечения и той, которая была определена в вызове RGN_OR Результирующая область отсечения является результатом выполнения логической операции ИЛИ над текущей областью отсечения и регионом, определенным в вызове RGN_XOR Результирующая область отсечения является результатом выполнения логической операции XOR над текущей областью отсечения и областью, определенной в вызове Области отсечения , возникающие в результате этих режимов выбора , такие же , как у u s e d i n t h e C o m b i n e R g n ( ) f u n c t i o n , a s s h o w n i n F i g u r e 2 4 . 15 . Функция ExtSelectClipRgn() возвращает одно из значений в таблице 24.8. Рисование сплошных фигур 673
Функция IntersectClipRect() создает новую область отсечения, выполняя логическое И между текущей областью отсечения и прямоугольной областью, определенной в вызове. Общая форма функции выглядит следующим образом: int IntersectClipRect( HDC hdc, // 1 int nLeftRect, // 2 int nTopRect, // 3 int nRightRect, // 4 int nBottomRect // 5 ); Второй и третий параметры - это координаты верхнего левого угла прямоугольника. Четвертый и пятый параметры - это координаты нижнего правого угла. Функция возвращает одно из значений в таблице 24.8. Функция ExcludeClipRect() вычитает прямоугольник, указанный в вызове, из области отсечения. Общая форма функции выглядит следующим образом: int ExcludeClipRect( исключающий код) HDC hdc, // 1 int nLeftRect, // 2 int nTopRect, // 3 int nRightRect, // 4 int nBottomRect // 5 ); Второй и третий параметры - это координаты верхнего левого угла прямоугольника. Четвертый и пятый параметры - это координаты нижнего правого угла. Функция возвращает одно из значений в таблице 24.8. Функция OffsetClipRgn() преобразует область отсечения вдоль горизонтальной или вертикальной осей. Общий вид функции выглядит следующим образом: int смещение clip RGN( HDC hdc, // 1 int nXOffset, // 2 int nYOffset // 3 ); Второй параметр - это величина перемещения области отсечения вдоль оси x. Третий параметр - это величина перемещения вдоль оси y. Положительные значения указывают на движение вправо или вниз. Отрицательные значения указывают на движение влево или вверх. Функция возвращает одно из значений в таблице 24.8. Функция SelectClipPath() добавляет текущий путь к области отсечения контекста устройства в соответствии с предопределенным режимом. Общая форма функции выглядит следующим образом: BOOL SelectClipPath( HDC hdc, // 1 int iMode // 2 ); 674 Глава 24 Второй параметр является одной из констант, перечисленных в таблице 24.10. Функция возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя. Пути обсуждаются позже в этой главе. 24.5.2 Информация об области отсечения Коду, использующему отсечение, часто требуется получить информацию о области отсечения. Для этой цели доступно несколько функций. Функция GetClipBox() извлекает ограничивающий прямоугольник для области отсечения. Этот прямоугольник является наименьшим, который
можно нарисовать вокруг видимой области контекста устройства. Общая форма функции выглядит следующим образом: инт GetClipBox( HDC hdc, // 1 LPRECT lprc // 2 ); Второй параметр - это указатель на прямоугольную структуру, которая получает координаты ограничивающего прямоугольника. Функция возвращает одно из значений в таблице 24.8. Функция GetClipRgn() извлекает дескриптор области отсечения для определенного контекста устройства. Общая форма функции выглядит следующим образом: int GetClipRgn( HDC hdc, // 1 ХРГН хргн // 2 ); Первый параметр - это дескриптор контекста устройства, область отсечения которого удалена . Второй параметр - это дескриптор существующей области отсечения, которая содержит результаты вызова. Функция возвращает ноль, если в контексте устройства нет области отсечения. Возвращаемое значение 1 указывает на то, что существует область отсечения и что второй параметр функции содержит ее дескриптор. Возвращаемое значение -1 указывает на ошибку. Функции f u n c t i o n r e f e r s t o c l i p p i n g r e g i o n s t h a t r e s u l t f r o m S e l e c t c l i p R g n () o f f функции ExtSelectClipRgn(). Области отсечения, назначенные системой при вызовах функции BeginPaint(), не возвращаются GetClipRgn(). Функция PtVisible() используется для определения, находится ли указанная точка в пределах области клипового пинга контекста устройства. Общая форма функции выглядит следующим образом: BOOL PtVisible( HDC hdc, // 1 int X, // 2 int Y // 3 ); Первый параметр - это дескриптор рассматриваемого контекста устройства. Второй и третий параметры - это координаты x и y рассматриваемой точки. Функция возвращает значение TRUE, если точка находится в пределах области отсечения, и значение FALSE в противном случае. Рисование сплошных фигур 675 Функция RectVisible() используется для определения, находится ли какая-либо часть прямоугольника в пределах области отсечения контекста устройства. Общая форма функции выглядит следующим образом: BOOL RectVisible( HDC hdc, // 1 CONST RECT * lprc // 2 ); Первый параметр - это дескриптор рассматриваемого контекста устройства. Второй параметр - это указатель на структурную переменную типа RECT, которая содержит координаты рассматриваемого прямоугольника. Функция возвращает TRUE, если какая-либо часть прямоугольника находится в пределах области отсечения, и FALSE в противном случае. 24.6 Пути В предыдущих главах мы обсуждали пути довольно неформально. Папка проекта Text Demo № 3, входящая в онлайновый пакет программного обеспечения книги, содержит программу, которая использует пути для достижения графических эффектов при отображении текста. Теперь мы рассмотрим пути пересмотра более строгим образом и применим пути к другим графическим операциям. Пути были введены в Windows NT и также поддерживаются Windows 95 и более поздними версиями. Как следует из названия, контур - это маршрут, по которому инструмент рисования следует
при создании определенной фигуры или набора фигур. Путь, который хранится внутри GDI, может служить для определения контура графического объекта. Например, если мы начнем с координат 100, 100 и перейдем к точке 150, 100, затем к 150, 200, оттуда к 100, 200 и, наконец, к начальной точке, мы определим путь для прямоугольной фигуры. Теперь мы можем обвести контур, чтобы нарисовать контур прямоугольника, заполнить контур, чтобы создать сплошную фигуру, или и обвести, и заполнить контур, чтобы создать фигуру, используя как контур, так и заливку. Как правило, существуют функции, связанные с путем, для выполнения следующих операций: • Чтобы нарисовать контур контура с помощью текущего пера. • Чтобы нарисовать внутреннюю часть контура с помощью текущей кисти. • Чтобы нарисовать контур и раскрасить внутреннюю часть пути. • Чтобы изменить траекторию, преобразующую кривые в отрезки. • Чтобы преобразовать контур в контур клипа. • Преобразовать путь в область. • Сгладить контур путем преобразования каждой кривой в контуре в серию линейных сегментов. • Чтобы получить координаты линий и кривых, составляющих контур. Путь - это объект контекста устройства, такой как область, перо, кисть или растровое изображение. Одной из характеристик пути является то, что в контексте устройства нет пути по умолчанию. Другая проблема заключается в том, что в контексте каждого устройства существует только один путь; это означает, что нет необходимости в дескрипторе пути. Каждый путь инициируется с помощью функции beginPath(). Это удаляет любой старый путь из контекста устройства и подготавливает к записи примитивы рисования, которые создают новый путь, иногда называемый скобкой пути. Любая из функций, перечисленных в таблице 24.11, может быть использована для 676 Глава 24 определение пути в Windows NT. Подмножество функций, которые можно использовать в paths в Windows 95 и более поздних версиях, перечислены в таблице 24.12. Поскольку контуры в основном используются в операциях отсечения, функция CloseFigure() обычно используется для закрытия открытой фигуры в контуре. После того как все фигуры, образующие путь, заключены в скобку path, приложение вызывает EndPath(), чтобы указать путь в контексте указанного устройства. Затем путь может быть преобразован в область отсечения с помощью вызова SelectClipPath(). Таблица 24.11 Функции определения пути в Windows NT AngleArc() lineTo() Ломаная линия() Arc() MoveToEx() ПолилинеТо() Аркто() Пирог() Полиполигон() Аккорд() Полибезье() Полиполилин() CloseFigure() Закрыть фигуру() ПолиБезьерТо()
Прямоугольник() Эллипс() Полидрайв() RoundRect() Круглая коррекция() ExtTextOut() Многоугольник() Вывод текста() Таблица 24.12 Функции определения пути в Windows 95 и более поздних версиях ExtTextOut() ПолиБезьерТо() Полиполигон() lineTo() Многоугольник() Полиполилин() MoveToEx() Ломаная линия() Вывод текста() Полибезье() ПолилинеТо() Обратите внимание, что термин clip path, или путь обрезки, иногда встречается в Windows d o c u m e n t a t i o n , c a n b e s o m e w h a t c o n f u s i n g . Функция I t i s b e t e r t o s a y h a SelectClipPath() преобразует путь в область отсечения, тем самым устраняя понятие пути отсечения как отдельного объекта. В таблице 24.13 перечислены функции, связанные с путями. Таблица 24.13 Функции, связанные с путем ФУНКЦИЯ Экшен СОЗДАНИЕ, УДАЛЕНИЕ И ПРЕОБРАЗОВАНИЕ ПУТИ: beginPath() Открывает скобку пути Конечный путь() Закрывает скобку пути и выбирает путь в контексте устройства Прервать путь() Закрывает и отбрасывает любую скобку открытого пути в контексте устройства Выберите clipPath() Превращает текущий путь в область отсечения для указанного контекста устройства. Объединяет новую область отсечения с любой существующей в соответствии с предопределенным режимом PathToRegion() Закрывает открытый путь и преобразует is в регион (продолжает) Рисование сплошных фигур 677 Таблица 24.13 Функции, связанные с путем (продолжение) ФУНКЦИЯ Экшен ОПЕРАЦИИ РЕНДЕРИНГА ПУТИ: StrokePath() StrokePath() Отображает контур текущего пути с помощью текущего пера FillPath() Закрывает и открывает фигуру в текущем контуре
и заполняет внутреннюю часть контура текущей кистью, используя текущий режим многоугольной заливки Штриховая дорожка () Отображает контур текущего контура с помощью текущего пера и заполняет внутреннюю часть с помощью текущей кисти CloseFigure() Рисует линию от текущего положения пера до начальной точки рисунка. Завершающая линия соединяется с первой линией рисунка с использованием текущего стиля соединения линий PolyDraw() Рисует линии и кривые, полученные с помощью getPath() (только для Windows NT) МАНИПУЛЯЦИИ С ТРАЕКТОРИЕЙ: FlattenPath() Преобразует кривые в текущем контуре в сегменты линии WidenPath() Переопределяет текущий контур в контексте данного устройства как область, которая была бы закрашена, если бы контур был обведен текущим пером SetMiterLimit() Задает длину стыковочных соединений для указанного контекста устройства ПОЛУЧИТЬ ИНФОРМАЦИЮ О ПУТИ: getPath() Извлекает координаты конечных точек линий и контрольных точек кривых на пути GetMiterLimit() Возвращает ограничение на длину соединяемых элементов miter в контексте указанного устройства GetPolyFillMode() Возвращает текущий режим заливки полигонов 24.6.1 Создание, удаление и преобразование контуров Путь инициируется вызовом функции beginPath() . Вызов отбрасывает любой существующий путь в контексте устройства и открывает скобку пути. Общая форма функции выглядит следующим образом: BOOL beginPath (HDC hdc); Единственным параметром является дескриптор контекста устройства. Функция возвращает значение TRUE, если она завершается успешно, и ЗНАЧЕНИЕ FALSE, если происходит сбой. После выполнения вызова beginPath() приложение может вызвать любую из функций, приведенных в таблице 24.11 или 24.12, в зависимости от платформы операционной системы. Функция EndPath() закрывает скобку пути и выбирает путь в контексте указанного устройства. Общая форма функции выглядит следующим образом: Конечный путь BOOL (HDC hdc); Единственным параметром является дескриптор контекста устройства. Функция возвращает ЗНАЧЕНИЕ TRUE в случае успешного выполнения и ЗНАЧЕНИЕ FALSE в случае сбоя. 678 Глава 24 Функция AbortPath() закрывает и отбрасывает любую скобку открытого пути в указанном контексте устройства. Общая форма функции выглядит следующим образом: BOOL AbortPath (HDC hdc); Единственным параметром является дескриптор контекста устройства. Скобка пути создается вызовом beginPath(), за которым следует одна или несколько функций рисования, перечисленных в таблицах 24.11 и 24.12, и закрывается вызовом EndPath(). На этом этапе приложения обычно переходят к обводке, заливке или обводке и заполнению контура или к установке его в качестве области отсечения. Для преобразования пути в область отсечения можно использовать два возможных метода.
Один из методов заключается в использовании PathToRegion() для создания региона, а затем вызове ExtSelectClipRgn(), чтобы сделать регион областью отсечения. Кроме того, код может вызывать SelectClipPath() и выполнять обе функции за один вызов. SelectClipPath() преобразует текущий путь в область отсечения для указанного контекста денаоборот, в соответствии с предопределенным режимом комбинирования. Общая форма функции выглядит следующим образом: BOOL SelectClipPath( HDC hdc, // 1 int iMode // 2 ); Второй параметр - это одно из значений, перечисленных в таблице 24.7. Функция перезапускается становится TRUE в случае успешного выполнения и FALSE в случае сбоя. Функция PathToRegion() закрывает открытый путь и преобразует is в регион. Общая форма функции выглядит следующим образом: HRGN PathToRegion (HDC hdc); Единственным параметром функции является дескриптор контекста устройства. Вызов предполагает, что путь в контексте устройства закрыт. PathToRegion() возвращает dle в созданный регион. Поскольку дескрипторов пути нет, эта функция предоставляет способ идентификации конкретного пути, хотя сначала он должен быть преобразован в region. К сожалению, не существует способа преобразования региона в путь. 24.6.2 Операции отображения пути После создания контура его можно отобразить в виде изображения, погладив его, заполнив или и то, и другое. В Windows NT также можно напрямую рисовать отрезки линий и кривые Безье, которые образуют путь, конечные и контрольные точки которого хранятся в массиве типа POINT. Функция StrokePath() отображает контур текущего контура с помощью арендованного пера. Общий вид функции выглядит следующим образом: BOOL StrokePath (HDC hdc); Единственным параметром является дескриптор контекста устройства, который содержит замкнутый путь. Поскольку контекст устройства может иметь только один путь, нет необходимости в дальнейшем Рисовании сплошных фигур 679 спецификация. Путь автоматически удаляется из контекста устройства после того, как он пройден. Функция возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя. Обратите внимание, что в документации Microsoft Visual C ++ не упоминается, что StrokePath() автоматически удаляет путь. Что еще хуже, замечания к функции StrokeAndFillPath() предполагают , что можно сначала обвести, а затем заполнить один и тот же путь, выполнив отдельные вызовы функций StrokePath() и FillPath(). В действительности функция StrokePath() уничтожает путь перед завершением выполнения. Последующий вызов FillPath() не имеет никакого эффекта, поскольку в контексте устройства пути больше нет. Именно по этой причине существует функция StrokeAndFillPath(). Без этой функции было бы невозможно одновременно обводить и заполнять контур. У объекта fillpath() функция закрывает фигуру в текущий путь и наполняет путь взадней с текущей кисти, с использованием текущего полигона режиме. Общая форма функции выглядит следующим образом: BOOL FillPath (HDC hdc); Единственным параметром является дескриптор контекста устройства, который содержит допустимый путь. Поскольку контекст устройства может иметь только один путь, нет необходимости в дальнейшем указании . Путь автоматически удаляется из контекста устройства после его заполнения. Функция возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя. Функция StrokeAndFillPath() закрывает открытую фигуру в текущем контуре, обводит контур контура текущим пером и заполняет внутреннюю часть контура текущей кистью, используя текущий режим многоугольной заливки. Общая форма функции выглядит следующим образом: BOOL StrokeAndFillPath (HDC hdc); Единственным параметром является дескриптор контекста устройства, который содержит допустимый путь. Путь автоматически удаляется из контекста устройства после его обводки и заполнения. StrokeAndFillPath() предоставляет единственный способ как для обводки, так и для заполнения пути в Windows, поскольку StrokePath() и FillPath() уничтожают путь после выполнения. Функция возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя.
Функция CloseFigure() рисует линию от текущего положения пера до начальной точки рисунка. Завершающая линия соединяется с первой линией рисунка с использованием текущего стиля соединения линий . Общая форма функции выглядит следующим образом: BOOL CloseFigure (HDC hdc); Единственным параметром является дескриптор контекста устройства, который содержит допустимый путь. Фигура в пути открыта до тех пор, пока не был выполнен вызов CloseFigure(), даже если начальная точка фигуры и текущая точка совпадают. Обычно начальной точкой рисунка является точка самого последнего вызова MoveToEx(). Эффект закрытия фигуры с помощью функции CloseFigure() отличается от использования вызова примитива рисования. Например, когда фигура закрывается вызовом функции lineTo(), в последнем углу вместо соединения используются заглушки. Если фигура нарисована толстым геометрическим пером, результаты могут быть совсем другими. Рисунок 24.19 показывает разницу между закрытием фигуры с помощью вызова lineTo() или CloseFigure(). 680 Глава 24 Треугольники на рис. 24.19 нарисованы ручкой с косым соединением и круглым торцевым колпачком. Одна из фигур закрывается с помощью функции рисования lineTo(), а другая - с помощью CloseFigure(). Вершина треугольника, замкнутого с помощью функции lineTo(), закруглена, в то время как вершина, замкнутая с помощью функции CloseFigure(), закруглена . Это связано с тем фактом, что два сегмента, нарисованные с помощью lineTo(), не имеют соединения в общей конечной точке. В этом случае внешний вид вершины определяется круглой торцевой крышкой рисунка. С другой стороны, когда фигура закрывается с помощью функции CloseFigure(), выбранное соединение используется во всех трех вершинах. Рисунок 24.19 Различия в закрытии рисунка Функция PolyDraw(), доступная только в Windows NT, рисует отрезки линий и кривые Безье. Из-за ее ограниченной переносимости мы не обсуждаем ее здесь. 24.6.3 Манипуляции с траекторией Несколько функций позволяют изменять существующие контуры или определять характеристики контура. FlattenPath() преобразует кривые текущего контура в отрезки. Общая форма функции выглядит следующим образом: BOOL FlattenPath (HDC hdc); Единственным параметром является дескриптор контекста устройства, который содержит допустимый путь. Функция возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя. Существует несколько документально подтвержденных применений функции FlattenPath(). Отображение на экране сглаженного контура практически невозможно обнаружить. Документированное применение этой функции заключается в размещении текста на кривой. Как только криволинейный путь был сглажен, вызов getPath() извлекает серию линейных сегментов, которые заменили кривые исходного пути. Теперь код может использовать эту информацию для размещения отдельных символов вдоль сегментов строки. Функция WidenPath() переопределяет текущий путь в контексте данного устройства как область, которая была бы закрашена, если бы путь был обведен текущим пером. Общая форма функции выглядит следующим образом: Рисование сплошных фигур 681 закрывается с помощью lineTo() в первом и последнем сегментах строки используются заглушки в первом и последнем сегментах строки используется соединение закрывается с помощью CloseFigure() BOOL WidenPath (HDC hdc); Единственным параметром является дескриптор контекста устройства, который содержит допустимый путь. Любые кривые Безье в пути преобразуются в прямые линии. Эта функция имеет значение, является ли текущее перо геометрическим или имеет ширину или более одной единицы устройства. WidenPath() возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя. Это еще одна функция с небольшим количеством документированных применений. Тот факт, что кривые преобразуются в отрезки линий, предполагает, что это можно использовать в операциях подгонки текста, таких как описанная для функции FlattenPath().
Функция SetMiterLimit() задает длину соединений miter для указанного контекста устройства. Общая форма функции выглядит следующим образом: BOOL SetMiterLimit( HDC hdc, // 1 ПЛАВАЮЩИЙ eNewLimit, // 2 ПЛАВАЮЩИЙ peOldLimit // 3 ); Первый параметр - это дескриптор контекста устройства. Второй параметр задает новое ограничение miter. Третий параметр - это указатель на переменную с плавающей запятой, которая содержит предыдущее ограничение miter. Если этот параметр равен NULL, значение предыдущего предела miter не возвращается. Функция возвращает TRUE в случае успешного выполнения и FALSE в случае сбоя. Длина митры - это расстояние от пересечения линейных стенок на внутренней стороне соединения до пересечения линейных стенок на внешней стороне соединения. Предел длины стежка - это отношение длины стежка к ширине линии. На рисунке 24.20 показаны длина скоса, ширина линии и предел скоса. Рисунок 24.20 Длина скоса, ширина линии и ограничение скоса Предел наклона определяет, нарисована ли вершина соединения, которая была определена с помощью стиля PS_JOIN_MITER (см. рис. 23.3 в предыдущей главе), с использованием угла наклона или соединения со скосом. Если предел скоса не превышен, то соединение скошено. В противном случае оно скошено. Скошенные соединения применяются только к ручкам, созданным 682 Глава 24 длина митры длина митры предел длины митры = ширина линии ширина линии с помощью функции ExtCreatePen() и для обводки контуров. Следующий фрагмент кода показывает создание двух объединений. статический жар Ожирение; // Ручка для пера статический ПОПЛАВОК oldMiter; // Хранилище для ограничения длины митры . . // Создайте специальное перо fatPen = ExtCreatePen (PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_ROUND | PS_JOIN_MITER, 15, &fatBrush, 0, NULL); SelectBrush (hdc, GetStockObject (LTGRAY_BRUSH)); SelectPen (hdc, fatPen); // Нарисовать первый угол beginPath (hdc); MoveToEx (hdc, 100, 100, NULL); lineTo (hdc, 250, 100); lineTo (hdc, 150, 180); Конечный путь (hdc); StrokePath (hdc); // Нарисовать второй угол GetMiterLimit (hdc, &oldMiter ); SetMiterLimit (hdc, 2, &oldMiter); Начальный путь (hdc); MoveToEx (hdc, 300, 100, NULL); lineTo (hdc, 450, 100); lineTo (hdc, 350, 180); Конечный путь (hdc);
StrokePath (hdc); SetMiterLimit (hdc, oldMiter, NULL); Рисунок 24.21 представляет собой снимок экрана выполнения предыдущего фрагмента кода . Обратите внимание на рис. 24.21, что изображение слева, на котором по умолчанию используется ограничение 10, нарисовано с помощью соединения miter. На рисунке справа предел скоса был изменен на 1, поэтому рисунок нарисован с использованием соединения скосов. Рисунок 24.21 Эффект функции SetMiterLimit() Рисование сплошных фигур 683 24.6.4 Получение информации о пути Несколько функций предоставляют информацию о пути или о параметрах GDI, которые влияют на путь. Функция getPath() извлекает координаты конечных точек линий и контрольных точек кривых в контуре. Структура функции очень похожа на функцию PolyDraw(), рассмотренную в главе 20. getPath() связана с функцией PolyDraw(), упомянутой ранее. Общая форма функции выглядит следующим образом: int getPath( HDC hdc, // 1 LP-ТОЧКА LP-точки, // 2 Типы LPBYTE lpTypes, // 3

Ошибка производительности программиста связана с тем фактом, что составление компьютерной программы, в отличие от копания канавы или сбора урожая яблок, не является задачей, которую легко разделить на изолированные функции, которые могут выполняться независимо. Различные части компьютерной программы взаимодействуют, часто сложным и труднопрогнозируемым образом. Разделению программы на индивидуально выполняемые задачи должно предшествовать значительное планирование и предусмотрительность. Кроме того, отдельные программисты группы разработчиков должны часто общаться, чтобы гарантировать, что отдельные индивидуально разработанные элементы будут сочетаться так, как ожидалось, и что последствия индивидуальных корректировок и модификаций будут приняты во внимание всеми членами группы. Все это приводит к выводу, что командное программирование подразумевает планирование и структурирование операций, а также взаимодействие между элементами команды. Совокупное заблуждение о качестве связано с некоторыми из тех же взаимодействий, которые ограничивают производительность программиста. Предположим, что в крайнем случае программа операционной системы была разработана десятью программистами. Девять из этих программистов выступили превосходно и безупречно реализовали все возложенные на них функции, в то время как один программист некомпетентен и написал код, который систематически приводит к сбою системы и повреждению сохраненных файлов. В этом случае вполне вероятно, что хорошие возможности этой гипотетической операционной системы, вероятно, останутся незамеченными для пользователя, который столкнется с разрушительным сбоем. Обратите внимание, что эта ситуация отличается от того, что обычно происходит с другими разработанными продуктами. Мы можем представить себе посудомоечную машину, которая не умеет правильно сушить, но другие ее функции продолжают быть полезными. Или автомобиль, который плохо поворачивает, но в остальном работает так, как ожидалось. Компьютерные программы, с другой стороны, часто дают катастрофические сбои. Когда это происходит, пользователю трудно оценить какую-либо остаточную полезность программного продукта. Это приводит к выводу, что вместо кумулятивного эффекта качества производство программного изделия подчиняется правилу минимального качества, которое определяет, что относительно небольшой дефект в программном продукте может ухудшить его удобство использования или, в лучшем случае, снизить его оцененное качество. Утверждение о том, что “неблагодарные смотрят на солнце и видят на нем пятна”, справедливо относится к пользователям программного обеспечения. Также обратите внимание, что правило минимального неправильного качества применяется не только к дефектам, которые приводят к катастрофическим сбоям, но даже к тем, которые не влияют на целостность программы. Например, текстовый процессор работает отлично, за исключением того, что он неправильно расставляет слова через дефис. Эта программа может чрезвычайно хорошо выполнять гораздо более сложные задачи; у нее может быть отличное экранное форматирование, высококачественная проверка орфографии, обширный словарь синонимов и антонимов и многие другие важные функции. Однако очень немногие пользователи рассмотрят возможность использования этого продукта, поскольку текст с неправильным переносом текста обычно недопустим. Наконец, существует маловероятное предположение, что мы можем проектировать программные программы во многом таким же образом, как мы проектируем мосты, здания и автомобильные жилые дома..........., Ошибка инженерных методов является следствием того факта, что программное обеспечение имеет дело не с материальными элементами, подчиняющимися законам физики, а с чисто интеллектуальными творениями. Программа - это скорее мысль, чем вещь. Вещи можно измерять, тестировать, растягивать, закалять, настраивать и трансформировать. Мошенничество724 Глава 26 инженер-строитель может взять образец бетона и измерить его прочность и сопротивляемость . Инженер-механик может определить, будет ли определенный двигатель достаточно мощным для приведения в действие данной машины. Что касается программных компонентов, то эти определения не являются однозначными. На современном этапе развития техники инженер-программист
не может найти определенный алгоритм или структуру данных в справочном руководстве и строго определить, подходит ли он для решаемой задачи. 26.0.1 Программист как художник Дональд Кнут определил в названии своей ставшей классической работы, что программирование - это искусство. Он начинает предисловие словами: “Процесс подготовки программ для цифрового компьютера особенно привлекателен не только потому, что он может быть экономически и научно выгодным, но также потому, что это может быть эстетическим опытом, во многом подобным сочинению стихов или музыки”. ..........." Следовательно, разумно сделать вывод, что любая попытка свести искусство программирования к следованию строгому и научно определенному набору правил, скорее всего, обречена на провал. Результат похож на сравнение холста талантливого художника с холстом, созданным с помощью игрушки "раскрашивай по номерам". Без таланта работа программиста будет состоять из скучного пересказа одних и тех же алгоритмов и подпрограмм, которые напечатаны во всех распространенных учебниках. С искусства и код оживает с вообраонных и изобретательные творения, которые делают программу красивая концепция. Тот факт, что компьютерное программирование - это искусство и что талантливые программисты в большей степени художники, чем техники, не исключает использования инженерных и научных принципов в развитии этого искусства. Разработка программного обеспечения - это не попытка свести программирование к механическому процессу, а изучение тех элементов программирования, к которым можно подойти технически, и механизмов, которые можно использовать, чтобы сделать программирование менее сложным. Однако мы всегда должны помнить, что техника, какой бы утонченной она ни была, никогда не сможет превратить художника в ремесленника. 26.1 Характеристики программного обеспечения С инженерной точки зрения программная система - это продукт, выполняющий определенную функцию. Однако одно уникальное свойство сильно отличает компьютерную программу от моста или самолета: программу можно изменять. Такая гибкость программного обеспечения является одновременно преимуществом и опасностью. Преимущество, поскольку часто исправить ошибку в программе гораздо проще, чем было бы исправить дефект в самолете или автомобильной желчи. Опасность, поскольку модификация в программе может привести к непредвиденным побочным эффектам, которые могут ухудшить функциональность тех компонентов, которые правильно выполнялись до изменения. Другая примечательная характеристика программ связана с типом ресурсов, необходимых для их создания. Программный продукт, по сути, является интеллектуальным товаром. Основным ресурсом, необходимым для ее создания, является человеческий интеллект. Фактическое производство программ простое и недорогое по сравнению с их проектированием, кодированием, тестированием и документированием. Это контрастирует со многими другими разработанными продуктами Основы системного проектирования 725 в котором ресурсы, используемые при его производстве, составляют существенную часть конечной стоимости продукта. Например, значительная часть цены нового автомобиля представляет собой затраты на его производство, в то время как менее значительная часть идет на оплату инженерных затрат на проектирование и разработку. В случае типичной компьютерной программы пропорции обратные. Наиболее важным элементом стоимости продукта являются человеческие усилия при проектировании и разработке, в то время как затраты на производство пропорционально незначительны. 26.1.1 Качества программного обеспечения Спроектированный продукт обычно ассоциируется со списком качеств, которые определяют его пригодность для использования. Например, при выполнении своих функций мост выдерживает заданный вес и выдерживает заданную силу ветра. Самолет способен перевозить определенный груз с определенной скоростью и высотой. По тому же принципу программный продукт ассоциируется с заданным набором качеств, которые определяют его функциональность. Основными целями разработки программного обеспечения является определение, конкретизация и измерение качеств программного обеспечения и описание принципов, которые могут быть применены для их достижения. Классификация качеств программного обеспечения может основываться на отношении к
программному продукту. В этом смысле мы можем говорить о качествах, желательных для пользователя, разработчика или менеджера. В таблице 26.1 перечислены некоторые качества в соответствии с этой классификацией. Таблица 26.1 Качества программного обеспечения ПОЛЬЗОВАТЕЛЬ Разработчик МЕНЕДЖЕР надежный проверяемый производительный простой в использовании ремонтопригодный управляемый эффективный расширяемый прибыльный портативный В этом смысле мы также можем говорить о качествах программного обеспечения, внутренних и внешних по отношению к продукту. Внутренние из них видны разработчикам и менеджерам, в то время как внешние видны пользователю. Легко видеть, что надежность с точки зрения пользователя подразумевает проверяемость с точки зрения разработчика. С другой стороны, это различие часто четко не определено. Ниже приведены некоторые из наиболее важных качеств, обычно ассоциируемых с программными продуктами. Корректность Термин, иногда неправильно используемый как синоним надежности или робастности, относится к тому факту, что программа ведет себя так, как ожидалось. Более технически мы можем сказать, что программа корректна, если она ведет себя в соответствии со своими функциональными спецификациями ( описанными позже в этой главе). Корректность может быть проверена экспериментально (путем тестирования) или аналитически. Некоторые программные инструменты и практики имеют тенденцию повышать корректность, в то время как другие имеют тенденцию снижать ее. Надежность Относится к надежности программы. Более формально надежность можно определить как статистическую вероятность того, что программа продолжит выполнять 726 Глава 26 как и ожидалось в течение определенного периода времени. Мы можем сказать, что надежность - это относительная мера корректности. Незрелость программной инженерии как технической дисциплины становится очевидной из-за того факта, что мы ожидаем дефектов в программных продуктах. Не испытывая особого смущения , разработчики часто выпускают программы, сопровождаемые списками известных ошибок. Кто стал бы покупать автомобиль, поставляемый со списком известных дефектов? Более того, программы часто продаются с отказами от ответственности, которые пытаются снять с производителя какую-либо ответственность за дефекты программы. В предупреждениях говорится, что разрыв термоусадочной упаковки отменяет любую ответственность со стороны производителя. Разработка программного обеспечения не станет технической областью до тех пор, пока мы не будем готовы взять на себя ответственность за наши продукты. Мы должны нести ответственность за правильность продуктов, которые, как мы утверждали, были технически спроектированы. Надежность Это качество программного обеспечения пытается измерить поведение программы в обстоятельствах, которые превышают условия формальных требований. Другими словами, это показатель реакции программы на неожиданные обстоятельства. Например, математическая программа предполагает, что входные значения находятся в допустимых диапазонах, определенных для операций. Пользователь не должен вводить отрицательное значение в функцию квадратного корня, поскольку эта операция не определена. Более надежная программа исправит эту ошибку, отправив сообщение об ошибке sage и запросив новый ввод, в то время как менее надежная программа может выйти из строя.
Хотя надежность и корректность являются взаимосвязанными качествами, они часто довольно различны. Поскольку корректность является мерой соответствия формальным спецификациям, математическая программа, которая выходит из строя при недопустимом входном значении, все еще может быть правильной , однако она не будет надежной. Очевидно, что надежность - это качество, которое трудно измерить и определить. Надежность также связана с надежностью, поскольку надежность обычно повышает надежность. Эффективность Это качество является мерой того, насколько экономно система использует доступные ресурсы. Эффективная система - это та, которая хорошо использует эти ресурсы. В разработке программного обеспечения эффективность приравнивается к производительности, поэтому термины считаются эквивалентными. Медленное приложение или приложение, занимающее слишком много места на диске, снижает производительность пользователя и увеличивает эксплуатационные расходы. Часто оценить эффективность программного обеспечения сложно и дорого. Обычные методы основаны на измерении, анализе и моделировании. Измерение состоит из определения времени выполнения программы или ее части. Иногда используется секундомер, но более разумный подход состоит из трех процедур синхронизации: одна запускает промежуточный конечный таймер, другая останавливает его, а третья отображает время, прошедшее между вызовами запуска и остановки таймера. В этом случае мы позволяем компьютеру измерять время выполнения между двумя точками кода - операция, которая довольно сложна при использовании внешнего устройства синхронизации. Обратите внимание, что для этого метода требуется доступ к исходному коду. Благоприятный результат измерения времени выполнения заключается в выявлении возможных узких мест обработки. В этом случае время выполнения разных разделов программы измеряется отдельно, используя постепенно уменьшающуюся сетку, до тех пор, пока код не будет готов Основы системной инженерии 727 раздел, выполнение которого занимает больше всего времени, изолирован. Одна из проблем с измерениями времени выполнения как способа определения эффективности заключается в том, что значения часто бессмысленны без точки отсчета. Однако при сравнении двух или более процедур обработки ни один другой метод не является столь простым и эффективным. Аналитический метод заключается в определении эффективности путем анализа сложности алгоритмов, используемых кодом. Область анализа алгоритмов определяет теории, которые определяют наихудшее, наилучшее и среднее поведение в случае с точки зрения использования критических ресурсов, таких как время и пространство. Но анализ алгоритмов часто слишком затратен для использования в небольшом проекте разработки приложений. Метод моделирования для определения эффективности основан на разработке моделей, которые эмулируют продукт, чтобы мы могли анализировать и количественно оценивать его поведение. Хотя этот метод иногда может быть полезен при определении эффективности приложений среднего размера, чаще всего его стоимость была бы непомерно высокой. Проверяемость Программный продукт поддается проверке, если его свойства могут быть легко установлены с помощью аналитических методов или тестирования. Часто проверяемость - это внутреннее качество, представляющее интерес главным образом для разработчиков. В других случаях пользователю необходимо убедиться, что программное обеспечение работает должным образом, как и в случае с безопасностью программы. Некоторые методы программирования способствуют проверяемости, в то время как другие - нет, как часто будет показано в этой книге. Ремонтопригодность Программисты довольно рано в своей карьере обнаруживают, что программный продукт никогда не является законченным: законченная программа - это оксюморон. Программы развиваются на протяжении всего своего жизненного цикла благодаря изменениям, исправляющим вновь обнаруженные дефекты, или модификациям, которые дополняют новые функциональные возможности по мере их возникновения. В этом смысле мы называем обслуживание программного обеспечения операцией по поддержанию программы. Чем проще
поддерживать программу, тем выше ее ремонтопригодность. Если модификация относится к исправлению дефекта программы, мы говорим о возможности ремонта. Если это относится к реализации новой функции, мы говорим о возможности развития. Удобство для пользователя Это, пожалуй, наименее ощутимое свойство программного обеспечения, поскольку оно относится к измерению удобства использования человеком. Основная проблема заключается в том, что разные пользователи могут рассматривать одну и ту же функцию программы как имеющую разную степень удобства. В этом смысле пользователь, привыкший к устройству мыши, может посчитать, что программа, использующая этот интерфейс, удобна, в то время как пользователь, непривычный к мыши, может посчитать программу не дружественной. Пользовательский интерфейс часто считается наиболее важным элементом удобства использования программы . Но и здесь предпочтения пользователя часто варьируются в зависимости от предыдущего уровня знаний. Также обратите внимание, что несколько других атрибутов влияют на удобство использования программы . Например, программа с низким уровнем корректности и надежности, которая плохо работает, вряд ли может рассматриваться как пользовательская 728 Глава 26 дружелюбный. Мы вернемся к теме удобства использования при более позднем обсуждении вопросов, связанных с человеческими инженерами. ........... Возможность повторного использования Зрелость инженерной области характеризуется степенью возможности повторного использования. В этом смысле тот же электродвигатель, который используется для питания стиральной машины, может также использоваться в печном вентиляторе и возвратно-поступательной пиле по дереву. Однако обратите внимание, что в случае электродвигателя возможно повторное использование благодаря высокой степени стандартизации. Например, в наличии имеются различные типы ремней и шкивов для адаптации двигателя к различным устройствам. Кроме того, электрическая мощность стандартизирована таким образом, что двигатель генерирует одинаковую мощность и обороты в минуту в различных приложениях. Возможно, наиболее важной причиной поощрения возможности повторного использования программного обеспечения является то, что оно снижает стоимость производства. С другой стороны, возможности повторного использования программных компонентов часто трудно достичь из-за отсутствия стандартизации. Например, поисковая система, разработанная для текстового процессора, может быть недоступна для непосредственного использования в другом из-за различий в структурах данных, используемых для хранения и форматирования текстового файла. Или математическая процедура, которая вычисляет квадратный корень, может быть не способна повторно использоваться в другой программе из-за различий в требуемой точности или в числовых форматах данных . Другой интересной особенностью повторного использования является то, что оно обычно вводится во время отмены подписи. Многократно используемые программные компоненты должны быть спроектированы таким образом, ввиду reusabilность в качестве запоздалой мысли редко бывает эффективным. Другое соображение заключается в том, что определенные методологии программирования, такие как объектная ориентация, способствуют повторному использованию с помощью определенных механизмов и техник. На протяжении всей этой книги мы обсуждаем возможность повторного использования, поскольку она связана со многими аспектами производства программного обеспечения. Переносимость Этот термин связан со словом "порт", которое обозначает подключение компьютера к внешнему миру. Программное обеспечение считается переносимым, если оно может быть передано через порт, обычно на другой компьютер. В более общем плане, программа или ее часть является переносимой, если она может выполняться в различных аппаратных и программных средах. Переносимость часто является важной
экономической проблемой, поскольку программы часто необходимо переносить на другие компьютеры и программные среды. Одним из больших преимуществ разработки приложений на языках высокого уровня является их большая переносимость. Программа, которая не использует специфичных для компьютера функций, написанная полностью на C ++ на ПК, вероятно, может быть преобразована для запуска на компьютере Macintosh без особых усилий. С другой стороны, разработчикам программ часто приходится использовать низкоуровневые функции компьютера, связанные либо с конкретными аппаратными, либо с программными свойствами , для достижения определенной функциональности своих продуктов. Это решение часто влечет за собой значительные потери в переносимости. Другие свойства Многие другие свойства программного обеспечения часто упоминаются в учебниках по программной инженерии, среди них своевременность, наглядность, производительность, интероперабельность и понятность. В особых обстоятельствах эти и другие свойства могут проявляться Основы системной инженерии 729 особая важность. Тем не менее, в общей разработке приложений те, которые конкретно перечислены, кажутся наиболее значимыми. 26.1.2 Показатели качества Одной из самых больших проблем разработки программного обеспечения является измерение атрибутов программного обеспечения. Относительно легко заявить, что программа должна быть надежной и благонадежной, и совсем другое дело измерить ее благонадежность в некоторых заранее определенных единицах измерения. Если у нас нет надежного способа измерения определенного качества, будет трудно определить, достигнуто ли оно в конкретном случае или в какой степени оно присутствует . Более того, чтобы точно измерить качество, мы должны сначала уметь точно определять его, что не всегда является легкой задачей. В большинстве инженерных областей разработаны стандартные показатели для измерения качества продукции. uct. Например, мы можем сравнить качество двух автомобильных аккумуляторов с помощью усилителей холодного проворачивания, которые они способны обеспечить. С другой стороны, неинженерным продуктам, как правило, не хватает показателей качества. В этом смысле мы не можем определить по этикетке на видеокассете, какова развлекательная ценность фильма, который она содержит, равно как и единицы информации, указанные на обложке технической книги. Программное обеспечение также является областью, в которой нет общепринятых показателей качества, хотя в этом направлении ведется значительная работа . Проверка корректности программы, обсуждаемая далее в книге, напрямую связана с показателями качества программного обеспечения. 26.2 Принципы разработки программного обеспечения Мы начали эту главу, исходя из предположения, что разработка программного обеспечения - это творческая деятельность и что программирование не является точной наукой. С этой точки зрения даже термин разработка программного обеспечения может считаться неподходящим, поскольку мы могли бы предпочтительнее умело говорить о технике разработки программного обеспечения, термин которой не подразумевает строгости формального инженерного подхода. По нашему мнению, ошибочно предполагать, что программы граммы могут быть механически сгенерированы с помощью какой-либо механической методологии, какой бы сложной она ни была . Когда разработка программного обеспечения не дает ожидаемых результатов, это происходит потому, что мы переоцениваем научные и технические аспекты разработки программ по сравнению с теми, которые носят художественный или эстетический характер или которые зависят от таланта, личных способностей или ноу-хау. Тем не менее, как в искусстве есть техника, так и в разработке программ есть техника. Программная инженерия - это условное название, объединяющее технические и научные аспекты разработки программ. Разработка программного обеспечения. Небольшие программные проекты обычно выполняются в рамках ограниченного бюджета. Часто финансовых ресурсов не хватает для найма обученных менеджеров программных проектов или специалистов в области разработки программного обеспечения. Человек, ответственный за проект, обычно носит много головных уборов, включая менеджера проекта и
инженера-программиста. На самом деле, нет ничего необычного в том, что менеджер проекта / инженер также работает неполный рабочий день дизайнером, программистом, тестировщиком и специалистом по документации. Все это означает, что формальность и строгость, используемые при разработке крупного проекта, могут не применяться к проекту меньших масштабов. Другими словами, строгость и ри730 Глава 26 гибкость принципов разработки программного обеспечения, возможно, придется сократить, чтобы соответствовать более мелким проектам. В этом смысле мы должны различать принципы, техники и инструменты разработки программного обеспечения. Принципы - это общие рекомендации, которые применимы на любой стадии процесса создания программы. Это абстрактные утверждения, которые описывают желательные свойства, но которые мало полезны при практической разработке программного обеспечения . Например, принцип, который поощряет высокую надежность программы, не говорит нам, как сделать программу надежной. Техники или способы относятся к определенному подходу к решению проблемы и помогают гарантировать, что продукт будет обладать желаемыми свойствами. Инструменты - это конкретные ресурсы, которые используются при внедрении определенной техники. В этом случае мы можем сформулировать в качестве принципа, что числа с плавающей запятой являются желательным форматом для представления десятичных дробей в цифровой машине. Также следует отметить, что методы с плавающей точкой, описанные в стандарте ANSI 754, подходят для нашего применения и им следует следовать. Наконец, что конкретная библиотека подпрограмм с плавающей запятой, которая соответствует стандарту ANSI 754, была бы адекватным инструментом для реализации математических функций, требуемых в нашем приложении. Рисунок 26.1 графически показывает взаимосвязь между этими тремя элементами. Рисунок 26.1 Взаимосвязь между принципами, техниками и инструментами 26.2.1 Строгость Одним из недостатков рассмотрения программы как вида искусства является то, что она делает упор фазис на вдохновение, а не на точность. Но программирование - это скорее прикладное, чем чистое искусство. Мы можем счесть очаровательным, что Микеланджело задумал свою статую Давида довольно небрежно и в итоге получил недостаточно мрамора для скульптуры ступней. Но клиент может быть не готов простить программиста с художественным складом ума, который не нашел вдохновения для внедрения расстановки переносов при разработке программы обработки word . Мы должны заключить, что тот факт, что программирование часто является творческой деятельностью, не освобождает нас от необходимости заниматься проектированием и разработкой программ с необходимой строгостью. Основы системной инженерии 731 принципы методы инструменты Некоторые авторы различают степени строгости. Высшая степень, называемая формальностью, требует, чтобы разработка производилась строго в соответствии с законами математики и логики. В этом смысле формальность - это высокая степень строгости. Одна из областей, в которой вопросы строгости и формальности приобретают особое значение, - это спецификации программного обеспечения . Был разработан логический формализм, который позволяет точно определять программное обеспечение. Математические выражения, обычно основанные на исчислении предикатов, позволяют представлять полные программы или фрагменты программ в символических выражениях, которыми можно манипулировать в соответствии с математическими законами. Некоторые из преимуществ этой методологии, заявленных сторонниками информационных технологий, заключаются в следующем: 1. Формальные методы позволяют инженеру-программисту определять, разрабатывать и проверять программное обеспечение программную систему, используя язык формальной спецификации, чтобы ее корректность могла быть
систематически оценена. 2. Они обеспечивают механизм устранения неоднозначности и непоследовательности программы с помощью математического анализа обнаруживаются ошибки, которые в противном случае могли бы остаться незамеченными. 3. Они позволяют выразить проблему программного обеспечения в терминах алгебраического вывода, таким образом, заставляя спецификатор мыслить в более строгих терминах. 4. Эти методы в конечном итоге сделают возможной механизацию программирования тем самым произведя революцию в разработке программного обеспечения. С другой стороны, у противников формальных методов спецификации также есть свои собственные контраргументы: 1. Формальные спецификации трудно выучить. Не требуется ничего, кроме курса для выпускников для получения даже поверхностного представления об этом предмете. 2. До сих пор эти методы не были успешно использованы при практической разработке программ улучшение. 3. Большинство программистов, даже большинство инженеров-программистов, работающих сегодня, незнакомы с официальными спецификациями. На этом этапе мы могли бы заявить, что, поскольку целью нашей книги является разработка программы меньшего размера, мы удовлетворимся строгостью и исключим формальные методологии из ее содержания.......... Но дело в том, что некоторые более мелкие программы, которые имеют дело с определенной тематикой, могут (возможно, должны) быть указаны формально. Поэтому мы исключаем рассмотрение формальных методов спецификации, основанных исключительно на целесообразности. Наше оправдание заключается в том, что это довольно запутанный предмет, и что большая часть программ, разрабатываемых сегодня, по-прежнему осуществляется без использования формальных методов спецификаций. Поэтому мы соглашаемся на самом низком уровне строгости, который мы определяем как сподвижниковлогии для программы развития на основе следующей последовательности четко определенных и точно сказано шагов. На этапе разработки программирования строгость неизбежна . Фактически программирование выполняется в формальном контексте с четко определенным синтаксисом и семантикой. Но строгость также должна применяться на каждом этапе процесса разработки программы, включая проектирование программы, спецификацию и верификацию. Программа , разработанная в соответствии со строгой методологией, должна содержать 732 Глава 26 желательные качества, упомянутые ранее. Другими словами, результаты должны быть надежными , понятными, поддающимися проверке, сопровождению и многократному использованию. 26.2.2 Разделение интересов Здравый смысл подсказывает, что при решении сложных проблем мы должны отдельно рассматривать их различные аспекты. Поскольку разработка программного обеспечения по своей сути является сложным видом деятельности, разделение задач становится практической необходимостью. Поверхностное наблюдение за любым строительным проектом сразу выявляет три проблемы или уровня деятельности: техническую , управленческую и финансовую. Технические проблемы связаны с технологической и научной частью проекта. Управленческие проблемы относятся к администрированию проекта. Финансовые проблемы связаны с денежно-кредитной и фискальной деятельностью. Пример демонстрирует это понятие разделения проблем на технические, управленческие и финансовые. Предположим, домовладельцы рассматривают возможность строительства нового гаража. Проект можно проанализировать, рассмотрев три отдельных действия, необходимых для его завершения. Технически, домовладельцы должны определить размер, тип и технические характеристики гаража, который будет построен, и его расположение на территории собственности. Более тонкий уровень технических деталей мог бы включать определение подготовительного основания, бетонных работ, необходимых для фундамента, количества и типа дверей и окон, сайдинга, электромонтажа, кровли, а также других параметров строительства. Управленческие аспекты проекта могут включать в себя контроль за работой различных субподрядчиков, закупку материалов, получение необходимых разрешений на строительство и своевременное выделение средств. Финансовая деятельность включает в себя оценку того, насколько проект увеличит стоимость недвижимости, отбор наиболее привлекательных предложений и получение кредита на строительство.
Важно понимать, что разделение задач - это удобство для просмотра различных аспектов проекта. Это аналитический инструмент, который не обязательно подразумевает разделение полномочий по принятию решений. В предыдущем примере домовладельцы могут решить сами осуществлять финансовую деятельность, в то время как они делегируют управленческую деятельность генеральному подрядчику, который, в свою очередь, оставляет технические строительные детали субподрядчикам. Тем не менее, основным правилом капитализма является то, что приказы отдает тот, кто оплачивает счета. Следовательно, домовладельцы сохранят за собой высший уровень власти. Они будут иметь право запрашивать управленческую или даже техническую информацию у генерального подрядчика, которого они могут заменить по своему усмотрению или даже уволить, если сочтут это необходимым. Точно так же участники проекта по разработке программного обеспечения должны всегда осознавать тот факт, что разделение интересов не подразумевает какого-либо постоянного делегирования полномочий. Тот факт, что разработчику программного обеспечения поручено отменить штрафы и конкретизировать программу, не означает, что тот, кто отвечает, не может вмешаться на любом этапе разработки проекта и переопределить разработчика. В этом отношении следует избегать двух крайностей. В одном случае участники проекта упускают из виду, где находится конечная власть, и вводятся в заблуждение предположением, что она была полностью и надолго делегирована в их пользу. Это неправильное представление о ten порождает отношение “бери или не бери" со стороны технического персонала проекта, которые, кажется, заявляют: "это то, что лучше всего, а несогласие только доказывает ваше техническое невежество”. Другая крайность возникает, когда авторитет настолько велик, что Основы системного проектирования 733 участники боятся демонстрировать свои знания или экспертизу из-за страха быть высмеянными или отвергнутыми. Задача тех, кто отвечает за проект, состоит в том, чтобы донести до своих подчиненных информацию о том, где находится конечная власть, и о том факте, что эта власть будет использована при необходимости, возможно, даже в форме, которая может показаться произвольной. Кроме того, каковы определенные уровни принятия решений и границы, в пределах которых каждый участник может свободно проявлять творческий подход. Это первоначальное определение различных уровней озабоченности, ответственности и полномочий является наиболее важным элементом успеха проекта разработки программного обеспечения. Мы видели, что понятие разделения забот связано с разделением обязанностей, более того, оно связано со специализацией труда. На более грубом уровне это определяет разделение деятельности на финансовую, управленческую и техническую. Каждая из групп может быть дополнительно подразделена. Например, технический персонал можно разделить на специалистов по разработке программ, кодированию и тестированию. Персонал, занимающийся программированием, может дополнительно специализироваться на языках низкого и высокого уровня , а также на языках высокого уровня в области программирования C ++, Pascal и Cobolmers. Точно так же разработчики программ могут быть специалистами в различных областях, как и менеджеры и финансовые эксперты. 26.2.3 Модульность Модульность - это универсальный механизм снижения сложности, который выходит за рамки языков программирования или сред. Модули должны быть концептуализированы как единицы программного деления, но не обязательно приравниваться к подпрограммам, подпрограммам или дисковым файлам. Для небольших проектов по разработке программного обеспечения модульность является наиболее эффективным организационным инструментом. Обычно выделяют два метода модуляции: один, называемый "сверху вниз", разлагает проблему на подзадачи, которые могут решаться независимо; другой, называемый "снизу вверх", создает систему, начиная с элементарных компонентов. В этом же контексте часто утверждается, что главное преимущество модульности заключается в том, что она делает систему понятной. Однако эта понятность обычно достигается поэтапно в ходе разработки программы. Модульность обычно происходит постепенно. Поскольку часто непрактично ждать, пока система будет полностью понята, чтобы приступить к ее разработке, наиболее практичным подходом является определение начального модульного разделения и постепенное уточнение его по мере продвижения проекта и более глубокого ознакомления участников с его особенностями.
Особенности. Термин модульная когезия относится к взаимосвязи между элементами модуля. Например, говорят, что модуль, в котором процедуры обработки, и структуры данных тесно связаны, обладает высокой связностью. Подключение к модулю какой-либо несвязанной функции только потому, что мы не можем найти для нее лучшего места , снижает сплоченность модуля. Обычно предпочтительнее выделить универсальный модуль, который мы можем несколько эвфемистически назвать модулем общей поддержки ule, в котором мы можем временно разместить те функции или структуры данных, для которых идеальное расположение не сразу бросается в глаза. Позже в разработке мы 734 Глава 26 может пересмотреть модульность программы и переместить элементы из универсального в другие модули. Термин модульная связь относится к взаимосвязи между модулями в одной и той же программе. Например, если один модуль сильно зависит от процедур или данных в другом модуле, то говорят, что модули тесно связаны. Модули , которые имеют тесную связь, трудно понимать, анализировать и повторно использовать по отдельности, поэтому тесная связь обычно считается нежелательным свойством. С другой стороны, все модули в программе должны быть каким-то образом связаны, поскольку они должны взаимодействовать при создании общей функциональности. Трудно представить модуль, который полностью отсоединен от системы и все еще выполняет какую-то связанную функцию. В качестве общего принципа часто утверждается, что хорошая модульная конструкция должна обладать высокой когезией и слабым сцеплением. Элементы должны быть тесно связаны внутри модуля и должна быть минимальная межмодульная зависимость. 26.2.4 Абстракция и сокрытие информации Уникальным свойством человеческого разума является то, что он может уменьшить сложность проблемы, сосредоточившись на определенном подмножестве ее характеристик, игнорируя при этом некоторые из недостатков. Например, мы можем изобразить автомобиль по его составным частям: двигателю, транспортному назначению, шасси, кузову и колесам. Эта функциональная абстракция, которая основана на задачах, выполняемых каждым компонентом, игнорирует детали определенных частей, которые включены в абстракцию. Например, абстракция может предполагать, что крышку багажника не нужно упоминать конкретно, поскольку она является частью кузова, или стартер, поскольку он является частью двигателя. Поскольку абстракция - это конкретное представление реальности, абстракций одного и того же объекта может быть много. Предыдущее описание автомобиля с разделением на двигатель, трансмиссию, шасси, кузов и колеса может быть полезно для общего описания. Однако предприятие по мойке автомобилей может не интересоваться компонентами, с которыми ему не приходится иметь дело. Поэтому проект автомойки может определить, что абстракция автомобиля, состоящая из капота, багажника, крыльев, крыши, лобового стекла и стекол, более полезна для его целей. Что касается программного обеспечения, мы часто обнаруживаем, что абстракция, которая могла бы быть полезной при описании системы пользователю, может оказаться не самой подходящей для разработчиков. Точно так же те, на кого возложена задача тестирования программной системы, могут предпочесть абстракцию, отличную от той, которая принята пользователями, дизайнерами или программистами. Поскольку абстракция помогает определить сущности, составляющие программный продукт, это инструмент модуляции. Мы модулируем с помощью абстракции. Сокрытие информации и инкапсуляция - это термины, появившиеся в 1970-х годах в связи с объектно-ориентированным проектированием и программированием. Идея была предложена Дэвидом Парнасом, который утверждает, что каждый программный компонент должен инкапсулировать или скрывать проектное решение таким образом, чтобы оно как можно меньше раскрывало его внутреннюю работу. Другими словами, скрытие информации является одним из наиболее желательных свойств абстракции. По мнению Коада и Юордона, его главная польза заключается в том, что он локализует волатильность, тем самым облегчая изменения, которые с меньшей вероятностью будут иметь побочные эффекты. Понятия инкапсуляции и в- Основы системной инженерии 735 скрытие формации будет повторно рассмотрено в главах, связанных с объектно-ориентированным анализом и проектированием. 26.2.5 Гибкость и ожидание изменений Программу следует рассматривать не как готовый продукт, а как этап в
нескончаемом процессе разработки. Мы можем говорить о программе как об удовлетворяющей определенному набору требований, как о подходящей для данной цели или определенной версии, готовой для выхода на рынок, но для разработчика программного обеспечения понятие “готовая программа” является оксюмороном. Программа была закончена, мы должны предположить, что это не имеет никакого оставшиеся дефекты, что все текущие или будущие потребности пользователей вполне Сатисда, и что никаких улучшений или изменений не будет необходимости. Поскольку ни одно из этих условий никогда не может быть доказано, программа никогда не может считаться законченной. Все это вопрос семантики, но постоянно меняющаяся природа программного обеспечения делает предпочтительным, чтобы мы рассматривали программу скорее как категорию, чем как продукт. В этом смысле мы можем сказать, что программа WordMaster состоит из различных реализаций, названных WordMaster 1.0, WordMaster 2.1 и WordMaster Deluxe. В том же смысле мы говорим об автомобильной категории под названием Chevrolet, которая включает модели ‘56 Corvette, ‘76 Универсал и ‘94 пикап. Тот факт, что программные продукты фундаментально хрупки, подтверждает предыдущее мнение. Зная это, мы должны разрабатывать программное обеспечение таким образом, чтобы оно было гибким, что означает , что мы должны предвидеть изменения. Причиной изменений являются две возможные причины: исправление программы и эволюция. Это действительно наивный разработчик программного обеспечения, который не знает, что дефекты программы будут обнаружены после ее выпуска, или который не может предвидеть, что будут выявлены новые требования и изменятся старые. Возможно, именно эта врожденная хрупкость отличает программное обеспечение от многих других промышленных продуктов. Две желательные характеристики программы - ремонтопригодность и эволюционируемость, о которых упоминалось ранее, - тесно связаны с гибкостью программы и ожиданием изменений. Также возможность повторного использования, поскольку программный компонент является многоразовым, если его можно легко адаптировать для выполнения другой функции или для выполнения в новом контексте. Это УсуЭлли требует, чтобы этот компонент был податлив, что, в свою очередь, подразумевает, что Оригинал дизайнеры ожидаемых перемен. 26.2.6 Максимальное обобщение Чем более обобщен программный продукт, тем шире аудитория, на которую он рассчитан. Чем более обобщена программная процедура, тем больше ее возможностей для повторного использования. Это подразумевает удобство максимального обобщения на уровне дизайна приложения и отдельных подпрограмм, процедур или модулей. В этих случаях подход максимального обобщения основан на попытке обнаружить более общую проблему, эквивалентную представленной, или более общее решение. На уровне разработки продукта принцип максимального обобщения предполагает, что мы ищем более общие проблемы, скрытые за текущей. Например, приложение для обработки изображений DOS требуется выполнить в 736 Глава 26 графический режим в реальном цвете. Первоначальный подход может включать в применения низкоуровневые драйверы для крепления VESA полноцветный режим. Однако тщательный анализ проблемы показывает, что более общим подходом была бы разработка библиотеки вспомогательных функций для нескольких режимов VESA true-color. Библиотека могла бы использоваться разрабатываемым приложением , а также предоставляться в виде коммерческого инструментария. В этом случае большие усилия по разработке, необходимые для создания библиотеки, могут оказаться вполне оправданными . 26.2.7 Постепенное развитие Лепка - это творческая деятельность, которая напоминает программирование. Как бы скульптор подошел к созданию реалистичной мраморной статуи Франклина Д. Рузвельта? Одним из возможных методов было бы начать с верха и попытаться вырезать и закончить шляпу, затем голову, затем туловище, затем руки и так далее, пока не дойдут до обуви, после чего статуя будет закончена. Но этот метод не очень разумен, поскольку он предполагает, что каждая часть анатомии Рузвельта будет доведена до мельчайших деталей до того, как начнется следующая . Настоящий скульптор, вероятно, подошел бы к проблеме с помощью прогрессивных подходов . Он или она начали бы с создания рисунка того, как должна выглядеть конечная статуя
. Затем основные следы рисунка были бы перенесены на мраморный блок в порядке , чтобы определить, какие части блока можно было бы грубо отколоть. Процесс удаления ненужного материала будет постепенно продолжаться на всей фигуре , что приведет к приближению конечного результата по этапам, а не по частям. Неопытный скульптор может допустить ошибку двумя способами: у него или у нее может возникнуть соблазн пропустить стадию рисования (программный дизайн) и перейти непосредственно к вырезанию (кодирование ), или же он может посвятить слишком много времени деталям рисунка и без необходимости отложить вырезание . С другой стороны, опытный художник знает, что рисунок - это основное руководство, но что более мелкие детали статуи появляются в процессе творчества.. Если на рисунке изображен Рузвельт с руками в карманах, позже скульптор не сможет передумать и изобразить президента, машущего толпе, поскольку необходимый материал больше не будет доступен. Поэтому общая форма и поза Рузвельта должны быть определены до начала вырезания. Однако точный размер владельца сигареты rette, выражение его лица или текстура его пальто - это детали, которые могут быть определены на более позднем этапе. Что касается программного обеспечения, поэтапная разработка иногда состоит из создания подмножеств продуктов, которые могут быть доставлены заказчику для ранней оценки и обратной связи. На самом деле, очень немногие программные проекты могут быть точно определены в начальной точке. Поэтому часто бывает разумно разделить спецификацию программы на последовательные этапы, возможно, связывая каждый этап с тестовой версией конечного продукта. Одним из преимуществ этого метода является то, что клиент может изучить и оценить несколько прототипов типов. Однако итеративный метод разработки создает новые управленческие и организационные проблемы из-за того, что сам проект определяется “на лету”. " Один из них вопрос, на который следует обратить особое внимание, - это управление документацией для каждой из различных стадий разработки, а также для соответствующих прототипов. Плохо управляемый проект поэтапной разработки может легко превратиться в беспорядок и анархию. Основы системной инженерии 737 26.3 Парадигмы программной инженерии Некоторые авторы в области разработки программного обеспечения, включая Прессмана, говорят о серии шагов в разработке программ, которые включают использование определенных методов, инструментов и политики . В программной инженерии термин методы относится к техническим процедурам, используемым при разработке проекта. Она включает в себя планирование проекта, анализ систем и требований, проектирование структуры данных, выбор и оценку алгоритмов, кодирование, оценку корректности программы и процедуры обслуживания. ............. в ............ Инструменты разработки программного обеспечения - это программные помощники при разработке программ. Эти инструменты, обычно называемые автоматизированной разработкой программного обеспечения, или CASE tools, создают среду разработки, которая стремится автоматизировать разработку и обеспечивает поддержку методов. Политики (иногда называемые процедурами) объединяют методы и инструменты в практическую методологию разработки программ. Например, политика, принятая для конкретного проекта, определяет методы, которые будут применяться, результаты, которые будут получены в процессе разработки, средства контроля, которые помогут гарантировать качество конечного продукта, и различные контрольные точки в графике разработки. Определенная серия шагов (методы, инструменты и политики) порождает определенные парадигмы. Три из этих парадигм широко обсуждались в литературе : водопадная модель, методы-прототипы и модель спирального развития . Четвертый метод, иногда называемый методом четвертого поколения, основан на использовании непроцедурных языков и специализированных программных средств. Из-за ее нетрадиционности мы не будем обсуждать методы четвертого поколения, хотя читателю следует отметить, что многие авторы предвидят многообещающее будущее для этой
методологии разработки. 26.3.1 Модель водопада Эта классическая модель проекта разработки программного обеспечения основана на понятии жизненного цикла системы. Хотя название "водопадная модель" появилось довольно недавно, как и понятие парадигмы жизненного цикла, понятие потока действий по разработке можно найти в ранней литературе по программной инженерии. Таусворт рассказывает о потоке действий при разработке сверху вниз и предоставляет график, на котором одно действие следует за другим, подобно водопаду. Рисунок 26.2 представляет классическую модель водопада для программного проекта. Этап спецификации жизненного цикла системы состоит из процесса сбора требований посредством анализа и системного проектирования. Всякий раз, когда проект должен взаимодействовать с существующими программными или аппаратными элементами, этап спецификации должен включать определение системных требований. На этом этапе заказчик и разработчик работают в тесном контакте: заказчик выдвигает требования, а разработчик отражает эти требования в формальной спецификации, которая, в свою очередь, проверяется заказчиком..........., Циклы требований / спецификаций продолжаются до тех пор, пока обе стороны не согласятся с тем, что с проекта был четко и недвусмысленно снят штраф. 738 Глава 26 Рисунок 26.2 Модель водопада Этап проектирования жизненного цикла программного обеспечения фокусируется на четырех элементах: структурах данных, программной архитектуре, процедурах и интерфейсах. Этот этап завершается представлением программного обеспечения, которое может быть оценено само по себе. Максима, гласящая, что инженерия - это проектирование, применима также к разработке программного обеспечения , поскольку этап проектирования часто является наиболее критичным и сложным. На этапе кодирования мы преобразуем дизайн в машинно-исполняемый продукт. В парадигме жизненного цикла традиционно принято, что фаза проверки начинается по завершении фазы кодирования. Как только код выполняется на компьютере, он должен быть оценен на корректность. Это означает, что мы должны убедиться в том, что он соответствует требованиям, разработанным на этапе спецификации, и что в нем нет дефектов. Хотя этот этап иногда связан с отладкой, он также должен включать все формальные и экспериментальные проверки корректности программы. Хотя этап обслуживания не был включен в первоначальную модель, сегодня общепринято, что программы, несомненно, претерпят изменения и что эти изменения являются частью жизненного цикла программного обеспечения. Причиной изменений могут быть обнаруженные ошибки, модификации в аппаратной или программной среде программы или внедрение новых функциональных требований, определенных заказчиком. Процедуры обслуживания финансирования требуют пересмотра всех этапов жизненного цикла программного обеспечения, как показано пунктирной стрелкой на рисунке 26.2. Несмотря на то, что модель водопада успешно использовалась во многих проектах по разработке программного обеспечения и до сих пор считается эффективным методом разработки программ, было указано на несколько недостатков. ....... ........... Наиболее примечательным из них является то, что проекты разработки программного обеспечения редко могут быть конкретизированы за один присест. Более типичным сценарием является серия повторяющихся циклов разработки, подобных тем, которые упоминаются в отношении принципа инкрементной разработки. Еще одна трудность в применении Основы системной инженерии 739 спецификация фаза проектирование фаза кодирование фаза проверка
фаза обслуживание фаза особенность этой модели заключается в том, что проекты часто не следуют последовательному потоку, описанному в парадигме. Например, верификация программы часто выполняется одновременно с в настоящее время фазой кодирования, равно как и фазами проектирования и спецификации. Другая критика заключается в том, что операции обслуживания часто требуют повторения жизненного цикла программы. Например, разработка новой версии существующей программы с целью исправления известных дефектов и включения недавно определенных функций требует пересмотра всех этапов жизненного цикла, начиная с этапа спецификации. Поэтому трудно рассматривать сопровождение программы как типичную фазу улучшения, поскольку она имеет уникальные эффекты, которые не проявляются на других фазах модели. Несмотря на эти и другие недостатки водопадной модели, эта парадигма является наиболее широко используемой в практике разработки программного обеспечения. Хотя другие, более сложные модели обычно считаются более строгими и точными, модель waterfall остается наиболее популярной и простой методологией разработки программного обеспечения. 26.3.2 Прототипирование Многие проекты по разработке программного обеспечения носят экспериментальный или умозрительный характер. Рассмотрим следующие примеры: • Исследовательская группа желает определить, возможно ли разработать экспертную систему, которая использует данные, полученные со спутников дистанционного зондирования, для определения уровней загрязнения в озерах и реках Соединенных Штатов. В озерах и реках Соединенных Штатов. • Предприниматель желает определить, возможно ли разработать текстовую программу, в которой пользователь оснащен ножными педалями, активирующими некоторые функции программы. Грамм. В любом из этих случаев мы можем видеть, что проект разработки программного обеспечения вряд ли может быть задан априори. Задачи изложены так вообще, что это сложнокульт для определения конкретных требований к программе, которые могли бы послужить основанием для дебелохвоста дизайн. В обоих случаях, а также во многих других, в которых первоначальный детальный дизайн невозможен или непрактичен, альтернативой может быть подход прототипирования. В качестве альтернативы При создании прототипа разработчик может создать модель программного обеспечения. Эта модель впоследствии может быть использована для лучшего определения конечного продукта или для определения его осуществимости. Прототипом может быть простая бумажная модель программного обеспечения, которая может быть создана практически без кодирования, рабочий прототип, реализующий подмножество программных функций, или полная программа, в которой некоторые функции не реализованы. Цель прототипа - позволить как заказчику, так и разработчику принимать решения относительно осуществимости и практичности проекта и, если это будет сочтено осуществимым и практичным, лучше определить конечный продукт. Прототипирование часто описывается как цикл разработки с последовательностью шагов, показанных на рис. 26.3. 740 Глава 26 Рисунок 26.3 Модель прототипирования Разработка прототипа начинается со сбора требований и спецификаций. Затем разрабатывается прототип, обычно следуя сокращенному процессу, который дает результаты быстрее, чем обычные процедуры разработки программы. Создается прототип, что также сокращает процессы разработки за счет пропуска всех этапов обработки, которые не являются строго необходимыми для данной цели. Прототип, наконец, оценивается сначала разработчиком, а затем заказчиком. При необходимости он дополнительно дорабатывается и настраивается в итерационном цикле. Готовый прототип используется для дальнейшей доработки конечного программного продукта.
Один из основных рисков разработки прототипа связан с заказчиками, которые неправильно понимают его цели. Это неправильное представление может привести к следующему: 1. Клиент, не знающий, что прототип является незавершенным продуктом, обычно с меньшей функциональностью и более низкой производительностью, которые могут быть достигнуты в окончательной версии, может принять неправильные решения относительно пригодности конечного продукта. В крайних случаях многообещающий проект может быть отменен заказчиком из-за неправильных представлений, заложенных в прототипе. 2. Заказчик, спешащий вывести продукт на рынок или применить его на практике, решает, что сам прототип достаточно адекватен. Затем заказчик запрашивает , чтобы разработчик выполнил некоторые окончательные корректировки и модификации и предоставил прототип в качестве конечного продукта. Одним из стимулов, который мог бы быть у этого заказчика, является ожидаемая более низкая стоимость использования самого прототипа вместо того, чтобы следовать процессу разработки , как первоначально планировалось. В большинстве случаев возможность недопонимания относительно этапа создания прототипа может быть устранена путем четкого объяснения заказчику этих рисков. В качестве альтернативы, может быть целесообразно обменяться меморандумом о согласии между разработчиком и заказчиком относительно ограниченной функциональности прототипа или того факта, что Основы системной инженерии 741 требования и спецификация прототип проектирование прототип разработка прототип оценка прототип доработка конечный продукт определение НАЧАЛО КОНЕЦ что это не будет пригодно в качестве готового продукта. К сожалению, в некоторых случаях риски неправильного толкования прототипа клиентом заставили разработчиков отказаться от принятия этой парадигмы. 26.3.3 Спиральная модель Эта модель, впервые предложенная Боемом в 1988 году, предлагает объединить лучшие черты жизненного цикла и парадигмы прототипирования с принципом постепенного развития . На рисунке 26.4 (на основе Pressman) показана спиральная прогрессия, проходящая через четыре различных этапа. Рисунок 26.4 Спиральная модель Обратите внимание, что рисунок на рис. 26.4 предназначен как общая иллюстрация метода и не должен интерпретироваться буквально. Например, количество циклов по спирали будет варьироваться от проекта к проекту. Уникальной особенностью спиральной модели является введение этапа анализа рисков, кульминацией которого является принятие решения "идти" или "не идти" в конце каждого цикла разработки . Однако этот этап также является его наиболее противоречивой особенностью. Во-первых, анализ рисков требует особого опыта и упрощается, когда выполняется необученным персоналом. Фактически, фаза анализа рисков нежелательна, если она может привести к неверной интерпретации результатов. Кроме того, заказчики часто считают, что они провели анализ рисков проекта, прежде чем принять решение о его реализации, и что в дальнейшем рассмотрении этого вопроса нет необходимости. Более того, возможность того, что по завершении каждого цикла разработки весь проект может быть свернут из-за решения о запрете, может привести к опасениям со стороны заказчика
. С другой стороны, если представить трудности и опасности, связанные с фазой анализа рисков, то спиральная модель представляет собой наиболее удовлетворительную парадигму для разработки больших программных систем. Поэтапный подход к разработке, предлагаемый этой моделью, с ее повторяющимся прототипированием и риском . 742 Глава 26 планирование начальные спецификации проекта циклы оценки заказчиком планирование, основанное на отзывах заказчика первоначальный анализ рисков первоначальный прототип обновленный прототип окончательная система РЕШЕНИЕ ИДТИ / НЕ ИДТИ обновление анализа рисков оценка риск анализ проект проектирование этапы оценки обеспечивают реалистичную основу для разработки программы. Как заказчик, так и разработчик имеют неоднократные возможности для выявления возможных дефектов и недочетов и внесения необходимых корректировок. Обратите внимание, что эта модель является относительно новой и что еще не накоплено большого опыта ее применения. 26.3.4 Прагматичный подход Практикующий разработчик программного обеспечения должен решить в каждом конкретном случае, какая модель или нации моделей наиболее подходят для рассматриваемого проекта. Решение часто основывается на практических рисках и ограничениях, а не на теоретической применимости. Например, разработчик может посчитать, что наиболее адекватной парадигмой является модель прототипирования, но значительный риск того, что заказчик неверно истолкует результаты, советует воздержаться от ее использования. В другом случае в проекте могут вполне в рамках спиральной модели, но тот факт, что обучение персонала анализ рисков не будет доступна предполагает изменения катионного парадигмы или принятие более обычных водопад модель. Один мудрый человек однажды сказал: “Все системы и никакой системы: узрите лучшую систему”. Эта максима вполне применима к различным парадигмам разработки программного обеспечения, упомянутым в предыдущих разделах, в частности, когда они касаются небольших проектов, в которых время разработки и ресурсы ограничены. Наиболее распространенный сценарий представляет собой комбинацию парадигм. Часто наиболее полезной является спиральная модель, в которой функция оценки риска уменьшена с учетом двух факторов. Первая - это нервозность клиентов, возникающая в результате возможности отмены проекта. Вторая - нехватка экспертов в области анализа рисков. Поскольку спиральная модель сама по себе является комбинацией водопада и модели прототипирования, фактически будут использоваться все упомянутые парадигмы. Возможное масштабирование фазы анализа рисков могло бы состоять в ограничении ее первым или первым и вторым циклами вокруг модели. После этого будет предполагаться, что решение о переходе находится в постоянном режиме. Эта модификация, безусловно, ставит под угрозу одну из лучших характеристик спиральной модели, но во многих случаях практические соображения сделают настройку необходимой. Другие обстоятельства также могут потребовать модификации теоретических моделей.
Например, в большинстве учебников по программной инженерии и системному анализу предполагается, что роли заказчика и разработчика берут на себя отдельные лица или организации . В реальной жизни многие проекты по разработке программного обеспечения осуществляются в условиях, когда заказчиком и разработчиком является одно и то же лицо или коммерческая фирма. Это тот случай, когда компания-разработчик программного обеспечения разрабатывает проект, который будет продаваться напрямую, или когда научно-исследовательская группа разрабатывает свой собственный проект. В любом случае это можно исправить, искусственно имитируя функцию клиента. Например, отдел маркетинга компании-разработчика программного обеспечения может играть роль заказчика, или руководитель исследовательского проекта может притворяться клиентом, для которого разрабатывается продукт. Двойная роль разработчиков и заказчика настолько важна в процессе разработки программного обеспечения, что любое решение, которое сохранит его, даже фиктивно, является привлекательным вариантом. Однако мы всегда должны иметь в виду, что виртуальный клиент - это вымышленное создание, которое в любой момент может быть отменено вышестоящими инстанциями. Когда это происходит, целостность парадигмы серьезно нарушается, поскольку разделение между суждением и авторитетом прекращается Основы системной инженерии 743 чтобы выйти. Тогда проект становится похожим на демократию, в которой исполнительная власть берет на себя право увольнять представителей судебной власти. В любом случае, хотя видимость может быть сохранена, фактический механизм сдержек и противовесов перестает существовать. 26.4 Параллельная документация Один из наиболее важных уроков разработки программного обеспечения связан с необходимостью разработки соответствующей и строгой проектной документации. Аналогичным образом, наиболее заметным отличием между правильно спроектированным проектом разработки и бессистемным effort является документация. Слишком часто существовала тенденция рассматривать программную документацию как второстепенную проблему, которую можно решить после завершения проекта . Эта тенденция, вероятно, прослеживается до того же человеческого заблуждения, которое заставляет некоторых программистов полагать, что комментарии могут быть вставлены в код после завершения программирования. Поскольку написание комментариев является частью рутинной работы по программированию, документация является частью задачи разработки программы. Ни к одному из них нельзя подходить запоздало, рискуя написать спагетти-код или ускорить разработку не поддающихся расшифровке проектов. Таусворт, который отвечает за концепцию параллельной документации, заявляет: “Второй принцип, которым руководствуется эта работа, заключается в том, что этапы определения, проектирования, кодирования и проверки разработки нельзя считать завершенными , пока документация не будет завершена и не будет сертифицирована с помощью той или иной формы аудита корректности ... хорошая документация неразрывно связана с каждым аспектом проекта, от концепции до дизайна, кодирования, тестирования и т.д., и поскольку формализация обеспечивает соблюдение дисциплины, создается программная методология”. Именно сообщество информационных систем подчеркнуло важность документирования проекта разработки программного обеспечения. Часто в книге по разработке программного обеспечения этой теме уделяется мало места, с другой стороны, в книге по системному анализу и проектированию, скорее всего, будет содержаться подробное обсуждение этой темы. Системный анализ и проектирование Capron включают тщательную обработку документации. Возможно, осознание важности документации более естественно для бизнес-сообщества, чем для программистов и тех, кто подписывает программы, которые часто проявляют отвращение к бумажной работе. В отношении разработки проекта программного обеспечения можно четко выделить следующие типы документации : 1. Письменные отчеты, знаменующие завершение этапа цикла разработки. Эти документы иногда называют конечными результатами, поскольку они часто представляются клиенту по завершении каждого этапа разработки. Типичными конечными результатами являются исследование
осуществимости, документ об анализе и требованиях, а также документ о детальном проектировании . ........... в 2. Руководства пользователя и учебные пособия, которые могут быть распечатаны или доступны онлайн. 744 Глава 26 3. Операционные документы, чаще всего встречающиеся в больших компьютерных средах, включают графики выполнения, формы и носители ввода и вывода, диаграммы доставки, маршрутизации и распространения, спецификации файлов данных, графики обновлений, процедуры восстановления и средства управления безопасностью . В 4. Альбом для вырезок проекта используется для сбора заметок, расписаний, протоколов собраний и других сообщений, полученных в ходе проекта. 26.4.1 Возражения и оправдания Программные проекты разрабатываются отдельными лицами, которых часто возмущает необходимость тратить время на то, что некоторые считают бесполезной бумажной волокитой. Даже когда руководители проектов осознают важность документации, им часто приходится преодолевать значительное сопротивление со стороны своего технического персонала. Ниже приведены оправдания, используемые для рационализации неприятия документации: 1. Наше время лучше потратить на программирование и другие технические вопросы, чем на бесполезную работуperwork. 2. Если мы попытаемся задокументировать это действие сейчас, результаты вскоре устареют. Отсюда поэтому предпочтительнее подождать, пока проект приблизится к завершению, прежде чем мы приступим к документации. 3. Люди не читают руководства, так зачем утруждать себя их написанием. 4. Мы программисты и дизайнеры, а не писатели. Нам следует нанять технических писателей для работы с руководствами и документами. 26.4.2 Преимущества хорошей документации Причины, по которым оправдания, упомянутые в предыдущем разделе, эффективны, заключаются в том, что они имеют некоторую ценность истины. Только осознав, что документация является одним из основных результатов проекта разработки и что о ней никогда не следует вспоминать во время завершения проекта, мы можем признать эти аргументы недействительными. Руководитель проекта должен осознавать тот факт, что программная документация по меньшей мере так же важна , как и любой другой технический этап разработки. Следующие неоспоримые преимущества одновременного документирования проекта разработки являются : 1. Хорошо документированный проект лучше способен противостоять кадровым изменениям, поскольку появляются новые специалисты составители могут наверстать упущенное, изучив проектную документацию. 2. Документация может служить инструментом управления, требуя, чтобы каждая стадия разработки завершалась документом, который должен быть утвержден до того, как начнется следующая стадия. 3. Сопутствующая документация устанавливает историю проекта и может служить, среди прочего , отчетом о ходе выполнения. Документы могут использоваться в качестве ориентиров планирования и поэтому служат для измерения и оценки прогресса проекта. Наиболее важным принципом проектной документации является принцип параллелизма. Документация должна составлять существенную часть усилий по разработке и должна выполняться одновременно с каждым этапом разработки. В то же время документирование часто является деятельностью по разработке, которую легче всего отложить или даже принести в жертву. Когда время поджимает, возникает соблазн отложить документацию. На этом этапе Основы системной инженерии 745 руководитель проекта должен знать, что когда документация теряет параллелизм, она также теряет значительную часть своей полезности. 746 Глава 26 Глава 27
Описание и спецификация Краткое содержание главы Эта глава посвящена одному из важнейших предварительных этапов проектирования и кодирования программной системы: ее спецификации. Эти подготовительные этапы включают в себя изучение осуществимости, анализ требований и спецификаций. Она также охватывает использование потоков данных и инструментов моделирования данных. 27.0 Этап системного анализа Первой задачей инженера является понимание системы, второй - ее определение, третьей - ее построение. Анализ должен предшествовать спецификации, поскольку невозможно определить или описать то, что неизвестно. Спецификация должна предшествовать конструированию, поскольку мы не можем построить то, что не было определено. Первые две задачи (понимание и определение системы) могут быть довольно сложными в области разработки программного обеспечения, особенно в отношении проектов малого и среднего размера. Этапы системного анализа относятся к оценке существующей системы. Поэтому можно утверждать, что этот этап может быть пропущен при внедрении системы, у которой нет предшественника. В любом случае, мы не должны предполагать, что фазу анализа можно обойти просто потому, что система была признана устаревшей или потому, что уже было принято решение относительно ее замены. Устаревшие, обединственные и даже совершенно непригодные системы могут содержать информацию, которая необходима или удобна для проектирования и внедрения новой. Очень редко было бы целесообразно полностью отказаться от существующей системы без анализа ее функционирования, производительности и характеристик внедрения. В то же время осуществимость новой системы часто должна определяться независимо от той, которая заменяется. Фактически, осуществимость системы должна оцениваться, даже если у нее нет предварительного декодера. 747 27.0.1 Системный аналитик В мире информационных систем специалиста, пытающегося понять и определить программную систему, обычно называют системным аналитиком. В средах, ориентированных на компьютерные науки, этого человека часто называют системным инженером. Другими терминами, используемыми для описания этого компьютерного профессионала, являются программист-аналитик, системный дизайнер, системный консультант, аналитик информационных систем и инженер по информационным системам. Как бы там ни назывался системный аналитик, описание его работы обычно сильно отличается от описания работы программиста. В то время как ответственность программиста заканчивается компьютерной программой, обязанности аналитика распространяются на управление и администрирование. Обычно аналитик отвечает за оборудование, процедуры и базы данных. Кроме того, системный аналитик обычно отвечает за команду разработчиков программного обеспечения . Следовательно, работа аналитика требует управленческих навыков в дополнение к технической компетентности. Навыки устного и письменного общения часто необходимы, поскольку аналитику обычно приходится проводить презентации для клиентов, вести обсуждения спецификаций и контролировать подготовку множества документов и отчетов. Проектами программирования и построения систем обычно руководит ведущий или главный аналитик, который может взять на себя ответственность за управление и надзор за деятельностью команды аналитиков, программистов, разработчиков программ, тестировщиков программного обеспечения и специалистов по документации. Идеальным профилем для главного системного аналитика является профиль опытного программиста и разработчика программ (предпочтительно с высшим образованием в области компьютерных наук или информационных систем), который имеет опыт и подготовку в бизнесе. Кроме того, ведущим аналитиком должен быть человек, который успешно руководил анализом и разработкой программного обеспечения или системных проектов сопоставимой сложности. Описание работы включает сбор и анализ данных, выполнение технико-экономических обоснований проектов разработки,
проектирование, разработку, установку и эксплуатацию компьютерных систем, а также проведение презентаций, рекомендаций и технических спецификаций. Однако небольшой программный проект редко может рассчитывать на услуги идеально квалифицированного ведущего аналитика. Довольно часто на выполнение этой задачи назначается человек с менее чем оптимальными навыками. В этом отношении руководителям проектов важно вновь осознать, что именно ведущий системный аналитик или менеджер проекта должен быть наиболее высококвалифицированным членом команды разработчиков. Одна из распространенных ошибок заключается в том, чтобы думать о главном аналитике как о рутинной работе, которую может выполнить профессиональный администратор с небольшими техническими знаниями или вообще без них. Или рассудить, что персонал с более высокой технической квалификацией лучше всего использовать в качестве программистов или разработчиков программ . Опыт показывает, что назначение кого-то, кто не является самым квалифицированным и технически компетентным членом команды разработчиков, на должность ведущего системного аналитика равносильно приглашению к катастрофе. Хотя ведущий системный аналитик часто носит много головных уборов, наиболее необходимыми навыками для этой работы являются технические. Компетентного программиста и дизайнера можно обучить или поддержать в выполнении административных и бизнес-функций, а также помочь улучшить коммуникативные навыки. Но было бы действительно удивительно, что человек 748 Глава 27 без необходимых технических навыков мог бы анализировать, конкретизировать и руководить проектом разработки систем. 27.0.2 Анализ и контекст проекта Общепринятая трактовка системного анализа, встречающаяся в большинстве современных текстов и справочников, предполагает, что типичный проект состоит из разработки коммерческой системы, которая будет внедрена на конкретном коммерческом предприятии. tem.,,,,,,,,,,, В этом контексте этап системного анализа состоит из оценки и описания существующей системы, и его нижней частью является определение того, будет ли новая система реалистичным и прибыльным предложением . Такая ориентация на бизнес-систему типична для компьютерных информационных систем и программ по системам управленческой информации, преподаваемых во многих наших университетах. С другой стороны, учебники по программной инженерии (иногда рассматриваемые как аналог системного анализа в учебных программах по информатике) рассматривают фазу анализа больше с точки зрения системного моделирования и обычно подчеркивают элемент анализа требований на этом этапе разработки. Другими словами, термин системный анализ имеет разные коннотации, когда рассматривается с точки зрения информационных систем, чем когда рассматривается с точки зрения информатики . Более того, иногда ни одна из точек зрения в точности не соответствует рассматриваемой проблеме . Например, предположить, что типичный проект разработки систем состоит в замене устаревшей бизнес-программы расчета заработной платы ( точка зрения информационных систем) или разработке новой, более эффективной операционной системы ( точка зрения информатики), не учитывает многие другие сценарии реальной жизни. Например, разработчиком проекта может быть экспериментальное, научное или исследовательское предприятие, для которого нет прецедентов и в котором прибыль не является фактором. Или же это может состоять в создании программного продукта, который будет продаваться широкой публике, такого как приложение для текстового процессора, компьютерная игра или инструментарий для выполнения инженерных расчетов. В любом случае, многие операции и детали системного анализа, описанные в книгах по системному анализу и проектированию или по разработке программного обеспечения, не имеют отношения к делу. Другая концептуализация, которая может варьироваться в зависимости от контекста проекта, относится к компонентам компьютерной системы. Обычно мы думаем о компьютерной системе как Интеграции аппаратных и программных элементов. Поэтому разработка систем состоит как из аппаратных комплексов и программного обеспечения инженерных систем. Таким образом, фаза системного анализа включает в себя исследование и спецификацию аппаратной и
программной подсистем. В действительности часто случается, что либо аппаратные, либо программные элементы компьютерной системы приобретают непропорционально большое значение в конкретном проекте. Например, в проекте, состоящем из разработки компьютерной программы, которая будет продаваться как продукт сообществу пользователей, элемент разработки аппаратного обеспечения становится гораздо менее важным, чем элемент разработки программного обеспечения. При других обстоятельствах относительная важность аппаратных и программных элементов могла бы поменяться местами. Следовательно, детали этапа системного анализа часто зависят от контекста проекта. То, что применимо и уместно в типичном сценарии развития бизнеса или в условиях технического развития, может потребоваться изменить в соответствии с конкретным случаем. Два элемента этого этапа, как правило, имеют решающее значение для успеха проекта: Описание и спецификация 749 технико-экономическое обоснование и анализ требований проекта. Каждое из этих действий обсуждается в следующих разделах. 27.1 Технико-экономическое обоснование Часто проект по разработке программного обеспечения начинается с отрывочной и предварительной идеи , практичность которой еще не определена. Другие проекты изначально хорошо оштрафованы и пользуются презумпцией того, что они подходят и оправданы. В первом случае системный аналитик должен начать работу с выполнения технического исследования, которое определит, является ли проект осуществимым или нет. По словам Прессмана, при наличии неограниченного времени и источников все проекты выполнимы. Но в реальном мире ресурсы ограничены, и часто осуществимость проекта приравнивается к экономическим выгодам, которые можно ожидать от его завершения. Это исключает системы для национальной обороны, следственных и исследовательских проектов или любых других разработок, в которых финансовая отдача не является критическим соображением. В большинстве случаев первая проектная деятельность называется технико-экономическим обоснованием или предварительным исследованием. Ее можно разбить на следующие категории: 1. Техническая осуществимость - это оценка, которая определяет, является ли предлагаемая система технически нологически возможной. Предполагаемые функции, производительность и эксплуатационные ограничения определяют техническую осуществимость проекта. 2. Экономическая целесообразность оценки денежной стоимости с точки зрения человека и материальные ресурсы, которые необходимо инвестировать в свое развитие, по сравнению с возможными финансовыми льготами и другими преимуществами, которые могут быть реально ожидании любимогольон от проекта к завершению. 3. Юридическая осуществимость - это оценка правовых нарушений и ответственности, которые могут возникнуть в результате в результате разработки системы. Перед началом этапа технико-экономического обоснования руководство среднего и высокого уровней (часто в консультации с ведущим аналитиком) должно определить, будут ли усилия выгодными и сколько времени и ресурсов будет вложено в этот этап. Трудно определить произвольные правила в этом отношении, поскольку удобство технико-экономического обоснования, а также время и человеческие ресурсы, выделяемые на него, часто определяются финансовыми и практическими соображениями. Разумно ожидать, что незначительный проект развития, который, как ожидается, принесет большие доходы и потребует относительно небольших инвестиций, часто будет осуществляться с менее тщательным технико-экономическим обоснованием, чем крупный проект, предполагающий большую сложность и неопределенность. На этапе технико-экономического обоснования системного анализа часто используются два метода управления бизнесом: анализ рисков и анализ затрат и выгод. С помощью анализа рисков мы пытаемся установить опасности, связанные с проектом, и определить моменты, в которые предпочтительнее отменить проект, чем продолжать его. С помощью анализа затрат и выгод мы пытаемся измерить выгоды, которые будут получены в результате разработки системы, и расходы, связанные с ней. Осуществимый проект - это такой, в котором риски и затраты приемлемы с учетом его
ожидаемых выгод. 750 Глава 27 В некоторых книгах по системному анализу и проектированию предлагается, чтобы анализ рисков и анализ затрат и выгод были мероприятиями, которые предшествуют основному системному анализу. Следовательно, их следует рассматривать как части фазы, иногда называемой предварительным лиминальным анализом. Обоснование такого подхода заключается в том, что если проект считается невыполнимым или финансово не оправданным, то никакого дальнейшего анализа или проектирования не требуется. Это согласуется с общепринятым определением анализа рисков как метода , который служит для определения технической осуществимости проекта, и анализа затрат и выгод как метода определения экономической осуществимости проекта. На самом деле техническая и экономическая осуществимость многих проектов считается само собой разумеющейся. В этих случаях анализ рисков и затрат-выгод становится либо формальностью, либо служит обоснованием первоначальных предположений. Аналитик должен иметь в виду, что потеря объективности при анализе рисков или затрат-выгод делает эти действия практически бесполезными. Единственная задача-установить небольшой размер программного обеспечения проекта заключается в том, что технически квалифные персонала для выполнения технико-экономического обоснования и анализ затрат и результатов часто не доступен. Таким образом, увязка технико-экономического обоснования с анализом рисков и методами анализа рентабельности соответствия может привести к тому, что технико-экономическое обоснование не будет выполнено. Поскольку в большинстве проектов разработки систем важно, чтобы была выполнена та или иная форма технико-экономического обоснования с максимальной степенью строгости, которую позволяют обстоятельства, то предпочтительно рассматривать анализ рисков и анализ затрат и выгод как альтернативные этапы. Блок-схема осуществимости проекта на рисунке 27.1 на следующей странице иллюстрирует это представление. Предостережение относительно технико-экономического обоснования связано с тем фактом, что те, кто его формирует, могут быть кровно заинтересованы в результатах. Например, мы можем подозревать, что технико-экономическое обоснование может превратиться в самореализующееся пророчество, если оно будет выполнено компанией-разработчиком программного обеспечения, которая, вероятно, станет конечным разработчиком проекта. В таких случаях может быть целесообразно нанять независимого консультанта для выполнения или оценки технико-экономического обоснования, гарантируя тем самым объективность результатов. 27.1.1 Анализ рисков Одним из факторов, рассматриваемых в технико-экономическом обосновании, является риск проекта. Например, крупный проект с большим потенциалом финансовой выгоды может быть сопряжен с большим риском, если он может привести к дорогостоящему судебному разбирательству. В этом случае указывается подробный анализ этого фактора риска. По той же причине небольшой проект с сомнительной технической и экономической осуществимостью может стать желательным риском, если его успешное завершение принесет существенную выгоду. В этом случае потенциальная выгода может сделать приемлемым осуществление проекта, даже если технико-экономическое обоснование определило, что возможности его завершения сомнительны. Другими словами, проект оценивается как удачная авантюра. Анализ рисков обычно связан со следующими видами деятельности: 1. идентификация риска 2. оценка риска 3. оценка риска 4. управление рисками Описание и спецификация 751 Рисунок 27.1 Блок-схема осуществимости проекта 752 Глава 27 НАЧАТЬ ПРОДОЛЖИТЬ
ПРОЕКТ Отмена ПРОЕКТ затененные шаги иногда пропускаются в небольших проектах НЕТ НЕТ НЕТ ДА ДА ДА провести риск анализ провести соотношение затрат и выгод анализ выполнить технические, экономические и юридические исследования осуществимости является ли риск проекта приемлемым ? являются ли затраты / выгоды благоприятными ? осуществим ли проект ? Мы обсуждаем каждую из этих операций, связанных с риском, отдельно. Идентификация риска Состоит из классификации рисков по определенным областям. В этом смысле риск проекта связан с проблемами бюджета, графика и укомплектования персоналом, которые могут повлиять на разработку. В этом смысле тип и сложность проекта могут создавать дополнительные факторы риска. Технический риск связан с возможностью возникновения трудностей, которые изначально не ожидались. Например, неожиданное развитие событий на местах может сделать проект технически безнадежным . Бизнес-риск, возможно, самый обманчивый, связан с изменениями рынка, стратегией компании , трудностями при продаже, потерей поддержки руководства и потерей бюджетной поддержки. Оценка риска На этом этапе анализа рисков мы пытаемся оценить вероятность и последствия каждого риска. Первый шаг заключается в установлении вероятности риска, второй - в описании последствий риска, третий - в оценке влияния риска на проект и, наконец, в определении точности самой оценки риска. Эти четыре элемента могут быть определены количественно с помощью ответа “да” или “нет”, но лучшим подходом является установление рейтинга вероятности риска, последствий, воздействия и точности прогнозирования. Исторические данные, собранные разработчиками, часто могут служить для количественной оценки некоторых рисков проекта. В качестве общего правила мы можем предположить, что чем выше вероятность возникновения и влияние риска, тем больше беспокойства он должен вызывать. Оценка риска Основана на вышеупомянутом воздействии и вероятности возникновения риска, как это определено на этапе оценки риска. Точность оценки риска также является фактором, который необходимо принимать во внимание. Первой задачей на этом этапе является установление приемлемых уровней риска для важнейших видов деятельности проекта. Для проектов разработки систем тремя типичными критическими действиями являются стоимость, график и производительность. Ухудшение одного или нескольких из этих факторов определит прекращение проекта. Аналитики рисков говорят о контрольной точке (или переломном моменте), в которой решение о прекращении или продолжении проекта одинаково приемлемо. Поскольку анализ рисков не является точной наукой, точку разрыва лучше визуализировать как
область разрыва. На рисунке 27.2 показано влияние превышения затрат и графика на программный проект. Темные области, ограниченные кривыми (область неопределенности или разрыва), представляют область, в которой решения о прекращении или продолжении проекта имеют приблизительно одинаковый вес. Под этой областью находится регион go, в котором проект продолжается без вопросов. Область над кривой (запретная область) представляет условия, которые определяют отмену проекта. Обратите внимание, что на рисунке 27.2 мы рассмотрели только два риска проекта, часто существуют и другие, которые могут повлиять на решение о прекращении. Читатель не должен придавать никакого значения конкретной форме кривой на рис. 27.2. Тот факт, что он не является однородным, просто указывает на то, что фактор неопределенности при оценке риска может не быть плавной функцией. Описание и спецификация 753 Рисунок 27.2 Референтный уровень в оценке риска В процессе оценки риска обычно выполняются следующие шаги: 1. Референтный уровень определяется для каждого значительного риска в проекте. 2. Взаимосвязь между риском, его вероятностью и его влиянием на проект определена для каждого референтного уровня. 3. Определены область завершения и область неопределенности. 4. Предпринята попытка предвидеть, как составной риск повлияет на референтный уровень для каждого участвующего риска. Управление рисками Как только элементы описания риска, вероятности и воздействия будут установлены для каждого риска проекта, их можно использовать для минимизации конкретного риска или его последствий. Например, предположим, что анализ исторических данных и процесс оценки рисков определяют, что для конкретного проекта существует значительный риск перевыполнения графика из-за низкой производительности программиста. Зная это, руководство может поддаться искушению найти альтернативные источники рабочей силы, которые можно было бы задействовать, если бы выявленный риск был реализован. Или, возможно, можно предпринять шаги для повышения производительности, выявив причины низкой производительности программиста. Управление рисками само по себе может стать дорогостоящим и отнимающим много времени. В крупном проекте разработки можно выявить множество рисков. Каждый риск может быть связан с несколькими мерами по управлению рисками. Отслеживание рисков и средств их устранения само по себе может стать крупным проектом. Поэтому менеджеры и директоры должны иногда проводить анализ затрат и выгод деятельности по управлению рисками, чтобы определить, как это повлияет на стоимость проекта. Принцип Парето, иногда называемый правилом 80/20, гласит, что 80 процентов неудач проекта связаны с 20 процентами выявленных рисков. Этап анализа рисков должен помочь распознать эти критические 20 процентов и исключить менее значительные риски из плана управления рисками. 27.1.2 Анализ рисков в рамках небольшого проекта Анализ рисков - это специализированная область, по которой были опубликованы многочисленные монографии и учебники . Легкомысленная или небрежная попытка анализа рисков и управления ими часто приводит к обратным результатам. В небольших программных проектах часто отсутствует специализированный персонал для выполнения точного анализа рисков. Более того, более того, даже если бы удалось найти технически квалифицированный персонал, сомнительно, что для754 Глава 27 увеличивается перерасход средств график o v ошибка un увеличивается область неопределенности ЗОНА, ЗАКРЫТАЯ ДЛЯ ПОСЕЩЕНИЯ ЗОНА, ЗАКРЫТАЯ ДЛЯ ПОСЕЩЕНИЯ
тщательное расследование рисков было бы экономически оправданным. Следовательно, большинство небольших проектов осуществляются без реального анализа рисков и без плана управления рисками. В этом случае в технико-экономическом обосновании или других проектных документах должно быть четко указано, что анализ рисков и меры по управлению рисками не были выполнены, причины, по которым этот шаг был пропущен, и опасности, которые могут возникнуть в результате упущения. Возможно, большей опасностью, чем полное отсутствие формального анализа рисков, является его тривиализация. Когда анализ рисков проводился поверхностно, с небольшой тщательностью и неподготовленным персоналом, наиболее вероятными результатами являются выявление нереальных рисков или, что еще хуже, ложное чувство уверенности, возникающее в результате плана, который не смог выявить реальные опасности, связанные с проектом. Если анализ рисков и управление ими были выполнены с использованием менее чем достаточных ресурсов, то результирующая низкая точность прогноза рисков должна быть четко отмечена в проектной документации ........... в . Иногда может быть сочтено целесообразным попытаться провести некоторую форму поверхностного анализа рисков с целью приобретения опыта в этой области или для экспериментирования с определенной методологией. Это также следует четко отметить, чтобы руководство не возлагало чрезмерной уверенности на итоговый план. Таблица 27.1 представляет собой вариант плана управления рисками и мониторинга, представленного Шареттом. Мы упростили план, включив в него только те элементы, которые, по нашему мнению, никогда не следует упускать. Таблица 27.1 Упрощение схемы управления рисками I. Введение II. Анализ рисков 1. Идентификация рисков a. Обследование рисков b. Контрольный список элементов риска 2. Оценка риска a. Вероятность риска b. Последствия риска c. Ошибка оценки 3. Оценка риска a. Оценка методов анализа риска b. Оценка референтных факторов риска c. Оценка результатов III. Управление рисками 1. Рекомендации 2. Варианты неприятия риска 3. Процедуры мониторинга рисков 27.1.3 Анализ затрат и выгод Большинство проектов по разработке компьютерных систем требуют ощутимой экономической выгоды. Это не означает, что каждая система должна приносить прибыль, скорее, ожидаемые выгоды должны оправдывать затраты. Исключениями из этого правила являются системы, которые имеют законный мандат или те, цель которых выходит за рамки экономического анализа, как это может быть в случае с проектом национальной обороны или научным или академическим экспериментом. При обычных обстоятельствах экономическое обоснование системы является наиболее важным результатом технико-экономического обоснования, поскольку оно является решающим фактором при принятии решения о поддержке проекта. Описание и спецификация 755 Анализ затрат и выгод является стандартным инструментом оценки бизнеса, но не все результаты проекта могут быть точно измерены в долларах и центах. Некоторые системы приносят менее ощутимые выгоды, которые связаны с такими факторами, как долгосрочные планы компании, более высокое качество продукта или услуг или повышение морального духа сотрудников. Попытка оценить в долларах некоторые из менее значимых преимуществ проекта может оказаться обременительной задачей. Часто предпочтительнее включать категорию нематериальных выгод в документ по анализу затрат и выгод, а не пытаться количественно оценить эти выгоды.
Отправной точкой для оценки преимуществ новой системы обычно является существующая система. Например, производитель под названием Acme Corporation планирует обновить свою нынешнюю систему управления запасами новой, основанной на использовании штрих-кодов для идентификации продуктов на складе. Новая система требует инвестиций в программное обеспечение и компьютерное оборудование в размере 500 000 долл., но имеет следующие преимущества: 1. Количество сотрудников, используемых в функциях управления запасами, будет сокращено на 4. Это приведет к экономии в размере 90 000 долларов США в год. 2. Поток материалов и расходных материалов на сборочную линию будет улучшен, что, как ожидается, приведет к запланированному сокращению потерь времени на 200 рабочих часов в месяц. Поскольку час потерянного времени на сборочной линии обходится компании в 12 долларов, это позволит сэкономить 2400 долларов в месяц. 3. Лучший контроль за запасами позволит компании сократить размер своих инвестиций история. По оценкам, меньшие инвестиции и сокращение складских расходов принесут компании экономию в размере 20 000 долларов в год. В этом случае анализ затрат и выгод может выглядеть следующим образом: Анализ затрат и выгод корпорации Acme для новой системы управления запасами за 5-летний период 1. ЗАТРАТЫ a. Компьютер и периферийные устройства .......................... $300,00 b. Программное обеспечение .......................................... 110,00 c. Установка и техническая поддержка ................ 90,00 ========= общий объем инвестиций ...........$500,00 2. ПРЕИМУЩЕСТВА a. Сокращение персонала .......... $450,000 b. Сокращение времени простоя ...... 144 000 c. Экономия затрат на складские запасы.... 100,000 ======== общая экономия ............... $694,000 Баланс ......................$194,000 В случае Acme Corporation выгоды за пятилетний период относительно скромны. Поэтому вполне вероятно, что высшее руководство Acme хотело бы увидеть более подробный и точный анализ, прежде чем приступать к капитальному ремонту существующей системы компании. С другой стороны, некоторые системы могут быть четко и окончательно экономически обоснованы. При других обстоятельствах разработка и внедрение компьютеризированного управления запасами привели бы к экономии, которая не оставляет сомнений в выгодах проекта, даже при... 756 Глава 27 не принимая во внимание менее ощутимые факторы, такие как улучшения в работе дистрибуции . 27.2 Анализ и спецификация требований Как только осуществимость предлагаемой системы установлена, следующей задачей, которую должен выполнить аналитик, является определение требований к системе. Анализ требований, или разработка требований, как его иногда называют, считается критическим этапом разработки компьютерных систем с момента появления первоначальной концепции инженерной методологии разработки программного обеспечения. Фундаментальное понятие состоит в создании однозначной и непротиворечивой спецификации, которая описывает основные функции предлагаемой системы. Другими словами, этап спецификации процесса разработки системы должен описывать, что система должна делать. Как это сделать, решается на этапе проектирования. 27.2.1 Этап анализа требований Прежде чем можно будет определить системные требования, необходимо проанализировать интерфейс между системой и реальным миром. На основе этого интерфейса разрабатывается набор спецификаций
требований к программному обеспечению. Документы, содержащие спецификацию программы, играют решающую роль на протяжении всего процесса разработки. Например, разработка программной системы для дистанционного управления работой зондирующего аппарата для работы на поверхности планеты Марс должна основываться на детальном анализе требований к интерфейсу. В этом случае следующие элементы можно считать критическими: 1. Какую информацию возвращают датчики на марсианском зонде. 2. Какие органы управления транспортным средством, установленные на датчике, регулируются программным устройством. 3. Каков промежуток времени между восприятием датчиками зонда и приемом земной системой. 4. Каков промежуток времени между командой, выданной земной системой, и ее выполнением ответом зонда. Анализ этих данных помогает определить эксплуатационные характеристики системы. В этом случае задержка в приеме информации и передаче команд может потребовать, чтобы при обнаружении препятствий определенного типа транспортное средство остановилось до получения командного сигнала. В этом случае этап анализа требований служит для определения спецификаций системы. Конкретные рекомендации по спецификации системы вытекают из этапа анализа требований. sis. В частности, это может относиться к трудностям, компромиссам и конфликтующим ограничениям, а также к функциональным и нефункциональным требованиям. Функциональные требования - это те, которые абсолютно необходимы, в то время как нефункциональные требования - это просто желательные характеристики. В этом смысле функциональным требованием марсианского зонда могло бы быть то, чтобы аппарат не был поврежден препятствиями на своем пути. Нефункциональным требованием может быть то, что при наличии нескольких возможных безопасных путей к точке назначения программное обеспечение выбирает тот, который может быть пройден за наименьшее время. Описание и спецификация 757 Участие клиента / пользователя Идеальный сценарий заключается в том, что заказчик или конечный пользователь системы предоставляет подробную спецификацию. В этом “аккуратном” мире все, что нужно сделать разработчику, - это внедрить систему в соответствии с этими спецификациями, гарантируя таким образом полное удовлетворение потребностей клиента. В действительности заказчик / пользователь часто имеет немногим больше, чем схематичное представление о предполагаемой системе; ее спецификация должна быть разработана посредством серии интервью, предложений, моделей и изменений. Коммуникационные навыки и опыт системного анализа являются важными факторами успеха на этом сложном и критическом этапе. Разработчик спецификации систем не должен предполагать, что клиент на самом деле не знает, чего он или она хочет. Это система клиента, а не разработчика. Новые системы часто сталкиваются со значительным сопротивлением со стороны сотрудников, которые должны посвящать время и усилия обучению их работе, часто с небольшой компенсацией или вообще без нее . Кроме того, сотрудники могут чувствовать угрозу со стороны новой системы, которая может сделать их работу ненужной. Чем больше клиенты и пользователи будут участвовать в спецификации и определении системы, тем меньше сопротивления они будут оказывать ее внедрению. Если и есть какой-то секрет того, чтобы быть успешным аналитиком, то он заключается в максимальном участии клиента на этапах спецификации, проектирования, разработки и внедрения , что позволяет убедиться в том, что среди участников есть те, кто будет эффективными пользователями новой системы........... Прискорбная ситуация заключается в том, что разработчик изначально имеет дело с административной элитой, в то время как фактические системные операторы, администраторы и пользователи становятся видимыми гораздо позже в процессе разработки. Виртуальный клиент Хотя во многих проектах разработки есть четко идентифицируемый заказчик, в других роль клиента не совсем видна. Например, компания-разработчик программного обеспечения, разрабатывающая программу, предназначенную для продажи широкой публике, может не иметь видимого клиента, который мог бы участвовать в этапах спецификации, проектирования и разработки системы. Другой
пример: исследовательская группа в университете или частной фирме часто сталкивается с задачей разработки программы, для которой нет пользователя или клиента непосредственно под рукой. Элемент клиент/пользователь играет такую важную роль в процессе разработки, что в этих случаях может быть целесообразно изобрести какого-нибудь виртуального участника, который будет играть эту роль. В контексте исследований и разработок директор команды иногда берет на себя эту функцию. Или, возможно, более высокий уровень управления на некоторое время будет играть роль адвоката дьявола. Подобно тому, как разделение властей обеспечивает функционирование демократии, разделение интересов между клиентами и разработчиками способствует созданию более совершенных систем. Когда эта двойственность не существует естественным образом, одним из вариантов может быть ее фиктивное создание. 27.2.3 Этап спецификации Как только системные требования определены, аналитик создает список спецификаций программы. Эти спецификации служат для разработки, кодирования и тестирования программного обеспечения 758 Глава 27 сформулируйте программу и, в конечном счете, подтвердите все усилия по разработке; их важность трудно переоценить. Поскольку спецификации выводятся непосредственно из документов по анализу требований, предварительным шагом к составлению списка спецификаций является проверка правильности этапа анализа требований. Какой бы метод или обозначение ни использовались при анализе требований, должны быть достигнуты следующие цели: 1. Определены содержание и поток информации. 2. Процессы определены. 3. Установлены системные интерфейсы. 4. Нарисованы уровни абстракции, а задачи обработки разделены на функции. 5. Составлена схема основных элементов системы и ее реализации. Мы можем придерживаться различной степени формальности и строгости при составлении фактических спецификаций. Многие трудности в проектировании системы и кодировании могут быть связаны с неточными спецификациями. Английский язык (фактически, любой естественный язык) содержит термины и выражения, которые требуют интерпретации. Например: утверждение о том, что процедура вычисления математических функций должна выполнять это с “максимально возможной степенью точности”, может показаться строгой спецификацией. Однако во время проектирования системы и кодирования выражение “максимально возможная степень предварительного решения” должно интерпретироваться как означающее одно из следующих значений: 1. Точность самого точного компьютера в мире. 2. Точность самого точного станка определенного типа. 3. Высочайшая точность, которая может быть достигнута на любом языке высокого уровня. 4. Высочайшая точность, которая может быть достигнута на конкретном языке программирования. 5. Высочайшая точность, которая может быть достигнута на определенном станке. 6. Высочайшая точность, которая потребуется самому требовательному пользователю или приложению tion. 7. Высочайшая степень точности, которая потребуется обычному пользователю или приложению tion. Этот список можно было бы легко расширить, включив в него множество других альтернатив. С другой стороны, в спецификациях могло бы быть указано, что математические функции будут вычисляться “таким образом, чтобы результирующая ошибка не превышала 20 единиц времени работы машины ”. В этом случае во время разработки или кодирования возможна очень незначительная интерпретация. Мехкроме того, спецификация может позднее использоваться для проверки полученного продукта путем выполнения тестов, которые удостовериться в том, что числовой ошибке математические расчетырасчеты в пределах допустимой ошибки диапазона. В последние годы сообщества разработчиков программного обеспечения и программистов перешли к более строгим и формальным методам спецификации, некоторые из которых обсуждаются далее в этом разделе. Аналитик должен остерегаться неточных терминов и выражений в документах спецификации и должен стремиться описывать имеющиеся проблемы и задачи в как можно менее однозначных выражениях. Описание и спецификация
759 Документ о спецификациях программного обеспечения Документ спецификаций программного обеспечения (SSD), также называемый Спецификацией требований к программному обеспечению , является кульминацией и завершением системного анализа. К элементам, всегда присутствующим в твердотельном накопителе, относятся следующие: 1. Информационное описание 2. Функциональное описание 3. Список требований к производительности 4. Список конструктивных ограничений 5. Параметры тестирования системы и критерий для валидации Национальное бюро стандартов, IEEE и другие предложили форматы для SSD. Таблица 27.2 представляет собой упрощенный обзор основных тем, которые необходимо изучить в этом документе. Таблица 27.2 Краткое изложение документа по спецификациям программного обеспечения I. Введение 1. Цель и область применения 2. Обзор системы 3. Общее описание и ограничения II. Стандарты и соглашения III. Элементы среды 1. Аппаратная среда 2. Программная среда 3. Интерфейсы IV. Спецификации программного обеспечения 1. Информационный поток 2. Информационное содержание 3. Функциональная организация 4. Описание операций 5. Описание функций управления 6. Описание поведения системы 7. База данных и структуры данных V. Тестирование и валидация 1. Параметры производительности 2. Тесты и ожидаемый ответ 3. Протокол валидации VI. Приложения 1. Глоссарий 2. Список литературы Хедингтон и Райли предлагают план спецификации программы, состоящий из шести этапов, состоящий из следующих частей: 1. Название задачи 2. Описание 3. Входные спецификации 4. Выходные спецификации 760 Глава 27 5. Обработка ошибок 6. Пример выполнения программы (набор тестов) Хотя это упрощение предназначено для спецификации небольших проектов в контексте курса бакалавриата по программированию на C ++, оно охватывает фундаментальные элементы. Даже если он не используется в качестве модели, может быть хорошей идеей сверить фактическую спецификацию с этим планом. 27.3.4 Формальные и полуформальные спецификации Авторы методов формальных спецификаций постулат о том, что программное обеспечение девелопмента будут революционизированы с помощью описания и моделирования программ с использованием строго Синналоговые и семантики на основе математической нотации. Основы формальной спецификации заложены в теории множеств и исчислении предикатов. Основными преимуществами
формальных спецификаций являются их отсутствие двусмысленности, а также их согласованность и полнота, что исключает множество возможных интерпретаций, которые часто возникают в результате описания проблем на естественном языке. Из этих атрибутов отсутствие неопределенности является результатом использования математических обозначений и логической символики. Последовательность может быть обеспечена путем математического доказательства того, что утверждения в спецификации могут быть выведены из исходных фактов. Однако полноту трудно обеспечить даже при использовании формальных методов спецификации, поскольку элементы программы могут быть намеренно или случайно опущены из спецификации. В спецификации могут быть использованы формальные методы спецификации. В спецификации могут быть использованы формальные методы спецификации. Оригинальную идею применения методов исчисления предикатов к задачам в вычислительной технике и, в частности, к формальной разработке программ, обычно приписывают профессору Эдсгеру В. Дейкстре (Техасский университет, Остин, Техас, США), который добился значительной известности в области структурированного программирования , впервые изложив свои идеи в книге под названием "Дисциплина программирования". Профессор Дэвид Грайс (Корнельский университет, Итака, Нью-Йорк, США) и Эдвард Коэн также внесли значительный вклад в эту область. Один из наиболее интересных постулатов, выдвинутых сторонниками формальных спецификаций , заключается в том, что после того, как программа математически описана, она может быть закодирована автоматически . Таким образом, утверждается, что формальные спецификации в конечном итоге приведут к автоматизированному программированию и, таким образом, к окончательному разрешению кризиса программного обеспечения. В этой книге мы решили не обсуждать детали официальных спецификаций по следующим причинам: 1. Пока что жизнеспособность этого метода была доказана только для довольно небольших программ , имеющих дело с проблемами специфического и ограниченного характера. 2. Использование этого метода предполагает математическую зрелость и подготовку в области формальной логики. Считается, что его трудно выучить и использовать. 3. Хотя формальные спецификации, проверка программ и автоматическое кодирование получили значительное распространение в последние годы, эти методы еще не утвердились в основном направлении системного анализа и программирования. Многие по-прежнему считали их скорее академической доработкой, чем практической процедурой. Описание и спецификация 761 27.3.5 Обозначения утверждений Хотя методы формальной спецификации еще не достигли статуса практической методологии, применимой в общем системном анализе и программировании, существует мало сомнений в том, что уменьшение двусмысленности и непоследовательности естественных языков является желательным достижением на этапе разработки спецификаций программного обеспечения. Некоторые современные авторы учебников по программированию (включая Хедингтона и Райли) приняли метод документирования программ, который также может служить моделью для полуформальных спецификаций. Этот метод, иногда называемый обозначением утверждений, состоит из определенного стиля комментариев, которые вставляются в код таким образом, чтобы определить состояние вычислений. Хотя утверждения изначально были предложены как метод документирования кода, нотация также может быть использована как полуформальный метод спецификации на этапе анализа. Описаны следующие типы утверждений: УТВЕРЖДАТЬ Этот заголовок комментария описывает состояние вычислений в определенной точке кода. До того, как была предложена эта нотация, некоторые программисты вставляли комментарии в свой код с заголовками типа AT THIS POINT:, которые выполняли функционально эквивалентную цель. Идея заголовка ASSERT: состоит в том, чтобы указать то, что, безусловно, известно о переменных и других программных объектах, чтобы эту информацию можно было использовать при разработке и тестировании следующего кода. Рассмотрим следующий пример C ++: функция с именем Power() реализована таким образом, что она возвращает значение с плавающей точкой, результат возведения базового значения типа float в целочисленную степень.
Результирующий код может быть задокументирован следующим образом: Мощность (база, индекс); // УТВЕРЖДАТЬ: // Мощность() == база повышена до индекса // (переменные base и index остаются неизменными при вызове) Определенные символы и термины часто используются в контексте утверждений. Например, достаточно, термин Assigned используется для представления переменных и констант, инициализированных значением. Это исключает возможность того, что эти элементы содержат “мусор”. В утверждениях C ++ символы && часто используются для представления логических И, в то время как символ || представляет логическое ИЛИ относительно условий утверждения. Символ == используется для документирования определенного значения, а символ ?? указывает, что значение неизвестно или не поддается определению в данный момент. Наконец, символ —, или слово Implies, иногда используются для представления логической импликации. Программист также должен свободно использовать другие символы и термины из математики, логики или из конкретного контекста проекта, при условии, что они ясны, недвусмысленны и последовательны. INV Этот заголовок комментария представляет инвариант цикла. Это утверждение, которое используется для описания состояния вычисления в определенной точке цикла. Цель инварианта цикла состоит в том, чтобы документировать основные задачи, которые должны выполняться циклом. Поскольку инвариант вставляется внутри тела цикла, его утверждение должно быть истинным до выполнения тела, во время каждой итерации цикла и сразу после тела цикла 762 Глава 27 выполнен в последний раз. Следующий пример на C ++ представляет собой цикл для инициализации нулем всех элементов массива целых чисел. // УТВЕРЖДАТЬ: // Все задано1[0 .. sizeof (Set1)/ sizeof (int)] == ?? для (int i = 0, i (sizeof (Set1) / sizeof (int)), i++) // INV: // 0 i элементов в массиве Set1[] set1(i) = 0; // УТВЕРЖДАТЬ: // Все задано1[0 .. sizeof (Set1) / sizeof *int)] == 0 В этом примере количество элементов в массиве определяется во время компиляции с помощью оператора C ++ sizeof. Выражение: размер (Set1) возвращает количество байт в массиве Set1. Это значение делится на количество байт в типе данных массива, чтобы определить количество элементов. Хотя этот способ определения количества элементов в массиве обычно допустим в C ++, его следует использовать с осторожностью, поскольку вычисление допустимо только в том случае, если массив виден sizeof в конкретном месте кода, где он используется. Инвариант цикла утверждает, что в этой точке тела цикла индекс массива (переменная i) находится в диапазоне от 0 до числа элементов массива, которые могут быть выведены из инструкции цикла. PRE и POST Эти заголовки комментариев являются утверждениями, связанными с подпрограммами. В C ++ они используются для установления условий, которые ожидаются в точках входа и выхода функции . До того, как была предложена нотация утверждений, некоторые программисты использовали термины ПРИ ВХОДЕ: и ПРИ ВЫХОДЕ:. Утверждение PRE: описывает элементы (обычно переменные и константы), которые должны быть предоставлены вызывающей стороной, а утверждение POST: описывает состояние вычисления в момент завершения функции ее выполнения. Таким образом, условия PRE: и POST: представляют собой условия контракта между вызывающим объектом и функцией, заявляющие, что если вызывающий объект гарантирует, что утверждение PRE: истинно во время вызова, функция гарантирует, что утверждение POST: удовлетворено во время возврата. Например, следующая функция с именем Swap() обменивает значения в двух целочисленных переменных. аннулирующий обмен (int& var1, int& var2) // ПРЕДВАРИТЕЛЬНО:
Присвоенный var1 && var2 // СООБЩЕНИЕ: var1 == var2 && var2 == var1 { int temp = var1; var1 = var2; var 2 = temp; возврат; } FCTVAL Это утверждение также используется в контексте подпрограмм для указания возвращаемого значения. В C ++ FCTVAL представляет значение, возвращаемое функцией и связанное с именем функции. Поэтому его не следует использовать для представления переменных, изменяемых Описание и спецификация 763 определяется функцией. Например, следующая тривиальная функция возвращает среднее значение трех действительных чисел, переданных вызывающим объектом. float Avrg (номер поплавка 1, номер поплавка 2, номер поплавка 3) // ПРЕДВАРИТЕЛЬНО: Присвоен номер 1 && номер 2 && номер 3 // номер 1 + номер 2 + номер 3 // СООБЩЕНИЕ: FCTVAL == -----------------// 3 { возврат ((num1 + num2 + num3) / 3)); } 27.4 Инструменты для моделирования процессов и данных Технические методологии разработки системы предполагают, что документы, полученные в результате этапа анализа, служат основой для этапа проектирования. Следовательно, чем более подробны, четко сформулированы и понятны документы анализа, тем более полезными они будут при разработке программы. Одним из способов добиться этого является использование инструментов моделирования, которые позволяют представить суть системы, обычно в графических терминах. За прошедшие годы было предложено множество таких инструментов, и два из них получили широкое признание, а именно диаграммы потоков данных и диаграммы взаимосвязей сущностей. Диаграммы потоков данных возникли в результате структурированного анализа и часто связаны с ним. Диаграммы сущностей-отношений связаны с системами баз данных и объектно-ориентированными методами. Тем не менее, мы считаем, что любой из методов полезен в зависимости от контекста или школы анализа, в которой он возник. Фактически, диаграммы потоков данных и взаимосвязи сущностей использовались при моделировании процессов и потоков данных как в рамках структурированного анализа, так и в рамках объектно-ориентированных цифровых систем. Моделирование системы может осуществляться с использованием различной степени абстракции. Один тип модели, иногда называемый физической моделью или моделью реализации, рассматривает технические детали, а также сущность системы. Блок-схемы программ часто рассматриваются как инструмент моделирования реализации, поскольку блок-схема определяет, как конкретный процесс может быть фактически закодирован. С другой стороны, основные или концептуальные модели не зависят от реализации. Их цель - изобразить, что должна делать система, не затрагивая вопроса о том, как она должна это делать. Хотя модели реализации полезны при документировании и объяснении существующих систем, среди специалистов по системному анализу существует общее мнение, что на этапе анализа предпочтение следует отдавать концептуальным моделям. Следующие аргументы из десяти перечислены в пользу концептуальных моделей: 1. Некоторые неправильные решения, возникающие в результате моделей, зависящих от реализации, могут быть приписаны предвзятости или невежеству разработчиков. Концептуальная модель, с другой стороны, способствует творчеству и способствует независимому и свежему подходу к решению проблемы. 2. Чрезмерная озабоченность деталями реализации при спецификации и анализе системы время отвлекает наше внимание от основных принципов. Это часто приводит к отсутствию основных требований в спецификации. 764
Глава 27 3. Модели, зависящие от реализации, требуют технического жаргона, который часто создает коммуникационный барьер между клиентами и разработчиками. Целью этого моделирования может быть как существующая, так и предлагаемая система. В этом отношении мы должны иметь в виду, что фундаментальной целью моделирования существующей системы является извлечение из нее уроков. Процесс обучения может быть сведен к тому, чтобы избегать дефектов и использовать преимущества желаемых функций. 27.4.1 Диаграммы потоков данных В своей работе по структурированному анализу Демарко предполагает, что одним из дополнений, необходимых на этапе анализа, является графическое обозначение для моделирования информационного потока. Затем он переходит к созданию некоторых основных графических символов и предлагает эвристику, которую можно использовать с этими символами. Несколько лет спустя Гейн и Сарсон усовершенствовали это обозначение, назвав его диаграммой потока данных или DDF. Диаграмма потока данных - это инструмент моделирования процесса, который графически отображает ход передачи данных по системе и операции обработки, выполняемые с этими данными. На рисунке 27.3 показаны основные символы и обозначения, используемые в диаграммах потоков данных. Рисунок 27.3 Символы Гейна и Сарсона для диаграмм потоков данных Один из вариантов, часто используемый в нотации Гейна и Сарсона, заключается в представлении процессов с помощью кругов (иногда называемых пузырьками).). Действительным оправданием этой альтернативы является то, что круги рисовать легче, чем закругленные прямоугольники. Процессы - это основная операция, изображенная на диаграммах потоков данных. Процесс часто описывается как действие, выполняемое над входящими данными с целью их преобразования. В описании и спецификации 765 Операция или действие, выполняемое над входящим потоком данных для создания исходящего потока данных Элемент данных или элементы, поступающие в процесс или из процесса Хранилище данных, используемое одним или несколькими процессами Внутренний или внешний производитель информации. Внутренние и внешние агенты находятся за пределами моделируемой системы Внешний или внутренний агент Обрабатывать Хранилище данных в этом смысле процесс может быть выполнен человеком, роботом или программным обеспечением. Процессы обычно обозначаются глаголами действия, такими как Pay, Deposit или Transform, за которыми следует предложение, описывающее характер выполняемой работы. Однако в DFDS более высокого уровня мы иногда видим общий процесс, описываемый существительным или именной фразой, такой как система учета или процесс улучшения изображения. Процессы маршрутизации, которые не выполняют никаких операций с данными, обычно опускаются в основных DFDS. Операции, выполняемые процессами, можно резюмировать следующим образом: 1. Выполните вычисления 2. Примите решение 3. Измените поток данных, разделяя, комбинируя, фильтруя или суммируя его исходные компоненты компоненты Внутренние или внешние агенты или сущности находятся за пределами границ системы. Они обеспечивают ввод и вывод данных в систему. В этом смысле их часто рассматривают либо как источники, либо как пункты назначения. Агент является внешним, если он физически находится вне системы, например, клиент или поставщик. Внутренние агенты расположены внутри организации, но логически находятся за пределами системы. Например, другой отдел внутри компании может рассматриваться как внутренний агент.
В DFD можно варьировать уровень абстракции и увеличение деталей. Некоторые авторы предлагают обозначение конкретных уровней абстракции. В этом смысле DFD уровня 0, называемый фундаментальной моделью системы, состоит из одного пузырька процесса, представляющего всю программную систему. DFD уровня I может отображать пять или шесть процессов, которые включены в один процесс, представленный в пузырьке уровня 0. Хотя представление о том, что несколько DFD могут использоваться для представления различных уровней абстракции, полезно, требование о том, чтобы с каждым уровнем было связано определенное число процессов, может быть слишком ограничительным. На рисунке 27.4 показан начальный уровень детализации для представления системы спутниковой съемки. Рисунок 27.4 Исходная модель системы спутниковой съемки 766 Глава 27 Спутники A Клиент Спутники B Спутники C Банк изображений Наземная станция raw изображение данные отформатированные изображения НАСА Изображение Обработка Система Изображение Доставка Система Обратите внимание, что на рисунке 27.4 пузырь процесса был доработан с помощью метки заголовка, которая добавляет информацию о процессе. В этом случае заголовок указывает на то, кто должен выполнять процесс. Также обратите внимание, что процессы представлены словосочетаниями с существительными, что согласуется с общим характером диаграммы. Хотя эта начальная модель предоставляет некоторую фундаментальную информацию о системе, в ней отсутствует множество важных деталей. Например, один спутник может быть оснащен несколькими чувствительными устройствами, которые генерируют различные типы данных изображения, каждое из которых требует уникальных операций обработки. Или конкретный спутник может выдавать изображения, которые являются секретными, поэтому они должны быть помечены как таковые, чтобы система доставки не делала их доступными для неквалифицированных клиентов. На рисунке 27.5 показано усовершенствование системы обработки изображений второго уровня. Рисунок 27.5 Уточненная модель системы обработки спутниковых изображений Уровень детализации, показанный DDF на рис. 27.5, намного выше, чем на рис. 27.4. На основе уточненной модели мы можем фактически идентифицировать различные приборы, установленные на борту спутников, и операции, которые выполняются с данными изображения, получаемыми каждым прибором. Однако фактические детали обработки по-прежнему опущены на диаграмме, которые лучше отложить до этапов проектирования системы и внедрения. Обратите внимание, что на рисунке 27.5 названия процессов correct, format и record используются в качестве глаголов, что соответствует более высокому уровню детализации, показанному этой моделью. Обратите внимание, что названия спутников, инструментов и форматы изображений не точно соответствуют реальным. Моделирование событий Многие типы систем могут быть точно представлены путем моделирования потока данных, но некоторые не могут. Например, система охранной сигнализации управляется событиями, как и в случае со многими системами реального времени. Сначала Уорд и Меллор, а позже Хатли и Прибхай представили варианты диаграммы потока данных, которые подходят для представления систем реального времени
Описание и спецификация 767 Сб: Landsat XI (Прибор MSS) Сб: Landsat XI (прибор TM) Сб: МЕСТО 7 (Прибор для измерения ВСР) Сб: ИДЕТ 5 (Инструмент VTV) Банк изображений МС Sr aw данные цифровое MSS-изображение Данные изображения ВСР Изображение VTV аналоговое изображение VTV цифровое изображение ВСР цифровое изображение ТМ TM ge исправлено cted d ата ТМ сырое да та МС Sg eoco правильно dd ата Исправить Геопространственные Ошибки Формат MSS Изображение Формат ВСР Изображение Запись VTV Изображение Формат ТМ Изображения tems или для управляемых событиями элементов обычных систем. Расширения Уорда и Меллора к символам DFD показаны на рисунке 27.6. Рисунок 27.6 Расширения Уорда и Меллора к диаграммам потоков данных Результирующая модель, называемая блок-схемой управления или CFD, включает символы и функции обычного DFD, а также другие, которые позволяют представлять процессы управления и непрерывные потоки данных, необходимые для моделирования систем реального времени..........."..........." Оснащенные этим инструментом, мы теперь можем моделировать системы, в которых имеется комбинация данных и элементов управления. Например, система охранной сигнализации, установленная в доме или на предприятии, включает в себя обычный компонент потока данных, а также элемент потока управления. В этом случае обычный поток данных состоит из пользовательских операций, таких как активация и деактивация системы и установка или изменение пароля. Элементами потока управления являются сигналы, излучаемые датчиками в окнах и дверях, и механизм сигнализации, который запускается этими датчиками. На рисунке 27.7 представлен CFD системы охранной сигнализации. При изображении схемы охранной сигнализации на рисунке 27.7 мы объединили набор символов обычных DFD Гейна и Сарсона с расширениями, предложенными Уордом и Меллором. Комбинируя оба набора символов на схеме потока управления, мы можем смоделировать систему, которая содержит как обычный поток данных, так и элементы реального времени . Обратите внимание, что датчики и механизм сигнализации являются компонентом реального времени , поскольку состояние чувствительных устройств должно постоянно контролироваться. Панель управления и пользовательский интерфейс, с другой стороны, активируются повторным запросом пользователя и не требуют непрерывного потока данных. Однако элементы по требованию и в режиме реального времени иногда взаимодействуют. Например, при включении системы 768
Глава 27 Управляющая операция или действие. Несколько процессов в многозадачной системе. Непрерывный элемент данных или элементы, которые поступают в процесс или из него . Элемент управления или событие. Хранилище элементов управления, используемых одним или несколькими процессами . Контроль процесс Обрабатывать Управлять хранилищем при включении или выключении соответствующие сигналы должны быть отправлены на панель управления, а также на датчики и логику управления датчиками. Другое взаимодействие между подсистемами является результатом того факта, что состояние датчика отображается на панели управления. Поэтому логика мониторинга датчика отправляет непрерывный сигнал (двуглавая стрелка) в процесс обновления панели управления, чтобы состояние датчика отображалось в режиме реального времени. Рисунок 27.7 Блок-схема управления системой охранной сигнализации 27.4.2 Диаграммы сущность-взаимосвязь В настоящее время наиболее используемым инструментом моделирования данных является диаграмма сущностьвзаимосвязь (E-R). Эта нотация, впервые описанная в начале 1980-х годов Мартином и позже модифицированная Ченом и Россом, тесно связана с моделью сущностей-отношений, используемой при проектировании баз данных. Символы диаграммы E-R постепенно уточнялись и расширялись, так что метод можно использовать для представления даже самых мельчайших деталей схемы относительной базы данных. Однако в контексте общего системного анализа мы ограничим наше обсуждение фундаментальными элементами модели. Поскольку одной из фундаментальных операций большинства компьютерных систем является обработка данных и их хранение, моделирование данных часто является обязательным этапом на этапе системного анализа . Диаграммы E-R часто ассоциируются с проектированием базы данных, однако их основным назначением является общее представление объектов данных и их взаимосвязи. Поэтому методика вполне подходит для анализа данных и моделирования в любых Описание и спецификация 769 Датчики Управление панель Состояние датчика данные и команды конфигурация обновление Обновление статуса ВКЛЮЧЕНИЯ/ выключения команда включения / выключения датчиков обновить данные Обновление статуса сигнала тревоги пароль ВКЛЮЧЕНИЕ/ВЫКЛЮЧЕНИЕ запрос конфигурация dat a конфигурация запрос Сигнал тревоги Тревога Монитор датчики Обновление управление панель Интерфейс с пользователем Включить систему ВКЛ/ВЫКЛ проверка пароля Настройка
системы конфигурации информации контекст. Кроме того, нотация E-R может быть легко использована для представления объектов и иерархий объектных типов, что делает этот метод полезным в объектно-ориентированном анализе. Модель сущность-отношения довольно проста для понимания. Сущность или тип сущности - это “вещь” в реальном мире. Это может быть человек, реальный или концептуальный объект . Например, физическое лицо, дом, автомобиль, отдел компании pany и курс колледжа - все это типы сущностей. Каждый тип сущности связан с атрибутами, которые описывают его и применяются к нему. Например, тип объекта company employee имеет атрибуты имя, адрес, должность, зарплата и номер социального страхования . В то время как тип объекта college course имеет атрибуты name, number, creditits и время занятий. Обратите внимание, что entity employee является физическим лицом, в то время как курс entity college является абстрактным или концептуальным. Модель сущность-взаимосвязь также включает отношения. Связь определяется как ассоциация между сущностями. В этом смысле мы можем сказать, что типы сущностей employee и department связаны отношением works for , поскольку в схеме базы данных каждый сотрудник работает в отделе. По тому же принципу мы можем сказать, что works on relationship связывает сотрудника с проектом, таким образом связывая типы сущностей employee и project. На рисунке 27.8 показаны элементарные символы, используемые на диаграммах E-R. Рисунок 27.8 Символы, используемые в диаграммах сущностей-Взаимосвязей Как показано на рис. 27.8, общепринятой практикой является ввод объектов и отношений заглавными буквами, в то время как имена атрибутов имеют начальные заглавные буквы. Для типов сущностей обычно выбираются существительные в единственном числе , а для отношений - глаголы. Атрибуты также являются существительными, описывающими характеристики типа сущностей. Тип сущности обычно имеет ключевой атрибут, который уникален и отличен для данной сущности. Например, номер социального страхования был бы ключевым атрибутом типа объекта employee, поскольку никакие два сотрудника не могут иметь один и тот же номер социального страхования. По тому же принципу название должности и зарплата не были бы ключевыми атрибутами, поскольку несколько сотрудников могли бы обладать этой характеристикой. Обратите внимание, что, хотя у некоторых организаций может быть больше 770 Глава 27 Связь между сущностями Свойство, связанное с объектом Указывает на участие между объектами, отношениями и атрибутами Реальный или абстрактный объект, о котором мы храним данные. Также называется типом сущности СУЩНОСТЬ ОТНОШЕНИЯ Атрибут чем один ключевой атрибут, другие не имеют ни одного. Тип объекта без ключевого атрибута иногда называют слабым объектом. Ключевые атрибуты обычно подчеркиваются в диаграмме E-R. Что касается взаимоотношений, мы можем выделить две степени участия: полное и частичное. Например, если схема требует, чтобы каждый сотрудник работал в подразделении, то отношения works for предполагают полное участие. В противном случае степень участия является частичной. Ограничения мощности относятся к количеству экземпляров, в которых может участвовать объект. В этом смысле мы можем сказать, что отношение works for для типов DEPARTMENT:EMPLOYEE имеет отношение мощности 1: N; в этом случае employee работает только в одном отделе, но в отделе может быть более одного employee. На рис. 27.9 показана диаграмма сущность-отношения для схемы небольшой компании. Рисунок 27.9 Диаграмма Сущность-отношения для компании Диаграммы читаются слева направо или сверху вниз. Это соглашение важно : например, когда диаграмма на рис. 27.9 читается слева направо, именно часть управляет проектом, а не наоборот.
Описание и спецификация 771 СОТРУДНИК ПРОЕКТ ОТДЕЛ Имя МЯ Кодовый номер. Расположение Последние Первый Имя Имя Адрес Зарплата SSN РАБОТАЕТ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ РАБОТА_ ДЛЯ Глава 28 Объектно-ориентированный подход Краткое содержание главы Эта глава представляет собой введение в парадигму программирования и разработки программного обеспечения , известную как объектная ориентация. В нем описывается история и эволюция этого подхода, обсуждаются его фундаментальные особенности и даются рекомендации о том, когда он подходит. 28.0 История и хронология Хотя объектно-ориентированный подход появился в мейнстриме вычислительной техники в начале 1980-х годов, его происхождение восходит к 1960-м годам. С объектно-ориентированными системами связаны три концепции: абстракция данных, наследование и динамическое связывание. Первые представления об абстракции данных появились благодаря Кристен Найгаард и Оле-Йохану Далю, работающим в Норвежском вычислительном центре. Найгаард и Дал разработали компьютерный язык под названием SIMULA I, предназначенный для использования в моделировании и исследовании операций. Несколько лет спустя за SIMULA I последовала SIMULA 67, которая фактически была продолжением ALGOL 60. Хотя SIMULA 67 оказала незначительное влияние на разработку программы, ее историческое значение заключалось в введении конструкции класса. Основная идея класса - это форма или шаблон, который объединяет данные и процедуры обработки. Обратите внимание, что это определение класса как шаблона подразумевает, что класс является формальной конструкцией, а не конкретной сущностью. Программист создает экземпляры класса по мере необходимости. В современной терминологии позиции класса называются объектами. Интересно отметить, что, хотя SIMULA 67 фактически реализовала абстракцию данных по классам, эта связь была официально признана только в 1972 году. Но хотя абстракция данных явно является составной частью SIMULA 67, наследование реализовано ограниченным образом, а динамическое связывание вообще отсутствует. Это был Алан Кей, который первым описал полностью работоспособный объектно-ориентированный язык. Кей предполагал, что настольные компьютеры станут очень мощными машинами с мегабайтами памяти и выполнением миллионов инструкций в секунду; поскольку эти 773
машины использовались бы в основном непрограммистами, пришлось бы разработать мощный графический интерфейс , чтобы заменить неудобные телетайпные терминалы и методы пакетной обработки того времени. На самом деле потребовалось более десяти лет, чтобы эти машины стали доступны . Оригинальным решением Kay для создания дружественного и простого в использовании интерфейса была система обработки информации Dynabook. Dynabook был основан на визуализации рабочего стола, на котором некоторые документы были видны, а другие были частично прикрыты. Пользователь выбирал документы с помощью клавиатуры. Сенсорный экран позволял перемещать документы на столе. Результат был очень похож на некоторые современные оконные среды. Обработка должна была выполняться языком программирования Flex, который Кей помог разработать и который был основан на SIMULA 67. В конце концов Кей перешла на работу в исследовательский центр Xerox Palo Alto, где эти идеи развились в управляемый мышью интерфейс с выводом данных и язык программирования Smalltalk. Обоим этим концепциям, Windows и объектно-ориентированным системам, было суждено стать главными движущими силами в вычислительных технологиях. Первым полностью работоспособным объектно-ориентированным языком программирования, который стал доступен, был Smalltalk 80. Команду разработчиков Smalltalk 80 в Xerox PARC возглавляла Адель Голдберг, и вскоре язык де-факто стал описанием объектноориентированного программирования. С тех пор было разработано несколько объектно-ориентированных языков . Возможно, наиболее заметными из них являются Eiffel, CLOS, ADA 95 и C ++. В Smalltalk 80 применен подход с динамической типизацией, в котором нет системы типизации вне структуры классов. Eiffel - это тоже чистая реализация, но со статической типизацией. Другие - это гибридные языки, которые используют смесь статической и динамической типизации и имеют системы типов, выходящие за рамки структур классов. 28.1 Объектно-ориентированные основы Разные объектно-ориентированные языки реализуют парадигму по-разному и с разной степенью концептуальной чистоты. В последние годы разработка и вывод на рынок объектно-ориентированных продуктов превратились в крупное коммерческое предприятие. Таким образом, к естественной неопределенности тщательно продуманной концепции мы должны добавить шумиху коммерческих предпринимателей и промоутеров. Результирующая панорама объектно-ориентированных методов представляет собой сбивающую с толку смесь обоснованных претензий и неоправданных похвал, часть из которых проистекает из реальных достоинств новой и полезной модели, часть из чистого меркантилизма. В следующих разделах мы попытаемся предоставить читателю описание основ объектно-ориентированного подхода и указать, в чем, по нашему мнению, заключаются сильные стороны этой модели. Поскольку объектно-ориентированные языки программирования ming различаются по чистоте обращения с лежащей в их основе парадигмой, мы сосредоточили наше описание на конкретной: C ++. Это не подразумевает суждения о пригодности подхода C ++, но определяется тем фактом, что программирование на C ++ является центральной темой этой книги. 774 Глава 28 28.1.1 Постановка проблемы и решение Компьютерная программа, будь то основная операционная система или небольшое приложение, является решением проблемы реального мира в машинном коде. Следовательно, в начале любого программного проекта есть набор задач, который его определяет, а в его завершении есть набор так называемых решений в виде группы инструкций, которые могут быть выполнены цифровой вычислительной машиной. Искусство и наука программирования - это переход от набора проблем реального мира к набору решений в машинном коде. В этой широкой концептуализации программирование включает этапы анализа, проектирования, кодирования и тестирования программного продукта. Рисунок 28.1 графически представляет эту концепцию. Рисунок 28.1 Эволюция программного проекта Плавный переход от белого к черному на рисунке 28.1 указывает на то, что различные
фазы разработки программы (анализ, проектирование, кодирование и тестирование) не имеют дискретных границ. Трудно точно определить, где заканчивается одна фаза и начинается следующая . Более того, в конкретном проекте некоторые фазы (за исключением фазы кодирования) могут вообще отсутствовать. В крайнем случае программа развития разработана на кодирование в одиночку, без анализа, проектирования или испытаний. Фактически, некоторые программы разрабатываются таким образом; иногда успешно, но чаще безуспешно полностью. Было разработано множество методологий, облегчающих переход от реальной проблемы к цифровому решению в виде машинных инструкций. Ассемблеры, языки программирования высокого уровня, инструменты CASE, методологии анализа и проектирования, формальные спецификации и научные методы тестирования программ - все это усилия в этом направлении . Возможно, наиболее значительными упрощениями являются те, которые основаны на обобщенных абстрагированиях. stractions. В этом смысле структурированное программирование можно рассматривать как абстракцию, которая фокусируется на наборе решений, в то время как объектно-ориентированный подход фокусируется на наборе задач. Следовательно, проект, разработанный в терминах структурированного программирования, основан на модели набора решений, в то время как проект, проанализированный и разработанный с использованием объектно-ориентированных методов, сосредоточен на наборе проблем. Существует дополнительный фактор, который следует учитывать при этом сравнении объектно-ориентированных и структурированных методов. Разработка высокоуровневого объектно-ориентированного Объектно-ориентированный подход 775 Анализ Время Дизайн ЭТАПЫ РАЗРАБОТКИ ПРОГРАММЫ Кодирование Тестирование НАБОР ЗАДАЧ НАБОР РЕШЕНИЙ языки программирования служат для устранения разрыва между набором задач и набором решений. Именно одновременное использование объектно-ориентированного анализа, ориентированного проектирования и объектно-ориентированного программирования делает возможным плавный переход . В действительности, хотя использование унифицированной модели может быть значительным преимуществом, за это также приходится платить с точки зрения производительности программы и размера кода, а также в значительной степени с точки зрения сложности программирования. Чтобы использовать модель реального мира, мы вынуждены ввести несколько дополнительных уровней сложности на этапе кодирования. 28.1.2 Обоснование объектной ориентации Результат применения объектно-ориентированных методов можно резюмировать, заявив, что мы усложнили фазу кодирования, чтобы упростить этапы анализа и проектирования разработки программы. Защитники объектно-ориентированного подхода выдвигают следующие обоснованные утверждения: 1. Объектно-ориентированный анализ и методы проектирования облегчают взаимодействие с интерфейсами поскольку модель не требует технических знаний в области программирования. 2. Многие проблемы реального мира легче моделировать в объектно-ориентированных терминах, чем при использовании методов структурированного анализа и проектирования. 3. Объектно-ориентированные языки программирования способствуют повторному использованию кода, что в конечном итоге повышает производительность программиста. 4. Объектно-ориентированные программы имеют внутренние механизмы, которые делают их устойчивыми к изменениям, поэтому они лучше способны приспосабливаться к естественной изменчивости проблемной области. Часто возникает вопрос не о том, обоснованны ли эти утверждения, а о том, стоят ли они сложностей. Один из ответов можно найти в том факте, что сообщество программистов, похоже, склоняется в пользу объектно-ориентированного подхода.
28.2 Классы и объекты В контексте объектной ориентации объект - это концептуальная сущность, относящаяся к предметной области. Это абстракция, а не вещь в реальном мире. Заблуждением, встречающимся в некоторых книгах, является утверждение, что концептуальный объект в контексте парадигмы может считаться эквивалентным объекту в общепринятом смысле. Это неправильное представление может превратиться в главный камень преткновения на пути к пониманию объектной ориентации. В действительности объект OO представляет собой гибрид, который разделяет некоторые характеристики обычных объектов с особенностями компьютерной конструкции. Вместо того чтобы давать формальное определение объекту, мы начнем с довольно упрощенного и стереотипного перечисления его свойств: 1. Каждый объект принадлежит классу объектов. Объект не может существовать без класса, который определяет его. В этом смысле мы говорим, что объект является экземпляром класса. Чтобы понять разницу между классом и объектом, мы можем визуализировать класс как форму для печенья , а объект - как файл cookie. В терминах программирования класс относится к определению типа, а объект - к объявлению переменной. 776 Глава 28 2. Объект (и класс, который его содержит) - это инкапсуляция, которая включает данные и их связанные операции обработки. Элементы данных объекта называются его атрибутами, а операции обработки называются его методами. Атрибуты и методы являются членами класса: атрибуты являются элементами данных, а методы функциями-членами. 3. Атрибуты объекта служат для хранения и сохранения состояния объекта. Методы объекта od - это единственный способ доступа к его данным или изменения его состояния. Это достигается путем отправки сообщения объекту. 28.2.1 Классы и абстракция данных В обычном программировании мы часто думаем о типе данных как о простой группировке. В терминах, ориентированных на объект ob, мы должны мыслить в терминах как типа данных, так и связанных с ним операций. Например, в определенной аппаратной среде тип integer включает в себя целые числа в диапазоне от +32 767 до -32 768, а также операции, которые могут выполняться с целыми числами, такие как сложение, вычитание, умножение, деление, повторное сопровождение и сравнение. В этом же контексте мы можем сказать, что переменная - это позиция типа. Например, переменные num1 и num2 можно рассматривать как экземпляры (объекты) целочисленного типа. В C ++ мы объявляем и инициализируем эти переменные с помощью инструкций: число чисел 1 = 12; число чисел 2 = 6; Теперь мы можем выполнить любую из разрешенных операций над экземплярами (объявленными переменными) типа int. Например: int num3 = (num1 / num2) + num2; что привело бы к присвоению значения 8 ((12/6) + 6) вновь объявленной переменной num3. Что произойдет, если нам нужно будет оперировать с сущностью, которая не определена как тип данных в нашем языке? Например, предположим, что нам нужно манипулировать несколькими целочисленными значениями таким образом, чтобы последнее сохраненное значение было первым извлеченным. Этот механизм соответствует структуре данных, известной как стек, но C ++ не поддерживает тип данных stack. Чтобы устранить это ограничение, объектно-ориентированные языки имеют средства для реализации определяемых программистом типов данных, также называемых абстрактными типами данных или ADT. В некоторых языках (таких как C ++) абстрактный тип данных реализуется в терминах одного или нескольких встроенных типов данных плюс набора допустимых операций. В случае стека встроенный тип данных может представлять собой массив целых чисел, а операции могут называться Initialize(), Push() и Pop(). В C ++ программными структурами, используемыми для реализации абстрактных типов данных, являются классы и объекты. В предыдущем примере мы могли бы определить стек классов с массивом атрибутов int и методами Initialize(), Push() и Pop(). Этот класс служил бы шаблоном для создания столько стеков, сколько необходимо. Каждый Объектно-ориентированный подход
777 создание экземпляра класса было бы объектом типа stack . Это согласуется с понятием класса, эквивалентного типу, а объекта - переменной. 28.2.2 Классы и инкапсуляция В объектно-ориентированных терминах инкапсуляция связана с сокрытием информации, что является фундаментальным понятием парадигмы. Вместо того, чтобы основывать язык программирования на подпрограммы, что доля глобальных данных или получения данных прошло звонка, объекту-ориентированный подход утверждает представление, что данные и функции (атрибуты и методы) упаковываются вместе с помощью класса построить. Методы, которые видны пользователю, называются интерфейсом объекта. Фундаментальный механизм инкапсуляции состоит в сокрытии деталей реализации при одновременном подчеркивании интерфейса. Цель состоит в том, чтобы создать абстракцию, которая заставляет программиста мыслить концептуально. Первое золотое правило объектно-ориентированного программирования, сформулированное Gamma et. al, это: программируйте для интерфейса, а не для реализации. В типичном классе элементы данных невидимы для пользователя. В C ++ это достигается с помощью ключевого слова private (называемого спецификатором доступа). Как правило, члены класса являются либо частными, либо общедоступными, либо защищенными. Общедоступные члены видны клиенту и составляют интерфейс класса. Закрытые члены видны только другим членам класса. Защищенные члены связаны с наследованием, которое обсуждается далее в этой главе. Если элемент данных должен быть доступен клиенту, то обычно предоставляется функция, которая проверяет его и возвращает его значение. В C ++ ключевое слово const также может использоваться для исключения возможности несанкционированного изменения значения переменной. 28.2.3 Передача сообщений В структурированном программировании доступ к подпрограммам осуществляется различными способами: посредством перехода, вызова, перехвата, прерывания или, в распределенных системах, межпроцессного взаимодействия. Объектно-ориентированные системы делают фактическую процедуру доступа невидимой с помощью механизма, называемого передачей сообщений. Одно из преимуществ передачи сообщений заключается в том, что оно устраняет жестко закодированные программные структуры, такие как таблицы переходов, каскадные операторы if или конструкции case. Одним из недостатков жестко запрограммированных компонентов является то, что они должны обновляться при каждой модификации программы. Например, предположим, что используется обычный графический пакет с функциями для рисования линий, прямоугольников и кругов. На языке, не ориентированном на objобъекты, эти операции выполнялись бы отдельными подпрограммами. Как правило, существует процедура с именем DrawLine(), другая с именем DrawRectangle() и третья с именем drawCircle(). Где-то в программе логика выбора направила бы выполнение на соответствующую процедуру . В C эта логика может состоять из каскадных ifs или конструкции case. Если бы мы хотели расширить функциональность графического пакета, предоставив новую функцию для рисования эллипса, нам нужно было бы не только закодировать процедуру рисования эллипса, но также изменить логику выбора, чтобы выполнение было направлено на эту новую функцию по мере необходимости, что подразумевает изменение существующего кода. 778 Глава 28 Механизм передачи сообщений, используемый в объектно-ориентированных системах, автоматически направляет выполнение соответствующей функции-члену. Объектно-ориентированный подход к эквивалентному графическому пакету заключался бы в создании классов с именами Rectangle, Line и Circle, каждый с функцией-членом с именем Draw(). Когда мы хотим отобразить прямоугольник, мы отправляем сообщение о рисовании объекту класса rectangle, и так далее. Если теперь мы хотим расширить функциональность для рисования эллипса, мы можем создать класс Ellipse, в котором есть функция Draw(). Затем мы можем нарисовать эллипс, передав сообщение draw объекту типа Ellipse. Используя службы препроцессора и библиотеки времени выполнения, мы можем добавить новую функциональность, не изменяя существующий код. Очевидно, что этот метод добавления функциональности дает два основных преимущества: во-первых, мы можем избежать внесения новых дефектов в код, который уже был протестирован и отлажен; во-вторых, системы можно расширить, поставляя относительно небольшие модули
, содержащие новые функции. 28.2.4 Наследование Понятие наследования, возникшее в области представления знаний, на самом деле относится к наследованию свойств. Знания обычно организованы в иерархии, основанные на отношениях классов. (Обратите внимание, что сейчас мы используем слово класс в его более распространенном значении). Таким образом, мы говорим, что индивид наследует свойства класса, к которому он принадлежит. Например, животные дышат, двигаются и размножаются. Если рыба принадлежит к классу animal, то мы можем сделать вывод, что рыба дышит, двигается и размножается , поскольку эти свойства наследуются от базового класса. На рис. 28.2 приведена диаграмма наследования для некоторых классов животных. Рисунок 28.2 Диаграмма наследования Объектно-ориентированный подход 779 ЖИВОТНОЕ РЫБА ПТИЦА КАНАРЕЙКА дышит двигается размножается плавает летает поет имеет чешую имеет перья желтая Системы наследования используются в базах знаний для обеспечения самых высоких уровней абстракции. Например, база знаний о птицах сначала определяет черты, которые являются общими для всех птиц (летают и имеют перья), а затем черты конкретного вида (канарейка поет и желтая). Таким образом, вид canary наследует свойства своего родительского класса bird, который, в свою очередь, наследует свойства своего родительского класса animal. Это уменьшает размер базы знаний, требуя, чтобы общие свойства утверждались только один раз. Наследование также служит для поддержания согласованности базы знаний и упрощения программирования. В объектно-ориентированных системах под наследованием понимается возможность получения одним классом открытых членов своего родительского класса. Как и в базах знаний, принадлежность к классу обеспечивает высочайший уровень абстракции, уменьшает размер кода и упрощает программирование. Это позволяет построить иерархию классов, от самых общих до самых специфичных. На схеме, приведенной на рис. 28.2, мы могли бы сказать, что animal - это базовый класс, а bird, fish и canary - производные классы. Производный класс обычно включает в себя все функции своих родительских классов и добавляет некоторые собственные уникальные функции. Кроме того, производный класс имеет доступ ко всем открытым членам своего базового класса. Термины "родитель", "потомок" и "сиблинг" иногда используются для выражения различий в наследовании между классами. Как и в биологических системах, сиблинг-классы не наследуют друг от друга. Следовательно, свойства рыбы (на рис. 28.2) не наследуются ее родственным классом bird. 28.2.5 Полиморфизм Этимологически слово полиморфизм означает “множество форм”. В объектно-ориентированных терминах полиморфизм относится к нескольким методам, которые имеют одно и то же имя. Одним из способов реализации полиморфизма является перегрузка обычных функций или операторов. Например, стандартная библиотека C содержит три функции, которые возвращают абсолютное значение операнда: abs() ссылается на абсолютное значение целого числа, labs() - на абсолютное значение long, а fabs() - на абсолютное значение float. Программаmer должна учитывать тип данных при вычислении абсолютного значения. В объектно-ориентированных системах возможно перегрузить функцию abs(), чтобы использовать одно и то же имя для трех разных вычислительных процедур. Компилятор проверяет тип данных операнда, чтобы определить, какая из функций abs() имеет отношение к вызову. Таким образом, программисту не нужно ни запоминать три разных имени, ни беспокоиться о типе данных операнда. Стандартные операторы, такие как арифметические символы, также могут быть перегружены в C ++.
Абстрактные классы Полиморфизм - это нечто большее, чем просто перегрузка функций или операторов. В сочетании с наследованием полиморфизм обеспечивает механизм для достижения следующих желаемых свойств: 1. Позволяет создавать расширяемые программы 2. Помогает локализовать изменения и, следовательно, предотвращает распространение ошибок 3. Предоставляет общий интерфейс для группы связанных функций 4. Позволяет создавать библиотеки, которые легко расширять и повторно использовать, даже когда исходный код недоступен программисту 780 Глава 28 Все это стало возможным благодаря использованию специального типа функций, называемых виртуальными функциями, и конструкции, называемой абстрактным классом. Абстрактный класс - это класс, в котором определен интерфейс метода, но он не реализован. Этот пустой метод служит шаблоном для интерфейса семейства функций с одинаковым именем. Концепция абстрактных классов требует, чтобы полиморфные функции в базовом и производном классах были виртуальными функциями и чтобы они имели одинаковое имя и идентичные интерфейсы. Это отличает абстрактные классы от перегрузки функций, которая требует, чтобы либо количество типов параметров было разным. В контексте виртуальных функций слово переопределение используется для обозначения переопределения виртуальной функции производным классом. В C ++ существует два типа виртуальных функций: обычные и чисто виртуальные функции. Как обычные , так и чисто виртуальные функции поддерживают полиморфизм , а также ранний и latebinding.Forexample,agraphicspackagehasabaseclascalled GeometricalFigure и три производных класса, называемых Rectangle, Line и Circle, все из которых имеют функцию-член с именем Draw(). Когда мы хотим отобразить прямоугольную форму, мы отправляем сообщение Draw() объекту типа rectangle. Поскольку каждый объект знает класс, к которому он принадлежит, нет сомнений в том, куда будет направлено сообщение Draw() , даже несмотря на то, что существуют другие классы с методами с одинаковыми именами. Поскольку метод назначения определяется в соответствии с типом объекта, известным во время компиляции, считается, что этот тип полиморфизма использует раннее связывание. Однако, когда доступ к виртуальной функции осуществляется с помощью указателя, привязка откладывается до времени выполнения. Этот тип позднего связывания, или полиморфизма во время выполнения, является одним из наиболее мощных механизмов объектно-ориентированных систем. В этой книге не обсуждаются детали реализации полиморфизма и виртуальных функций в C++. 28.4 Обозначения для классов и объектов Похоже, что каждый автор книги по объектно-ориентированным темам чувствует необходимость разработать новую нотацию или внести модификацию в существующую. Таким образом, у нас есть обозначения Coad и Yourdon, обозначения Booch, обозначения Rumbaugh , а также полдюжины уточнений и модификаций. Эти вариации не всегда обусловлены простыми вопросами формы или стиля. Каждая нотация предназначена для моделирования определенного взгляда на проблему и выполнения ее с желаемой степенью детализации. Кроме того, каждое из различных обозначений часто ассоциируется с определенной фазой процесса разработки или с методологией проектирования. Например, обозначения Coad и Yourdon часто связаны с фазой анализа, обозначения Booch связаны с фазой проектирования , а обозначения Rumbaugh's - с моделями и шаблонами проектирования. Из этого нотационного лабиринта нет простого выхода. Мы начнем с использования объектно-классовой нотации, предложенной Коадом и Юордоном, поскольку мы будем внимательно следить за этими авторами в нашем подходе к объектноориентированному анализу. Позже мы добавим уточнения, согласующиеся с символикой Румбо, по мере того, как они станут необходимыми для обсуждения моделей и паттернов. Не в состоянии противостоять вышеупомянутой тенденции к модификации, мы также внесем некоторые незначительные изменения в символы Coad и Yourdon. Наше главное возражение против Coad и Объектно-ориентированный подход 781 Примечание Yourdon заключается в том, что для этого требуются оттенки серого, чего трудно добиться
с помощью карандаша и бумаги. Поскольку диаграммы классов и объектов часто необходимы во время мозговых штурмов и неофициальных встреч, лучше, если компоненты можно будет легко нарисовать от руки. На рисунке 28.3 показаны символы для классов и объектов. Рисунок 28.3 Обозначения для классов и объектов На рисунке 28.3 мы замечаем, что экземпляр обычного класса может быть создан, в то время как класс abstract не может. В своей книге по объектно-ориентированному анализу Coad и Yourdon ссылаются на классы, которые могут быть созданы как элемент Class-&-Object в диаграмме. Следовательно, их обозначение класса всегда относится к абстрактному классу. Методы в абстрактном классе представляют собой интерфейс без реализации, как будет показано в примере следующего раздела. Рисунок 28.4 Обозначения для наследования и отношений классов На рисунке 28.4 показаны обозначения Coad и Yourdon для представления наследования и типов ассоциаций между классами. Эти типы ассоциаций называются структурами. Структура обобщения-спецификации (Gen-Spec) соответствует отношению “является своего рода”. Например, в классификации, в которой транспортное средство является 782 Глава 28 Имя_класса Имя_класса Атрибуты Методы() Методы() КЛАСС (возможно создание экземпляра) АБСТРАКТНЫЙ КЛАСС (без создания экземпляра) Транспортное средство Автомобиль Автомобиль Шасси Самолет Движок Структура спецификации поколения (является своего рода отношением) Структура "Целое-часть" (является частью отношения) базовый класс, тогда автомобиль и самолет являются специализациями базового класса, поскольку автомобиль “ - это своего рода ”транспортное средство, как и самолет. С другой стороны, классификация может основываться на ассоциации Целого с Частью, изображенной символом треугольника на рис. 28.4. В этом случае шасси является частью автомобиля, как и двигатель. 28.5 Пример классификации Концепции и обозначения, рассмотренные в этой главе, можно проиллюстрировать примером. Предположим, что следующий проект: Мы должны разработать и закодировать графический инструментарий, который может отображать геометрические фигуры в любом положении экрана. Фигуры представляют собой прямые линии, прямоугольники, круги, эллипсы и параболы. Первое прочтение описания задачи показывает, что программа предназначена для рисования географических метрических фигур четырех различных типов: линия, прямоугольник, круг, эллипс и парабола. Мы также замечаем, что окружность, эллипс и парабола принадлежат к семейству кривых, называемых коническими сечениями. Они получаются путем сечения прямоугольного конуса, как показано на рисунке 28.5. Рисунок 28.5 Визуализация конических сечений Грубая и инстинктивная классификация может быть основана на использовании класса для каждого из отображаемых элементов рисунка. Понятие геометрической фигуры могло бы служить базовым классом, как показано на рисунке 28.6 на следующей странице. Поскольку GeometricalFigure является абстракцией, она представлена абстрактным классом без атрибутов и без возможного создания экземпляра. Line и Rectangle являются конкретными классами, поскольку можно нарисовать линию или прямоугольник, следовательно, у них есть атрибуты
, и они могут быть созданы. Круг, эллипс и Парабола являются конкретными классами Объектно-ориентированного подхода 783 Круг Эллипс Парабола Гипербола абстрактный класс ConicSection, который также является подклассом GeometricalFigure. Обратите внимание, что интерфейс, который является методом Draw(), определен в абстрактных классах (GeometricalFigure и ConicSection) и реализован в конкретных классах. Мы использовали соглашение C ++ о приравнивании функции к нулю, чтобы указать, что методы в абстрактных классах являются чисто виртуальными функциями и что реализация не предусмотрена. Также обратите внимание, что виртуальные функции в абстрактном и конкретном классах выделены курсивом. Рисунок 28.6 Классификация графического инструментария Поскольку конкретные классы могут быть созданы, мы можем создавать объекты типа Линии, Прямоугольника, Круга, Эллипса и Параболы. Очевидно, что не может быть объекта abstract классов ConicSection или GeometricalFigure, поскольку не может быть никакой возможной реализации этих понятий. По этим причинам абстрактные классы не имеют атрибутов . Теперь мы можем приступить к рисованию на видеодисплее с использованием объектов. Например, например: 784 Глава 28 Геометрическая фигура Пересечение Линия Прямоугольник Круг Эллипс Парабола x_start y_start x_end y_end толщина x_top_left y_top-left x_bottom_right y_bottom_right толщина линии x_coordinate y_coordinatet радиус толщины линии x_coordinate y_coordinate x_semiaxis y_semiaxis толщина линии x_coordinate y_coordinateтонкость линии фокуса Рисовать() Рисовать() Рисовать() Рисовать() Рисовать() Ничья() = 0 Draw() = 0 Объект: Атрибуты Line_A x_start = 10 y_start = 5 x_end = 40 y_end = 20 толщина = 0,005 Прямоугольник_a x_top_left = 5 y_top_left = 20
x_bottom_right = 25 y_bottom_right = 30 толщина линии = 0,010 Прямоугольник_b x_top_left = 30 y_top_left = 5 x_bottom_right = 45 y_bottom_right = 10 толщина линии = 0,030 Circle_A x_coordinate = 40 y_coordinate = 30 радиус = 5 толщина линии = 20 На рисунке 28.7 представлены эти объекты на видеодисплее. Обратите внимание, что каждый объект идентифицируется по своему имени и сохраняется по своим атрибутам. Также, что есть два объекта класса Rectangle, один обозначается как Rectangle_A, а другой как Rectangle_B. Тот факт, что отсутствуют объекты класса Ellipse или Parabola, означает только то, что эти классы не были созданы. Рисунок 28.7 Объекты, отображаемые с помощью Graphics Toolkit Объектно-ориентированный подход 785 Line_A ПРЯМОУГОЛЬНИК_B Прямоугольник_а Circle_A x y 10 10 20 20 30 30 40 На рисунке 28.5 мы замечаем, что существует тип конической кривой, гипербола, которая не определена в инструментарии. Структура класса в объектно-ориентированной парадигме позволяет впоследствии реализовать этот подкласс без внесения изменений в существующий код. Механизм динамического расширения библиотеки обсуждается далее в книге. 28.6 Когда следует использовать объектную ориентацию Тот факт, что название этой книги включает C ++ в качестве языка программирования, не означает, что мы предполагаем, что каждый программный проект выиграет от объектно-ориентированного подхода. Одним из преимуществ гибридных языков, таких как C ++, является то, что они делают объектную ориентацию необязательной. Более того, в среде гибридного языка у нас есть возможность использовать некоторые функции объектно-ориентированной системы , но не другие. Например, мы можем разработать программу, которая использует классы и объекты без наследования, или ту, которая использует наследование, но не полиморфизм во время выполнения. Или мы можем использовать одни формы наследования, а не другие. Другими словами, разработчик систем волен решать, насколько объектная ориентация, если таковая имеется, подходит для конкретного проекта. Часто это решающее и значимое решение, которое может определить успех или неудачу проекта. 28.6.1 Руководство по эксплуатации Объектно-ориентированные методы проникли в деятельность по программированию на такую глубину, что иногда мы забываем, что его фундаментальной целью является предоставление модели проблемной области. На рисунке 28.1 мы показали программирование как переход от набора реальных задач к набору решений с машинным кодом. В этом представлении программирования мы видим, что объектно-ориентированная парадигма делает упор на моделирование проблемной области, в то время как структурированное программирование моделирует область решения. Моделирование области решения облегчает фазу кодирования (которую оно приближает). Моделирование проблемной области облегчает анализ. В одном случае мы концентрируемся на завершающей стадии процесса, а в другом - на его начале.
Уделяя особое внимание проблемной области на этапе анализа, мы можем достичь двух неоспоримых преимуществ: во-первых, мы можем анализировать более сложные проблемы; во-вторых, аналитик может лучше общаться с экспертами в предметной области , поскольку из модели удалены многие технические элементы. Однако эти соответствия значительно уменьшаются, если объектно-ориентированную модель, полученную в результате анализа, приходится переводить в код не объектно-ориентированного языка программирования . Несмотря на заявления некоторых ярых защитников объектной ориентации , этот подход имеет сомнительное преимущество, когда он не пронизывает весь процесс разработки шин. Поэтому обычно нежелательно выполнять объектно-ориентированный анализ и проектирование проекта, который должен быть закодирован на не объектноориентированном языке. Смешивать модели и инструменты разработки обычно плохая идея. Хотя в последние годы сообщество программистов, по-видимому, отдает предпочтение объектно-ориентированным методам, иногда бывает трудно четко определить преимущества объектно-ориентированного программирования перед обычным структурированным программированием для конкретного проекта. Чтобы вынести обоснованное суждение, нам пришлось бы проследить за множеством крупных программных проектов, разработанных с использованием обоих методов, mea786 Глава 28 следите за усилиями по разработке и критически оценивайте результаты. Даже тогда многие субъективные соображения могли бы запутать наши выводы. В любом случае мы не должны забывать, что преимущества объектной ориентации даются высокой ценой усложнения программирования. Мало кто будет спорить, что объектно-ориентированные языки сложнее в освоении и использовании и что методы объектно-ориентированного анализа и проектирования сложны. Решение о том, использовать или не использовать объектно-ориентированный подход, должно быть тщательно рассмотрено аналитиком. Следующие элементы благоприятствуют использованию объектно-ориентированных методов: 1. Проекты, в которых моделирование предметной области является основной проблемой 2. Проекты, в которых общение с экспертами предметной области может быть затруднено культовая и критическая часть деятельности по разработке 3. Нестабильные проекты, в которых можно предвидеть изменения или в которых будут требоваться частые обновления 4. Проекты со многими элементами, потенциально пригодными для повторного использования 5. Наборы инструментов и фреймворки часто являются хорошими кандидатами для объектно-ориентированного подхода С другой стороны, есть элементы, которые часто рекомендуют отказаться от объектной ориентации: 1. Если структуры данных и операции обработки, возникающие в результате объектной ориентации, несовместимы с существующими базами данных. Другими словами, может оказаться плохой идеей разрабатывать систему управления объектно-ориентированными базами данных, если новая система вынуждает к повторному проектированию или реструктуризации существующих баз данных. 2. Если проект или его существенная часть должны быть разработаны на не объектно-ориентированном языке программирования. Таким образом, объектно-ориентированные методы могут оказаться сомнительными при разработке короткого низкоуровневого драйвера устройства для аппаратного аксессуара. 3. Если модель предметной области уже четко определена в не объектно-ориентированных терминах. 4. Если проблемная область лучше выражается с использованием обычной, не объектно-ориентированной модели. Проекты, в которых объектами моделирования являются программные элементы, иногда попадают в эту категорию. Как правило, крупные бизнес- и промышленные приложения подходят для ориентации объектов , в то время как небольшие технические или вспомогательные утилиты - нет. Объектно-ориентированный подход 787 Глава 29 Объектно-ориентированный анализ
Краткое содержание главы Объектно-ориентированное программирование - это в основном методология проектирования программного обеспечения. В этой главе мы обсуждаем элементы объектно-ориентированного анализа, который является первым и наиболее важным шагом в разработке объектно-ориентированного приложения. Темы включают моделирование проблемной области, декомпозицию на классы и объекты, проверку валидности объекта, системы и подсистемы, атрибуты, сервисы, подключения к экземплярам и сообщениям . 29.0 Элементы объектно-ориентированного анализа Несколькими авторами было замечено, что обе основные парасистемы разработки программного обеспечения (структурированное программирование и объектная ориентация) начинались с языков программирования, позже были распространены на проектирование программного обеспечения и, наконец, на анализ. Это подразумевает, что появление методологии системного анализа знаменует зрелость определенной парадигмы. Что касается объектно-ориентированных систем, мы видели, что полнофункциональные языки программирования были доступны уже в конце семидесятых и начале восьмидесятых. Обсуждения системного проектирования начались примерно в то же время, в то время как методологии объектноориентированного анализа являются продуктом конца восьмидесятых и начала девяностых. Обратите внимание, что объектно-ориентированный подход к анализу не пытается заменить каждую технику и инструмент обычного системного анализа. Технико-экономическое обоснование, анализ рисков и анализ затрат и выгод могут выполняться независимо от того, моделируется ли проект с использованием структурированного программирования или объектно-ориентированного подхода. Объектно-ориентированный анализ предоставляет практическую методологию моделирования проекта на этапе анализа. Его цель - дополнять, а не заменять. Мы упоминали в разделе 3.6, что аналитик должен критически оценить, выиграет ли проект от использования объектно-ориентированного подхода. Также, что наибольшие преимущества объектно-ориентированной парадигмы достигаются, когда методология пронизывает весь процесс разработки . В большинстве случаев наше решение использовать объектно-ориентированный анализ имплементировано 789 предполагает, что мы также будем придерживаться объектной ориентации на этапе проектирования и что программа будет закодирована на объектно-ориентированном языке программирования. Смешивание парадигм теоретически возможно, и иногда рекомендуется сторонниками жесткой разработки объектной ориентации. Однако, что касается меньшего проекта, сложности, возникающие при одновременном использовании более чем одной модели, намного перевешивают неопределенные преимущества. В частности, что касается проекта разработки меньшего размера , мы считаем, что решение приступить к объектно-ориентированному анализу должно предполагать, что объектная ориентация также будет использоваться на этапах проектирования и кодирования разработки. В этой главе мы следуем примеру Coad и Yourdon, хотя и не очень строго. Эти авторы являются главной силой в области объектно-ориентированного анализа. Кроме того, их подход элегантен и прост. Обозначения Coad и Yourdon были описаны в разделе 28.4. 29.0.1 Моделирование проблемной области Одной из главных характеристик объектной ориентации, а также ее главным оправданием является то, что она обеспечивает разумный способ моделирования проблемной области. Другими часто упоминаемыми преимуществами являются то, что это делает возможным моделирование сложных систем, облегчает построение систем, которые являются более гибкими и адаптируемыми, и способствует повторному использованию. Объектно-ориентированный анализ наиболее полезен при моделировании множества задач. Одно из затруднений разработки программного обеспечения заключается в том, что программист должен стать экспертом в предметной области. Таким образом, тот, кто заключил контракт на разработку системы управления воздушным движением, должен изучить радар, связь "воздух-земля", системы реагирования на чрезвычайные ситуации, расписание полетов и множество других технических и деловых вопросов, связанных с текущей деятельностью. Какой объем знаний должен приобрести аналитик и на какой технический уровень должны распространяться эти
знания, трудно определить заранее. Многие проекты потерпели неудачу из-за того, что разработчики не разобрались в важных деталях поставленной задачи. Стремление “продолжить кодирование” часто работает против нас на этом этапе. Поскольку аналитику необходимо быстро разобраться в предметной области, любой инструмент , облегчающий этот этап процесса, действительно ценен. Как только аналитик овладеет необходимыми знаниями, эта информация должна быть передана другим членам команды разработчиков. Здесь опять же высоко ценится любой инструмент, который помогает в распространении знаний. Наконец, модель предлагаемого набора решений должна быть представлена клиентам или пользователям для их проверки и получения обратной связи. Модель, которая облегчает эту коммуникацию между клиентами и разработчиками, является дополнительным преимуществом. 29.0.2 Определение обязанностей системы В дополнение к предоставлению модели проблемной области, объектно-ориентированный анализ должен определять обязанности системы. Например, анализ системы управления воздушным движением включает в себя определение того, какие функции и операции входят в нагрузку на систему. Обязана ли система управления воздушным движением информировать коммерческие авиакомпании о задержках прибытия или вылета воздушных судов? Как работает 790 Глава 29 интерфейс системы управления воздушным движением с системой аварийного реагирования в воздушном порту? В какой момент слежение за воздушным судном становится обязанностью конкретной системы управления воздушным движением и когда эта ответственность прекращается?...........? Обязанности системы относятся к тому, что система должна делать. Ответ на этот вопрос является одной из фундаментальных задач объектно-ориентированного анализа. Вопросы, связанные с тем, как работает система, оставлены для этапов проектирования и кодирования. 29.0.3 Управление сложностью Анализ необходим, поскольку природные системы часто настолько сложны , что их трудно понять или управлять ими. Объектно-ориентированный анализ предоставляет набор инструментов для управления сложностью. Этот набор инструментов основан на абстракции, инкапсуляции, наследовании и передаче сообщений. Абстракция Абстракция состоит в устранении того, что является излишним или тривиальным, и концентрации на том, что является фундаментальным. Любое определение - это упражнение в абстракции. Мы можем утверждать, что перьевая ручка - это ручной пишущий инструмент, в котором используются жидкие чернила, механизм слива чернил и тонкая прокладка. В этом описании предпринята попытка собрать воедино основные характеристики авторучки, которые отличают ее от пишущей машинки, шариковой ручки и карандаша. Однако при этом игнорируются менее важные характеристики, такие как цвет пера, цвет чернил, точные размеры инструмента и его дизайн или стиль. В контексте объектно-ориентированного анализа абстракция относится к классовому механизму, который одновременно обеспечивает процедурную абстракцию и абстракцию данных. Другими словами, объектно-ориентированное понятие класса - это абстракция, которая представляет как элементы обработки, так и данные набора задач. Инкапсуляция Инкапсуляция является одним из фундаментальных понятий объектной ориентации. В объектноориентированном подходе инкапсуляция связана с понятием данных и функций ( атрибутов и методов), упаковываемых вместе с помощью конструкции класса. Те методы, которые видны за пределами класса, являются его интерфейсом. Основная цель encapsulation - скрыть детали реализации, делая акцент на интерфейсе. Наследование В объектно-ориентированной парадигме наследование относится к возможности доступа одного класса к открытым и защищенным членам своего родительского класса. Наследование классов обеспечивает самый высокий уровень абстракции, уменьшает размер кода и упрощает программирование. Результатом является иерархия классов, которая идет от наиболее общего к наиболее конкретному. Обычно производный класс включает в себя все функции своих родительских классов и добавляет некоторые собственные уникальные. Передача сообщений
Объектно-ориентированные системы получают доступ к функциям обработки с помощью механизма, называемого передачей сообщений. Одним из недостатков жестко запрограммированных программных структур, таких как таблицы переходов, каскадные операторы if или конструкции case, является то, что они должны обновляться при каждой модификации программы. Механизм передачи сообщений, основанный на объектно-ориентированном анализе 791 с другой стороны, автоматически направляет выполнение к соответствующей функции-члену. Эта функциональность дает два основных преимущества: во-первых, мы избегаем внесения новых дефектов в код, который уже был протестирован и отлажен. Во-вторых, система может быть расширена за счет поставки относительно небольших модулей, содержащих новые функции . 29.1 Декомпозиция классов и объектов Первая дисциплина объектно-ориентированного анализа - это научиться мыслить в терминах классов и объектов. На этом этапе мы должны освободить свой разум от беспокойства по поводу алгоритмов, программных структур или любых других проблем реализации. Наша задача во время анализа состоит в том, чтобы смоделировать проблемную область и определить обязанности системы. Обе эти цели достигаются с помощью декомпозиции классов и объектов. В этом смысле объект можно рассматривать как абстракцию дискретного элемента в проблемной области. Объектно-ориентированная инкапсуляция - это вопрос определения атрибутов и методов. Объект характеризуется возможностью сохранения системой его идентичности . Поэтому каждый объект должен обладать уникальностью, которая позволяет его идентифицировать. Каждый объект принадлежит к классу объектов. Класс - это описание набора уникальных атрибутов и методов, связанных с типом объекта. Объект - это позиция класса. Абстрактный класс - это описание интерфейса, которому не хватает реализации. Его основная цель - определить путь наследования. Невозможно создавать экземпляры объектов из абстрактного класса. Начинаем ли мы моделирование, думая об объектах или классах? Это вопрос Семантики, но, строго говоря, объект-это время выполнения сконструировать А класса от Abstraction, что группы общие атрибуты и методы объекта типа. Поэтому представляется, что мы должны думать о типах объектов или классах, а не о возможных экземплярах. С другой стороны, ментальная концепция класса объектов требует , чтобы мы визуализировали типичный объект. Например, предположим, что мы пытаемся смоделировать систему, которая использует окно viewport для отображения текстового сообщения. Чтобы изменить тип окна текстового сообщения, мы должны сначала представить, как будет выглядеть объект. На рисунке 29.1 показано, какой может быть наша первоначальная визуализация объекта текстового окна. Из визуализации объекта мы можем вывести атрибуты и методы самого объекта и класса, который его представляет. Например, местоположение экрана и размер объекта определяются начальными и конечными координатами. Объект также имеет толщину границы и цвет. Его текстовыми атрибутами являются заголовок окна и его текст. Кнопка управления в верхнем левом углу объекта позволяет пользователю удалить текстовое окно сообщения. С этим объектом связаны три метода: один для отключения воспроизведения окна, один для сообщения о его текущем состоянии и один для его удаления. Используя дидиаграммы классов, мы теперь можем представить объект типа TextWindow с любой желаемой степенью детализации. На диаграмме слева на рис. 29.2 просто указано, что существует класс объектов с именем TextWindow, в то время как его атрибуты и методы еще не определены. На диаграмме справа показаны конкретные атрибуты и методы. 792 Глава 29 Рисунок 29.1 Визуализация объекта текстового окна Рисунок 29.2 Диаграммы классов для объекта TextWindow Во время классификации обычной практикой является создание имени класса в единственном числе существительным или прилагательным, за которым следует существительное. Также неплохо использовать стандартный словарь клиента при именовании классов. Предположим, мы моделируем домашнюю сигнализацию Объектно-ориентированный анализ 793
x y 10 10 20 20 30 30 40 Заголовок окна Текстовое сообщение закрыть окно кнопка начальная точка координаты конечная точка координаты толщина цвет Текстовое окно TextWindow start_x start_y end_x end_y цвет толщина window_title text_message статус drawWindow() Возвращает статус() Стирает окно() система с довольно сложным видеодисплеем и командным управлением. По историческим причинам клиент называет видеодисплей и командное управление “кнопочной панелью”. В ходе анализа у нас может возникнуть соблазн переименовать этот элемент, чтобы его описание более точно описывало его фактическую функциональность. Очевидно, что термин “командная консоль” был бы более выразительным и описательным, чем “кнопочная панель”. Однако это новое название только запутало бы клиентов, которым пришлось бы мысленно переводить “командная консоль” на “кнопочная панель” каждый раз, когда они сталкиваются с новым термином. На этапе анализа название класса может быть введено с использованием любого разумного стиля интервалов и заглавных букв. Однако, если наши имена классов не совместимы с синтаксисом, который будет использоваться на этапах проектирования и кодирования, то эти имена придется изменить позднее. Поскольку любая модификация может быть источником ошибок, лучше называть классы, атрибуты и методы, используя стиль, который соответствует более поздним этапам среды разработки. В контексте этой книги принятию решений относительно стиля способствует тот факт, что мы придерживаемся C ++ как языка программирования. Поэтому, чтобы улучшить удобочитаемость и в соответствии с одним популярным стилем программирования на C ++, мы пишем заглавными буквами первую букву каждого слова в именах классов и методах. Методы сопровождаются круглыми скобками для обозначения обработки. Атрибуты, которые позже становятся переменными, набираются в нижнем регистре, а символ подчеркивания используется в качестве разделителя . Обратите внимание, что любой другой стиль одинаково удовлетворителен до тех пор, пока его можно последовательно поддерживать на протяжении всего процесса разработки . В то время как методы изображаются глаголами и глагольными фразами, атрибуты представлены существительными или именными словосочетаниями, как на рис. 29.2. C ++ позволяет нам использовать довольно простые схемы именования атрибутов и методов, поскольку эти имена имеют область видимости класса, где одни и те же имена могут быть повторно использованы в других классах. В этом смысле метод drawWindow класса TextWindow не противоречит другим методам,
называемым drawWindow, которые позже могут быть определены для других классов. По тому же признаку атрибут thickness также может быть повторно использован в отношении других типов графических объектов. Упрощенные диаграммы классов, в которых явно не перечислены атрибуты и методы, могут быть полезны для демонстрации отношений классов и наследования. Рассмотрим систему с двумя типами объектов window: один представляет окна, которые не содержат ничего , кроме текста, второй - для окон, которые содержат растровое изображение. Первый тип может быть обозначен как TextWindow, а второй - как GraphicWindow. Структура класса была бы изображена на диаграмме классов на рисунке 29.3. П о т е ч а т и н Ф и г у р е 2 9 . 3 Те х т ви н д о в и с а с у б к л а с т ви н д о в, графическое окно. Кроме того, это Window является абстрактным классом, что подразумевает, что его методы не реализованы. Также, что во время выполнения мы могли бы создавать экземпляры объектов типа TextWindow или типа GraphicWindow, однако объекты Window могут не существовать, поскольку абстрактная природа базового класса исключает такую возможность. Более подробная схема классов показана на рисунке 29.4. 794 Глава 29 Рисунок 29.3 Пример схемы наследования для классов Window_Type Рисунок 29.4 Усовершенствованная диаграмма для классов оконного типа На рис. 29.4 класс GraphicWindow является подклассом Window, который добавляет функциональность базовому классу. Новая функциональность представлена в виде трех новых атрибутов, которые размещают растровое изображение на видовом экране, и нового метода рисования растрового изображения. Основные координаты и характеристики самого окна определены в базовом классе, поскольку эти атрибуты применимы к любому объекту window. Объектно-ориентированный анализ 795 Графическое окно Window Текстовое окно Окно Графическое окно TextWindow start_x start_y end_x end_y цвет толщина window_title text_message статус bitmap_x bitmap_y drawWindow()=0 ReturnStatus()=0 EraseWindow()=0 drawWindow() Возвращает статус() Стирает окно() drawWindow() Возвращает статус() Стирает окно() drawBitmap() 29.1.1 Поиск объектов Поиск объектов не следует начинать до тех пор, пока аналитик не получит некоторого представления о самой проблемной области. Аналитик, незнакомый с системой управления воздушным движением , не должен заниматься поиском объектов управления воздушным движением. Вместо этого первым шагом должна стать попытка понять фундаментальные характеристики системы. Бесполезно пытаться идентифицировать объекты и классы в незнакомой технологии
iar. Ниже приведены основные источники информации о проблемной области: 1. Опубликованные материалы и Интернет 2. Наблюдение из первых рук 3. Консультация с экспертами в предметной области 4. Документы клиента Поиск опубликованных материалов обычно ведет в библиотеки, книжные магазины и Интернет. Наблюдение из первых рук состоит в ознакомлении с системой путем участия в ее функционировании. Например, для анализа системы управления воздушным движением мы можем начать с того, что в течение нескольких дней будем сидеть в диспетчерской вышке, наблюдая за работой диспетчеров воздушного движения. Эксперты в предметной области часто являются источником ценной и уникальной информации. Многие системы полностью или частично неопубликованы, являются частью коммерческой или правительственной тайны или считаются конфиденциальными. В этих случаях опубликованные материалы могут оказаться малополезными. Наш единственный вариант найти людей, обладающих знаниями и опытностью, и попросить их описать или объяснить нам систему. Документация клиента также может быть ценным источником информации. Для экспертов-аналитиков стало стандартной практикой запрашивать у клиента подробный документ . В этом документе должна быть описана система и ее назначение с максимально возможной детализацией. 29.1.2 Аккуратные и грязные классы В некоторых контекстах классификация является ясным и аккуратным процессом, но не в других. Когда концептуальные объекты соответствуют объектам реального мира, процесс классификации издесяти прост и прямолинейен. Например, когда задача состоит в том, чтобы определить мини-CAD программу, классификация может оказаться относительно несложной. В этом случае объекты экрана порождают классы, такие как рисунки, состоящие из линий, кривых, геометрических фигур и текста. С другой стороны, классификация, может быть, несколько менее понятно, когда задача разработать редактор или программу обработки текстов. В этом случае мы можем начать думатьную программу действий в качестве объектов. Но разумно ли рассматривать операции , такие как вставка, замена и удаление текста, как физические объекты? Как насчет пользовательского текстового файла? Однако, поскольку текстовый файл хранится в виде массива символов, у нас может возникнуть соблазн рассматривать его как атрибут. Одна из возможных классификаций показана на рисунке 29.5. 796 Глава 29 Рисунок 29.5 Возможная диаграмма классов для простого текстового редактора Другие проекты классифицировать может быть еще сложнее. Например, предположим, что задачей является разработка и кодирование утилиты обнаружения вирусов. В этом случае являются ли вирусы объектами? Означает ли это, что если вирусы не найдены, программа выполняется без создания экземпляра? Или сам поиск является объектом? В этом случае, какой бы вид классификации ни был разработан, катификация, вероятно, будет неудовлетворительной, а сами классы будут неопрятными и нереальными. Что происходит, так это то, что это, вероятно, один из тех случаев, описанных в разделе 3.6, который лучше всего обрабатывать вне объектной ориентации. 29.2 Поиск классов и объектов Навыки объектно-ориентированного аналитика включают в себя способность человека находить объекты и выводить достоверные и полезные классификации. В этом отношении ничто не заменит опыт работы с различными типами проблемных областей. Однако существуют некоторые обстоятельства, паттерны и конструкции, которые аналитик может использовать для определения возможных объектов . Одна из предлагаемых методик состоит в поиске по элементам ассоциаций классов, механизмам и устройствам, связанным системам, сохраненным данным, выполняемым ролям, сайтам и организационным подразделениям. 29.2.1 Анализ ассоциаций классов В разделе 27.4 мы описали обозначение для представления двух типов ассоциаций классов: спецификации поколения и
структуры "Целое-часть".......... Этот первый тип относится к отношению обобщенияспециализации, которое соответствует одному классу, находящемуся в “своего рода” ассоциации с родительским или базовым классом. Второй тип относится к ассоциации, в которой класс является “частью” родительского или базового класса. Обозначение ассоциаций классов часто используется для обозначения конфигурации наследования, при которой общедоступные элементы базового класса становятся доступными для производного класса. Хотя это предположение справедливо при анализе , мы должны упомянуть, что ассоциации целых частей часто реализуются без использования наследования классов. Ассоциации (также называемые структурами) могут использоваться для идентификации объектов в предметной области lem и для выявления уровней сложности в структурах классов. Метод состоит в просмотре ассоциаций объектов в проблемной области. Например, на рисунке 29.3 мы обнаружили, что два типа окон, называемые GraphicWindow и Объектно-ориентированный анализ 797 Пользовательский файл текстовый файл Вставить() Удалить() Заменить() Сохранить() Загрузить() TextWindow, были подклассами общего типа Window. На основе этих ассоциаций позже была получена подробная диаграмма классов . Специализированные структуры Название специализации обычно происходит от ее обобщения. В этом случае ner TextWindow и GraphicWindow являются специализациями типа Window (см. Рисунок 29.4). В структуре, основанной на спецификации поколения, самые нижние классы - это конкретные классы, из которых могут быть созданы экземпляры объектов. Классы более высокого уровня могут быть конкретными или абстрактными. Часто структуры классов являются гибкими и могут быть выражены различными способами. Например, мы могли бы переделать диаграмму классов на рис. 29.4 так, чтобы окно конкретного класса служило обобщением с возможной специализацией в виде графического окна. Эта схема показана на рисунке 29.6. Рисунок 29.6 Альтернативная структура для классов оконного типа Обратите внимание, что на рисунке 29.6 изображена структура класса, в которой методы drawWindow(), ReturnStatus() и EraseWindow() фактически реализованы в базовом классе. Следовательно, их не нужно повторно реализовывать в производном классе, за исключением случаев, когда эти методы нуждаются в обновлении или модификации. Как только мы определим структуру обобщения-специализации, выполните тесты, чтобы убедиться, является ли структура одновременно действительной и необходимой. 798 Глава 29 Окно GraphicWindow start_x start_y end_x end_y цвет толщина window_title text_message статус bitmap_x bitmap_y
drawWindow() Возвращает статус() Стирает окно() drawBitmap() 1. Реагирует ли подкласс на отношение “является своего рода” относительно родительского класса? 2. Добавляет ли подкласс атрибуты или методы к родительскому классу? 3. Действительно ли структура относится к предметной области в том виде, в каком она относится к предлагаемой системе? Другими словами: действительно ли эта структура необходима для представления рассматриваемой проблемы? 4. Входит ли данная структура в круг обязанностей системы? Часто структура, которая фактически существует в проблемной области, оказывается ненужной в модели, поскольку предложенная система не выполняет никакой связанной с ней функции. Например, мы можем распознать, что графическое окно (см. рисунки 29.3 и 29.6) может содержать векторные рисунки, а также растровые изображения. Однако, если разрабатываемая система работает только с растровой графикой, то внедрение векторной графики было бы ненужным усложнением, поскольку это выходит за рамки обязанностей системы. 5. Существуют ли линии наследования между подклассом и родительским классом? Обобщениеструктура специализации, в которой никакие методы не наследуются от родительского класса , должна быть подвергнута сомнению, поскольку именно наследование делает структуру полезной. 6. Было бы проще использовать атрибут, определяющий тип, чем спецификационную структуру Gen? Например, система, которая имеет дело с транспортными средствами, должна различать автомобили, мотоциклы и грузовики. Возможно, это могло бы быть достигнуто с помощью структуры Gen-Spec, в которой Vehicle является базовым классом, а AutoVehicle, MotorcyleVehicle и TruckVehicle являются подклассами. В качестве альтернативы систему можно смоделировать с использованием класса Vehicle с атрибутом vehicle_type. Оба варианта показаны на рисунке 29.7. Рисунок 29.7 Две альтернативы моделирования Объектно-ориентированный анализ 799 Грузовой автомобиль Транспортное средство МОДЕЛЬ ИСПОЛЬЗУЕТ СПЕЦИФИКАЦИОННУЮ СТРУКТУРУ ПОКОЛЕНИЯ МОДЕЛЬ ИСПОЛЬЗУЕТ АТРИБУТ Автомобиль Транспортное средство MotorcycleVehicle тип транспортного средства (авто) (мотоцикл) (грузовик) Общее правило, используемое при определении того, какой модели следует отдать предпочтение, заключается в том, что структура обобщения-специализации не должна использоваться для извлечения общего атрибута. Множественное наследование в структурах, специфичных для поколения Общим случаем в структуре, основанной на спецификации поколения, является то, что класс наследуется от одного предкатора. Однако Coad и Yourdon различают спецификационную структуру, используемую в качестве иерархии, и структуру, используемую в качестве решетки. Иерархия является наиболее распространенной формой структуры и соответствует примерам, использованным ранее в этой главе. В этом смысле самая верхняя модель на рис. 29.7 представляет иерархию класса транспортного средства. Понятие иерархии соответствует единичному наследованию в объектно-ориентированных языках . Решетка, с другой стороны, соответствует множественному наследованию, которое возникает, когда класс наследует атрибуты или методы от более чем одного предка. Например, в структуре классов транспортных средств на рис. 29.7 мы могли бы определить два дополнительных подкласса. Класс PickupVehicle может обладать некоторыми свойствами автомобильного транспортного средства (перевозит пассажиров) и некоторыми свойствами грузового транспортного средства (перевозит грузы). Или ATVVehicle может наследовать некоторые свойства от класса AutoVehicle, а другие - от MotorcycleVehicle. На рисунке 29.8 показана результирующая диаграмма решетчатого типа. Рисунок 29.8
Решетка структуры общего назначения Структуры из целых частей Треугольник маркировки на рис. 28.4 представляют собой единое целое-часть структуры, которая соответсоответствует понятию “является-частью-класса” отношения. Иногда Целочисленная нотация дополняется цифрами или кодами для обозначения количества элементов 800 Глава 29 Грузовой автомобиль Транспортное средство Автомобильное транспортное средство Квадроцикл Пикап-транспортное средство Автомобиль-мотоцикл которые принимают участие в отношениях. Например, модели может помочь, если мы уточним, что у автомобиля veможет быть четыре колеса, но только один двигатель, или что колесо может принадлежать только одному транспортному средству, но человек может владеть более чем одним транспортным средством. Запятая отделяет нижний числовой предел от верхнего числового предела. Буква “м” часто используется в качестве сокращения для “многих”. На рис. уре 29.9 показана структура из целых частей, касающаяся некоторых компонентов класса AutoVehicle. Рисунок 29.9 Структура из цельных частей Обратите внимание, что цифры на рисунке 29.9 добавляют информацию к модели. Например, мы можем сказать, что автомобиль оснащен одним двигателем, четырьмя колесами и нулевой или одной автоматической коробкой передач. Также, что двигатель, колесо и автоматическая коробка передач могут существовать независимо от автомобиля, но что цилиндры не существуют вне двигателя. ..........., Наконец, что двигатель может иметь четыре, шесть или восемь цилиндров. Существуют определенные общие конструкции и взаимосвязи, которые могут быть исследованы при поиске ассоциаций между Целым и Частью. В примере на рис. 29.9 структура может быть описана как взаимосвязь сборка-компоненты. Другой распространенный случай может быть описан как взаимосвязь контейнер-содержимое. Например, водитель автомобиля не является одним из его компонентов, но может рассматриваться как находящийся во взаимосвязи контейнер-содержимое. Третья распространенная конструкция - это взаимосвязь коллекционера и участника. Например, коллекционные монеты содержат элементы quarter, dime, nickel и penny. Или в больнице сбора есть пациенты-члены, медсестра, врач и администратор. Следующие тесты могут быть выполнены, чтобы определить, является ли конструкция из цельных частей допустимой и необходимой: Объектно-ориентированный анализ 801 Автоматическая передача Автомобиль Двигатель Цилиндры Колеса 4 0, 1 1 0, 1 4, 6, 8 1 0, 1 0, 1 1. Соответствует ли подкласс отношению “является частью” относительно родительского класса? 2. Добавляет ли подкласс атрибуты или методы к родительскому классу? 3. Действительно ли структура относится к предметной области в том виде, в каком она относится к предлагаемой системе? Действительно ли эта структура необходима для представления рассматриваемой проблемы? 4. Входит ли данная структура в обязанности системы? 5. Было бы проще использовать атрибут, который определяет тип, а не Целую часть структура? Составные структуры
Часто случается, что структура содержит как общие характеристики, так и отношения между классами целых частей. Например, структуры на рисунках 29.8 и 29.9 приведут к составной структуре, показанной на рисунке 29.10. Рисунок 29.10 Составная структура 29.2.2 Изучение механизмов и устройств Другим элементом поиска классов и объектов является расположение механизмов и устройств в пределах предметной области. Например, при моделировании системы охранной сигнализации мы можем заметить, что она включает в себя такие устройства, как датчики, которые должны быть размещены в возможных точках входа, звуковые предупреждения, которые звучат при обнаружении нарушения безопасности, переключатели для включения и выключения системы и аппаратные средства ввода / вывода, такие как клавиатура для ввода паролей и дисплеи для эхо-воспроизведения ввода и сообщения о состоянии системы . В этом случае все устройства и механизмы, которые входят в сферу ответственности системы, являются кандидатами на классификацию. В случае с охранной сигнализацией 802 Глава 29 Грузовой автомобиль Транспортное средство Автомобильное транспортное средство Мотоцикл Автоматическая передача Двигатель Колесики система мы должны изучить возможность наличия класса с именем Sensor, класса с именем AudibleAlarm, класса с именем OnOffSwitch и так далее. 29.2.3 Связанные системы На периферии ответственности системы мы иногда можем обнаружить другие системы , с которыми она взаимодействует. Взаимодействие может быть вшита (если обе системы мнеchanically подключены), через связи, или могут возникнуть из-хувзаимодействия человек-компьютер. Любая связанная система должна рассматриваться как возможный кандидат на классификацию, если она входит в обязанности базовой системы и если она является частью проблемной области. Например, если система охранной сигнализации, упомянутая в разделе 29.2.2, подключена к местной системе оповещения полиции, то, возможно, некоторые элементы системы оповещения полиции следует включить в модель. 29.2.4 Сохраненные данные Другим возможным источником классов и объектов являются данные или события, которые должны быть предварительно обработаны или запомнены системой. События сохранились в исторических целях, Модификации к базам данных, и нормативно-правовые документы, включены в эту категорию. Например, компьютерная система, которая использует телескоп для сканирования неба в поисках неизвестных космических объектов, должна каким-то образом записывать и запоминать положение новой кометы на траектории возможного столкновения с Землей. Файл, используемый для хранения времени, даты, небесных координат и фотографического изображения вновь обнаруженного объекта, является возможной кандидатурой для классификации. Более приземленным примером могут быть данные о транзакциях, хранящиеся в банкомате. 29.2.5 Выполняемые роли Отдельные лица играют роли в организациях, и эти роли являются другим возможным источником классов, которые следует изучить. Например, на содержание медицинских оргаорганизации (ОПЗ) и лица, исполняющие роли врачей, медсестер, пациентов, адministrators, и персонал. Вполне вероятно, что модель этого HMO будет рассматривать эти роли как классы. На рисунке 29.11 показана одна из возможных (довольно расплывчатых) интерпретаций взаимодействия классного врача в вымышленном HMO. ..........., Рисунок 29.11 Класс, изображающий взаимодействие с человеком Объектно-ориентированный анализ
803 Врач наименование специальности , разрешенной для выполнения Обследующий пациент () Назначает лечение () Выполняет операцию () переводит пациента () сообщает о смерти() 29.2.6 Эксплуатационные площадки Операционные площадки или физические местоположения в пределах ответственности системы часто являются источником классов. Например, гипотетическая система обработки спутниковых изображений включает сайт в Нью-Мексико, где данные изображения загружаются со спутников , второй сайт в Южной Дакоте получает эти необработанные данные и переформатирует их в конкретные файлы изображений, которые затем передаются на третий сайт в Калифорнии, который распространяет файлы изображений среди клиентов системы. В этом случае участки в Нью-Мексико, Южной Дакоте и Калифорнии выполняют разные функции и поэтому могут рассматриваться в качестве кандидатов для классификации. Другой пример: отдел контроля качества производителя пищевых продуктов состоит из офиса и лаборатории. Образцы пищевых продуктов собираются на восьми заводах-изготовителях по всему штату. При моделировании этой системы нам, возможно, придется решить, основана ли классификация на десяти объектах, один из которых является лабораторией, другой - офисом, а восемь объектов - производственными предприятиями, или модель может быть рационализирована до трех классов путем объединения всех восьми производственных предприятий в один класс. В этом последнем варианте атрибут может быть использован для описания каждого отдельного растения. Этот случай показан на рисунке 29.12. Рисунок 29.12 Пример классификации на основе сайта 29.2.7 Организационные подразделения Организации разделены на подразделения, которые выполняют различные задачи и берут на себя различные обязанности в рамках системы. Во время моделирования эти организационные единицы также являются сильными кандидатами на классификацию. Например, производственное подразделение включает в себя отделы, называемые инжиниринг, производство, контроль качества и отгрузка. Эти организационные единицы следует рассматривать как возможные классы. 29.3 Валидность объекта тестирования После того как мы разработали базовую классификационную схему для рассматриваемой проблемы, хорошей идеей будет подвергнуть сомнению ее валидность и обоснованность. Существуют определенные требования, обычно связанные с объектами, которые могут служить для проверки их легитимности и значимости . При оспаривании предлагаемых классов и объектов важны следующие вопросы: tinent: 1. Существует ли информация, которую системе необходимо запомнить относительно этого объекта? 2. Предоставляет ли этот объект какой-либо метод или операцию обработки? 3. Существует ли более одного атрибута, связанного с этим объектом? 804 Глава 29 Завод-изготовитель имя_сайта Офис Лаборатория 4. Будет ли создано более одного экземпляра объекта из класса? 5. Всегда ли атрибуты применяются к объекту? 6. Всегда ли методы применимы к объекту? 7. Относятся ли объекты к проблемной области или к конкретной реализации? 8. Являются ли полученные результаты простым вычислением или выводом? В следующих разделах мы переходим к рассмотрению каждого из этих вопросов отдельно
. 29.3.1 Информация для запоминания Для индивидуального запоминания объекта системой он должен быть связан с одним или несколькими связанными атрибутами. В этом смысле экземпляр класса Clerk должен иметь имя, а экземпляр класса Office должен иметь адрес для набора конкретных функций, экземпляр класса AutoVehicle должен иметь модель и серийный номер и так далее. Объект, который не связан ни с какой атрибут должен быть вопрос ложен, поскольку она может стать неактуальной в систему, хотя она может быть объектом в реальном мире. Хотя объект может иметь методы, но не иметь атрибутов, этот случай всегда следует исследовать, прежде чем он будет принят в качестве исключения из общего правила. 29.3.2 Поведение объекта Класс без методов подозревается в ошибке классификации. Даже абстрактный класс имеет методы, хотя эти методы не реализованы. Если мы примем, что в принципе атрибуты не должны быть доступны извне класса, то тот факт, что объект связан с одним или несколькими атрибутами, подразумевает, что существует один или несколько методов для доступа к этим атрибутам. Тщательное исследование необходимо, если у объекта есть атрибуты, но нет методов. Некоторые авторы считают, что классы с методами, но без атрибутов, являются результатом мышления в функциональном стиле и должны быть пересмотрены. 29.3.3 Множественные Атрибуты В то время как класс без атрибутов подозревается в неправильной классификации, класс с единственным атрибутом может указывать на плохо продуманную классификацию. В этом случае аналитик мог бы использовать атрибут (обычно в родительском классе) вместо полноценного объекта. 29.3.4 Множественные объекты Фундаментальное понятие классификации подразумевает, что класс - это тип, который обычно состоит более чем из одного индивидуума. В этом смысле мы можем правильно представить себе класс FootballPlayer или класс Quarterback. Могут быть созданы экземпляры нескольких объектов любого класса. Однако класс JohnSmith, экземпляр которого может быть создан только для одного объекта, должен выдавать сигнал тревоги, указывающий на необходимость дальнейшего исследования. С другой стороны, часто нам приходится создавать класс только с одним возможным объектом. Например, система обработки спутниковых изображений может иметь класс ReceivingStation с единственным объектом. Однако в этом случае мы можем решить проверить класс, учитывая, что может быть построена другая принимающая станция. Решение о валидности и пригодности класса с одним объектом должно приниматься с учетом предметной области. Объектно-ориентированный анализ 805 29.3.5 Всегда применимые атрибуты Общее правило заключается в том, что атрибуты в классе применяются ко всем объектам, созданным из этого класса. Например, предположим, что класс AutoVehicle включает атрибуты manufacturer, model_number и size-of-bed. Все объекты, созданные на основе класса , включают первые два атрибута, однако атрибут size-of-bed не применяется к тем транспортным средствам, которые не являются грузовиками. Тот факт, что атрибут применим не ко всем объектам, созданным из класса, предполагает, что существует возможная структура класса, еще не раскрытая. В этом примере, возможно, мы могли бы использовать структуру Gen-Spec, показанную на рисунке 29.7. 29.3.6 Всегда применимые методы Тот факт, что в классе есть методы, которые не применяются ко всем возможным экземплярам, указывает на то, что в классификации есть возможности для улучшения. Возможно, существует классовая структура, которая еще не была рассмотрена. Это не связано с тем, насколько прост или сложен конкретный метод, но связано с тем фактом, что ожидается, что методы будут применимы ко всем возможным объектам. Например, рассмотрим класс с именем HospitalEmployee, который включает врачей, медсестер и администраторов. В этом классе есть метод с именем PerformMajorSurgery, который применим только к врачам, но не к медсестрам или администраторам. Тот факт, что метод не применяется ко всем объектам, которые могут быть созданы из класса, убедительно указывает на отсутствие структуры класса.
29.3.7 Объекты относятся к предметной области Многим начинающим аналитикам трудно изолировать себя от проблем внедрения на этапах анализа. Из-за невнимательности вопросы, относящиеся к внедрению, часто пронизывают модель. Предположим, что система занимается лицензированием водителей автомобилей. Потому что система должна сохранить в цифровом виде каждого водителя ПерСонал данных может возникнуть соблазн создать класс под названием DriverDiskFile. Но, на самом деле, концепция дискового файла относится к набору решений. С точки зрения набора задач класс, в котором хранятся данные драйвера, лучше было бы назвать DriverInfo. То, как эта информация сохраняется в компьютерном оборудовании, выходит за рамки модели на этапе анализа. Важный вывод заключается в том, что выбор операций обработки, аппаратных устройств, форматов файлов и других факторов, связанных с компьютером, не следует делать на этапе анализа. Конкретные методы, системы или устройства, используемые при реализации решения, на данном этапе несущественны. Поэтому класс, который представляет элемент, связанный с решением, должен быть тщательно исследован. Предположительно, мы можем обнаружить, что спецификации проекта требуют использования определенного алгоритма или метода вычислений. Например, клиент графического пакета запрашивает в документах спецификации, чтобы линии рисовались с использованием algorithm Брезенхема. В этом случае алгоритм становится требованием, основанным на предметной области, и его включение в модель является действительным и уместным. С другой стороны, если в спецификации не упоминается конкретный алгоритм, то было бы преждевременно решать, какой метод следует использовать для рисования линий на этапе анализа. Хорошее общее правило заключается в том, что все вопросы внедрения лучше отложить до проектирования или внедрения, за исключением случаев, когда проблемная область конкретно требует этого. 806 Глава 29 29.3.8 Полученные или вычисленные результаты Наконец, мы должны исследовать те объекты, которые представляют собой простое вычисление или которые получены путем манипулирования сохраненными данными. Например, предположим, что система расчета заработной платы, которая должна выдавать печатный отчет обо всех окладах, выплаченных за период. В этом случае у нас может возникнуть соблазн создать класс, возможно, называемый WeeklyPayrollReport , для представления этого документа. Однако, поскольку этот документ является прямым производным от сохраненных данных, это может быть неправильная классификация. Альтернативное и более удовлетворительное решение состоит в том, чтобы сделать генерацию отчета о заработной плате методом в классе, который хранит соответствующую информацию, а не отдельным объектом. 29.4 Подсистемы Сложность модели иногда выходит из-под контроля. Согласно Coad и Yourdon, система, содержащая 35 классов, имеет средний размер, в то время как система, содержащая около 110 классов, считается большой. Также часто проблемная область включает в себя несколько поддоменов, каждый из которых содержит до 100 классов. При таком уровне сложности основные особенности проблемной области могут стать трудными для понимания и, следовательно, для объяснения клиентам и пользователям. Удобным механизмом уменьшения сложности модели является ее разделение на подсистемы, иногда называемые субъектами в аналитической литературе объектно-ориентированного характера. Подсистемы снижают сложность не за счет упрощения модели, а скорее за счет постепенного сосредоточения внимания на ее различных компонентах. Например, предположим, что модель, изображающая полнофункциональную графическую библиотеку, показана на рисунке 29.13. Рисунок 29.13 Классы и структуры графического инструментария Объектно-ориентированный анализ 807 Графический Текст Устройство мыши Открытая кривая Прямая Закрытая фигура
Строка Курсор мыши Символ leftButton Правая кнопка В таких случаях результирующая модель может быть тщательно продуманной и усложненной. Однако, заметив, что графические операции относятся к рисованию геометрических фигур, отображению текста и взаимодействию с мышью, мы можем разложить их на три независимые подсистемы. Затем каждую из этих подсистем можно рассматривать как логическую сущность, даже если они могут совместно использовать функции или взаимодействовать друг с другом. На рисунке 29.14 мы изменили расположение некоторых классов и добавили прямоугольники, чтобы окружить подсистемы. Каждый прямоугольник помечен именем подсистемы. Некоторые авторы используют цифры для обозначения подсистем. В любом случае результатом является более управляемая модель. Рисунок 29.14 Подсистемы графического инструментария 29.4.1 Подсистемы как модули В разделе 26.2.3 мы обсудили концепцию программного модуля как логической единицы разделения, основанной на структурных или функциональных соображениях. Понятие программного модуля не следует отождествлять с понятием процедуры, подпрограммы или файла на диске, хотя иногда они могут совпадать. Эта точка зрения определяет, что 808 Глава 29 Графика Графическая подсистема Текстовая Текстовая подсистема Подсистема мыши MouseDevice Открытая кривая Прямая ClosedFigure Строка Курсор мыши Символ Левая кнопка Правая кнопка модульность следует рассматривать не как проблему реализации, а как проблему, связанную с системным анализом и проектированием. В этом смысле понятие подсистемы вполне совместимо с понятием модуля. Следовательно, подсистемы, идентифицированные на этапе анализа, могут стать модулями на этапе проектирования. Но для того, чтобы это произошло, подсистема должна соответствовать тем же требованиям, которые предъявляются к модулям. Сплоченность подсистемы Что касается модулей, сплоченность или привязка относится к взаимосвязи между ее внутренними элементами; то же самое относится и к подсистемам. Подсистема, в которой содержащиеся классы тесно связаны, обладает высокой связностью. Иногда мы включаем класс в подсистему просто потому, что для него нет лучшего места. Включение несвязанного класса снижает сплоченность подсистемы. Однако это не означает, что все классы в подсистеме должны быть тесно связаны, скорее, они должны быть тесно связаны в задаче main. Например, на рисунке 29.14 подсистема Мыши содержит класс MouseCursor, который не является частью какой-либо структуры классов. Однако, поскольку этот класс тесно связан с проблемной областью функций мыши, он хорошо вписывается в подсистему. Используя общепринятую терминологию, мы можем сказать, что подсистема, в которой классы слабо связаны, может демонстрировать только случайную связность, в то время как подсистема, в которой классы связаны логически, демонстрирует логическую связность. Уровни когезии трудно определить количественно, но функциональная сила подсистемы зависит от того, обладает ли она высокой когезией . Связь подсистем Концепция связи является мерой степени взаимосвязи между подсистемами . системы. В этом смысле две тесно связанные подсистемы обладают высокой степенью взаимодействия и взаимозависимости, в то время как слабо связанные подсистемы стоят независимо друг от друга. Подсистемы с высокой степенью сцепления трудно понимать, анализировать и повторно
использовать по отдельности, поэтому высокая степень сцепления обычно считается нежелательным свойством . С другой стороны, все подсистемы обычно каким-то образом связаны, поскольку они должны взаимодействовать для создания общей функциональности. Трудно представить систему, в которой все составляющие ее подсистемы полностью не связаны между собой. В общем, хорошая структура подсистемы должна обладать высокой когезией и низкой связностью. Классы должны быть тесно связаны внутри подсистемы, и между подсистемами должна быть минимальная взаимозависимость. 29.5 Атрибуты Атрибут - это элемент данных внутри класса, и каждый отдельный объект, созданный из класса, связан с уникальным набором значений для его атрибутов. Например, на рис. 29.11 каждый объект создается из класса врач имеет уникальное имя, оинcialty, и authorized_to_perform значения атрибутов. Этот уникальный набор значений иногда называют состоянием объекта. Одно из фундаментальных правил объектной ориентации заключается в том, что атрибуты не должны быть доступны напрямую извне класса. Единственный способ получить или изменить Объектно-ориентированный анализ 809 значение атрибута определяется с помощью метода, который выполняет эту функцию. Однако многие языки программирования не налагают этого правила; например, в C ++ можно объявить атрибут типа public access и, таким образом, сделать его видимым для других классов и клиентского кода. Тем не менее, создание атрибутов видимыми за пределами класса нарушает принципы инкапсуляции и абстракции данных, что, в свою очередь, противоречит одной из фундаментальных целей объектно-ориентированной парадигмы. Хотя классы и структуры относительно стабильны на протяжении всего срока службы системы, атрибуты, скорее всего, изменятся. При уточнении или расширении функциональности класса нам часто приходится вводить новые атрибуты и методы. Например, класс Physician на рис. 29.11 может быть уточнен путем введения атрибутов, отражающих номер социального страхования человека и домашний адрес. В этом случае инкапсуляция требует разработки новых методов для доступа к новым атрибутам. 29.5.1 Идентификация атрибута На начальном этапе анализа мы часто рисуем диаграммы классов и структур, исключая атрибуты и методы, определение которых откладывается до тех пор, пока мы не станем более знакомыми с основными элементами модели. Однако этап анализа не будет завершен до тех пор, пока мы не определим атрибуты и методы для каждого класса. Фундаментальное понятие атрибута относится к тому, за знание чего отвечает содержащий класс. Например, у аналитика, определяющего класс Employee, может возникнуть соблазн включить в качестве атрибута пол. Однако, если система не использует пол сотрудника ни в одной из операций обработки, выполняемых классом, и если этот атрибут не требуется при определении состояния объекта, то это выходит за рамки обязанностей системы и его не следует включать. Если атрибут позже будет необходим, он может быть добавлен в это время. Попытка предсказать во время анализа атрибуты или методы, которые могут понадобиться в будущем, обычно является плохой идеей. Результатом часто является система, перегруженная бесполезными элементами, которые только усложняют ее и удорожают. Гораздо практичнее делать общие допущения, которые будут учитывать рост или модификацию, а чем пытаться угадать все требования, которые могут стать необходимыми. При определении атрибутов мы можем задать следующие вопросы: 1. Как описывается объект? 2. Какие свойства связаны с объектом в проблемной области? 3. Какие свойства, связанные с объектом, необходимы для реализации системыобязанности пользователя? 4. Какие свойства объекта необходимо запомнить с течением времени? 5. В каких состояниях может находиться объект? Не все эти вопросы применимы ко всем объектам, и в некоторых случаях более чем на один вопрос может быть получен один и тот же ответ. В то же время некоторые объекты концептуально очень просты, в то время как другие сложны. Например, объект leftButton устройства мыши (см. рис. 29.14) является довольно простым. Похоже, что
810 Глава 29 В данном случае уместны вопросы № 3, 4 и 5. В частности: обязанности системы могут потребовать, чтобы было известно, была ли нажата левая кнопка мыши (кнопка вниз) или отпущена (кнопка вверх). Свойства, которые необходимо запомнить с течением времени , - это текущее состояние кнопки, которое также является возможными состояниями объекта. Следовательно, представляется, что в этом случае может быть достаточно одного атрибута, который кодирует состояние левой кнопки мыши как в данный момент нажатой или поднятой. С другой стороны, классу Employee, который отслеживает лиц, занятых в организации, могут потребоваться такие атрибуты, как имя, адрес, номер так называемой cial_sec_number, date_of_employment, почасовая оплата, hours_worked, доход от уплаты налогов и многие другие. На данный момент может быть рассмотрено несколько этических и юридических вопросов . Например, если незаконно основывать любое решение о приеме на работу на возрасте работника, следует ли сохранять дату рождения этого человека в качестве атрибута? Если у компании нет других причин знать возраст сотрудника, может ли тот факт, что эта информация хранится вместе с другими данными сотрудников, быть использован против компании в споре о дискриминации по возрасту? В этом случае можно было бы привести следующий аргумент: зачем фирме нести расходы на хранение данных, которые она не намерена использовать? Решение о включении или исключении атрибута обычно принимается совместно клиентом и аналитиком. Самый безопасный совет - не включать атрибуты, которые выходят за рамки текущих обязанностей системы . Кристалл-шар-на основе системного анализа можно counterproтворным. 29.5.2 Атрибуты и структуры Иногда мы можем быть не уверены в том, какой класс структуры с общей спецификацией должен содержать определенный атрибут. Например, при присвоении атрибутов классам, изображенным в текстовой подсистеме (показано на рис. 29.14), мы можем решить, что каждая текстовая строка или символ будет иметь определенный стиль письма и размер точки. Должны ли эти атрибуты отображаться в классах String и Character или в тексте родительского класса ? Общим правилом в этом случае является размещение атрибута в самой высокой точке структуры классов, применимой ко всем специализациям. Поэтому атрибуты должны соответствовать тексту класса, как показано на рис. 29.15. Рисунок 29.15 Позиционирование атрибутов в структуре спецификации поколения Объектно-ориентированный анализ 811 Текст Строка letter_style point_size Характер 29.6 Методы или услуги Система функциональна и полезна благодаря выполняемым ею операциям обработки: система должна что-то делать. Совершенная структура классов и атрибутов, не имеющая методов, бесполезна. Конкретное поведение, которое должен демонстрировать объект, называется сервисом. Сервис является синонимом операций обработки и совпадает с методами класса. 29.6.1 Идентификация методов Аналитик может следовать нескольким процедурам, чтобы идентифицировать методы, связанные с каждым классом. В этом отношении могли бы быть полезны следующие вопросы: 1. Каковы состояния объекта? 2. Какие услуги требуются? Состояния объекта Когда объект создается, он ассоциируется с набором значений атрибутов, которые определяют его состояние. Эти атрибуты могут меняться с момента создания объекта до его повторной
сдачи в аренду. Каждое изменение значений атрибутов определяет новое состояние объекта. Мы можем определить состояние объекта, изучив диапазон значений, которые могут быть присвоены каждому атрибуту в свете обязанностей системы. На основе этого анализа мы можем построить простую диаграмму, показывающую различные состояния, которые определены для объекта , а также переходы между состояниями. Например, в отношении объекта MouseCursor на рис. 29.14 мы можем определить, что в обязанности системы входит, в том числе, и то, что она, в том числе, знает, включен курсор или выключен; если включен, то его текущее положение на экране. Теперь класс можно усовершенствовать, нарисовав диаграмму перехода состояний. Позже мы сможем использовать диаграмму перехода состояний для вывода методов класса. Предварительные шаги показаны на рисунке 29.16. Рисунок 29.16 Диаграмма атрибутов и состояния объекта Требуемые службы Из состояний объекта и атрибутов объекта можно вывести требуемые службы или методы. Здесь опять же, рассматриваемые службы должны относиться к тем, которые входят в сферу ответственности системы. Несколько категорий алгоритмически простых 812 Глава 29 MouseCursor Определяющий атрибут класса MouseCursor Диаграмма перехода состояний Состояние = ВЫКЛЮЧЕНО Состояние = ВКЛЮЧЕНО (screen_location) Статус (ВКЛЮЧЕНИЕ - ВЫКЛЮЧЕНИЕ) местоположение экрана службы всегда следует проверять. Обычно они называются Create, Connect, Access и Release: 1. Создайте. Эта служба используется для создания и необязательной инициализации объекта. Когда не требуется конкретная инициализация, служба создания часто является неявной. Однако, если создание объекта требует инициализации атрибутов, он должен быть четко определен. Этот подход согласуется с работой функций конструктора в C ++. 2. Подключайтесь. Этот тип сервиса используется для установления или разрыва соединения между объектами . Это соответствует понятию инстанс-соединения, описанному в разделе 29.7. 3. Доступ. Этот тип сервиса присваивает значения атрибутам или возвращает значения атрибутов. Его существование обусловлено принципами инкапсуляции и абстракции данных, которые требуют, чтобы к атрибутам нельзя было получить доступ извне класса. 4. Освободить или уничтожить. Эта служба уничтожает объект. Это соответствует деструкторам в C ++. Большая часть требуемого поведения объекта может быть выполнена службами Создания, подключения, доступа или уничтожения. Во многих системах на эти алгоритмически простые методы приходится от 80 до 95 процентов требуемой функциональности. Однако часто требуются и алгоритмически сложные методы. Обычно они соответствуют типам Calculate и Monitor. • Рассчитать. Этот тип сервиса использует значения атрибутов в объекте для выполнения вычисления . • Монитор. Этот тип службы обычно связан с внешним устройством или системой. Его функции относятся к вводу, выводу, сбору данных и управлению. Методология, которой следует следовать при определении требуемых сервисов, состоит в том, чтобы сначала определить объект для четырех алгоритмически простых типов сервисов (Создание, подключение, доступ и уничтожение), а затем для двух алгоритмически сложных типов (Вычисление и мониторинг). Все объектные методы попадают в одну из этих категорий. Например, достаточно, рассматривая класс MouseCursor на рис. 29.17, мы можем выделить два алгоритмически простых сервиса. Служба типа доступа используется для определения, находится ли
курсор во включенном или выключенном состоянии. Служба типа Connect используется для установления связи между объектом MouseCursor и службой, которая отслеживает перемещение курсора по экрану. На рисунке 29.17 показана соответствующая диаграмма классов. Рисунок 29.17 Определение служб для объекта MouseCursor Объектно-ориентированный анализ 813 Курсор мыши CursorOn() MonitorCursor() CursorOff() статус (ВКЛЮЧЕНИЕ - ВЫКЛЮЧЕНИЕ) расположение экрана 29.7 Соединения с экземплярами В то время как структуры отображают связи между классами, соединения с экземплярами, как следует из названия, показывают связи между объектами. Поскольку объекты не могут быть созданы из абстрактных классов, не может быть подключения экземпляра к абстрактному классу или из него. На этапе анализа соединение с экземпляром отражает сопоставление между объектами в проблемной области. Обратите внимание, что, хотя соединения экземпляра отменяют ассоциацию примечаний, они не подразумевают наследования, поскольку соединения экземпляра являются более слабым типом ассоциации, чем структура "Целое-часть" или "Спецификация поколения". Как и все другие элементы системного анализа, все подключения к экземплярам должны входить в обязанности системы. 29.7.1 Обозначение соединений с экземплярами Соединения с экземплярами показаны на диаграммах классов в виде сплошных линий между объектами. Необязательно, строки подключения экземпляра могут быть помечены, чтобы показать характер ассоциации . Coad и Yourdon считают, что эти метки обычно не нужны. Обозначение включает цифры, которые указывают верхнюю и нижнюю границы связи объекта . На рисунке 29.18 показан пример обозначения соединения экземпляра. Рисунок 29.18 Пример обозначения подключения к экземпляру На рисунке 29.18 мы отмечаем, что у каждого водителя может быть ноль или одна лицензия, и что каждая лицензия должна соответствовать одному водителю. В первом случае числовое ограничение разделяется запятой для обозначения диапазона. Когда нет явной верхней границы, буква m может быть использована для замены слова “много”. 29.8 Соединения с сообщениями В то время как соединение с экземпляром представляет сопоставление между объектами в проблемной области, соединение с сообщением относится к сообщениям, отправляемым между объектами. В этом контексте часто используются термины “отправитель” и “получатель”. Цель сообщения - получить операцию обработки, которая должна быть определена как в спецификациях отправителя, так и получателя. Тривиальные и буквальные сообщения не включены в это соглашение. Соединения с сообщениями возникают, когда объект создает экземпляр объекта другого класса или когда объект использует методы, определенные в другом классе. 29.8.1 Обозначения соединений с сообщениями Обозначения Coad и Yourdon используют серые линии для обозначения соединений с сообщениями. На нарисованных от руки диаграммах мы могли бы заменить их пунктирными линиями. Чтобы проиллюстрировать соединение message, предположим, что графический пакет содержит класс StraightLine и класс 814 Глава 29 Лицензионный документ Лицо-водитель 0, 1 1 Многоугольник. Каждый объект прямой линии имеет начальную точку, конечную точку и значение толщины . Полигон рисуется путем создания экземпляра нескольких объектов класса StraightLine.
На рисунке 29.19 показаны объекты и соответствующая нотация соединения с сообщением. Рисунок 29.19 Пример обозначения соединения с сообщением Обратите внимание, что стрелка на рисунке 29.19 указывает от отправителя к получателю сообщения. Это подразумевает, что отправитель выполняет какое-то указанное действие, на которое получатель отвечает возвратом результата отправителю или выполнением предопределенной функции . Хотя обычно соединение с сообщением происходит между объектами, также возможно сделать получателя классом, и в этом случае он отвечает созданием экземпляра нового объекта. Альтернативное обозначение использует многопунктовую стрелку в случае, когда получателем является более одного объекта. Обратите внимание, что взаимодействие человека с системой (обычно в форме команд или запросов) часто может быть изображено с помощью символов связи message. 29.9 Заключительная документация После завершения анализа мы должны подготовить соответствующие документы. Наиболее важным из них обычно является диаграмма классов. Однако в крупных проектах часто бывает необходимо уменьшить сложность путем расслоения диаграммы классов следующим образом, как это показано ниже: 1. Слой, указывающий различные подсистемы 2. Слой для каждой подсистемы, указывающий наследование в форме Gen-Spec и Структуры из целых частей 3. Слой, показывающий атрибуты и службы для каждого класса 4. Слой, изображающий подключение к экземпляру и сообщению для каждого объекта В дополнение к диаграммам классов и объектов, с фазой анализа обычно связаны другие документы, а именно: ........... 1. Описание класса и спецификаций объекта 2. Диаграммы перехода состояний 3. Документы, описывающие системные ограничения и критические потоки выполнения Объектно-ориентированный анализ 815 Прямая линия start_x start_y end_x end_y толщина количество линий line_attributes Многоугольник Нарисуйте линию() drawPolygon() Нарисовать полигон() (для строк = от 0 до... Ничья (...)) ЧАСТЬ IV Приложения 817 Приложение A Математическое программирование на C ++
Краткие сведения Это приложение посвящено программированию математического модуля Intel, который является частью всех процессоров Pentium и более поздних версий. Содержание этой главы относится к методам программирования математического модуля Intel с использованием низкоуровневого кода (язык ассемблера) из C ++. AA.0 Программирование математического модуля Языки высокого уровня, такие как C ++, не предоставляют никаких возможностей для программирования модуля Intel math . Обычно единственный выбор программиста состоит в выборе конкретной библиотеки, которая документирована для использования математического модуля, и в надежде на лучшее. Что касается математического модуля, управление программистом ограничено выбором режимов округления и уровней точности в функциях ввода и вывода C ++. Численные вычисления выполняются внутри черного ящика; все, что вы видите, - это входные данные и результаты. Трудность заключается в том, что для программной обработки math unit требуется язык ассемблера. К счастью, некоторые системы разработки на C ++ для ПК, такие как Microsoft Visual C ++, позволяют относительно легко использовать код языка ассемблера из программ на C ++. Архитектура процессора Intel поддерживает сегментированную 16-разрядную модель памяти и плоскую 32-разрядную модель. На протяжении всей книги мы используем более позднюю модель. Использование 32-разрядной модели с плоской памятью гарантирует, что результирующий код совместим с приложениями Windows. В то же время консольные приложения Visual C ++ также могут быть разработаны в модели плоской памяти. Единственное возражение против такого подхода заключается в том, что 32разрядный код несовместим с программами MS DOS. Существует два возможных маршрута для разработки низкоуровневого компонента приложения на C ++: 1. Разработайте низкоуровневый код с помощью среды на основе ассемблера, такой как Microsoft MASM. После сборки исходного кода результирующие объектные файлы включаются в Visual C ++ project и ссылаются на них во время компоновки. 2. Разработайте низкоуровневые подпрограммы с использованием встроенной сборки. Хотя оба метода работают хорошо, вот несколько моментов, которые следует иметь в виду: 819 1. Встроенный код обычно проще разрабатывать и отлаживать. Программа создается и тестируется в единой среде, что часто приводит к сокращению сроков разработки. 2. С другой стороны, встроенный код не может использовать директивы ассемблера для определения объектов данных . Программа должна использовать переменные C ++ для всех данных, но не все типы данных математических единиц могут быть созданы на C ++. 3. Разработка кода математического модуля в MASM приводит к созданию отдельных низкоуровневых объектных файлов. Эти файлы могут быть позже преобразованы в статическую библиотеку или в библиотеку динамических ссылок (DLL), которая может совместно использоваться несколькими приложениями. Подход, которому мы следовали в этой книге, заключается в следующем: 1. Мы используем модули чистого языка ассемблера, разработанные с MASM версии 6.14, для основных подпрограмм математического модуля. Подпрограммы используют плоскую 32-разрядную модель памяти, которая совместима как с программами Windows, так и с консольными приложениями на Visual C ++ . Эти подпрограммы содержатся в модулях с именем UN32_?.ASM, которые являются частью MATH32.LIB. 2. Каждая процедура на языке ассемблера, доступная напрямую, снабжена функцией взаимодействия с C ++ face. Интерфейс C ++ часто содержит встроенный ассемблерный код. Функции интерфейса расположены в заголовочных файлах для соответствующих модулей MATH32.LIB. Например, UN32_4 определяет следующие общедоступные процедуры: ПУБЛИЧНЫЙ _ASCII_TO_EXP ;.... Строка ASCII в экспоненциальной форме PUBLIC _MATH_UNIT_OUTPUT ;.... ST(0) регистрируется в десятичном формате ASCII PUBLIC _MATH_UNIT_INPUT ;.... Экспоненциальная строка ASCII в ST(0) ОБЩЕДОСТУПНЫЙ _GET_TAG ;.... Получить код тега регистрации математической единицы
Эти четыре процедуры низкого уровня были разработаны в MASM, и в результате получился obфайл ject (UN32_4) является одним из модулей MATH32.LIB. Функции интерфейса C ++ для этих четырех процедур расположены в заголовочном файле Un32_4.h. Они имеют следующий прототип: int MathunitInput(char[]); аннулирует AsciiToExp(char[], char[]); int MathunitOutput(char[]); int getTag(int, char[]); В заголовочном файле также объявляются внешние ссылки на соответствующие процедуры в MATH32.LIB. Разработчик приложений на C ++ использует функции интерфейса для доступа к основным процедурам. . AA.1 Исходные тексты MASM в программах на C ++ Предположим, модуль языка ассемблера с именем TESTX.ASM, который содержит процедуру с именем _ID_CPU. Модуль использует следующие директивы MASM: ОБЩЕДОСТУПНЫЙ _ID_CPU ; ПУБЛИЧНОЕ объявление делает процедуру ; доступной извне модуля .486 ; Использовать набор инструкций по математическим единицам .МОДЕЛЬ ПЛОСКАЯ ; Генерировать 32-разрядный код для плоской памяти ; архитектура .ДАННЫЕ ; Далее следуют данные модуля 820 Приложение А ... .КОД ; Далее следует код модуля ... Для сборки модуля вы будете использовать команду: MASM TESTX /Zi; Обратите внимание, что переключатель /Zi обеспечивает совместимость с Codeview для упрощения отладки. При успешной сборке MASM создает файл TESTX.OBJ. После сборки вы копируете оба файла, TESTX.ASM и TESTX.OBJ, в каталог проекта Visual C ++. Теперь в Visual C ++ вы используете кнопку Добавить в проект, чтобы добавить оба файла. На этом этапе как исходный, так и объектный файлы для низкоуровневого модуля доступны в среде Visual C++. Чтобы получить доступ к низкоуровневой процедуре с именем _ID_CPU (расположенной в TESTX.OBJ module) вы должны объявить его как внешнюю ссылку следующим образом: внешний “C” аннулирует ID_CPU(); Обратите внимание, что C ++ автоматически добавляет начальный символ подчеркивания ко всем внешним ссылкам. По этой причине функция языка ассемблера (_ID_CPU) должна содержать начальный _. По тому же принципу оператор C ++ extern не использует ведущее _ при ссылке на процедуру. Программа на C ++ компилируется и связывается обычным образом, будь то Windows или консольное приложение. AA.1.1 Пример кода Следующий пример взят из теста проекта C ++ Un32_1, который разработан в другом местегде в книге. Проект содержит три исходных файла: 1. Основной исходный код C ++, который является точкой входа в программу. Этот файл называется Un32_1.cpp. 2. Заголовочный файл C ++ с именем Un32_1.h, который содержит процедуры интерфейса к низкоуровневой функции. 3. Исходный файл на языке ассемблера, содержащий низкоуровневый код для обработки подпрограммы. Исходный файл C ++, Test Un32_1.cpp, закодирован следующим образом: //************************************************************** // Тесты и упражнения для функций интерфейса к
// подпрограммы в модуле Un32_1 библиотеки MATH32 //************************************************************** // Ссылка: ПРОГРАММНЫЕ РЕШЕНИЯ ДЛЯ УЧЕНЫХ И ИНЖЕНЕРОВ // Автор: Санчес и Кантон // Рабочее пространство : \Test Un32_1 // Точка входа: Test Un32_1.cpp // Другие файлы: Un32_1.asm // Un32_1.obj // Un32_1.h // Разработка: консольное приложение Visual C ++ 6.0 // Даты: 23/2000 декабря // Апрель 4/2001 Математическое программирование на C ++ 821 // Описание: // Модуль драйвера для выполнения процедур в // Модуль Un32_1 MATH32.LIB //*********************************************************** #включить <iostream.h> #включить “Un32_1.h” int main() { // Переменные для программы int menuOption = 1; char buf5[] = “ ”; int num1; char buf4[] = “ ” ; символ asc10[10]; int res1; while(опция меню) { // Отобразить меню cout << “Функции ИНТЕРФЕЙСА C++ Для МОДУЛЯ UN32_1”; cout << “ MATH32.LIB\n” << “ 0 = ЗАВЕРШЕНИЕ ТЕСТА \n” << “ 1 = IntToAsc10() << “ 2= IntToAsc16()\n” << “ 3 = AscToInt ()\n” << “ ВЫБРАТЬ: ”; cin >> Опция меню; переключатель (menuOption) { случай 1: // IntToAsc10() cout << “Тестирование IntToAsc10()\n”; cout << “Введите значение: ”; cin >> num1; IntToAsc10(num1, buf5); cout << “Результат =” < buf5; cout << “\n\n”; перерыв; случай 2: // IntToAsc16() cout << “Тестирование IntToAsc16()\n”; cout << “Введите значение: ”; cin >> num1;
IntToAsc16(num1, buf4); cout << “Результат =” < buf4; cout << “\n\n”; перерыв; случай 3: // AscToInt () cout << “Проверка AscToInt \n”; cout << “Введите строку ASCII (10 цифр или меньше): ”; cin >> asc10; res1 = AscToInt (asc10); cout << “Результат = ” < res1; cout << “\n\n”; перерыв; 822 Приложение А } } возвращает 0; } Заголовочный файл C ++, Un32_1.h, кодируется следующим образом: //************************************************************ // Функции интерфейса к модулю Un32_1 MATH32.LIB //************************************************************ // Ссылка: ПРОГРАММНЫЕ РЕШЕНИЯ ДЛЯ УЧЕНЫХ И ИНЖЕНЕРОВ // Санчес и Кантон // Рабочее пространство : \Test Un32_1 // Точка входа: Test Un32_1 // Другие файлы: Un32_1.asm // Un32_1.obj // Тестировать Un32_1.cpp // Разработка: консольное приложение Visual C ++ 6.0 // Даты: 23/2000 декабря // Апрель 4/2001 // Описание: // Функции интерфейса C++ для некоторых процедур // в модуле Un32_1 библиотеки MATH32 // Примечание: // Интерфейс к другим процедурам в Un32_1 // модуль MATH32.LIB не предоставляется, поскольку // в книгу включены эквивалентные функции на C ++. Если // требуются другие интерфейсы, читатель может использовать // перечисленные в качестве шаблона. //************************************************************ // Список всех процедур в модуле Un32_1 MATH32.LIB extern “C” BIN_TO_ASC10(); // 16-разрядный двоичный файл в 5-значный десятичный код ASCII extern “C” BIN_TO_ASC16(); // 16-разрядный двоичный файл в 4-значный шестнадцатеричный код ASCII extern “C” ASCII_TO_EDX(); // 4-значный десятичный код ASCII в двоичный код в EDX extern “C” BINF_TO_DECF(); // 8-разрядный двоичный код до 8-значной десятичной дроби extern “C” DECODE_SINGLE();// Изолировать элементы одинарной точности extern “C” BIN_TO_BCD(); // 16-разрядный двоичный код до 5 цифр BCD extern “C” BCD_TO_ASCII(); // 5 распакованных BCD до 5 цифр ASCII extern “C” BCD_INTO_REG(); // 5-разрядный BCD в двоичный файл в формате DX extern “C” BCD12_TO_ASCSTR();// число BCD12 в строке ASCII extern “C” ASCII_TO_BCD12(); // десятичная строка ASCII в формате BCD12 extern “C” ASCII_TO_EDX(); // Преобразование ASCII в двоичный файл в регистре EDX //**************************************************************
// Прототипы //************************************************************** аннулировать IntToAsc10(int, char[]); аннулировать IntToAsc16(int, char[]); ввести AscToInt (char[]); //************************************************************** // Функции интерфейса C ++ для некоторых процедур в // модуль Un32_1.asm MATH32.LIB //************************************************************** аннулирует IntToAsc10(значение int, char * buffer) { // Функция для преобразования 16-разрядного двоичного числа в 5 ASCII // десятичных цифр // При вводе: // значение параметра содержит двоичное число Математическое программирование на C ++ 823
// буфер параметров[] - это массив символов для результата // Использует процедуру BIN_TO_ASC10 в модуле Un32_1 // библиотеки MATH32 _asm { MOV EAX, значение MOV EDI, буфер ВЫЗОВ BIN_TO_ASC10 } возврат; } void IntToAsc16(значение int, char * buffer) { // Функция для преобразования 16-разрядного двоичного числа в 4 ASCII // шестнадцатеричные цифры // При вводе: // значение параметра содержит двоичное число // буфер параметров[] - это массив символов для результата // Использует процедуру BIN_TO_ASC16 в модуле Un32_1 // библиотеки MATH32 _asm { MOV EAX, значение MOV EDI, буфер ВЫЗОВ BIN_TO_ASC16 } возврат; } int AscToInt(символ * буфер) // Преобразует строку ASCII в двоичный код // При вводе: // буфер параметров[] представляет собой массив символов, содержащий // десятичная строка из 10 цифр в формате ASCII в диапазоне // от 0 до 4 294 967 295 // При выходе: // возвращает двоичное значение типа int // Использует процедуру ASCII_TO_EDX в модуле Un32_1 // библиотеки MATH32 { int aVal = 0; _asm { ПЕРЕМЕЩЕНИЕ ESI, буфер ВЫЗОВ ASCII_TO_EDX ПЕРЕМЕЩЕНИЕ аВал, EDX } вернуть значение aVal; } Исходный файл на языке ассемблера называется UN32_1.ASM. Ниже приводится частичный список кода: ;***************************************************************** ;***************************************************************** ; UN32_1.ASM ;***************************************************************** ;***************************************************************** 824 Приложение А
; Библиотека математических процедур из книги ; ; Дата создания: 9 января 2001 ; Обновлено: 31 марта 2001 ; Название библиотеки: MATH32.LIB ; Ссылка: Глава 1 ; Имя модуля: UN32_1.ASM ; Описание: ; Процедуры преобразования системы счисления. ; ;***************************************************************** ; О библиотеке MATH32 ;***************************************************************** ; Окружающая среда: ; Все модули в этой библиотеке были собраны с версией MASM ; 6.11 и используется в консольных приложениях под управлением Visual C ++ 6.0. ; 32-разрядный код не может быть использован в DOS. ; Имена процедур: ; Процедуры, на которые ссылается код C ++, имеют имена, начинающиеся с ; символ _. В именах процедур локальной поддержки не используется ; _ символ в качестве первого символа. ; ;***************************************************************** ; общедоступные ;***************************************************************** ОБЩЕДОСТУПНЫЕ _BIN_TO_ASC10 ; .. 16-разрядный двоичный файл в 5-значный десятичный код ASCII ОБЩЕДОСТУПНЫЙ _BIN_TO_ASC16 ; .. 16-разрядный двоичный файл в 4-значный ASCII hex ОБЩЕДОСТУПНЫЙ _ASCII_TO_DX ; .. преобразование 4-значного ASCII из десятичного в двоичный формат в формате DX ОБЩЕДОСТУПНЫЙ _BINF_TO_DECF ; .. 8-разрядный двоичный файл с преобразованием в 8-значную десятичную дробь PUBLIC _DECODE_SINGLE ; .. Изолировать элементы одинарной точности PUBLIC _BIN_TO_BCD ; .. 16-разрядный двоичный файл с 5 разрядами BCD ОБЩЕДОСТУПНЫЙ _BCD_TO_ASCII ; .. 5 распакованных BCD до 5 цифр ASCII ОБЩЕДОСТУПНЫЙ _BCD_INTO_REG ; .. 5-значный BCD в двоичный файл в DX PUBLIC _BCD12_TO_ASCSTR;.. Номер BCD12 в строке ASCII PUBLIC _ASCII_TO_BCD12 ;.. Десятичная строка ASCII в формате BCD12 ОБЩЕДОСТУПНЫЙ _ASCII_TO_EDX ; .. ASCII в двоичный файл в регистре EDX ;**************************************************************** ; определения для 32-разрядной плоской модели ;**************************************************************** .486 .МОДЕЛЬ плоская .ДАННЫЕ ;*************************************** ; данные для этого модуля ;*************************************** ; Коэффициенты преобразования BCD BCD_FACTORS ДБ 50Ч, 00,00,00 ; .50000000 ДБ 25Ч, 00,00,00 ; .25000000 ДБ 12Ч, 50Ч, 00,00 ; .12500000
ДБ 06Ч, 25Ч, 00,00 ; .06250000 ДБ 03Ч, 12Ч, 50Ч, 00 ; .03125000 ДБ 01Ч, 56Ч, 25Ч, 00 ; .01562500 ДБ 00Ч, 78Ч, 12Ч, 50Ч ; .00781250 ДБ 00Ч, 39Ч, 06Ч, 25Ч ; .00390625 ; Временные данные сегмента кода ASC_TEMP_BUF DB 5 дублирующих операций (20 часов) DB 0 ; Терминатор Математическое программирование на C ++ 825 OFFSET_ASC DD 0 ; Смещение записи буфера ASCII OFFSET_BCD DD 0 ; Смещение ввода буфера BCD OFFSET_ASCBUF DD 0 ; Смещение записи буфера ASCII BCD_BUF ДБ 5 ДУБЛИРУЮЩИХ (0) ;***************************************************************** ; код ;***************************************************************** .КОД _BIN_TO_ASC10 Процедура ; Преобразования 16-разрядного двоичного числа в 5 десятичных ; цифр в формате ASCII; При вводе: ; AX = двоичное число ; EDI —> 5-байтовый буфер ASCII ; При выходе: ; Буфер содержит десятичные цифры ASCII ; ;***********************| ; очистить буфер ASCII | ;***********************| MOV ECX, 5 ; Пять цифр для очистки CLEAR_5: MOV БАЙТ PTR [EDI], 20 ЧАСОВ ; Четкая цифра ВКЛ EDI ; Указатель поворота ЦИКЛ CLEAR_5 ; Повторите для 5 цифр ДЕКАБРЬ EDI ; Установить указатель буфера на последнюю
; цифру MOV ECX,10 ; Десятичный делитель в CX ;***********************| ; получаем десятичный код ASCII | ; цифра | ;***********************| ПОЛУЧАЕМ_ASC10: MOV DX, 0 ; Очистить для разделения слов РАЗДЕЛ CX ; Выполнить деление AX/CX ; Частное в AX, а остаток в DL ; Преобразовать десятичную дробь в десятичную в формате ASCII Добавить Продолжительность, 30 часов ; Добавьте 30 часов, чтобы привести к диапазону ASCII ;***********************| ; цифра магазина | ; указатель буфера перехода | ;***********************| MOV БАЙТ PTR [EDI], DL ; Сохранить цифру в буфере ДЕКАБРЬ EDI ; Указатель буфера на следующую цифру ; Примечание: двоичное частное оставляется в AX инструкцией DIV CX ;***********************| ; является ли частное = 0 ? | ;***********************| CMP AX,0 ; Проверка на окончание двоичного файла JNZ ПОЛУЧИТЬ_ASC10 ; Продолжить, если не 0 ;***********************| ; окончание преобразования | ;***********************| CLD RET _BIN_TO_ASC10 КОНЕЦ 826 Приложение А ; ; _BIN_TO_ASC16 ПРОЦЕДУРА ИСПОЛЬЗУЕТ процедуру edi esi ebx ebp ; для преобразования 16-разрядного двоичного числа в 4 ASCII ; шестнадцатеричных цифры ; При вводе: ; AX = двоичное число ; EDI —> 4-байтовый буфер ASCII ; При выходе: ; Буфер содержит десятичные цифры ASCII
; Таблица совместимости процессоров: ; | 80386 | ДА | ; | 80486/PENTIUM | ДА | ; . . . и так далее ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ В режиме ОНЛАЙН Пример, упомянутый в этой главе, содержится в проекте TestUn32_1, размещенном в папке главы 26 онлайнового программного обеспечения книги. Математическое программирование на C ++ 827 Приложение B Точность экспоненциальных функций Краткие сведения В этом приложении мы сравниваем точность и производительность трех основных методов вычисления экспоненциальных функций, упомянутых в тексте. Хотя строгий алгоритм анализа выходит за рамки данной книги, мы пытались некоторые эмпирические оценки различных методов. Реализация alалгоритма факторизации экспоненты, используемого для тестов для случая 10 y - это процедура с именем TEN_TO_EXP. Эта процедура содержится в модуле Un32_4 библиотеки MATH32. Реализации алгоритмов двоичного включения питания и логарифмической аппроксимации для 10 -го случая y были основаны на процедурах для получения x y ..................... y перечислены в главе 8. Расчеты точности AB.0 Средняя производительность для различных алгоритмов определялась по времени вычисления всех возможных 4932 значений показателя степени 10 y . Результаты этого теста приведены в таблице AB.1. Таблица AB.1 Среднее время выполнения при расчете 10 y иx y ЛОГАРИФМИЧЕСКИЙ ПОКАЗАТЕЛЬ СТЕПЕНИ ДВОИЧНЫЙ РЕГИСТР ПРИБЛИЖЕНИЕ ФАКТОРИНГ МОЩНОСТЬ 10 y 1 1.08 0.80 x y 1
0.43 1.28 Тесты производительности, приведенные в таблице AB.1, позволяют нам сделать вывод, что наилучшая общая производительность достигается с помощью алгоритма факторизации экспоненты, который был немного лучше, чем при логарифмической аппроксимации. Тесты точности, которые также были эмпирическими, были основаны на предположении, что накопленная ошибка увеличивается с увеличением количества арифметических операций. Поэтому ошибки расчета были проанализированы для самых высоких значений показателя в пределах тестовой среды (10 4932 ). Точка отсчета для точности 829 тесты представляли собой константы памяти, определенные с машинной точностью epsilon. В таблице AB.2 приведены результаты этого теста точности. Таблица AB.2 Тесты точности для вычисления 10 y ПОКАЗАТЕЛЬ ПОСТОЯННАЯ ЭКСПОНЕНТА ДВОИЧНАЯ LOG КE МАХОВИК ФАКТОРИНГ МОЩНОСТЬ ПРИБЛИЖЕНИЕ 4926 D11CCH D11CCH D11E0H CFBC3 0 5 12 4927 82B1FH 82B1FH 82B2CH 82501 0 4 10 4928 A35E7H A35E8H A35CCH A37CEH 1 5 8 4929

именем ACTION1, если BX = 2, то выполняется процедура с именем ACTION2 завершается. Следует отметить, что программный код IND_JMP выполняет проверку входных данных в допустимом диапазоне. Эта проверка целесообразна, поскольку при таком типе выполнения недопустимое смещение отправляет программу на мусорный адрес, что приводит к почти верному сбою. Простая таблица отправки AC.9.1 C ++ реализует косвенное перенаправление кода с помощью указателей на функции. Поскольку адрес функции является ее точкой входа, этот адрес может быть сохранен в указателе и использоваться для вызова функции. Когда эти адреса хранятся в массиве указателей, то результирующей структурой является таблица вызовов, аналогичная той, которая обсуждалась в предыдущем разделе. Таблицы переходов и вызовов иногда называются таблицами отправки программистами C и C ++mers. Реализация указателей на функции и диспетчерские таблицы в C и C++ требует специального синтаксиса. Во-первых, указатель на функцию имеет тип, который соответствует типу данных, возвращаемому функцией, и объявляется в скобках . Например, чтобы объявить указатель на функцию с именем fun_ptr, который получает два параметра типа int в переменных с именами x и y и возвращает void, мы бы закодировали: void (*fun_ptr) (int x, int y); Круглые скобки имеют эффект привязки к имени функции, а не к ее типу данных . Если бы в этом утверждении мы убрали круглые скобки, результатом было бы 854 Приложение C указатель на функцию, которая возвращает тип void. Обратите внимание, что в предыдущей строке создается указатель на функцию, который еще не инициализирован. Этот указатель может быть установлен так, чтобы указывать на любую функцию, которая получает два параметра типа int и возвращает void. Например, если существовала функция с именем Fun1 с этими характеристиками, мы могли бы инициализировать указатель на функцию с помощью инструкции: fun_ptr = Fun1; Обратите внимание, что компилятор C и C ++ предполагает, что имя функции является указателем на ее точку входа, поэтому адрес оператора (&) не используется. Как только указатель на функцию инициализирован, мы можем получить доступ к функции Fun1 с помощью инструкции: (*Забава 1)(6, 8); В этом случае мы передаем функции два целочисленных параметра обычным образом. Объявление и инициализация диспетчерской таблицы также требуют специального синтаксиса. Поскольку диспетчерская таблица не должна изменяться с помощью кода, хорошей идеей будет объявить ее статического типа. Например, следующая инструкция создает массив с именем dispatch, который содержит адреса функций с именами Fun1, Fun2, Fun3 и Fun4, все из которых не получают аргументов и возвращают void. статическая пустота (* отправка[]) (void) = {Fun1, Fun2, Fun3, Fun4}; Чтобы получить доступ к определенной функции, адрес которой хранится в таблице отправки, мы должны создать переменную для хранения этого смещения. Например: unsigned int off_var = 1; Поскольку off_var теперь инициализирован для второй записи в массиве (напомним, что индексы массива основаны на нуле), мы можем получить доступ к Fun2 с помощью инструкции: (* отправка[off_var]) (); Следующая программа демонстрирует использование диспетчерской таблицы на C ++ для достижения той же цели, что и программа на языке ассемблера с именем IND_JMP, перечисленная в разделе AC.9. //************************************************************* // Программа на C++ для иллюстрации указателей на функции и использования // таблиц отправки // Имя файла: SAM06 09.CPP //************************************************************* #включить <iostream.h> #включить <string.h> // Прототипы функций
аннулируют действие 1 (void); аннулируют действие 2(void); Косвенность C ++ 855 недействительное Действие3 (недействительно); недействительное Действие4 (недействительно); //**************************** // main() //**************************** main() { // Создать таблицу отправки со статической пустотой (*add_table[]) (void) = {Action1, Action2, Action3, Action4}; // Создать переменную для смещения в таблицу unsigned int table_offset = 0; // Запрашивать пользователя для ввода cout < “\nEnter число от 1 до 4: ”; cin > table_offset; // Проверка на недопустимый пользовательский ввод if (table_offset < 1 || table_offset > 4) { cout < “\nInvalid input \ n”; возвращает 1; } table_offset ; // Настроить смещение на диапазон (*добавить таблицу[table_offset]) (); // Функция доступа с использованием таблицы // и смещения, введенного пользователем возвращает 0; } //**************************** // функции //**************************** аннулирует действие 1(void) { cout < “\nAction 1 выполнено\n”; возвращает; } недействительное Действие2(void) { cout < “выполнено действие 2\n”; возврат; } аннулировать действие 3(void) { cout < “выполнено действие 3\n”; возврат; } аннулировать действие 4(void) { cout < “выполнено действие 4\n”; возврат; } Таблица индексированной отправки AC.9.2 Программа SAM06-09.CPP, упомянутая ранее, показывает самый простой из возможных способов реализации диспетчерской таблицы. Обработка может быть изменена таким образом, чтобы отображать меню опций, из которого пользователь выбирает ту, которая будет выполнена. Затем код манипулирует пользовательским вводом таким образом, чтобы он служил смещением в таблице переходов. Однако существует несколько возражений против этого простого механизма. Возможно, самый важный 856 Приложение C важным из них является то, что код предполагает, что адреса различных процедур обработки хранятся в одном слове памяти. Поэтому он использует unsigned int в качестве индекса для различных записей. Один из возможных рисков такого подхода заключается в том, что он может быть верен в одних memoryмоделях, машинах или компиляторах, но не в других. Более элегантное и строгое решение, хотя и немного более сложное, основано на индексированной таблице отправки. Подход с индексированной таблицей отправки позволяет уменьшить размер сохраненных адресов, поскольку правильный адрес определяется не путем добавления смещения к базе, а путем выполнения операции поиска в таблице. Сама таблица соотносит входные коды , введенные пользователем (или другие идентификационные коды или
s t r i n g s ) , с тем , что происходит с e a d d r e s s e s o f h e v a r i o u s s e l e c t a b l e f u n c t i o n s . P r o g r a m SAM06-10.CPP использует индексированную таблицу отправки для получения указателя на соответствующую функцию. //************************************************************* // Программа на C++ для иллюстрации указателей на функции и использования // индексированных таблиц отправки // Имя файла: SAM06 10.CPP //************************************************************* #включить <iostream.h> // Прототипы недействительное Действие1 (недействительно); недействительное Действие2 (недействительно); недействительное Действие3 (недействительно); недействительное Действие4 (недействительно); // Структура для создания индексированной диспетчерской таблицы struct index_cmd { int user_code; void (*функция) (void); }; //**************************** // main() //**************************** main() { // Настройка индексированной диспетчерской таблицы с использованием структуры index_cmd struct index_cmd add_table[] = { { 1, Действие 1 }, { 2, Действие 2 }, { 3, Действие 3 }, { 4, Действие 4 } }; // Создать переменную для хранения пользовательского ввода int user_input = 0; // Запрашивать пользователя для ввода cout < “\nEnter число от 1 до 4: ”; cin > user_input; // Проверка на недопустимый ввод if(user_input < 1 || user_input > 5){ cout < “\ninvalid input \ n” ; Косвенность C ++ 857 возврат 1; } // Поиск в таблице поиска соответствия для (int i = 0; i < 5; i++) { if (user_input == add_table[i].пользовательский код) { (*add_table[i].функция) (); разрыв; } } возвращает 0; } //**************************** // функции //**************************** недействительное Действие1(void) { cout < “выполнено действие 1\n”; возвращение; } аннулировать действие 2(void) { cout < “выполнено действие 2\n”; возвращение; } аннулировать действие 3(void) { cout < “выполнено действие 3\n”; возвращение; } аннулировать действие 4(void) { cout < “выполнено действие 4\n”; возвращение;
} Программа SAM06-10.CPP использует целочисленное значение, введенное пользователем, для поиска в структуре, которая содержит все четыре юридических кода (от 1 до 4), сопоставленных с объявлениями соответствующих функций. Поиск выполняется путем сравнения значения, введенного пользователем, с сохраненными кодами. Как только правильный код найден, функция вызывается с помощью инструкции (*add_table[i].функция) (); Альтернативный подход, основанный на строке, может быть реализован с помощью strcmp() для выполнения операции сравнения. AC.10 Усложнение косвенности C позволяет создавать указатели на указатели, таким образом формулируя механизм для усложнения косвенности. Например, сначала мы создаем и инициализируем целочисленную переменную, способную: 858 Приложение C в значении 1 = 10; затем мы создаем и инициализируем указатель на переменную value1: int *int_ptr = &value1; на этом этапе мы можем создать указатель на переменную указателя int_ptr с помощью инструкции int **ptr_ptr = &int_ptr; Рисунок AC.3 представляет собой сложную косвенность. Рисунок АС.3 Визуализация сложной косвенности На рисунке AC.3 переменная ptr_ptr указывает на указатель int_ptr, который, в свою очередь , указывает на целочисленную переменную с именем value1. Чтобы получить доступ к переменной value1 и изменить ее значение, вы можете закодировать: value1 = 20; или простым косвенным обращением: *int_ptr = 20; также с помощью сложной косвенности: **ptr_ptr = 20; Косвенность C ++ 859 значение int1 = 10; int *int_ptr = &value1; int **ptr_ptr = &int_ptr; значение1 int_ptr ptr_ptr 10 адрес value1 адрес int_ptr Приложение D Программы с несколькими файлами Краткие сведения Это приложение предназначено для удобства разбиения программы на несколько файлов или компонентов. Приложения вскоре вырастают до неуправляемых размеров, а разбиение на несколько файлов упрощает и помогает организовать разработку и обслуживание. ОБЪЯВЛЕНИЕ.0 Разбиение программы на разделы Существует много причин для разделения программы на несколько файлов, наиболее очевидная
из которых, упомянутая ранее, заключается в том, что исходный код разросся до таких размеров, что стал неуправляемым. В этом случае разделение на файлы основано на физических причинах или на удобстве хранения и обработки. Результирующие файлы представляют собой простые физические подразделения программы без какой-либо другой логической или организационной основы. .......... Однако, даже когда основной причиной разделения программы на файлы является простой вопрос размера, программист или дизайнер обычно пытается найти какие-то другие, более разумные критерии, по которым можно выполнить фактическое разделение. Например, одним из вариантов может быть помещение всех функций и вспомогательных подпрограмм в отдельный файл, в то время как основная функция и другие центральные программные компоненты хранятся в другом файле. Хотя этот метод разбиения на части далек от идеала, он лучше, чем просто произвольное нарезание кода на две или более частей, как если бы это была буханка хлеба. В большинстве случаев разбиение программы на несколько файлов исходного кода МОtivated по иным причинам, чем ее физический размер, например, для облегчения девелопмент несколькими программистами или группами программист. В этом случае разделение программы обычно выполняется в соответствии с модулями, определенными на этапах анализа и проектирования. Тот факт, что разделение на модули облегчает организацию проекта, а также распределение задач на этапе кодирования, не означает, что модульность - это не что иное, как инструмент управления проектом. Модули являются логическими подразделениями программы и сами по себе имеют законную причину для существования. Фундаментальный 861 понятие программного модуля должно быть связано с концептуальным дизайном программы , а не с удобством администрирования. Как только модули установлены, размещение каждого из них в отдельных программных файлах (или в группе связанных файлов, если того требует размер модуля) обычно само по себе является хорошей идеей, поскольку это способствует сохранению логической структуры программы. Программы компоновщика, поставляемые со всеми текущими версиями C ++ для ПК, поддерживают несколько исходных файлов. Как на самом деле создаются многофайловые программы на C ++, обсуждается в разделе AD.1. Библиотеки классов AD.0.1 Библиотеки объектного кода поддерживаются большинством процедурных языков. В fundamental отсутствует понятие-обеспечить механизм для повторного использования кода, который не требует очистки или вырезать и вставить операций. Служебные программы, иногда называемые менеджерами библиотек, позволяют создавать программные файлы, на которые можно ссылаться во время создания ссылки. Внедрение библиотек обычно зависит от операционной системы. В мире ПК библиотеки DOS имеют формат, отличный от формата библиотек Windows, и библиотечный код не может быть легко перенесен в две среды. Библиотека обычно организована в файлы или модули в соответствии с ее логическим оформлением. Библиотеки для реализации функций графического интерфейса, графики и предоставления специальной математической поддержки, такой как статистика или векторный анализ, доступны уже много лет. Структура обычной библиотеки DOS для не объектно-ориентированного языка обычно состоит из набора функций и связанных структур данных. Библиотеки классов стали возможны после появления языков объектно-ориентированного программирования . Поскольку классы обеспечивают механизм инкапсуляции данных и операций , а также поскольку объектная ориентация облегчает моделирование проблем реального мира, библиотеки классов являются эффективным способом обеспечения определенной функциональности. В этом случае интерфейс между клиентским кодом и самой библиотекой является одновременно гибким и чистым. Единственным недостатком библиотек классов является то, что клиент должен относительно свободно владеть объектно-ориентированным программированием, чтобы получить доступ к его сервисам. Это объясняет, почему в течение многих лет разработчики библиотек Windows держались подальше от объектной ориентации. Лишь недавно объектно-ориентированные средства разработки для программирования под Windows получили широкое распространение. AD.0.2 Общедоступные и закрытые компоненты Компоненты библиотек классов обычно делятся на две группы: общедоступные и
частные. Общедоступными элементами библиотеки классов являются файлы и объявления, необходимые для документирования требований к интерфейсу. В C ++ общедоступные компоненты обычно предоставляются в заголовочных файлах, которые используют расширение .h. Затем клиентский код может использовать директиву #include для физического включения файла заголовка в свой собственный исходный код. В программах, ориентированных на object, файлы заголовков обычно содержат объявления классов. Эти объявления позволяют клиенту создавать экземпляры соответствующих объектов , чтобы получить доступ к методам класса или наследовать от этих классов. Интересной возможностью является использование абстрактных классов для определения интерфейса библиотеки, а затем сбор всех абстрактных классов в заголовочный файл. 862 Приложение D Закрытые элементы объявленных классов обычно содержат программные переменные и константы, структуры данных и функции-члены, которые необязательно делать видимыми для клиентского кода. Придание этим программным элементам приватности обеспечивает достижение двух практических целей : • Клиенту не приходится иметь дело с информацией, которая не является необходимой для использования библиотеки классов. • Конфиденциальность исходного кода защищена. В соответствии с этим разделением на общедоступные и закрытые элементы библиотеки классов для ПК обычно содержат следующие типы файлов. 1. Заголовочные файлы. Они используются для кодирования элементов открытого класса, которые описывают и документируют интерфейс и предоставляют необходимые перехваты доступа. Эти файлы могут быть коллекций простой predeclarations или прототипы. Они могут содержать фактическую реализацию класса, предоставляющего интерфейс библиотеки, или они могут содержать комбинацию как объявлений, так и реализаций. В C и C ++ заголовочные файлы имеют расширение .h. Заголовочные файлы на языке ассемблера для ПК обычно называются include files и имеют расширение .INC. 2. Объектные файлы. Они содержат исполняемый код для частных элементов библиотеки. На эти объектные файлы обычно ссылаются в ранее упомянутых файлах заголовков. Файлы Objject представлены в двоичном формате, поэтому исходный код недоступен клиенту. 3. Библиотечные файлы. Это коллекция объектных файлов, организованных и отформатированных библиотекой утилита управления. Одним из преимуществ библиотек перед отдельными объектными файлами является то, что клиент может иметь дело с одним программным элементом вместо нескольких отдельных компонентов, которые из десяти взаимодействуют. Другая проблема заключается в том, что менеджер библиотеки проверяет согласованность во всех компонентных модулях и обнаруживает любые повторяющиеся имена или структурные ошибки. Всякий раз, когда приравнивание файлов и модулей является практичным и логичным, никакое другое обоснование разделения не может быть предпочтительнее. Однако практические соображения часто вынуждают деподписантов включать несколько модулей в один файл или распространять один модуль по нескольким файлам. AD.0.3 Библиотеки объектно-ориентированных классов Наследование - мощный механизм повторного использования кода. Его основная идея заключается в том, что полезный тип часто является вариантом другого типа. Вместо того, чтобы создавать один и тот же код для каждого варианта, производный класс наследует функциональность базового класса. Затем производный класс может быть изменен путем добавления новых членов, модификации существующих функций или изменения прав доступа. Идея о том, что таксономическая классификация может быть использована для обобщения большого объема знаний, является центральной для всей науки. Оба типа полиморфизма (во время компиляции и во время выполнения) способствуют повторному использованию кода. Полиморфизм во время компиляции, либо путем классификации, либо путем перегрузки функций и операторов, позволяет расширить функциональность без замены завершающего кода.
Механизм искажения имен в компиляторе генерирует разные внутренние имена для функций, которые отображаются с одинаковым внешним назначением, при условии, что переданные параметры различны. Для полиморфизма во время выполнения требуется указатель на базовый класс Программы с несколькими файлами 863 который перенаправляется на производный объект, содержащий виртуальную функцию. В этом случае тип объекта определяет версию вызываемой функции. В случае полиморфизма во время выполнения никакие изменения в списке параметров не требуются; фактически, любое изменение в списке параметров порождает перегруженную функцию и ее виртуальная природа теряется. На языке программирования, таком как C ++, библиотека классов может использовать преимущества всех механизмов и конструкций объектной ориентации для создания мощного продукта с простым доступом. Разрешая наследование классов, библиотека позволяет клиенту изменять или расширять свои функциональные возможности, не изменяя того, что уже было протестировано. Более того, библиотека может использовать объектную композицию, чтобы освободить клиента от необходимости выполнять множество сложных задач и скрыть другие детали реализации, как показано в некоторых шаблонах, описанных в главе 12. Поддержка нескольких файлов AD.1 в C ++ В реальности коммерческого программирования для всех приложений, кроме самых простых, требуется несколько файлов. Все коммерческие компиляторы C ++ и платформы разработки обеспечивают поддержку многофайловых проектов. Некоторые среды (например, менеджеры проектов в Borland C ++ и Microsoft Visual C ++) включают средства разработки, которые автоматизируют создание и обслуживание многофайловых проектов. В этих средах программист определяет список исходных текстов и объектных файлов, а также другие структурные ограничения, а менеджер проекта выполняет операции компиляции и компоновки, которые необходимы для создания исполняемого кода. Альтернативно, каждый исходный файл может быть скомпилирован отдельно, тогда из командной строки можно вызвать компоновщик со списком объектных файлов и библиотек, которые должны использоваться для создания исполняемого файла. Или файлы могут быть перечислены в специальном текстовом файле, который считывается компоновщиком. В любом случае результатом является программа, состоящая из более чем одного исходного файла. На рисунке AD.1 показаны различные компоненты многофайловой программы на C ++. Архитектура многофайловой программы способствует повторному использованию, размещая потенциально повторно используемые компоненты в отдельных программных элементах, на которые легко ссылаться. Так обстоит дело с файлами заголовков, библиотеками классов и объектных файлов. ОБЪЯВЛЕНИЕ.2 Многоязычное программирование До сих пор мы рассматривали многофайловую программу как программу, состоящую из нескольких файлов, закодированных на одном языке программирования. Однако использование более чем одного языка при создании программы является мощным методом разработки. Когда мы говорим о многоязыковом программировании, мы имеем в виду смешение двух или более языков высокого или низкого уровня. Смешивание языков высокого уровня может быть способом повторного использования кода, разработанного в другой среде программирования. Например, нам может быть доступна мощная математическая библиотека, разработанная на FORTRAN, которую мы хотим использовать в приложении, закодированном на C ++. 864 Приложение D Рис.1 Компоненты многофайловой программы на C++ Во многих языковых реализациях возможен доступ к процедуре, функции или подпрограмме, закодированной на другом языке высокого уровня. Таким образом, мы можем быть в состоянии создать код на C ++, который вызывает функции в математической библиотеке FORTRAN. В большинстве сред разработки интерфейс реализован на уровне объектного файла, а не на уровне исходного кода. В многоязыковом программировании необходимо учитывать три проблемы: соглашения об именовании, вызове и передаче параметров. ОБЪЯВЛЕНИЕ.2.1 Соглашения об именовании
Это относится к тому, как компилятор или ассемблер изменяет имена по мере их чтения из исходного файла в объектный файл, а также к количеству символов верхнего или нижнего регистра, которые распознаются. Например, широко используемый компилятор FORTRAN, продаваемый Microsoft, переводит все строчные символы в верхний регистр и допускает не более шести символов на имя, в то время как новейший компилятор C ++ распознает тридцать два символа , которые могут быть в верхнем или нижнем регистре, и добавляет символ подчеркивания перед именем. Программы с несколькими файлами 865 VENDOR.H ПОСТАВЩИК.OBJ USER2.CPP USER1.CPP USER.OBJ PROG.EXE КОМПИЛЯТОР КОМПОНОВЩИК ПОЛЬЗОВАТЕЛЬ.H VENDOR.LIB USER.LIB предоставленный поставщиком файл заголовка предоставленный поставщиком объектный файл написанный пользователем исходный файл написанный пользователем исходный файл написанный пользователем заголовочный файл поставляемый поставщиком библиотека написанный пользователем библиотека Несоблюдение соглашений об именовании каждого языка может привести к тому, что компоновщик сообщит о неразрешенном внешнем символе. Например, процедура, названная Add в исходном коде FORTRAN, будет отображаться как ADD в объектном файле. Та же процедура в C ++ обозначается как _Add. Поскольку ADD и _Add - это разные имена, возникает ошибка времени соединения. Поэтому программисту, работающему на нескольких языках, часто приходится учитывать соглашение о вызовах участвующих языков и вносить необходимые коррективы. Например, подпрограмма на языке ассемблера, на которую следует ссылаться как ADD в коде C ++, должна быть закодирована как _ADD в исходном коде сборки, поскольку компилятор C ++ автоматически добавляет подчеркивание к имени. Конкретные настройки зависят от конкретных языков и реализаций, с которыми взаимодействуют. ОБЪЯВЛЕНИЕ.2.2 Соглашения о вызовах Соглашения о вызовах относятся к определенному протоколу, используемому языком при реализации вызова функции, процедуры или подпрограммы. В этом смысле мы часто можем говорить о соглашениях о вызовах C, BASIC или Pascal. Языки Microsoft BASIC, FORTRAN и Pascal используют одно и то же соглашение о вызовах . Когда выполняется вызов на любом из этих языков, параметры помещаются в стек в том порядке, в котором они отображаются в исходном коде. С другой стороны, соглашение о вызовах C и C ++ заключается в том, что параметры помещаются в стек в порядке, обратном их появлению в исходном коде. Результатом является то, что в БАЗОВОМ операторе ВЫЗОВА подпрограммы с именем Add(A, B) параметр A помещается в стек первым, а B - вторым. Однако в функции C Add(A, B) верно обратное. Соглашение о вызовах языка определяет, как и когда будет очищен стек . В соглашениях о вызовах BASIC, FORTRAN и Pascal стек восстанавливается вызываемой процедурой, которая отвечает за удаление всех параметров перед возвратом вызывающей программе. Соглашение о вызовах C, с другой стороны, заключается в том, что вызывающая процедура выполняет очистку стека после возврата вызова. Преимущество соглашений о вызовах BASIC, FORTRAN и Pascal заключается в том, что сгенерированный код более эффективен. Соглашение о вызовах C и C ++ имеет то
преимущество, что параметры показывают одинаковое относительное положение в стеке. Поскольку в соглашении C первый параметр является последним введенным, он всегда появляется в верхней части стека, второй параметр появляется сразу после первого и так далее. Это позволяет создавать вызовы с переменным числом параметров, поскольку регистр указателя фрейма всегда содержит один и тот же относительный адрес независимо от количества параметров, хранящихся в стеке. Некоторые языки программирования позволяют изменять соглашение о вызовах по умолчанию на соглашение о вызовах другого языка. Например, большинство компиляторов C и C ++ позволяют использовать соглашение о вызовах Pascal всякий раз, когда этого пожелает программист. В этом случае компилятор C имитирует поведение Pascal, помещая параметры в стек в том же порядке, в котором они объявлены, и выполняя очистку стека перед возвратом вызова. В C и C ++ ключевое слово calling convention может быть включено в объявление функции. 866 Приложение D ОБЪЯВЛЕНИЕ.2.3 Соглашения о передаче параметров В C и C++ параметры могут передаваться функции по ссылке или по значению. Когда параметр передается по ссылке, используйте адрес скалярной переменной или переменной указателя, способной. При передаче по значению копия значения переменной сохраняется в стеке, так что она может быть удалена вызываемой функцией. При передаче по ссылке в стек помещается adформа переменной. В архитектуре сегментированной памяти систем, основанных на процессорах Intel x86 (таких как ПК), у нас есть два возможных представления адреса памяти параметра. Если параметр расположен в том же сегменте, то его адрес представляет собой смещение слова от начала сегмента. Однако, если он расположен в другом сегменте , то адрес должен содержать два элемента: один идентифицирует сегмент в адресном пространстве компьютера, а другой - смещение внутри сегмента. Когда параметр находится в том же сегменте, мы иногда говорим о ближней ссылке. Когда параметр находится в другом сегменте, мы говорим, что это дальняя ссылка. Третий вариант - это когда архитектура основана на плоском пространстве памяти, и в этом случае адрес является линейным ear, 32-разрядным значением, которое определяет местоположение элемента в пределах области памяти объемом 4 гигабайта. По этим причинам вызывающая программа и вызываемые процедуры должны согласовать, передаются ли параметры по ссылке или по значению, а также по модели памяти. Если параметр передается по значению, вызываемая процедура удаляет параметр непосредственно из стека. Если он передается по ссылке, то вызываемая подпрограмма получает объявление параметра из стека и использует этот адрес для разыменования его значения. Кроме того, при выполнении вызова по ссылке в среде с архитектурой сегментированной памяти, такой как ПК под управлением DOS, вызываемая процедура должна знать, является ли адрес, сохраненный в стеке, ближней или дальней ссылкой, и действовать соответствующим образом. Языки программирования иногда позволяют выбрать условие передачи параметров. В C и C++ модель памяти, выбранная во время компиляции, определяет тип адреса по умолчанию для функций и указателей. Язык также допускает использование ключевых слов near и far в качестве модификаторов типа адреса, чтобы заставить указатели и адреса иметь тип, отличный от используемого по умолчанию. Объявление.2.4 Трудности и осложнения Одна из трудностей программирования на смешанных языках заключается в том, что интерфейс между языковыми компонентами меняется в разных языковых комбинациях и в продуктах разных поставщиков программного обеспечения. Более того, разные реализации или версии одних и тех же языковых комбинаций могут взаимодействовать по-разному. Так обстоит дело с языками, разработанными Borland и Microsoft. Стремясь облегчить программирование на разных языках, Borland и Microsoft опубликовали директивы и соглашения, которые пытаются упростить требования к взаимодействию . Основная трудность заключается в том, что эти директивы и соглашения не идентичны. Поэтому многоязычный код часто не переносим между средами разработки разных поставщиков. Другой трудностью, связанной с программированием на смешанных языках, является недостаточная техническая информация. Хотя руководства по программному обеспечению иногда включают несколько параграфов о
Программах с несколькими файлами 867 как взаимодействовать с программными элементами на разных языках, обсуждение часто не содержит всех необходимых данных. Например, рассмотрим довольно простую задачу передачи массива с Borland C ++ на язык ассемблера x86. В документации Borland указано, что в C ++ переменные массива передаются по ссылке. Чего в руководстве не сказано, так это того, что если массив объявлен общедоступным в коде C ++, то ему отводится место в области данных программы. В противном случае массив распределяется в стеке динамически. В обоих случаях вызывающая процедура передает массив по ссылке, как указано в руководстве, но в одном случае вызываемый код находит сегментную базу адреса в сегментном регистре SS, а в другом случае - в DS. Из этих наблюдений мы должны сделать вывод, что многоязычное программирование, хотя и является мощным и универсальным методом, обычно ставит под угрозу переносимость кода даже на другие версии языков или среды разработки. Решение использовать несколько языков в процессе разработки всегда должно учитывать эту возможную потерю переносимости. ОБЪЯВЛЕНИЕ.3 Смешивание языков низкого и высокого уровней Хотя возможны многие сочетания языков программирования, безусловно, наиболее вероятным и полезным является сочетание языков низкого и высокого уровней. Для программистов на C ++, работающих в среде ПК, это означает смешивание языка ассемблера x86 с кодом на C ++. Возражения против программирования на языке ассемблера обычно связаны с трудностями в изучении языка и с более низкой производительностью работы программиста. Оба пункта спорны, но даже если бы мы согласились с их обоснованностью, все еще существует множество других веских причин для использования языка ассемблера в программировании для ПК. Следующий список охватывает некоторые из наиболее важных из них: 1. Питание. Язык ассемблера обеспечивает доступ ко всем функциям машины. Язык itself не накладывает никаких ограничений на функциональные возможности, которые могут быть достигнуты. Другими словами, каждая операция, которую способна выполнить машина, может быть выполнена на языке подобия. Справедливо утверждать, что если это не может быть выполнено в коде на языке ассемблера , то это вообще невозможно сделать. 2. Компактность. Программы, закодированные на языке ассемблера, более компактны, чем те, которые , закодированные на любом языке высокого уровня, поскольку код настроен так, чтобы содержать только те инструкции, которые необходимы для выполнения поставленной задачи. Компиляторы, с другой стороны, должны генерировать значительные объемы дополнительного кода, чтобы использовать многочисленные альтернативы и опции, предлагаемые языком. 3. Скорость. Программы, закодированные на языке ассемблера, могут быть оптимизированы для выполнения с максимально возможной скоростью, допускаемой процессором и аппаратным обеспечением. И здесь код, сгенерированный языками высокого уровня, должен быть более сложным, что определяет более низкую скорость его выполнения. 4. Контроль. Чтобы обнаружить дефекты программы или узкие места в обработке, код на языке ассемблера можно отслеживать или выполнять одношагово по одной машинной инструкции за раз. Тот факт, что между программой и аппаратным оборудованием нет программного обеспечения, определяет, что все дефекты могут быть найдены и исправлены. Единственные исключения 868 Приложение D являются ли эти ошибки вызванными дефектами аппаратного обеспечения или микрокода. Дефекты в программах на языках высокого уровня иногда вызываются компилятором, генерирующим неправильный прямой код. Эти ошибки очень трудно обнаружить и невозможно исправить. Эти преимущества кода на языке ассемблера суммируются в четырех словах: мощность, скорость, компактность и контроль. Всякий раз, когда один из этих четырех элементов имеет первостепенное значение, следует исследовать возможное использование языка ассемблера. AD.3.1 Интерфейс C++ для ассемблера Из всех языков высокого уровня C и C ++ обеспечивают самый простой интерфейс с подпрограммами управления языком сборки
. Причина в том, что C (и его потомок C ++) вызывают процедуру управления языком сборки таким же образом, как они вызывали бы функцию C или C ++. C++ передает переменные, параметры и адреса маршрутизатору языка ассемблера в стеке. Как упоминалось ранее, эти значения отображаются в порядке, обратном тому, как они перечислены в коде C ++. Уникальной особенностью интерфейса языка C ++ является то, что он автоматически восстанавливает целостность стека. По этой причине процедура на языке ассемблера может возвращаться с простой инструкцией RET или RETF, без учета параметров, которые были помещены в стек вызовом. Одним из следствий этого режима работы является то, что подпрограммы на языке ассемблера могут быть спроектированы так, чтобы работать в зависимости от количества параметров, передаваемых кодом C ++, при условии, что может быть определено смещение стека тех параметров, которые необходимы для подпрограммы. Не все реализации и версии C ++ следуют идентичным конвенциям интерфейса. В версиях Microsoft C и C ++ модель памяти, адаптированная программой C , определяет структуру сегментов исходного файла на языке ассемблера. В объявлении.1 на следующей странице показана структура сегментов различных моделей памяти, используемых компиляторами Microsoft C и C ++. Для программиста таблица AD.1 означает, что если исходный файл C ++ был скомпилирован с использованием small или compact model, то процедура языка ассемблера должна быть определена с использованием директивы NEAR. С другой стороны, если исходный код C ++ был скомпонован с использованием моделей medium или large, то процедура на языке ассемблера должна быть определена с помощью директивы FAR. . ОБЪЯВЛЕНИЕ.4 Примеры интерфейсных программ В этом разделе мы приводим два примера сопряжения кода на C ++ и языке ассемблера . Первый основан на использовании компиляторов C или C ++ Microsoft или IBM, а второй - на компиляторах Borland. В первом примере параметр передается по значению подпрограмме, закодированной на языке ассемблера. В этом случае код на языке ассемблера также изменяет переменную, объявленную в исходном файле C ++. Во втором примере мы используем компилятор Borland C или C ++ для передачи адреса структуры в код на языке ассемблера . Ассемблерный код изменяет структурные переменные в исходном коде C ++. В любом случае мы намерены предоставить читателю общий пример, иллюстрирующий интерфейс, но ни в коем случае не следует считать эти примеры универсальными . При сопряжении C ++ и языка ассемблера каждая реализация должна быть тщательно проанализирована на предмет ее требований к интерфейсу. Элементы именования, Программы с несколькими файлами 869 все соглашения о вызове и передаче параметров должны быть приняты во внимание. Даже разные версии одного и того же языка часто отличаются друг от друга. В общем, безопаснее использовать средства разработки языков от одного и того же поставщика, поскольку разумно ожидать, что эти средства были разработаны для взаимодействия друг с другом. В этом смысле при использовании компилятора Microsoft C ++ используйте ассемблер Microsoft, такой как MASM. То же самое относится к продуктам Borland или любого другого поставщика. Таблица AD.1 Структуры сегментов в моделях памяти Microsoft C++ ПАМЯТЬ СЕГМЕНТИРОВАТЬ ВЫРАВНИВАТЬ ОБЪЕДИНЯТЬ КЛАСС Группа Модель Тип Имя Тип Тип Имя Маленький код _ ТЕКСТ слово ОБЩЕДОСТУПНЫЙ "КОД" ДАННЫЕ
_DATA слово ОБЩЕДОСТУПНЫЙ ‘ДАННЫЕ’ DGROUP CONST слово ПУБЛИЧНО ‘CONST’ DGROUP _BSS слово ОБЩЕДОСТУПНЫЙ "BSS" DGROUP СТЕК СТОПКА para СТЕК "СТЕК" DGROUP Компактная код _ ТЕКСТ слово ОБЩЕДОСТУПНЫЙ "КОД" ДАННЫЕ _DATA слово ОБЩЕДОСТУПНЫЙ ‘ДАННЫЕ’ DGROUP CONST слово ПУБЛИЧНО ‘CONST’ DGROUP _BSS слово ОБЩЕДОСТУПНЫЙ "BSS" DGROUP FAR_DATA пункт частный ‘FAR_DATA’ FAR_BSS пункт частный ‘FAR_BSS’ СКЛАДЫВАТЬ СКЛАДЫВАТЬ para СТОПКА "СТЕК" DGROUP Средний код xx_TEXT word ОБЩЕДОСТУПНЫЙ
‘КОД’ ДАННЫЕ _DATA слово ОБЩЕДОСТУПНЫЙ ‘ДАННЫЕ’ DGROUP CONST слово ПУБЛИЧНО ‘CONST’ DGROUP _BSS слово ОБЩЕДОСТУПНЫЙ "BSS" DGROUP СТЕК СТОПКА para СТЕК "СТЕК" DGROUP Большой код xx_TEXT слово ОБЩЕДОСТУПНЫЙ "КОД" ДАННЫЕ _DATA слово ОБЩЕДОСТУПНЫЙ ‘ДАННЫЕ’ DGROUP CONST слово ПУБЛИЧНО ‘CONST’ DGROUP _BSS слово ОБЩЕДОСТУПНЫЙ "BSS" DGROUP FAR_DATA para частное ‘FAR_DATA’ FAR_BSS пункт частный ‘FAR_BSS’ СКЛАДЫВАТЬ СКЛАДЫВАТЬ para СТОПКА "СТЕК" DGROUP Версии C и C ++ Microsoft и Borland имеют дополнительное ограничение интерфейса: машинные регистры SI и DI используются для хранения значений переменных, определенных с помощью ключевого слова “register”. Следовательно, процедура определения языка сборки должна всегда сохранять эти регистры. Реализации Microsoft также предполагают, что флаг направления процессора (DF)
становится ясным, когда C восстанавливает управление..........."..........." Следовательно, если существует какая-либо возможность изменения этого флага во время обработки, код на языке ассемблера должен содержать инструкцию CLD перед возвратом. Хотя в руководствах Borland это требование не задокументировано , вероятно, в любом случае будет хорошей идеей снять флажок направления, даже если в этом может не быть строгой необходимости. Даже если компилятор не использует флаг direction, как, по-видимому, в случае с компиляторами из Borland, инструкция CLD не производит никаких нежелательных эффектов. 870 Приложение D Компиляторы C и C ++ автоматически добавляют символ подчеркивания перед каждым общедоступным именем. Это справедливо как для компиляторов Microsoft, так и для компиляторов Borland C и C ++ . По этой причине ссылка языка C ++ на функцию с именем RSHIFT составляется так, что она ссылается на функцию с именем _RSHIFT. При кодировании программного элемента на языке ассемблера необходимо принять это во внимание и добавить требуемый символ underscore перед именем. Это соглашение применимо не только к функциям, но и к элементам данных, которые глобально видны в коде C ++. Например, если исходный код C ++ содержит переменную с именем ODDNUM, эта переменная видна исходному коду на языке ассемблера под именем _ODDNUM. Код на языке ассемблера может получить доступ к переменной с помощью инструкции EXTRN. Обратите внимание на написание в этом случае, которое отличается от ключевого слова extern в C и C ++. Объявление.4.1 Интерфейс Microsoft C-to-Assembly В первом примере интерфейса используется код, сгенерированный компилятором Microsoft C или C ++ и ассемблером MASM. Пример состоит из программы на C ++, которая вызывает подпрограмму на языке ассемблера . Вызывающий объект передает целочисленную переменную в код на языке ассемблера, и язык ассемблера возвращает значение со всеми битами, сдвинутыми вправо на одну позицию. Кроме того, подпрограмма языка ассемблера изменяет значение переменной C ++ с именем NUMB2, которая определена глобально. Пример реализован в двух исходных файлах. Файл C++ (TESTC.CPP) был создан с использованием стандартного редактора и скомпилирован с использованием компилятора Microsoft C++. Исходный код на языке ассемблера был создан с помощью текстового редактора ASCII и собран с помощью ассемблера DOS MASM. Собранный файл называется CCALLS.OBJ. Обратите внимание, что приведенные примеры работают со всеми полнофункциональными компиляторами Microsoft C и C ++, но не со средой программирования Microsoft QuickC. Ниже приведен список исходного файла C ++ в этом примере интерфейса. /* Имя файла: TESTC.CPP */ /* Исходный код C ++ для демонстрации взаимодействия с языком ассемблера код. В этом примере предполагается использование компиляторов Microsoft C или C ++ , за исключением QuickC */ #включить <iostream.h> extern int RSHIFT(int); /* Переменные, видимые в исходном коде сборки, должны быть объявлены глобально в исходном коде C ++ */ int numb1, NUMB2; main() { cout << "Введите переменную numb1: "; cin >> numb1; cout << "Сдвинутое число равно: %d\n" << RSHIFT(число 1)); cout << "Переменная NUMB2 имеет значение: %u\n" << ЧИСЛО 2; } Программы с несколькими файлами 871 Обратите внимание на следующие моменты в исходном файле C ++: 1. Исходный код на языке ассемблера объявляется внешним по отношению к коду C с помощью ключевого слова extern . Имени в этом объявлении не предшествует символ подчеркивания , поскольку оно автоматически добавляется компилятором. 2. После объявления external подпрограмма RSHIFT вызывается так, как если бы это была функция C ++.
3. Переменные numb1 и NUMB2 объявлены как имеющие тип int. Обе являются глобальными в область применения. Значение numb1 передается кодом C ++ в исходный файл языка ассемблера с помощью инструкции RSHIFT(numb1). Код на ассемблере восстанавливает значение переменной numb1 из стека. Во время выполнения код языка сборки напрямую обращается к переменной NUMB2, используя ее имя. Обратите внимание, что переменная, к которой можно получить доступ по имени, объявляется заглавными буквами. Это для совместимости с программами на ассемблере, которые автоматически преобразуют все имена в увеличенный процент. Код на языке ассемблера выглядит следующим образом: ;**************************************************************** ; CCALLS.ASM ;**************************************************************** ; Пример интерфейса C ++ на языке ассемблера для Microsoft ; Компиляторы C и C ++, исключая QuickC ; ОБЩЕДОСТУПНЫЙ _RSHIFT ДОПОЛНИТЕЛЬНЫЙ НОМЕР _NUMB2: СЛОВО _ТЕКСТ СЕГМЕНТНОЕ слово ОБЩЕДОСТУПНЫЙ ‘КОД’ ПРЕДПОЛАГАТЬ CS:_TEXT ;********************************| ; Процедура интерфейса языка C | ;********************************| _RSHIFT ПРОДОЛЖЕНИЕ NEAR ; Функция для сдвига вправо битов в передаваемом целом числе ; сдвинутое вправо значение возвращается вызывающему толкать BP ; Сохранить BP вызывающего абонента MOV BP, SP ; Установить указатель на фрейм стека MOV AX,[BP+4] ; Значение, передаваемое по значению ; который является режимом языка C по умолчанию ВЫЗОВ INTERNAL_P ; Процедура для выполнения сдвига и ; для доступа к переменной C NUMB2 CLD ; Очистить флажок направления ВСПЛЫВАЮЩЕЕ сообщение BP ; Восстановить BP вызывающего абонента Возврат ; Простой возврат. C восстанавливает стек _RSHIFT ENDP ; INTERNAL_P ПРОЦЕСС NEAR ; значение произвольного нового значения, присвоенного переменной C SHR AX,1 ; Сдвиг вправо AX на 1 бит ; возвращаемое значение оставлено в AX MOV CX,12345 ; Произвольное значение в CX
MOV _NUMB2,CX ; и к переменной C NUMB2 RET INTERNAL_P ENDP _TEXT ЗАКАНЧИВАЕТСЯ КОНЕЦ 872 Приложение D Обратите внимание на следующие пункты исходного файла на языке ассемблера: 1. Процедура _RSHIFT объявлена ОБЩЕДОСТУПНОЙ, чтобы сделать ее видимой во время подключения. Символ подчеркивания символ добавляется в начале имени процедуры, чтобы оно соответствовало имени, используемому компилятором C ++. 2. Структура сегментов совместима с моделью малой памяти Microsoft, как показано на Таблица AD.1. Модель памяти программы на языке Си определяется во время компоновки выбранной библиотекой. Также обратите внимание, что сегмент кода определяется с помощью инструкции: _ТЕКСТ СЕГМЕНТНОЕ слово ОБЩЕДОСТУПНЫЙ "КОД" которые совпадают с теми, которые перечислены для моделей C и C ++ small и compact в таблице ОБЪЯВЛЕНИЕ.1. 3. Процедуры, которые должны быть связаны с программами на C ++ модели small или compact, должны быть объявлены с помощью оператора NEAR следующим образом: _RSHIFT ПРОЦЕСС РЯДОМ Если бы программа C ++ использовала модели medium, large или huge, то программа на языке ассемблера процедура была бы объявлена с использованием оператора FAR. 4. Процедура _RSHIFT в коде языка ассемблера может служить общим интерфейсом шаблон для программирования на Microsoft C++/assembly. Однако обратите внимание, что этот шаблон требует изменений, чтобы адаптировать его к программам на C ++, которые используют другие модели памяти или которые передают или возвращают параметры других типов данных. 5. Структура фрейма стека во время вызова также зависит от принятой модели памяти. В в этом случае, поскольку выбранная модель невелика, передаваемый параметр находится в BP +4, а обратный адрес состоит только из компонента смещения. 6. Переменная, объявленная как NUMB2 и инициализированная как int в исходном коде C, объявляется как EXTRN _NUMB2: СЛОВО в коде языка ассемблера. Символ подчеркивания добавлен в исходный файл сборки, потому что C ++ добавляет этот символ в начало имени переменной. Переменной присваивается произвольное значение в процедуре INTERNAL_P. 7. После завершения работы программа на языке ассемблера очищает флаг направления (CLD), восстанавливает BP регистр (POP BP) и возвращается с простой инструкцией RET. Обратите внимание, что процедура обработки в этом примере не использует регистры SI или DI; следовательно, они не были сохранены. Однако всегда, если при обработке используются SI или DI, входное значение должно быть сохранено и восстановлено с помощью кода на ассемблере. Фактическая конфигурация фрейма стека во время вызова варьируется в зависимости от различных моделей памяти . Одним из определяющих факторов является то, что для адреса возврата требуется два байта в небольших и компактных моделях и четыре байта в других. Кроме того, каждый параметр , передаваемый по значению, занимает место в памяти, эквивалентное его типу. Следовательно, переменная char может занимать один байт, значение int занимает два байта и так далее. Когда параметры передаются по ссылке, объем пространства, занимаемого каждым параметром, зависит исключительно от того, относится ли он к БЛИЖНЕМУ или ДАЛЬНЕМУ типу. В среде ПК для ДАЛЬНИХ адресов требуется четыре байта, а для БЛИЖНЕГО адреса - два байта. На рисунке AD.2 показан фрейм стека, в котором были переданы два параметра типа int с использованием моделей small или compact memory (РЯДОМ с эталонными). Программы с несколькими файлами 873
Рис.2 Пример фрейма стека C++ Возвращаемые значения в компиляторах Microsoft C ++ В реализациях Microsoft C++ для ПК параметры возвращаются вызывающей процедуре в регистре AX или в регистрах AX и DX. В таблице AD.2 приведены языковые соглашения Microsoft для возвращаемых значений. Таблица AD.2 Microsoft C ++ возвращал значения ТИП ДАННЫХ ИЛИ РЕГИСТР ИЛИ ПАРА РЕГИСТРОВ УКАЗАТЕЛЬ Символ AX short int беззнаковый символ без знака short без знака int Длинный DX = слово высокого порядка длинный без знака AX = слово младшего порядка структура или объединение AX = значение адреса, плавающее или двойное рядом с указателем AX = значение смещения адреса дальний указатель DX = значение сегмента адреса AX = значение смещения адреса 874 Приложение D высокие адреса низкие адреса Аргумент 2 (тип int) Аргумент 1 (тип int) БЛИЖАЙШИЙ возврат Адрес Сохраненный BP Аргумент 2 ДАВЛЕНИЕ + 6 ДАВЛЕНИЕ + 4 ДАВЛЕНИЕ + 2 ВР Другим способом обмена значениями между кодом на C ++ и языке ассемблера является доступ к переменным C ++ по имени (как было продемонстрировано в предыдущем примере ). Этот метод требует, чтобы ассемблерный код использовал тот же формат хранения, что и исходный код C ++. Поскольку форматы хранения переменных меняются в разных реализациях C ++, этот метод доступа должен быть адаптирован к каждой конкретной реализации языка. ОБЪЯВЛЕНИЕ.4.2 Borland C++ для интерфейса Assembly
Следующий пример, закодированный на Borland C++ и на языке ассемблера x86, также внедряет интерфейс C++ для языка ассемблера. Код C ++ может быть скомпилирован с использованием Borland C ++ версии 3.1 и более поздних версий. Код на ассемблере создается с использованием TASM от Borland или Microsoft MASM. Пример состоит из передачи структуры в исходный код на языке ассемблера . Вызов выполняется с использованием типа удаленного доступа, а адрес структуры является удаленной ссылкой. /* Название программы: TEST1.CPP Другие файлы: Проект: ASMTEST.PRJ (в C:\BORLANDC) Исходный файл сборки: CCALLS.OBJ (в C:\BORLANDC) */ #включить <iostream.h> #включить <string.h> внешний "C" { int SETSTR(struct pix_params *t); /* Передача структуры мимо | | | ссылка | | |_____ имя тега структуры | |____________________ Имя процедуры (_SETSTR в ассемблере | исходный файл) |_________________________ Возвращаемый тип Процедура SETSTR устанавливает новые значения в структурной переменной pix_params , переданной ей по ссылке */ } // СТРУКТУРА { struct pix_params указывает на 1; // Объявить одну структурную переменную point1.x_coord = 0x20; // Инициализировать структурные переменные point1.y_coord = 0x40; point1.blue = 0x30; point1.green = 0x31; point1.red = 0x32; strcpy ( point1.text_string, "Это тестовое сообщение"); SETSTR[&point1]; return (0); } struct pix_params { int x_coord; // координата точки по оси x Программы с несколькими файлами 875 int y_coord; // координата точки по оси y символ без знака синего цвета; // цвет компонентов беззнаковый символ зеленый; беззнаковый символ красный; текстовая строка символа[80]; }; main() . . . В отношении программы на C ++ следует соблюдать следующие моменты: 1. Ссылки на источники определяются файлами, на которые есть ссылки в проекте. 2. Имена процедур сборки, которые будут вызываться из C ++, определяются с помощью внешней директивы "C". 3. Процедуры сборки должны учитывать модель памяти при доступе к памятипараметры памяти в стеке. Если доступ к процедуре осуществляется с помощью вызова NEAR, то фрейм стека содержит 2-байтовый обратный адрес, а первый параметр расположен в BP + 4. Если к
процедуре обращаются с помощью FAR-вызова, то фрейм стека содержит 4-байтовое объявление о возврате , а первый параметр находится в BP + 6. В компиляторах Borland модель C++ определяется настройкой пункта меню Options/Compiler (Параметры/Компилятор). В этом случае модель памяти БОЛЬШАЯ, а вызов УДАЛЕН. 4. Код языка C добавляет к имени функции начальный символ подчеркивания (_) во время вызова. Код на ассемблере должен учитывать это, вставляя соответствующий символ _ перед общедоступным именем. Ниже приведен исходный код языка ассемблера, который принимает вызов C ++: ;******************************************************************* ; CCALLS.ASM ;******************************************************************* ; Пример подпрограммы интерфейса C++ на языке ассемблера для Borland ; компиляторы ; ; ОБЩЕДОСТУПНЫЙ _SETSTR ;******************************************************************* ; локальный код ;******************************************************************* _TEXT СЕГМЕНТНОЕ слово ОБЩЕДОСТУПНЫЙ "КОД" ПРЕДПОЛАГАТЬ CS:_TEXT ;********************************| ; Процедура интерфейса языка C | ;********************************| _SETSTR ПРОДОЛЖЕНИЕ FAR ; Функция для доступа к структуре, расположенной в коде C ++, и изменения ; значения некоторых ее переменных ; При вводе: ; адрес структуры, передаваемый по ссылке толкать BP ; Сохранить BP вызывающего абонента MOV BP, SP ; Установить указатель на фрейм стека 876 Приложение D ; Оператор регистра C ++ использует SI и DI для временного хранения ; поэтому обычно хорошей идеей является сохранение этих регистров толкать SI ; Сохранен в стеке толкать DI ; Модель FAR определяет местоположение первого параметра размера слова на +6 MOV BX,[BP+6] ; Смещение адреса MOV ES,[BP+8] ; Сегмент адреса ; На этом этапе ES: BX содержит адрес структуры в исходном коде C ++ ; Следующий код обращается к переменным в структуре C ++ ; и изменяет их MOV PTR-Ы СЛОВ: [BX],80 ; изменить x_coord MOV ВАРИАНТЫ СЛОВ PTR: [BX + 2],90 ; изменить y_coord MOV
БАЙТЫ PTR ES: [BX + 4], 0A0H ; изменить красный MOV БАЙТ PTR ES: [BX + 5], 0B0H ; изменить синий цвет MOV БАЙТЫ PTR ES: [BX + 6],0C0H ; изменить зеленый ; C код выхода ВСПЛЫВАЮЩЕЕ DI ; Восстановить DI и SI из стека ВСПЛЫВАЮЩЕЕ SI CLD ; Флажок очистить направление ВСПЛЫВАЮЩЕЕ сообщение BP ; Восстановить BP вызывающего абонента MOV AX,0 ; Вернуть 0 Повторно ; Простой возврат. C восстанавливает стек _SETSTR ENDP _TEXT ЗАКАНЧИВАЕТСЯ END ОБЪЯВЛЕНИЕ.4.3 С использованием заголовков интерфейса Обратите внимание в приведенных выше примерах, что код на языке ассемблера разработан с учетом современных соглашений о вызовах C ++. Следовательно, процедура _SETSTR не работает с другим языком высокого уровня. Кроме того, модель памяти C ++ определяет, где в стеке размещаются переданные параметры. Если вызывающая процедура скомпилирована с другой моделью памяти, интерфейс завершается с ошибкой. Одним из способов учесть различия в соглашениях о вызовах между языками или между реализациями одного и того же языка является использование заголовков интерфейса. Обычно это короткие подпрограммы, которые получают параметры, переданные одним языком , и размещают их в машинных регистрах или структурах данных, ожидаемых другим языком. Эта схема обеспечивает переносимость при сохранении максимальной эффективности кодирования на обоих языках. Она также позволяет повторно использовать код, который не предназначен для взаимодействия с конкретным языком. Например, библиотека графических примитивов на языке ассемблера может быть использована из C ++ с помощью заголовков интерфейса, даже хотя исходный код ассемблера изначально не был разработан для вызова с языка высокого уровня. Единственным недостатком использования подпрограмм заголовка интерфейса является некоторая потеря производительности, поскольку каждый вызов сопряжен с дополнительной нагрузкой на прохождение кода заголовка. В программировании на C ++ процедуры заголовка интерфейса могут быть закодированы в строке и помещены в включаемые файлы, которые выбираются с помощью условных директив препроцессора. Этот простой прием позволяет выбрать другую процедуру заголовка в соответствии с моделью памяти программы на C ++, решая таким образом основное ограничение интерфейса языка guage. Программы с несколькими файлами 877 Приложение E Библиотека MATH32
Краткие сведения Это приложение содержит список всех низкоуровневых процедур в библиотеке MATH32, предоставленной в онлайновом программном пакете книги. Таблица AE.1 Процедуры в MATH32.LIB ИМЯ ПРОЦЕДУРЫ ОПЕРАЦИЯ МОДУЛЬ UN32_1 _BIN_TO_ASC10 16-разрядный двоичный файл в 5-значный десятичный код ASCII _BIN_TO_ASC16 16-разрядный двоичный файл в 4-значный шестнадцатеричный код ASCII _ASCII_TO_DX преобразование 4-значного ASCII из десятичного в двоичный формат в формате DX _BINF_TO_DECF из 8-разрядной двоичной системы в 8-значную десятичную дробь _DECODE_SINGLE Изолировать элементы одинарной точности _BIN_TO_BCD 16-разрядный двоичный файл с 5 разрядами BCD _BCD_TO_ASCII 5 распакованных BCD до 5 цифр ASCII _BCD_INTO_REG преобразование 5-значного BCD в двоичный файл в DX _ASCII_TO_EDX Преобразования ASCII в двоичный код в регистре EDX BCD12: _BCD12_TO_ASCSTR Номер BCD12 в строке ASCII _ASCII_TO_BCD12 Десятичная строка ASCII в формате BCD12 Преобразование в BCD20: _BCD20_TO_ASCSTR Номер BCD20 в строке ASCII _ASCII_TO_BCD20 Десятичная строка ASCII в формате BCD20 МОДУЛЬ UN32_2 _SIGN_DIV_BCD12 Арифметика BCD12 со знаком _SIGN_MUL_BCD12 _SIGN_ADD_BCD12 _SIGN_SUB_BCD12 МОДУЛЬ UN32_3 _SIGN_DIV_BCD20 Арифметика BCD20 со знаком _SIGN_MUL_BCD20 (продолжает) 879 Таблица AE.1 Процедуры в MATH32.LIB (продолжение) НАЗВАНИЕ ПРОЦЕДУРЫ ОПЕРАЦИЯ _SIGN_ADD_BCD20 _SIGN_SUB_BCD20 МОДУЛЬ UN32_4 _ASCII_TO_EXP Десятичная строка ASCII в экспоненциальной форме _FPU_OUTPUT ST(0) регистрируется в десятичную строку ASCII
_FPU_INPUT Экспоненциальная строка ASCII в ST(0) _GET_TAG Получить код тега регистрации математических единиц МОДУЛЬ UN32_5 _TEN_TO_Y_BYFAC 10**y путем факторинга экспоненты _X_TO_Y_BYFAC x ** y путем факторинга экспоненты _X_TO_Y_BYBP x**y с помощью двоичного питания _X_TO_Y_BYLOG x **y с помощью логарифмов _X_TO_Y_MIXED x **y с помощью логов и двоичного питания _LOG_10 логарифм по основанию 10 _LOG_E логарифм по основанию e _LOG_10 антилогарифм основания 10 _ALOG_E антилогарифм основания e _ALOG_2 антилогарифм основания 2 МОДУЛЬ UN32_6 _DEG_TO_RAD преобразование градусов в радианы _RAD_TO_DEG преобразование радианов в градусы _CIRCLE_SCALE масштабируйте угол до единичной окружности _OCTANT_SCALE угол масштабирования до первого октанта _ТАНГЕНС функция касания _SINE sine _КОСИНУС косинус _COTANGENT кокасательный _СЕКУЩИЙ секущий _ КОСЕКАНТ косекант _ARC_SINE функция arcsine _АРК_КОСИН арккосин _ АРК_ТАНГЕНС арктангенс _SINH гиперболический синус _КОШ гиперболический косинус _TANH гиперболическая касательная _ARC_SINH гиперболическая арксина
_ARC_COSH гиперболический аркозин _ARC_TANH гиперболический арктангенс МОДУЛЬ UN32_7 _ФАКТОРИАЛЬНЫЙ x! _ УПОРЯДОЧИТЬ упорядочить x > y _MAX найдите большее или два




913 сопроцессор данных 96 кодирование данных 107-108 в матричной форме 345 в памяти 31,33,35,37,39,41,43,45, 47,49,51,53,123-124 исполнительный блок 137 расширение процессора 96 блок 102 анализа 311 Численные приближения производной 330ошибок дифференцирования 327 ошибок 272методов интегрирования 332 272,278,311,370 синтаксического анализа 375 O Объектные файлы 443,821-823,865-866 Восьмеричная кодировка 11 однобайтовых токенов 383 режима операндов 162 172 Операционная система /2 432 операции на элементах матрицы 349 на прямоугольниках 687 Операторы 56-57,92,126,376-382,386387,424,617,704,760,782,849,852853,865 ИЛИ и НЕ нейроны 394 Упорядочены сравните 102 171 173 упорядочения числовых данных 221 Обычный аннуитет 263 упорядочения с 1 бит 59 OS / 2 432 warp версия 4.0 432 Точность вывода 608 Переполнение 36-38,56-57,62,70-71,106,111112,114-115,129,131,142-144,194 , 516,845 флаг исключения 142 57,70-71 Упаковано BCD 49-51,79,81,91,117-120,125-127, 131,160-161,168,825,827,881 показатель 81 Формат BCD 49,119,125,127 десятичный 49,72,117 формат 49 Палитра цветов 612 Палмера и Морзе 96,166,174,182,184,202 Джона Ф. 96,106 Соглашения о передаче параметров 867,872, Флаг четности 56,70,149, Синтаксический анализатор 377-379,381-383, частичный касательная к дуге 172,181,199
поворот 370 остаток 102,161,163-166 касательная 172,181,199,201 Pascal 345 соглашение о вызовах 469 868 Пути 12,520,525,599,604,613-614,621, 678-679,683,685,687,759 -связанные функции 679 Рисунок кисть 617,643,701,715-717 передача 716 PCX 693 PDP 11 33,42,423 пера позиция 599,619-624,626,630-633,680, 682,686 тип 614 Pentium математическое устройство 31,47,49-50,103,105 Персептрон 389,399-406,409-410,412 обучение 400 обучение 402 Периодический платеж 252,257-259,26- 270 883 Тождества периодичности 201 Перестановки 282-283 Высота тона и семейство 525 Питтман, Том 106 Питтс, Уолтер 391 Основное сгущение 370 Планетарная система данных 277 Полман, Билл 95 Пойнтер арифметические 63,835,840,844-846 для ввода VOID 712 переменных 835,837-840,845,847 Указатели на функции 835,848,856-857,859 на структуры 843 для АННУЛИРОВАНИЯ 278,835,845-846,848 Полярные и декартовы координаты 244 координаты 244-245,883 система 244 Польские обозначения 376-377 914 Указатель Режим заливки полигонов 640-642,649,665,680, 682,687 Полиморфизм 782-783,788,835, 865-866 Полиномиальная интерполяция 317 Всплывающие меню 573-574,687 Позиционные 5-8, 13-15, 18-20, 25,39-40 характеристика 6-7 вес 7,15,18-20,25,39-40 Степенная функция 183,190,323 ДО и после 458,765 Точности и ошибки вычислений 274 исключение 116,143,163 Предопределенные дочерние Windows 556 классов 551-552,556,558 классов управления 558 элементов управления 455 Упреждающая многозадачность 434
Префиксная форма 377 Приведенная стоимость 164,249-255,257-259, 265-266,271,883 под сложные проценты 254 от аннуитета 265,883 Контекст частного отображения 502-504 Вероятность нормального распределения 301 Инструкции управления процессором 98,100,178 Процессор/сопроцессор интерфейс 99 синхронизация 99 Программа события 434,437 ресурс 440,475,491,571-572,576, 578,693 Стиль программирования 429,447,796 Индикаторы выполнения 455,552,583 Панель рабочей области проекта 571-572,579-580 Проективное замыкание 110-111 Доказательство правильности 176 Таблицы свойств 455,552,583 Шрифт с пропорциональным интервалом 509-510,513 Прототипирование 742,744-745 Машина чистого стека 162 Кнопочное управление 560 Теорема Пифагора 9 Q QNAN 113 квадратичный уравнение 10,215,229-231,240,243,305 формула 229-230,240 Показатели качества 732 Количественная оценка взаимосвязей данных 305 Quiet NAN 112-113,122-123,129,171 R Радианы в градусах 200-201 Переключатели 428,561,566 радиуса 7-8,10,35-36,38-45,80,174,181 дополнение 35-36,38,42-44 представление дополнения 36,42-44 -минус одна форма 36 Рамон-и-Кахаль, Сантьяго 389 Серия 127,129,170,201,294 -операции масштабирования 201 Растровые шрифты 523 графика 689,711 операции 617,695,698-699,701-702, 716,719 код 698-699,701,716,719 операции 695,698,716 Рациональные и иррациональные числа 9,120 числа 9,120,225,273 Равенел, Брюс 96 Действительных чисел и комплексных корней 240 чисел 9,40,43,110,114-115,117, 120-121,125,184,241,243 Прямоугольная структура 518,534,538,550, 611,656 Рекурсивный определение 220 факториальной функции 220
Переопределение символа ошибка 571 Перерисовать ответственность 600 Уменьшение до первого октанта 165,174 до единичного круга 201 Ссылочные переменные 849-850 Область комбинированные режимы 667 манипуляций 669 операций на основе 663 связанных функций 663,672 Регистр операнды 158 стекаются 137-139,142,152-153,158,160 Регрессия 304 307 анализ 277 307 319 Ошибка представления 274 Указатель 915 Представления для нулевого уровня 34 Фаза анализа требований 759 к ресурсу редакторы 440,491-493 файлов 429,440,442,586,588 заголовочный файл 492 идентификатор 572,592,594 Реакция на недостаточный поток 143 Обратных польских обозначений или формат rpn 377 RGB 612,707-708,713 элементов управления расширенным редактированием 552,583 Ричардса, Мартина 423 Риска анализ 744-745,752-753, 755-757,791 оценка 753,755-756 оценка 753,755-756 идентификация 753 управление 753,756-757 Римские цифры 4-5 Корневая инструкция 138 ROP2 617-618,698 Розенблатт, Фрэнк 399,403,406 Круглые до ближайших 109,114,225 до ближайших даже 109,225 до отрицательной бесконечности 110,114 до положительной бесконечности 109,114 Округление и вычислительные ошибки 160 контролируют поле 132,140,168,225 до ближайшего 129 до ближайшего четного 109,129,166 -выключено 109 Ошибки округления 80 Вектор строк 349-350,352-358 Порядок следования строк 345 RPN (см. Также: Обратное польское обозначение) 377 S Масштабируемые режимы отображения 504,507,605 Коэффициент масштабирования 163 Сканирование код 529 строка 690 Сканер 377-378,381 модуль 381
Диаграммы рассеяния 304 и корреляции 304 Научная нотация 41,133,136 Экран координаты 506,573,575,699 флаг стирания 603 пикселя 118,620,690-691,707 операция изменения размера 600 Файл сценария 440,492,494,548,570-571,579 Элементы управления полосой прокрутки 557,562-563 Полосы прокрутки 437,454-455,500,504,552,557, 560,562-564 GUI 468,499,540,551,864 Сортировка выбора 280 полупроводников 32 Разделение проблем 735-736 разделителей 25,27-28,535-536,568, 585-587,589-590, 592-593,595 Модель последовательного программирования 434 Ошибка стека 144 Сдвиг и поворот битов 56 влево 64 Короткие реальные 117 сочетания клавиш 568,570,572 Формула бокового угла 228 Знак битовое поле 120 инструкции по расширению 72 флаг 57,70-71 Сигнализация и тихие NANS 113 NAN 112-113,122-123,142,171 Числа со знаком 8,32,34-35,43,57,60-61, 64,70 Поле значения 48,50,91,107,120,126,131,168 Представление знаковой величины 34 Похожие треугольники 314 Симони, Чарльз 450 Простые интерес 249-251,256, 259,883 окно сообщения 578 Правило Симпсона 302,336,338-339 приближение 337 однозначная точность 48,117,122,124-126,130, 132,184,355,825,827,881 проходческий фонд 269-270 уклон секущей 327 -форма перехвата 320,324,409-410 SNaN 113 Программное обеспечение эмуляторы 128 182 качества 728 Решение линейного уравнения 343 линейной системы 368 Решение 916 Указатель и анализ уравнений 375,377,379, 381,383,385,387 треугольники 227 Сома 389-390 Сортировка 280
алгоритмы 280 Исходные файлы 429,438-440,442-444 456,494,586,588,823,864,873 Код спагетти 456,746 Специальные кодировки 121-123,125-126 Технические характеристики фаза 741,759 Спиральная модель 744-745 Квадратный корень 98,107,112,114,138,142, 161-162,170,182,185,210,215,228, 296,349,379,729,731 Верхний регистр стека 130-132,134,138, 146-147,207 Стандартное отклонение 296 набора данных 296 совокупности 296 форма 32,341-342,578,614 нормальная кривая 299 кнопка 587 для суммирования 278 кнопки на панели инструментов 591-592 Стэнфордский университет 405 Startz 184 Статических элементов управления 476 560 565 Статистических данных 277 292 обозначения 282 284 Статус строка 454-455, 500, 504, 551-552,583-584 флаги 56-57,61 регистр 56,179 Стивенсон, Дэвид 106-107 Stock кисти 615-616,644 ручки 613 Хранение числовых данных 32 Растяжение или сжатие растрового изображения 717 Обводка контура 637 Страуструп, Бьярне 423 Структура типа растрового изображения 703 Подсистема сцепления 811 Внезапное переполнение 114 Суммирование операции 279 примитивы 307 Вычисление суммы произведений 395 Таблица символов 381 Синапс 390-391 Синаптивная щель 390 Синтаксические правила 380 Система фаза анализа 749,751,771 аналитик 750,752 фиксированный шрифт 556 шрифт 510,512,556 память 711,713-714,845 линейных уравнений 341,343-344,368-369 очередь 481-482,528 графическая информация 599 уровня сообщения 530 уровня растровое изображение 700 памяти Системы линейных уравнений 343 Вкладка
элементы управления 455 кодов 152 Слово тега 137,151-153,155-156 регистр 152 Система подсчета 3-4 Временных реальных 97,117,131,832-833 Троичных растровых операций 698 Текст коробка 464-465,551 размеры 512-513 дисплей 454,499,505,508,515,517, 519-520,525,678,708 форматирование 489,513 графика 508,520 обработка 512-513 Текстометрическая структура 510-513,521 Теория счета 12 Томпсон, Кен 423 TIFF 693 Маркировка ключевых слов 383 токена 377,383 панели инструментов растровое изображение 588,590-592,595 кнопки 586-588,591-592,594 Панели инструментов 455,492,551-552,576,583-585 , 587,590-591 Всплывающая подсказка 601 Трансцендентная алгоритмы 176-177 функции 102,176,181 инструкции 138,142,161,168,172-174 примитивы 174,215 Трансцендентные 172,174,176 Трапециевидные Указатель 917 аппроксимация интеграла 333 правило 332,334-336,338 Древовидные представления 455 треугольников 314 683 тригонометрических дуговые функции 207 функций 97-98,163-164,166,172-174, 181,199-200,202,206-207,216,349 коды операций 199 трансцендентные 172 Усечь 91,114,142 Закон о правде в законе 261 TTY 431,508 турбо-ассемблер 120,123 Щелчка 507 Двухкнопочная мышь 541 Шрифт 509-510,524-525 семейство 509 типематических действий 528-529 Типов чисел 8,121 U Неклассифицированные дочерние окна 552 Переполняются 71,112,115-116,121,129, 131-132,142-144,272 исключение 115,132,143 Единицы памяти 12 UNIVAC I 31 Операционная система UNIX 423 441 Ненормальность 174
Непредставимое число 273 Умножение без знака 61 Область обновления 601,603,671 V Векторные 350,352 шрифты 523,609 графика 508,689,801 операции 341,350 скалярных операций 350,352 Очень маленькие числа 115 274 Окно просмотра 506-507,794,797 и окно 506-507 Virtual-key 529,531-534,539 code 529,531-534,539 von Neumann, John 6,31 von Neumann computer 31 von Neumann, Burks, and Goldstine 6 W ПОДОЖДИТЕ 99-101,105,147,149,153, 178-179,185,202-203,208,222-223, 225,242,549 Модель водопада Уорда и Меллора 769-770 740-742,745, взвешенная показатели центральной тенденции 292, медиана 293, режим 293 Чип Weitek 96 Целых чисел 8-9,33,779 Widrow Бернард 405 -Хофф изучает 405,414 Win32 51,82,355 Режим намотки 640,642,665 окно класс 471-472,474-476,479,482-483, 502,544,552,556,558 имя класса 474-475,479 контекст отображения 506 функция 482,579 процедура 473,483,495,529,546-547, 549,552-553,555,578-579,586,601 типы 551 Windows клиентская область 452 проводник 474,491 544,553 Графический интерфейс 468,551 процедура 470,472,480-487,503,542, 545,549,556,566,572 Мастера 463,552,583 Структура WNDCLASSEX 469,471,475, 483,491,493,502,546-547,549,551552,555,558 Размер слова 32-33,38,67,879 Возняк, Стив 432 X Вычисление f 299 Xerox PARC 432,540,776 с помощью XORing с 1-разрядным значением 59 Z Делитель нуля 81,86 Нулевой флаг 57,60,69,71,74,149 Система на основе нуля 13 918 Указатель