/
Tags: исследование операций программирование
ISBN: 5-03-003355-6
Text
К. Ш. Тан, В.-Х. Стиб, И. Харди
Символьный C++
введение
в компьютерную алгебру
с использованием
объектно-
ориентированного
программирования
Tan Kiat Shi, Willi-Hans Steeb and Yorick Hardy
Symbolic C++:
An Introduction to
Computer Algebra using
Object-Oriented Programming
2nd extended and revised edition
JS Springer
London • Berlin • Heidelberg • New York •
Barcelona • Hong Kong • Milan • Paris • Singapore • Tokyo
К.Ш.Тан, В.-Х.Стиб, Й.Харди
Символьный C++:
Введение
в компьютерную алгебру
с использованием
объектно-ориентированного
программирования
Перевод со второго
расширенного и пересмотренного
английского издания
В. И. Ахмолина
и С. В. Чудова
под редакцией
Г. М. Кобелькова
Москва «Мир» 2001
УДК 519.88
ББК 32.973
Т18
Тан К.Ш., Стиб В.-Х., Харди И.
Т18 Символьный C++: Введение в компьютерную алгебру с
использованием объектно-ориентированного программирования:
Пер. со 2-го англ. изд. — М.: Мир, 2001. —622с, ил.
ISBN 5-03-003355-6
В книге представлен подход к разработке новой системы
компьютерной алгебры, основанной на объектно-ориентированном программировании.
В первых вводных главах излагаются требования пользователя к таким
системам, описан необходимый математический аппарат, лежащий в
основе разработки системы, дан обзор наиболее популярных из существующих
систем (REDUCE, MAPLE, AXIOM, MATHEMATICA, MuPAD). Далее
обсуждаются основные понятия объектно-ориентированного
программирования, языки Java, Eiffel, Smalltalk, Лисп и Оберон, излагаются средства языка
C++, вводится новая система компьютерной алгебры SymbolicC++.
Приводятся примеры применения системы к задачам математики и физики
(нумерация Гёделя, аппроксимация Паде, техника рядов Ли, метод Пикара,
фрактальное множество Мандельброта и др.). В книгу включены листинги
всех компонентов системы, что позволяет пользователям развивать и
наращивать систему в соответствии со своими потребностями.
Для студентов и аспирантов вузов в качестве учебного пособия, для
специалистов по компьютерной алгебре как справочное пособие, а также для
непрофессионалов, желающих расширить свои познания в возможностях
вычислительной техники.
ВВК 32.973
Федеральная программа книгоиздания России
Редакция литературы по математическим наукам
Translation from the English
language edition: Symbolic C++:
An Introduction to Computer
Algebra using Object-Oriented
Programming by Kiat Shi Tan,
Willi-Hans Steeb, Yorick Hardy
Copyright © Springer-Verlag
London Limited, 1998, 2000
ISBN 5-03-003355-6 (русск.) ^ AU RightS Reserved
TSRN 1 ЯЧ9ЧЧ 9КП ч )»„„„ \ ® перевод на русский язык,
ISBN 1-85233-260-3 (англ.) оформление, .Мир», 2001
От редактора перевода
Подробное содержание предлагаемой читателю книги освещено в
предисловии авторов и хорошо просматривается из оглавления, поэтому мы не будем
останавливаться на нем, а сделаем несколько замечаний об эволюции
языков программирования, обусловленной потребностями вычислительной
математики.
С полной уверенностью можно сказать, что данная книга явилась
результатом долгой, жесткой, порой драматической конкуренции различных
разновидностей языков программирования, применяемых в научных и инженерных
вычислениях. Несколько последних десятилетий одним из любимых вопросов
специалистов по вычислительной математике оставался вопрос об основном
языке программирования, используемом в этой области. В течение долгих
десятилетий несомненным лидером оставался Фортран с его разновидностями
Фортран-4, Фортран-77, Фортран-90 и т.д. Многолетний груз созданного на
этих языках программного обеспечения и разнообразных математических
библиотек сделал почти невозможной замену данного инструмента. С другой
стороны, развитие этого языка в сторону матричных вычислений укрепляло
его позиции в различных областях прикладной математики.
Активное развитие Фортрана таило в себе и подводные камни. В сущности
их было два. Первый — это снижение стандартизированности языка
программирования. На отдельных платформах стали развиваться мощные
разновидности языка Фортран, но написанное на них программное обеспечение не было
легко переносимым на другие платформы. Вторым подводным камнем стала
устаревшая идеология языка Фортран. Попытки ее замены упирались в первую
из указанных проблем.
Казалось, что ситуацию не смогло переломить появление изящного
многоцелевого языка программирования С. Будучи созданным для системных
целей, этот язык стал быстро набирать очки в изначально свойственной для
него области — в области прикладных вычислений. Меньшая приспособленность
для решения математических задач искупалась в этом языке его несравнимо
большей изящностью, что чисто субъективно не могли не оценить
конкретные пользователи. Оказалось, что проблема, связанная с багажом
математических библиотек относительно легко разрешима, так как появились программы-
ретрансляторы программ, написанных на разновидностях языка Фортран, в
программы на языке С.
Коренной перелом произошел с момента появления новой модификации
языка С —языка C++. Чисто формально язык C++ является некоторой
надстройкой над языком С. Фактически же на свет появился новый мощный
объектно-ориентированный язык, совмещающий в себе при грамотном
программировании высокую скорость выполнения кода и всю широту
возможностей объектно-ориентированного языка: возможность создания новых типов,
6
От редактора перевода
определение математических операций над этими типами, инкапсуляцию и
наследование, переопределение уже существующих операций и функций и т.д.
Изначально язык программирования C++ не содержит специальных
средств, ориентированных на решение задач, связанных с компьютерной
алгеброй, но создание специальных библиотек классов с блеском решает эту
проблему. При этом вопрос о конкретной реализации этой библиотеки
уходит на второй план по сравнению с вопросом о том, как ее реализовать. При
грамотном описании всех свойств библиотеки сама ее реализация занимает
относительно немного времени и в данной книге не рассматривается. Основной
упор делается на содержательную сторону построения подобной библиотеки,
т.е. на описание классов библиотеки, функций, объектов этих классов,
операций, доступных над классами.
В книге дается развернутое описание основных понятий компьютерной
алгебры и взаимосвязей между ними. Язык C++ является далеко не
единственным языком, на котором можно решать проблемы компьютерной алгебры, и в
книге приводится краткий обзор использования в этой области других
специализированных языков.
Следует еще раз отметить, что все вводимые в книге понятия
оцениваются именно с точки зрения их применимости к решению задач компьютерной
алгебры, что делает эту книгу интересной для математиков и математиков-
прикладников, имеющих дело с подобными вопросами. Мы надеемся, что эта
книга будет весьма интересной широкому кругу российских читателей.
Г. М. Кобельков
Предисловие
В этой книге мы показываем, как объектно-ориентированное
программирование может использоваться для реализации системы символьной алгебры и как
эта система применяется к различным областям математики и физики.
В самом узком значении компьютерная алгебра используется для обработки
научных и инженерных формул. Обычно математическая формула с помощью
языков программирования (например, С, Фортран и Pascal) может быть
только вычислена и каждой переменной присваивается соответствующее значение.
Однако та же формула может рассматриваться как математический объект
в системе символьной алгебры, который допускает формальные
преобразования, такие, как, например, дифференцирование, интегрирование и разложение
в ряд, на основе численных манипуляций. Следовательно, такой подход крайне
полезен в исследовательских и научных вычислениях.
Объектно-ориентированное программирование открыло новую эру для
программирования в информатике, где оно появилось как возможное средство при
разработке программного обеспечения. В целом, объектно-ориентированное
программирование является важным подходом в анализе задач, разработке
систем и построении решений. С эффективным использованием этой
методологии программные продукты становятся в большей степени избавленными от
ошибок, более легкими в сопровождении, более часто используемыми и
расширяемыми.
Целью этой книги является демонстрация того, как особенности объектно-
ориентированного программирования могут быть применены к разработке
системы компьютерной алгебры. Среди многих объектно-ориентированных
языков программирования, доступных в настоящее время, мы выбрали C++ в
качестве нашего языка программирования. Это наиболее широко используемый
язык объектно-ориентированного программирования, который успешно
применяется многими программистами в различных прикладных областях.
Разработка основана частично на признанных принципах и частично на большом
опыте и обратной связи ее практического использования. Многие опытные
программисты и программистские коллективы в промышленности и
академической среде используют C++. Помимо причин, указанных выше, мы предпочли
C++ другим объектно-ориентированным языкам из-за его эффективности в
скорости выполнения и использовании указателей и шаблонов.
В главе 1 вводится понятие компьютерной алгебры. В ней мы обсуждаем
важнейшие свойства и требования системы компьютерной алгебры, приводим
справочный материал с указанием некоторых опасностей и ограничений.
Наконец, мы представляем систему компьютерной алгебры — SymbolicC++. Эта
новая система имеет много преимуществ перед существующими системами
компьютерной алгебры. Кратко суммируются характеристики и функциональные
возможности системы.
8 Предисловие
В главе 2 представлены общематематические объекты для системы
компьютерной алгебры. Мы описываем, как строятся базовые математические
структуры для формирования более сложных математических структур.
В главе 3 дается краткое описание некоторых систем компьютерной
алгебры, доступных на рынке, например, REDUCE, MAPLE, AXIOM, MATHE-
MATICA и MuPAD. Для каждой системы описываются основные операции.
Для демонстрации характеристик этих систем используются многочисленные
примеры.
В главе 4 вводятся основные понятия объектно-ориентированного
программирования, такие, как объекты, классы, абстрактные типы данных,
передача сообщений, наследование, полиморфизм и т.д. Даются примеры,
чтобы облегчить читателям понимание этих важных понятий. Подробно
обсуждается объектно-ориентированный язык Java, и разработанные в нем два
класса. Наконец, в главе кратко описываются три других языка объектно-
ориентированного программирования —Eiffel, Smalltalk и Оберон.
В главе 5 мы вводим инструментальные средства языка C++. Мы
описываем и создаем тип данных String, который служит в качестве способа
ознакомления со средствами, доступными в C++. Приведены также некоторые
другие примеры. Также введена библиотека стандартных шаблонов вместе с
множеством примеров. Наконец, мы описываем рекурсию. В конце этой главы
приводятся все характерные особенности языка C++.
Глава 6 содержит обзор классов, полезных для компьютерной алгебры. Мы
исследуем сверхдлинные числа, рациональные числа, комплексные числа,
кватернионы, точные производные, векторы, матрицы, массивы, строки, битовые
векторы, конечные множества и многочлены. Они строятся из математических
блоков, как описано в гл. 2. Внутренние структуры и внешние интерфейсы этих
классов описаны с большой детализацией.
В главе 7 мы описываем, как можно конструировать математическое
выражение, используя объектно-ориентированную технику. Мы вводим новую
систему компьютерной алгебры SymbolicC++ и описываем ее внутреннее
представление и открытые интерфейсы. Чтобы продемонстрировать
функциональные возможности системы, приведены различные примеры.
В главе 8 мы применяем классы, разработанные в гл. 6 и 7, к задачам из
математики и физики. Приложения распределены в соответствии с классами.
Для решения конкретной задачи могут быть одновременно использованы
различные классы. Представлены многие интересные проблемы, например,
блуждающие решения, аппроксимация Паде, техника рядов Ли, метод Пикара,
множество Мандельброта и др.
В главе 9 мы обсуждаем, как язык программирования Лисп может быть
использован для реализации системы компьютерной алгебры. Мы
реализовали в нем алгебраическое упрощение и программу дифференцирования.
Кроме того, мы разрабатываем здесь систему Лисп, использующую объектно-
ориентированный язык C++.
Листинги заголовочных файлов классов (абстрактных типов данных),
введенных в гл. 6 и 7, помещены в гл. 10, тогда как в гл. 11 мы показываем
как PVM (параллельная виртуальная машина) может быть использована с
Предисловие
9
абстрактными типами данных. В главе 12 мы описываем некоторую технику
обработки ошибок. Мы вводим понятие обработки исключительной ситуации.
В демонстрационных целях приводятся некоторые примеры. Наконец, в гл. 13
мы показываем, как Gnuplot и PostScript могут использоваться для рисования
фигур.
Уровень представления предмета в данной книге таков, что ее можно
изучать, еще не получив специального научного образования. Мы соблюдаем
баланс между практическим программированием и лежащим в его основе
языком. Книга идеально подходит для использования в курсе по символьным
вычислениям и объектно-ориентированному программированию. Начинающий
программист также извлечет пользу из книги.
В списке литературы приведены учебные издания, полезные при изучении
языка C++ [6], [13], [23], [29], [31], [37], [45], [55]. По структурам данных мы
отсылаем читателей к Budd A994) [9], а в области научных приложений —к
работам Steeb et al. A993) [48], Steeb A994) [49] и Steeb A999) [52].
Приведенные в книге C++ программы протестированы со всеми
последними компиляторами C++, которые поддерживают стандартный C++ и
включают реализацию стандартной библиотеки шаблонов.
Несомненно, эта книга может быть расширена. Если у вас есть замечания
или предложения, мы были бы рады получить их. Электронные адреса
авторов:
Willi-Hans Steeb: whs@na.rau.ас.za
steeb_wb@yahoo.com
Tan Kiat Shi: ktan@ilog.co.jp
Yorick Hardy: yorickhardy@yahoo.com
SymbolicC++ был реализован в International School for Scientific Computing.
Вэб-сайты International School for Scientific Computing таковы:
http://zeus.rau.ac.za/
http://issc.rau.ac.za/
На втором из этих сайтов также содержатся заголовочные файлы для
SymbolicC++.
Тан К. Ж,
Стиб В.-Х.,
Харди Й.
Глава 1
Введение
1.1. Что такое компьютерная алгебра?
Компьютерной алгеброй ([11], [33], [39]) называется технология символьных
преобразований математических формул цифровыми компьютерами.
Например, такое выражение, как
х- 2*х + —[(ж-аJ],
ах
в результате символьных вычислений приводится к виду
х — 2 * а.
Символьные упрощения алгебраических выражений составляют основу
компьютерной алгебры. Символьное дифференцирование, использующее правила
вычисления производной от суммы, произведения и частного, также
должно включаться в любую систему компьютерной алгебры. Кроме того, такие
выражения, как sin2 (ж) + cos2 (x) и ch2(a;) — sh2(a;), должны заменяться их
численным значением — числом 1. Еще один важный аспект любой системы
компьютерной алгебры состоит в том, что она позволяет пользователю
определять правила. Примерами служит реализация внешнего произведения и
скобки Ли. Значительную часть систем компьютерной алгебры составляют
также символьные преобразования многочленов, например при вычислении
наибольшего общего делителя двух многочленов или нахождении коэффициентов
многочлена.
Долгое время эту дисциплину называли по-разному: то символьными и
алгебраическими вычислениями, то символьными и алгебраическими
преобразованиями и, наконец, остановились на названии «компьютерная алгебра».
Программы алгебраических вычислений уже давно применяются во многих
областях науки и техники. Наиболее широко они используются в таких
приложениях, в которых алгебраические вычисления крайне громоздки и трудоемки,
скажем в общей теории относительности, небесной механике и квантовой хро-
модинамике. Одним из первых применений таких программ было вычисление
кривизны поля заданного метрического тензора. При этом в основном
использовалось символьное дифференцирование.
12
Глава 1. Введение
1.2. Свойства систем компьютерной алгебры
Что должна уметь делать система компьютерной алгебры? Прежде всего, она
должна уметь работать с такими типами данных, как очень длинные целые
числа, рациональные числа, комплексные числа, кватернионы и т. п.
Основу символьной части системы должно составлять упрощение выражений,
например:
а + 0 = а, 0 + а = а,
а — а = 0, —а + а = 0,
а*0 = 0, 0*а = 0,
а*1 = а, I* a — а,
о° = 1.
В большинстве систем символы считаются перестановочными, т. е.
a*b = b * а.
Так, выражение
(а + Ъ) * (а — Ъ)
должно преобразовываться к виду
а* а — Ъ*Ъ.
Если символы перестановочны, то это должно указываться специальной
командой. Кроме того, система компьютерной алгебры должна выполнять
упрощения тригонометрических и гиперболических функций, например,
sin(O) = 0, cos(O) = 1,
ch@) = 1, sh@) = 0.
Выражение ехр@) должно упрощаться до 1, a In 1—до 0. Такие выражения,
как
sin2(х) + cos2(ж), ch2(ж) - sh2(a;),
должны упрощаться до 1. Кроме символьного дифференцирования и
символьного интегрирования любая система компьютерной алгебры должна также
осуществлять символьные вычисления с векторами, матрицами и массивами. Так,
скалярное произведение и векторное произведение должны вычисляться в
символьной форме. Для квадратных матриц символьно должны вычисляться след
и детерминант. Кроме того, такая система должна уметь обращаться с
числами. Поэтому она должна располагать возможностью переходить от символьных
вычислений к численным и обратно. Далее, система компьютерной алгебры
должна быть языком программирования. Например, она должна содержать
условные переходы и циклы, а также допускать функции и процедуры.
1.3. Недостатки систем компьютерной алгебры
Хотя системы компьютерной алгебры существуют уже много лет, им все еще
свойственны ошибки и ограничения. Ниже перечислен ряд типичных для таких
систем дефектов.
1.3. Недостатки систем компьютерной алгебры 13
Характерный пример — вычисление выражения
yV + b2- 2ab.
Некоторые системы компьютерной алгебры показывают, что результатом
служит а — Ь. Ясно, что на самом деле результат равен ±\а — Ъ\. В качестве еще
одного примера рассмотрим ранг матрицы
ранг матрицы — это число линейно независимых столбцов (которое равно
числу линейно независимых строк). Если х — 0, то ранг этой матрицы равен 0. С
другой стороны, если х Ф 0, то ее ранг равен 1. Таким образом, системы
компьютерной алгебры сталкиваются с неопределенностью при вычислении ранга
этой матрицы. Аналогичная проблема возникает при нахождении матрицы,
обратной к матрице
Обратная матрица существует, лишь если х ф 0.
Другая проблема возникнет, если предложить системам компьютерной
алгебры взять интеграл
/ xndx,
где п —целое число. Если п ф — 1, этот интеграл дается формулой
xn+1
п+ Г
Если же п = -1, то получим In (ж).
Еще одна неопределенность возникает при вычислении 0°. Рассматривая
f(x) = хх = ехр(а;1пB:)) при х > 0 и затем применяя правило Лопиталя,
находим 0° = 1 в качестве возможного определения 0°. Многие системы
компьютерной алгебры сталкиваются с трудностями, вычисляя значение выражения
х
х + sin(a;)
при х = 0 по правилу Лопиталя. Правильный результат равен 1/2.
Следует также иметь в виду, что при решении уравнения
а* х = 0
система компьютерной алгебры должна различать случаи а = 0 и х = 0-
Многие недостатки описываемых систем обнаруживаются при обращении с
комплексными числами и точками ветвления функций в комплексном
анализе. Комплексные числа и функции должны удовлетворять критерию Аслаксе-
на [2]. Так, выражение
ехрAп(г))
14
Глава 1. Введение
должно упрощаться до z, но выражение
ln(exp(z))
для комплексных чисел упрощаться не должно. Нужно обращать внимание на
разрезы комплексной плоскости при рассмотрении многозначных комплексных
функций. Большинство систем компьютерной алгебры по умолчанию считают
аргумент вещественным.
Более подробный обзор недостатков систем компьютерной алгебры можно
найти в статье [52].
1.4. Разработка системы компьютерной алгебры
Большинство систем компьютерной алгебры основаны на языке Lisp.
Название этого языка программирования — аббревиатура английских слов List
processing — обработка списков. Основное назначение языка Lisp —
манипуляции со структурами данных, называемыми списками, которые
заключаются в скобки. Многие мощные системы компьютерной алгебры основаны на
этом языке, например REDUCE, MACSYMA, DERIVE, AXIOM и MuPAD.
Структура системы AXIOM основана на объектно-ориентированном
программировании в среде языка Lisp. Системы компьютерной алгебры MAPLE и
MATHEMATICA основаны на языке С. Все вышеперечисленые системы —это
мощные программные продукты, способные совершать символьные
вычисления. Однако эти программные системы независимы, и перенос выражений из
одной из них в другую программную среду вроде С — задача довольно
утомительная, времяёмкая и подверженная ошибкам. Поэтому было бы
полезно наделить какой-нибудь язык высокого уровня способностью обрабатывать
символьные выражения. С другой стороны, объектно-ориентированные языки
программирования обладают всеми необходимыми средствами, чтобы
выполнить эту работу красиво.
Здесь мы покажем, что объектно-ориентированное программирование с
использованием языка C++ может применяться для разработки системы
компьютерной алгебры. Объектно-ориентированное программирование — это
методология построения программных систем, основанная не на процедурах, а
на классах. Этот подход в наибольшей степени реализует такие качества
программ, как модульность и скрытие информации. Объектно-ориентированная
структура обладает множеством достоинств. Например, она объединяет
данные и функции, обрабатывающие эти данные, в единый модуль. Такой модуль
{абстрактный тип данных) называется классом.
Мы выбираем C++ в качестве объектно-ориентированного языка
программирования по следующим причинам: C++ позволяет ввести абстрактные
типы данных. Поэтому мы можем ввести типы данных, используемые в
системе компьютерной алгебры, как абстрактные типы данных. Язык C++
поддерживает основные понятия объектно-ориентированного программирования:
инкапсуляцию, наследование, полиморфизм (включая динамическое
связывание переменных) и перегрузку операций. Он обеспечивает хорошую
поддержку управления динамической памятью и допускает как процедурное, так
1.4. Разработка системы компьютерной алгебры
15
объектно-ориентированное программирование. Менее абстрактная форма
полиморфизма обеспечивается через поддержку шаблонов. Мы перегружаем
операции
+ .-,*./
пля наших абстрактных типов данных, таких, как сверхдлинные целые числа,
рациональные числа, комплексные числа или символьные типы данных.
Классы векторов и матриц реализуются на основе шаблонов, чтобы их можно было
использовать с другими абстрактными типами данных.
Другое преимущество этого подхода состоит в том, что поскольку сама
система символьных вычислений написана на C++, то ее легко расширить и
приспособить к конкретной решаемой задаче. Классы (абстрактные типы данных)
включены в заголовочный файл и могут быть использованы в любой
программе на C++ посредством команды #include "ADT.h" в начале программы.
Для реализации этой концепции нужно применить следующие средства
языка C++:
A) понятие класса
B) перегрузка операций и функций
C) наследование классов
D) виртуальные функции
E) шаблоны функций
F) шаблоны классов
G) библиотека стандартных шаблонов
Развитая система SymbolicC-|—|- включает следующие абстрактные типы
данных (классы):
Verylong сверхдлинные целые числа.
Rational шаблон-класс для рациональных чисел.
Complex шаблон-класс для комплексных чисел.
Quaternion шаблон-класс для кватернионов.
Derive шаблон-класс для точного дифференцирования.
Vector/Matrix шаблоны-классы для векторов и матриц.
Array шаблоны-классы для массивов размерности до четырёх.
String строки.
Bitvector бит-векторы.
List связанные списки.
Set конечные множества.
Polynomial шаблон-класс для многочленов.
Sum шаблон-класс для символьных вычислений, включающий
такие правила, как упрощение, дифференцирование,
коммутативность и некоммутативность.
Type/Pair атом и связанная пара для упрощенной Lisp-системы.
Система включает подходящие преобразования типов между абстрактными
типами данных, а также между абстрактными типами данных и основными
типами данных.
16
Глава 1. Введение
Преимущества этой системы следующие:
A) Объектно-ориентированная структура и адекватный подход к основным
и абстрактным типам данных.
B) Система независима от используемой операционной системы, т. е. для
любых операционных систем существуют мощные компиляторы с C++.
C) Пользователю предоставляются исходные тексты.
D) Легко добавляются новые классы (абстрактные типы данных).
E) "Учтен стандарт языка C++.
F) Пользователю достаточно изучить C++, чтобы применять систему
компьютерной алгебры.
G) Легко добавить ассемблерный код для прогонки на конкретном
процессоре.
(8) Методы и операторы преобразования типов обеспечивают алфавитно-
цифровой интерфейс.
(9) Классы (абстрактные типы данных) включаются в заголовочный файл и
могут быть сделаны доступными любой программе на C++ посредством
команды #include "ADT.h" в начале программы.
A0) Классы могут быть связаны с параллельной виртуальной машиной
(ПВМ).
A1) Можно использовать библиотеку стандартных шаблонов.
Глава 2
Математические основы
компьютерной а
2.1. Кольца и поля
Типы данных системы компьютерной алгебры включают в себя целые числа,
рациональные числа, вещественные числа, комплексные числа, кватернионы
и символы. С другой стороны, основные типы данных в таких языках
программирования, как С и C++, суть целые числа, символы, числа с плавающей
точкой (с обычной и двойной точностью) и указатели. Из этих основных
типов данных мы можем образовывать массивы, например массивы целых чисел
или массивы символов (строки). Математическими структурами этих типов
данных (кроме указателей) служат кольца и поля [22], [3].
Кольцо — это упорядоченная тройка (R, +, *), состоящая из множества R и
двух определенных на нем бинарных операций + (сложение) и * (умножение),
удовлетворяющих следующим условиям:
(a) пара (R, +) является коммутативной группой;
(b) умножение ассоциативно;
(c) оно допускает тождественный (или единичный) элемент, обозначаемый
символом I;
(d) умножение дистрибутивно (с обеих сторон) относительно сложения, т. е.
х * (у + z) =х*у+х*ги (x + y)*z = x*z + y*z
для всех х, у, z € R.
Обычно в аксиомы кольца не включают условие (с) — существование
единичного элемента относительно умножения. Тогда различают кольца вообще и
кольца с единицей. Те же различия применимы к приводимым ниже определениям
подкольца и области целостности. Если к тому же умножение коммутативно,
то R называют коммутативным кольцом.
Подколъцом кольца R называется его подмножество S, удовлетворяющее
бедующим условиям:
(a) S является подгрупой аддитивной группы R;
(b) если х е S иу е S, то х*у £ S;
(с)./€5.
лгебры
Таким образом, подкольцо является кольцом.
18 Глава 2. Математические основы компьютерной алгебры
Если элемент a 6 R имеет обратный элемент относительно умножения, т. е.
существует (единственный) элемент а 6 R, такой, что
а* а = а'1 * а = I,
то этот элемент а называется обратимым элементом кольца R.
Множество обратимых элементов кольца R обозначается R*. Если каждый
ненулевой элемент кольца R обратим, то R называется кольцом с делением.
Коммутативное кольцо с делением называется полем. S является подполем
поля F, если S является подкольцом поля F и из условий х 6 5, х ф 0 следует,
что х € S.
Кольцо R является упорядоченным, если существует непустое
подмножество Р С R, называемое множеством положительных элементов R, которое
удовлетворяет следующим условиям:
(a) если аеРяЬеР, то а + ЬеРиа*ЪеР;
(b) для любого а € R верно в точности одно из следующих утверждений:
аЕР, а = 0, (-а) £ Р.
Коммутативное кольцо К называется областью целостности, если из условий
хЕК,уЕКих*у = 0 следует, что или х = О, или у — 0. Если х*у = 0, х ф 0
и у Ф Q, то жиг/ называются делителями нуля в К.
Характеристика кольца определяется как аддитивный порядок его
(мультипликативного) тождественного элемента, т. е. R обладает характеристикой
т, если т есть наименьшее целое положительное число, для которого т*1 — 0.
Кольцо имеет характеристику 0 (или, при другом определении, оо), если
никакое кратное единичного элемента не равно нулю.
Примерами колец служат (Z, +, *) и (Q, +, *). Z является подкольцом
кольца Q, но можно показать, что у Z нет собственных подколец. Единственными
обратимыми элементами Z служат 1 и —1, тогда как в Q все ненулевые
элементы обратимы. Поскольку Q — коммутативное кольцо, отсюда следует, что
Q является полем. Q — подполе поля R. Z, Q и R можно упорядочить
символом <, понимаемым в обычном смысле. Все эти кольца являются областями
целостности и имеют характеристику 0.
Двусторонний идеал I кольца R — это непустое подмножество R,
удовлетворяющее следующим аксиомам:
(a) I является подгруппой аддитивной группы R,
(b) для всех а; 6 J и всех а,Ь 6 R выполняется
(i) a*x e R и (ii) х * b € R.
I является левым идеалом, если это множество удовлетворяет аксиомам (а) и
(b) (i), и правым идеалом, если оно удовлетворяет аксиомам (а) и (b) (ii). Если
кольцо R коммутативно, все его идеалы двусторонние.
Идеал I ф К коммутативного кольца К называется максимальным
идеалом этого кольца, если для любого его идеала М, удовлетворяющего условию
I С М С К, либо М = I, либо М = К. Для двух данных идеалов I та J
коммутативного кольца К определим их сумму I + J как множество всех элементов
2.2. Целые числа.
19
льда К вида х + у, где а; € J и г/ 6 J, а их произведение / J как множество
х элементов if, которые могут быть записаны в виде
xi * г/i + х2 * 2/2 Н Ь ж„ * г/„,
ngN Zj 6 / и г/г € J для всех i = I,... ,п. I + J и IJ также являются
идеалами.
Если if — коммутативное кольцо и / = {х * Ъ\х € К}, где Ь —
фиксированный элемент К, то J является идеалом, называемым главным идеалом. Тогда
говорят, что Ь порождает I. Область целостности, все идеалы которой
являются главными, называется кольцом главных идеалов. Если I есть идеал из
кольца главных идеалов D и порождается элементами х1г... ,хп, т. е. I есть
множество всех элементов вида
Ui * Xi + U2 * Х2 + ■ ■ ■ + Un * Хп,
где u^D, то I порождается некоторым единственным элементом d. Этот
элемент d называется наибольшим общим делителем (НОД) элементов xi,..., хп.
Элементы х\,...,хп называются взаимно простыми, если их НОД есть 1.
Пересечение п идеалов, порожденных элементами х\,..., хп, взятыми по
одному, также является идеалом и поэтому будет порождаться неким
единственным элементом т. Этот элемент т называется наименьшим общим кратным
(НОК) элементов х\,..., хп. Элемент р кольца главных идеалов D называется
простым или неприводимым, если он не является обратимым элементом D и
если из р = а*Ь, где а, Ь € D, следует, что либо а, либо Ь — обратимый элемент.
Область целостности D является областью однозначности разложения на
множители, если
(a) для всех а € D\{0} а либо является обратимым элементом, либо
представим как произведение конечного числа неприводимых элементов
области D и
(b) упомянутое в (а) разложение единственно с точностью до упорядочения
неприводимых элементов и подстановки ассоциированных элементов.
Пример. Множества
h = {... ,-4,-2,0,2,4,...} и h = {.-., -6, -3,0,3,6,...}
являются максимальными идеалами кольца Z.
/i2 = {• • • , -24, -12,0,12,24,...} и 18 = {... , -16, -8,0,8,16,...}
являются идеалами, но не максимальными идеалами.
2.2. Целые числа
В этом разделе мы введем целые числа. Доказательства приведенных ниже
теорем можно найти в [3], [21]. Пусть N —это множество натуральных чисел
N:={0,1,2,...}.
Множество целых чисел
Z = {...,-3,-2,-1,0,1,2,3,...}
20 Глава 2. Математические основы компьютерной алгебры
можно построить из N следующим образом. Определим отношение
эквивалентности £ на N х N формулой
(х, у)Е(х',у') <=> х + у' = х' + у.
Мы хотим, чтобы формула х — у = х' — у' выполнялась, как только будет
определена операция «минус». Множество Z тогда определяется как N х N/E.
Для любых элементов z и z' кольца Z, таких, что
z=pE{x,y), z' =pE(x',y'),
где ре обозначает каноническое отображение из N х N на Z, определим сумму
и произведение z и z' формулами
z + z' =Ре(х + х',у + у'), z*z' = ре(х*х' +у*у',х*у' + х *у').
Такое определение выбрано потому, что мы хотим, чтобы выполнялись
формулы
{х-у) + (х' - у') = {х + х1) -{у + у') и
(х — у) * (х' — у') — (х * х1 + у * у') — (х * у' + х' * у).
С этими двумя операциями Z является коммутативным кольцом.
Отображение п —> ре(п,0) является инъективным отображением N в Z, сохраняющим
сложение и умножение. Мы можем, следовательно, отождествить N с
некоторым подмножеством кольца Z. Если определить — z как элемент, обратный к
z относительно сложения, то можно показать, что либо z € N, либо —z € N.
Если z € N\{0}, будем называть это z положительным целым числом; если же
—z € N\{0}, будем называть это z отрицательным целым числом. Заметим,
что а — Ь = а + (—6), если а,Ь ф N. Целое число а ф 0 называется делителем
целого числа 6 (пишется а|6), если существует целое число с, такое, что b = а*с.
Если а\Ъ, мы будем называть это Ъ кратным а. Чтобы показать необходимость
ограничения а ф 0, предположим, что 0|Ь. Если Ь ф 0, то для некоторого с g Z
должно выполняться Ь = 0 * с, что невозможно; если же Ь = 0, мы получим
0 — 0 * с, что верно для любого с 6 Z. Если Ь, с, х,у € Z, то целое число
6 * х + с * у называется линейной комбинацией бис. Мы получили следующее
утверждение.
Теорема. Если а\Ь и а\с, то а\{Ъ * х + с * у) для всех х, у G Z.
Целое число р ф 0,±1 называется простым тогда и только тогда, когда у
него нет иных делителей, кроме ±1 и р. Ясно, что число — р является простым
тогда и только тогда, когда р есть простое число. В дальнейшем нас будут
интересовать в основном положительные простые числа. Число
положительных простых чисел бесконечно. Если а — Ь * с, где |Ь| > 1 и \с\ > 1, то целое
число а называется составным. Поэтому каждое целое а ф 0, ±1 является либо
простым, либо составным. Если а\Ъ и а\с, то а называется общим делителем
чисел Ъ и с. Если, кроме того, каждый общий делитель чисел Ъ та с является
также делителем числа а, то число а называется наибольшим общим
делителем чисел бис. Пусть с и d суть два наибольших общих делителя чисел
а ф 0 и Ь ф 0. Тогда c\d и d\c; значит, числа cud различаются только знаком.
2.2. Целые числа
21
ничимся лишь положительным наибольшим общим делителем двух целых
сел о и Ь, который будем обозначать либо d, либо (а, Ь). Таким образом, d и
гамом деле является наибольшим целым числом, которое делит и а, и Ь.
Мы предположили, что (а) у каждой пары ненулевых целых чисел есть
оложятельный наибольший общий делитель и что (Ь) любое целое a > О
оедставимо в виде произведения положительных простых чисел
единственным образом (с точностью до порядка, в котором расположены эти
множителя). Разумеется, если само число а простое, то «произведение положительных
гтостых чисел» в (Ь) нужно понимать как состоящее из одного простого числа.
Алгоритм деления. Для любых двух ненулевых целых a u b существует
единственная пара целых q и г, называемых соответственно частным и
остатком, таких, что
a=b*q + r, О < г < |Ь|.
Отсюда следует, что Ь\а и (a, b) = b тогда и только тогда, когда г = 0.
При г ф 0 легко показать, что общий делитель чисел а и b также делит г, а
общий делитель чисел b и г также делит а. Тогда (о, b)\(b,r) и (b,r)\(a,b), так
что (а, Ь) = (Ь, г). Далее, либо г\Ь, либо г/ Ь. В последнем случае с помощью
алгоритма деления получаем
Ъ = г * qi + п, 0 < п < г.
Опять же, либо п|г и (а, Ь) = г\, либо с помощью алгоритма деления находим
г = п * q2 + г-2, q < г2 < щ
и (а, V) = (Ь,г) = (r,ri) = (ri,r2). Если предположить, что этот процесс будет
продолжаться, то поскольку остатки г\, г2,... образуют монотонно
убывающую последовательность неотрицательных целых чисел, в конце концов одно
из них должно обратиться в нуль. Предположим, что процесс оканчивается
так:
(k) rfc_3 = rk-2 * qk-i + rk-i 0 < rfc_i < rfc_2
(к + 1) гк-2 - Гк-i *qk+rk 0 < rk < rfc_i
(к + 2) rfc_i = rfc * qk+1 + 0.
Тогда
(а,Ь) = (b,r) = (r,n) = ... = (rfc-2,rfc_i) = (г/ь-ьг*) = rfc.
Поскольку г = a—b*q = a+ (—q) * b = гп\ * а + п\ * Ь, находим
r\ = b — г * qi
= b — (mi * а + щ * b) * qi
— —mi * q% * a + A — ni * qi) * b
= Ш2 * a + щ * b,
r-2 = r - ri * q2
— (mi * a + щ * b) — (m2 *а + П2 *b) *q2
= (mi — Q2 * ТП2) * a + (ni — q2 * «2) * b
= тъ * a + П3 * b.
22 Глава 2. Математические основы компьютерной алгебры
Продолжая, получаем в конце концов
Гк = rn,k+i *a + nfc+i * b.
Таким образом, мы доказали следующее утверждение.
Теорема. Если d — {a,b), то существуют такие т,п £ Z, что d = (а, Ь) =
т * а + п * Ь.
В представлении (a, b) =m*a + n*b целые тип определены неоднозначно;
в самом деле, (а, Ь) = (т + к * Ь) * а + (п — к* а) * b для любого к ф N. Важность
этой теоремы вытекает из следующего. Если а\с, Ь\с и если (а, Ь) — d, то a*b\c*d.
Поскольку а\с и Ь\с, то существуют такие целые s и t, что с = a* s = b*t. По
условию теоремы найдутся такие т,п £ Z, что d = m*a + n*b. Тогда
c*d = c*Tn*a + c*n*b = b*t*m*a + a*s*n*b = a*b*(t*m + s*n)
и а * b\c * d.
Второе следствие алгоритма деления — это
Теорема. Любое непустое множество целых чисел К, замкнутое
относительно бинарных операций сложения и вычитания, — это либо {0}, либо оно
состоит из всех элементов, кратных его наименьшему положительному
элементу.
Пусть для данных a,b £ Z существуют такие ш,п € Z, что а*т + Ь*п = 1.
Но любой общий множитель чисел а и b является множителем правой части,
т.е. числа 1; следовательно, (а,Ь) — 1. Два целых числа а и Ь, для которых
(о, Ь) — 1, называются взаимно простыми.
Теорема единственности разложения. Каждое целое а > 1 может быть
единственным способом (с точностью до порядка сомножителей)
представлено в виде произведения положительных простых чисел:
a=pi *p2 *р3 * ■■■ *рп-
Очевидно, что
-а = -(pi *р2*рз* ...*рп).
Кроме того, поскольку этир необязательно все разные, мы, можем записать
где каждое ац > 1 и всеР\,Р2,Ръ,- ■ ■ ,Ps различны.
Пусть т есть положительное целое число. Отношение сравнимости по
модулю m, (= (modm)), определено для всех a, b € Z следующим условием: а = b
(modm) тогда и только тогда, когда т\(а — Ь). Альтернативное определение,
часто более полезное, чем приведенное выше, таково: а = b (modm) тогда и
только тогда, когда числа о и b при делении на т дают один и тот же остаток.
Из этих определений немедленно вытекают следующие результаты:
Теорема. Если а = b (modm), то для любого n£Z,m*n + a = b (modm) и
обратно.
2.2. Целые числа
23
ема. Если a = b (modm), то для всех i E Z, a + х = b + x(modm) и
aTx^b*x(modm).
rr оема. Если a = fe(modm) и с = e(modm), то a + c = & + e(modm), a — c =
k-e(modm), a * с = b*e(modm).
Теорема. Пусть (с,т) = d. Запишем m = mi * d. Если с* а = с* b(modm),
moa = b(modm!) и обратно.
Отношение = (mod m) на Z является отношением эквивалентности,
разбивающим все целые числа на m классов эквивалентности [0], [1], [2],... , [т - 1],
которые называются классами вычетов по модулю т, где
[г] := {а : а б Z,a = r(modm)}.
Обозначим через Z/(m) множество всех классов вычетов по модулю т. Два
основных свойства классов вычетов по модулю т таковы: если а и b
принадлежат к одному классу вычетов [s], то а = fe(modm); если [s] и [t] суть разные
классы-вычетов, то a £ b(modm).
Рассмотрим линейное сравнение по модулю
а*х = fc(modm),
в котором а, Ь, т суть фиксированные целые числа и т > 0. Под
решением сравнения мы понимаем любое целое х = х\, для которого выполняется
т\(а * xi — Ь). Но если xi есть решение, так что т\(а * xi — b), то для любого
k G Z выполняется т\(а * (xi + к * т) — Ь), так что х\ + к * тп является еще
одним решением. Таким образом, если xi — это решение, то решением является
и любой другой элемент класса вычетов [xi] по модулю т. Если у линейного
сравнения есть решения, то они состоят из всех элементов одного или более
классов вычетов кольца Z/(m).
Пусть (а,тп) — 1 — s*a + t*m. Тогда b = b*s*a + b"*t*m,vi xi —b*s
является решением. Теперь допустим, что Х2 ^ xi (modm) — это еще одно решение.
Поскольку a*xi = fe(modm) и а * х2 = b(modm), из свойства транзитивности
отношения = (modm) следует, что а*хг = a*x2{modm). Тогда тп\а* (хх - хг)
и xi = X2(modm), вопреки допущению. Поэтому имеется только одно
решение, скажем xi, и класс вычетов [xi] £ Z/(m), называемый также классом
сравнений по модулю, включает все решения.
Теорема. Сравнение а * х = fe(mod тп) имеет решение тогда и только тогда,
когда d = (а,т) является делителем числа Ь. Если d\b, это сравнение имеет
ровно d несовпадающих решений (d классов сравнений по модулю).
Число 827016 можно записать в виде
827016 = 8 * 105 + 2 * 104 + 7 * 103 + 0 * 102 + 1 * 10 + 6.
а этом представлении используются свойства сравнения целых чисел по
модулю. Так, пусть а —целое число. В силу алгоритма деления
a=10*qo+r0, 0 < г0 < 10.
■ьсли q0 = о, мы записываем а — го. Если q0 > 0, то q0 = 10*^ +ri, 0 < r\ < 10.
еперь если ^ = 0, то а = 10 * п + г2, и мы записываем а — rir0; если q\ > 0,
24 Глава 2. Математические основы компьютерной алгебры
то gj = 10 *q-2+r2, 0 <г2 < 10. Снова, если q2 = 0, то a = 102 *г2 + 10*п +г0,
и мы записываем a = r2rir0; если q2 > 0, мы повторяем этот процесс. В конце
концов он должен оборваться, и мы получим
a — 10s * rs + 10s-1 * rs_i + ... + 10 * ri + r0 = rsrs_i ... rxr0.
Это следует из того факта, что qi образуют набор убывающих
неотрицательных целых чисел. Заметим, что используемые в этом представлении
символы ri принадлежат множеству {0,1,2,3,... ,9} вычетов по модулю 10. Такое
представление единственно. Процесс не зависит от выбранного основания, и
в качестве него можно взять любое другое положительное целое. Так, если в
качестве основания взять 4, то любое положительное целое будет
представлено некоторой последовательностью символов 0, 1, 2, 3. Например, десятичное
число 155 представимо в четверичной системе как
155 = 43*2 + 42*1 + 4*2 + 3 = 2123 по основанию 4.
Далее мы опишем алгоритм, порождающий последовательность простых чисел
и называемый «решето Эратосфена». Он был разработан в третьем веке до
нашей эры. Этот алгоритм находит все простые числа, меньшие заданного
целого N. Он работает, удаляя все составные числа, и то, что остаётся, является
последовательностью простых чисел.
Рассмотрим следующую последовательность целых чисел > 1:
2 3 4 5 6 7 8 9 10 11 1.2 13 14 15 16 17 18 19 20 21 ...
Первое простое число —это 2, и все числа, кратные 2, составные. Поэтому мы
вычеркиваем все чётные числа, большие 2:
2 | 3 -4- 5 -ё- 7-8- 9 40 11 Ш 13 44 15 Ш 17 Ш 19 ЗД 21...
Следующее невычеркнутое число этой последовательности — число 3. Значит,
это и есть следующее простое число. Аналогичным образом мы вычеркиваем
все числа, кратные 3. Заметим, что мы пытаемся вычеркнуть числа 3x2 = 6,
3x4= 12, 3x6= 18, которые уже были вычеркнуты ранее, так как они кратны
2. Действительно, мы могли бы уменьшить количество операций, вычеркивая
только 3C + 0) = 9, 3C + 2) = 15, 3C + 4) = 21,...
2 3 | 5 7-9- 11 13 44 17 19 £4 23 25 Ш 29 31 Ш 35 37...
В общем случае, начиная с некого простого числа р, мы последовательно
вычеркиваем числа, кратные р2, р(р + 2), р(р + 4), — Мы начинаем процесс
вычеркивания с р2, поскольку все кратные р числа, меньшие этого, уже должны
были быть вычеркнуты на более ранних стадиях процесса. Например, начиная
с простого числа 5, мы вычеркиваем 5E+0) = 25,5E+2) = 35, 5E+4) =45,
Нам нет нужды вычеркивать 5x2 или 5x3, так они уже были удалены на
стадии р — 2 и р = 3 соответственно.
Заметим, что в ходе этого процесса нам всё же придется иногда
вычеркивать уже вычеркнутые ранее числа. Так, 5E + 4) = 45 уже было вычеркнуто
как кратное 3. Последовательность для р = 5 выглядит так:
2 3 5 | 7 11 13 17 19 23 3& 29 31 £& 37 41 43 47 53 ■&&...
2.3. Рациональные числа
25
л/Г i продолжаем этот процесс, пока не достигнем простого р, для которого
2 > N где N — наибольшее число, которое мы проверяем на простоту. На
й стадии все составные числа < N уже должны оказаться вычеркнутыми,
гг что у нас останется, — это последовательные простые числа < N. Ниже мы
пиводим список чисел, меньших 100 и полученных с использованием
описанного выше алгоритма:
2 3 5 7 11 13 17 19 23 29 31 37 41
43 47 53 59 61 67 71 73 79 83 89 97
2.3. Рациональные числа
Множество рациональных чисел Q можно построить аналогично построению
множества Z следующим образом. Пусть Е есть отношение эквивалентности
на множестве Z x (Z \ {0}), определенное выражением
(a, b)E(c, d) <£=*• а * d = Ь * с.
Определим Q как
Z х (Z \ {0})/Е.
Сложение и умножение определены на Q через каноническое отображение рв
формулами
Ре(л,Ь) + Ре(с,d) - рЕ{а *d + b*c,b*d),
Ре{о,,Ъ) х pE(c,d) =pE{a*c,b*d).
Можно показать, что так определенные операции превращают Q в поле и что
существует естественное инъективное отображение Z в Q, сохраняющее
операции умножения и сложения. Мы можем, следовательно, рассматривать Z как
подмножество Q.
Каждой упорядоченной паре (а, Ь) из множества Z x (Z \ {0}) соответствует
дробь а/b с числителем а и ненулевым знаменателем b (из-за необходимости
сделать b отличным от нуля в определении Q использовалось Z \ {0} вместо
Z). Две дроби тогда эквивалентны, если соответствующие им упорядоченные
пары эквивалентны в определенном выше смысле и рациональное число есть
класс эквивалентности дробей.
Теперь определим два особых рациональных числа:
нуль -> Ре@,Ь), единица -^ рв(а,а),
и обратные элементы:
(аддитивный): -Ре(о.,Ь) = рЕ(-а,Ь),
(мультипликативный): РЕ(а, Ь)-1 = Ре(Ь, а)
° Дальнейшем мы будем пользоваться обозначением а/b. Сложение и
умножение подчиняются дистрибутивному и ассоциативному закону. Кроме того, для
каждого а/b (а, ЪфО) существует обратный элемент относительно умножения
ь/а, такой, что (а/Ь) * (Ь/а) = 1.
26 Глава 2. Математические основы компьютерной алгебры
Вычитание и умножение определяются формулами
а с a*d—b*c , , ._ т a*d
b-d= b*d ' М^° и 1=б7с-
На множестве рациональных чисел существует отношение порядка. Элемент
а/Ъ € Q называется положительным тогда и только тогда, когда а * Ь > 0.
Аналогично, а/Ь называется отрицательным тогда и только тогда, когда а *
Ь < 0. Поскольку в силу закона трихотомии либо а * Ь > 0, либо а * Ь > 0,
либо а * Ь = 0, из этих определений следует, что каждый элемент Q либо
положителен, либо отрицателен, либо равен нулю. Отношения порядка < и >
на Q определяются следующим образом: для любых х,у € Q
х < у тогда и только тогда, когда х — у < 0,
х > у тогда и только тогда, когда х — у > 0.
Эти отношения транзитивны, но не рефлексивны и не симметричны. Q также
удовлетворяет закону трихотомии: если х, у € Q, то выполняется одно и только
одно из следующих трех соотношений:
(а) х = у, (Ъ) х < у, (с) х > у.
Рассмотрим произвольную дробь s/m € Q, где т ф 0. Пусть
(положительный) наибольший общий делитель чисел s и т есть d; запишем s = d * si и
т = d*mi. Поскольку (s,m) ~ (si,mi), то s/m — Si/mi. Поэтому любое
рациональное число Ф 0 может быть (единственным способом) записано в виде а/Ь,
где а и Ь взаимно простые. Всякий раз, когда s/m было заменено на а/Ь, мы
будем говорить, что дробь s/m приведена к несократимому виду. Всякое
вводимое впредь произвольное рациональное число будет считаться приведенным
к несократимому виду.
Теорема. Если х и у суть положительные рациональные числа и х < у, то
1/х > 1/у.
Свойство плотности. Если х и у, где х < у, суть два рациональных числа,
то существует рациональное число z, такое, что х < z < у.
Архимедово свойство. Если х и у суть положительные рациональные
числа, то существует положительное целое числор, такое, чтор* х > у.
Рассмотрим положительное рациональное число а/Ъ, где Ь > 1. Тогда
а — q0 * Ь + г0, 0 < г0 < Ь и 10 * г0 = </i * Ь + п, 0 < п < Ь.
Поскольку го < Ь и, следовательно, gi * b + ri = 10 * г0 < 10 * Ъ, то gi < 10. Если
г\ = 0, то
Qi , , , gi , a gx
го = То*Ъ, а = <ь*Ь+-*Ъ, -ь=Яо + ^-
Запишем а/Ь — go • gi и назовем g0 • gi десятичным представлением дроби а/Ь.
Если г-! ф 0, то получим
10 * г\ — д2 * Ь + г2, 0 < г2 < Ь,
2.4. Вещественные числа
27
о2 < 10. Если т-2 = 0, то n = f| * Ь, так что го = f£ * Ь + ^ * Ь, и
ятичное представление дроби a/b есть до • 9i92- Если г2 = гь то десятичное
даедставление дроби a/b -это периодическая дробь
9о • 91929292 • ■ ■ •
тг ля га ^ 0, мы повторяем этот процесс. Заметим, что различные остатки
П, ■ ■ • СУТЬ элементы конечного множества {0,1,2,3,... ,Ь— 1} вычетов
' модулю Ь, так что в крайнем случае гь должно совпасть с одним из остатков
г го ,гл_1, скажем с гс, и тогда десятичным представлением дроби alb
будет периодическая десятичная дробь
9о • 9i9293 • • ■ 9&-i9c+i9c+2 • • • 96-i9c+i9c+2
Таким образом, каждое рациональное число может быть выражено либо
обрывающейся, либо периодической десятичной дробью.
Пример. Для дроби 11/6 находим
11 = 1*6 + 5
10*5 = 8*6 + 2
10*2 = 3*6 + 2
9о = 1,г0 = 5,
gi=8,n=2,
92 = 3, г2 = 2 = п
и 11/6 = 1.833333....
И наоборот, ясно, что каждая обрывающаяся десятичная дробь является
рациональным числом. Например, 0.17 = 17/100, а 0.175 = 175/1000 = 7/40.
Теорема. Каждая периодическая десятичная дробь является рациональным
числом.
В доказательстве используются две вспомогательные теоремы:
(i) Каждую периодическую десятичную дробь можно записать как сумму
бесконечной геометрической прогрессии.
(ii) Сумма бесконечной геометрической прогрессии, знаменатель которой г
удовлетворяет условию |г| < 1, есть конечное число.
2.4. Вещественные числа
Начав с N, мы можем построить Z и Q, рассматривая фактормножества
подходящих прямых произведений. Однако подобным образом невозможно
построить R, множество всех вещественных чисел, и здесь требуются иные методы.
Сначала заметим, что отношение порядка <, определенное на Q, обладает
тем свойством, что для любых a, b € Q, таких, что а < Ь, существует с £ Q,
для которого а < с и с < Ь. Пусть V(Q) есть степень Q, т.е. V{Q) обозначает
множество, элементами которого служат подмножества Q. Теперь рассмотрим
упорядоченные пары элементов V(Q), скажем (А, В), удовлетворяющие
следующим условиям:
(i) Al)B = Q, ЛПВ = 0,
(||) А и В оба непусты,
(ui) Если а € А и Ь € В, то а < Ь.
28 Глава 2. Математические основы компьютерной алгебры
Такая пара множеств (А, В) известна под названием дедекиндова сечения.
На множестве сечений можно определить соотношение эквивалентности R
выражением
(A,B)R(C,D)
тогда и только тогда, когда существует хотя бы одно рациональное число,
принадлежащее либо одновременно А и D, либо одновременно В я С. Это
гарантирует, что сечения {{х\х < q},{x\x > q}) и ({х\х < q},{x\x > q})
эквивалентны для всех q € Q. Каждый класс эквивалентности
относительно этого отношения определяется как вещественное число. Множество всех
вещественных чисел, обозначаемое символом R, тогда является множеством
всех таких классов эквивалентности. Если данный класс содержит сечение
(А, В), такое, что А содержит положительные рациональные числа, то этот
класс является положительным вещественным числом, тогда как если В
должно содержать отрицательные рациональные числа, то этот класс является
отрицательным вещественным числом. Так, например, \/2, содержащий сечение
({х(х2 < 2},{х(х2 > 2}), —это положительное вещественное число, поскольку
1 б А = {х\х2 < 2}. Чтобы определить сложение вещественных чисел, нужно
рассмотреть сечения (Ai,Bi) и (А2,В2), представляющие вещественные числа
ai и а2. Мы определим а.\ + а2 как класс, содержащий сечение (Аз, Вз), где А3
состоит из всех сумм а = а\ + а2, полученных выбором ai из А\ и а2 из А2. Для
заданного вещественного числа а, представленного сечением (Ai,B\),
определим —а, т.е. отрицательное а, как класс, содержащий сечение (—В\,— Ai),
определенное соотношениями а 6 А\ 4=£- —а б —А\ и Ь 6 В± -ф=^ — Ь € —В\.
Как мы увидим, а+(—а) = 0, и вычитание теперь можно определить формулой
а — C — а + {—($)■ Из двух ненулевых чисел а и —а одно всегда положительно.
То, которое положительно, называется абсолютной величиной или модулем
числа а и обозначается \а\. Поэтому |а| = а, если а положительно, и \а\ = —а,
если а отрицательно. Определим [0| = 0. Если а\ и а2 суть два положительных
вещественных числа, то произведение e*i * а2 — это класс, содержащий сечение
(Ац,В^), гДе Аа состоит из отрицательных рациональных чисел, нуля и всех
произведений а = ах *а2, полученных выбором положительного а\ из А\ и
положительного а2 из А2. Это определение распространяется на отрицательные
числа соглашением, что если e*i и а2 положительны, то
(—ах) * а2 = ai * (—а2) = —а\ * а2, (—ai) * (—«2) = °ч * «2-
Наконец, определим
0 * а = а*0 — 0 для всех а.
При этих определениях можно показать, что вещественные числа R образуют
упорядоченное поле.
Сопоставив элементу q б Q класс, содержащий сечение
({x\x<q}, {x\x>q}),
можно определить мономорфизм (полей) Q —> R. Мы можем, следовательно,
рассматривать Q как подполе поля R (т. е. отождествить Q с неким подпо-
2.4. Вещественные числа
29
п\ -ре элементы поля R, которые не принадлежат Q, называются
ггРМ ПОЛЯ *Ч/-
L«i4U0HaAbHbW«« числами.
"^^ можно установить следующее важное свойство поля К.: для любо-
пустого подмножества V С R, для которого существует верхняя грань
^ Нт е. такой элемент М £ R, что v < М для всех v £ V, существует точ-
верхняя грань (sup) L, такая, что если М — произвольная верхняя грань
П ОГфКа V то L < М. Подобным образом мы можем определить точную
множеыьа ) _
ююнюю грань (mf) для любого непустого подмножества V С R, имеющего
нижнюю грань.
Не всякое подмножество поля R имееет верхнюю (или нижнюю) грань в R;
апример, ее нет для N. Чтобы преодолеть некоторые последствия этого
факта в анализе часто пользуются расширенной системой вещественных чисел,
обозначаемой R и состоящей из R и двух символов —сю и +со, обладающих
следующими свойствами:
(a) Если х £ R, то -со < х < +оо и х + (+оо) = +со, х + (-со) = -со,
-fco -со
(b) Если х > О, то х * (+со) = +оо, х * (-со) = -со.
(c) Если х < О, то х * (+со) = -со, х * (-со) = +со.
Заметим, что R не обладает всеми алгебраическими свойствами R. Далее
будут перечислены основные свойства R —системы всех вещественных чисел.
Сложение
Ai. Закон замкнутости
Аг. Закон коммутативности
Аз. Закон ассоциативности
А4. Закон сокращения
А5. Тождественный элемент
-А-6. Обратные элементы
Умножение
■Mi. Закон замкнутости
"&2. Закон коммутативности
Мз. Закон ассоциативности
М4. Закон сокращения
^5- Тождественный элемент
Мб. Обрати
ые элементы
г + s 6 R для всех г, s £ R.
г + s = s + г для всех г, s ё R.
г + (s + i) = (r + s) + i для всех г, s, t 6 R.
Если r + i = s + i, то г = s для всех г, s,t E R.
Существует единственный аддитивный
тождественный элемент 0 6 R, такой, что г + 0 =
О + г для любого г £ R.
Для любого г € R имеется единственный
обратный относительно сложения элемент
—г € R, такой, что г + (—г) = (—г) + г = 0.
г * s € R для всех г, s 6 R.
г * s = s * г для всех г, s € R.
г * (s * i) = (г * s) * i для всех г, s, t £ R.
Если т*р = п*р, то m=nHp^O£R для
всех m, n 6 R.
Существует единственный
мультипликативный тождественный элемент 1 £ R, такой,
что г *1 = 1 * г = г для любого г € R.
Для любого г 9*= 0 € R существует
единственный обратный относительно умножения
элемент г-1 £ R, такой, что г*г-1 = г-1*г = 1-
30 Глава 2. Математические основы компьютерной алгебры
Распределительные законы Для любых r,s,teR
Di. г * (s + t) — г * s + г *t
JJ • (s + t)*r = s*r + t*r
Свойство плотности Для любых г, s € R, где г < s, существует
t € R, такое, что г < t < s.
Архимедово свойство Для любых г, s £ R+, где г < s, существует
п € N, такое, что n *r > s.
Свойство полноты Для любого непустого подмножества R,
имеющего нижнюю грань (верхнюю грань),
существует наибольшая нижняя грань
(наименьшая верхняя грань).
2.5. Комплексные числа
Поле комплексных чисел, обозначаемое С, можно определить несколькими
способами. Рассмотрим R х R и возьмем в качестве элементов С упорядоченные
пары (х, у) £ R х R. Операции сложения и умножения элементов С
определяются формулами
(Xi,yi) + (х2,У2) ■■= (Xl +Х2,У1 +2/2),
(Zl,2/l) * (z2,?/2) := (xlx2 ~ 2/12/2, ^12/2 + ^22/1 )■
Теперь мы покажем, что С является полем. Мы можем определить
мономорфизм (полей)
г : R -> С формулой г(х) = (х, 0).
Это позволит нам рассматривать R как подполе поля С. Кроме того, можно
проверить, что
(х,у) = (х,0) + @,1)*(у,0).
Поэтому, воспользовавшись определенным выше мономорфизмом, можно
записать
(х,у) =x + i*y,
где х, у € R и i = @,1). Видим, что
г2 = @,1) * @,1) = (-1,0) = -1.
Для заданного комплексного числа
z = х + i * у, где х, у € R,
назовем х вещественной частью, %{z), а у — мнимой частью, J{z), числа z.
Число
х — i * у
называется комплексно сопряженным числа z и обозначается z (или z*).
Очевидное геометрическое представление комплексных чисел — точки декартовой
плоскости R х R. Это представление известно как диаграмма Арганда. Такая
2.5. Комплексные числа
31
строится с помощью двух перпендикулярных координатных осей
диагр ти чиСЛу z = х + г * у сопоставляется точка с координатами (х,у).
а& П представлении сложение комплексных чисел интерпретируется как сло-
В Э^е векторов на плоскости. Длина г отрезка
@,0) -(х,у)
кается абсолютной величиной или модулем числа z и обозначается \z\.
Тем самым имеем
= г = \Д;2 + у2 = \/z~z.
Угол который отрезок Oz составляет с осью Ох, называется аргументом
(амплитудой или углом) числа z и обозначается arg z. Итак, мы получаем
tg(argz) = tg0 = -,
х
a arg z определяется как вещественное число по модулю 2тт (если z ф 0,
поскольку arg0 не определен). Некоторые авторы берут 0 < argz < 2тт, тогда как
другие предпочитают — 7г < arg z < тт. Это ограниченное значение называется
главным значением аргумента.
Координаты г и в называются полярными координатами. Соотношения
между полярными координатами точки и комплексным числом z = х + i *y,
которое эта точка представляет, суть
£ = rcos#, y = rsmd,
г = \z\ = л/х2 +у2, 9 = a,rgz,
z = г (cos в + sin#).
Из геометрических свойств треугольника вытекает неравенство
\zi + 221 < \zi\ + \Z2\,
известное как неравенство треугольника. С помощью двух формул
\ziz2\ = \zi\\z2\ и
a,ig(z1z2) = arg(zi) + arg(z2)(mod27r)
можно дать геометрическую интерпретацию умножения комплексных чисел.
Другие конструкции поля С таковы.
(&) Рассмотрим множество М, состоящее из всех матриц M2(R) вида
а —Ь
b а
операциями сложения и умножения матриц множество М образует поле, в
котором нулем служит 02, а единицей — единичная матрица 12. Получаем
fa —b \ ( с -d\_fac — bd -(ad + bc) \
\ b a J у d с J ~~ у ad + bc ас- bd J '
Роме того, отображение р : R -> М, определенное формулой
32
Глава 2. Математические основы компьютерной алгебры
является изомофизмом между полем R и неким подполем поля М. Отображая
матрицу
а —Ъ
Ь a
на а + г * Ь, получим изоморфизм между М и С.
(Ь) Определим С как факторкольцо поля R[i] посредством главного идеала
(i2 + l),T.e.
С = R[t]/(t2 + 1).
Если обозначить образ полинома t б R[i] при каноническом отображении
через г, то каждый элемент С можно единственным образом записать в виде
х + гу, где х,у € R. Операции сложения и умножения в С суть естественные
операции факторалгебры.
Для облегчения изучения некоторых кривых (например, логарифмической
спирали) часто снимаются ограничения г>0и0<#<27гна полярные
координаты. Тогда получается расширенная система полярных координат, в
которой г и 9 могут принимать любые вещественные значения. В этой
расширенной системе любая пара (p,w) определяет единственную точку плоскости,
но у каждой точки плоскости имеется бесконечное число полярных координат,
а именно
(p,w + 27r), {-p,u} + Bn + l)n)
для всех п € Z. В этой расширенной системе логарифмическая спираль
описывается одним уравнением г = еав, тогда как в ограниченной системе для ее
определения потребовался бы целый набор уравнений.
2.6. Векторы и матрицы
Пусть F есть поле. Векторное пространство (называемое также линейным
пространством) V над полем F определяется как аддитивная абелева группа
V вместе с функцией F xV —>V, (A, v) —>A*v, удовлетворяющая условиям
A*(a + b) = A*a+A*b,
(A + /i)*a = A*a+;Li*a,
(A * д) * a = A * (д * a),
1 * a = a
для всех A, (j, € F и a, b € V. F называется основным полем векторного
пространства, его элементы называются скалярами, а элементы V —
векторами. Буквы, обозначающие векторы, печатаются жирным шрифтом. Векторное
подпространство векторного пространства V определяется как любое
подмножество V множества V, для которого
(a) V является подгруппой аддитивной группы V,
(b) для любого аё^'и любого А € F справедливо А * а е V.
2.6. Векторы и матрицы
33
оЖество всех кортежей (жь ... ,хп), где ж» е R, г = 1,... ,п, образует
пространство, которое мы обозначим R™, над основным полем R,
Бекторное пр
еслй определить
(xi,.-- ,хп) + {У1,--- ,yn) = (xi+yi,--. ,хп+уп),
A* (xi,... ,xn) = (X*xi,... ,Х*хп), где А е R.
я ктовное пространство V над полем F, удовлетворяющее аксиомам коль-
способом, что сложение в кольце является сложением в векторном
пространстве и такое, что
А * (vi * v2) = (А * vi) * v2 = vi * (A * v2)
ля всех vi,V2 € V и А € F, называется алгеброй над F. Если, кроме того,
но образует коммутативное кольцо, мы будем называть его коммутативной
алгеброй. Подмножество алгебры V называется подалгеброй, если оно
одновременно является векторным подпространством и подкольцом алгебры V.
Для двух данных векторов х = (xi,x2,x3), У = (УьУ2,Уз) € R3 определим
их векторное произведение, обозначаемое х х у, как вектор
X X у := (Х2 *Уз~Хз *У2,Х3 *J/i -XI *УЗ,Х1 *У2-Х2 * Ух).
Векторное произведение некоммутативно. Это пример алгебры Ли. Получаем
xx(y + z)=xxy + xxz,
xxy = -yxz,
х х (А*у) = А* (х х у),
х х (у х z) + z х (х х у) + у х (z х х) = 0.
Последняя формула называется тождеством Якоби.
Для двух векторных пространств U nV над полем F гомоморфизмом U на
V называется отображение t: U —> V, удовлетворяющее соотношениям
*(а + Ь)=*(а)+*(Ь),
t(X * а) = А * (а)
Для всех а,Ь 6 [/ и А Е F. Гомоморфизмы векторных пространств поэтому
сохраняют линейные комбинации вида
Ах * ai + А2 * а2 + ... + А„ * ап,
гДе А1;... , An e F, ai,... , а„ g U. По этой причине гомоморфизмы векторных
ространств называются линейными преобразованиями или линейными ото-
Ражениями. Множество всех линейных комбинаций векторов ai,... , ап €
образует векторное подпространство пространства U, называемое подпро-
Ранством, порожденным ai,... , ап (или натянутым на ах,... , а„). Вектор-
е пР°странство V называется конечнопорожденным, если существует конеч-
множество элементов ai,... , а„, порождающее V.
екторы ai,... , а„ называются линейно независимыми, если единственный
°Р Xi,... дп; удовлетворяющий соотношению
Ai * ах + А2 * а2 + ... + А„ * а„ = 0, 0 eV,
2-
1548
34 Глава 2. Математические основы компьютерной алгебры
есть
Ах = А2 = ... = Ап = О, О б F.
Векторы ai,... , а„ являются линейно зависимыми тогда и только тогда, когда
существуют Ai,... , А„, не все равные нулю, для которых
Ах * ai + Аг * а2 + ... + Ап * а„ = 0.
Если ai,... , ап порождают некое векторное пространство V и являются
линейно независимыми, то мы будем говорить, что а1;... ,а„ образуют базис
пространства V. Если ai,... , а„ образуют базис пространства V и b есть
произвольный вектор пространства V, то существует единственный кортеж
(Ai,... , А„), такой, что
b = Ах * ai + Аг * аг + ... + А„ * а„.
Скаляры Ai,... , А„ называются тогда координатами или компонентами
вектора b относительно базиса aj.,... , а„. Можно показать, что каждое конечнопо-
рожденное векторное пространство V имеет базис и что, в частности, любые
два базиса пространства V содержат одно и то же число элементов, скажем,
п. Число п называется размерностью векторного пространства V над полем
F, и мы будем писать diml^ = п. Векторное пространство V тогда
изоморфно Fn, т.е. векторному пространству всех кортежей (xi,... ,xn), где ж» € F,
i = 1,... , п. По определению dim{0} = 0.
Если U и V суть конечномерные векторные пространства над одним и тем
же полем F и если «сложение» линейных преобразований и «умножение
линейных преобразований на скаляр» определить формулами
(*i+*2)(a)=*i(a)+t2(a),
(А * i) (а) = Ах * (£(а)) для всех a e U,
то легко показать, что множество линейных преобразований само образует
конечномерное векторное пространство над F (имеющее размерность (dim U) х
(dimV)). Это векторное пространство обозначается Нот([/, V). Пусть t £
Hom([/, V), щ,... , u„ — базис пространства U, a vi,... , vm — базис
пространства V. Тогда t полностью определяется формулами, показывающими, как
компоненты (х[,... ,х'т) вектора £(х) относительно базиса vi,... ,vm
пространства V можно выразить через (х\,... ,хп), т.е. через компоненты вектора х
относительно базиса щ,... , u„ пространства U. Получим
х[ = ацХ1 + 0-12X2 + ■ ■ • + а.1пХп,
х'2 = ai2Xi + CL22X2 + • ■ ■ + 02пХп,
х'т = amlXi + ат2Х2 + ■ ■ ■ + атпХп, ttij е F,
2.6. Векторы и матрицы
35
гЬгЬициенты ац определяют гомоморфизм t однозначно по отношению
Г,Де *б анным базисам. Прямоугольный массив коэффициентов
( «и а12 ... а\п \
U21 ^21 ■ • • а2п
\ «ml «m2 ■ - - «ттгп /
вается матрицей А, имеющей т строк и п столбцов. Более абстрактно
можно представлять себе матрицу как функцию
A:{l,2,...,m}x{l,2,...,n}-+F.
Матрицу А часто сокращенно записывают в виде (ау), и чтобы показать, что
она представляет линейное преобразование t, мы будем писать (t) = (а^).
Матрица соответствующая нулевому отображению t: х —» 0 (для любого х €.[/),
называется нулевой матрицей и обозначается знаком 0.
Сумма двух (т х п)-матриц А — (ау) и Б = F^) определяется как (m x п)-
матрица
А + Б = (ay +bij).
Произведение (т х п)-матрицы А = (а^) на скаляр А определяется как (m x п)-
матрица А * А — (А * ац). С этими определениями множество всех (т х п)-
матриц с коэффициентами из поля F становится векторным пространством
с размерностью т *п, изоморфным Нот(Е/, V). Мы определим произведение
С = А * В двух матриц А (а^ является (т х п)-матрицей) и В (bjk является
(n x р)-матрицей) как (т х р)-матрицу (с^), где
Cik
Y^aHbi
i=i
1акое определение мотивировано потребностью образовывать композицию
линейных преобразований sot e Eom(U,W), если даны преобразования t e
tiom(U,V), соответствующее матрице В, и s e Нот(У, W), соответствующее
матрице А. Матричное произведение А * В матриц А и Б не определено, если
олько число столбцов матрицы А не равно числу строк матрицы В (равно-
У Размерности пространства V). Существование произведения А * В поэтому
означает существования произведения В * А. С так определенным умно-
нием множество квадратных матриц порядка п, т. е. матриц с п строками
столбцами, и с коэффициентами из кольца К, образует некоммутативное
кольцо (если п > 1 и К ф {0}), обозначаемое Мп{К). Матрица А е Мп(К) на-
ется невырожденной или обратимой, если существует матрица В, такая,
что
А*В = В*А = 1п
36 Глава 2. Математические основы компьютерной алгебры
где 1П является тождественной матрицей (называемой также единичной
матрицей) порядка п, коэффициенты которой равны <5у (дельта-символу Кроне-
кера). Последний определяется как
5■■ ■= / *' если* = -?'
*•* (^ 0 в противном случае.
Матрица Б тогда определяется единственным образом и называется матрицей
обратной к матрице А; она обозначается символом А-1. Если в МП(К) нет
такой матрицы Б, для которой А * Б = Б * А = 1„, то матрица А называется
вырожденной или необратимой в МП(К). Линейное отображение С/ —> V
будет являться изоморфизмом векторных пространств в том и только том случае,
когда его можно представить некой обратимой (квадратной) матрицей.
Матрица, транспонированная по отношению к (т х п)-матрице А = (<%•),
определяется как (п х т)-матрица (а„), полученная из А заменой ее строк ее столбцами.
Матрица, транспонированная к матрице А, обозначается Ат. Если А = Ат, то
матрица А называется симметричной.
Для любого п > 0 множество невырожденных (n x п)-матриц над полем
F образует мультипликативную группу, называемую общей линейной группой
GL{n, F). Элементы группы GL(n, F), имеющие определитель 1, образуют
подгруппу, обозначаемую SL(n, F) и называемую специальной линейной группой.
Матрица размера тх 1, у которой есть только один столбец, называется вектор-
столбцом, а матрица размером 1 хп называется вектор-строкой. Уравнения,
определяющие гомоморфизм t с матрицей А, можно поэтому записать в виде
х' = А * х,
где х' — это вектор-столбец, т элементов которого суть компоненты
отображения i(x) относительно vi,... , vm, а х — вектор-столбец из п элементов,
являющихся компонентами вектора х относительно ui,... ,u„.
В частности, A хп)-матрица описывает гомоморфизм из n-мерного
векторного пространства U с базисом ui,... , un в одномерное векторное
пространство V с базисом vi. Множество всех A х п)-матриц образует векторное
пространство, изоморфное векторному пространству Нот (С/, F) всех
гомоморфизмов, отображающих U на его основное поле F, которое можно рассматривать
как векторное пространство размерности 1 над самим собой. В общем
случае, если V — произвольное векторное пространство над полем F, то
векторное пространство ¥Lom(V, F) называется сопряженным к V пространством и
обозначается V* или V. Элементы Hom(I^, F) называются линейными
функционалами. Можно показать, что любое конечномерное векторное пространство
изоморфно сопряженному к нему.
Пример. След квадратной матрицы — это сумма ее диагональных элементов.
Поэтому след квадратной матрицы является линейным функционалом. Это
означает, что
ti{A + B) =tr(A)+tr(£),
tr(c* A) = c*tr(A).
2.7. Кватернионы
37
анных подпространств S и Т векторного пространства V определим
Для ДВУХ д
Л 5 + T:={a: + i/|a;65,y6T}.
с + Г является подпространством пространства V. Мы будем называть
Тогда а СуММОй S и Т (записывается 5 Ф Т) тогда и только тогда, когда
Vn^„j>H5nT = {0}. 5 и Т называются тогда прямыми слагаемыми
"~ янства V- Всякое подпространство S конечномерного векторного про-
П^° рткя V является прямым слагаемым для V. Кроме того, если V = S © Т,
странствл
то
dimT = dimF — dimS.
р пи 5 и Г — любые конечномерные подпространства векторного пространства
V,to
dimS + dimT = dimE П Т) + dimE + Т).
2.7. Кватернионы
Подходящим образом определив умножение на R x R, можно построить поле
С являющееся расширением поля R. В самом деле, поскольку С — это
векторное пространство размерности 2 над R, то С есть коммутативная алгебра
с тождественным элементом над R. Естественно попытаться повторить этот
процесс и попробовать погрузить С в некую алгебру, определенную над R"
(п > 2). На самом деле невозможно найти такое расширение, которое
удовлетворяло бы аксиомам поля, но, как показывает приведенное ниже построение,
некоторого успеха достичь удается. Рассмотрим ассоциативную алгебру ранга
4 с базисными элементами
1, I, J, К,
гДе 1 — тождественный элемент, т. е.
1*J = J, 1*J=J, \*К = К.
Попарные произведения этих элементов суть
I*I=J*J=K*K=-1, I*J = K, J*K = I,
K*I=J, J*I=-K, K*J=-I, I*K = -J.
To так называемая алгебра кватернионов. Умножение, определенное выше,
чевидным образом некоммутативно, поэтому полученная структура не может
ть полем. Это кольцо с делением и есть алгебра кватернионов. Она
ассоциативна.
Шбой кватернион q можно представить в виде
Q := aj * 1 + a/ * I + aj * J + ак * К, где ai, ai,aj,ax g F.
ма, разность, произведение и частное двух кватернионов
q := а\ * 1 + а/ * / + aj * J + ац * К,
р := Ь\ * 1 + bi * I + bj * J + Ьк * К
38 Глава 2. Математические основы компьютерной алгебры
определяются следующими формулами:
q + р :- (ai + 6j) * 1 + (a/ + bi) * I + (aj + bj) * J + (aK + bK) * K,
q - p — (ai - bt) * 1 + (ai - bi) * I + (aj - bj) * J + (aK - bK) * K,
q * p := (ai * 1 + ai * I + aj * J + ак * K) * (bi * 1 + bi * I + bj * J + bK(K)
= (ai * b\ — ai * bi — aj * bj — ак * Ьк) * 1
+ (ai * bi + ai * bi + aj * bK - ак * bj) * I
+ (ai * bj + aj * b\ + ак * bi — ai * Ьк) * J
+ (ai *Ьк + ак *b\ + aj *bj - aj * bj) * K.
q/p := q *p_1, где р-1 является элементом, обратным к р.
Элемент, обратный q относительно сложения, — это
—q — — а\ * 1 — aj * I — aj * J — ак * К.
Элемент, сопряженный q, обозначаемый, скажем, q*, определяется так:
q* := а\ *1 — ai * I — aj * J — ак * К.
Величина, обратная q, — это
Абсолютная величина q — это
I |2 2,2,2,2
\q\ - a1+aI + aj + aK-
Нормированное значение q определяется как q/\q\-
Матричное представление кватернионов дается соответствиями
п / 1 О Л ,
1
0
0
1
0
1
1
0
I -¥ —i\ -, г, = -iax, К -» —i = ia
0
i
1
0
—i
0
0
-1
где i :— y —1, a crx,ay,az суть спиновые матрицы Паули. Это также
показывает, что алгебра кватернионов ассоциативна.
Алгебру кватернионов можно получить и другим способом — как подкольцо
кольца Mi(R), состоящее из матриц вида
( х -у -z -t \
у х —t z
z t х —у
\ t -z у х )
2.8. Многочлены
В этом разделе мы введем многочлены. Доказательства использованных здесь
теорем можно найти в литературе [3], [21], [29]. Функции вида
1 3
l + 2*z + 3*z2, х + х5, - -4*х2 + - *х10
о 2
2.8. Многочлены
39
многочленами от х. Коэффициенты в этих примерах — целые и
называют числа. В обычном анализе область значений, принимаемых пе-
радяонал бласть определения функции), есть R. В алгебре в качестве обла-
ременнои ^ многочленов берется С. Рассмотрим, к примеру, многочлен
сТи опр ^ реШением уравнения р(х) = 0 служит ±г. Любой многочлен от х
Р ~~ читать отображением множества 5 (области определения) на множе-
можн -ласть значений) рассматриваемого многочлена. Возьмем, к примеру,
СТВ eHi + y/2*x-3*x2. Если 5 = Z, то Т с R, и то же самое справедливо,
много ^ ^^ 5 = R; если 5 = С, то Т с С. Два многочлена от х равны,
6СЛИ они совпадают по форме. Например, а + Ь* х — с + d * х тогда и только
тогда, когда а = с и b = d. ^
Пусть Д —кольцо, и пусть ж, называемый свободной переменной, является
бым символом, не принадлежащим R. Многочленом от х над R мы будем
называть произвольное выражение вида
а(х) = а0 * х° + аг * х1 + а2 * х2 + ... = ^ ак * хк, ак € R,
к=0
где лишь конечное число ак отлично от z, т. е. от нулевого элемента кольца R.
Два многочлена от х над R, а именно определенный выше а(х) и многочлен
/3(х) = bo * х° + Ьг * х1 + Ь2 * х2 + ... = ^bk * хк, Ьк€ R,
к=0
равны (записывается а(х) = C(х)), если ак = Ьк для всех значений к.
В любом многочлене, вроде а(х), каждый из составляющих его
компонентов ao*x°,ai *х1 ,а2 *х2,... называется членом; в любом члене, вроде щ *хг,щ
называется коэффициентом при данном члене. Члены многочленов а(х) и /3(х)
были выписаны в заданном (но естественном) порядке. Индекс г,
являющийся верхним индексом при х, — это просто указатель положения члена щ * х1 в
данном многочлене. Подобным образом запись aj перед хг в члене а^хг не
следует понимать как указание на умножение, а знаки плюс между членами
следует воспринимать просто как полезные связки, а не как операторы. Пусть z —
нулевой элемент кольца. Если в произвольном многочлене, например в а(х),
коэффициент апф z, тогда как коэффициенты при всех последующих членах
суть z, то мы будем говорить, что многочлен а(х) имеет степень п, и
называть ап его старшим коэффициентом. В частности, многочлен ао * х° + z * х1 +
z * х + ... имеет степень нуль и старший коэффициент ао, когда ао ф z, и он
е имеет никакой степени (и никакого старшего коэффициента), когда ао = z.
Обозначим символом R[x] множество всех многочленов от х над R и для
роизвольных а(х),р(х) е R[x] определим сложение (+) и умножение * на R[x]
Формулами
а{х) + р(х) := (а0 + Ь0) * х° + (сц + h) * х1 + (а2 + Ь2) * х2 + ...
](ак +Ьк)*хк,
/ X"
fc=0
а{х) * Р(х) := а0 * bo * х° + (а0 * Ьх + ах * Ь0) * х1
2 , _ \"^ „ , „к
+ (а0 *b2 + ai *bi + а2 *Ь0) * х +...= > ск * х
к=0
40 Глава 2. Математические основы компьютерной алгебры
где
к
Ск ~^ai*bk~i-
»=о
Сумма и произведение элементов R[x] являются элементами R[x], и имеется
лишь конечное число членов с ненулевыми коэффициентами из R. Сложение
в R[x] ассоциативно и коммутативно, и определенное выше умножение
ассоциативно и дистрибутивно относительно сложения. Кроме того, нулевой
многочлен
z*x° + z*x1 + z*x2 + ... = ^2z*xh g R[x]
fc=0
является аддитивным тождественным элементом, или нулевым элементом
множества Щх], тогда как многочлен
-а(х) - -а0 * х° + (-at) * х1 + (-а2) * х2 + ... = £)(-а*) * хк <Е Д[ж]
к=й
является обратным элементом для а(х) относительно сложения. Итак,
справедлива
Теорема. Множество всех многочленов R[x] от х над кольцом R является
кольцом относительно определенных выше сложения и умножения.
Пусть а(х) и C(х) имеют степени тип соответственно. Если т ф п, то
степень а(х)+C(х) равна большему из чисел т, щ если т = п, то степень а(х)+
/3(х) не превосходит т. Степень а[х) * /?(ж) не превосходит т + п, поскольку
a-mbn может оказаться равным z. Однако если в R нет делителей нуля, то
степень произведения равна т + п.
Рассмотрим теперь подмножество S := {г * ха : г € R} кольца Щх],
состоящее из нулевого многочлена и всех многочленов степени нуль. Отображение
R -> 5 : г -» г *х°
является изоморфизмом. Поэтому мы можем в дальнейшем писать ао вместо
а0 * х° в любом многочлене а(х) £ R[x].
Пусть R — кольцо с единицей и. Тогда и — и * х° является единицей кольца
Щх], поскольку их0 * а(х) = а(х) для любого а(х) е R[x]. Кроме того, записав
х — и-х-х1 = z*x°+u*x1, имеем х £ R[x]. Далее, ак (х-х-х- до к сомножителей) =
а/.*хк S R[x], так что в выражении а(х) = ао + a,i * х + а^ * х2 + ... мы можем
рассматривать верхний индекс г в хг как подлинный показатель степени, знак
* в любом члене щ *хг как символ умножения в кольце многочленов, а связку +
как символ сложения в кольце многочленов. Любой многочлен а(х) степени т
над R со старшим коэффициентом и, т. е. с единицей кольца R, будем называть
нормированным.
Теорема. Пусть R — кольцо с единицей и, и пусть а(х) — ao+ai*x+.. .+am*
хт € R[x] является либо нулевым многочленом, либо многочленом степени
т, а /3(х) = ba + bi *x+.. . + bn*xn g R[x] — это нормированный многочлен
степени п. Тогда существует единственная четверка многочленов дв.(х), rR{x),
2.8. Многочлены
41
/r\ g R[x], где tr{x), ri(x) суть либо нулевые многочлены, либо име-
qi^lmeneHU < n, такая, что
(i) a(x) =qR(x)*/3(x)+rR(x),
(ii) а(х) = P(x)*qL(x)+rL(x).
("\ теоремы мы утверждаем, что а(х) был поделен справа на /3(х), в резуль-
П тот получились правое частное qn{x) и правый остаток tr{x). Аналогич-
(и) мы утверждаем, что а(х) был поделен слева на р{х), и получились
тате
но
левое
частное qb(x) и левый остаток ri(x). Если гд(ж) = z (соответственно
С ч = z) мы называем /3(ж) правым (соответственно левым) делителем
многочлена а (ж).
Рассмотрим теперь коммутативные кольца многочленов с единицей. Пусть
р _ коммутативное кольцо с единицей. Тогда R[x] — коммутативное кольцо с
рлиницей, и эту теорему можно переформулировать, не различая левые и
правые частные (заменив qn(x) = qb{x) на q(x)), левые и правые остатки (заменив
rL(x) — tr{x) на г(х)) и левые и правые делители. Тогда пп. (i) и (ii) теоремы
можно заменить на
(ш) а(х) — q(x) * E{х) + г(х)
и, в частности, мы получаем следущее утверждение.
Теорема. В коммутативном кольце многочленов с единицей многочлен а(х)
степени т имеет х — Ь в качестве делителя тогда и только тогда, когда
остаток
г = а0 + а2 * Ь + <22 * Ь2 + ... + ат * хт — z.
Если г = z, то Ь называется нулем (корнем) многочлена а(х).
Если в R нет делителей нуля, их нет и в R[x]. В самом деле, допустим, что
а(х) и /3(ж) суть элементы кольца R[x] степеней тип соответственно и что
а(х) * Р(х) = а0 * Ъ0 + (а0 * h + ах * b0)x + ... + ат * bnxm+n = z.
J-Огда каждый коэффициент этого произведения, и в частности ат * Ьп, равен
2- Но R не содержит делителей нуля; поэтому ambn = z тогда и только тогда,
когда ат = z или Ьп = z. Поскольку это противоречит допущению, что степени
многочленов а(х) и C(х) равны тип соответственно, то R[x] —это кольцо без
Делителей нуля.
орема. Кольцо многочленов R[x] является областью целостности тогда
олько тогда, когда кольцо коэффициентов R является областью целост-
Рассмотрение остатка
г = а0 + а2 * Ь + а2 * Ь2 + ... + ат * Ьт
ьщает, что этот результат можно получить автоматически, повсюду в а(х)
ив х на Ъ и, разумеется, интерпретируя знак * как умножение в R. Так,
Ределению считая /(b) выражением, полученным подстановкой Ь вместо
42 Глава 2. Математические основы компьютерной алгебры
х повсюду в f(x), мы можем вместо г взять а(Ь). Это обычный процесс
подстановки в элементарной алгебре, где х рассматривается как аргумент, а не как
свободная переменная. Для данного Ъ £ R отображение
/(ж) -» /(&) для всех /(ж) £ R[x]
является гомоморфизмом R[x] на R.
Самые важные алгебры многочленов — те, в которых роль кольца
коэффициентов играет некое поле F. Всякий ненулевой элемент поля F —это единица
поля F. Для области целостности F[x] важнейшие результаты таковы:
Алгоритм деления. Если а(х), C(х) £ F[x], где /3(х) ф z, то существуют
и единственным образом определены многочлены q(x), r(x), где г(х) является
либо нулевым многочленом, либо имеет меньшую степень, чем /3(х), такие, что
а(х) = q(x) * fi(x) + r(x).
Если г(х) —нулевой многочлен, то /3(х) называется делителем многочлена
а(х), и мы пишем /3(х)\а(х).
Теорема об остатке. Если а(х), х — Ь £ F[x], mo остаток от деления а(х)
на х — Ъ равен а(Ь).
Теорема о множителе. Если а(х) £ F[x] и Ъ £ F, то х — Ь является
множителем многочлена а(х) тогда и только тогда, когда a(b) = z; другими
словами, х — Ъ является множителем многочлена а(х) тогда и только
тогда, когда Ъ является нулем многочлена а(х). Это приводит к следующей
теореме.
Теорема. Пусть многочлен а(х) £ F[x] имеет степень т > 0 и старший
коэффициент а. Если различные элементы &i, &2, - ■ - ,Ьт поля F являются
нулями многочлена а(х), то
а(х) = а * (х — bi) * (х - b2) * ■. ■ * (х - Ьт).
Теорема. Всякий многочлен а(х) £ F[x] степени т > 0 имеет в поле F
самое большее т различных корней.
Теорема. Пусть а(х), /3(х) £ F[x] таковы, что a(s) — C(s) для всех s £ F.
Тогда если число элементов в F превышает степени обоих многочленов а(х)
и @(х), то а{х) = /3(х).
В алгебре многочленов F[x] единственными единицами служат ненулевые
элементы (т. е. единицы) кольца коэффициентов F. Поэтому единственными
ассоциированными элементами многочлена а(х) £ F[x] служат элементы вида
v * а(х) области целостности F[x], где г; — произвольная единица кольца F.
Поскольку для любого v ф z £ F ж любого многочлена а(х) £ F[x]
а(х) — v~l * а(х) * v,
и всякий раз, когда а(х) = q(x) * /3(х),
а(х) = (v * q(x)) * (v * fi(x)),
2.8. Многочлены
43
со
отсюда
следует, что (а) каждая единица кольца F и каждый ассоцииро-
- Д" мент многочлена а(х) является делителем этого многочлена и что
занныи \\а(х), то любой ассоциированный элемент многочлена /3(ж) так-
(р) если р ителем а(х). Единицы кольца F и ассоциированные элементы
*6 ЯВ ваются тривиальными делителями а(х). Все прочие делители а(х),
Н вые существуют, называются нетривиальными делителями. Много-
еслй . , степени m > 1 называется простым (неприводимым)
многочлен о(Х) с ; i J
над f, если все его делители тривиальны.
ЧЛ6Т перь рассмотрим область целостности многочленов С [ж]. Рассмотрим
произвольный многочлен
/3(ж) =b0 + bi*x + b2*x2 + ... + Ьт*хт б С[х]
пени го > 1- Приведем несколько элементарных теорем, связанных с нулями
ких многочленов и, в частности, с подмножеством всех многочленов из С [ж],
коэффициенты которых являются рациональными числами. Пусть г 6 С есть
нуль многочлена /3(ж), т.е. /?(г) = 0, и поскольку б € С, то верно также
l-1 * в(г) = 0. Поэтому нули многочлена /3(ж) в точности совпадают с нулями
ассоциированного с ним нормированного многочлена
а(ж) = б * /3{х) =а0+а1*х + а2*х2 + ... + am_i * xm_1 + хт.
При m = 1 многочлен
а (ж) = ао + ж
имеет нулем — ао, а при m = 2 многочлен
а(х) = ао + ахж + ж2
имеет два нуля:
2 ( ~ai - уа2 - 4a0 J , 2 ( _Gl + V a* ~ 4a°
У каждого многочлена ж" - a € С [ж] имеется п нулей над С. Существуют
формулы, дающие нули всех многочленов степеней 3 и 4. Известно также,
что невозможно предложить формулу для нулей произвольных многочленов
степеней т > 5.
Любой многочлен а(х) степени т > 1 может иметь не больше т различных
улей. Многочлен а(х) = ао + а\ * ж + ж2 будет иметь два разных нуля тогда
и только тогда, когда дискриминант а2 - 4 * ао ф 0. Тогда каждый из них
взывается простым нулем многочлена а(х). Однако если а{ - 4 * а0 = 0, то
приведенные выше формулы дадут в качестве нуля — | * а\. Тогда мы
вем — - * ai нулем кратности два многочлена а (ж) и выпишем эти нули в
виде -I *п, 1 ..
Ная теорема алгебры. Всякий многочлен а(х) 6 С[ж1 степени т > 1
еъ ™ крайней мере один нуль.
рема. Всякий многочлен а (ж) € С[ж1 степени т > 1 имеет в С в
точности т и ~~
"* нулей, если каждый нуль кратности п считать за п из этих т
пулеи.
44 Глава 2. Математические основы компьютерной алгебры
Теорема. Всякий многочлен а(х) € С[х] степени т > 1 либо имеет степей
1, либо может быть записан как произведение многочленов £ С[х], каэкды'-
из которых имеет степень 1.
Теперь мы изучим некоторые подмножества С [ж], ограничив кольцо коэсЬ
фициентов. Во-первых, предположим, что у многочлена
а(х) = ао +ai *х + а,2*х2 + ... + ат*хт € R[x]
степени т > 1 есть нуль вида г = a + b*i, т.е. что
а(г) = а0 + а\ * г + а2 * г2 + ... + ат * rm = s + t * г = 0.
Тогда получим
а(¥) = ао + ai *¥ + а2 *¥ + ... + ат *7 =s + t*i = 0,
откуда вытекает следующая
Теорема. Если г € С является нулем произвольного многочлена а (ж) с
вещественными коэффициентами, то г также является нулем а(х).
Пусть г — а + Ъ * г, где Ь ф 0, является нулем многочлена а(х). Тогда
¥ = а — b*i также является нулем, и мы можем записать
а(х) = [х — (а + Ь * г)] [х — (а — Ъ * г)] * Qi (ж)
= [ж2 - 2 * а * х + а2 + б2] * а\ (ж),
где а\ (х) — многочлен с вещественными коэффициентами, степень которого
меньше на два, чем степень многочлена а(х). Поскольку квадратичный
многочлен с вещественными коэффициентами имеет мнимые нули тогда и только
тогда, когда его дискриминант отрицателен, то справедливы следующие
утверждения.
Теорема. Многочлены первой степени и квадратичные многочлены с
отрицательным дискриминантом суть единственные многочлены из И[х],
неприводимые над R.
Теорема. Многочлен нечетной степени из R[x] обязательно имеет
вещественный нуль.
Предположим, что
Р(х) = bo + Ъх * х + Ъ2 * х2 + ... + Ът * хт е Q[x].
Пусть с —наибольший общий делитель числителей коэффициентов bi, a d —
наименьшее общее кратное их знаменателей; тогда многочлен
а(х) - - * /3(х) = а0 + а\ * х + а2 * х2 + ... + ат * хт е Q\x]
с
имеет целые коэффициенты, единственными общими делителями которых
служат ±1, единицы кольца Z. Кроме того, нули многочленов а(х) и fi(x) в
точности совпадают. Если г £ Q является нулем многочлена а(х), т. е. если
а(г) = а0 + ах * г + а2 * г2 + ... + ат * гт = 0,
то получим:
2.8. Многочлены
45
n„rgZ, то r\ao,
(a) еСЛ „и т е простой несократимой дроби, то
(b) еслиг = 5/г' х-
f1 * a{s/t) =a0*tm + ax*s* t"^1 + a2 * s2 * im~2 + ...
+ am_i * s-1 * t + am * sm = 0,
„ что slao и *|am. Мы доказали следующее.
Пусть а(х) = ао + ai * х + а2 * х2 + ■ ■ ■ + ат* хт есть многочлен
™ > 1 с целыми коэффициентами. Если s/t € Q, где (s,i) = 1
степени т __ ->
вляется нулем многочлена а(х), то s\a0 ut\am.
Пусть а(х) и /3(ж) —ненулевые многочлены из F[x]. Многочлен d(x) e F[x\,
обладающий следующими свойствами:
(a) d{x) нормирован,
(b) d\x)\a{x) и d(x)\P(x),
(c) для любого с(х) е F[x], такого, что с(х)\а(х) и с(х)\/3(х), выполняется
c(x)\d(x),
называется наибольшим общим делителем многочленов а{х) и /3(х).
Наибольший общий делитель двух многочленов из F[x) можно найти тем
же способом, что и наибольший общий делитель двух целых чисел.
Теорема. Пусть ненулевые многочлены а{х) и /3(х) принадлежат F[x].
Нормированный многочлен
d(x) = s(x) * а(х) + t{x) */3(x), s(x),t(x) 6 F[x],
наименьшей степени является наибольшим общим делителем многочленов а(х)
яР(х).
Теорема. Пусть а(х) степени т > 2 и /3(ж) степени п > 2—многочлены
из F[x]. Тогда ненулевые многочлены /л(х) степени не больше п — 1 и v(x)
степени не больше т — 1, такие, что
ц(х) * а(х) + v(x) * /3(х) = z, где z — нулевой многочлен,
существуют, в F[x] тогда и только тогда, когда а(х) и /3(х) не являются
взаимно простыми.
еорема. Если а(х), /3(х), р(ж) е F[x], где а(х) и р(х) взаимно просты, то
из Р{х)\а(х) * р(х) следует, что р{х)\/3(х).
°рема о единственности разложения. Любой многочлен а(х) степени
~ i- со старшим коэффициентом а в F[x] может быть записан в виде
а(х) = a* bi(*)]ml * Ы*)Т2 * ■ • • * \Pii*)]mJ,
Pi(x) —нормированные простые многочлены над F, а тг- — положитель-
Целые числа. Кроме того, с точностью до перестановки сомножителей
Разложение единственно.
46 Глава 2. Математические основы компьютерной алгебры
2.9. Дифференцирование
Пусть / : I —> R есть некая функция, причем I — открытый интервал. Будб1у,
говорить, что / дифференцируема в точке a € I, если существует линейное
отображение L : R —> R, такое, что
Um /(о + е)-/(о)-Де) = 0_
Это линейное отображение, которое, если оно существует, является
единственным, называется тогда дифференциалом от / (или производной от / в точке а)
и обозначается daf. В обычных учебниках принято вводить дифференциалы
d/ и dx и получать соотношения вроде
d/ = —dx.
J dx
В современных обозначениях это соотношение имело бы вид
daf = f'(a)dzl,
где Х(= id) обозначает тождественное отображение х —^ х. Если /и j
дифференцируемы, из определения производной находим следующие правила
дифференцирования суммы, разности, произведения и отношения функций:
d ,, ч df dg
~7~\f + 9) = ~г" + ~г ~ производная суммы,
dx dx dx
d ,, , df dg
— if ~ 9) — Г" — производная разности,
dx dx dx
d , . . df T dg
~T~\J * 9) = 9 * ~,—r J * -г- — производная произведения,
dx dx dx
d
dx
(9-
9"
dt
dx
-f*
92
dx
g Ф 0 для x £ I — производная отношения
d n
и соотношение —с = 0, где с — постоянная.
2.10. Интегрирование
Система компьютерной алгебры должна быть способна осуществлять
формальное интегрирование элементарных функций, например
/
dx 1 fl+x
■In
1 - х2 2 V 1
Как правило, предполагается, что основное поле, в котором производятся
вычисления, — это R. Символьное дифференцирование было реализовано на
весьма ранних этапах развития систем компьютерной алгебры, тогда как
символьное интегрирование (называемое также формальным интегрированием) было
2.10. Интегрирование
47
и системы значительно позже. Причина состоит в серьезном разли-
,ведено формальным интегрированием и формальным
дифференцированней ме^5 г>енцирование — это алгоритмическая процедура, и знание производ-
,м. ДйфФ нЫХ функций и правил дифференцирования суммы,
произведена эл нИЯ и сложной функции позволяет дифференцировать любую дан-
10Я' °1. кцию. Единственная реальная трудность дифференцирования — это
!уЮ ФУ полуЧенного результата. Интегрирование, напротив, представляет-
Л1^° невольным набором специальных приемов и частных случаев. Здесь есть
;Я П^ два общих правила, а именно правило интегрирования суммы и прави-
ГаЛинтегрирования по частям. Если мы интегрируем сумму двух функций, то
10 fi ем случае мы интегрируем каждое слагаемое по отдельности, т. е.
/ (Л(«) + /2(ж)) da: = / /i(a;)da; + / f2(x)dx.
Что так называемое правило интегрирования суммы. Может случится так,
что для суммы /i + /2 имеется явная форма интеграла, но слагаемые Д и /г
яеинтегрируемы в явном виде. Например,
/
(хх + (\nx)xx)dx = хх.
Однако интегралы
!*•*,, /(ь^
не могут быть записаны в замкнутой форме. Правило интегрирования суммы
можно применять лишь тогда, когда известно, что два из этих трех интегралов
существуют. Для прочих комбинаций, кроме сложения (и вычитания), общих
правил не существует. Например, хотя мы знаем, как интегрировать ехр жиж2,
отсюда не следует, что мы можем проинтегрировать ехр(ж2). Для этой
функции не существует более простого выражения, чем fexpx2dx. Поэтому мы
заучиваем несколько «методов», скажем интегрирование по частям,
интегрирование подстановкой, интегрирование просмотром таблиц интегралов и так
Далее. Кроме того, мы не знаем, какой метод или какая комбинация методов
сработает для данного интеграла. Далее мы будем излагать материал, тесно
следуя работам Дэвенпорта и др. [11], Маккалума и Райта [33] и Риша [42].
Поскольку дифференцирование явно проще интегрирования, уместно
переформулировать задачу интегрирования как «обратную задачу» дифференци-
ания, т.е. если дана функция /, то вместо того, чтобы искать ее интеграл
MbI будем искать такую функцию д, для которой dg/dx — f.
Ределение. Если даны два класса функций А и В, то задача
интегрировала А и В состоит в том, чтобы найти алгоритм, который для любого
^ енто. f класса А либо дает элемент g класса В, такой, что f = dg/dx,
"оказывает, что в В нет такого элемента д, что f = dg/dx.
нал аПримеР> если А - Q(x) и в = Q(z). гДе Q(x) означает класс рацио-
нЫх функций, то ответом для 1/ж2 должно быть -1/х, тогда как для 1/х
48 Глава 2. Математические основы компьютерной алгебры
в этом множестве функций решения не существует. С другой стороны, е„
В = Q(x,lax), то ответом для 1/х должно быть In ж.
Рассмотрим теперь интегрирование рациональных функций. Будем пак
тать со случаем А — С(х), где С —это поле констант. Любая рациональна
функция / может быть записана в виде р + q/r, где р, q и г суть многочл
ны, q ж г взаимно просты и степень q меньше, чем степень г. "У многочлев-
р всегда имеется конечный интеграл, поэтому правило интегрирования сумм,
справедливо для fi(x) — p(x) и /2B;) = q(x)/r(x). Поэтому задача интегрнр0.
вания / сводится к задаче интегрирования р (которая очень проста) и к задаче
интегрирования подходящей рациональной функции q/r.
Наивный метод состоит в следующем: если многочлен г разлагается на
линейные множители, так что
п
г(х) = 1[(х-сцГ,
г=1
то мы можем разложить q/r на элементарные дроби
q(x) _ ул bj(x)
Г (ж) ~ f-f (Ж - di)ni '
где Ьг — многочлены степени меньше, чем щ. Эти многочлены можно разделить
на х — сц и получить следующее разложение:
I \ П ГЦ ,
q(x) v~^V^ ":
1,3
r(x) fr[jr[{x- ai)i '
где bij — константы. Это разложение можно проинтегрировать и получить
/С N\ ".
~~dx = J~] biyi log(a; - а»)
гч
Kl
Таким образом, мы доказали, что каждая рациональная функция имеет
интеграл, который можно выразить в виде суммы рациональной функции и
логарифмов рациональных функций с постоянными коэффициентами; это значит,
что такой интеграл принадлежит полю С(ж,1пж). При таком алгоритме
требуется полностью разложить на множители многочлен г, что не всегда
возможно без пополнения С некоторыми алгебраическими величинами.
Манипулировать такими алгебраическими расширениями часто бывает очень трудно. Даже
если алгебраические расширения не потребуются, очень трудоемко разлагать
на множители многочлен г высокой степени. Кроме того, данный алгоритм
требует громоздкого разложения на элементарные дроби.
В методе Эрмита мы определяем рациональную часть интеграла от
рациональной функции, не вводя никаких новых алгебраических величин.
Подобным образом, этот метод находит производную суммы логарифмов, которая
тоже является рациональной функцией с коэффициентами из того же поля.
Мы видели, что если некий множитель знаменателя г входит в него в степени
п, то в знаменатель интеграла этот множитель входит в степени п — 1.
Значит, разложение знаменателя не должно содержать квадратов сомножителей.
2.10. Интегрирование
49
положим, что существует свободное от квадратов разложение г
0оЭтомУ V { ^ОГда rt взаимно просты, и мы можем построить разложение
вИДа ильной функции на простые дроби:
ра ФО = д(х) = А 9<(ж)
о слагаемого правой части есть интеграл, и поэтому применимо пра-
у кажд вания суммы; достаточно проинтегрировать каждое слагаемое
посереди. Интегрирование дает
гЦх) Х \ rj-1 ) J т\~1
а и Ъ удовлетворяют соотношению ari + bdri/dx = 1. Следовательно, мы
огли уменьшить показатель степени множителя tv Продолжим делать это,
ка этот показатель не станет единицей, и тогда оставшийся интеграл станет
суммой логарифмов. Метод Эрмита очень подходит для вычислений вручную.
Его недостаток состоит в том, что он требует использования нескольких
дополнительных подпрограмм, а это существенно осложняет программирование.
Метод Горовица состоит в следующем: целью, как и прежде, является
возможность записать J(q(x)/r(x))dx в виде qi/n + j(q2/r2)dx, где оставшийся
интеграл после его взятия дает только сумму логарифмов. Мы знаем, что у
знаменателя Г\ те же самые множители, что и у г, только они входят в на
единицу меньших степенях, что в г2 нет повторяющихся множителей и что
все эти множители —те же, что в т. Получаем: т\ = НОД(г, <ir/cfa) и т2
делит тIНОJX(r,dr/dx). Мы можем полагать, что q2/r2 записано в приведенной
форме, и поэтому г2 = г/НОД(г, dr/dx). Тогда
q(x) _ d (qy\ 02 _ }^<ki_ _ (h_dri_ <?2
r(x) dx Vri/ r2 ri dx r\ dx r2
_ r2dqi/dx — qiS + q2r\
- ; >
где s = (r2dri/dx)/ri (здесь деление без остатка). Тогда получаем
dqi
q = r2— qis + q2rx,
dx
-Д q, s, г и r2 известны, a q% и q2 нужно отыскать. Поскольку степени много-
нов qx и q2 меньше, чем степени тип многочленов qi и q2 соответственно,
запишем
9i(х) = Yl aixl> q2^ = !>2bi
i=0 i=0
__ образом, уравнение для q можно переписать в виде системы m + п
Da НЫХ ^Равнении Для п + тп неизвестных. Более того, эту систему можно
за ть и свести интегрирование (по крайней мере для этой подзадачи) к
Даче Линейной алгебры.
50 Глава 2. Математические основы компьютерной алгебры
Опишем теперь метод логарифмических слагаемых. Два описанных вьт
метода позволяют свести интегрирование произвольной рациональной фуНк
ции к интегрированию некой рациональной функции (скажем, q/r), интегря
которой состоит лишь из суммы логарифмов. Мы знаем, что этот интегра
можно вычислить, полностью разложив на множители ее знаменатель, но эт
не всегда бывает необходимо для выражения результатов. На самом деле за
дача состоит в том, чтобы найти этот интеграл, не используя никаких других
алгебраических чисел, кроме тех, что нужны для выражения результата. Пусть
/
—— dx = У^Ъ log Vi (x)
r(x) t—1
4 ' %=\
— выражение для этого интеграла, в котором в правой части фигурирует
наименьшее возможное число алгебраических расширений. Коэффициенты а —
это константы, и в общем случае Vi — рациональные функции. Поскольку
ln(a/&) = In а — 1п&, мы можем без потери общности предположить, что Vi
являются многочленами. Далее мы можем выполнить разложение на множители
свободное от квадратов, что не добавляет никаких алгебраических
расширений, и применить тождество
г i
В силу тождества
с* ln(p* q) +d* ln(p*r) = (с + d) *lnp + c*\n.q + d*\nr
мы можем считать, что Vi взаимно просты, по-прежнему сохраняя
минимальность алгебраических расширений. Кроме того, мы можем считать, что все с,
различны. Дифференцируя наш интеграл, находим
q(x) _ ^ Ci dvj
г(х) •" vi dx
Из предположения о том, что v^ не содержат квадратов, следует, что ни одно
слагаемое в этой сумме не может быть упрощено, а из предположения о том,
что Vi взаимно просты, следует, что никакое сокращение этих дробей
невозможно. Отсюда вытекает, что и$ должны быть в точности множителями
многочлена г, т. е. что г(х) = П£=1 vi{x)- Обозначим щ = YijM vi- Затем мы можем
продифференцировать произведение vi, откуда получим г = J2iuidvi/dx. Эти
два выражения для q и для dr/dx позволяют вывести следующий результат:
vk(x) = НОД@,vk) = НОД iq-^2CiUidvi/dx,vk j
= НОД(д - ckukdvk/dx,vk),
поскольку все другие w, делятся на vk.
Теперь рассмотрим алгебраические решения дифференциального
уравнения первого порядка
-^ + f{x)y = g{x).
2.11- Коммутативность и некоммутативность
51
нас к алгоритму Риша. Мы ставим задачу найти такой алго-
дто ПривОД „ я данных / и с/, принадлежащих к классу функций А, либо
рйтМ, K°^v нкЦию 2/, принадлежащую к данному классу В, либо
доказывала^111 ^с # не содержит функций, удовлетворяющих данному уравнению.
еТ; что к рассмотрим случай, когда В всегда является классом функций,
Для пр относительно класса А. Чтобы решить это дифференциальное
Мнение, сделаем подстановку
у{х) = z(x) exp ( - / /(s)ds
Это приводит к решению
у(х) = ехр Г- J f{s)ds\ J (g(s) (exp J f(t)dt\ ds\ .
Вообще говоря, этот метод алгоритмически неудовлетворителен для
нахождения у, так как алгоритм интегрирования, описанный в последнем разделе,
сводит задачу нахождения этого интеграла к решению того самого
дифференциального уравнения, с которого мы начинали. Риш [42] нашел метод решения
таких уравнений для случая, когда А является полем рациональных
функций или алгебраическим расширением некого поля, над которым эта задача
разрешима. Данную задачу можно сформулировать следующим образом: для
двух данных рациональных функций / и д найти рациональную функцию у,
такую, что dy/dx + f(x)y = g(x), или доказать, что таковой не существует.
Функция / удовлетворяет условию, гласящему, что ехр (Jx f(s)ds) не
является рациональной функцией, и ее интеграл не является суммой логарифмов с
рациональными коэффициентами. Задача решается в два этапа: сведение ее к
чисто полиномиальной задаче и затем решение этой задачи. Алгоритм Риша
рекурсивен. Прежде чем применять его, нужно (в принципе) проверить, что
различные переменные, являющиеся алгебраическими расширениями,
алгебраически независимы. Для рациональных функций алгоритм Риша совпадает
с алгоритмом для метода Горовица. Дальнейшие подробности, касающиеся
алгоритма Риша и его расширений, можно найти в литературе [42], [33], [11].
•■11. Коммутативность и некоммутативность
мпьютерной алгебре обычно предполагается коммутативность символов.
ие математические структуры некоммутативны. Здесь мы обсудим неко-
Рйе из этих структур.
помним, что ассоциативная алгебра — это векторное пространство V над
в , которое удовлетворяет аксиомам кольца таким образом, что сложение
с * (л КОльЧе является сложением в векторном пространстве и выполняется
того * В^ ~ (с* А) * в = А*(с* в) №я всех А В е V и с G F. Кроме
•чеп ' СПравеДЛив закон ассоциативности, т. е. А * {В * С) = (А * В) * С. При-
Ссоциативной алгебры служит множество (n x п)-матриц над полем
52 Глава 2. Математические основы компьютерной алгебры
вещественных или комплексных чисел с матричным умножением в качег
операции *. Тогда в общем случае имеем 6?
А* В фВ*А.
Другой важный пример некоммутативной структуры — это алгебра Ли. Алг
ра Ли определяется следующим образом: любое векторное пространство L н
полем F с операцией L x L —)• L, определенной соотношением ^
О, у) -4 [х, у]
и называемой коммутатором векторов хяу, называется алгеброй Ли над полр
F, если оно удовлетворяет следующим аксиомам:
(L1) Операция взятия коммутатора билинейна.
(L2) [х, х] = 0 для всех х £ L.
(L3) [х, [у, z}] + [у, [z, x]] + [z, [x, у]] = О, x,y,z£ L.
Простой пример алгебры Ли — это векторное произведение в векторном
пространстве R3.
Замечание. Связь между ассоциативной алгеброй и алгеброй Ли такова:
пусть А, В —элементы некой ассоциативной алгебры. Определим коммутатор
следующим образом:
[А, В] := А * В - В * А.
Легко доказать, что так определенный коммутатор удовлетворяет
приведенным выше аксиомам. Таким образом, мы построили алгебру Ли из
ассоциативной алгебры.
Другой пример некоммутативной структуры — это кватернионы.
Кватернионы имеют матричное представление.
2.12. Тензорное произведение
и кронекерово произведение
Пусть V и W — векторные пространства над полем F. Определим тензорное
произведение [29] элементов V и W. Значение этого произведения должно
лежать в некотором векторном пространстве. Если обозначить через v ®w
тензорное произведение элементов v g V и w € W, то должны выполняться
следующие соотношения: если V\, v% £ V и w £ W, то
(v\ + V2) <8> w = vi ® w + V2 ® w.
Если «л, и>2 £ W и v £ V, то
V <g> (Wi + VI2) =V®W\ + V ®U>2-
Если с £ F, то
(c * v) ® w = с * (v ® w) = v ® (c * w).
Построим теперь такое произведение и докажем его разнообразные свойства-
Пусть U, V и ТУ — векторные пространства над полем F. Под билинейным
отображением
g-.VxW^U
Тензорное произведение и кронекерово произведение 53
бражение, КОТОр0е каждой паре элементов (v,w), где v £ V
аьг пониМае соответствие элемент g{v,w) пространства U', обладающее
w , хаг стави1 »
й ^ютими свойствами:
сл^ДУ 0 веу отображение w -» g(v,w) пространства ТУ в простран-
для ка ^ ^ ^ каждого w e W отображение v -» g(v,w) пространства
ство ^ ли „ ' „ w линейно. Доказательства нижеследующих теорем приво-
V в пространство
дятся в [29].
Пусть V, U — конечномерные векторные пространства нас полем
'•^е0? гтвиют конечномерное пространство Т над полем F u билинейное
F /аэкение V xW ->T, обозначаемое формулой
(v,w) H+ v С§> w,
довлетворяющие следующим свойствам.
1 Если U — векторное пространство над Fug: VxW^>U является
билинейным отображением, то существует единственное линейное
отображение
9.-.T-+U,
такое, что для всех пар (v,w), где v е V и w £ W, выполняется
g{v,w) = g*(v ® w).
2. Если {г>1,... ,vn} —это базис пространства V, a {w\,... ,wm} —базис
пространства W, то элементы
Vi®Wj, г = 1,... , п и j — 1,... , т
образуют базис пространства Т.
Пространство Т называется тензорным произведением пространств V и W
и обозначается V ® W. Его размерность задается формулой
dim(V ®W) = (dim У) (dim W).
■элемент v®w, ассоциированный с парой (v, w), также называется тензорным
произведением элементов v и w.
Насто нам будет нужно находить тензорное произведение более чем двух
пространств. Такое произведение ассоциативно.
орема. Пусть U, V, W —конечномерные векторные пространства над F.
оа существует единственный изоморфизм
U ® {V <g> W) -4 (U ® V) ® W,
"такой, что
для всех u£U,v£V uw£W.
теорема позволяет записывать без скобок тензорное произведение
йена с ИХ С0МН0Жителей. Поэтому если \\,... Vr суть векторные пространства
> мы можем образовать их тензорное произведение
Vi ® V2 ® ... ® Vr
54 Глава 2. Математические основы компьютерной алгебры
и тензорное произведение
Vi <g> V2
..<8>vr
элементов Vi € У». Приведенные выше теоремы дают общие полезные свойст
тензорного произведения.
Теперь введем кронекерово произведение двух матриц. Его можно рассма
ривать как реализацию тензорного произведения.
Определение. Пусть А есть (m x п)-матрица, а В есть (р х <7)-матрицй
Тогда кронекерово произведение матриц А и В —это [(тр) х (п<7)]-матриця
определенная формулой
А®В :=
f anB ai2B
U2\B CL22B
йщВ \
0-2nB
\ am\B am2B ... amnB j
Иногда кронекерово произведение называют также прямым произведением
или тензорным произведением. Очевидно, что
(А + В)®С = А®С + В®С,
где А и В — матрицы одного размера. Аналогично
А®(В + С)=А®В + А®С,
где В и С одного размера. Кроме того, имеем
(А®В)®С = А®(В + С).
Пример. Пусть
Л =
Тогда
А®В =
1-1
О
V о
-2
2
0
0
0
-3
0
-1
Мы видим, что А® В ф В <
Пример. Пусть
2 3
О 1
-1
1 J
А.
В =
О
-1
В®А-
-1
1
0
0
-2
0
0
0
-3
-1
-2
0
2
0
А
3
Ч
ei =
Тогда
ei ® ei
О
О
W
ei ®е2
1
О
W
е2 =
е2 <g> в! =
/0\
О
1
W
е2 ® е2 =
О
О
V1/
2.13. Внешнее произведение
55
г р-Л есть стандартный базис в R2. Мы видим, что
ттнО ЧТ0 Iе1'
ОчевиД > ^ ^ ei ^ ei g, е2; еа 0 ei) e2 ® е2}
стандартный базис в R4-
есть
о !3. Внешнее произведение
внешнее произведение (называемое также альтернированным
Введем ^^ грассмановыл* произведением). Пусть V — конечномерное
произве aHCTB0 над R, r — целое число > 1, а У(г) — множество всех
^тежей длины г элементов из V, т.е. V^ = VxVx,..xV. Элемент из
^Гтаким образом, есть кортеж (vb ... vr), где все v; G V. Пусть [/-другое
не'чномерное векторное пространство над R, и пусть f:VxVx...xV^U
К°Н мультилинейное отображение пространства V в U, линейное по каждой
компоненте. Иначе говоря, для каждого г = 1,... ,г имеем
/(Vl,... ,Vi + V-,... ,Vr) =/(v1;... ,Vj,... ,Vr)+/(Vl,... ,vj,... ,Vr),
/(VI, . • . , С * Vi, . . . , Vr) = С * /(Vb ... , Vr)
для всех Vj, v- G V и с € R.
Будем называть мультилинейное отображение / альтернированным, если
оно удовлетворяет условию
/(vb... ,vr)=0
всякий раз, когда два соседних компонента равны, т. е. всякий раз, когда
существует такой индекс j < г, что vj = Vj+i ■ Заметим, что условия, которым
удовлетворяют такие мультилинейные отображения, аналогичны свойствам
определителей. Следующая теорема охватывает альтернированные произведения
общего вида.
Теорема. Пусть V — конечномерное векторное пространство над F
размерности п, а г—целое число, 1 < г < п. Существуют конечномерное
пространство над F, обозначаемое ArV, и мультилинейное альтернированное
отображение уМ -+ дгУ, обозначаемое
(их,... , ur) 4UiA...Aur,
Удовлетворяющие следующим свойствам.
ели U — векторное пространство над Fug: уМ -» U есть мульти-
инеиное альтернированное отображение, то существует единственное
линейное отображение
g» : ЛГУ -> U,
а-кое, что для всех щ,... , ur G V выполняется
д(щ,... ,ur) = gt(ui Л ...Лиг).
ivi,... , vn} —это базис пространства V, то набор элементов
{v^ Л ... Л v»r}, 1 < ii < ... < ir < п,
яегпся базисом пространства ArV.
56 Глава 2. Математические основы компьютерной алгебры
Поэтому если {vi,... , v„} — базис пространства V, то каждый элемент лг1/
обладает единственным представлением в виде линейной комбинации
£
«1<...<гг
где суммирование производится по всем кортежам (ii,... ,ir) целых чисел о
1 до п, удовлетворяющих условию
i\ < ... < ir.
Можно также упростить обозначения, записав (г) = (ii,... ,ir). Тогда выпи-
санная выше сумма примет вид
п
^c^v^ Л ...Avj,-
(О
Введенная выше структура называется также грассмановой алгеброй.
Размерность векторного пространства ArV дается формулой
Пример. Рассмотрим векторное пространство R4 со стандартным базисом
еь е2, е3, е4.
Тогда базис для два-форм будет таков:
eiAe2, е:Ле3, e:Ae4, е2Ле3, е2Ле4, е3Ле4.
Базис для три-форм дается выражениями
eiAe2Ae3, е!Ле2Ле4, e:Ae3Ae4, е2Ле3Ле4.
Базис для четыре-форм состоит из одного-единственного элемента, а именно
ех Л ег Л е3 Л е4.
Определитель квадратной матрицы можно вычислить с помощью внешнего
произведения. Рассмотрим D х 4)-матрицу
( 1 2 5 2 \
3
О
7 J
Тогда
/1 \
О
1
Л
/2\
1
О
V3/
Л
/5\
2
1
Л
= 24е: Ле2 Ле3 Ле4,
/2\
3
О
где ei, e2, е3, в4 — стандартный базис пространства R4. Следовательно,
определитель этой матрицы равен 24. В гл. 8 мы приведем реализацию этого
(очевидно, что небыстрого) способа вычисления определителя.
2.13. Внешнее произведение
57
еперь некоторые практические методы вычисления определи-
рассмотри именЯемый метод обычно зависит от типа матрицы — явля-
теля матр^исловой или символьной.
еТся ли она ч
««еловая матрица
# чи" ^ матрица размера п х п. Определитель матрицы Л — это сумма
Пусть ^ элеМентов главной диагонали матрицы, полученной переста-
прои ^ ол5цов матрицы А, взятая по всем возможным перестановкам.
Н°ВК с которым каждое из таких слагаемых входит в эту сумму, являет-
тюжительным или отрицательным в зависимости от того, является ли
соответствующая перестановка четной или нечетной.
Очевидный случай — матрица, состоящая только из одного элемента, и
в этом случае мы определим
det (A) = an при п = 1.
На самом деле никто не вычисляет определители матриц большего
размера, чем 4x4, выписывая все перестановки столбцов и находя произведения
диагональных элементов. Более эффективный способ вычисления
определителей основан на следующих фактах:
— Добавление одной строки (или столбца) матрицы А, умноженной на
произвольное число, к другим ее строкам (или столбцам) не меняет
det A
— Если матрица В получена из матрицы А перестановкой двух ее строк
(или двух столбцов), то det В = — det А. Если две строки (или столбца)
матрицы А пропорциональны друг другу, то detA = 0.
Идея метода состоит в том, чтобы преобразовать матрицу А с помощью
двух описанных выше операций к треугольному виду, т. е. к такой форме,
в которой все элементы, расположенные ниже главной диагонали, равны
нулю. Из определения ясно, что определитель треугольной матрицы равен
произведению элементов главной диагонали.
• Символьная матрица
Определитель символьной матрицы лучше всего вычислять с помощью
метода Леверье.
-Характеристический многочлен (п х п)-матрицы А — это многочлен
степени п относительно А. Его можно записать в виде
Р{Х) = det(A7 - А) = А" - dA" - с2Хп~2 - ... - cn_iA - сп.
жим теперь метод Леверье нахождения коэффициентов с^ данного
очлена. Этот метод совершенно нечувствителен к индивидуальным
енностям матрицы А. Он имеет то дополнительное преимущество, что
Рица, обратная к А, если таковая существует, также вычисляется в про-
е 0пРеДеления коэффициентов Cj. Очевидно, что заодно мы находим
Мый определитель. Коэффициенты с; многочлена Р(А) находятся вы-
58 Глава 2. Математические основы компьютерной алгебры
числением следа каждой из матриц В\,В2,.-- ,ВП, построенных еле
щим образом. Положим В]=Аи вычислим сх = tr(Bi). Затем найд»у
B*=A(B*_1-cfc_i/), ck= (^\tv(Bk), t = 2,3,...,n.
Матрицу, обратную к невырожденной матрице А, можно найти из соот
шения °~
а определитель матрицы А равен с„, если п четно, и —с„, если п нечетно
Системы
омпьютерной алгебры
К'
3.1. Введение
ой главе мы рассмотрим некоторые системы компьютерной алгебры и опи-
их приложений. Ныне имеется много систем компьютерной алгебры,
к что мы сосредоточимся лишь на немногих из них. Системы REDUCE и
AXIOM основаны на языке Лисп. С другой стороны, MATHEMATICA, MAPLE
и MuPAD основаны на языке С. Кроме символьных вычислений, все эти
системы способны проводить числовые вычисления. Это не только интерактивные
среды, действующие в ответ на вводимые команды, но и языки
программирования.
REDUCE —одна из старейших систем компьютерной алгебры. Она
основана на Portable Standart Lisp и была разработана в конце 60-х Энтони Хирном
[18], [47], [49]. Она позволяет включать в систему программы, написанные на
языке Лисп.
MAPLE [10] —продукт, разработанный компанией Waterloo Maple Software.
Это система для математических вычислений — символьных, числовых и
графических.
AXIOM —это символьная, числовая и графическая система, разработанная
в компании IBM Исследовательским центром им. Томаса Дж. Уотсона. Она
предоставляет пользователю все базовые функции и алгебраические методы,
необходимые для создания компьютерной реализации сложных
математических объектов в точно таком виде, как это сделал бы математик. AXIOM
распространяется компанией The Numerical Algorithms Group Limited [24].
MATHEMATICA [62] — это продукт, разработанный компанией Wolfram
search, Inc. Это программная система общего назначения и язык, пригод-
е Для символьных, числовых и графических вычислений.
~~ символьно-вычислительная система компьютерной алгебры, раз-
танная отделом математики университета Paderborn [39]. Эта система поз-
т вам определять свои собственные типы данных.
мимо описанных выше систем компьютерной алгебры доступны и другие
Ревосходные системы:
г. З'^МА — продукт фирмы Macsyma, Inc. Она основана на языке Лисп.
и Реализуется тест Аслаксена для функций комплексной переменной.
дробное описание системы Macsyma можно найти в книге Дэвенпорта и
60
Глава 3. Системы компьютерной алгебры
• DERIVE —небольшая, но мощная система компьютерной алгебры фИп
Software Warehouse, Hawaii. Она также основана на Лиспе. В системе Dp •Ь'
для решения большого круга математических задач применяются фор^ Ve
из алгебры, тригонометрии, математического анализа и матричной алг к'
ры. Эта система содержит и графические возможности.
• MAGMA (фирма Computational Algebra Group, School for Mathematics
Statistics, University of Sydney) — это система для вычислений в компьюте
ной алгебре, в области геометрических и комбинаторных задач, таких
группы, кольца, алгебры, модули, графы и коды.
• MATHCAD — продукт фирмы MathSoft, Cambridge, Massachussetts, пред,
назначенный инженерам, научным работникам и исследователям для
обработки, проведения и документирования символьных и численных
вычислений.
Имеются также пакеты для специальных задач в таких областях, как
теория чисел, теория графов и алгебра. Интернет-сайт фирмы International School
for Scientific Computing (см. предисловие) позволяет выйти на другие сайты
где можно найти дополнительную информацию о пакетах компьютерной
алгебры.
3.2. REDUCE
3.2.1. Основные операции
Мы приведем краткое описание команд, чаще всего используемых в системе
REDUCE. REDUCE не различает заглавные и строчные буквы. Так, записи
sin(x), Sin(x) и SIN(X) означают одну и ту же команду.
Команды, чаще всего используемые в настоящей книге, — это
дифференцирование и интегрирование. Чтобы продифференцировать х3 +2х по х, запишем
df(х**3+2*х,х);
На выходе получим 3*х~2 + 2. Чтобы проинтегрировать х2 + 1, пишем
int(x**2 + 1,х);
Выходом служит х*(х~2 + 3)/3. И **, и " обозначают операцию возведения
в степень.
Команда solve () решает ряд алгебраических уравнений, систем
алгебраических уравнений и трансцендентных уравнений. Например, команда
solve(х**2 + (а+1)*х + а=0,х);
выдает решения х = -1их = -а.
Другая важная команда —это команда подстановки sub(). Например,
команда
sub(x=2,х*у+х**2);
выдает результат 2*у + 4.
Среди прочих REDUCE включает следующие математические функция:
sqrt(x) (квадратный корень, у/х), ехр(х) (показательная функция, ехр(ж))>
3.2. REDUCE
61
альный логарифм, 1п(ж)) и тригонометрические функции sin(x),
с аргументами, выраженными в радианах.
coS(x);tatl ogpUCE зарезервированы выражения i (или I) для представ-
g сйсте1^ ^^ р^ ^ р^ ддя чисда ^ ТаК; вшд
ления V
дает-1,аВВ0Д
sin(pi);
к получению результата 0. Другие предопределенные константы —
привед ^ infinity, где Т означает «истина», a nil— «ложь».
ЭТ°тт' дифференцирования REDUCE предлагает два способа реализации.
6 ав первый способ, мы объявляем функцию, которую нужно продиффе-
повать как operator. В следующем примере показано, как пользоваться
Рбим способом. Цифра 2 в df () указывает, что мы дифференцируем дважды.
operator f\
f(x) := х*х + sin(x);
result := df(f(x),x,2);
На выходе получаем
result := -sin(x) + 2
С другой стороны, мы также можем объявить, что f зависит от х, и затем
продифференцировать f по х.
depend f, x;
f := x*x + sin(x);
result := df(f(x),x,2);
В системе REDUCE тип цифровых данных по умолчанию — это рациональные
числа. Команда
2 + 0.1 + 1/3;
Даст на выходе
73/30
Р ключатель on rounded позволяет проводить вычисления с вещественными
Ослами. Команды
°Ь rounded;
2 + 0.1 + 1/3;
да>АУт на выходе
2-43333333333
Reduce
известны лишь самые элементарные тождества, вроде
sin!"; = C0S(X>
loS(e) = 1
e~(i*pi/2) = ±
62
Глава 3. Системы компьютерной алгебры
Пользователи могут, применяя команду LET, добавлять другие правила v
щения выражений, вроде тригонометрического тождества ^°~
for all x let sin(x)~2 + cos(x)~2 = 1;
тогда ввод выражения
sin(y) + cos(y) - 5;
даст на выходе
-4
Другие команды можно найти в руководстве для пользователей REDUCE [171
3.2.2. Пример
В качестве примера покажем, как из псевдосферических поверхностей можно
вывести солитонные уравнения. Мы покажем вывод уравнения синус Гордона
Обобщения на другие солитонные уравнения очевидны. Солитонные
уравнения можно описать посредством псевдосферических поверхностей, т. е.
поверхностей с постоянной отрицательной гауссовой кривизной. Примером служит
уравнение синус Гордона
д2и .
——— = sin(u).
ОХ\ОХ2
Покажем, как можно использовать систему REDUCE для вывода уравнения
синус Гордона из элемента длины такой поверхности. Поле метрического
тензора задается выражением
g = dxi ® &х\ + cos(u(xi,X2))dxi ® dx2
+ cos(u(xi,x2))dx2 <8> dxi + dx2 <8> cfo2,
т. е. элемент длины есть
\ 2 / ч 2 /\2
ds\ I dx\ \ , dxi dx2 (dx2
dxj \dxj +2соз{и^Х2)ЖЖ + \л\
Здесь и — это гладкая функция переменных Xi их2. Сначала нужно вычислить
скалярную риманову кривизну R из д. Тогда мы получим уравнение синус
Гордона, наложив условие
Д = -2.
Вычисление скалярной кривизны хорошо описано во многих учебниках (см.,
например, [49]). Для полноты приведем соответствующие формулы. Имеем
911 = 922 = 1, #12 = 321 = COS(u(xi,X2)).
Метрический тензор д можно записать в матричной форме
, _ ( 9п 912
9
' 921 922
3.2. REDUCE
63
ина обратная д, задается выражением
Тогда велич , ^ ^ ^
9 ~{д21 д22
у у snru sinu
ычислим символы Кристоффеля. Они определяются как
1 тп '~~ ~п9 [9bm,n i~ 9bn,m 9mn,b)i
льзовано правило суммирования по повторяющимся индексам и
_ ддът _ ддът
9Ьт,1 '■— ~а > 9Ьт,2 '■— „ ^2-
Ч тем нам нужно вычислить тензор римановой кривизны, который дается
выражением
туг рг рг I рг рп рг рп
Отсюда выводится тензор Риччи
п . па _ туа
^mq ■— ^maq ~ llmqal
т.е. тензор Риччи строится посредством свертки. Из Rnq мы получим Д™ с
помощью соотношения R™ := gmnRnq. Наконец, скалярная кривизна R дается
формулой R := Д™. С помощью выписанного выше поля метрического тензора
находим, что
2 д2и
R =
sinu дх\д%2
Если R = —2, то получаем уравнение синус Гордона.
Мы применяем концепцию операторов. Операторы — наиболее общий тип
объектов, доступных в системе REDUCE. Обычно они содержат параметры, и
их можно параметризовать совершенно произвольным образом. В объявлении
ператора описывается лишь идентификатор оператора. Число параметров не
является. Операторы представляют математические операции или функ-
• Мы объявляем и как оператор, и он зависит от х. Сам параметр х также
ляется как оператор, и он зависит от 1 и 2. Поскольку в результате на-
вьгчислений появляются члены вида cos2(х) и sin2(ж), нам необходимо
чить тождество cos2 (х) + sin2 (x) = 1 для упрощения выражений.
'/• tensor, red
*atri*gBJ);
atrix glB 9 • v
arrav кг->г> > /• матрица, обратная к g;
§ШПтаB.2,2); array RB,2,2,2); array RicciB,2);
°Per4t
or ц.
64
Глава 3. Системы компьютерной алгебры
g(l,l) := 1; gB,2) := 1;
g(l,2) := cos(u(x(l),xB))); gB,l) := cos(u(x(l),xB)));
gl := g~(-l); У, вычисление обратной матрицы
for a := 1:2 do
for m := 1:2 do
for n := 1:2 do
gamma(a,m,n) := A/2)*
(for b := 1:2 sum gl(a,b)*(df(g(b,m),x(n))
+ df(g(b,n),x(m)) - df(g(m,n),x(b))));
for a := 1:2 do
for m := 1:2 do
for n := 1:2 do
write "gamma(",a,",''>ni>",">n,")=", gamma(a,m,n);
for b := 1:2 do
for m := 1:2 do
for s := 1:2 do
for q := 1:2 do
R(b,m,s,q) := df(gamma(b,m,q),x(s))-df(gamma(b,m,s),x(q))
+ (for n := 1:2 sum gamma(b,n,s)*gamma(n,m,q))
- (for n := 1:2 sum gamma(b,n,q)*gamma(n,m,s)) ;
cos(u(x(l),xB)))**2 := 1 - sin(u(x(l),xB)))**2;
for m := 1:2 do
for q := 1:2 do
Ricci(m.q) := for s := 1:2 sum R(s)m>s,q);
for m := 1:2 do
for q := 1:2 do
write "Ricci(",m,",",q,") = ", Ricci(m,q);
array RiccilB,2);
for m := 1:2 do
for q := 1:2 do
Riccil(m,q) := (for b := 1:2 sum gl(m,b)*Ricci(q,b));
CS := for m := 1:2 sum Riccil(m,m);
Вычисление скалярной кривизны исходя из поля метрического тензора
(например, метрики Геделя, метрики Шварцшильда) — это одно из самых первых
применений компьютерной алгебры. Здесь мы показали, что такие
вычисления можно обобщить для нахождения солитонных уравнений. Модифицировав
3.3. MAPLE
65
Допо.
s
го тензора, можно получить другие солитонные уравнения.
поЛе ^е^ИЧе
ЦаЯРя*^ A1(хA),хB))); gB,l) := cos(u(x(l) ,xB)));
g(l,2) :~ с°
„жно заменить на
* .= cosh(u(x(D,xB))); gB,l) := cosh(u(x(l),xB)));
g(l,2J •
иг. отжно ввести правило тождественного преобразования
^лнительни п.у
,./- (Ч-ГП хB)))**2 := cosh(u(x(l),xB)))**2 - 1;
„ упрощения выражения.
iJ жество прикладных программ, написанных в системе REDUCE, мож-
и в [И], [19], [49], [50], [51]. В [49] описаны приложения к квантовой
Н° ике В [50] даны приложения к дифференциальным уравнениям. В [52]
ятриваются приложения к нелинейным динамическим системам.
3.3. МАРЬЕ
3.3.1. Основные операции
В системе MAPLE различаются строчные и прописные буквы. Команда
Sin(O.l) выдаст sin(.l), тогда как sin(O.l) выдаст желаемый результат
0.09983341665.
Команда дифференцирования в MAPLE —это diff (). Ввод
diff(х'З + 2*х,х);
приведет к результату Зх~2 + 2. Команда интегрирования — это int(). Ввод
int(x + 1,х);
дает х~3/3 + х.
Ь MAPLE есть две разных команды для решения уравнений. Команда
solve(x-2 + A+а)*х + а=0,х);
решает уравнение X + A+а)х + а = 0 и выдает результат х = -1 и х = -а.
fsolve(x-2 - х - 1=0,х)
^6-L8o УРавнение х~2-х-1.0 = 0и выдает результаты -0.6180339887 и
анда подстановки имеет вид subsQ. Например, команда
3цЬ^х=2)Х*у + х~2);
**т 2у + 4
s<lrt(Y^ ,ПР°ЧИХ MAPLE включает следующие математические функции:
log(x-j , *-кваДРатный корень, л/х), ехр(х) (показательная функция, ех),
c°s(x>) V ^Р^ьный логарифм, 1п(а;)) и тригонометрические функции sin(x),
3 - 1548
аргументы которых выражаются в радианах.
66
Глава 3. Системы компьютерной алгебры
Предопределенные константы в MAPLE исчерпываются следующими-
Catalan, E, Pi, false, gamma, infinity, true.
Прочие команды можно найти в руководстве пользователя к системе МАРт
[10]. 1Ё
3.3.2. Пример
В качестве примера рассмотрим одну квантовомеханическую задачу. ДЛя _
данной пробной функции одномерного потенциала найдем приближенное зн
чение энергии основного состояния. Уравнение на собственные значения в о
номерном пространстве имеет вид
Мы будем применять вариационный принцип для вычисления энергии
основного состояния частицы в поле с потенциалом
v(x) .= ( сх Для х > 0.
^ ' \ оо для х < О,
где с > 0. Для такого потенциала энергетический спектр дискретен и ограничен
снизу. Возьмем
хехр(-ах) для х > О,
4 ' ' 0 для х < О
в качестве пробной функции; здесь а > 0. Нужно помнить, что пробная
функция пока не нормирована. Из уравнения на собственные значения находим, что
математическое ожидание энергии дается выражением
.= <ц|я|ц> = $™хе~ах {-&& + сх) ™~ахЛх _ зс fiV
(и\и) J™ х2 exp(-2ax)dx 2а 2т '
где { | ) означает скалярное произведение в гильбертовом пространстве
£2@,00). Это математическое ожидание имеет минимум при
/Зтс\1/3
а=Ы) ■
В приведенной ниже программе мы вычисляем {Е) и затем определяем
минимум (Е) при варьировании а. Итак, энергия основного состояния больше или
равна
9 /2Й2с2\1/3
4 V. Зт
# energy.тар
# потенциал
V := с*х;
3.4. AXIOM
67
u := х*ел^ на собственные значения
# yPaBH^2/B*m)*diff (u,x,x) + V*u;
Ни := '
рование для нахождения среднего значения
J ^кГне нормированного
#Т-= int(u*Hu,x);
resl •
экспонент
* суммирование коэффициентов
Л •= collect(resl,exp);
res2
# подстановка граничных условий
es3 := 0 - subs(x=0,res2) ;
# нахождение нормы и для нормировки
res4 := int(u*u,x);
res5 := -subs(x=0,res4) ;
# нормированное среднее значение
ехре := res3/res5;
# нахождение минимума по а
minim := diff(ехре,а) ;
res6 := solve(minim=0,a);
а := res6[l] ;
# приближенное значение энергии основного состояния
appgse = subs(a=0,ехре);
-замечание. В данном случае годится лишь вещественное решение
уравнения res6, а именно res6 [1] .
3-4. AXIOM
•4-1- Основные операции
ком Делает упор на строгую проверку типов. В отличие от других систем
че^к терной алгебры, типы в AXIOM не фиксированы, а являются
динамизма 00ъектами. Они создаются в процессе работы программы в ответ на
пов fH Г ПОЛЬЗователя. Типы в AXIOM варьируются от алгебраических ти-
(наппи Р' мн°гочлены, матрицы и степенные ряды) до структур данных
осмысл СПиски, словари и входные файлы). Типы можно комбинировать
Многоч ЫМ °^Разом- Мы можем строить многочлены от матриц, матрицы
ЧаМи и °В °Т CTenelJHbix рядов, таблицы хеширования с символьными клю-
Ментами в виде рациональных функций и так далее.
68
Глава 3. Системы компьютерной алгебры
Категории в AXIOM определяют алгебраические свойства, обеспечив
щие математическую корректность. С помощью категорий программы м.ор°~
обнаружить, что умножение для многочленов от непрерывных дробей ком
тативно, тогда как для многочленов от матриц —нет. Подобным образом
горитм вычисления наибольшего общего делителя может найти «НОД»
элементов для любого евклидова кольца, но отвергнет попытки вычисли
осмысленный «НОД» двух таблиц хеширования. Категории также позволяю
компилировать алгоритмы в управлямый машинный код при использован»
произвольных типов.
Объявления типов в AXIOM для стандартных типов в интерактивном
языке обычно допустимо опускать. Основные типы называются доменами
вычисления или просто доменами. Домены определяются в следующем виде:
Name(...): Exports == Implementation
У каждого домена есть начинающееся с заглавной буквы имя (Name), которое
используется для обозначения класса его элементов. Например, Integer
обозначает «класс целых чисел», a Float обозначает «класс чисел с плавающей
точкой» и так далее. Часть «...», следующая за Name, перечисляет параметр(ы)
для конструктора. Некоторые основные типы, вроде Integer, не имеют
параметров. Другие, вроде Matrix, Polynomial и List, имеют один параметр,
который снова может быть доменом. Например,
Matrix(Integer) обозначает «матрицы целых чисел»
Polynomial (Float) обозначает «многочлены с коэффициентами с плавающей
точкой»
На число типов параметров конструктора домена нет никаких ограничений.
Раздел Exports программы в AXIOM определяет операции создания
объектов некого домена и манипуляции над ними. Например, тип Integer
экспортирует константы 0 и 1 и операции +, - и *. Раздел Implementation
определяет функции, реализующие экспортированные операции данного домена.
Эти функции часто описываются через другой домен более низкого уровня,
используемый для представления объектов данного домена.
Каждый объект AXIOM принадлежит только к одному домену. Домен
объекта называется также его типом. Так, число 7 имеет тип Integer, а строка
' 'willi'' имеет тип String. Однако тип объекта может быть не
единственным. Тип целого числа 7 — это не только Integer, но и NonNegativelnteger,
а также Positivelnteger и, возможно, любой другой «подцомен» домена
Integer.
Подцомен —это домен с «предикатом принадлежности к множеству»-
Positivelnteger —это подцомен домена Integer с предикатом «является ли
это целое положительным?». Поддомены с именами определяются
программами абстрактных типов данных, подобными тем, которые определяют доменЫ-
Однако часть Exports поддомена должна перечислять некое множество экс-
портов соответствующего домена. Часть Implementation может давать
специальные определения объектов поддомена. Ниже приведены некоторые
примеры кода AXIOM.
3.4. AXIOM
69
""" спользует D для дифференцирования выражения
f := ехр ехР х
n(f,х)
ный третий аргумент п в D инструктирует AXIOM взять n-ю про-
Яеобязатель ^ например D(f f х, 3).
йзводИУ ИМеется много библиотечных подпрограмм для интегрирования.
ЯапримеР,
integrate((x**2+2*x+l)/((x+l)**6+l),x)
Да6 arctan(x3 + Зх2 + Зх + 1)
3
AXIOM использует команду rule для описания необходимых правил
преобразования. Например,
sinCosExpandRules := rule
sin(x+y) == sin(x)*cos(y) + sin(y)*cos(x)
cos(x+y) == cos(x)*cos(y) - sin(x)*sin(y)
sinB*x) == 2*sin(x)*cos(x)
cosB*x) == cos(x)**2 - sin(x)**2
Поэтому команда
sinCosExpandRules(sin(a+2*b+c) )
применяет описанные выше правила.
Многие другие команды можно найти в [24].
3.4.2. Пример
При решении систем полиномиальных уравнений центральную роль играет
базис Гребнера [12], [24], [34]. Если многочлены
{pi : i = 1,... ,n}
ностью обращаются в нуль, это делает и их комбинация
Z^
CiPi
мож ^ФЩибнты с, в общем случае тоже являются многочленами. Всевоз-
лом И ом(:)Инации этого вида порождают пространство, называемое идеа-
лидей ' П0Р°жДаемый семейством многочленов, состоит из множества всех
Ми- Си комбинаций этих многочленов с полиномиальными коэффициента-
нера (о а разУЮш.их (или базис) G для идеала / является базисом Греб-
иДеала I Ительно порядка <), если в результате любой редукции элемента /
аУль. Эт Р^УЦированному многочлену (относительно G) всегда получается
°1е Лекси 0РЯД0К может быть лексикографическим (имеется в виду, что по-
СТеПеням РаФического упорядочения переменных следует упорядочение по
Генных), обратным лексикографическим (сначала упорядочение
70
Глава 3. Системы компьютерной алгебры
переменных в обратном лексикографическом порядке, затем упорядочен
степеням), общим степенным (сначала все упорядочивается по степеням ^
тем в лексикографическом порядке) и обратным общим степенным (сна' 3*~
все упорядочивается по степеням, затем в обратном лексикографическом
рядке). Большинство систем компьютерной алгебры способны находить б °
Гребнера для данной системы многочленов. Здесь мы рассмотрим, как эт0 ^'
лает AXIOM. Де-
DMP означает DistributedMultivariatePolynomial. Рассмотрим следую,
команды:
(dl, d2, d3) : DMP([z,y,x] ,FRAC INT)
dl := -4*z + 4*y**2*x + 16*x**2 + 1
d2 := 2*z*y**2 + 4*x + 1
d3 := 2*z*x**2 - 2*y**2 - x
groebner [dl,d2,d3]
Это даст на выходе
1568 6 1264 5 _6_ 4 182 з 2047 2 103 2857
Z~rmX ~^0ЬХ +ШХ +ШХ ~~ШХ +2745;Е_ 10980'
2 112 6 84 5 1264 4 13 з 84 , 1772 2
У + 7^7;х ~ ^7^х + -^Гх ~ TJKX + ^7^х + т^7^х +
2745 305 305 549 305 2745 2745'
, 29 „ 17 4 11 з 1 2 I5 1
х +ТХ ~Гвх ~ТХ +Т2Х +Гбх + 4-
3.5. MATHEMATICA
3.5.1. Основные операции
Система MATHEMATICA различает заглавные и строчные буквы. Команда
sin[0.1] для вычисления синуса от 0.1 выдаст сообщение об ошибке:
«возможно, ошибочное написание», тогда как команда Sin[0.1] выдаст
правильный ответ 0.0998334.
В системе MATHEMATICA команда дифференцирования
D[x~3 + 2*х,х]
выдает 2 + 3*х~2. Команда
Integrate[х~2 + 1,х]
выдает х + х/3.
Команда Solve [ ] может решать ряд алгебраических уравнений, систем
алгебраических уравнений и трансцендентных уравнений. Например, команд3
Solve[x + (а + 1)*х + а ==0,х]
выдаст решения х = -1 и х = -а.
Оператор замены /. применяет правила к выражениям. Рассмотрим выра
жение
х*у + х*х
3.6. MuPAD
71
ГоГДа команда
х„у + *** /■* "> 2
пезультат 4 + 2*у.
вЫдаст у ^ чИХ MATHEMATICA включает следующие математические
Среди |-х-| (квадратный корень, у/х), Ехр[х] (показательная функция,
функции- атуралЬный логарифм, 1п(ж)) и тригонометрические функции
е1^ Log Tan[x], аргументы которых выражаются в радианах.
SinLAj, деленные константы представлены следующим списком:
finity, Pi. Degree, GoldenRatio, E, EulerGamma, Catalan
команды можно найти в руководстве пользователя к системе МАТНЕ-
SSlCA [62].
3.5.2. Пример
В качестве примера рассмотрим спиновые матрицы для спина 1
/ О \/2Н 0 \ /0 00
s+ := 0 0 у/2П , s_ := ч/2Й
V о о о / \ о
Вычислим коммутатор двух этих матриц
[s+, S-] := s+s_ — s_s+
и затем найдем собственные значения этого коммутатора. Программа в системе
MATHEMATICA будет следующей:
(* spin.m *)
sp = {{ 0, Sqrt[2]*hb, 0}, {0, 0, Sqrt[2]*hb}, {0, 0, 0}}
sm = -С-СО, 0, 0}, {Sqrt[2]*hb, 0, 0}, {0, Sqrt[2]*hb, 0}}
comm = sp . sm - sm . sp
Eigenvalues[comm]
На выходе получим
2 2
{0' -2 hb , 2 hb }
3-6- MuPAD
• • • Основные операции
MuPad-си
3°м в уни тема компьютерной алгебры, которая разработана главным обра-
фической Итете в Падерборне. Она является символьной, численной и гра-
Раллелива тем°й- MuPAD предоставляет пользователю возможность
распаяет объ ИЯ ИНстРУкЦии- По синктасису MuPAD близок системе MAPLE и
системе тут р°РиентиРованные возможности, схожие с системой AXIOM.
AD прописные и заглавные буквы в инструкциях различаются.
72
Глава 3. Системы компьютерной алгебры
Инструкция Sin(O.l) выдает в качестве результата Sin(O.l), тогда как
инструкция sin(O.l) дает требуемый результат 0.09983341664. В MuPAD
дифференцирование производится с помощью инструкции diff (). Введя
diff(х~3 + 2*х,х),
получим на выходе
2
3 х + 2
Команда интегрирования — int (). Введя
int(x + 1,х),
получим
3
X
х + --
3
Командой
solve(x~2 + A+а)*х + а=0,х);
решают уравнение х~2 + A+а)*х + а=0, что дает в качестве результата
{-а, -1}. Подстановка выполняется командой subs(). Например, выполнив
subs(x*y + х~2,х=2),
получим 2 у + 4. Среди прочих, MuPAD включает следующие
математические функции: sqrt(x) (квадратный корень), ехр(х) (экспоненциальная
функция, ех), 1п(х) (натуральный логарифм) и тригонометрические функции
sin(x), cos(x), tan(x) с аргументами в радианах.
В MuPADe имеются встроенные константы
I, PI, E, EULER, FALSE, gamma, infinity
3.6.2. Пример
В качестве примера рассмотрим, как метод Пикара будет аппроксимировать
решение дифференциального уравнения dy/dx — f(x, у) с начальным условием
у(х0) = J/0, где / — аналитическая функция относительно жиг/. Интегрируя
обе стороны уравнения, получаем
У(х) =г/о+ / f(s,y(s))ds.
Jx0
Теперь начиная с г/о эта формула может использоваться для
последовательной аппроксимации точного решения, если процесс будет сходиться.
Следующее приближение решения задается формулой
гх
yn+i(x) =y0+ f(s,y(s))ds.
Jxo
3.6. MuPAD
73
Пля примера аппроксимируем решение уравнения dy/dx = х + у, исполь-
уя пять шагов метода Пикара. Для ввода из файла используется команда
ea(j (filename). В нашем случае эта команда выглядит так: read ("picard");
на выходе получим
3 4 5 6
2 х х х х
х + х + — + — + — + + 1
3 12 60 720
/*picard*/
х0:=0; /* начальное х*/
у0:=1; /* начальное у*/
у:=У0:
yl:=subs(y,x=s) ;
f:=func(x+y,x,y): /*объявляем функцию f(x,y)=x+y */
for i from 1 to 5 do
y:=(yO+subs(int(f(s,yl),s),s=x)-subs(int(f(s.yl),s),s=x0));
yl:=subs(y,x=s) ;
end_for;
print(y);
Глава 4
Объектно-ориентированное
программирование
В этой главе мы обсудим основные понятия объектно-ориентированного
программирования. Мы начнем с определений объектов, классов и абстрактных
типов данных. Далее рассмотрим такие концепции, как передача сообщений,
наследование, полиморфизм и другие. Они будут пояснены множеством
примеров. В разд. 4.5 мы опишем объектно-ориентированные языки C++ и Java.
Наконец, завершим главу кратким введением в объектно-ориентированные языки
Eiffel, Smalltalk и Oberon.
По прочтении этой главы читатели будут ознакомлены с важнейшими
концепциями, применяемыми в объектно-ориентированных системах. В
последующих главах мы увидим, чем эти концепции полезны для разработки систем
компьютерной алгебры.
4.1. Объекты, классы и абстрактные типы данных
4.1.1. Объекты
Любая вещь, существующая на свете, — это объект. Цветы, автомобили,
матрицы — все это примеры объектов. Автомобили характеризуются размером,
цветом и другими признаками. Им присущи операции, вроде запуска двигателя,
включения фар, нажима на педаль тормоза и так далее. Подобным образом, у
матриц есть такие характеристики, как число строк, столбцов и т. д. Им
присущи операции вроде сложения, умножения, взятия тензорного произведения
и так далее.
В этой главе мы изучим применение объектно-ориентированных методов
(ООМ) моделирования мира. Как указывает амбициозное название ООМ,
объекты играют важную роль. Объект в компьютере обладает уникальным
идентификатором (ID), не зависящим от значений его атрибутов. ID используется
для того, чтобы отличить один объект от другого. Он не может измениться
на протяжении всего времени жизни объекта. На рис. 4.1 показаны основные
составляющие объекта.
Внутренняя структура объекта состоит из двух частей: атрибутов (данных)
и методов (операций).
Атрибуты описывают свойства объекта в конкретный момент времени.
Это говорит нам кое-что об объекте, но не о том, как данный объект действу-
4.1. Объекты, классы и абстрактные типы данных
75
Методы внешнего интерфейса
Внутренние методы
Атрибуты
ID
рис. 4.1. Схематическая диаграмма объекта.
ет. Их можно считать локальными данными, связанными с данным объектом.
Например, у кота есть вес, порода, пол и возраст. У многочлена есть степень,
число членов и коэффициенты при каждом члене.
Атрибуты, которые мы задаем для объекта, зависят от потребностей
решаемой задачи. Иногда нам не нужно помнить, что у кота четыре лапы, или же
мы можем просто считать это известным заранее. Иногда структура матрицы
бывает важна; скажем, является ли она разреженной или плотной? На самом
деле решения о том, что включать в набор атрибутов объекта, это вопрос
проектирования системы.
Методы описывают поведение, свойственное объекту, и то, что этот объект
может делать или сделает, когда что-нибудь случится. Например, у
работников есть имя, возраст, пол, заработная плата и табельный номер. Однако
одних атрибутов недостаточно,чтобы полностью описать работников. Нам нужно
знать также, какие операции применимы к работникам. Эти операции могут
включать повышение в должности, увеличение заработной платы, получение
разрешения на пользование автомобилем и так далее. Они представляют
собой
действия, которые могут быть выполнены объектом или над объектом.
о результате таких действий состояние объекта может измениться.
На самом деле есть два вида методов. Первый из них —это методы
внешнего интерфейса, обеспечивающие способ общаться с объектом. Второй вид —
то внутренние методы. Они задают поведение объекта, но недоступны извне
екта. Другими словами, их достаточно знать лишь проектировщику объек-
• ^ ользователям объекта достаточно знать лишь о методах внешнего интер-
ньщ
Метод,
свойственный некому объекту, активируется сообщением, послан-
^ Другим объектом тому объекту, который содержит этот метод. Кроме то-
п ' М0Жет быть вызван другим методом, принадлежащим тому же объекту,
ваютДСТВ°М локального сообщения. Контракт устанавливается между вызы-
ЧеЦ д объектом и вызванным объектом. Этот механизм называется переда-
Разде ° W'eHUU между объектами, и он будет подробнее обсужден в следующем
76
Глава 4. Объектно-ориентированное программирование
rows, columns
value.entry
check-determinant
f ind_inverse
addition
multiplication
inverse
Рис. 4.2. Схематическая диаграмма класса Matrix.
В качестве примера рассмотрим объект, представляющий класс Matrix,
показанный на рис. 4.2. У него есть уникальный идентификатор (ГО),
отличающий его от всех иных объектов. Этот объект содержит информацию вроде
числа строк/столбцов, значений каждого элемента и т.д. Он также содержит
некоторые методы внешнего интерфейса, применяемые для обработки
информации, такие, как inverse, determinant и т.д. Известно, что для
нахождения обратной матрицы нужно проверить, не равен ли нулю определитель этой
матрицы. Значит, метод inverse должен содержать следующие внутренние
методы:
check_determinant проверить, равен ли нулю определитель матрицы
f ind_inverse найти обратную матрицу.
Заметим, что методы check_determinant и f ind_inverse не видны
снаружи. Только методы, принадлежащие к классу Matrix, имеют к ним доступ. Эта
концепция скрытия данных важна. Она способствует модульности и
уменьшает эффекты интерференции при разработке класса, проявляющиеся тогда,
когда изменения в одном модуле требуют внесения изменений также и в другие
модули [63].
В объектно-ориентированных системах эффекты интерференции
вызываются изменениями лишь в интерфейсе объекта, а не изменениями во
внутренних деталях. Чтобы свести эффекты интерференции к минимуму, интерфейс
объекта должен сохраняться как можно более простым и скрывать от
пользователей как можно больше подробностей.
4.1. Объекты, классы и абстрактные типы данных
77
4 1.2. Абстрактные типы данных
Атрибут может быть простым типом вроде целого числа, буквы или же другим
бъектом. Объект, составленный из других объектов, известен как аггрегация.
Такая возможность построения объектов обеспечивается абстрактными
типами данных (АТД), в которых объекты строятся из существующих структур
ланных. АТД —это набор данных и множество операций с этими данными.
Он скрывает внутреннюю информацию, но обеспечивает пользователям
открытый набор методов манипуляции с этим объектом. Поэтому другие точно
знают, какие операции АТД может выполнять. Однако они не знают, в каком
виде эти данные хранятся и как производятся эти операции. Это очень мощная
методология, поддерживающая модульность и снижающая общую сложность
программных систем.
Ни один объект не может непосредственно считывать или изменять
состояние никакого другого объекта. Доступ к атрибутам другого объекта или их
изменение выполняется посредством методов его внешнего интерфейса.
Однако такой доступ возможен, лишь если сам объект это разрешает. Это свойство
называется инкапсуляцией. Именно этим способом объектно-ориентированная
система осуществляет скрытие информации. Скрытие информации включает
скрытие как самих данных, так и реализации операций. Инкапсуляция
собирает вместе данные и функции, работающие с этими данными. Таким образом,
этот набор данных и функций можно рассматривать как нечто единое. Эти
свойства приводят к слабой взаимозависимости между объектами.
Рассмотрим, например, раздатчик напитков, изображенный на рис. 4.3. Он
принимает на входе горячую воду и выдает на выходе либо чай, либо кофе в
зависимости от того, какая кнопка нажата. Это устройство подобно абстракт-
Горячая вода
/
Смешивание
кофейного
порошка
РИС- 4-3. Схем
Кофе
Заваривание
чайного
листа
т т
Чай
атическая диаграмма раздатчика напитков.
78
Глава 4. Объектно-ориентированное программирование
ному типу данных. Горячая вода, чай и кофейный порошок подобны набору
данных, а операции — розливу чая или кофе. Устройство рассматривается как.
черный ящик. Нас не интересуют подробности того, как именно
осуществляются операции. То, чего мы хотим, — это чтобы устройство наливало нужный
нам напиток.
Заметим, что внутренние механизмы заключены в металлический кожух.
Отверстия в кожухе предназначены лишь для входа (горячей воды) и выхода
(чая или кофе). Таким образом, внутренние механизмы не только скрыты от
глаз пользователей, но и физически недоступны им. Более того, механизм
одной операции скрыт от другой операции и недоступен ей. Поэтому мы можем
усовершенствовать операцию размешивания кофейного порошка, не
затрагивая операции заваривания чая. Мы можем также добавить еще одну операцию
например разлив горячего шоколадного молока, не затрагивая двух исходных
операций. Вот так абстрагирование и скрытие информации работают в этом
примере.
4.1.3. Классы
Много схожих между собой объектов можно охарактеризовать одним и тем
же общим описанием. Возьмем, например, четное число и простое число. Их
можно описать более общей абстракцией — Integer. У них есть много общих
свойств и способов действия. Их можно расставлять в фиксированном порядке,
складывать, умножать и так далее (точное определение целого числа см. в
гл. 2). Собрание экземпляров объектов, обладающих этими свойствами, можно
обозначить как Integer.
Объекты проверяют, сравнивая их с общими понятиями, чтобы выяснить,
можно ли рассматривать данный объект как Integer. Эти соображения
приводят к понятию класса. Описание объектов, имеющих сходные свойства,
называется классом. Каждый объект является экземпляром некого класса. Класс
состоит из структуры данных и операций, применимых к данному классу. Он
также полезен для классификации объектов и обеспечивает основу для
создания нового объекта, служащего экземпляром этого класса.
В объектно-ориентированном мире в каждом классе есть три разных вида
атрибутов [25]:
1. Атрибуты класса описывают свойства класса как целого. Это могут быть
конкретные числовые значения или средние величины, связанные с
классом. Например, рассмотрим класс Card, являющийся надклассом всех
карточных игр, продающихся в отделе универмага, таких, как карты для
покера, для пасьянса, для гадания и другое. Атрибутами этого класса могут
быть общее число имеющихся в продаже колод карт, число различных
типов колод или средняя цена колоды карт.
2. Общие атрибуты экземпляров описывают общие свойства, присущие всем
экземплярам этого класса. Рассмотрим, например, подкласс Poker_card.
Общим атрибутом экземпляров может быть число карт в каждой колоде,
или же размер каждой карты, скажем, 6 см х 9 см. Иногда общие атрибуты
4.2. Передача сообщений
79
з.
емпляров называют переменными класса. Они отличаются от атрибутов
са определенных выше, которые описывают характеристики набора
объектов-экземпляров класса как целого.
Атрибуты экземпляров описывают атрибуты каждого объекта. Хотя на-
ания этих атрибутов для данного класса заданы, их значения приписы-
яются лишь индивидуальным объектам. В классе Poker_card могут быть
следующие атрибуты экземпляров:
масть например, бубны, черви и т.д.,
ранг например, валет, король, туз и т.д.,
цвет например, черный или красный.
4.2. Передача сообщений
Объекты с атрибутами и методами общаются между собой посредством
передачи сообщений. Сообщение содержит имя, присвоенное методу внутри объекта.
Оно может также содержать несколько аргументов. Для передачи сообщений
нужно две стороны: отправитель и получатель. Когда объект получает
сообщение, активируется связанный с ним метод. После завершения работы этого
метода объект-получатель возвращает результат объекту-отправителю.
Предположим, что служащий некой компании по имени Джон устраивает
вечеринку, чтобы отпраздновать дни рождения всех служащих этой компании,
родившихся в январе. Чтобы найти дни рождения (ДР) всех служащих
компании, он должен запросить информацию из их личных дел. В этом процессе
объект, называемый «Джон», отправляет сообщение Запрос_ДР каждому
объекту из класса Служащие. ДР каждого служащего затем возвращается в качестве
результата. На рис. 4.4 изображена эта ситуация.
Объект 'Джон'
Методы
Атрибуты
Запрос_ДР
Возвращаемая ДР
Объект 'служащий
Запрос _ДР
I
J
Рис
в
4.4. Обмен сообщениями между объектами (ДР —дата рождения).
данном примере сообщение исходит от метода, принадлежащего объекту
Ахов». Адресат сообщения — другой объект, называемый служащий. Название
Щения — это Запрос_ДР, а его действие состоит в том, чтобы получить ДР
00ъекта служащий.
а самом деле объекты разных классов могут реагировать на одно и то
^сообщение по-разному. Это один из видов полиморфизма, о котором речь
иДет позже в этой главе.
80
Глава 4. Объектно-ориентированное программирование
4.3. Наследование
Наследование —это мощная концепция объектно-ориентированных систрч.
Она чаще всего используется в качестве механизма создания специальных е
риантов некого более общего класса. Это называется обобщенной специалщ '
цией. Она применяется к семействам классов, имеющих похожие, но не совця~
дающие атрибуты и методы. В такой ситуации полезно наследование свойстй
Оно обеспечивается наличием нескольких уровней абстракции, причем каж
дый уровень представляет различные варианты или уточнения некого класса
Более низкие уровни строятся из более ранних. Другими словами, надкдасс
является обобщением подклассов. Аналогично, подкласс данного класса пред,
ставляет специализацию этого класса, относящегося к более высокому уровню
Наследование также обеспечивает механизм управления классами и
совместного использования общих кодов.
В качестве примера рассмотрим обратимые матрицы, нижнетреугольные
матрицы и симметричные матрицы. У них есть некоторые общие свойства
скажем, число строк, столбцов и так далее. Эти общие свойства могут быть
унаследованы от более общего класса, называемого Матрица. На рис. 4.5
показана иерархия наследования. Подклассы наследуют свойства от надкласса
Матрица. Подклассы являются специализированными вариантами этого
надкласса.
Рис. 4.5. Иерархия наследования класса.
Надкласс Матрица может также наследовать свойства от некоторых
надклассов, вроде Математическая Структура, который в свою очередь может
наследовать свойства от класса, называемого Структура. Таким образом можно
построить иерархию наследования.
Различные объектно-ориентированные языки могут поддерживать разные
виды наследования. Smalltalk поддерживает лишь простое наследование. ДрУ"
гие языки, скажем С-Ы-, поддерживают множественное наследование. Оно
позволяет классу наследовать свойства более чем от одного надкласса-предка-
Например, обратимая симметричная матрица является одновременно
обратимой матрицей и симметричной матрицей, и здесь имеет место
множественное наследование, как показано на рис. 4.6. Всегда требуется, чтобы правила,
определяющие множественное наследование, были способны разрешить любое
противоречие, которое может возникнуть, скажем конфликт названий, когда
одно и то же название используется для двух разных надклассов и наследуется
4.3. Наследование 81
рис
4 6. Пример множественного наследования.
ним и тем же подклассом. Отсюда ясно, что хотя множественное
наследовало __ это мощный механизм, он увеличивает сложность.
Положение станет еще сложнее, если Обратимая и Симметричная наследуют
от одного и того же над-надкласса Матрица, как показано на рис. 4.7. Подкласс
Обратимая Симметричная Матрица потенциально может наследовать свойства
от одного над-надкласса дважды. Эта ситуация называется повторное
наследование.
с. 4.7. Пример повторного наследования.
Ть также другая форма наследования, называемая выборочным наследо-
ем- " этом случае подкласс наследует лишь некоторые свойства от над-
а. Это делает часть информации недоступной некоторым подклассам,
ример, база данных компании может состоять из нескольких частей, до-
до Ь1Х ли^° менеджеру, либо бухгалтеру, либо продавцу. Продавец имеет
Do K записям ° клиентах, которые недоступны бухгалтеру. С другой сто-
^ > бухгалтер имеет доступ к записям о продажах, недоступным продавцу,
том ер имеет доступ ко всей информации, хранящейся в базе данных, в
Пр0 Исле к записям о персонале компании, недоступным ни бухгалтеру, ни
ЧУ- Эта ситуация изображена на рис. 4.8.
82
Глава 4. Объектно-ориентированное программирование
Продавец
имеет доступ к
записям о клиентах
База данных компании
Записи о клиентах
Записи о продажах
Записи о персонале
Бухгалтер
имеет доступ к
записям о продажах
Рис. 4.8. Пример выборочного наследования.
Менеджер
имеет доступ к записям
о клиентах
о продажах
о персонале
Подклассы наследуют методы и атрибуты от надклассов. Однако подкласс
может переопределить любой из методов или атрибутов, унаследованных от
надкласса, а также определить дополнительно свои собственные атрибуты и
методы. Это чрезвычайно мощное средство, особенно в сочетании с
полиморфизмом. Эта концепция будет рассмотрена в следующем разделе настоящей
главы.
Иногда бывает полезно ввести классы на неком уровне, не существующие
в реальности, но являющиеся удобными понятийными конструкциями. Такие
классы называются абстрактными классами. Абстрактный класс обычно
занимает подходящее положение в иерархии классов. Он определяет формы
поведения своих наследников, но не их фактическую реализацию. Назначение
этих абстрактных классов — определять, что должно быть сделано, но не как
эти задачи должны решаться. Это дело наследников — обеспечивать
реализацию предписанных форм поведения. В эти абстрактные классы не входят
никакие экземпляры, созданные ими самими. Напротив, экземпляры создаются
подклассами этих абстрактных классов, и эти экземпляры соответствуют
некоторым объектам реального мира.
Библиотеки классов, содержащие много классов, обычно связаны
отношениями наследования. Наследование снижает избыточность и дублирование
программ. Это одно из основных преимуществ использования данной
концепции. Классы организованы в иерархии, причем более общие классы находятся
на верхних уровнях иерархии, а более специализированные классы
располагаются ниже.
4.4. Полиморфизм
83
, долиморфизм
слово полиморфизм означает «имеющий много форм». Это просто
ГРеЧв чТо объекты-адресаты (получатели) могут оказаться относящимися бо-
Зцачй 1 НОМу типу. Отправитель сообщения не обязан знать, к какому клас-
левЧ яплежит получатель. Он лишь запрашивает некое событие с помощью
с^ я сообщений, а получатель знает, что ему делать. Это позволяет объек-
Пе^ чных типов (получателям) различными способами реагировать на одно
там у с00бщение. В результате программы становятся более компактными,
И Т° „от их легче понять. Существует три вида полиморфизма:
а значш, ил
1 Полиморфизм включения.
2 Операционный полиморфизм.
3 Параметрический полиморфизм.
4.4.1. Полиморфизм включения
Традиционные языки программирования требуют от программистов, чтобы
те точно определяли, какой метод будет применяться в процессе вычисления.
Этот подход называется статическим связыванием. Полиморфизм
включения позволяет принимать такие решения лишь во время работы
программы, что называется динамическим связыванием (или поздним связыванием).
С помощью динамического связывания система обеспечивает, что нужный
метод будет вызван для нужного класса в ходе работы программы. Хотя
динамическое связывание гибко, оно снижает производительность программ. Это
происходит из-за того, что теперь в ходе работы программы нужно запускать
алгоритм просмотра и подбора. К счастью, эти затраты в большинстве языков
минимальны. Статическое связывание, с другой стороны, надежнее и
эффективнее.
Этот вид полиморфизма применим к языкам программирования со строгим
контролем типов, вроде языков Eiffel, Simula и C++, и он связан с
наследованием. Это средство, обеспечивающее общий интерфейс для разных классов в
одном и том же дереве наследования с помощью одного и того же названия
етода. Однако каждый класс может реализовывать этот метод по-своему.
Рассмотрим дерево наследования, изображенное на рис. 4.9. Круг и
реугольник—это подклассы абстрактного надкласса Фигура. Пусть метод
сление_площади возвращает площадь геометрической фигуры. Метод
исление_площади определен в классе Фигура. Однако никакой метод для
сления площади фигуры неприменим без явного определения того, к
У виду фигур она относится. Подклассы наследуют этот абстрактный
вы И пеРеписывают его определение посредством подходящих алгоритмов
то НИя площади круга или треугольника. Иначе говоря, когда одно и
быт СооЦение посылается всем классам, реакция каждого из них может
с пя ЛИЧн°й от реакции другого. Это свойство позволяет легко обращаться
Радтт ро,а'НЬ1ми объектами. Если у нас есть много разных фигур, вроде па-
огРаммов, ромбов, прямоугольников и так далее, мы можем разослать
84
Глава 4. Объектно-ориентированное программирование
Фигура
Вьгчисление_площади
Круг
Треугольник
Вычисление_площади
= тех (радиусJ
Вычисление_плащади
■■ 1/2 х основание х высота
Рис. 4.9. Пример полиморфизма включением.
одно сообщение Вычисление_площади и ожидать, что каждый из них возвратит
правильный ответ. Нам не нужно знать наперед, какая у каждой из них форма.
Этот вид полиморфизма позволяет обойтись без некоторых процедур
проверки типа в языках со строгими типами. Это также делает разработку
программ проще и динамичнее.
4.4.2. Операционный полиморфизм
Этот вид полиморфизма применим к одному и тому же методу с несколькими
определениями. Решение о том, какое именно определение будет
использовано, принимается исходя из типа параметров, содержащихся в сообщений. Для
примера рассмотрим следующее определение класса на языке C++, которое
называется Document:
class Document
■С
void print(char* s);
void print(char c);
void print(int n);
};
В этом классе один и тот же метод print определен трижды. Каждый
вариант отличается типом входных данных. Поэтому когда объекту типа Document
посылается некое сообщение, будет вызван тот или иной вариант метода prin*
в зависимости от типа данных параметров сообщения. Это называется пер£'
грузкой функции.
На самом деле этот вид полиморфизма давно используется для
арифметических операций в большинстве языков. Операции +, -, * и / могут
использоваться для сложения, вычитания, умножения и деления как целых чисел, так
4.5. Объектно-ориентированные языки
85
плавающей точкой. Компьютер автоматически генерирует подходя-
й чиСв инный код на основании типа операндов (целые числа или числа с
^аю^ей точкой).
Параметрический полиморфизм
„ полиморфизма использует типы в качестве параметра в объявлении
^Т°Т Например, пусть в стеке хранятся целые числа. Для языков со стро-
КЛаС нтролем типов, вроде Eiffel, Java и C++, при изменении типа данных,
ГИМ м чтобы хранить в стеке строки, нужно будет переписать программу.
CIfiue говоря, придется переписывать программу для каждого нового типа
ых что сделало бы работу программиста слишком трудоемкой и подвер-
Д ной'ошибкам. Эту трудность можно преодолеть с помощью концепции
параметрического полиморфизма.
Параметрический полиморфизм обычно применяется для такой
реализации структур данных и алгоритмов, которая была бы в значительной степени
независима от типа объектов, с которыми они работают. Например,
параметрический Stack может описывать реализацию структуры Stack, содержащую
объекты произвольного типа. Тогда объект Stack можно будет использовать
для хранения строк, целых чисел, указателей и так далее. Компилятор
автоматически будет генерировать коды для этих классов Stack. Другой
хороший кандидат для применения параметрического полиморфизма — структура
Vector. В принципе тип данных, содержащихся в структуре вектора, слабо
зависит от того, какие операции можно совершать над векторами. Алгоритм
сортировки—еще один идеальный кандидат на параметрический полиморфизм.
Обычно нас интересует только правильность сортировки элементов данных,
а не типы данных этих элементов, лишь бы их можно было расположить в
подходящем порядке. Короче говоря, использование разными типами данных
одних и тех же программ — самое важное преимущество параметрического
полиморфизма.
Эти концепции реализованы в Библиотеке Стандартных Шаблонов (БСШ).
Ьиблиотека Стандартных Шаблонов включает следующие классы: deque, list,
queue, set и vector. Мы подробно опишем эту библиотеку в гл. 5.
Пока что мы ввели главные понятия объектно-ориентированного програм-
Рования, такие, как объект, класс, абстрактный тип данных, передача со-
Щений, наследование и полиморфизм. В следующем разделе мы обсудим
оинства наиболее популярных ныне объектно-ориентированных языков
раммирования. Мы опишем, как перечисленные выше понятия объектно-
тированного программирования реализуются в этих языках.
Объектно-ориентированные языки
3Дес ^аже Имеется много объектно-ориентированных языков. Мы обсудим
°бъ екоторые из них и сравним их с C++. Чаще всего используемые
й Dej .^ориентированные языки —это C++, Java, Smalltalk, Eiffel, Оберон
86
Глава 4. Объектно-ориентированное программирование
В начале 60-х гг. в Европе группа программистов Норвежского вычисли
тельного центра под руководством Оле-Йохана Дала и Кристана Нигарда раз
работала язык имитационного моделирования, названный Simula 67 [7]. Он бьтп
предназначен для описания и моделирования поведения систем. Под системой
понимался набор независимых объектов, имеющих общую цель, причем
система проверяла жизненный цикл своих элементов. Язык Simula был основан на
Алголе-60 с одним очень важным дополнением — концепцией класса. С
помощью этой концепции можно объявлять класс, создавать объекты этого класса
присваивать имена этим объектам и строить иерархическую структуру
объявления класса. Хотя эта концепция предназначалась лишь для описания
жизненных циклов конечных элементов при численном моделировании, позднее
она была признана весьма общим инструментом программирования, идеально
подходящим для описания и разработки программ в абстрактном виде.
Основная идея состояла в том, что данные (или структура данных) и операции,
применимые к ним, можно рассматривать как нечто единое, а это создает основу
для реализации абстрактных типов данных. Концепция класса была принята
и использована во многих современных языках, таких, как Параллельный
Паскаль, Модула-2, CLU и Ада. Она оказалась полезной для программирования
параллельных вычислений. Классы в Simula основаны на объявлении процедур
и блочной структуре Алгола-60, но последняя была освобождена от
встроенной в язык гнездовой структуры разрешением одновременно сосуществовать
нескольким экземплярам блоков. Simula 67 оказала существенное влияние на
столь разные языки программирования, как Smalltalk, C++ и Ада.
Smalltalk был разработан в исследовательском центре компании «Ксерокс»
в Пало-Альто. Smalltalk-80 был первой коммерческой версией, выпущенной
компанией «Ксерокс».
Delphi можно рассматривать как потомка ТурбоПаскаля, тогда как Oberon
можно считать потомком языка Модула-2.
Eiffel —это язык программирования, разработанный для обеспечения
корректного построения компонент программного обеспечения посредством
объектно-ориентированного подхода. Раннюю спецификацию языка Eiffel
можно найти в книге Объектно-ориентированная разработка программных
систем [36], в которой Бертран Мейер показал, как хорошая
программистская практика может быть воплощена в языке, основанном на объектно-
ориентированной парадигме.
Объектно-ориентированный язык Java был спроектирован компанией Sun
Microsystems «с нуля» для надежного выполнения программ в
вычислительных сетях. Он имеет много общего с C++, но все конструкции, которые его
разработчики сочли ненадежными (вроде указателей и ссылок для основных
типов данных), из него устранены.
4.5.1. Язык C++
С тех пор как язык C++ был впервые изобретен, он претерпел три
существенные ревизии, каждая из которых добавляла к нему новые конструкции и
изменяла его. Первая ревизия имела место в 1980 г., а вторая —в 1990г. Тре-
4.5. Объектно-ориентированные языки
87
?ья
реви:
зия осуществлялась в процессе стандартизации C++. Несколько лет
началась работа над стандартом C++. Для этой цели был создан специ-
ia3 й совместный комитет ANSI и ISO (International Standards Organization).
^Ь тй вариант предложенного стандарта был закончен 25 января 1994 г. В
варианте комитет ANSI/ISO по C++ сохранил возможности, определен-
"первоначальный вариант отражал текущее состояние C++. После за-
тй вариант предложенного стандарта был закончен 25 января
варианте комитет ANSI/ISO по C++ сохранил возможности, or
создателем языка Бьярном Страуструпом, и добавил несколько новых.
НЬ первоначальный вариант отражал текущее состояние C++. После за-
рния первого варианта произошло событие, повлекшее за собой очень
свое расширение стандарта: создание Библиотеки Стандартных Шаблонов
/КГШ)- БСШ —это набор типовых подпрограмм вроде сортировки и поиска,
ооые можно использовать для обработки данных. После утверждения пер-
варианта комиссия проголосовала за включение БСШ в спецификацию
C++ Добавление БСШ расширило область применения C++ за пределы его
первоначального определения. Стандартизация C++ потребовала больше
времени чем кто-либо ожидал в тот момент, когда эта работа начиналась. При
этом в язык было добавлено много новых возможностей и внесено множество
мелких изменений. Версия C++, определенная комитетом по C++,
обширнее и сложнее, чем исходная разработка Страуструпа. Однако ныне стандарт
завершен. Последний вариант был одобрен комитетом 14 ноября 1997 г. В
настоящей книге для реализации Символьного C++ использован Стандартный
C++. Стандартный С+Н—это та версия, которая ныне принимается всеми
большими компиляторами.
Поскольку основой разработки C++ явилось объектно-ориентированное
программирование, важно точно определить, что это такое. Это методология,
взявшая на вооружение лучшие принципы структурного программирования и
соединившая их с несколькими мощными концепциями, позволяющими
организовывать наши программы более эффективно. В общих чертах этот метод
можно описать так: когда программирование ведется в объектно-ориентированном
стиле, мы разбиваем задачу на ее составные части. Каждая часть
становится отдельным объектом, содержащим свои собственные инструкции и
данные, относящиеся к этому объекту. В результате сложность уменьшается. Все
ооъектно-ориентированные языки обладают тремя общими свойствами:
инкапсуляцией, полиморфизмом и наследованием.
осе программы состоят из двух основных компонент: операторов програм-
(.кода) и данных. Код —это та часть программы, которая выполняет дей-
я! а данные —это информация, которую эти действия изменяют. Инка-
яция —это программный механизм, соединяющий воедино код и данные,
рыми код оперирует, и охраняющий и то и другое от внешнего вмешатель-
неправильного использования.
с Ъектн°-ориентированном языке код и данные могут быть связаны вме-
В Им °бразом, что возникнет относительно автономный черный ящик.
ев ^ яЩика находятся все необходимые данные и код. Когда код и данные
еКт__ Ме*ДУ собой таким способом, создан объект. Другими словами, объ-
Данн ° устР°йство, поддерживающее инкапсуляцию. Внутри объекта код,
Юльк ли и то и другое могут быть закрытыми (private) (т. е. доступными
Для этого объекта) или открытыми (public). Код или данные, являю-
88 Глава 4. Объектно-ориентированное программирование
щиеся закрытыми, известны и доступны лишь другой части этого объекта, а
означает, что закрытые код или данные недоступны никакому фрагменту n, °
граммы, не принадлежащему объекту. Если код или данные открыты, то а
доступны другим частям нашей программы, хотя и определены внутри об
екта. Обычно открытые компоненты объекта используются для организащ,
управляемого интерфейса с закрытыми компонентами объекта.
Полиморфизм — это свойство, позволяющее использовать один и тот же ии
терфейс для целого класса действий. Специфическое действие определяете
конкретной ситуацией. Рассмотрим, например, стек (т. е. очередь, организо
ванную по принципу «последним обслуживается первый в очереди»). В нацм
программе могут потребоваться три разных вида стека. Один стек
используется для целых чисел, другой для чисел с плавающей запятой и еще одцн
для литер. В этом случае алгоритм, реализующий стек, будет одним и тем
же, хотя хранящиеся в нем данные различны. В языке, не поддерживающем
объектно-ориентированный стиль, нам, возможно, пришлось бы создать три
разных набора подпрограмм управления стеком, каждый из которых
вызывался бы по своему отдельному имени и имел отдельный интерфейс. Однако
благодаря полиморфизму в C++ мы можем создать один общий набор
подпрограмм управления стеком (один интерфейс), работающий во всех этих трех
конкретных ситуациях. Таким образом, если мы знаем, как использовать один
стек, мы можем пользоваться всеми тремя. Понятие полиморфизма часто
описывается выражением «один интерфейс, много методов». Это означает, что
можно создать общий интерфейс для группы родственных видов
деятельности. Полиморфизм помогает снизить сложность, позволяя использовать один
интерфейс для задания общего класса действий. Теперь это уже дело
компьютера выбрать определенную спецификацию (т. е. метод), применимый к той или
иной ситуации. Первые объектно-ориентированные языки программирования
были интерпретируемыми, так что полиморфизм, конечно, поддерживался в
процессе работы программы. Однако С+н—это компилируемый язык.
Поэтому в C++ полиморфизм поддерживается и во время работы программы, и при
компиляции.
Наследование — это процесс, посредством которого один объект может
приобретать свойства другого объекта. Это важно потому, что такой процесс
поддерживает принцип иерархической классификации. Знания, как правило,
обозримы именно из-за того, что они организованы иерархически (т. е. сверху
вниз). Например, Студент является Личностью. Без использования
классификации для каждого объекта пришлось бы в явном виде задавать все его
характеристики. Однако с помощью наследования в объекте достаточно
задавать лишь те его качества, которые отличают его от всех объектов того же
класса. Он может наследовать общие атрибуты от своего предка. Таким обра'
зом, именно механизм наследования свойств позволяет объекту быть
специфическим экземпляром более общего случая.
Многие конструкции C++ специально предназначены для поддержки
инкапсуляции, полиморфизма и наследования. Однако мы можем писать на C++
программы любого типа, применяя самые разные подходы. То, что язык C++
поддерживает объектно-ориентированное программирование, не означает, чт°
4.5. Объектно-ориентированные языки
89
можно писать только объектно-ориентированные программы. Как и
на й редшественника С, самое крупное преимущество C++ состоит в его
5.2. Язык Java
то объектно-ориентированный язык программирования, разработан-
" омпанией Sun. Он имеет много общих черт с языками С, C++, Objective С
ЙЬ1 имер циклы for имеют одинаковый синтаксис во всех этих четырех язы-
л я0 он не основан ни на одном из этих языков, и разработчики не стреми-
сделать его совместимым с ними. Java —это язык, используемый в сети
и ронет. Большое влияние на него оказал язык C++. Java и C++ используют
и тот же базовый синтаксис, и объектно-ориентированные конструкции
ыка Java похожи на аналогичные конструкции C++. В самом деле, если
особо не присматриваться, легко спутать программу на языке Java с программой
„ C++. Из-за их внешнего сходства часто ошибочно считают, что Java —это
просто альтернатива C++. Но хотя эти языки родственны, Java и C++
были разработаны для решения разных классов задач. C++ оптимизирован для
создания высокоэффективных программ. Для этой цели C++
компилируется в высокоэффективный исполняемый код. Java оптимизирован для создания
переносимых программ. Для обеспечения переносимости Java компилируется
в псевдокод (называемый байт-кодом Java), который обычно
интерпретируется. Это делает программы на языке Java очень хорошо переносимыми, но
не очень эффективными. Поэтому Java—превосходный язык для Интернет-
приложений, которые должны работать на самых разных компьютерах. Но
для высокоэффективных программ C++ останется наиболее
предпочтительным языком.
Из-за сходства между Java и C++ большинство программистов, умеющих
писать на C++, легко переходят на язык Java. Навыки и знания,
приобретенные при знакомстве с C++, несложно перенести на язык Java. Однако
некоторые важные отличия все же существуют, так что будьте внимательны, чтобы
не прийти к ошибочным выводам.
Первоначально язык Java был создан потому, что C++ неадекватен для
екоторых задач. Поскольку разработчики не были связаны необходимо-
ыо сохранять его совместимость с существующими языками, они смогли
сть и исправить выявленные практикой недостатки более ранних объектно-
ентированных языков. Было добавлено несколько новых возможностей, от-
твовавщих в C++, такие, как сборка мусора и многопотоковость. Некото-
озможности C++, оказавшиеся на деле не столь полезными, как в теории,
Д. множественного наследования и перегрузки операций, были отброшены.
пор продолжаются споры, насколько удачен был этот выбор.
безо Важнее то, что язык Java был разработан «с нуля», чтобы обеспечить
Мы б Н°е Выполнение программы в сети, даже если источник этой програм-
ния ненадежньш и, возможно, злокозненным. Это потребовало устране-
отсут Некоторых возможностей языков С и C++. Наиболее примечательно
теоре1, е в ЯЗЫке Java указателей. Программы не могут (по крайней мере
ски) иметь доступ к произвольному адресу в памяти.
90
Глава 4. Объектно-ориентированное программирование
Кроме того, Java была разработана не только как независимая от аппара
ной платформы система, вроде С, но и в виде скомпилированного двоично
кода. Поскольку это невозможно сделать для процессора произвольной ару
тектуры, Java скомпилирована в промежуточный байт-код, интерпретируем^
в процессе исполнения специальным интерпретатором Java. Поэтому для це
реноса языка Java на новую платформу достаточно загрузить интерпретатоп
Кроме того, язык Java был спроектирован для написания безошибочныу
программ. В поставляемых программах на С содержится в среднем одна
ошибка на 55 строк кода. Примерно половина этих ошибок связана с выделением
свободной памяти и с ее освобождением. Напротив, Java обладает рядом
особенностей, снижающих вероятность появления ошибок в программах:
• строгий контроль типов;
• отсутствие ненадежных конструкций;
• язык компактен, так что им легко овладеть в совершенстве;
• тексты легко писать и читать;
• Java объектно-ориентирован, так что повторное использование кода не
вызывает затруднений;
• Java поддерживает параллельные вычисления;
• в Java имеется много встроенных классов.
Синтаксис в Java намеренно сделан подобным С и C++. Он тоже различает
строчные и прописные буквы. Ниже мы опишем синтаксис программирования
на языке Java.
• Типы данных
Основные типы данных в языке Java очень похожи на те, что
применяются в С. Добавлены булевы массивы, массивы строк и истинные массивы.
Однако реализация типов данных сделана существенно более четкой в
нескольких отношениях:
1. В С и C++ ряд особенностей реализации зависят от платформы и
компилятора (например, разрядность int). В Java все такие параметры
заданы явно.
2. Java запрещает преобразование друг в друга произвольных
переменных. Разрешены лишь преобразования числовых переменных и
преобразования между классами и надклассами одного и того же объекта.
3. Все числовые переменные в Java имеют знак.
Ниже мы перечислим встроенные типы данных, имеющиеся в Java:
— boolean
1-бит. Может принимать только 2 значения: true и false,
true и false являются определенными константами языка. Это не то
же самое, что True и False, TRUE и FALSE, нуль и не-нуль, 1 и 0 или
любое другое численное значение. Булевы переменные не могут быть
преобразованы ни в какой другой тип переменных, и никакая другая
переменная не может быть преобразована в булеву переменную.
4.5. Объектно-ориентированные языки
91
byte
1 байт, со знаком (дополнительный код).
Охватывает значения от —128 до 127.
__ short
2 байта, со знаком (дополнительный код).
Значения от —32 768 до 32 767.
— int
4 байта, со знаком (дополнительный код).
От —2 147 483 648 до 2 147 483 647. Как и для всех числовых типов,
переменные типа int можно преобразовывать в переменные других
числовых типов (byte, short, long, float, double). Если проводятся
приближенные преобразования (скажем, int в byte), то они
делаются по модулю длины более «короткого» типа.
— long
8 байтов, со знаком (дополнительный код).
От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807.
— float
4 байта, IEEE 754.
Охватывет числовой диапазон
от 1.40129846432481707е-45до 3.40282346638528860е + 38
(положительные или отрицательные). Как и для всех числовых типов,
переменные типа float могут быть преобразованы в переменные других
числовых типов (byte, short, int, long, double). При приближенных
преобразованиях в переменные целых типов (скажем, float в short)
дробная часть отбрасывается, и преобразование делается по модулю
длины более «короткого» типа.
— double
8 байтов, IEEE 754.
Охватывает диапазон значений
от 4.94065645841246544е - 324d до 1.79769313486231570е + 308d
(положительные и отрицательные).
— char
2 байта без знака, Unicode.
Объекты типа char —это не то же самое, что объекты типа byte, int,
short или String. Их нельзя преобразовать ни в какой другой тип, и
эти типы нельзя преобразовать в тип char.
~~ String
Строка (string) — это объект. Он содержит 0 или большее число знаков,
заключенных в кавычки.
array
Массивы — это объекты. Многомерные массивы создаются как массивы
массивов.
Оператор sizeof не нужен в языке Java, поскольку все размеры
определены точно. Например, данные типа int всегда содержат 4 байта.
92
Глава 4. Объектно-ориентированное программирование
Java включает те же операторы if, else, for, while и switch, что j»
язык С. Однако все проверки условий должны возвращать булевы значв
ния. Поскольку операторы присваивания и арифметические операторы gp
возвращают булевых значений, некоторые из наиболее запутанных провв
рок условий языка С в языке Java запрещены.
• Аргументы командной строки
Аргументы командной строки похожи на принятые в С, но argv стал
массивом строк, называемым args, a args [0] стал первым аргументом командной
строки, а не именем программы.
• Комментарии
Java поддерживает обе формы комментариев — ту, что принята в С, и ту
что принята в C++:
/* Это комментарий С и Java*/
// Это комментарий C++ и Java
Однако комментарии, начинающиеся с /**, обрабатываются
компилятором особым образом. Эти комментарии должны применяться только перед
объявлениями. Они указывают, что такой комментарий должен быть
включен в автоматически генерируемую документацию для этого объявления.
• Классы
Java не поддерживает множественного наследования. Надклассы класса
обозначаются ключевым словом extends.
• Методы
В отличие от C++, методы должны определяться внутри блока,
определяющего класс.
• Параллелизм
Java поддерживает параллелизм на уровне нитей (многопотоковость).
Модель включает нити, синхронизацию и мониторы.
Многие языковые конструкции были удалены из C++, поскольку
разработчики языка Java полагают, что эти конструкции делают C++
ненадежным и трудным для чтения. К удаленным возможностям относятся #def ine,
typedef ine, перегрузка операций, enums, union и struct. Другая важная
возможность, которая была удалена,— это арифметика указателей. Это делает
язык Java более надежным и устойчивым, чем C++. Кроме того, были
удалены глобальные переменные, функции, не являющиеся членами класса,
дружественные функции и виртуальные функции.
В Java были дополнительно включены такие возможности, как истинные
массивы с проверкой границ, сборка мусора, параллелизм, интерфейсы (из
языка Objective С) и пакеты. В Java не нужно явным образом освобождать
память.
4.5. Объектно-ориентированные языки
93
, уПоминали, что в Java нет указателей. Значит ли это, что в Java нет
тх списков? Как построить связный список без указателей? В java.util
свЯ „„а класс Vector. Он может делать все то, что может делать связный
имеется л л
ок В конце концов, повторное использование кода —это одно из основ-
СП гвойств ООП. Все переменные объектов в Java суть ссылки, тогда как в
НЬ их языках они могут быть указателями. Главное различие состоит в том,
«и не можем производить арифметические операции со ссылками, тогда
с указателями это возможно. Поэтому всякий раз, когда в C++ нам ну-
указатель, в Java нам нужно использовать сам объект. С другой стороны,
основные типы данных
byte, short, int, long, float, double, char, boolean
являются ссылками. Если нам нужно сослаться на один из них, нам
придется сначала «обернуть» этот тип данных в некий класс. Java предоставляет
готовые «оберточные» классы для
Byte, Short, Integer, Long, Float, Double, Character, Boolean.
В Java есть встроенные классы String, Vector и Bit set. Кроме того, в
Java есть встроенные классы Biglnteger и BigDecimal для работы с большими
целыми числами и с большими числами в десятичной системе. Ниже приведена
программа, показывающая применение класса Biglnteger.
//Verylong.Java
import Java.math.*;
public class Verylong
{
public static void main(String [] argv)
{
Biglnteger bl = new Biglnteger(2345");
Biglnteger b2 = new Biglnteger(6789");
Biglnteger b3 = new Biglnteger("-56712");
Biglnteger b4 = bl.add(b2);
System.out.println("b4 = " + b4)
Biglnteger b5 = bl.multiply(b3);
System.out.Println("b6 = " + b5)
Biglnteger b6 = b3.abs();
System.out.println("b6 = " + b6)
^Integer b7 = bl.add(b2);
System.out.printIn("b7 = " + b7)
double x = bl.doubleValueQ;
ystem.out.println("x = " + x);
94 Глава 4. Объектно-ориентированное программирование
В языке Java определен один особый класс, класс Object, являющий
предком всех других классов. Его можно также считать классом-контейнерп *
В нем описано двенадцать компонентов: один конструктор и одиннадцать \
тодов. Четыре из них, а именно cloneO, hashCodeQ, equalsQ и toStrine^
предназначены для того, чтобы они могли быть переопределены. Посредство
этого они станут четкими и непротиворечивыми инструментами дупликат.
объектов и выяснения того, когда они одинаковы. В следующей программ
показано небольшое применение класса Object.
// MyObject.Java
import Java.math*; // for Biglnteger, BigDecimal
public class MyObject
public static void main (String args [])
■C
Object[] a = new Object[5]
a[0] = new String ("Good Morning");
a[l] = new IntegerD567);
a[2] = new Character('X');
a[3] = new DoubleC.14);
a[4] = new Biglnteger(34567890");
for(int i=0; i<a.length; i++)
•C
System.out.printIn(a[i]);
}
a[3] = new BigDecimalO'll.5678903452111112345");
System.out.println(a[3]);
boolean b = a[4].equals(a[3]);
System.out.printlnC'b = " + b); // => false
Object [] x = new Object [1];
x[0] = new FloatB.89);
boolean r = x.equals(a);
System.out.printlnC'r = " + r); // => false
}
}
Java все передает по значению. Когда мы передаем некому методу основные
типы данных, мы делаем отдельную копию соответствующего основного типа
данных. Когда передаем методу дескриптор (handle), то делаем копию
этого дескриптора. Поэтому все передается по значению. Это позволяет считать
4.5. Объектно-ориентированные языки
95
тор чем-то вроде «объекта», поскольку дескриптор неявным образом
дес^Р БЬ1вает его при каждом вызове метода. Это показано в следующей
^оГрамме:
// pass.java
import java.math.*;
public class Pass
static void inverse(int x)
{
x = _x>
System.out.println("x = " + x); // => -7
}
static void twice(Biglnteger b)
{
Biglnteger b2 = new Biglnteger(");
b = 22.multiply(b);
System.out.printIn(b); // => 666666
}
public static void main(String[] argv)
{
int j = 7;
inverse(j) ;
System.out.println("j = " + j); // => -7
Biglnteger Ы = new Biglnteger(33333");
twice(bl);
System.out.println(bl); //=> 333333
}
Массивы всегда передаются по ссылке. Следующая программа показывает
Для основных типов данных и абстрактных типов данных.
11 passI.java
imP°rt java.math.*;
РЦЬ1*с class Passl
^atic void inverse (int x[])
x[0]
}
-x[0]
96 Глава 4. Объектно-ориентированное программирование
static void twice(Biglnteger b[])
{
Biglnteger bl = new Biglnteger(");
b[0] = bl.multiply(b[0]);
}
public static void main(String[] argv)
{
// отведение памяти для одномерного массива
// с одним элементом
int [] j = new int[l];
j[0] = 7;
inverse(j);
System.out.println("j[0] = " + j [0]); // => -7
Biglnteger [] b = new Biglnteger [1];
b[0] = new BigIntegerC'333333");
twice(bl) ;
System.out.println("b[0] = " b[0]); // => 666666
}
>
В следующей программе мы покажем, как можно реализовать в
языке Java рациональные числа. Класс Rational, java реализует
рациональные числа и арифметические операции с ними: сложение (add), вычитание
(substract), умножение (multiply) и деление (divide). Этот класс расширяет
класс java. long.Number, реализуя абстрактные методы этого класса. Методы
equals, toString и clone из класса Object переопределены.
// Rational.java
import java.lang.*;
class Rational extends Number
{
private long num;
private long den;
public Rational(long num,long den)
{
this.num = num; this.den = den;
}
private void normalsize()
{
long num = this.num;
4.5. Объектно-ориентированные языки
10ng den = this'den;
if(den < 0)
„ - (-l)*num; den = (-l)*den;
У
У
private void reduce()
^kis.normasizeO ;
long g = gcd(this.num,this.den);
this.num /= g;
this.den /= g;
}
private long gcdClong a,long b)
{
long g;
if (b == 0)
{
return a;
}
else
{
g = gcd(b,(a7.b));
if(g < 0) return -g;
else return g;
}
}
Public long num() { return this.num; }
public long den() { return this.den; }
Public void add(long num, long den)
this.num = (this.num*den) + (num*this.den);
this.den = this.den*den;
this.normalsize();
PubHc void add (Rational r)
his.num = (this.num*r.den()) + (r.num()*this.den)
this.den = this.den * r.denQ;
4 ~1548
98 Глава 4. Объектио-ориевтироваяное программировав,
this.normalsize() ;
}
public void subtract(long num.long den)
{
this.num = (this.num*den) - (num*this.den);
this.den = this.den * den;
this.normalsizeO ;
}
public void subtract(Rational r)
{
this.num = (this.num*r.den) - (r.num()*this.den);
this.den = this.den*r.den0;
this.normalsizeO ;
}
public void multiply(long num.long den)
•C
this.num = (this.num*num);
this.den = (this.den*den);
this.normalsizeO ;
}
public void multiply(Rational r)
{
this.num = (this.num*r.num());
this.den = (this.den*r.den());
this.normalsizeO ;
}
public void divide(long num.long den)
{
this.num = (this.num*den);
this.den = (this.den*num);
this.normalsizeO ;
}
public void divide(Rational r)
•C
this.num = (this.num*r.den());
this.den = (this.den*r.num());
this.normalsizeO ;
}
public static boolean equals(Rational a,Rational b)
4.5. Объектно-ориентированные языки
i к* nUm()*b.den()) == (b.num()*.den()))
{ return true; }
r return false, i
У
blic boolean equals(Object a)
-f(!(a instanceof Rational))
/ return false; }
return equals(this,(Rational) a);
}
public Object cloneO { return new Rational(num,den); }
public String toStringO
{
StringBuffer buf = new StringBufferC2);
long num, den, rem;
this.reduce();
num = this.num;
den = this.den;
if(num == 0) return ";
if (num == den) return ";
if(num < 0)
{
buf.append("-") ;
num = -num;
}
rem = mim*/.den;
if (num > den)
{
buf.append(String.valueOf(num/den));
if(rem == 0)
{
return buf.toStringO;
•r else
i.
Ь«.append(" '■);
}
ur-append(String .valueOf(rem));
b^-*PPend(V);
•aPpend(String.valueOf(den));
100 Глава 4. Объектно-ориентированное программирование
return buf .toStringO ;
}
public float floatValueO
{
return (float) ((float)this.num/(float)this.den);
}
public double doubleValueO
{
return (double) ((double)this.num/(double)this.den);
}
public int intValue()
•C
return (int) ((int)this.num/(int)this.den);
}
public long longValue()
•C
return (long) ((long)this.num/(long)this.den);
}
public void print()
System.out.print(this.toString()) ;
>
public void printlnO
{
System.out.println(this.toString());
}
// метод mainO используется для проверки других методов.
public static void main(String args[])
•C
Rational rl = new Rational(-4,6);
Rational r2 = new RationalA3,6);
rl.add(r2);
System.out.println(r1.toString());
Rational r3 = new RationalA23,236);
Rational r4 = new Rational(-2345,123);
r3.multiply(r4);
System.out.println(r3.toString());
4.5. Объектно-ориентированные языки
101
^^^ 5 = riew Rational C,6) ;
Rati0lia r6 = new Rational A,2);
&atioIia bi = equals (r5,r6) ;
bo0leaI1oUt.println("M = " +bl);
System' b2 = equals(r4,r5);
booleaDout.println("b2 = " +b2);
System-
-, r7 = new RationalC,17) ;
Ratl0llal r8 = (Rational) r7.clone();
RatlTout.println("r8 = " + r8.toString());
, // end main
} // end class Rational
r гледующих программах показано, как можно реализовать класс Matrix,
йзованы два конструктора, а методы equals и toString из класса Object
переопределены.
// Matrix. Java
class Matrix
{
private int rows, columns;
public double entries [] [] ;
Matrix(int m.int n)
{
rows = m;
columns = n;
entries = new double [m] [n] ;
int i, j;
for(i=o; j< rows; i++)
f°r(i=o; j< columns; i++)
entries [i][j] =o.O;
Mat._v
"atrix(int m.int n,double[][] A)
int i
^luains = n.
entries = A:
P'ibliv
{ ic v01d add(Matrix M)
if (th •
is.rows != M.rows) || (this.columns != M.columns))
102 Глава 4. Объектно-ориентированное программирование
•с
System.out.printlnC'matrices cannot be added");
System.exit@);
}
int i, j;
for(i=0; Kcolumns; i++)
for(j=0; Krows; j++)
this.entries[i] [j] = this.entries[i][j] + M.entries[i][j]
}
public Matrix multiply(Matrix M)
{
if i, j. t;
if(columns != M.rows)
{
System.out.printlnC'matrices cannot be multiplied");
System.exit@);
}
Matrix product = new matrix(rows.M.columns);
for(i=0; j<rows; i++)
{
for(j=0; j<M.columns; j++)
{
double tmp = 0.0;
for(t=0; t<columns; t++)
tmp = tmp + entries[i][t]*M.entries[t][j];
product.entries[i] [j] = tmp;
}
}
return product;
}
public void randomize()
{
int i, j;
for(i=0; j<rows; i++)
for(j=0; j<columns; j++)
entries[i] [j] = Math.randomO
}
public boolean equals(Matrix A,Matrix B)
i
int i, j;
for(i=0; j<rows; i++)
{
for(j=0; j<columns; j++)
4.5. Объектно-ориентированные языки
{f(A.entries[i][j] != В.entries [i] [j] )
Jeturn false;
>
>
return true;
}
ublic boolean equals(Object ob)
i:f(!(ob instanceof Matrix))
{
return false;
return equals(this,(Matrix) ob);
}
public Object clone()
{
return new Matrix(rows,columns,entries);
}
public String toStringO
{
int i, j;
String result = new StringO;
for(i=0; Krows; i++)
{
for(j=0; j<columns; j++)
{
result = result + String.valueOf(entries[i][j])+" ";
result = result + "\n";
}
result = result + "\n";
return result;
}
Public void onStdoutQ
System.out.println(toString()) ;
Pu lie static void main(String args[])
atrix м = new MatrixB,2);
104 Глава 4. Объектно-ориеитированяое программирование
М.entries[0][0] = 3.4; М.entries [0] [1] = 1.2;
М.entries[1][0] = 4.5; М.entries[1][1] = 5.8;
Matrix N = new MatrixB,2);
N. entries[0][0] = 6.4; N. entries[0][1] = -1.2;
N. entries[1][0] =8.5; N. entries[1][1] = 6.8;
M.add(N);
System.out.println("M = \n" + M.toStringO) ;
System.out.println("\n") ;
Matrix X = new MatrixB,2);
X = M.multiply(N);
System.out.println("X = \n" + X.toStringO) ;
Matrix Y = new MatrixB,2);
Matrix Z = new MatrixB,2);
boolean bl = Y.equals(Z);
System.out.printlnC'bl = " +bl);
Z.randomize();
System.out.println("Z = \n" +Z);
boolean b2 = Y.equals(Z);
System.out.println("b2 = " +b2);
System.out.println("\n");
double d[][] = new double [2] [2] ;
d[0][0] = 2.1; d[0][0] = -3.4;
d[l][0] = 0.9; d[l][l] = 5.6;
Matrix В = new MatrixB,2,d);
B.add(B);
System.out.printlnC'B = \n" + B.toStringO);
System.out.println("\n");
Matrix U = (Matrix) X.cloneO;
System.out.println("U = \n" + U.toStringO);
}
Чтобы проиллюстрировать различия между C++ и Java, мы представим
программы построения бинарного дерева. Программа строит бинарное дерево
поиска из 100 000 вершин. Содержимое дерева обходится с помощью
последовательного обхода. Заметим, что элементы упорядочены в восходящем порядке
обхода. В общем, C++ работает значительно быстрее, чем Java.
4.5. Объектно-ориентированные языки
6ййарно^ереВ° С++:
ц tree.cpP
aincJude <iostream.h>
#include <stdlib.h>
class Tree
{
private:
int data;
Tree *left, *right;
public:
Tree(int);
void insert(int);
void inorderQ ;
};
Tree: .-Tree (int n) : data(n) , left (NULL), right(NULL) {}
void Tree: ".insert(int n)
{
if(n < data)
{
if(left != NULL)
left->insert(n);
else
left = new Tree(n);
}
else
if(n > data)
{
if(right != NULL)
right->insert(n);
else
right = new Tree(n);
}
}
void Tree::inorder()
if(this != NULL)
left->inorderQ.
'/ здесь печатать данные...
i-iglit-^iiiorderO.
}
106 Глава 4. Объектяо-ориент.
void main()
{
Tree *root = NULL;
int i, m, n = 100000;
for (i = 0; i < n; i++)
{
m = randQ;
if(root == NULL)
root = new Tree(m);
else
root->insert(m) ;
}
root->inorder() ;
}
Бинарное дерево Java:
// tree.Java
import java.util.Random;
class Tree
{
private int data;
private Tree left = null;
private Tree right = null;
public Tree(int n)
{ data=n; }
public void insert(int n)
{
if (n<data)
{
if (left != null)
left.insert(n);
}
else
left = new Tree(n);
}
else
if(n>data)
{
if(right != null)
right.insert(n);
else
right = new Tree(n);
}
4.5. Объектно-ориентироваляые языки
107
,,r void inorderO
риЫ1С
{ if(left != null)
,eft.inorderО;
// здесь печатать данные...
if (right '= null)
right. inorder U;
}
ublic static void main(String args[])
Random r = new Random(start);
Tree t = null;
int n = 100000;
int i;
for(int i=0; i<n; i++)
{
int m = Math.abs(r.nextlnt()\'/.1000);
if (t == null)
t = new Tree(m);
else
t.insert(m);
}
t.inorderO ;
}
}
4.5.3. Другие объектно-ориентированные языки
Кроме C++ и Java, есть ряд других объектно-ориентированных языков. В
настоящем разделе мы вкратце обсудим Eiffel, Smalltalk и Oberon.
Eiffel [57] — это чисто объектно-ориентированный язык программирования
со структурой управления, аналогичной языку Паскаль. Он удовлетворяет
следующим требованиям к реализации абстрактных типов данных (АТД) в языке
программирования:
язык должен позволять определять новые, задаваемые пользователем типы
Данных, т. е. обеспечивать средства построения АТД;
н должен поддерживать создание одного или более экземпляров опреде-
емых пользователем типов данных, соответствующих экземплярам АТД;
нем должны быть средства поддержки процедурных абстракций, т. е. он
Д лжен обеспечивать процедуры и функции с подходящими механизмами
Редачи параметров для реализации операций с АТД;
^ «к должен допускать реализацию общих АТД;
# Должен полностью поддерживать инкапсуляцию;
Должен поддерживать повторное использование компонент программ
°торые из предыдущих требований также способствуют этому).
108 Глава 4. Объектно-ориентированное программирование
Абстрактные типы данных легко реализуются в Eiffel в качестве кла^
(соответствующих типам). Программирование в Eiffel включает постро °6
новых классов из существующих классов либо посредством задаваемых к ^
ентом взаимоотношений, либо посредством наследования, либо обеими эт "
способами. Идея договора между компонентами программ приводит в п
граммистской практике к увеличению вероятности порождения безошибочл
программ. Eiffel можно описать как язык, который: х
• основан на манипуляциях с объектами,
• строго контролирует типы данных,
• поддерживает создание безошибочных программ,
• способствует легкости повторного использования кода,
• основан на теории абстрактных типов данных,
• позволяет строить новые типы данных из существующих,
поддерживает метод программирования, называемый программированием
посредством контракта.
•
Объект в Eiffel является экземпляром класса, а класс — описанием типа
данных. Типичный класс состоит из описания операций, применимых к
экземплярам соответствующего типа данных, вместе с описанием хранящихся данных.
Поэтому нам нужно изучить следующее:
1. Как создавать отдельные объекты — экземпляры типа данных.
2. Как записывать описания классов, реализующих типы данных.
3. Как разрабатывать и выполнять программу на Eiffel.
Начнем с того, что покажем, как объявлять объекты (посредством
объявлений) и как применять к ним операции, присущие этому типу данных
(посредством инструкций). В Eiffel операции, присущие некому типу данных,
реализуются как свойства (features) этого класса. Поэтому мы вкратце рассмотрим,
как создаются новые классы. В последнем разделе мы объясним, как строить
на языке Eiffel систему (исполнимую программу), что позволит нам заняться
практическими вопросами программирования.
Типичная программа на Eiffel манипулирует объектами. Чтобы облегчить
построение инструкций, определяющих, какие манипуляции должны быть
осуществлены, для наименования объектов применяются идентификаторы.
Например, чтобы объявить, что некий идентификатор является ссылкой на
объект типа PERSON, мы пишем:
р: PERSON
где р является ссылкой на объект, a PERSON—имя определенного ранее типа
данных. Мы будем говорить, что р относится к типу PERSON. Чтобы объявить
идентификатор для очереди объектов типа PERSON, мы пишем:
q: QUEUE[PERSON]
где QUEUE—это обобщенный тип данных, т.е. тип данных QUEUE был
реализован таким образом, что тип элементов, содержащихся в конкретной очереди*
4.5. Объектно-ориентированные языки
109
ялан в тот момент, когда объявляется объект «очередь». Таким обра-
1ЛоГбьГГЬ струкции QUEUE [PERSON] квадратные скобки [PERSON] представля-
зо^' в „ обобщенный параметр, который определяет, что очередь содержит
К)Т с0 тйпа PERSON. Следовательно, тип данных QUEUE [PERSON] является
эЛеменТ цией ранее определенного обобщенного типа данных QUEUE.
сцеИиа^ , имеется несколько основных типов данных: INTEGER, REAL,
м CHARACTER и DOUBLE, которые можно использовать при объявле-
В нтификаторов. Например, ниже i и г —это ссылки на объекты типа
^TEGEB и REAL соответственно:
i: INTEGER
Г: REAL
R чтих примерах для наименования объектов использовались идентифика-
ы из одной буквы, но обычно бывает удобнее пользоваться более содержа-
рльными именами, чтобы программу было легче читать. В Eiffel
идентификатор может состоять из одной или большего числа букв и/или цифр, если
первым символом является буква. В идентификатор можно также включать
символ подчеркивания (_). На число символов в идентификаторе нет
ограничений, прописные и строчные буквы не различаются. Вот некоторые примеры
законных идентификаторов в языке Eiffel:
address, make, phone_immber, size, СЗРо, the_Date,
customer1, customer2, put_char, addToQueue, PERSON
Eiffel отличает язык, применяемый для определения типов данных, от самих
типов данных. Производители программ обычно предлагают клиентам
обширную библиотеку часто используемых классов, таких, как списки, деревья и
проч., дающих пользователям набор повторно используемых компонент, из
которых они могут начать строить новые приложения. Несмотря на отделение
языков от библиотек классов, Eiffel признает существование основных типов
данных: INTEGER, REAL, BOOLEAN, CHARACTER и DOUBLE. Каждый из них
обладает набором стандартных операций и используется в точности так же, как те
типы, что мы вводили до сих пор. Однако есть и некоторые отличия, которые
мы теперь изучим.
Eiffel механизм объявления объектов, в котором их создание и
инициация разделены, излишне громоздок и неэффективен для основных типов
bix. Поэтому представляется упрощенный способ. Например, объявление
i: INTEGER
дат область памяти, в которой хранится целое число i. Кроме того, i
ссы ализиРУется значением по умолчанию нуль. Таким образом, i — это не
где На некое Целое число: i непосредственно связано с местом в памяти,
Ной НИтся это число. Это эквивалентно обычной (не указательной) перемен-
Eiffel ^гих языках. Подобный тип данных называется расширенным типом.
типо °3воляет связывать идентификатор непосредственно с памятью для всех
еКть ^нных, обходясь тем самым без механизма ссылок. Однако обычно объ-
Рассматриваются как динамические в том смысле, что каждый из них
110 Глава 4. Объектно-ориентированное программирование
существует ограниченное время, поскольку они создаются, изменяют
чтожаются в процессе исполнения программы. Механизм ссылок пол Й ^Ч
ет этот подход. Поэтому в Eiffel есть и ссылочные типы (обычная с ^^а
и расширенные типы (используемые преимущественно для основны ^N
В следующей таблице приведены исходные значения по умолчанию
ных типов:
Для
°сц0
Класс
Значение по умолчанию
INTEGER 0
REAL 0.0
CHARACTER '%U' (нулевой символ)
DOUBLE 0.0 (двойная точность)
BOOLEAN ложь
Рассмотрим теперь конструкцию класса:
class <гиля-класса>
creation
— имена процедур, используемых при создании
новых экземпляров
feature
— объявления свойств класса
end — CLASS.NAME
Объявление класса может содержать разные разделы, каждый из которых
обозначен ключевыми словами, вроде creation и feature. Хотя это и
необязательно, рекомендуется включать имя класса в качестве комментария после
ключевого слова end.
В случае ссылочных типов процесс инициализации означает, что мы можем
построить несколько процедур создания для одного типа данных, чтобы
обеспечить инициализации, пригодные для разных ситуаций. Заметим, что
существование процедуры создания означает, что инициализация по умолчанию не
создает настоящего объекта. Поэтому Eiffel настаивает, чтобы мы вызвали одну
из процедур инициализации, а в противном случае он порождает сообщение о
ошибке. Для расширенных типов, однако, поскольку процедура создания
автоматически вызывается системой Eiffel при создании объекта, допускается лишь
одна процедура создания во избежание неоднозначности.
Smalltalk можно охарактеризовать как сочетание языка программирован >
операционной системы, среды программирования и методологии разработк
программирования [17], [35]. Эту систему можно рассматривать как средс
представления знаний, а значит, как серьезного конкурента языкам ЛиС
Пролог. Она предпочтительна для разработки прототипов приложений,
тем интерактивной графики и имитационного моделирования. Хотя пока о
ются нерешенными некоторые проблемы со скоростью исполнения (возмо
порождаемые интерпретирующей природой системы), были достигнуты вп
тляющие результаты в снижении стоимости разработки некоторых разно
ностей программных систем, особенно в приложениях, содержащих слоЖ
л 5 Объектно-ориентированные языки 111
вактивные компоненты. Главная причина этих успехов
м чесКяе ялИ еда Smalltalk позволяет в максимальной степени восполь-
Р ит в т°м' ЧТ° вами повторного использования программных компонент.
°С Ться пРеЯМ5Г1Ц сПОдьзуемые в Smalltalk-80, являются объектами. Объект
° gee су!йноСТИ' число, букву, чертеж, список, программу или редактор и
,0*ет яРеДС^адает двумя основными свойствами:
Д ет хранить данные и осуществлять доступ к ним;
* °Й "Гжет отвечать на сообщения.
# он можеа
лй^™т л о отвечает на сообщение sin, возвращая значение О,
НаПря^еР' ооъекх «.
(ОТорое записывается в виде
0.0 sin
ми можно манипулировать лишь посредством передачи сообщений. Со-
й ия таким образом, образуют интерфейс между объектами и внешним
D Способ которым объект реагирует на сообщение, является его внутрен-
делом и нет необходимости, чтобы он был известен вне самого объекта.
Написание прикладных программ в системе Smalltalk состоит в определении
объектов, которые описывают проблему, и в определении операций для
каждого объекта. После того, как основные объекты данного приложения и операции
на них определены, программист может использовать их, не зная подробностей
их устройства; он может также повторно использовать их в других ситуациях,
чем те, в которых они были построены.
В среде Smalltalk определено несколько типов переменных, отличающихся
кругом объектов, имеющих к ним доступ:
1. Переменные экземпляра. Это переменные, принадлежащие объекту, и
существует две разновидности переменных экземпляра — именованные
переменные экземпляра и индексированные переменные экземпляра.
Именование переменные экземпляра — это те переменные экземпляра, которые
отличаются по имени. Например, в классе Point имеется две именованные
ременные экземпляра, х и у. Когда в выражении появляется имя пе-
енной экземпляра, оно отсылает к ее значению для соответствующего
пляра. Когда создается новый экземпляр, он содержит переменные
пляра, задаваемые его классом; значение по умолчанию для этих
лева Ь1х экземпляра есть объект, называемый nil. Доступ к индексиро-
с по пеРеменным экземпляра осуществляется не с помощью имени, а
имен числового индекса. В отличие от объектов, обладающих лишь
НЬ1е НЬШи переменными экземпляра, те, что содержат индексирован-
2- Пео енные, могут иметь непостоянное число переменных экземпляра,
^асса и**6 класса- Это переменные, общие для всех экземпляров данного
строит апРИМеР> в случае компьютерного черчения окружность может
Резков И3 нескольких прямолинейных отрезков, причем число этих от-
Ка* nerJw Наков° для всех окружностей; тогда это число можно определить
°""%ю класса.
ПеРеменк
112 Глава 4. Объектно-ориентированное программирование
3. Глобальные переменные. Это переменные, общие для всех обЪ|
хранятся в словаре. Например, все классы вызываются глобаль Т°6' О
менными, имена которых являются ключом доступа к словарю ^ ^t *'
4. Общие переменные. Это переменные, доступные экземплярам н
классов. Чтобы определить набор переменных, общих для нескодк К^\-
сов, необходимо определить словарь, общий для этих классов в Х^,
сам этот словарь является глобальной переменной. ' °т°Рь^
Рассмотрим теперь классы и подклассы в среде Smalltalk. Каждый
в Smalltalk принадлежит к одному и только одному классу. Однако мож Ъе1A
заться полезным сделать некоторые элементы описания общими ли» 01<а'
ких классов или описать один класс посредством другого. Эта возмпм,
о m„iu „_„ „„„,„.„-. „-. , „„„ „ ш*НосТ]
ДРУгой
реализуется в Smalltalk, где каждый новый класс описывается через "°СТь
класс, называемый его надклассом. Экземпляры нового класса совпадет
экземплярами старого, за исключением дополнительных элементов описан С
явным образом определенных для нового класса. Этот новый класс называет
подклассом своего надкласса.
Подкласс в свою очередь может иметь один или большее число подклассов
Множество классов, таким образом, принимает форму дерева, корнем которого
является класс, называемый Object. Object является единственным классом
у которого нет надкласса. Экземпляры каждого подкласса наследуют все
переменные экземпляра, переменные класса и методы от своего надкласса.
Чтобы дать полное определение некоего класса, нам нужно задать
следующее:
• Имя: Имя класса должно быть обязательно, и оно должно отличаться от
имени любого другого существующего класса.
• Надкласс: Надкласс также обязателен, и он должен соответствовать одному
из существующих классов.
• Новые переменные класса: Переменные необязательны, но если они есть, то
их имена должны отличаться от любого уже существующего имени,
определенного в множестве надклассов данного класса. Можно также добавить
новые методы и переопределить методы, уже определенные в множеств
его надклассов.
• Его новые общие переменные.
• Его новые переменные экземпляра.
• Список его новых методов.
Каждая сущность в Smalltalk является объектом. Следовательно, класс
тоже является объектом. Мы уже видели также, что каждый объект принаД
жит к одному и только к одному классу. Сам класс поэтому является эк
пляром другого класса, который мы называем метаклассом. В более ра#
версиях Smalltalk все классы были экземплярами одного-единственного м
класса. Однако в современной версии Smalltalk каждый класс является е^
ственным экземпляром некого метакласса по соображениям гибкости (осоо
при создании новых экземпляров класса).
Объектно-ориентированные языки 113
ся классами и поэтому содержат методы, позволяющие
-гаКлаССЫ яВ классам) отвечать на полученные ими сообщения. В ре-
емпляРаМ ^ ' „азываются методами классов, тогда как другие методы
"* Э ате этИ иег -, ии экземпляров. При создании класса автоматически со-
зуЛ таится >*е7П0 аСС. в отличие от других классов, метаклассы не имеют
И . ется новый м экземпляров метаклассов. Все они являются экземпля-
%п; У них Т^Кт*г0 же класса, называемого Metaclass.
„ами оДй°г0 к метаклассу класса можно, послав сообщение class. Ha-
ПолучИТЬ ^ класс точек, называемый Point. Мы можем связаться с
ПР',МетаКРлассомТпосредством выражения
Point class
являются классами, поэтому у них есть надкласс, называемый
Метакла м0жем рассматривать метаклассы двумя способами:
Class. Итак, м
Они являются экземплярами класса Metaclass.
2 Они являются подклассами класса Class.
Metaclass и Class являются двумя подклассами класса ClassDescription.
Имя переменной — это простой идентификатор, состоящий из начальной
буквы, за которой следует последовательность букв или цифр. Все символы
являются значащими. Обычно в Smalltalk принято нанизывать слова в один
идентификатор, без пробелов, в котором каждое из слов начинается с
прописной буквы, кроме, возможно, первого слова. Простые переменные начинаются
со строчной буквы (например, hydeParkCorner или redPen); имена классов,
глобальных переменных и переменных классов принято начинать с прописной
буквы (например, LargePositivelnteger, DisplayScreen и BigBen). Это
правило не является обязательным, но его рекомендуется придерживаться, чтобы
написанная программа была понятна другим программистам.
Smalltalk поддерживает пять видов литералов. Это знаки, цифры, строки,
символы и массивы литералов. Литералы Smalltalk всегда обозначают один и
же объект, и их можно считать литералами-константами.
паковые литералы являются экземплярами класса Character. Это знаки
да АЬСИ, перед которыми стоит знак доллара, например
$4 ** U П $|
пере Ыв ЛитеРалы представляют числа. Это последовательности цифр,
нУю ОТОРЬШИ может стоять знак минуса. Они могут содержать десятич-
чиспп У ° хотя ^ы одной цифрой до и после этой точки. Вот примеры
ВЬк литералов:
123 ~ю
и 3.14 -3.14 0.0 0
также числовые литералы в научной нотации, например
' е -ЗеЗ -3.13е-3
ЛиЧной теРалЬ1 могут быть также выражены в системе счисления, от-
Десятичной, если перед записью числа стоит '< base > г'. Для
114 Глава 4. Объектно-ориентированное программирование
оснований, больших 10, дополнительные цифры представляют
ными буквами, начиная с 'А'. Чро^
• Строковые литералы —это объекты, отвечающие последовав
знаков. Это экземпляры класса String, и они представляются btI°cTj
тельностями знаков, ограниченных апострофами, например ^Аоь
'There' 'isn"t' 'any' 'more!'
Заметим, что кавычки (") используются здесь для представлени
вого литерала $' в строке 'isn"t'. Отдельные знаки в строке фак 3l,afe
представляются соответствующими знаковыми литералами. Ск
• Символьные литералы — это строки знаков, применяемые для наим
ния объектов в системе Smalltalk. Все они являются экземплярами °^
са Symbol. Они представляют идентификаторы и селекторы сообщен г'
обозначаются уникальными последовательностями знаков, перед кот
ми стоит символ #. Такие строки знаков должны начинаться с буквь '
могут содержать цифры. Некоторые примеры символов приводятся ниж
#а #alpha2 #intersectsWith #max:
• Массивы литералов — это индексированные числами структуры данных
все элементы которых являются литералами. Элементы массива литералов
должны быть разделены пробелами и заключены в скобки с одним
символом # перед ними. Ниже приведено несколько разновидностей массивов
литералов:
#( 12 3) — массив числовых литералов,
#( $а $Ь ) — массив знаковых литералов,
#( 'to' 'be' 'or' 'not' 'to' 'be') — массив строковых литералов,
#( jack frost ) — массив символьных литералов,
#( jddf 8r45 'crazy' $b A 2) ) — массив литералов.
Заметим, что в двух последних примерах символьные литералы #jack,
#frost, #jddf и массив #A 2) не требуют помещения перед ними знака
#, если они находятся внутри другого массива литералов.
Объекты могут приписываться переменным. Это осуществляется
использованием префикса приписывания. Префикс приписывания состоит из имени
переменной, за которым следует направленная влево стрелка. Его также мо»'
но повторять. Переменные Smalltalk весьма похожи на указатели языка О те >
что они всегда указывают на объект и они могут указывать на объекты любо
вида (или на зону памяти в С) в любой момент их жизни.
Переменная может быть приписана к псевдопеременной. Псевдопереме
ная — это специально определенный объект в Smalltalk, доступ к котор0
возможен лишь через имя переменной. Поскольку псевдопеременные предсТ
ляют заранее определенные объекты, за ними зарезервирован ограничен^
набор имен. Нам могут встретиться такие псевдопеременные, как nil, пре^
ставляющую неинициализированный объект неопределенного типа, true Д
логической истины и false для логической лжи.
4 5. Объектно-ориентированные языки 115
мых должны быть объявлены до того, как они будут ис-
т/ТмеН» пеРеМ разрешается использовать для переменной то же самое
оВайЫ- ^д 0бщения. Синтаксис выражения, являющегося сообщением,
" чт° и ,ДЛЯ тся ли это имя к сообщению или к переменной.
ёдеЛИТ' °Tqi rfill — это последний из семейства языков, общим прототипом
06ePoS t я Алгол-60 A960). К другим членам этого семейства отно-
^горых явЛЯ /iQ70) и Модула-2 A979). Паскаль не свободен от некоторых
^гся П»сК^^*_ ВБОдных курсов программирования они несущественны, но
яеДОстатКОБ' „ыми при создании больших систем. Паскаль поощряет струк-
тогда как в разработке программных систем все большее
Typsyi° яобретает модульность. Это понятие имеет по крайней мере два
злачевяе Р ^ известен как скрытие информации. Любая большая систе-
аспекта- модулей, разрабатываемых в значительной степени независимо
• W** л „ otrnmrntu Ttrrn Г>ТГТТГО/^Т'Г>ТГОТ1 И ОТ/-«"fir УЖЪ1гг£^Т\/Т\ОТХП ЛТТТЧО ТТ/3 ттст
ма сип Отсюда следует, что существует некий интерфейс, определя-
rmvr ОТ (*Л1г У
яй все свойства каждого модуля, доступные другим модулям, и скрыва-
Ю^^ _ «чП.тпта Rrric\r*r»TT si^ttowt— frovHWruorwwii si мляонггг» плоила
. „_, „их Все остальные. Второй аспект — технический, а именно разделъ-
ющии от лил
колтиляч«л модулей, при которой компилятор проверяет правильное
негодование их интерфейсов. Когда некий модуль скомпилирован, описание
его интерфейса записывается в его символьный файл. Во время компиляции
модуля-клиента компилятор берет символьные файлы импортированных
модулей и получает тем самым всю необходимую информацию для проверки
типов.
Принципиальным новшеством языка Модула-2 по сравнению с Паскалем
была именно модульность, включающая скрытие информации, и раздельная
компиляция. В отличие от независимой компиляции, известной из ассемблеров
и других компиляторов языков, раздельная компиляция позволяет
компилятору выполнять те же самые проверки согласованности типов между модулями,
что и внутри отдельного модуля. Явное определение интерфейсов и сохранение
полной надежности проверки типов оказалось огромным преимуществом.
Модули, экспортирующие один или несколько типов данных, вместе с
набором процедур, оперирующих с переменными этих типов, представляют понятие
шрактпога типа данных. В этих случаях лишь имена типов фигурируют
ерфейсе модуля, тогда как структура полей записей доступна лишь че-
спортируемые процедуры, которые поэтому полагаются на корректность
рых инвариантов, управляющих абстрактными типами.
стр е того> Модула-2 устранила один из наиболее серьезных недостатков
Ры пп контР°ля типов: здесь динамические массивы вводятся как парамет-
Менньгу Д^>' акже примечательно введение процедурных типов для пере-
МиРОва' TatOKe средств параллельной обработки и возможности програм-
Непоспе На ЯЗыках низкого уровня. Последние позволяют программисту
какре енно обращаться к устройствам конкретного компьютера, таким,
Ва способ ИНтеРфейса, для управления операциями ввода/вывода. Это сно-
°бластях ОВало расширению спектра приложений языка, особенно в таких
Не менее' пР°ектирование систем и управление процессами. Наконец, что
«^нноо Н0' были
устранены многие синтаксические дефекты Паскаля, а
торы циклов с неопределенными условиями окончания if, while
116 Глава 4. Объектно-ориентированное программирование
и for. Это именно те конструкции, которые были взяты из Алгола-бО
нены ради преемственности, чтобы избежать отторжения языка пр0г С%'
ским сообществом, привыкшим пользоваться Алголом. а1и,,Чк
После нескольких лет практического применения модульного пп
вания посредством Модула-2 и других языков системного программи К^°-
выяснилось, что конечная цель состоит в расширяемости. Структупн EaiI,,!t
дульное программирование были просто промежуточными этапами ^^
к этой цели. Введение абстракций, представленных модулями, и испо ^
ние процедур, вызывающих процедуры, объявленные на более низких v °Ba''
иерархии абстракций, воплощает процедурную расширяемость. Однаю ^
успешного проектирования столь же важна расширяемость определения ^
ных. В этом отношении язык Модула-2 неадекватен, поскольку типы не и
быть расширены и при этом оставаться совместимыми. ""'л
Объектно-ориентированные языки эффективно решили эту проблем
стали модными в 80-х. Они позволяют определять подтипы Г1, называем
подклассами, для данного типа (класса) ТО, которые обладают тем свойством
что все операции, применимые к экземплярам класса ТО, применимы так
же к экземплярам класса Т\. Мы осознали на этом этапе, что самым
важным новшеством стала расширяемость типов данных, которая, к сожалению
осталась невыявленной термином «объектно-ориентированный». Этот термин
дополнялся также целой системой новых названий уже известных понятий,
введенной для того, чтобы подчеркнуть новый подход или новую
методологию программирования «в большом». Поэтому типы стали классами,
переменные — экземплярами, процедуры — методами, а вызовы процедур —
сообщениями, и т. д.
Главное достоинство языка Оберон состоит в обеспечении расширяемости
типов данных на основе общепринятых, легко воспринимаемых понятий,
вроде типа данных и процедуры. Вследствие этого не нужно никакого разрыва с
обычными приемами программирования и не требуется привыкать к целому
новому классу понятий. Единственная новая возможность — это расширение
типа «запись». Оберон поэтому объединяет традиционные понятия процедур-
ного программирования с методами, необходимыми для получения
расширяемости данных.
Эту единственную новую возможность вполне можно было добавить к язь
ку Модула-2. Зачем же было создавать еще один новый язык? Причина сост
яла в желании иметь язык, сохраняющий принцип процессоров. Оберон запр
сто оказывается на порядок компактнее, чем системы с сопоставимыми I
даже меньшими) функциональными возможностями.
Система, написанная на языке Оберон, —это иерархия модулей, болыя
ство из которых экспортирует один или несколько абстрактных типов Д
ных. Каждого пользователя поощряют расширять систему —эти расы1 V
ния создаются простым добавлением новых модулей. Не существует Р
граничения между «системой» и «прикладной программой». Кроме моДУ
Ядро и Дисплей, вся система написана на языке программирования
рон [42].
4.6. Заключение
117
Чя*Л*>чеВйе
Л.6- J основные понятия объектно-ориентированного програм-
- „яве введены
В ^°« ТиЯ в том числе:
иярован > „^гпвляет как атрибуты, так и методы посредством процес-
'.06*e«m-OHJ^
са инкапсу ^ данных. Это набор данных и операций над этими дан-
. АбстРаКТППЫ П
ньши- объектов, обладающих некоторыми общими свойствами.
# Класс- $1 бшений. Она обеспечивает общение объектов друг с другом.
# ueptuw ^^ обеспечивает механизм управления классами и повтор-
# ^ТисполГзование ими общих кодов.
пЬизм. Он придает методам дополнительный смысл, делая их более
# гибкими и расширяемыми.
П вставленные здесь понятия являются общими свойствами объектно-
тированного программирования. Они не ограничиваются каким-то кон-
тным языком программирования. Наконец, в этой главе были описаны че-
ьгое объектно-ориентированных языка программирования, а именно Eiffel,
Smalltalk, Java и Оберон.
У каждого объектно-ориентированного языка есть свои достоинства.
Возникает вопрос: на каком языке нужно писать систему компьютерной алгебры?
Как уже упоминалось, мы выбрали C++ в качестве языка разработки нашей
системы компьютерной алгебры. Это было сделано по следующим причинам:
1. C++ работает быстрее других языков.
2. Нам нужна гибкость, предоставляемая указателями.
3. Нам нужно такое свойство, как шаблоны, и оно есть в C++.
4. Для приложений в физике и математике уже написано много программ на
C/C++. Эти программы можно связать с системой компьютерной алгебры.
• Нам нужно связать нашу систему с Параллельной Виртуальной Машиной
(ПВМ) [15].
• Мы можем использовать Библиотеку Стандартных Шаблонов с
Символьным C++.
гося однДУЮЩеЙ главе 0письюаются основные средства языка C++, являюще-
нейщих гл И3 Самых попУлярных объектно-ориентированных языков. В даль-
ЗИке с пом изУченные здесь понятия будут применены к математике и фи-
м°Щью языка программирования C++.
Глава 5
Основные средства
языка C++
C++ не только исправляет большинство недостатков, обнаруженных
но также вводит много совершенно новых конструкций, разработанных
этого языка с целью обеспечить абстракцию данных и поддержку объект '
ориентированного программирования. Вот некоторые из важных новых ко
струкций:
• Классы, основная языковая конструкция, состоящая из структуры данных
и операций, применимых к данному классу.
• Переменные-члены, описывающие атрибуты класса.
• Методы, определяющие допустимые в данном классе операции.
• Перегрузка операций, придающая операциям дополнительный смысл,
чтобы их можно было применять для определенных пользователем типов
данных.
• Перегрузка функций, похожая на перегрузку операторов. Она позволяет
одной функции иметь несколько определений, что уменьшает потребность
в необычных именах функций и облегчает чтение программ.
• Контролируемое программистом автоматическое преобразование типов,
позволяющее сочетать определенные пользователем типы с основными
типами данных, которые предоставляет C++.
• Производные классы, называемые также подклассами, наследующие вну
тренние переменные и методы от своих базовых классов (называемых так»
надклассами). Они могут отличаться от своих базовых классов добавле
ем новых внутренних переменных методов или перекрытием существую
функций.
• Виртуальные функции, позволяющие производному классу переопреДе
методы, унаследованные от базового класса. С помощью динами4
связывания система в процессе работы выберет подходящую фун№и
конкретного класса.
чтй Я""
В нескольких следующих разделах мы покажем, как применять й
вые конструкции при создании типов данных для конкретных прилоЖе g
как составлять из этих абстрактных типов данных объектно-ориентиров
программы.
5.1. Указатели и ссылки
119
- устели и ссылки
5.1* сылка на данные или часть кода внутри программы. Это в
v пзагпв,лЬ~~ Э е адРес в памяти того элемента, на который он указывает.
J квальЯ°М °МЬ яют нам писать более гибкие программы, и это в особенности
^ззателй позв о_ориентир0ванным программам. Указатели применяются,
относится к°^ пе ЧИСЛенные ниже ситуации:
когда возникаю
пмма оперирует с большими объемами данных (более 64К).
* блема возникает лишь для персональных компьютеров, работаю-
ггоавлением операционной системы DOS. Для больших, сложных
JSfSX П°мм область памяти 64К, выделяемая C++ под данные, может ока-
П^° слишком мала, чтобы вместить все данные. Указатели позволяют
о5ти это ограничение.
Когда мы объявляем в C++ глобальные переменные, компилятор
разово- vrx r области памяти, называемой сегмент данных. Наибольший
мешаем ал. о
оазмер сегмента данных составляет о4К, и здесь должны быть размещены
все глобальные переменные. Для небольших программ это ограничение
несущественно, но встречаются случаи, когда нам может понадобиться для
этого больше памяти. Допустим, например, что программе нужен массив
из 400 строк, в каждой из которых содержится до 100 знаков. Этот массив
займет примерно 40 Кбайт, что меньше максимального размера 64К.
Такой большой массив не вызовет трудностей, если для прочих переменных
хватит оставшихся 24К.
Но что, если нам понадобятся два таких массива одновременно? На это
потребуется около 80К, что не уместится в сегмент данных размером в 64К.
Чтобы обрабатывать большие объемы данных, нужно использовать кучу.
Программа может разместить на куче 80К, сохраняя указатель в качестве
ссылки на место размещения данных. Такой указатель займет в сегменте
данных всего 4 байта.
ели в программе используются данные, размер которых во время
компиляции неизвестен.
уча—это вся память, отводимая операционной системой, которая не
щ^ р Уется под саму программу, ее сегмент данных и ее стек. С помо-
к ней МЫ можем зарезервировать область памяти в куче, обращаться
котоп РеДством указателя и затем снова освобождать эту область. Не-
3аДани ЭЛементы Данных в C++ (например, знаковые массивы) требуют
них мрг Р^меров во время компиляции, даже если все отведенное под
Mo^cej п И не ПотРебуется при исполнении программы. Простым примером
Леь1> скаж УЖить пР°грамма, считывающая строку, заданную пользовате-
СтРоковой ' ИМЯ пользователя- Если бы это имя хранилось в виде обычной
Я*1я- хРанр Ременной, нам бы пришлось зарезервировать достаточно места
ВсеГо из Я Самой Длинной возможной строки, даже если это имя состоит
йс*одцт Кольких букв. Если же размещение переменных на куче про-
время компиляции, а во время исполнения программы, мы
120
Глава 5. Основные средства языка C++
можем отвести как раз столько места, сколько потребуется для
фактических строковых данных. ^%,
Это банальный пример, но в приложениях, где используются
добных элементов данных (например, множественные окна или сч ^ ^
страниц из файлов), отведение как раз такого объема памяти кот ^"е
обходим, может оказаться решающим обстоятельством для ycneu ^ "е-
полнения программы: иначе ей может просто не хватить памяти Г° ^~
Если в программе используются временные буферы данных.
Указатели и куча очень удобны в ситуациях, в которых нам нуж
водить память на какое-то время, но мы не хотим занимать эту п 0г"
на все время исполнения программы. Например, редактору файлов об ЯТЬ
бывает нужен буфер данных для каждого редактируемого файла. Вм Н°
того чтобы объявлять во время компиляции, что у нас будет определен
количество буферов определенного размера, отведенных для файлод С
можем отвести на эти цели ровно столько памяти, сколько нужно в данный
момент времени, сделав память доступной для других целей.
Другое часто встречающееся использование временной памяти—это
сортировка. Обычно при сортировке большого массива данных мы делаем
копию этого массива, сортируем копию и затем копируем отсортированные
данные обратно в исходный массив. Это защищает целостность данных.
Однако для этого нужно во время сортировки хранить две копии данных.
Если мы разместим сортируемый массив в куче, мы сможем рассортировать
его и скопировать результат обратно в исходный массив, затем отбросить
сортировочный массив и освободить эту память для других целей.
Если программа использует много типов данных.
Один из более редких случаев применения указателей — это указание
на меняющиеся в ходе работы программы структуры данных. Например,
какой-то блок памяти может быть отведен под «журнал регистрации» строк
символов различной длины, последовательно набираемых пользователем в
поле ввода. Для чтения этого журнала некая подпрограмма будет сканир^
вать этот блок в поисках отдельных строк. Мы можем использовать прост
указатель, чтобы отметить, где начинается этот блок. Это просто ука»
нам, где находятся некоторые данные, без необходимости заранее опр д
лять, что же именно там лежит.
Если программа использует связанные списки.
Еще одно типичное применение указателей — это объединение дай
связанные списки. Во многих простых приложениях вроде баз данн
можем хранить записи в виде массивов или в виде набираемых пол
телем файлов. Однако иногда нам бывает нужно что-то более гибко >
массив фиксированного размера. Размещая динамические записи та > ^
каждая запись содержит указатель на следующую запись, мы смо# .
строить список, содержащий столько элементов, сколько нам потре У
5.1. Указатели и ссылки
121
адрес в памяти компьютера. Это может быть адрес пере-
Vi<a3aTeJIb~~ я функции. Обычно нам неважно, в каком месте памяти
надой, залйСЙ gC6) что нам требуется, — это сослаться на нее по имени.
Мб мтся переме ее искать. Именно это и происходит в случае объявления
%оа ^,ят\ -знает, ад
КомпЫ°тер Напрймер, если программа содержит код
переменной-
• Bt number; __
отведет область в памяти, ссылкой на которую будет служить
то компйлЯТ ^.ожем найти адрес переменной в памяти с помощью операто-
лшвьег. JV в памяти может быть присвоен указательной переменной,
ра &■ Эт0Т здаес данных или кода, хранящихся в памяти.
int number = Ю;
х _ // Р это указатель переменной
1П- fenumber; // адрес в памяти для number присвоен р
Пока что мы видели, как присваивать значения указателям, но от этого
л, rmnKv если мы не сможем получать эти значения обратно. Мы можем
обращаться с напечатанным указателем как если бы он был переменной
соответствующего типа, разыменовав его. Чтобы разыменовать указатель, мы
используем унарный префиксный оператор *. Пусть р содержит адрес
переменной number; тогда *р даст нам значение переменной number. Рассмотрим
следующий код:
// pointer.срр
#include <iostream.h>
void main()
{
double *pl, *p2; // объявляет два указателя как double
double a = 2.5, b = 5.1;
P "" *a; // pi содержит адрес переменной а
P2 = pi;
cout « "*pl = „ << 1 << „ d 2 = „ << 2 <K endl
*P2 = b; V
у C°Ut << "*Pl = " « *pl « " and *p2 = " « *p2 « endl;
Результат
*P1 " 2-5 and
*p2 = 2.5
*P1 = 5 1
b-l and *p2 = Б.1
в первом
ИМе1°т значениПеРаТ°Ре C°Ut И p1' и р2 Уквзывают на а- Поэтому *pl и *p2
ен°вания сол втором операторе cout с помощью оператора разы-
>КазЬ1Вают ^ржимому Р2 присвоено значение Ь. В результате и pi, и р2
а качение 5.1.
122
Глава 5. Основные средства, языка C++
Теперь опишем, как можно динамически использовать указатели'
• Размещение динамических переменных: C++ использует ключ
евые
new и delete соответственно для создания и уничтожения динам ^Ч
переменных. Оператор new размещает динамические переменны^ ^^йу
Например, а Ц^
new int;
создает один объект типа int, тогда как
new int[100];
создает массив из 100 объектов типа int. Оператор new возвращает v
тель к обозначенному типу. Он может быть присвоен указательной *"
менной, вроде ре'
int *р = new int[500];
Здесь динамически размещенный массив int неинициализирован. Чтоб),
инициализировать объекты класса, созданного оператором new, мы исполь
зуем их конструктор. Он имеет следующий вид:
Thing *p = new Thing(argument list);
Оператор new создает объект класса Thing, который инициализируется
своим конструктором, сверяющим список аргументов.
• Освобождение динамических переменных: переменные, созданные
оператором new, должны быть уничтожены по окончании работы с ними, чтобы
освободить место на куче для других динамических переменных. Для этого
используется оператор delete. Например,
delete p;
возвращает память, прежде отведенную оператором new, в состояние
свободного места для хранения. Чтобы освободить массив объектов,
используется
delete [] р;
Скобки [] использованы здесь для освобождения произвольного масс
объектов.
К примеру, предположим, что мы считываем некоторые строки из фай
храним их в памяти. Мы не знаем точно, насколько длинными могут быть
строки, поэтому нам придется объявлять достаточно длинные области пам
чтобы там могла поместиться наидлиннейшая возможная строка. Если я
строки имеют наибольшую длину, то много места будет пропадать впУ
Чтобы избежать этого, мы считываем каждую строку в буфер, а затем в
дим ровно столько памяти, сколько нужно для ее размещения, как показ
следующей программе:
5.1. Указатели и ссылки
123
. Q-t-restfO-fr
lade ^i°s ь> // для ifstream (поток входного файла)
*lDrlude <fstr6^ ь> // для strlen (длина строки)
#ioclude
= 1000;
. * dataSize = J-
«** -n -LenStb = 256;
const in*
void mainO
{ ifstream finC'LongString.dat");
char buffer [maxLength];
char *Long[dataSizeJ;
int i> ,.
for (i=°; KdataSize; i++)
{
fin » buffer;
Long[i] = new char[strlen(buffer)+l];
strcpy(Long[i] , buffer);
}
}
В этой программе считываем строки из файла данных "LongString.dat".
Пусть этот файл состоит из 1000 строк, каждая из которых содержит не более
256 знаков. Наша цель — сохранить весь текст в виде массива строк,
названного Long. На каждом шаге мы считываем одну строку и отправляем ее в buffer.
Затем мы измеряем точный размер этой строки и отводим для нее ровно
столько памяти, сколько нужно. Этот процесс тщательно избегает излишних затрат
памяти компьютера. Вместо отведения 256К для всего массива строк B56
знаков на строку, умноженное на 1000) мы используем для текста ровно столько
и, сколько необходимо, плюс буфер, размер которого составляет 256 байт,
сто имеется специальный указатель, который называется this. Это по-
чает » указатель на объект класса, содержащего этот метод, т. е. он обозна-
РУем эт 0(:)ъявленный самоименующий указатель объекта. Проиллюстри-
о понятие следующей простой программой:
'' A) Метод i
// vk ncrement использует неявно объявленный
^ обр ЛЬ ^is для возврата новых увеличенных значений
Ч B)
Метод П^ременных °1 and c2.
I лае» where_am_I показывает адрес
ч,(з) С:вг:е°бъекта-.
самотгв- СЛов° this обеспечивает встроенный
Указывающий указатель.
Пс1и^ <in
<lostreaa.h>
124
Глава 5. Основные средства языка C++
I' / объявление класса Cpair
class Cpair
•С
private:
char cl, c2;
public:
Cpair(char);
Cpair increment();
void print();
void where_am_I();
>;
// Определение класса Cpair
Cpair:-.Cpair(char c) : c2(c), cl(l+c) {}
Cpairu Cpair::increment() { cl++; c2++; return *this; }
void Cpair::print() { cout « cl « " and " « c2 « endl; }
void Cpair::where_am_I() { cout << this << endl; }
void main()
{
Cpair x('A');
x.print(); // вывод: В и А
x.where_am_I(); // вывод: 0x7fffaee0
Cpair z('X');
z.where_am_I(); // вывод: 0x7fffaedc
z.increment();
z.printO; // вывод: Z и Y
z.where_am_I(); // вывод: 0x7fffaedc
Cpair n('l');
n.increment().print(); // вывод: З и 2
n.where_am_I(); // вывод: 0x7fffaed8
>
В C++ указатели являются мощным средством. Однако при их при^е
нужно избегать двух часто встречающихся проблем:
• Разыменовывание неинициализированных указателей. Один из 4aCTTy„^ii
точников ошибок — применение неинициализированных указателей-
для всех прочих переменных в C++, значение указателя остается нео т
ленным, пока мы не присвоим ему некого значения. В принципе ой ь
указывать на любое место в памяти. Если разыменовать такой У^33
5.1. Указатели и ссылки
125
"яые биты. Это может стать фатальным, если мы присвоим
уЧатся сЛУ4 шЯости, на которую он указывает, потому что может ока-
Йекие значеНИ нЫМ некий важный сегмент данных, например программ-
аТьсЯ пере11 операционная система. Это звучит немного страшновато,
иыйк°'ДйЛЙ q ой дисциплине с этим нетрудно справиться.
ио прй не1С0Т жать потенциально опасного разыменования неинициализи-
Чтобы и3 лей; C++ содержит ключевое слово NULL, которое можно
рованяых у ^ указателей, не указывающих ни на что. Указатель NULL
лсйользов чему не привязан. Одно из частых применений указателя
законен, я ть свЯзанный список. Пусть Li, L2, £3 — элементы связанного
NULL— Р указать, что связанный список исчерпан, обычно делается
^лягка. последний элемент указывал на указатель NULL, как показано
списка.
так:
ниже
Li ->■ Ьг -> L3 -> NULL
v ка mrtu. Другая частая проблема, связанная с использованием дина-
* мйческих переменных, известна как утечка кучи. Это ситуация, при
которой память размещается в куче и затем утрачивается — по какой-то причине
указатель более не указывает на размещенную область памяти, так что на
нее нельзя сослаться или освободить ее.
Частая причина утечек кучи — это повторное присваивание значений
динамическим переменным без уничтожения предшествующих значений.
Например,
#include <iostream.h>
void main()
{
int* ptr;
Ptr = new int[500] ;
Ptr = NULL;
тельной переменной ptr сначала было присвоено значение указателя
ено °ИВ целых чисел из 500 элементов. Сразу после этого ей было присво-
внача. еНИ6 NULL' ^то сДелало область памяти (массив int), на который
можно 17Казьтала переменная ptr, потерянной, и восстановить ее невоз-
30 врем П0ДО ой ситуации память утекает. Значительная утечка памяти
из-чя » ИСподнения может привести к аварийной остановке программы
за схватки памяти.
Д^ляет псевп Т°Я К0НСТРУКЦИЯ1 называемая ссылкой. В сущности, она опре-
^сически э М' ИЛИ ^РУгое название для любого экземпляра данных. Син-
ВегствуЮ1 ~° осУЩествляется приписыванием слева знака & к имени соот-
int * • ТИПа даннЬ1х- Например,
и
5:
*nt *pi = i- ■ Автоматическая переменная ii
nt &ri = j-.' ' Указатель на int, который указывает на ii
' // Ссылка на переменную ii
126
Глава 5. Основные средства языка C++
Теперь мы можем использовать ri повсюду, точно так же, как ii
Предположим, что мы написали ^Ч к
ri *= 10; // Умножение ri на 10
Значения переменных ii, *pi и ri становятся равными 50. Например
рим следующую программу: ' с%т_
// ref.cpp
#include <iostream.h>
vo
{
id main()
int i =
int *ip;
ip =
cout
с out
cout
&i;
<<
<<
<<
23;
114 — II
"ip =
"&i =
1
II
11
<< i << endl; // i = 23
« ip « endl; // ip = 0x7fffaed4
« &i « endl; // &i = 0x7fffaed4
int &j = i; // псевдоним j со значением i
int *jp;
j = 45; // значение i тоже изменилось!
jp = &j;
cout << "jp = " << jp << endl; // jp = 0x7fffaed4
cout « "&j = " « &j « endl; // &j = 0x7fffaed4
cout « "i = " « i « endl; // i = 45
cout << "j = " << j « endl; // j = 45
}
Заметим, что значения адресов для &i, ip, jp и &j совпадают. Это показыв
что ссылки в C++ действуют, отсылая к тем же самым областям в пам
компьютера. Это также объясняет, почему значение переменной i также и
нилось, когда сработал оператор j = 45. оВ
Намного более важное применение ссылок —это передача аргу^ ,
функциям (см. в разд. 5.8 пример использования ссылки в качестве аргуме
5.2. Классы
Класс — это важнейшая конструкция языка C++, применяемая для с0 ^
абстрактных типов данных (АТД). Она описывает способы поведения о
такие, как атрибуты, операции, типы аргументов для операций и так Р-
Общий синтаксис объявления класса следующий:
5.2. Классы
127
,.я класса
claSS имя
ilaZ^e Мет°дЫ>
3cted:
ащищеппые методы>
^ащппеппие Заппие>^
?ПЪТотпритые дапные>
<0тпрытые методы>
г4--\- тэедлагают три уровня видимости для объектов данных и
Классы в о1 г г i"f
„ы i г nrotected и private. Эти уровни называются специфика-
«РТОДОВ — p^lD•^-J■^-! г
доступа. У каждого вида доступа есть свои достоинства, как описано
ниже:
rivate: Только методы класса имеют доступ к закрытым данным.
Экземплярам класса доступ к ним закрыт.
• protected: Только методы класса и его классов-потомков имеют доступ к
защищенным данным. Экземплярам класса доступ к ним закрыт.
• public: Данные видимы методам класса, экземплярам класса, методам
классов-потомков и их экземплярам.
Далее мы перечислим некоторые простые руководящие правила
надлежащего использования классов:
• Спецификатор доступа может появиться в любом порядке.
• Один и тот же спецификатор доступа может появиться более одного раза.
• Ьсли спецификатор доступа опущен, то компилятор считает данные
защищенными (protected).
зоегайте помещения внутренних данных в открытую (public) область,
ели только такое объявление не упрощает существенно разработку
программы.
Р нние данные обычно помещаются в защищенную область, чтобы ме-
• И(лю КЛассов"потомков могли иметь к ним доступ.
аьк у е Методы для изменения или запроса значений внутренних дан-
После тот
°&ьявления КаК Класс ^Ь1л объявлен, имя класса можно использовать для
^явление ^^ экземпляРов
класса. Синтаксис объявления напоминает
Построим Vn ременньгх. В оставшейся части обсуждения мы шаг за шагом
ним исподьзу П*еннЬ1Й ваРиант класса String. По ходу построения мы объяс-
ВаРиант ^ bie понятия и языковые конструкции языка С+-К Улучшенный
В обычно^ aS будет представлен в гл. 6.
^ что стр0 ЫКе стРока представляется указателем на char. Это озна-
ВлеНйи коне Мо>кно Рассматривать как массив типа char. В таком пред-
тРоки обозначается сочетанием знаков '\0'. Это условное обо-
128
Глава 5. Основные средства языка. C++
значение страдает одним крупным недостатком. Для '\0' нужно от
байт, и это сочетание является частью строки. Значит, для хранен ^ %
«abc» потребуется 4 байта памяти. Несоответствие между длиной ^ С^г%!'
количеством отводимой под нее памяти весьма смущало многих и ^^и''
лей. К тому же это расточительно. Поэтому, чтобы избежать ощикЬз°Ь;*Тг.
высить эффективность, лучше всего реализовать строку как абстрак * * ^
данных. ь'й тц,,
В приводимом ниже листинге показано типичное описание клаг
реализация. Как мы видим, у класса String имеется два поля данн * tr,J
вое поле, datalength, содержит размер самой длинной строки. Втоп ^
data, — это указатель на тип char. После его размещения в подходяще" П°е'
сти памяти оно будет использовано для хранения знаков строки. Чаше
поля данных объявляются закрытыми с целью скрытия данных Олня^а Cetu
м 1ако вп0л
не законно помещать их в открытую область.
// estring.h
#include <iostream.h>
#include <string.h>
#include <assert.h>
class String
•C
private:
// Поля данных
int datalength;
char *data;
public:
// Конструкторы и деструктор
String О;
String(char);
String(const char *);
String(const String &);
"StringO ;
// Оператор присваивания
const String & operator = (const String &);
// Метод
int length() const;
// Оператор преобразования
operator const char *() const;
// Друзья: конкатенация и поток вывода
friend String operator + (const String &, const String &)i
friend ostream & operator << (ostream ft, const String &)»
>;
// Реализация класса
5.2. Классы
129
„« я деструктор
^йстрУкТ°Рп • datalength(l), data(new char[l])
// К°.-string^ '
string'
; ,Л гЯ ' = NULL);
aSS ro = 'xo';
/ г,яг c) : datalengthB) , data(new char[2])
Strings:Strxng(cha
{ aSSert(data!=NULL);
data[0] = c>'
}
. .String(const char *s)
datalength(strlen(s) + 1), data(new char [datalength])
assert (data != NULL);
strcpy(data.s);
String:: String (const String &s)
: datalength(strlen(s.data) + 1), data(new char [datalength] )
{
assert(data != NULL);
strcpy (data, s. data);
}
String::"String()
delete [] data;
сопГзадР BPBBnBun
{ lng & String: .-operator = (const String k s)
J* (&s «- this)
flet* □ data;
^^-strlen(..data> + l;
assert^6! Char[datalength];
} PyUata,s.data);
> retUra *this;
5 - 1548
130
Глава 5. Основные средства языка C++
int String::length() const
{
return strlen(data);
}
// оператор преобразования
String::operator const char *() const
{
return data;
}
// оператор конкатенации
String operator + (const String &sl, const String &s2)
{
String S(sl.length() + s2.1ength());
strcpy(S.data, si.data);
strcat(S.data, s2.data);
return S;
}
// операторы дружественности
ostream & operator << (ostream &out, const String &s)
{
out << s.data;
return out;
}
5.3. Конструкторы и деструктор
Два самых важных метода класса — это конструктор и деструктор- В кл
может быть много конструкторов, но у каждого класса только один АеС у-
тор. Назначение конструктора — создавать экземпляры класса, т.е. ооъ
Его можно использовать для выполнения инициализации и размещени
мической памяти для объекта. Заметим, что конструкторы всегда имею
имя, что и класс. Деструктор, напротив, применяется для уборки п00, ^
как объект уничтожен. Процесс уборки обычно освобождает неиспо .
ную память, возвращая ее в систему. Он имеет то же название, что и
с добавлением префикса «тильда» (~). ^р
Для введенного здесь класса String есть три перегруженных попе V
Каждый из них отвечает за свой собственный тип построения:
1. StringO ;
например, String x; String y=0; String z=" "; ь ч*1"
присвоить переменным х, у и z значения «пустая строка»- ЗаМе
и " " можно использовать в качестве строки типа NULL.
5.4.
■rvvKTOp копирования и оператор присваивания 131
^°i::%™^:jizi\
„гтп0МеР> „„или х значение А, а переменной у значение # соответ-
йпр^иТЬ Пвре
fTBeH*°(const char*)
З^меРString xC'abc");
например. енной х строковое значение «abc».
присвоить пер
в каждом конструкторе данные размещаются в определенной
Заметим, всегда необходимо, так как области памяти должны быть
области паМЯТ стемь1 для хранения строк. Однако динамически размещенная
выделены Рождается системой автоматически, это должны делать пользо-
память не ождение неиспользуемой памяти важно для того, чтобы она мо-
"аТеЛИ'тоРно использоваться для последующих размещений. В C++ деструк-
гЛа повТор лля освобождения памяти, когда переменные находятся вне
ТПП ИСПОЛЬЗуе-1 ил w __
лимости. Это делается неявно самой системой. Деструктор для нашего
класса строк определяется следующим образом:
String::~String()
{
delete [] data;
}
Квадратные скобки [] используются здесь для удаления всего массива.
5.4. Конструктор копирования и оператор присваивания
Как можно догадаться по названию, конструктор копирования
используется для дупликации содержимого экземпляра в операторе объявления. Кроме
го. он будет вызван для порождения временных значений, когда аргумен-
"ласса передаются как параметры-значения. Если поля данных не содер-
кон каких Указателей, которые нужно инициализировать динамически, то
работ Т°Р копиРования> созданный компилятором и копирующий почленно,
менные равильно- Однако если поля данных содержат указательные пере-
Возьм Не °дим поДходящий конструктор копирования.
ПиРУется с"' К ПримерУ> наш класс String. Пусть строка SI = "Computer" ко-
Дет скопиро ош;ью почленного конструктора копий в другую строку S2. Бу-
11 Sl> и S2 у ■ аДР6С Указателя строки S1 вместо ее содержимого "Computer".
больще не ну Ывают на ОДно и то же место в памяти. Если строка S2 уже
к°торое указь ' то будет вызван деструктор String, и место в памяти, на
'^ем ~~ Указател * 2' ^удет освобождено. Это сделает S1 повисшим указате-
гДа оставщяд ' 6 указывающим ни на какой задействованный блок памяти.
°бодИТь §л тРока S1 будет уничтожаться, оператор delete попытается
ект аЛьнь1м, если аМяти' который уже был освобожден. Это может оказаться
Для Эт°му бь Т памяти уже был отведен под какие-то другие объ-
правильно всегда создавать конструктор копирования
<. ределяемот ™„т™„„„„„ „„„„„
пользователем класса.
132
Глава 5. Основные средства, языка. C++
В нашем классе String конструктор копирования берет ;->кземгг
в качестве аргумента, создает новый буфер памяти и копирует г ^i
аргумента, как показано ниже: ^fy,,'"
String::String(const String &s)
: datalength(strlen(s.data)+l), data(new char[datalPn
assert(data != NULL);
strcpy(data,s.data);
}
где strlenO, strcpyQ суть процедуры из <string.h>, a assert() из б к
теки assert (h). Л1|Г|-
• char *strcpy(char *sl,const char *s2);
Копирует строку s2 в строку si, включая терминальный символ '\0' в
вращает значение si.
• size_t strlen(const char *s) ;
Возвращает длину строки s, где длина определяется как число знаков
строке без терминального символа ' 0'.
• void assert(int expr);
Если expr равно нулю (ложь), то печатаются диагностические данные п
программа останавливается. Диагностические данные включают
выражение, имя файла и номер строки файла.
Оператор присваивания необходим всякий раз, когда поля данных
требуют динамического размещения памяти или когда конструктор копий класса
нужно переписать. Для нашего класса String его определение следующее:
const String feString::operator = (const Stringfe s)
i
if(&s != this)
{
delete [] data;
datalength = strlen(s.data) + 1;
data = new char[datalength];
assert(data != NULL);
strcpy(data,s.data);
}
return *this;
}
Из приведенной выше распечатки видно, что эта функция похожа на к°
тор копирования, но отличается от него двумя важными чертами:
• Оператор присваивания должен обрабатывать особый случай, хогД ^ fl.
присваивается самому себе. Отсюда понятно, почему эта функция i ^:
ся с оператора if. Условный оператор гарантирует, что наш опера ,, *
работать правильно, когда строка присваивается самой себе, напр ' $£■
х; дело тут не только в том, что излишне присваивать что-либо с&ъ
5.5. Преобразования типа
133
озатели data и s. data указывают на один и тот же адрес.
,нРеДРУг°е' ' йтлтгаиэтом delete [] data; и strcpy (data, s .data) ; pa-
" 3tfO#H0'ч
йоТаЛй пРавИ оператор возвращает ссылку Stringft. Это позволяет опе-
ио чтО НЭ-^
gtring х> ^'„д'асе Shuttle";
х = У = Z "
ильно Возврат ссылочного типа сделал возможным левосто-
^яботать прави
^„нееприс^ивание.
5.5. Преобразования типа
еется много правил преобразования типа, которым подчиняется ком-
и Шменении значения объекта от одного фундаментального типа к
П" ■ ому Эти правила позволяют производить действия с операндами, не Co-
падающими по типу. Такие операции удобны, но потенциально опасны, если
использовать их ненадлежащим образом. Рассмотрим следующий пример:
// convert.схх
void f(long);
void f(int) ;
void g(long) ;
void g(unsigned int)
void main()
{
)' II Компилятор выбирает f(int)
у >'> II Неопределенность!!!
Заметим, что
3°валягч.' г я пРе°бразования знаков 'А' и 'Р' в целые числа исполь-
ojwcb таблица АЧгтт г> ,-
и из-за н ло^11- Однако эта программа порождает ошибку компиля-
неявное преобПРеДеЛеНН°СТи межДУ g(unsigned int) и g(long). Кроме того,
Нсполнения п ование может привести к малозаметным ошибкам во время
Как Работают Граммы' которые трудно обнаружить. Поэтому полезно знать,
^РазоваяИя тПравила преобразования в C++. Общие правила неявного пре-
Ипа включают два этапа.
ож!Чала типы CW „v. г-
^«Дается • snort, enum преобразуются в int всякий раз, когда
Тавить в Ви '. Ни преобразуются в unsigned int, если их нельзя пред-
134
Глава 5. Основные средства, языка С++
2. Если выражение по-прежнему содержит смешанные типы да
низкие по рангу типы преобразуются в более высокие согля Ь1х> t?0f ^
иерархии: ° ^еДуЛ'4
int -> unsigned int -> long -> unsigned 10цЕ
—> float —> double —)■ long double
Полученное значение смешанного выражения имеет тип того оцР
рый занимает более высокое положение в иерархии типов. Пусть riH^a'K°J'
2 и long b = 3; тогда операция а+b делает следующее: ^6 а-
•
b преобразуется к типу double.
• Выполняется сложение по правилам double, и полученный np-^vr,
имеет тип double. l(j*
Явное преобразование использует каст. В обычном С каст имеет сле
вид: ВВ|,':
(тип) выражение
тогда как в C++ возможна и другая форма (функциональная нотация)
тип (выражение)
Например, следующие два эквивалентных выражения преобразуют int n в
double:
int n = 10;
double x, у;
x = (double) n;
у = double(n);
Однако функциональная нотация предпочтительна.
Конструкция с одним аргументом служит оператором преобразования о.
типа аргумента к типу конструктора класса. Рассмотрим, например, констр;к
тор класса строк:
String::String(const char *s)
: datalength(strlen(s)+l), data(new char[datalength])
{
assert(data != NULL);
strcpy(data,s);
}
Этот конструктор при необходимости преобразует переменные тлпа ь
String. Такой способ преобразования работает только в случае преобр ^
от уже определенного типа к определяемому пользователем типу- & ^
но добавить какой-либо конструктор ни к какому встроенному типу ^
(скажем, к int или double). Иногда бывает желательно преобраз°в $$■
деленный пользователем тип ко встроенному типу данных. Пусть, f сТро^-
мы хотим, чтобы класс String работал с теми функциями обрабо
которые имеются в традиционном С:
5,5. Преобразования типа.
135
схх
//str^"
. <iostream.h>
~Л пав г, »
vo
id mainO
•C . „ „(""Microscope");
String s<-
int
i = strlen(s), j = s.lengthO;
lf('1 't << nThe string " << s << " consists of " << i
« " characters " << endl;
}
Результат
Строка Microscope состоит из 10 знаков
В этой программе компилятор автоматически преобразует String в char *
в аргументе функции strlenQ. C++ имеет для этого специальную функцию
преобразования. Эта функция преобразования имеет следующий общий вид:
operator typeO const {...}
Для нашего класса String функция преобразования — это
operator const char *() { return data; }
ервое const в этом выражении означает, что память, на которую указыва-
возвращаемое значение, не должна изменяться программой, внешней для
класса, а второе говорит, что этот оператор не меняет содержимого
и"Ъекта типа Q-t--^-,-
*utd string, на который она действует.
теля м ования типов применимы также к указателям. Любой тип
указавшего Т ь пРе°бразован к указателю общего вида void*. Но указатель
Указател онео°хоДимо преобразовать к явному типу, если он присваивается
Ьной пеРеменной необщего вида.
" generic.схх
Qe <iostream.h>
generic_ptr;
vcid * = NULL;
136
Глава 5. Основные средства языка C++
generic_ptr = п; // ОК
n = (int*)generic_ptr; // OK
n = generic_ptr; // Ошибка!!!
}
Эта программа порождает сообщение об ошибке компиляции:
значение типа "void *" нельзя присвоить объекту типа "in1-
n = generic_ptr;
Нулевой указатель может быть преобразован в указатель любого тип
char *ptrl = 0;
int *ptr2 = ptrl; // Неправильно: нужно (int *)ptrl;
int *y = 0; //OK
Указатель на любой класс можно преобразовать в указатель на класс из к
рого он был произведен открытым образом. Это относится также к ссылка
Рассмотрим простой пример, относящийся к абстрактному типу данных
классу Verylong. Этот класс включает лишь большие положительные целые
числа, превосходящие предельное значение встроенного типа unsigned 1оцг
Это упрощенный вариант класса, который будет рассмотрен в гл. 6.
В примере мы сфокусируем наше внимание на преобразовании типов в
классе. Для абстрактного типа данных существуют преобразования двух
видов. Преобразование первого вида преобразует данные встроенных типов в
абстрактный тип данных. Они совершаются конструкторами класса. Второй
вид преобразует данные абстрактного типа во встроенный тип. Этот вид
преобразований совершается оператором преобразования, имеющимся в C++-
// cast.cxx
#include <iostream.h>
#include <string.h>
#include <math.h>
class Verylong
{
private:
char *data;
public:
Verylong(const char * = NULL);
Verylong(unsigned long);
Verylong(const Verylong &);
Verylong & operator = (const Verylong &);
"Verylong();
operator int() const;
friend ostream & operator « (ostream &, const Verylong &)>
};
5.5. Преобразования типа
'VeryWc°nStChar*ValUe)
VerylOIlg:'
< .f (value)
J new char[strlen(value) + 1] ;
Scpy^-ValUe):
}
else
{ data - new char [1] ;
*data= '\0';
}
}
Verylong: Verylong (unsigned long n)
{
int digits;
digits = (int)loglO(n) + 1;
data = new char[digits + 1] ;
data[digits—] = '\0';
while (n >= 1) // извлекаем число цифра за цифрой
{
data [digits—] = n'/ЛО + 'О';
n /= 10;
}
}
Verylong:: Verylong (const Verylong &x)
data = new char[strlen(x.data) + 1];
strcpy(data, x.data);
{ °ng & Verylong::operator = (const Verylong &rhs)
else 1S == &rhs) return *this;
{
dS6t![] data;
str.^ =,new char [strlen(rhs.data) + 1] ;
trcPy(data, rhs dataV
retUrn *+u- rns.aata;,
} ura *this;
}
138
Глава 5. Основные средства, языка C++
Verylong::"Verylong() { delete [] data; }
Verylong::operator int() const
i
int a, number = 0;
a = strlen(data);
if (a > 5)
i
cerr « "Преобразование невозможно" << endl;
return 0;
}
else
{
for (int j=a; j>=l; j—)
number += (data[a-j] - 48)*powA0,j-1);
}
return number;
}
ostreamft operator « (ostreamfe os, const Verylongft x
{ return os « x.data; }
void main()
i
/I string => Verylong
Verylong sl(234");
cout << "si = " « si « endl; cout « endl;
// Verylong => int
int n = si;
cout « "n = " << n « endl; cout « endl;
// Число Verylong выходит за диапазон int
Verylong s2(777777777");
cout « "s2 = " « s2 « endl; cout « endl;
// Verylong => int
int m = s2;
cout « "m = " « m « endl; cout « endl;
// unsigned long => Verylong
Verylong nlB34567890);
cout « "nl = " « nl « endl;
5.6. Перегрузка операторов 139
; s 1234
^H— НеБ03М0ЖН°
■•Л°2345бГ890
r б. Перегрузка операторов
д' ' й оператор может иметь более чем один смысл — это зависит
В C++ вСТР Рассмотрим бинарный оператор сложения а + Ь. Типами
,,. РГо аргу ^ могут служить int, float, double или даже определенный
ланных Pfl скажем Matrix. Оператор сложения матриц несомненно
.„пьзователем ши, __ ^
ьно отличается от сложения чисел с плавающей запятой. Мы будем
" " ь что оператор + имеет более чем один смысл. На самом деле и многие
" е операторы в C++ могут быть перегружены, как показывает следующий
i iiiii'ok:
Тип Обозначение оператора
Унарные ++ (префиксная/постфиксная)
— (префиксная/постфиксная) &(адрес объекта)
* (разыменование) + - ~ ! (тип)
Арифметические */'/, + -
Сдвиги << >>
Отношения >>=<<=== ! =
Побитовые & - |
Логические && | |
Присваивания = *= /= '/,= += -= «= »= &= -= | =
Доступ к данным [] -> ->*
Пр0Чие О , new delete
ператоры в C++ могут быть перегружены, кроме:
Вь«5ор объекта а.ь
Р^реМщеН°ВаНИеУказателя а.*Ь
Услов^6™6 области видимости а: : Ъ
шовный оператор а?Ь:с
ы He Можем ° ПеРегРУжать лишь определенные в самом языке операторы.
новых обозначений для операторов, вроде обо-
JTsl 0Г1ерато 0ператора возведения в степень, используемого в Фортране.
, изМенить мо>кно придавать дополнительный смысл, порядок выполне-
"'Л1°Чающее ^°зм°жно. Предположим, мы хотим перегрузить оператор
wj; придав ему смысл возведения в степень. Нам придется
140
Глава 5. Основные средства языка C++
проявить крайнюю осторожность в отношении порядка вычисле "
ние a+b*c~d будет вычисляться как (a+(b*c))~d, ане как а+(ь%(' . ^\
выражение было правильным, нужны дополнительные скобки 'Ь
Фактически в C++ есть два вида перегрузки операторов: п
тодов класса и глобальная перегрузка. Снова рассмотрим опера ?'^Ч
а + Ь, где а и Ъ суть объекты класса С. Общий вид перегрузки CJlr)'i%
класса следующий: 'я Хщ...
С С::operator + С { ... }
В этой программе выражение а + b означает a.operator+(b). Мы \
же перегрузить этот оператор с помощью глобальной перегрузки: вМ Tl:
friend С operator + (С,С) { ... }
где а и b суть первый и второй параметры данной функции соответст
Заметим, что для глобальной перегрузки оператора нужно объявить соо
ствующую функцию как friend в этом классе. Дружба между фунющя\
классами обсуждается в разд. 5.9.
Рассмотрим один пример перегрузки оператора. Пусть мы хотим перего-
зить оператор сложения +, чтобы осуществить конкатенацию двух объекта-
класса String.
// concat.cxx
#include <iostream.h>
#include "MString.h"
void main()
{
String SI = "father", S2 = "mother", S3;
S3 = "My " + SI;
S3 = S3 + " and my ";
S3 = S3 + S2;
cout « "The concatenated string is " « S3 << endl;
}
Результат
The concatenated string is My father and my mother
Если бы мы использовали для этого оператора перегрузку метод ^
са, то "My " + SI было бы проинтерпретировано как "My ". operate ^
Это ошибка, так как "My " не является экземпляром класса. Ника ^
тод не может применяться к "My ". Это происходит потому, что к°м иКаксГ
C++ не производит автоматического преобразования левого операяДа ^ре-
го метода. Поэтому такая программа не может работать без глобаль
грузки.
"МУ " + Sl ^разует левый операнд функции + в объект типа String.
*f .тйчески ПР „й0тает конкатенация, рассмотрим следующий код;
if*10 яять, «аК р
Чтобы п° + (Const String &sl, const String &s2)
5.6. Перегрузка операторов 141
глобальная перегрузка операции + преобразует выра-
ругой с'гор0^Ь1ВЬ1ражение operator+(String("My "),S1). Компилятор
^ески Щ
нять, кг
Stri»e °РеГа"Г
{ 4fsl lengthО + s2.1ength());
::rS(S.data)s2.data);
return S;
нерирует временный буфер S подходящего размера, копирует SI в
9Т0Т К°ешет S2 позади S с помощью strcatO .
S и пом одного примера рассмотрим семейство линейных операторов
htWi векторном пространстве V со скалярным произведением. Коммута-
е соотношения для этих операторов (так называемых операторов Бозе)
даются формулами
[6,6+J = 7, E.1)
где / — тождественный оператор, а
[Ь,Ь] = [Ъ*,Ь*] = 0,
где 0 —нулевой оператор, [,] означает коммутатор. Оператор о называется
оператором уничтожения, а оператор 6^ — оператором создания. Векторное
пространство V должно быть бесконечномерным, чтобы выполнялось E.1). Пусть
|0) есть вакуумное состояние, т. е.
Ь|0>=0, @|0> = 1. E.2)
Любое состояние задается формулой
(Ь+Г|0), где п = 0,1,2,....
Тома60™6 ПрИМера Рассмотрим оператор Н = tfbtfb и состояние \ф) = ЬЩ.
Н\ф) = Ь+ЬЬ+&Ь+H>
[в силу E.1)] = tfbtf{I + tfb)\0)
[в силу E.2)] = Ь*ЪЬ*\0)
[в силу E.1)] = Ь+G ч- Ь+Ь)|0>
В ^ДУющей [всилуE.2)] = ^|0).
в °ПеРатор со гРамме мы перегрузим оператор сложения, превратив его
ПеРаторЬ1 „ „ Ия> а оператор вычитания —в оператор уничтожения. Эти
другое состояв ^т?Г На ВакУУмное состояние |0), и в результате получается
• /Аля иллюстрации используется несколько операторов:
6tl°>, ь|о), ьь+|о>, bb+b+b+jo), ъЧъЧьЦо).
142
Глава 5. Основные средства языка C++
I/ bose.cxx
#include <iostream.h>
class State
{
private:
int m; // число операторов создания, действующих на
int factor; // число идентичных состояний акУ!;><
public:
// Конструктор
StateO; // создать состояние
// методы
void display() const; // показать состояние
void bose_state(int); // операция на состоянии
void reset О; // восстановить состояние
};
// оператор Бозе
class Bose
{
public:
// Конструктор
Bose();
// Операторы
State operator + (State &); // оператор создания
State operator - (State &); // оператор уничтожения
};
State::State() : m@), factor(l) {}
void State::bose_state(int bstate)
{ if(bstate == -1) factor *= m—; else m++; }
void State::display() const
{
if(!factor) cout « ";
else
{
cout « factor « "*";
if (m)
■C
cout « "(";
for (int i=0; i<m; i++) cout « "b+";
cout « ")";
}
cout « "|0>";
}
a
6. neperpy^^fP^^L
, 0; factor = 1; >
.rese*u
oidState:-
re B°se"
{ tesl(s2)''nv
: __ _ (State & s2)
.. operator ^
State «
return si;
}
void main О
{
State g;
Bose Ъ;
8«;ЛЛ-";8^вр1ауО; cout «endl;
g.reset0 ;
coutV-g = "; g.displayO; cout « endl;
g.resetO ;
g = b- (b+ g);
cout « "g = "; g.displayO; cout « endl,
g.resetO;
g = b- (b+ (b+ (b+ g)));
cout « "g = "; g.displayO; cout << endl;
g.resetO;
g = b+ (Ъ- (Ъ+ (Ъ- (b+ g))));
cout « "g = ... g.diSpiay(); cout « endl;
Результат
S = l*(b+)|0>
S = 0
g = l*IO>
g~ Wb+)|0>
144
Глава 5. Основные средства языка C++
Теперь давайте рассмотрим оператор Ферми. Возьмем семейс
операторов Cj, с] для j = 1,... , п, определенных на конечномерц0 ^н%
пространстве V и удовлетворяющих антикоммутационным соот Ь^\> '*
[c3>cl)+=ujkl, {Ч
где 0 —нулевой оператор, / — единичный оператор, j, fe = 1 2 ''^
ры, удовлетворяющие уравнениям E.3) и E.4), называются операт ра»-
чтожения и создания фермионов. ^ уц,.
Определим состояние |0) (так называемое вакуумное состояние)
щее следующими свойствами: ' ЛаЛн>
с,-|0) = 0 при j = 1,2,... ,п,
(ОЮ) = 1. f*'
E.6i
Состояние |0) нормировано. Другие состояния теперь можно постпои
|0) и операторов создания с у
Применяя приведенные выше правила, находим
с1с4с|4|0)
[поE.4)] = -с1с{с4с1|0)
[поE.4)]=-с1С1(/-4с4)|0)
[по E.5)] = - схс+|0)
[noE.4)] = -(/-cIci)|0)
[по E.5)] = - |0)
В следующей программе мы реализуем операторы Ферми и приведем
несколько примеров:
// fermi.схх
#include <iostream.h>
#include <assert.h>
#include "MString.h"
// Информация о все еще активных операторах содержится
// в классе Power
{
public:
String с; // название оператора, напр. cl, с4+
int n; // степень оператора, напр. (с4+)
};
class State
{
private:
int factor; // умножение состояния
5.6. Перегрузка операторов
„г *Р'
// число разных операторов
// указатель на Power
риь/;ско*<^ктор
7/ о-
Stated .
(State &); // оператор присваивания
v0id °Perat°^eator(String, int); // операция на состоянии
void Ferffl1- COnst; // показать состояние
void disp У // 0бновить состояние
void reset(;;
};
// оператор Ферни
class Fermi
{
nrivate:
String f; // хранит название оператора, напр. cl, сА
public:
// Конструктор
Fermi(String);
State operator + (State &); // оператор создания
State operator - (State &); // оператор уничтожения
};
State:: State () : m@) , f actor A) , p(NULL) О
void State::operator = (State & s2)
m = s2.m;
P = new Power[m]; assert(p);
for (lnt i=0. .<m. .++) = ^
} factor = 82.factor;
Void State- -v
i ••termi-creator(String ch, int s)
jf factor)
" £cl,c2] + _ r
for (int .-* ~ tcl+,c2+]_+ = [cl,c2+]_+ = 0
if ((n+^ 1<m && (ch != (p+i)->c); i++)
^+1>-n'/„2) factor *= (-1);
'J если ес„
lf A==ш) Новый оператор
146
Глава 5. Основные средства языка C++
■С
if (s==l) // оператор создания
{
Power *р2;
т++;
р2 = new Power[m]; assert(p2);
for (int j=0; j<m-l; j++) p2[j] = p[j];
(p2+m-l)->c = ch; (p2+m-l)->n = 1;
delete [] p;
P = P2;
}
else // оператор уничтожения
{ m=0; factor=0; delete [] p;}
}
else // оператор появлялся раньше
{
// оператор создания, cl+ (cl+)"n = (cl+)~(n+l)
if (s==l) (p+i)->n++;
else // оператор уничтожения, [cl,c2+]_+ = I
■С
if ((p+i)->n '/, 2) // если степень оператора нечет:
{
(p+i)->n—;
if (!(p+i)->n)
{
for (int j=i+l; j<m; j++) p[j-l] = p[j];
m~;
}
}
else // если степень оператора четна
{ m=0; factor=0; delete [] p;}
}
}
}
}
void State::display() const
{
if (.'factor) cout « ";
else
{
if (factor != 1) cout « "(" « factor « ")*";
for(int i=0; Km; i++)
■C
5.6. Перегрузка операторов
„ « (p+i)->c « "+";
c0ut<< >Т1 ,= 1) cout « "~" « (p+i:
if ((p+1)'>n '
U«,,|0>B!
У
}
ra- -resetО
,oid State--r
{ л- factor=l;
B ' П т>- p=NULL;
delete LJ P> F
}
ml..Fermi (String st) : f(st) О
State Fermi: operator + (State & s2)
State sl(s2;;
sl.Fermi_creator(f,1) ;
return si;
}
State Fermi: :operator - (State & s2)
{
State sl(s2) ;
sl.Fermi_creator(f,-1) •
return si;
}
void mainQ
{
State g;
Fermi ci(..clu)j c4(„c4„);
g = cl- g.
c°ut « »' „.
Б . g-display О; cout << endl;
S-reset();
S = cl+ „.'
C°Ut«"g=". „•
• g-display(); cout << endl;
g-reset().
' 8-displayО; cout << endl;
g'resetn -
«*tcJ; ,;** (ci+ (c4+g)));
' g-display(); cout « endl;
148
Глава 5. Основные средства языка C++
g.resetO ;
g = (с4- (с4+ (с4+ (cl+ (c4+ g)))));
cout << "g = "; g.display(); cout << endl;
g.resetO ;
g = c4- (c4+ (c4- (c4+ (c4- (c4+ g)))));
cout « "g = "; g.displayO; cout << endl;
g.resetO ;
g = (c4+ (c4+ (c4+ (c4+ (cl+ g)))));
cout << "g = "; g.displayO; cout << endl;
}
Результат
g = 0
g = cl+|0>
g = Ю>
g = 0
g = (-1)* c4+ cl+|0>
g = Ю>
g = cl+ c4+|0>
5.7. Шаблоны классов
Шаблоны обычно используются для реализации структур данных и
алгоритмов, не зависящих от типов данных тех объектов, с которыми они
оперируют. Это позволяет применять один и тот же код к разным типам, где ишь.
действуют как параметры структур данных или алгоритмов. Они называются
также параметризованными типами. Одно из важных применений
шаблонов — это реализация классов контейнеров, таких, как стеки, очереди, списм-
и т.д. Шаблон «стек» может содержать объекты, принадлежащие к ти ■■
int, double или даже к определяемым пользователем типам. Компилятор
томатически сгенерирует реализации соответствующих классов, отвеч
шаблону Stack. Синтаксис объявления этого класса выглядит так:
template <class T>
class Stack
{
public:
Stack(int);
};
i си;
Чтобы объявить стек, используем следующий код:
ьц1 т":'
где символ Т служит аргументом шаблона, обозначающим произволь
5.7. Шаблоны классов
149
A00) // ЮО элементов стека типа char
cjc<ichar> oc50) // ^0 элементов стека типа int
StacK late всегда предшествует любому предварительному объ-
чев°е CJl0B° ению шаблона класса. За ним следует формальный список
^Леяию й опре,Д енНый в угловые скобки (< >). Формальный список шабло-
яВ^„0нов, за1Т nvCTbiM Если параметров много, они разделяются запяты-
11,3 ме может быть пу
"^апрямеР
>'"■ л=„ т1 class T2, class ТЗ, class T4> class Myclassl
-i„te <ciass> i4
teisplate
также объявления с указанием конкретного типа, вроде int, на-
пример
late <class T, int size> class Myclass2
омпилятор встречает определения объектов, он подставляет вместо
Д ,„™ r гнаблоне имен типов фактические типы данных. Если в шаблоне
имеющихся D . r ~
сса используется более одного шаблона, то компилятор будет выполнять
подстановки типов по очереди, начиная с первого упомянутого класса. Для
примера рассмотрим упрощенный вариант шаблона класса Vector (полный
вариант будет представлен в гл. 6):
// svector.h
«include <iostream.h>
«include <string.h>
«include <assert.h>
// определение класса Vector
template <class T> class Vector
private:
// поля данных
int size;
T *data;
publiC:
>:
"Vector (Г^ Vector<T> *) :
T ^opera.
Void resi°r, (int) const:
//Q*epaT (lnt);
Co*st Ver!P Присваивания
"ector<T> o,
«operator = (const Vector<T> &);
150
Глава 5. Основные средства языка С++
II реализация класса Vector
template <class T> Vector<T>: :Vector() : size@), data(fjm
template <class T> Vector<T>::Vector(int n)
: size(n), data(new T[n])
{ assert(data != NULL); }
template <class T> Vector<T>::Vector(const Vector<T>& v)
: size(v.size), data(new T[v.size])
■C
assert(data != NULL);
for (int i=0; Kv.size; i++) data[i] = v.data[i];
}
template <class T> Vector<T>::Vector()
{ delete [] data; }
template <class T> T &Vector<T>::operator[] (int i) const
{
assert(i >= 0 && i < size);
return data[i];
}
template <class T> void Vector<T>::resize(int length)
{
int i;
T *newData = new T[length]; assert(newData != NULL);
if (length <= size)
for (i=0; Klength; i++) newData[i] = data[i];
else
for (i=0; i<size; i++) newData[i] = data[i];
delete [] data;
size = length;
data = newData;
template <class T>
const Vector<T> &Vector<T>: .-operator = (const Vector<T> &v)
■C
if (this == &v) return *this;
if (size != v.size)
{
delete [] data;
data = new T[v.size]; assert(data != NULL);
size = v.size;
}
5.7. Шаблоны классов 151
c-i^e- i++) data[i] = v.data[i]
re*0**
>
•old inaiI10
'■° оГ(,гда должен сопровождаться
„n Vector sceo-w ^
1 // сгмв°л "_rv в уГловых скобках
Vectoi
int i>J;
„„ Vector в^**- " ,
СЯМБ ггавяых в угловых скобках
^доои ■ц<№^[--. . / / порождает вектор целых чисел
ctor<int> хE;.
,._0. i<5; ++i) XW = 1+1;
for 'Го! i<5; ++i) cout « x[i] « » ";
ZX endl; cout « endl;
or<char> chC); // порождает вектор символов
ch[0] = -a'; ch[l] = 'b'; ch[2] = 'C;
cout « ch[l] « endl;
cout « ch[l]-ch[2] « endl;
cout « endl;
Vector<char *> с B); // порождает вектор строк
с[0] = "aba"; c[l] = "bab";
for (i=0; i<2; i++) cout « c[i] « endl;
strcpy(c[0],c[l]);
cout « c[0] « endl; cout « endl;
Vector<Vector<int> > vecC);
vecroUm ±<3'' i++) vecM-resized) ;
vec. = 7< vec[0][l] = 4; vec[0][2] = -3; vec[0][3] = -17;
vec[2 го = 4; vec[l][1^ = 9; vec[l][2] = 0; vec[l][3] = 5;
LUJ =7; vec[2][l] = Ц; vec[2] [2] = 8; vec[2][3] = 77;
f{°r (i=°: i<3; i++)
cout «' J<4; J++) cout « vec[i][j] « " ";
<< endl;
Co«t « „
>COUt<<;vecfo]1K] = "«vec[0][0] «endl;
JL0J*vec[2][3] = » « vec[0][0
vec[0][0]*vec[2] [3] « endl;
152
Глава 5. Основные средства языка, C-f-f
Результат
0 1 4
b
-1
aba
bab
bab
7 4
4 9
7 11
9 16
-3 -
0 5
-17
8 77
vec[0][0] = 7
vec[0][0]*vec[2][3] = 539
5.8. Шаблоны функций
В предыдущем разделе мы рассмотрели шаблоны классов, или параметр»
ванные классы. На самом деле функции тоже могут быть параметризованы
В шаблоне функции все аргументы, характеризующие тип, должны быть
перечислены в аргументах этой функции. Тогда нам не понадобится задавать
никаких аргументов шаблона при вызове шаблона функции. Компилятор
определит аргументы, характеризующие типы, по фактическим аргументам.
Применением шаблонов функций мы можем определить семейство ро:
ственных функций, сделав параметром сам тип данных. Это увеличит
компактность кода, не умаляя никаких преимуществ языка со строгим контр-
лем типов. Обычно, чтобы функция могла работать, ее приходится опредем-
для конкретного типа данных. Если нам хочется написать библиотеку ма •'
тических программ, включающую функции Бесселя, мы должны опред
функции Бесселя для типов int, float, double, long double, comply
далее. Если мы хотим усовершенствовать алгоритм на более поздне
разработки, приходится модифицировать программы вычисления все -
ций Бесселя для каждого типа аргументов. Это утомительная и подв Р ^.
ошибкам процедура. Если же определить эти функции как шаблоны,
пилятор автоматически сгенерирует код для каждого типа данных- ,?..
Ключевое слово template всегда предшествует определению и v „3$?
тельному объявлению шаблона функции. За ними следует разделен С1->
тыми список параметров, заключенный в угловые скобки (< >)■ ^т ^ть"-'
называется списком формальных параметров шаблона. Он не
стым. Каждый формальный параметр состоит из ключевого слова с 0дре^
торым следует идентификатор, который может быть встроенным и 3$#&
ленным пользователем типом. Имя формального параметра может
5.8. Шаблоны функций
153
0х0ООЗЬЙ8
ОхОООЗЬЙс
gb-ЧЗ
рис
5.1.
Обмен
0х0ООЗЬЮ4
значений двух указателей.
ОхОООЗЬЮО
0x0003bf84
тров лишь один раз. Определение функции следует за списком
j.,-писке пара g0T пример объявления шаблона функции:
1Ы,рмальных параме v
" tefflplate <class T> T min(T хД у) { ... }
ллюстрируем понятие шаблона функции на примере функции, об-
ПРкзшей значения двух указателей. В первом варианте программы мы ис-
рм указатели, а во втором — ссылки. Схематическая диаграмма показана
на рис. 5.1-
// Первый вариант
// swapl.cxx
«include <iostream.h>
template <class T> void swap(T * a,T * b)
{
T temp;
temp = *a;
*a = *b;
*b = temp;
oi<i main()
int i, *ip;
ip = &i;
Cout « "&i =
C°Ut « M
cout«"fcip =
int н 4.
p = &j;
ut « "&i -
co*t « „p =
« &i « endl;
ip = и «.,, .
<< 3-P « endl;
« &ip « endl;
<<c &j « endl;
<<: JP « endl;
« &jp « endl;
// 0x0003bf8c
// 0x0003bf8c
// 0x0003bf88
// 0x0003bf84
// 0x0003bf84
// 0x0003bf80
154
Глава 5. Основные средства языка C++
swap(&ip,&jp);
cout « "&i = " « &i « endl;
cout « "ip = " « ip « endl;
cout « "&ip = " « &ip « endl;
cout « "&j = " « &j << endl
cout « "jp = " « jp « endl
cout « "&jp = " « &jp « endl
// 0x0003bf8c
// 0x0003bf84
// 0x0003bf88
// 0x0003bf84
// 0x0003bf8c
// 0x0003bf80
// Второй вариант
// swap2.cxx
#include <iostream.h>
template <class T> void swap(T & a,T
{
T temp;
& b)
temp = a;
a = b;
b = temp;
void mainQ
{
int i, *ip;
ip = &i;
cout « "&i =
cout << "ip =
cout « "&ip
« &i « endl;
« ip « endl;
« &ip « endl;
// 0x0003bf8c
// 0x0003bf8c
// 0x0003bf88
int j, *jp;
JP = &j;
cout « "&j =
cout << "jp =
cout « "&jp
swap(ip,jp) ;
cout << "&i =
cout << "ip =
cout « "&ip
<< &j « endl;
« jp « endl;
« &jp « endl;
<< &i « endl;
« ip « endl;
<< &ip « endl;
// 0x0003bf84
// 0x0003bf84
// 0x0003bf80
// 0x0003bf8c
// 0x0003bf84
// 0x0003bf88
5.9. Дружественность
155
„ << &j « endl; // 0x0003bf84
« "Ы " !. « jp « end1'' I' 0x0003bf8c
c0tt! « "JP * „ « &ip « endl; // 0x0003bf80
cout
' а ДрУ^сТВеНВ°СТЬ
5.У. i^fJ бсуЖДенИЙ нам известн0' что лишь методы имеют доступ к
предЫДУ1ДИХ класса. Иногда это правило становится слишком ограничи-
акРь1ТЬ1М ДаН^Ьективным. В таких случаях нам может захотеться позволить
тельным и неэ(Р^е яБЛяющейся методом класса, обращаться к закрытым
данной ФУнКЦЯИ' ОННО Этого можно добиться, объявив такую функцию другом
., непосреДСТБеи •
НЬпН1 этого класса.
(friewv * -^ _эт0 такая функция (или класс), которая, хотя она и
rinvr F класса .<»■,
методом X, имеет полные права доступа к закрытым и
защипе явля ым jyjacca x. Во всех прочих отношениях F — это нормальная
щенны класс) во всем, что касается области видимости, объявлений
(Ь\'НКЦИЯ 1ИЛИ )
' елелений. Отношение дружественности классов не транзитивно: из того
"гоХ является другом Y, a Y — другом Z, не следует, что X является другом
Z Однако дружественность может быть унаследована. Как и методы,
дружественная функция явным образом указывается в объявлении класса. Ключевое
слово friend является спецификатором объявления функции. Оно имеет
следующий общий вид:
friend возвращаемый-mun имя-функции ( список параметров )
Для примера рассмотрим оператор потокового вывода в нашем классе String.
Заметим, что он объявлен как friend в объявлении класса. Это значит, что
> казанная функция имеет доступ к закрытым данным этого класса.
class String
{
};
о
{
friend ostreamft operator « (ostreamft,const Stringft);
{ operator « (osream& out,const String& s)
°Ut « s.data;
} return out;
^ Функция no
"e данных s d Щает содержимое строки в выходной поток. Заметим, что по-
1*УЖестВен а самом деле относится к закрытым данным класса. Именно
в м Данньщ R Т°И ФункЧии данному классу сделала возможным доступ к
опиЛс°баЛьн°й пе ^ейсТвительности мы уже пользовались дружественностью
Н° в Разд к гРУ3ке операции конкатенации в классе String, как было
156
Глава 5. Основные средства языка C+-f
Рассмотрим еще один пример. Пусть мы хотим построй ^^\
класс комплексных чисел Complex (его полный вариант бул ^^Рош
в гл. 6). Вместо того чтобы определять add() и display() к °^с^Чд Ц
объявим их как дружественные функции класса Complex. ^^Дь ^
// sration.cpp
#include <iostream.h>
class Complex
{
private:
double real, imag;
public:
Complex();
Complex(double);
Complex(double, double);
friend Complex add(Complex, Complex);
friend void display(Complex);
};
Complex::Complex() : real(O.O), imag(O.O) {}
Complex::Complex(double r) : real(r), imag(O.O) {}
Complex:-.Complex(double r, double i) : real(r), imag(i) {}
Complex add(Complex cl, Complex c2)
{
Complex result;
result.real = cl.real + c2.real;
resuit.imag = с1.imag + c2.imag;
return result;
>
void display(Complex c)
{
cout « "(" « c.real « " + i*" « с.imag « ")";
}
void mainQ
{
Complex aA.2, 3.5), bC.1, 2.7), c;
с = add(a, b);
display(a); cout « " + "; display(b); cout « " = ";
display(c);
}
Результат
A.2 + i*3.5) + C.1 + i*2.7) = D.3 + i*6.2)
5.10. Наследование
157
JJaCJie^' гдный механизм повторного использования кода. Оно
5Д ' „го^ЭТ
^дов»"'- „очески «"-»- ^
Н-,( еТ иеРаР еханизм порождения новых классов из старых. Напри-
,,^В&П свойстб- Это льник и Квадрат могут быть объединены в более об
oolU"NnaCcbi КрУг' ие __ фигура. Экземпляры класса Фигура обладают ря
-,{'Р'.!йгтрактное П011я^их как площадь, периметр и т.д. Круг, Треугольник i
\^бстр^а* Б таких,
1 , 0бших свои^ ^ассЫ (или производные классы) класса Фигура, тогда как
-°аДРаТ__ЭТ0 П°класс (или базовый класс). Производные классы наследуют
*йГУРа~~эТ° НЭ^ иональные особенности базового класса. С помощью
наследства
(.BoflciJ" gHTb выстроена иерархия, позволяющая использовать один и
л0Вания может „с общая форма подкласса, производного от существу-
го КОЛ Й ИН1"Р4^
^базовогокласса, это
,„,,„ . г cublic/protected/private ] имя-базового-класса
class имя-класьи . l г
{
У;
Кчючевые слова public, protected и private описывают, как производный
класс наследует информацию от базового класса. Они позволяют
контролировать, в каком качестве данные и методы надкласса входят в подкласс:
• открытое (public) наследование: если надкласс наследуется открыто, то
доступ ко всем данным и методам надкласса остается в подклассе
неизменным,
• защшценное (protected) наследование: при этом виде наследования все
открытые данные и методы надкласса становятся в его подклассе
защищенными,
• закрытое (private) наследование: всякий раз, когда надкласс наследуется
рыто, все открытые и защищенные данные и методы надкласса стано-
закрытыми в его подклассе. Следовательно, внутренние данные и
етоды надкласса недоступны подклассу.
J5 следующей таблице
"■едования:
подытожено поведение этих трех разных видов на-
^^^ледования
Открытое
Доступ в надклассе => Доступ в подклассе
открытые члены => открытые члены
защищенные члены => защищенные члены
закрытые члены => закрытые члены
открытые члены => защищенные члены
защищенные члены => защищенные члены
закрытые члены => закрытые члены
открытые члены => закрытые члены
защищенные члены => закрытые члены
закрытые члены => закрытые члены
Десь подразумеваются данные и методы класса.
158
Глава 5. Основные средства, языка C+-f
Фигура
напечатать_Площадь()
Треугольник
(открытое наследование)
Круг
(закрытое наследование)
площадь
= Уг х основание х высота
площадь
= я х (радиусJ
Рис. 5.2. Схематичная диаграмма наследования среди классов «Фигуп
угольник» и «Круг». ' 4
%
Открытое наследование соответствует соотношению является, т.е. X* ,-
егпся Y (класс X — производный класс базового класса Y). Оно используете
когда наследование служит частью интерфейса. Например, описанное вьп»
наследование, относящееся к геометрическим фигурам, это открытое
наследование, поскольку Круг — это Фигура, Треугольник —это тоже Фигура, и так
далее.
Защищенное и закрытое наследование имеют иной смысл. Защищенное
наследование используется, когда наследование является частью интерфейса для
производного класса, но не частью интерфейса, предназначенного для
пользователей. Что же касается закрытого наследования, то оно используется, когда
наследование вообще не является частью интерфейса, а лишь особенность»
реализации.
В приведенной ниже программе базовый класс ФигураСЭЬаре) имеет
щищенные данные площадь (area) и открытый метод print_Area() I ■■
напечатать_Площадь). Классы Треугольник и Круг являются подклас
класса Фигура (рис. 5.2). Поскольку подкласс Треугольник наследует о
то, то открытый метод print_Area() остается открытым. Поэтому его
вызывать из основной программы. Однако подкласс Круг наследует з
так что в этом классе метод print_Area() становится закрытым. След ,.
но, попытка вызвать этот метод из основной программы приводит
компиляции.
// shapel.cxx
#include <iostream.h>
const double PI = 3.14159265;
class Shape
П. Виртуальные функции 159
}; . ^ дгеаО const { cout « area « endl; }
vold Shape: :p»nt.Ar
. „„лв • public Shape
class Triangle P
' CSe base, height;
^Jangle(double, double);
};
gle::Triangle(double b, double h) : base(b), height(h)
{ area = 0.5*b*n; }
class Circle : private Shape
{
private:
double radius;
public:
Circle(double) ;
};
Circle:: Circle (double r) : radius (r) { area = PI*radius*radius ; }
void main()
{
Triangle tB.5,6.0);
^:Print_Area(); //7.5
Circle rC.5);
- геа(); // функция "Shape::print_Area" недоступна
} // r.print_Area();
^ZTc^16 ФУНКЦИИ
в°Ляет систр 1вание в C++ реализуется виртуальными функциями. Это
с п ЗД11еаая прог В иРать нужный метод для конкретного класса во время
М01цЬю Кл аммы. Виртуальные функции определяются в базовом классе
6а° Иди Пп Г° СЛ0ва virtual. Определение этих функций может быть
гнорировано в любом подклассе. Если производный класс
160
Глава 5. Основные средства языка C++
Фигура
Вычисяить_Площадь
Круг
Треугольник
Вычислить_Площадь
= л х (радиусJ
Вычислить_Площадь
= Иг х основание х высота
Рис. 5.3. Схематичная диаграмма иерархии наследования.
не дает своей собственной реализации, то будет использовано определ
базового класса.
Рассмотрим тот же пример, что и в разд. 4.4. Иерархия наследов»
строится с использованием Фигура как абстрактного базового класса a for-
и Треугольник —как производных классов (рис. 5.3). Виртуальная функт,-
Calculate_Area() (Вычислить_Площадь()) объявляется в абстрактном базово"
классе Фигура. Однако ее реализация откладывается и переопределяется в ег
производных классах, в которых используются конкретные формулы для вь:
числения площади геометрических фигур.
// shape2.cxx
#include <iostream.h>
const double PI = 3.14159265;
class Shape
{
public:
virtual double Calculate_Area() const = 0;
};
class Circle : public Shape
{
private:
double radius;
public:
Circle(double);
double Calculate_Area() const;
};
class Triangle : public Shape
5.12. Обертывающий класс 161
d<"
Р'
double
..-не: oP double);
l^^e.AreaO const;
-ircie-
(double r) : radius(r) {}
. cle-:Calculate_Area() const
double ClJj adiUs*radius ; }
, return Р1*гл
• •Triangle(double b, double h) : base(b), height(h) {}
d0uble Triangle: Calculate AreaO const
J return 0.5*base*height; }
void mainO
{
Shape *s;
Circle с E);
Triangle tC,8) ;
s = &c; lis указывает на Circle с
cout « "s->Calculate_Area() = " << s->Calculate_Area() << endl;
s = &t; lis указывает на Triangle t
cout « "s->Calculate_Area() = " << s->Calculate_Area() << endl;
}
Результат
^>Calculate_AreaO = 78.5398
s->Calculate_Area() = l2
вается в 3^е3ультата видно, что подходящая функция Calculate_Area() вызы-
ГЬ1 пРограмм ИМг?СТи от ™na объекта, на который указывает s, во время
работник. ' та форма полиморфизма называется полиморфизмом вклю-
ты
в!!' °бертываюпщй класс
ЯЗь«се с++
Ст Придожения ИчаютСя основные и абстрактные типы данных. В некото-
ц Тный. Это гт Ывает полезно трактовать основной тип данных как аб-
Нов MeP> в язьттг0 т 0дит к к°нцепции, называемой обертывающим классом.
O^jV-i 'V- 13 OTTO "Wiwl *-"^*С£/ J. HLJJ Ua^l-Ll^lC IXJICUV^UA ^Х/ЛЛ. -1_>^^Л UWlUUllUlrt Л"
пРеобразуе Щей пР°гРамме мы покажем, как основной тип данных
—н, в ЯЗьткр Т а"пци1ции, tiajbiBa,ej
<4bia?abDc- B слр„Java имеются обертывающие классы для всех основных ти-
Щей программе мы покажем, как о
СЯ в абстрактный тип данных Double.
162
Глава 5. Основные средства, языка, C+-f
// doubleс.схх
#include <iostream.h>
#include <stddef.h> // для size_t
class Double
{
private:
double value;
public:
Double(double = 0.0);
void * operator new (size_t);
void operator delete(void *, size_t);
operator doubleO const; // оператор преобразования типа
>;
// реализация
Double:-.Double(double f) : value(f) {}
void * Double::operator new(size_t s)
{ return (void *) new char[s]; }
void Double::operator delete(void *p, size_t)
{ delete Dp;}
Double::operator double() const { return value; }
void main()
{
Double *sl, s2E.23);
si = new DoubleO. 14) ;
cout « "Content of si = " « *sl << endl; // 3.14
cout « "Content of s2 = " « s2 « endl; // 5.23
// применен оператор преобразования к типу 'double' !
cout « "*sl + s2 = " « *sl + s2 << endl; // 8.37
delete si;
}
5.13. Библиотека стандартных шаблонов
5.13.1. Введение
Библиотека стандартных шаблонов, или БСШ, это имеющаяся в ер^':
блиотека контейнерных классов, алгоритмов и итераторов [1]. ^ , „j.tai''
много основных алгоритмов и структур данных, известных в инФ $*>
Назначение БСШ — обеспечить набор контейнерных классов, кот
Библиотека стандартных шаблонов 163
. хО- .
нь1 и удобны. Наличие такой библиотеки упрощает
.^^ е^еНН° пограмм, а поскольку такая библиотека стандартна, то
pf одН°Вйе сЛО#НЬ1ХсТроенные на ее основе программы будут обладать высо-
niXTp°e!j0M счете п0С ости. Этот раздел посвящен новой стандартной вер-
„ *онеЧпенЫ° пеРеН ние БСШ, скорее всего, сделает программное обеспе-
к"" Tr TI. ЙсП0ЛЬ м более переносимым и более общим, а также снизит
с*« ьь- нужным ::г
Один из наиболее интересных аспектов БСШ —
lTbi на еш F e структуры от структуры почти всех ранее написан-
!11цкальн°е oTJI*L ольку C++— объектно-ориентированный язык, эти бо-
Р'|Х библи°тек' и широко использовали объектно-ориентированные ме-
\'еС ранние би л оъашя в БСШ наследование почти не применяется. Что-
тоДы, вр°Де яа этого не-объектно-ориентированного подхода, отметим, что в
бы понять смь анном программировании инкапсуляция считается основ-
объектно-ор ХОрошо сконструированного объекта мы должны стараться
ной целью, л ^^ состояния и способы поведения, необходимые для выпол-
иикапсулир ним задачи, и в то же время скрыть как можно боль-
нения СТОЯЩси i'^f «
обностей его внутреннего устройства. Почти во всех предшествующих
Ш-Р П «n-поиентированных библиотеках контейнерных классов эта философия
являлась в виде наборов классов с избыточно богатыми функциональными
возможностями, а значит, и с обширными интерфейсами и сложными
реализациями. Разработчики БСШ избрали совсем иной путь. Каждый компонент
предназначен работать совместно с богатым набором типовых алгоритмов. Эти
типовые алгоритмы не зависят от контейнеров и поэтому способны опериро-
Бать с большим разнообразием типов контейнеров. Отделив функциональные
возможности типовых алгоритмов от самих контейнерных классов, БСШ
реализует значительную экономию размера как самой библиотеки, так и
сгенерированного кода. Здесь нет дублирования алгоритмов в каждом из примерно
десятка различных контейнерных классов, так как одно и то же определение
лиотечной функции может использоваться с любым контейнером. Кроме
: определения этих функций обладают такой общностью, что их можно
•заннь ^ К Ычньм массивам языка С и указателям, а также к иным типам
^ужа'т т УЛГВСКИЙ тип Данных bool также реализован в БСШ. Ядром БСШ
ри Фундаментальные конструкции:
}■ к°нтейнеры,
Г- ^оритмы,
итераторы.
Ъекты, содег) ^ии Работают совместно друг с другом. Контейнеры — это
НТейнер0в ц Другие объекты. Алгоритмы действуют на содержимое
J ПеРебират"ерат°Ры-зто объекты, служащие указателями. Они позволя-
УкэзатеЛи пп РЖИмое контейнера таким же образом, каким мы применя-
al8orithm еРебора массива. Классы, имеющиеся в БСШ, суть
aeque f
Oe0or xuactional iterator list map
queue set stack utility vector
164
Глава 5. Основные средства, языка C+-f
5.13.2. Концепция пространства имен
Пространства имен — это недавнее добавление в C++. Прог
здает область объявления, в которой можно разместить разпи СТВ° %
программы. Элементы, объявленные в одном пространстве им Ь1е 3jW' '
элементов, объявленных в другом пространстве имен. Строка и» ■' ^^«ч,"'-
std сообщает компилятору, что нужно использовать простраист ^вц'"
// namesp.cpp
#include <iostream>
using namespace std;
namespace A { int i = 10; }
namespace В { int i = 5; }
void fA()
{
using namespace A;
cout « "In fA: " « A::i « " " « B::I « " " <« i « endl;
// => 10 5 10
}
void fB()
{
using namespace В;
cout « "In fB: " « A::i « " " « B::i « " " « i « endl;
// => 10 5 5
}
void mainO
{
fA(); fB();
cout « A::i « " " « B::i « endl; // => 10 5
using A::i;
cout « i « endl; // => 10
}
5.13.3. Класс vector
мйче
Класс векторов — это контейнерный класс. Он поддерживает дин ^ ef*
массив, т.е. массив, могущий расти по мере надобности. Для досту
элементам можно использовать стандартные индексные обозначеви ?gCt"r
Конструкции и методы (функции-члены), используемые в клас
перечислены ниже.
Библиотека стандартных шаблонов
ЗД'Э' ■— — —
v;
„rt°r
<:T>
досЯгп:
v. front
v. back U
v(aVector)
эле:
ментам
О
конструктор по умолчанию
инициализируется явным заданием размерности
размерность и начальное значение
конструктор копирования
доступ по индексу, возможо присваивание
первое значение в наборе
последнее значение в наборе
добавить элемент в хвост вектора
Вставь
v.pusb_bac value) вставить новый элемент после итератора
v. insert (iterator,
.swap
(vector<T>)
обменять значения с другим вектором
Удаление
ь ckO удалить последний элемент вектора
Terase(iterator) удалить один элемент
■/erase(iterator,iterator) удалить ряд последовательных значений
Размер
v.capacity О
v.size()
v. resize (unsigned, T)
v, reserve (unsigned)
v.empty()
наибольшее число элементов в буфере
текущее число элементов в буфере
изменить размерность до указанного значения
задать физический размер буфера
истина, если вектор пуст
закончить перебор
Итераторы
• : iterator itr объявить новый итератор
v-begin () с
v.end О начать перебор
//Mvector.cpp
Sc!1? <iostream>
lude <vector>
lng b^space std.
VeCtor<rb
Cout « wr^i wCl] = ,y'; «и
int j. WL2J « endl; // => +
for(j=o- •
' ^ J++)
w[3]
166
Глава 5. Основные средства языка C++
У
cout « "w[" << j « "] = " << w[j] << endl;
vector<double> v;
double x;
cout << "ввести число типа "double", за которым следу
while(cin » х, х != 0.0) °-0\ц-,
v.push_back(x); // добавляет значение х типа "н
// в хвост вектора v e"
vector<double>::iterator i;
for(i=v.begin(); i != v.end(); ++i)
cout << *i « endl; // разыменование
bool b = v.emptyO;
cout « "b = " « b << endl;
// вставить элемент в вектор
double у;
cout << "ввести вставляемое значение"
cin » у;
vector<double>: : iterator p = v.beginQ;
р += 2;
v.insert(р,4,у); // указать на третий элемент,
// вставить четыре элемента
for(i=v.begin(); i != v.end(); ++i)
cout << *i << endl;
int length = v.sizeQ;
cout << "размерность = " << length << endl;
return 0;
Мы читаем текстовый файл строка за строкой, пока не встретим kj
слово eof. Единственная загвоздка в том, что нельзя применять опер'
токового ввода » string. Эта функция читает только слово, а не bci
Решение состоит в применении глобального оператора
getline(istream&,string)
Пусть наш текстовый файл (файл ASCII) называется william.txt,
жит следующее:
to be or
not to be
После компиляции и сборки исполняемого файла мы вводим в кома
ке следующее:
13 ^блиотека_стацдарт„ых шаблонов
i.cpP
eam>
,/r^din
яе <ioStr
»ioeltt£ <strlnS>
#i»«lttJ! <vector>
e „amespace Btd;
" , ar*c,char -rgvG)
intBain(^argC'
:ector<strmg> v,
string s;
tfhile(getline(i^.s»
* / ->
v puch.backCs;;
cout « s «endl;
}
cout « »,[!] = » « vCll « «^-. " => ^ t0 Ье
return 0;
}
//bubblest.cpp
«include <algorithm>
«include <vector>
«include <stdlib.h>
♦include <iostream.h>
using namespace std;
voirilate <<class RandomAccessIterator>
RaL bUbbleSort(RandomAccessIterator first,
T^KAccessIterator last)
V* exchanged;
do
{
exchanK , _ n ,
for(R 6ea - 0; last—;
. ^^Accessiterator iter=first; iter < last;
168
Глава 5. Основные средства, языка C+-f
if(*iter > *(iter+l))
{
iter_swap(iter,iter+l);
exchanged++ ;
}
}
> while(exchanged);
}
void main(void)
{
const int nData = 6;
vector<unsigned long> data(nData);
for(int n=0; n < nData; n++)
{
data[n] = rand();
cout << data[n] << " ";
}
cout « endl;
bubbleSort(data.begin(),data.end())
// после сортировки
for(n=0; n<nData; n++)
cout « data[n] « " ";
}
5.13.4. Класс List
Класс списков — это тоже контейнерный класс. Связанный список —это
структура данных, которую мы выбираем, когда число элементов в набор
может быть ограничено или сильно варьируется в процессе работы прогр
Подобно классу векторов, класс связанных списков содержит значения одо
типа. Списки не индексируются. Ниже перечислены конструкторы и
этого класса.
Конструкторы и присваивание
list<T> 1; конструктор по умолчанию
list<T> l(aList) конструктор копирования
1 = aList присваивание
l.swap (aList) обмен значениями с другим списком
Доступ к элементу
1.front() первый элемент в списке
l.backQ последний элемент в списке
Библиотека, стандартных шаблонов
.J.J-
169
1-P,sb baC .for,value)
l-PttS ;t(iterator,
l.i^LontO
^^(^^S^terator)
nIre (valued
геШ if(predicate)
:em°ve- V
j.
1
1
l.r
добавить значение к началу списка
добавить значение к концу списка
вставить значение в указанное место
удалить значение из начала списка
удалить значение из начала списка
удалить указанный элемент
удалить ряд элементов
удалить все вхождения значения
удалить все значения,
удовлетворяющие условию
размер
1.empty
l.size
Итераторы
list<T>:: iterator itr
1.beginО
I.endO
1.rbegin
1.rend
истина, если список пуст
возвращает число элементов в списке
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор
закончить обратный перебор
Прочее
1.reverse()
l.sortQ
1. sort (comparison)
1.merge(list)
обратить порядок следования элементов
разместить элементы в восходящем порядке
упорядочить элементы с помощью сравнения
соединить с другим упорядоченным списком
приведенной ниже программе показано использование класса List,
/enlist. срр
«delude <iostream>
delude <list>
include <string>
Usi»g падезрасе std.
v°id
{
maib(void)
1st;
Ust<ch*r>
int i;
for(i=n. ■
colltP:h:back(-x,+i
OUt << "^'».-A'+i);
Cout « „;a3MeP списка =» « Ist.sizeO « endl;
list<char>°flep)KIIMoe списка: ";
•iterator p = lst.beginQ;
170
Глава 5. Основные средства языка C-t—f-
while(p != lst.endQ) { cout << *p << " "; p++; }
1st.push_back('A');
cout « endl;
1st.sort();
cout « "отсортированное содержимое:\n";
p = las.begin();
while(p != Ist.endO) { cout « *p « " "; p++; }
cout << endl;
list<string> str;
str.push_back("Good");
str.push_back(" ");
str.push_back("Night");
str.push_back(" ");
str.push_back("Egoli") ;
list<string> str: :iterator q = str.beginO;
while(q != str.endO) { cout << *q « " "; q++; >
5.13.5. Класс Stack
Класс стек представляет собой контейнерный класс с ограниченной
функциональностью. По умолчанию тип данных этого контейнера—vector. Это
структура данных «последним вошел — первым вышел». Класс Stack не
допускает итеративного перебора своих элементов. Это набор элементов данных.
расположенных в линейную последовательность, вместе со следующими пятью
операциями:
1. CreateStack создает стек.
2. MakeStackEmpty удаляет из стека все содержащиеся в нем данные.
3. Push добавляет элемент с одного конца стека, называемого вершиной ст ^
4. Pop удаляет элемент, находящийся в вершине стека, и делает этот эл
доступным для использования.
5. StacklsEmpty проверяет, содержит ли стек какие-либо элементы.
Элементами стека могут служить целые числа, вещественные числа, зн
абстрактные типы данных. Шаблон стека имеет два аргумента:
stack<T,vector<T> > s;
Ниже перечислены операции, предоставляемые БСШ для класса Sta
Вставка и удаление
s.push(value) помещает значение в вершину стека
s.topO доступ к значению в вершине стека
s.pop удаляет значение из вершины стека
Библиотека стандартных шаблонов
., „мер
ваЯв
Прнсвз
равнения
ание
число элементов
стека
и
стина,
если стек пуст
>_ ой ппогоамме показано одно простое применение класса Stack.
В следующей uy f
ц stackl-cpp
#include <iostream>
«include <vector>
«include <stack>
using namespace std;
int main О
{
stack<char, vector<char> > S, T, U;
cout « "&S = " << &S << endl; // => адреса
S.push('X')
S.push('Y');
S.push('Z');
cout « "символы, размещенные в S: X Y Z\n";
cout « "размер S = " « S.sizeQ « endl; // => 3
T = S;
co^t Г '^°СЛе Т = S; мы имеем ";
cout « (S == т ?
u-push('X') •
U-Push(>w>)!
c°ut «
T"
'S != T") « endl; // => S
*-°ut « ",.
cout « „размеЧено в U: X W\n";
c°ut « ^епеРь выведем ";
^ < U ? "S < U" : "S >= U") « endl; // => S >= U
b°ol b = Гт
« b « endl; // => 1 (true)
} r6t*rn o-
172
Глава 5. Основные средства языка, C++
5.13.6. Класс Queue
Очередь — это структура данных «первым вошел — первым вьш
чает, что элементы добавляются в хвост очереди и могут быт *'^тос.
ее головы. Очередь —это адаптер контейнера, т.е. она реализует ^^к'"''
коего лежащего в ее основе контейнерного типа. Очередь не л tl°BePx
ративного перебора ее элементов. Очередь — это набор элементов УСКае? и
ганизованных в линейную последовательность, вместе со следую ЙНь,)<. <,-
операциями: Ми п%ь[
1. CreateQueue создает очередь.
2. MakeQueueEmpty удаляет все содержащиеся в очереди элементь
3. EnQueue добавляет один элемент к одному из концов очереди
му ее хвостом. Ва(;:'-
4. DeQueue удаляет один элемент с другого конца очереди, называем
ловой, и делает этот элемент доступным для использования. ° г'~
5. QueuelsEmpty проверяет, не пуста ли очередь.
Шаблон очереди имеет два аргумента
queue<T,list<T> > q;
Ниже перечислены операции, предоставляемые БСШ для класса Queue.
Вставка и удаление
q.push(value) ставит значение в хвост очереди
q.front() доступ к значению в голове очереди
q.back() доступ к значению в хвосте очереди
q.popO удаляет значение из хвоста очереди
Размер
q.size() число элементов в очереди
q.empty() истина, если очередь пуста
В следующей программе показано простое применение класса Queue.
// queue1.срр
#include <iostream>
#include <list>
#include <queue>
using namespace std;
int mainO
{
queue<double,list<double> > 0;
cout « "&Q = " « &Q « endl; // => адрес
„ ииблиотекя стандартных шаблонов 173
Q-Pus;D.5);
°-£bF-9)' размещения 3.14, 4.5, 6.9:\п;
Q.P ^ ..0осле Р „ <<; q. front () « endl; // => 3.14;
со»* „g.frontW и Ьаск() <<; endl // => 6-9
с << "Q-back О -
c0ut
а-Рор();„^гяе выполнения Q.pop():\n";
cout « °troatQ = » « Q. front () « endl; // => 4.5
cout ^
,, «о sizeO = " « Q-sizeO « endl; // => 2
cout <^ 4'
queue<double,list<double> > P;
P = Q<< "P front О = " « P.front() « endl; // => 4.5
C°ut « "p'back О = " « P.backO « endl; // => 6.9
return 0;
\
5.13.7. Класс Deque
Двусторонняя очередь — это абстрактный тип данных, в котором вставки и
удаления можно производить с обоих концов очереди. Тем самым она
сочетает свойства стека и очереди. На практике такая очередь применяется в двух
вариантах. Первый из них — двусторонняя очередь с ограничением на вставку,
в которую можно вставлять элементы лишь с одного конца, а второй —дву-
< горонняя очередь с ограничением на удаление, в которой изъятие элементов
возможно лишь с одного конца.
иже перечислены операции, предоставляемые БСШ для класса
двусторонних очередей.
deque<T> Л. конструктор по умолчанию
deque<T> ,.. '' конструктор с исходным размером
a6^<T>d(int,value).
конструктор с исходным размером
и начальным значением
конструктор копирования
SWaP(aDeau 1 присваивание очереди из другой очереди
^eque<T-i а г ~ и начальным значением
d _ y-y d(aDeque) •
~ ^eq^g. ' конструктор копирования
обмен содержимого с другой очередью
ДостУп к ^
Jti] Рентам и вставка
•fro
3 , ^О доступ по индексу, возможно присваивание
d , О первое значение в очереди
ei"t(iterat последнее значение в очереди
>value) вставить значение перед итератором
174
Глава 5. Основные средства языка, C++
d.push_front(value)
d.push_back(value)
Удаление
d.pop_front()
d.pop_back()
d.erase(iterator)
d.erase(iterator,iterator)
вставить значение в ronoBv ^
вставить значение в хвост °15'Гей&
удалить элемент из головы йп
удалить элемент из хвоста век
удалить один элемент °
удалить ряд элементов
Ра
Размер
d.sizeO
d.empty()
текущее число элементов
истина, если вектор пуст
Итераторы
deque<T>::iterator itr
d.begin()
d.endO
d.rbeginO
d.rend()
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор
закончить обратный перебор
Приведенная ниже небольшая программа показывает применение клас
Deque.
// dequel.cpp
#include <iostream>
#include <deque>
using namespace std;
int main()
■С
queue<char> DA0,"A");
queue<char>::iterator i;
f or (i=D. begin () ; i != D.endO; ++i)
cout « *i « " "; // => A A A A A A A A A A
cout << endl;
deque<char> E(D); // конструктор копирования
E.push_front('Z');
E.push_back('Y');
for(i=E.begin(); i != E.end(); ++i)
cout « *i « " "; // => Z A A A A A A A A A A Y
cout << endl;
со
ut
<:<:
ut
со
сцаг
g.empty
5.13
О
О << e
библиотека, стандартных шаблонов
175
« endl; // => 0 (false)
ndl; // => I2
= е[оЗ;
<:<:
_ " << с
« endl; // => Z
с out
cout
<:<:
<<; с << en'
dl; // => А
return
0;
}
513.8. Класс Bit Set
битов>> в БСШ реализует побитные операции.
э
Класс
Конструкторы
bitset<N> s
bitset<N> s(aBitSet)
Побитные операции
s.flipO
s.flip(i)
s.reset(O)
s.reset(i)
s.set()
s.set(i)
s.test(i)
Q-
конструирует набор из N битов
конструктор копирования
инвертирует все биты
инвертирует позицию i
задает всем битам значение "ложь"
задает значение "ложь" позиции i
задает всем битам значение "истина"
задает значение "истина" позиции i
проверяет, имеет ли бит в позиции i
значение "истина"
ерации над всем набором
s-anyO
возвращает значение "истина", если
s.none()
s-count()
ПРИсваиван:
:ие
какой-либо бит имеет значение "истина"
возвращает значение "истина", если
все биты имеют значение "ложь"
возвращает число битов,
имеющих значение "истина"
К.
S1 I s2
Другими наборами битов
si -
s2
s2
побитное И
побитное включающее ИЛИ
побитное исключающее ИЛИ
возвращает значение "истина",
если эти два набора совпадают
176
Глава 5. Основные средства языка C++
Другие операции
s << n сдвиг набора влево на одну позицию
s >> п сдвиг набора вправо на одну позицию
s. to „string возвращает представление набора в витт=
В следующей небольшой программе показано применение класг
// bitsetl.cpp
#include <iostream>
#include <bitset>
#include <string>
using namespace std;
int main()
{
const unsigned long n = 32;
bitset<n> s;
cout << s.setQ << endl; // задает всем битам значение 1
cout << s.flipA2) << endl; // инвертировать бит в позиции 12
bitset<n> t;
cout << t.resetO << endl; // задает всем битам значение "ложь"
t.setB3);
t.setB7);
bitset<n> u;
u = s & t;
cout << "u = " << u << endl;
bitset<n> v;
v = s | t;
cout << "v = " << v « endl;
bitset<n> w;
w = s ~ t;
cout « "w = " << w << endl;
bitset<n> z;
z = w ~ w;
cout « "z = " << z << endl;
cout « "z.to_string() =" « z.to_string();
return 0;
Библиотека стандартных шаблонов
177
множество» определяется через объекты и опе-
„ данных «»ит— 7 Г 7
Ь1й тип Да ост0 мцожества (конечные) в обычном математиче-
давление которых не указано. В БСШ оба класса set и
!,а11ИИ' ысле' яРея анных, являющиеся шаблонными структурами данных,
,^\с' t —это тяяы Д g(ioHa представляет тип элементов, из которых состоит
зи1"„..пс аргУменТ ,„ГТНПсть В классе set каждый элемент идентичен сво-
которь1Л „Л,оя совокупна10
" матРйБ ючи уникальны. Поэтому два разных элемента множества
'''"■ кЛ1°чУ> и эТИ Мультимножество отличается от множества только тем,
'М' .nrv'T быть рав павные элементы. Операции в классе «множество»
'"'N ' .-пжет содержа!ь F
1Г<»
оно
может
npi
,ведены
ниже.
Конструкт
set<T> s:
BUltlSet<T> m;
set<T> s(aSet);
;Bltiset<T> m(aMultiset)
о = aSet
s.swap
(aSet)
конструктор по умолчанию
конструктор по умолчанию
конструктор копирования
конструктор копирования
присваивание
обменивает элементы с множеством,
указанным в аргументе
Вставка и удаление
s. insert (value_type)
s. erase (value_type)
s. erase (iterator)
s.erase(iterator,iterator) удалить последовательность значений
вставить новый элемент
удалить все элементы,
имеющие указанное значение
удалить элемент, указанный итератором
Проверка содержимого множества
s-empty ()
s-si2e()
^t(value_type)
' ^d(value_type)
;;;°-b0un,(Value_type)
s-4u г'Г value-type)
^range(value_type)
;тераТоры
set<T>. .
3-beei;;'ter«or itr
e§in()
•Геад()
"истина", если множество пусто
число элементов множества
подсчитывает число вхождений
находит значение
первое вхождение значения
следующий элемент после значения
нижняя и верхняя грани
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор
закончить обратный перебор
178 Глава 5. Основные средства языка C++
В следующей программе покгизано применение класса J
// set2.cpp
#include <iostream>
#include <set>
using namespace std;
int mainО
■С
set<int> s;
s.insertB3);
s.insertD5);
s.insert(-1);
s.insert(-2);
s.insertB3);
s.insertE1);
cout << s.empty() << endl; // => 0 (false)
cout << s.size() << endl; // => 5
cout << s.countB3) << endl; // => 1
set<int>: : iterator i = s.beginO;
for(i=s.begin(); i != s.end(); ++i)
cout << *i << " ";
cout << endl;
s.eraseD5) ;
for(i=s.begin(); i != s.endQ; ++i)
cout << *i << " ";
cout << endl;
cout << s.size() << endl; // => 4
return 0;
}
библиотека стандартных шаблонов 179
Класс
Map
MaD (называемый также словарем или таблицей) — это ин-
■ необязательно целые числа:
13-1°
5' -сотобРаЖеНдоЖество. Значения индексов
КЛ'1С оВанН°е М й любые упорядоченные значения данных. Поэтому ото
могут
бра:
саМй м^^- «упность пар ключ-значение. Для отображений никакие
уценке
-это
быть равны. Мультиотображения отличаются от отобра-
К МОГУТ Dbixo Fi»~"~- --j- - i -г--
кЛюча не м в них допускаются повторяющиеся ключи. Ниже
передний лйШь т ' 0Перации для класса отображений.
Делены возмо*
КоВс1РУ^Ры_
/-Т1 Т2> т;
шар ^Tl T2> m;
„<:Т1 Т2> пи
шаР • n<Tl T2> m
ш = aMap
(aMultiMap)
конструктор по умолчанию
конструктор по умолчанию
конструктор копирования
конструктор копирования
присваивание
Вставка и удаление
mtkey]
m. insert (value.typej
m. erase (key)
m. erase (iterator)
возвращает ссылку на значение с ключом
вставляет данную пару ключ-значение}
удаляет значение с данным ключом
удаляет значение, указанное итератором
Проверка содержимого совокупности
ш. empty ()
m.sizeO
m. count (key)
m. find(key)
m.lower_bound(key)
m- upper _bound(key)
m-equal.range(key)
Итераторы
ШаР<Т>:: iterator itr;
m■begin()
m-end()
E-rbegin()
m-rend()
B,
^еДУЮщих
aP.cPp
истина", если совокупность пуста
возвращает размер совокупности
подсчитывает число элементов с данным ключом
находит элемент по данному ключу
первое вхождение ключа
следующий элемент после ключа
нижняя и верхняя грани
объявить новый итератор
начать перебор
закончить перебор
начать обратный перебор
закончить обратный перебор
Двух программах показано применение класса Map.
^lude <
iostr
«incbT <maP>
Us
ln=lude
Ude <
eam>
•ng
паше.
stri
ing>
space
std;
180
Глава 5. Основные средства языка C-f-f
int mainQ ^~^
{
map<char,int> ml;
int i ;
for(i=0; i<26; i++)
{
ml. insert(pair<char,int>('A'+i,65+i)); // ASCII таблица
char ch;
cout << "введите ключ: ";
cin >> ch;
map<char, int>::iterator pi;
// поиск значения данного ключа
pi = ml.find(ch);
if(pi != ml.endО)
cout << "номер ASCII в таблице: " « pi -> second << endl-
else
cout << "ключа нет в таблице.\п";
map<string,double> m2;
m2.insert(pair<string, double>("Willi",0.0));
m2.insert(pair<string, double>("Fritz",1.0));
m2.insert(pair<string, double>("Charles",2.0));
map<string, double>::iterator p2;
p2 = m2.find("Fritz");
cout << p2 -> second « endl; // => 1
cout << p2 -> first << endl; // => Fritz
return
}
\\ mystl.cpp
#include <iostream>
#include <map>
#include <string>
using namespace std;
typedef map<string, string> vocabulary;
void main(void)
{
string wrd, def;
библиотека стандартных шаблонов 181
voCi
,аВ«1аГ1Г iterator I"!
/•
{ ^ "введя слово:
coUt.fiuSb();
cin » W^;"Q") II (wrd == "q")) break;
if(("rd
cout.fl*sh0;
cin » def;
voc
[«rd] = def;
if (voc. empty 0)
{ cout « "словарь пуст!" « endl'
exit(O);
>
cout « "в словаре имеется ;
cout « voc.size() « "элементов." « endl;
cout « "поиск слов в словаре" << endl;
while(true)
{
cout << "введи слово: "; cin >> wrd;
if((wrd — "Q") II (wrd == "q")) break;
itr = voc.find(wrd) ;
if(itr != voc.endO) cout « itr->second « endl;
else cout « "не найдено" « endl;
5лз-П. Класс Algorithm
щ,и инцп».^еаСТВуЮт на соДеРжимое контейнеров. К ним относятся функ-
"'"ч'ероп а изаЧИи' соРтировки, поиска и преобразования содержимого кон
"и функции мог;
Функции суть:
«1ных Эт j_ унки'ии могут также применяться к массивам основных типов
sort
С.-,,..,,. ' lnd' СОрУ> reverse, replace
пи
i,; Рамма показывает, как применяются некоторые из этих функ-
//aW
•срр
*
delude <■
<Vector>
182
Глава 5. Основные средства языка С+-{-
using namespace std;
int main()
{
const int n = 5;
double x[n];
x[n] = 3.13; x[l] = 4.5; x[2] = 1.2; x[3] = 0.8;
sort(x,x+n);
cout << "массив после сортировки: \п";
double* p;
for(p=x; p != x+n; p++)
cout << *p « " ";
cout << endl;
double y;
cout << значение типа double может быть выбрано
cin » у;
double* q = find(x,x+n,y);
if(q == x+n)
cout « "не найдено\n";
else
i
cout << "найдено";
if (q == x)
cout << "как первый элемент";
else
cout « "после " « *—q;
>
cout << endl;
reverse(x,x+n);
double* r;
cout << "массив после обращения: " << endl;
for(r=x; r != x+n; r++)
cout « *r « " ";
cout << endl;
int a[6] = { 4, 5, 10, 20, 30, 84 };
int b[5] = { 7, 9, 14, 35, 101 };
int с [11] ;
merge(a,a+6,b,b+5,c);
int i;
for(i=0; i<ll; i++)
cout « "c[" « i « "] = " « c[i] « endl;
5.14. Рекурсия
183
.^-""""^" - "otto";
Ct^^%fr+length)'t ','1');
1П\»re(.9tT' A-\- II => oll°
vecto^jf:^^; v[2] = 3; v[3] = 2;
Sor^ "E!;10. w[2] = 3; w[3] = 12; w[4] = 5;
.rni = 8; wlxj
■ [O]
ort(v.beginO.-en^^;
;:rrt(u.begin(),w-endO);
vector<int ' end())W-begin(),w.end(),inserter(z,z.begin))):
merge (v.Deg-uiw
for(i=0; i<9; i++)
cout « "z[" « i « = " << z[i] K< endl;
return 0;
I
5.14. Рекурсия
/'- курсия играет в информатике центральную роль. Например, строка и
линейный связанный список суть рекурсивные структуры. Рекурсивная функция —
,гп така-я функция, определение которой включает вызов ее самой. Для рекур-
1 >ш необходимо условие обрыва. Конечно, недопустимо использовать функцию
~ащ в рекурсивном вызове.
^ - ы рассмотрим пять примеров. В первом примере мы покажем, как можно
win аТЬ умножение с помощью рекурсии. Второй пример показывает ре-
.••!ожно ^еления посредством вычитания и рекурсии. С помощью рекурсии
»JHIb. не я** таКже ДЛИНУ строки. На каждом шаге нам необходимо прове-
:;0-~юм >\о> IifJIIICb ли мы уже до конца строки, обозначенного нулевым сим-
51" -Метолк четвертом примере мы рассматриваем связанный список. Все
;я'к>'рсии сп°льзуют рекурсию. Наконец, мы приведем пример взаимной
УХ чисел с помощью рекурсии
<10stream.h>
;asigaed -.
■Long щи]ч./
"Unsigned long a,unsigned long b)
iong result = o-
184
Глава 5. Основные средства языка C+-f-
if(а == 0) return 0;
if(b == 0) return 0;
if(a == 1) return b;
if(b == 1) return a;
else result = mult(a,b-l) + a;
return result;
>
int mainО
{
unsigned long n, m;
cout << "введите неотрицательное целое n = ";
cin » n;
cout << "введите неотрицательное целое m = ";
cin >> m;
unsigned long result = mult(n,m);
cout << "результат = " << result << endl;
return 0;
>
// recursion2.cpp
//
// целочисленное деление двух чисел с помощью рекурсии
// Ъ - это делитель
//а - это делимое
#include <iostream.h>
#include <stdlib.h>
unsigned long divide(unsigned long a,unsigned long b)
{
unsigned long result = 0;
if(b > a) return 0;
if(b == 0) { cout « "нельзя делить на 0"; exit@); }
if(b == 1) return a;
else
result = divide(a-b,b) + 1;
return result;
>
int main()
{
unsigned long n, m;
cout << "введите неотрицательное целое n = ";
5.14. Рекурсия
185
» n; ,__ неотрицательное целое m = ;
и >> т'
Cl result = divide(n,m);
^ened l°nS _ .. « result « endl ;
^ \c "результат
cout
return 0;
; определяет длину строки рекурсивно. Строка состоит из
Эта пр°гРа щенНОй в памяти последовательности символов, оканчиваю-
непреРьШН° Р олом \' 0'. Концептуально мы можем представлять себе стро-
щейся NUL ■ типа NULL, состоящей лишь из NULL символов, либо как
kV либо ка м следует строка. Такое определение строки описывает ее
символ, структуру данных. Значит, мы можем воспользоваться этим
как рекур чтобы запрограммировать основные функции, работающие со
аро^ми, рекурсивно.
// recursion3. срр
«include <iostream.h>
«include <string.h> // для функции strcpy
int
{
}
int
{
length(char
if(*s == >\0!
return 0;
else
return
mainQ
A+
*s)
')
length (:
b+D);
=ter. st, .,il]ita,,
c°ut « еднИНа °Троки st Равна: " « resl; // => 10
lnt res2 - , '
C°Ut <<: "Дли mpty);
C°Ut << endl^ ПУСТ0Й СТР0КИ равна: " « res2; // => 0
г г „ Z = NULL;
etrreBcbar[4]. Vv
РУ(г,"о11„ ' N\ выделение памяти
');
186
Глава 5. Основные средства языка
int res3 = length(z);
cout << "Длина строки z равна: " << res3; /,
delete [] z ;
return 0;
}
В следующей программе показано, как можно рег
сок посредством рекурсии.
// rlist.h
#ifndef RLIST_HEADER
#define RLIST.HEADER
#include <assert.h>
template <class T>
class RList
{
public:
RList();
RList(const RListft);
"RList();
RList &operator=(const RListft);
int operator==(const RListfe);
void Insert(const T&);
int Search(const T&);
int Delete(const T&) ;
T Head(void);
RList* Tail(void);
int Empty(void);
RList* Reverse (RList*);
private:
T Head;
RList* tail;
int empty;
}
template <class T>
RList<T>::RList()
{
empty = 1;
}
5.14. Рекурсия
■I-" tS'^ RLlSt<T>& RU
RlAst
., = new
t.ai-1
}
1я,е <class T>
'"effiP<1>-:RListO
RUst<i
1^(!ешр*у)
delete tail!
}
template <class T>
RList<T> &RList<T>: :operator= (const RList<T> &RL)
{
if (this == &RD return;
if(!empty) delete tail;
empty = RL.empty;
head = RL.head;
tail = new RList<T>(*RL.tail);
template <class T>
int RList<T>::operator==(const RList<T> &RL)
"CeaptyttRL.empty) return 1;
;JJ№is==&RL) return 1;
. XQ «List<T>. -Tr,
• -insert(const T fttolnsert)
Jf(empty)
tan e toInsert;
6mpty=0new RList<T>;
ert^olnSert);
else
} taU-I,s
188
Глава 5. Основные средства я
template <class T>
int RList<T>::Search(const T fctoSearch)
{
if(empty) return 0;
else if(head == toSearch) return 1;
else return tail -> Search(toSearch);
>
template <class T>
int RList<T>::Delete(const T fctoDelete)
{
if(empty) return 0;
else if(head == toDelete)
{
head = tail -> head;
empty = tail -> empty;
tail -> Delete(tail -> head);
if(empty) delete tail;
return 1;
}
else return tail -> Delete(toDelete);
>
template <class T>
T RList<T>::Head(void)
i
assert(!empty);
return head;
}
template <class T>
RList<T> *RList<T>::Tail(void)
{
assert(!empty);
return tail;
>
template <class T>
int RList<T>:-.Empty(void)
i
return empty;
>
template <class T>
RList<T> *RList<T>::Reverse(RList<T> *RL)
i
5.14. Рекурсия
{ ^<r? *temp
s new
temp
temp
return
pise
RLlSo<Terse(RL'>TailO);
.J) insert(RL->Head());
return R;
Примененгю этой программы показано ниже,
/rlisteg.cpp
«include <iostream.h>
«include "rlist.h"
:nt main(void)
?.List<int> L;
ir.t i;
far(i=l; i)=8. i++) l. Insert (i);
f--ist<int>* LX = &L;
одный RL
Empty())
^<<LX-,Head() « , ,.
} Lx "> Taii().
C°Ut « *ndl « endl.
'-lst<int>* LP =LRReverse(&L);
"-"hile( iLr
( -> Empty())
У= 2 -> !> Head()
?иеОт7СХОДНЫЙ R^St 6CTb: " « endl;
Taii().
<<c endl;
190
Глава 5. Основные средства, языка C-f-f-
cout << "что произошло с исходным списком: "« endl-
LP = &L;
while(!LP -> Empty());
{
cout « LP -> HeadO « ' ' ;
LP = LP -> Tail О;
>
cout << endl;
cout « "удалим некоторые элементы: "<< endl;
L.Delete(l):
L.DeleteD):
L.Delete(8):
LP = &L;
while(!LP -> Empty())
{
cout « LP -> Head « ' ';
LP = LP -> Tail();
}
cout << endl;
cout << "есть ли 3 в списке: " « L.SearchO) « endl;
cout << "есть ли 4 в списке: " << L.SearchD) << endl;
return 0;
}
На выходе получаем:
Исходный Rlist есть
12345678
87654321
что произошло с исходным списком:
12345678
удалим некоторые элементы:
2 3 5 6 7
есть ли в списке 3: 1
есть ли в списке 4:0
В случае взаимной рекурсии у нас имеются две функции, вызываю 0
друга. В качестве примера рассмотрим одну реализацию синуса и
Используем тождества
sin(a;) = 2sin(a;/2)cos(a;/2),
cos{x) = cos2 (ж/2) - sin2(ж/2) = 2 cos2 (ж/2) - 1-0-
5.15. Заключение
191
я ют друг ДРУга и потому рекурсивны. Функция синус
.пСцНУс бьГ ,.г /Тля функции косинус у нас есть две возможности.
-г I* г^. КОСИНУ ^ "
"■ лч'НКИ0 ,- олл RTODvro возможность, в которой косинус вызывает
,,аСТ ы мы выбираем w i .у
"•"'" роты
•;1.М
^"("r!;6B.
h>
uble,double); // опережающее описание
double sine ( о 'g double); // опережающее описание
double cosine
v01d mainO
{ double x = 3.14159;
double eps =0.001;
double resl = sine(x,eps);
cout « "resl = " « resl « endl;
double res2 = cosine(x,eps) ;
cout << " res2 = " « res2 << endl;
i
double sine (double x,double eps)
double s;
if(fabs(x) < eps) { s = x*A.0 - x*x/6.0); }
else s = 2.0*sine(x/2.0,eps)*cosine(x/2.0,eps);
return s;
Г8 cosi^(double x.double eps)
double c-
^(fabsfyl < л г
else c = 2 n ^ { ° = 1"° " x**/2-0; >
return c- °Slne(x/2.eps)*cosine(x/2,eps) - 1.0;
в'15* Зак^чение
Fa*, ^^ Korit= v""r"t-- iVlbi начали с описания указателей и ссылок в
■ кЧии р i ^а и как mv , -п
ь++ в ^ их можно применять. Были описаны разные
конвой
с^п^ч11УСапы «Ровные
•~эдые в """мме программные конструкции и инструменты,
заЧем адыке C++.
Мь
с
том числе:
192
Глава 5. Основные средства, языка C++
• класс,
• конструкторы и деструктор,
• преобразование типов,
• перегрузка операторов,
• классы-шаблоны,
• функции-шаблоны,
• дружественные функции,
• наследование,
• виртуальные функции,
• библиотека стандартных шаблонов,
• рекурсия.
В каждом разделе для иллюстрации приведены примеры. Они показ
возможность поддержки объектно-ориентированного программировани *'
торое включает: 'к>
• инкапсуляцию,
• передачу сообщений,
• наследование,
• полиморфизм и другое.
С помощью этих свойств, заложенных в языке, программисты могут писать
мощные объектно-ориентированные программы.
В следующей главе с помощью C++ будет построен набор полезных
классов, в котором будет использовано большинство описанных здесь свойств.
Классы
^пьютерной алгебры
ко
,„„ мь1 начнем основное строительство классов нашей символьной
п этой главе
Будут охвачены многие математические структуры, а также неко-
СИСТ бшие структуры данных информатики. В описании классов мы будем
Т°Р ться от простых структур (очень длинные целые, рациональные и т. п.)
Д более сложным структурам, таким, как вектор, матрица и т.д. Вот полный
список классов, представленных в этой главе:
1 Verylong описывает абстрактный тип данных (АТД) «целые числа»
(без верхней и нижней границ значений).
2. Rational описывает АТД «рациональные числа».
3. Complex описывает АТД «комплексные числа».
4. Quaternion описывает АТД «кватернионы».
5. Derive описывает точное дифференцирование.
6. Vector описывает структуру данных «вектор».
7. Matrix описывает структуру данных «матрица».
0. Array описывает структуру данных «массив».
9. String описывает АТД «строка».
10. BitVector описывает АТД «вектор битовых полей».
1. List описывает АТД «связанный список».
• Polynomial описывает АТД «многочлен».
• Set описывает АТД «конечное множество».
его ^ждого класса объясняются основные идеи и теория, лежащие в
к°мпон ' С послеДУюЩим абстрагированием. Также описываются различные
^ Кажл Ы Классов (п°ля данных, конструкторы, операторы, методы и т. д.).
Жения r разДеле приводятся небольшие примеры. Более развернутые прило-
°УДУТ представлены в гл. 8.
асс сверхдлинных целых
1- бдение
лОЧи
Це.г
ков п1И01еннь1Й
прогрд Тип Данных реализован непосредственно в большинстве язы
размеры регистров ЦП компьютера наклады-
огРаничения на диапазон допустимых значений целых чисел.
>Н8
194
Глава 6. Классы компьютерной алгебры
Так, для 4-байтных целых значения лежат между —231 и 231 — 1, или
ду —2147483648 и 2147483647. Эти границы, как правило, слишком узка
сложных вычислений, в процессе которых появляются очень большие п
жительные и отрицательные числа. Назначение этого раздела — избавитьс °
этого ограничения путем построения нового целочисленного типа данных °Т
пользуя класс сверхдлинных целых (Verylong Integer).
Для того чтобы можно было записывать целые произвольной длины
должны иметь способ хранения в памяти цифр целого, не накладывают -
ограничения на представление целых чисел. Один из возможных путей — й
пользование строки цифр для представления очень длинных целых чисел. В от
личие от встроенного типа целых, которые должны помещаться в 4 байта rja
мяти, мы в строковом представлении обходим это ограничение. В принципе
для строкового представления нет ограничения на длину строки (кроме размер
ра доступной памяти). Как следствие, представлены могут быть целые числа
произвольной длины.
Строка, в которой хранится число из класса Verylong, состоит лишь из
символов цифр ' 0',...,' 9'. Каждая цифра в строке представляет
десятичную цифру числа. Например, строка 23" представляет значение 123 (в
десятичной системе счисления). Арифметические операции над числами в этом
представлении могут быть выполнены с помощью обычных манипуляций с
цифрами числа. Вообще говоря, мы могли бы использовать представление из
цифр другой системы счисления (например, двоичной), и достигли бы
большей простоты в алгоритмах реализации арифметических операций. Но нам
важнее простота в достижении других целей, и поэтому мы выбираем
десятичное представление.
При определении абстрактного типа данных (АТД) определяется не
только представление данных (например, в случае класса Verylong — это строка
символов цифр), но также и операции, которые могут совершаться над
данными этого типа. При этом детали реализации представления и операций должны
быть скрыты. Пользователю необходимо знать лишь поведение объектов АТД
и внешний интерфейс. Вообще нужно стремиться к полноте функционально-
:ти класса при минимальности его интерфейса. Будем придерживаться этого
принципа в нашем строительстве класса Verylong. Перечислим основные
требования к объектам класса Verylong:
• Мы должны достаточно легко создавать новые экземпляры объектов класса
Verylong.
• Мы должны уметь совершать над элементами класса Verylong
арифметические операции, такие, как+, -, *, /, "/., ++, —.
• Мы должны уметь присваивать значения чисел из Verylong переменным
типа Verylong, используя оператор =.
• Должны поддерживаться модификации оператора присваивания, такие,
как +=,-=,*=,/=, '/,-.
• Должны быть доступны операторы сравнения >, >=, <, <=, ==, .'=.
• Мы должны уметь, когда это нужно, преобразовывать числа из Verylong Б
формат данных других стандартных типов, например int, double, char-
6.1. Класс сверхдлинных целых
195
"^^ должны быть включены некоторые общеупотребительные функции,
t Так* ^ абсолютная величина abs(), возведение в целую степень pow(),
ТаКЙ «сленный квадратный корень sqrt() и деление с двойной точностью
дело411
<ilV пжны иметь возможность ввода/вывода чисел из Verylong.
ф jvlbi дол-»-
„ 1 2 Поля данных
Veryl°ng имеет три поля данных: vlstr, vlen, vlsign. Следуя прин-
скрытия информации, поля описаны как private, что делает их недо-
ными вне класса. Доступ к ним или работа с ними возможны лишь через
книи доступа, предусмотренные в классе и имеющиеся в классе операторы.
Кратко опишем каждое поле:
Переменная vlstr хранит строку символов цифр, которая представляет
число из Verylong. Цифры в строке, в принципе, можно хранить как в
прямом порядке, так и в обратном. Мы выбираем обратный порядок, имея в
виду облегчение выполнения некоторых манипуляций с числами из Verylong.
• Переменная vlen хранит длину числа без учета знакового бита.
• Переменная vlsign хранит 0 для положительного числа из Verylong и 1 —
для отрицательного.
6.1.3. Конструкторы
В этом разделе мы покажем, как создаются числа из Verylong. Как и для всех
типов данных, наиболее простой путь создания — оператор описания.
Например,
Verylong x;
Это описание создает переменную с именем х. Этот простой оператор в
действительности обращается к конструктору, предопределенному в классе. С
помощью конструктора число из Verylong по умолчанию инициализируется
знанием нуль. При необходимости его переменную можно проинициализировать
енулевым значением. C++ допускает перегрузку конструкторов с многократ-
Ми 0пРеделениями. Разные конструкторы могут различаться (друг от друга)
ными списками аргументов в операторах описания. В следующем приме-
оператор описания создает новую переменую у, которая инициализируется
значением 3:
Verylong у(з);
д ' что данный способ порождает следующую проблему. Что произой-
' Сли пользователь захочет инициализировать значение, которое превыша-
обр СИмальное значение для встроенного целого типа? Одно из ее решений —
а»г Чить конструктору возможность чтения строки символов цифр как его
а ент. При такой реализации можно объявить переменную типа Verylong
инициализировать ее значением любой возможной величины. Например,
96
Глава 6. Классы компьютерной алгебры
Verylong u(23") Инициализирует переменную и
значением 123.
Verylong v(234567890123") Инициализирует переменную v
значением, которое превышает
границу встроенного целого типа.
Verylong w("-567890") Инициализирует переменную w
отрицательным значинием.
Ниже дается краткое описание конструкторов, доступных в классе:
• Verylong(const Char*) берет строку символов цифр как аргумент. Пров
ряется и назначается знак целого, выделяется память и в нее загружается
непосредственно строка. Если аргумент отсутствует, число
инициализируется нулем.
• Verylong (int) берет целое как аргумент, преобразует его в строку символов
цифр и загружает непосредственно ее.
• Конструктор копирования является критичным для класса. Хотя
компилятор C++ автоматически генерирует его, если он опущен, сгенерированный
конструктор копирования может быть некорректным при использовании
динамического распределения памяти, так как в этом случае он просто
копировал бы указатели. Этого мы хотели бы избежать. Поэтому лучше
определить соответствующий конструктор копирования, чтобы гарантировать
корректность копирования во всех ситуациях.
• Деструктор просто освобождает память, которая больше не используется.
.1.4. Операторы
!огда мы перемножаем две матрицы А*В, мы используем метод, совершенно
гличающийся от умножения двух чисел с плавающей запятой. В C++ ариф-
етические операторы могут быть перегружены. Единственное требование—
эвое определение не должно быть неоднозначным. Это означает, что опреде-
эние не должно требовать аргументов, которые соответствуют другому суще-
гвующему определению.
В случае класса Verylong, следующие операторы перегружаемы:
++, —. -(унарный), +, -, *, /, У., =,
+=, -=, *=, /=, '/.= , ==, <=, <, «, », >= •
пишем функции и алгоритмы, используемые для каждого оператора:
i Оператор присваивания = используется, чтобы присвоить значение числа
типа Verylong другому числу. Реализация подобна конструктору копир0'
вания, но их функции различны.
| Как и для любого другого встроенного типа данных, операторы инкремейта
(++) и декремента (—) перегружаются в классе. Однако они могут
использоваться в двух разных формах — префиксной и постфиксной. Перегруя^3*1
эти операторы, нам нужно знать различия между ними. Ниже показань1
обе формы оператора инкремента. Оператор без параметра —для префи^0'
6.1. Класс сверхдлинных целых
197
- Лормы инкремента и оператор с параметром int — для постфиксной
формы инкремента:
class Verylong
{
Verylong operator ++ О; // префикс: ++Verylong
Verylong operator ++ (int); // постфикс: Verylong++
}
При осуществлении этих двух функций необходимо помнить, что:
/1) В префиксной записи величина изменяется и затем используется.
12) В постфиксной записи величина используется и затем изменяется.
Это объясняет структуру таких функций:
// Префиксная форма оператора инкремента
Verylong Verylong::operator ++ ()
{
return *this = *this + one;
}
// Постфиксная форма оператора инкремента
Verylong Verylong::operator ++ (int)
{
Verylong result(*this);
♦this = *this + one;
return result;
}
Оператор сложения + складывает два числа типа Verylong.
С помощью оператора " исключающего ИЛИ (XOR) определяются
знаки каждого операнда, которые в свою очередь определяют, как оператор
сложения должен быть выполнен.
Вычисление u+v, где u, v — представители класса Verylong,
осуществляется следующим образом:
Шаг 1. Если и и v имеют различные знаки, то
— если и>0, то возвращается u- I v I, иначе возвращается v- I u |,
— отметим, что в обоих случаях результат получен
с использованием операции вычитания,
иначе, получая цифру за цифрой от каждого операнда,
складываем их, используя обычное арифметическое сложение.
Шаг 2. Определяется знак, и возвращается результат операции.
Ри выполнении оператора вычитания - вычитается одно число типа
ryiong из другого. Так же как в сложении, определяются знаки каждо-
°перанда. Вычисление u-v, где u, v — представители класса Verylong,
0сУ1Цествляется следующим образом:
98
Глава 6. Классы компьютерной алгебры
Шаг 1. Если и и v имеют различные знаки, то
— если и>0, то возвращается u+1 v I,
иначе возвращается - (v+1 u I ),
— отметим, что в обоих случаях результат получен
с использованием операции сложения,
иначе, получая цифру за цифрой от каждого операнда,
вычитаем одну из другой, используя обычное
арифметическое вычитание.
Шаг 2. Определяется знак, и возвращается результат операции.
Отметим, что операторы сложения и вычитания вызывают друг друга в
процессе вычисления результата.
• Предположим, что мы вычисляем u*v, где u, v — представители класса
Verylong. Умножение производится применением обычного метода:
Каждую цифру v мы умножаемся на и с помощью внутренней функции
multdigit(). Суммируя эти произведения, получаем произведение и и v.
• Для оператора целого деления I используется алгоритм обычного деления
столбиком. Сначала мы удостоверимся, что делитель v ненулевой. Если u<v
в качестве результата выдается нулевое значение. Остальная часть
оператора включает нахождение частного цифра за цифрой. В конце остается
назначить правильный знак результату и выдать результат.
• Оператор взятия по модулю '/, задается формулой u-v* (u/v).
• В классе определены 6 операторов сравнения. В качестве результата они
выдают булевские значения — 1 (истина) и 0 (ложь). Для == мы
сравниваем знаки и содержимое (как строк—прим. перев.) этих двух чисел. Если
оба они совпадают, то эти два числа полагаем равными и результатом
операции будет истина, в противном случае —ложь. Оператор != может быть
вычислен, как ! (u==v).
Чтобы проверить, является ли u<v истиной, произведем следующее:
— Сравним знаки из чисел и и v; положительное число всегда больше, чем
отрицательное число.
— Если знаки чисел одинаковы, сравним длины каждого числа (как
строк — прим. перев.).
— В зависимости от их знака, большее число имеет большую (знак
положительный) или меньшую (знак отрицательный) длину.
— Если оба числа имеют одинаковую длину, инвертируем строки цифр
(вспомним о способе хранения чисел класса Verylong) и сравним их,
используя метод strcmpO.
— Возвратим полученное булевское значение— 1 или 0 (True/False).
Остальные операторы сравнения конструируются с использованием опера'
тора <:
— <= эквивалентна (u<v или u==v ),
— > эквивалентна (! (u<v) и u!=v ),
— >= эквивалентна (u>v или u==v ).
6.1. Класс сверхдлинных целых
199
0ператоры преобразования типов
"' ' C++ число с плавающей точкой могло быть присвоено целому числу
3 Язь1К т При этом преобразование типов происходит неявно. Для класса
й п&° а желательно иметь такие же свойства. Имеются два вида преобразо-
Verylong
вайЯя:
образование чисел Verylong во встроенные типы данных. Этот вид пре-
1- й азования соответствует операторам преобразования, предусмотренным
0 р , +_ в классе используются три оператора преобразования:
Оператор int() преобразует число Verylong во встроенный
целочисленный тип, если оно находится в границах диапазона, иначе выдается
сообщение об ошибке.
• Оператор double() преобразует число Verylong в число с плавающей
точкой двойной точности, когда это применимо.
• Оператор char* О преобразует Verylong число в указатель на
символьный тип. Это преобразование полезно при использовании подпрограмм
из библиотеки <string.h> (см. гл. 11).
2. Преобразование данных встроенных типов в числа Verylong. Этот вид
преобразования выполняется конструкторами класса. Конструкторы читают
тип данных параметров и осуществляют соответствующее преобразование,
которое преобразует встроенный тип данных к числу Verylong.
6.1.6. Закрытые методы
Некоторые операции над полями данных не должны быть видны
пользователю. Методы, реализующие их, как и сами поля данных, объявляются как
private. Объекты класса Verylong имеют 3 закрытых метода. Их содержание
описывается следующим образом:
• multdigit(int шип) умножает Verylong число на пит, где пит — целое
число, лежащее между 0 и 9. Этот метод вызывается при умножении двух
Verylong чисел.
• niultlO(int num) умножает Verylong число на 10 в степени num. Например
v-multl0E) эквивалентно v*100000.
strrev(char *s) полностью меняет порядок символов строки s на
обратный и возвращает это в качестве результата.
' '• Другие функции
йи„ Д°лжен иметь набор доступных методов (функций), входящих во
внешен - ТеРфейс. Класс Verylong не мог бы рассматриваться как законченный,
1 отсутствовали следующие функции:
* аЬ с
* IT nst Verylong &) возвращает абсолютное значение Verylong числа.
°численная функция квадратного корня sqrt(const Verylong к).
200 Глава 6. Классы компьютерной алгебры
Для данного положительного целого числа Ъ, целочио
и<~ленй1«.
ныи корень а однозначно определяется соотношениями и ^8а
a2<b<(a+lJ. Ь
Например, целочисленный квадратный корень из 105 есть in
105 <П2. Кка*1(Ь
Имеется много способов поиска целочисленного квадрат
ложительного целого числа. Метод, который мы используем ° К°РНя Иг
тождестве (а + ЬJ = а2 + 2ab + b2. Алгоритм следующий: ' °Сн°вая ^
Шаг 1. Начиная от младшей цифры числа разобьем его н
по 2 цифры в каждом. Число сегментов равно количе СеГМевть!
целочисленного квадратного корня. ВУ Цифр
Шаг 2. Получим первую цифру, вычислив целочисленный квал
корень из первого сегмента. Запишем результат в a TabIii
Шаг 3. Вычтем а2 из первого сегмента и получим остаток.
Шаг 4. Делим остаточный член на 2а, чтобы получить вторую пил
корня Ь. Запишем результат. р-
Шаг 5. Вычтем 2аЬ и Ь2 из остатка в соответствующей позиции.
Шаг 6. Если все требуемые цифры получены, возвращаем конечный
результат.
Шаг 7. Переходим на шаг 4 для нахождения следующих оставшихся
цифр целочисленного квадратного корня.
Для иллюстрации рассмотрим следующий пример: sqrt C94384) = ?
а -» б Перваяцифра
34-=-2*б-»2 Втораяцифра
2а*6=2*6*2 = 24
б2 = 4
998 -г- 2 * 62 -» 8 Третьядифра
2 об * с = 2 * 62 * 8 = 992
3 9
3 6
3
2
4
4
4
9
9
3
3
4
9
9
8
8
2
6
4
4
4
Поэтому, sqrt C94384) = 628.
• pow (const Verylongfe, const Verylongfe)
Предположим, что мы хотим вычислить х29. Мы могли прос ьто>
схи умножать на х двадцать восемь раз. Однако возможно ntw gr0 $
же самый ответ, проведя лишь семь умножений: берем х, возвод ^>( f
квадрат, умножаем на х, возводим в квадрат, умножаем на х, в ^0^
квадрат, снова возводим в квадрат, умножаем на х. Символьно
изобразить последовательностью
х -> х2 -)■ х3 -»■ х6 -»■ х7 -»■ хи -»■ ж28 -)■ ж29-
6 1. Класс сверхдлинных целых
201
ость могла быть получена из двоичного представления
0слеДо15аТеЛЬ„оГо в обратном порядке: 11101. Заменим каждую «1»
Эта I 29, запИСаях каждый «0» -S; получаем SX SX SX S SX и удалим
чй « сймВ°ЛОВ пение SX , получаем последовательность SXSXSSX, ко-
"^дирУ1016 ВХ°рядок операций: «S» интерпретируется как возведение в
ЛЙрая зада^ п^Ркак умНОЖение на х. Этот метод легко может быть запро-
ваДРаТ И Лля удобства считаем, что число уже записано справа налево
г0аммйР015аН-^т
от старших разрядов к младшим).
(T-pSS^H ^ложительного N:
Установим N <-n,Y <r-l,Z 4-х
^ Если N нечетно, Y <- Y*Z, N <- [iV/2], если N = 0, возвращаем
ШаГ 2" ответ Г, иначе TV <- [ВД
„ 7 <- Z * 2 и идем на шаг 2
Шаг 3. п ^
Г 1 обозначает целую часть ж. Она определяется как наибольшее целое
ие меньшее чем х. Рассмотрим этот алгоритм на примере вычисле-
НИЯ129:
После шага 1.
После шага 3.
После шага 3.
После шага 3.
После шага 3.
После шага 3.
N
29
14
7
3
1
0
Y
1
X
X
X6
я13
X29
Z
X
X2
X*
X8
ж16
—
• div(const Verylongfe, const Verylongfe)
Обычный оператор деления / осуществляет целочисленное деление. Во
многих случаях, однако, мы нуждаемся в вещественном значении частного
деления Verylong чисел. Функция div(u.v) используется, чтобы осуще-
ь деление с плавающей запятой, и возвращает результат типа double.
__ деление функции происходит следующим образом:
^ начала мы убеждаемся, что знаменатель не равен 0.
~ ЗатеМ МЫ Находим поРяДок частного.
лрн Ь1 нахоДим мантиссу частного цифру за цифрой обычным де-
__ ением столбиком.
знячо ак°нчено, если не имеется никакого остатка, или получено 16
начащих цифр.
-1*- т
On,
еРаТ01
охоки
РЬ1 » ,
Мь pbI Работают СПОльзУются для ввода и вывода, соответственно. Эти опе-
Нен ер6Гру>Каем всеми встроенными типами данных. Для класса Verylong
5КаеЛ Иэ Входног ратоРы ввода/вывода. В следующем примере читается зна-
Я' тока в переменную х типа Verylong и затем оно отобра-
202
Глава 6. Классы компьютерной алгебры
#include <iostream.h>
«include "\verb|Verylongl.h"
void main()
i
Verylong x;
cin >> x;
cout << "The value entered is " « x << endl;
}
Реализация этих функций достаточно тривиальна:
• istreamfe operator » (istreamfe, Verylongfe) Оператор читает последо-
вательно символы цифр из потока (например, с клавиатуры), принимая
максимально до 1000 символов.
• ostreamfe operator << (ostreamfe, const Verylongfe) Оператор
отображает значение Verylong числа на дисплее.
6.2. Класс рациональных чисел
6.2.1. Введение
В математике числовая система строится путем расширения классов чисел.
В этом разделе мы построим класс чисел Rational, который является
естественным расширением целых чисел Z. Математика рациональных чисел была
описана в разд. 2.3.
Здесь мы сталкиваемся снова с проблемой, упомянутой в предыдущем
разделе, а именно встроенные типы данных в C++ ограничены в диапазоне.
Чтобы решить эту проблему, для представления числителя и знаменателя
Rational числа вместо int используется Verylong. Однако в расширении мы
идем дальше; для построения класса мы используем возможности шаблона
класса, имеющегося в C++. Это позволяет пользователям выбирать тип
данных, который удовлетворит их потребности.
Для начала мы должны определить поведение чисел АТД Rational:
• Это — шаблонный класс, для которого тип числителя и знаменателя должен
быть определен пользователями.
• Создание нового экземпляра Rational числа должно быть просто.
• Rational числа можно приводить к более простой форме, используя
алгоритм наибольшего общего делителя.
• Доступны такие арифметические операторы, как +, -, *, /.
• Доступны операторы присваивания и присваивания с изменением =, +=)
-=, *=, /=•
• Доступны операторы сравнения >, >=, <, <=, ==, !=.
• Поддерживается приведение к типу double.
• Доступны функции, которые возвращают числитель, знаменатель и др°"'
ную часть Rational чисел.
• Поддерживаются операторы ввода и вывода Rational чисел.
9.4. Система Лисп, основанная на C++
403
return os;
>s«»nil»;
return os;
>
plate <class T> Type<T>::Type() : thing(T@)) О
opiate <class T> Type<T>::Type(T t) : thing(t) О
late <class T> Type<T>::Type(const Type<T> &t)
{ thing = t.value О; }
template <class T>
Tvpe<T> & Type<T>::operator = (const Type<T> &t)
{
if(this != &t) thing = t.value();
return *this;
}
template <class T> const T & Type<T>:lvalue() const
{ return thing; }
template <class T> int Type<T>::atom() const
{ return 1; }
template <class T> Element * Type<T>::clone() const
{
Type<T> *t = new Type<T>(*this);
return t;
}
template <class T> void Type<T>::print(ostream &os) const
{ °s « thing; }
template <class T>
ostreamfe operator << (ostreamfe os, const Type<T> *t)
t~>print(os);
return os;
lr * cons(const Element *el, const Element *e2)
Pair *p = new Pair;
!>->car(el);
6.2. Класс рациональных чисел
203
„ о Шаблон класса
нальные числа с числителем и знаменателем типа int не могут предста-
^ все множество рациональных чисел из-за ограничений на данные типа
вЛ Что возможно только, если числитель и знаменатель можно представлять
*П ми во всем диапазоне целых чисел. Класс Verylong, сконструированный в
ц .тдущем разделе, в принципе позволяет представлять целые числа произ-
П15 «ой длины. Поэтому необходимо так или иначе' включить Verylong числа
„Rational класс.
Шаблон в C++ обеспечивает параметризованные, типы. В соответствии с
й особенностью один и тот же код может использоваться в отношении раз-
«ных типов, где тип — параметр тела кода. Шаблоны классов дают простой
пособ повторно использовать код, что позволяет транслятору
автоматизировать процесс реализации типов.
Мы реализовали Rational числа как шаблонный класс. Причина в том,
чтобы элементы данных, числитель и знаменатель, могли бы иметь тип int или
Verylong no желанию пользователя. Имея эту особенность, можно спокойно
осуществлять любые вычисления, не боясь того, что числа могут выйти за
границы диапазона.
6.2.3. Поля данных
В классе Rational объявляет два поля данных типа Т. Это означает, что
пользователь должен определить фактический тип данных, предоставляемый дли
использования в классе. Первое поле р содержит числитель, второе1 поле1 q
знаменатель Rational числа. Заметьте, что в соответствии с концепцией
скрытия данных оба поля объявлены как private (закрытые). Поэтому они
доступны только в пределах класса.
6.2.4. Конструкторы
Объявить Rational число можно следующими способами:
// Объявление Rational числа и int-типа,
// инициализированного значением 0
Rational <int> u;
// Объявление v, инициализированного значением 5
&ational<int> vE);
объявление w, инициализированного значением 2/3
Rational<int> wB,3);
объявление х Verylong-типа, инициализированного значением
I' 12?7б2/2384623
ational<Verylong> xA27762,2384623);
оъявление у, инициализированного значением
^ а ^еделами int-диапазона
1onal<Verylong> y(2872134727762",48972938479822384623");
204
Глава 6. Классы компьютерной алгебры
В классе имеются три конструктора, которые дают различные способы
струирования Rational числа: ^
1. Конструктор по умолчанию объявляет переменную типа Rational и и
циализирует ее нулем.
2. Rational (const T N) объявляет переменную типа Rational и иниций
зирует ее значением N.
3. Rational (const T N, const T D) объявляет переменную типа Ration
и инициализирует ее значением N/D.
Можно было бы объединить эти три конструктора в один (к примеру, Пе~
вый конструктор выглядел бы как Rational (const T = 0, const T = п\
Однако компиляторы, которые мы используем, не принимают эту
конструкцию, потому что это —шаблон класса. Чтобы не создавать себе проблем, мы
включаем в описание класса все три конструктора.
В процессе конструирования вызывается внутренний метод gcd() для
приведения Rational числа в его стандартную форму. Конструктор копирования и
деструктор, предоставляемые компилятором, работают хорошо в нашем
классе, но для полноты картины мы включили их в класс.
6.2.5. Операторы
Так как класс Rational —математический объект, общие операторы,
используемые в классе — арифметические операции. Мы включили много операторов
в класс, а именно
-(унарный), +, -, *, /, =, +=, -=, *=, /=, ==, !=, >, >=, <, <=.
Определения некоторых из этих операторов следующие:
-
a
Ъ
a
Ь
a
с
+ d
с
—а
Т"'
а * d + Ь
b*d
а* с
b* d'
* с
а с a* d — b
* с
b d b*d
a c a* d
b d b*с'
-==-=>• (о == с) и (b==d), -г < - =>• (a*d) < F* с).
b d b d
Реализация этих операторов достаточно проста и мы не будем останавливаться
на ней.
6.2.6. Операторы преобразования типов
Оператор преобразования типов позволяет при необходимости перевести ти11
данных в другой. Представление Rational числа в виде числа с плавают6"
запятой всегда полезно. Мы включили оператор преобразования в тип данны*
double. Операторы преобразования в классе существует в двух видах. ОпеРа'
торы первого вида позволяют привести данные любых типов в тип double
г
6.2. Класс рациональных чисел 205
аторы ВТорого вида переводят данные лишь Rational<Verylong> —
тип double. Можно спросить, для чего нам нужен дополнительный опе-
1*1а преобР^013^1 для Ratioiial<VerylonS>? Как мы увидим в следующем
раТОР имеется лучший и более точный метод для Rat ionaKVerylong>
дримере,
^Пбщий метод, который работает для любого типа данных, просто возвра-
приведенное к double значение Rational числа:
return double(p)/double(q);
о-от метод работает достаточно хорошо для Rational<Verylong> чисел в об-
м случае, но он подводит иногда из-за ограничений точности в double. Ha-
мним что имеется функция, называемая div(), которая производит деление
двойной точностью Verylong чисел. С этой функцией мы способны ту же
работу делать лучше:
return div(p.q);
В целях иллюстрации рассмотрим следующий пример:
// division.cpp
#include <iostream.h>
#include "\verbIVerylongI.h"
void main()
{
Verylong P("999"), Q("lll"), D(05");
P = pow(P,D); // 999~105 = 9.00277e+314 (превьпдена граница
// диапазона типа double)
Q = pow(Q,D); // 111-105 = 5.74001e+214
cout << div(P.Q) << endl;
cout « div(Q,P) « endl;
cout « double(P)/double(Q) « endl;
cout « double(Q)/double(P) « endl;
}
Оператор double (P) /double (Q) не работает, когда Р или Q превышают
границы диапазона типа данных double, которые составляют приблизительно
1.7977 х 10308, как сказано выше. Слово NaN есть сокращение для «не число»,
этого примера мы заключаем, что определение оператора преобразования
^Рого вида для Rational<Verylong> необходимо.
"*•'• Закрытые методы
■^РЫтые методы — это внутренние функции, которые известны только вну-
класса. Они недоступны вне класса. Имеется лишь один метод в нашем
се- Функция gcd(T a,T b) возвращает наибольший общий делитель а и Ь
пользованием следующего алгоритма:
// 1.56842е+100 -
// 6.37583е-101 -
// NaN
// NaN
OK
OK
206
Глава 6. Классы компьютерной алгебры
Шаг 1. До тех пор, пока b > 0, делается следующее:
*т «— a mod b
*a «— 6 и 6 «— т.
Шаг 2. В качестве результата выдается а.
Например, gcdD,8) возвращает 4.
6.2.8. Другие методы
В классе определены только три метода, отличные от арифметических
операторов: num(), den() и fracQ. Они возвращают числитель, знаменатель и
дробную часть Rational числа, соответственно.
template <с1азз Т> Т Rational<T>::num() const i ... }
template <class T> T RationaKTv. :den() const { ... }
template <class T> Rational<T> Rational<T>::frac() const { ... }
Отметим, что они объявлены как функции-константы. Это указывает, что их
значения не могут быть изменены. Любое значение в C++ может быть
объявлено как константа. Константная переменная привязана к значению, которое
никогда не может быть изменено. Поэтому только константные операции могут
быть выполнены на значении. Приложение этих методов дается в следующей
программе:
// testfrac.cpp
#include <iostream.h>
#<include "Rational.h"
int mainО
■С
Rational<int> rG,3);
int n = r.num():
int d = r.den();
Rational<int> f;
f = r.fracQ;
cout « "n = " « n « endl; // => 7
cout « "d = " « d « endl; // => 3
cout « "f = " « f « endl; // => 1/3
return 0;
6.2.9. Потоки
В этом разделе мы описываем, как операторы » и << перегружены, чтобы
осуществить ввод и вывод для Rational чисел:
• Оператор ввода потока » осуществляется таким образом, чтобы моя<н()
было вводить дробь подобно а/b с клавиатуры, и класс мог бы распозна11"
Г"
6.3. Класс комплексных чисел
207
ргть числитель и Ь —знаменатель. С другой стороны, если введена
что а с ,
ообь, класс должен распознать, что это — целое число (то есть рацио-
Й ьное число со знаменателем, равным единице).
Н Чтобы удовлетворить эти потребности, используются некоторые функ-
из библиотеки <iostream.h>. Процедура ws очищает все незаполненное
остранство после ввода. Функция peek () используется для предваритель-
го просмотра следующего символа, и функция get () читает текущий сим-
из входного потока. Никаких предосторожностей против ошибок ввода
принято. Пользователи несут полную ответственность за корректность
использования функции.
Выполнение оператора вывода потока « более просто. Если знаменатель
Rational числа равен единице, то выводится только числитель. Иначе
выводится значение дроби.
6.3. Класс комплексных чисел
в.3.1. Введение
В библиотеке C++ доступен заголовочный файл <complex. h>. Однако тип
данных вещественной и мнимой части этих чисел — double. Этого не достаточно
для прикладных программ, которые требуют точных манипуляций с
комплексными числами. Это ограничение потребовало создать другой класс
комплексных чисел с использованием шаблона. Этот класс позволяет нам
использовать класс Rational чисел, разработанный в предыдущем разделе, в качестве
вещественной и мнимой частей комплексного числа. Таким образом, точное
оперирование с комплексными числами становится возможным. Мы опишем
представление и дадим реализацию для класса Complex чисел. Пример в конце
кгого раздела демонстрирует, как этот класс может быть использован.
Так как Complex АТД — числовой тип, его объекты должны иметь все
свойства, которыми обладают числа. Поведение чисел АТД Complex следующее:
• Это — шаблонный класс, для которого тип вещественной и мнимой частей
должен быть определен пользователем.
• Имеются три простых способа создать Complex число.
• Доступны арифметические операторы типа +, -, *, /.
• Доступны операторы присваивания и присваивания с изменением =, +=,
-=, *=, /=.
• Доступны шесть операторов сравнения >, >=, <, <=, ==, !=.
Реализованы следующие шесть методов:
1. realPartQ возвращает вещественную часть Complex числа.
2. imagPart () возвращает мнимую часть Complex числа.
о. magnitude() возвращает модуль Complex числа.
4. argument () возвращает угловой параметр Complex числа в полярных
координатах.
«• conjugateQ возвращает число, сопряженное с Complex числом.
Ь. negate () возвращает инверсию Complex числа.
Под.
ерживаются операции ввода и вывода с Complex числами.
208 Глава 6. Классы компьютерной алгебры
6.3.2. Шаблон класса
Как и в классе Rational чисел, пользователи могут захотет>
контроль над компонентами Complex числа. Это может бштл N"Tb %
тем реализации класса как шаблонного. Вот некоторые вот СТи'нУг( '^
задания класса Complex чисел: "Ь1(- ец(л,г":'-
• Complex<int>
z = х + iy х,у имеют тип данных int
• Complex<double>
z — х + iy x,y имеют тип данных double
• Complex<Rational<int> >
z = a/b + ic/d a, b, c, d имеют тип данных int
• Complex<Rational<Verylong> >
z = a/b + ic/d a, b, c, d имеют тип данных Verylong
Заметим, что пробел между двумя символами >, которыми оканчиваю™
на шаблонов, требуется всегда. Если он опущен, транслятор по этому п ''*"
будет выдавать сообщение об ошибке.
6.3.3. Поля данных
Так как имеются два различных способа представления комплексного числа.
мы должны выбрать одно из них для внутреннего представления полей
данных. Мы будем использовать декартову форму комплексного числа как более
простую. Однако выбор всегда остается за читателем. Независимо от того,
какое представление было выбрано, пользователи не должны задумываться >■
нем. Комплексные числа в декартовой форме имеют два компонента:
вещественную и мнимую части. В классе они представлены переменными real и
imag.
6.3.4. Конструкторы
Наиболее важные функции класса — конструкторы. Мы предоставляем
различных способа создания Complex чисел:
1. Заданный по умолчанию конструктор объявляет Complex перемет.
специфицирования его начального значения. Оно устанавливается .
автоматически. „,к
2. Конструктор, который содержит один параметр, объявляет Comp ^
с мнимой частью, равной нулю, и вещественной частью, инициал"-
ной значением, специфицированным в параметре. 4„r.ii1
3. Конструктор, который содержит два параметра, объявляет Comp ц11ро-
с вещественной и мнимой частями, инициализированным" спе11
ванными значениями параметров. .,.
Следующие примеры поясняют, как Complex число может бып> '
личными способами:
o-uano P*
6.3. Класс
комплексных чисел
„lex числа и int-типа;
Compl^ числа v d<
яе8*е С°°„яЯ0 значением О
союр
l^ числа v double типа;
СоШр1еХ
, v ц»»"~ .1.) v;
:"SP инициализировать его значением 2
и инициализировать его значением 2 + 3i
,/ объявить х
nier<int> хB,^Ь
_«. я инициализировать у
'/ ^^Ll^ational^nt:-
Rati onaKint ->-типа
У/ С K°Xtion^l<int> > у (Rational*: int> A,2), Rational<int> C ,4)) ;
Объявить и инициализировать z
Сошр
// Гкмпонентами RationaKVerylong>-THna
-omplex<Rational<Verylong> > z(Rational<Verylong> (2345" , 45123") ,
RationaKVerylong>("987659876543", "82567834536572693487");
Как и в случае класса Rational, можно было бы попытаться заменить одним
конструктором с заданным по умолчанию параметром
Complex(const T = 0, const T = 0)
игр три конструктора, упомянутые выше. Однако из соображений общности
мы сохраняем их по отдельности.
Конструктор копирования и деструктор тривиальны в этом классе. Однако
а™ законченности мы включили их в описание класса.
63-5. Операторы
классе О с-дим Реализацию арифметических операторов, имеющихся в
раторы, которые были перегружены, включают
"(Унарный), + _ * , '
'Пределен ' '' =' +=' ~=' *=' <г <=' >' >=' =='
6НИЯ ДЛЯ этих операторов следующие:
(а + ib) + (с + и1) = (а + с-) + г-(Ь + dj
(а + ib) - (f; + ld) = (а _ r;) + ф _ d)
(« + ib) х (с + id) = (ас - bd) + i(ad + be)
ас + db . (be — ad
+ г
c2+d2 \c2+cP
({a + ib)<c(c + ld)^(a2+b2)<(c2+d2)
a + гЬ) == (Г + uL) ^ (h =_ c) и (/) _= (/)
210
Глава 6. Классы компьютерной алгебры
Реализация этих операторов непосредственно вытекает из определения
татели не должны иметь никаких трудностей в их понимании. ' \~
6.3.6. Операторы преобразования типов
Мы имеем
ZCQCRCC,
т.е. множество целых чисел есть подмножество рациональных чисел, кото
являются подмножеством вещественных чисел, являющихся подмножеств '?
комплексных чисел. Таким образом, набор комплексных чисел — надмНол
ство всех других множеств, включая множества, задаваемые встроенными т
пами данных. Поэтому мы не определяем никаких функций преобразованц
типов к любому встроенному типу данных, потому что при таком прообраз
вании комплексные числа теряли бы часть информации.
6.3.7. Другие функции
Далее мы опишем функции, в которых реализованы некоторые свойства
Complex чисел a + ib, необходимые для нас:
• Вещественная и мнимая части комплексного числа.
• Модуль комплексного числа, который определяется как л/a2 + Ь2.
• Аргумент числа (в полярных координатах), который определяется как
arctgF/a). Формулы в этом и предыдущем пункте задают правила
перевода комплексного числа из декартовой формы в полярные координаты.
• Комплексно сопряженное с а + ib, которое равно а — ib.
• Инверсия комплексного числа а + ib, которое равно —а — ib.
Они легко могут быть получены с помощью шести методов, определенных
в классе:
realPartO, imagPartO, magnitudeO, argumentO, conjugateO, negateO-
Следующая программа демонстрирует использование этих методой.
// scomplex.cpp
#include <iostream.h>
«include "MComplex.h"
int main()
i
Complex<int> cC,4); // описывается комплексное число 3+4i
cout << c.realPartO << endl; // 3
cout << c.imagPartQ « endl; // 4
cout << c.magnitudeO « endl; // 5
cout « с argumentO << endl; // 0.927295
6.4. Класс кватернионов
211
ut « c.conjugateO « endl; // C,-4i)
C°ut « c.negateO « endl; // (-3,-4i)
return 0;
Я завершенности класса Complex нам еще необходимо включить все спе-
"_Lmmp математические функции, которые могут быть применены к ком-
яяаЛЬ1и>
длексным числам, например
sin(). cos(), exp О, coshO, sinh (), sqrtQ, log().
P ализацию этих функций оставляем в качестве упражнений для читателей.
g,4. Класс кватернионов
6.4.1. Введение
Кватернионы, описанные в разд. 2.7, — математическая структура самого
верхнего уровня по отношению к основным числовым структурам, таким, как
рациональные и комплексные числа. Поэтому кватернионы базируются на
основных структурах, введенных нами ранее. Описание АТД Quaternion может
быть суммировано следующим образом:
• Это — шаблонный класс, для которого тип коэффициентов при компонентах
чисел должен быть определен.
• Создание экземпляров чисел Quaternion просто.
• Арифметические операции для Quaternion перегружаемы. Например,
-(унарный), +, -, *, /.
• Доступен оператор присваивания =.
• Доступны операторы, такие, как нахождение модуля, сопряжения и
инверсии Quaternion чисел.
• Поддерживаются операторы потокового ввода и вывода.
••4.2. Шаблон класса
оэффициенты Quaternion могут иметь тип int, double или Verylong и т.д.
Добные классы лучше реализовывать в форме шаблона. В этом случае мы
„гаем Дублирования кода для различных типов данных коэффициентов.
Же мы перечислим некоторые возможные способы объявления чисел класса
^«ernion:
• Quaternion<
int>, Quaternion<double> объявляют Quaternion число с ко-
Ффициентами встроенного типа int и double, соответственно.
&ternion<Verylong> объявляет Quaternion число с коэффициентами
• п°ЛЬзовательского типа Verylong.
att*"nion<Rational<int> >, Quaternion<Rational<Verylong> > объяв-
I°T Quaternion число с коэффициентами Rational-типа.
212
Глава 6. Классы компьютерной алгебры
• Quaternion<Complex<int> >, Quaternion<Complex<Verylong> >,
Quaternion<Complex<Rational<int> > >,
Quaternion<Complex<Rational<Verylong> > > — различные способы
явить Quaternion числа с коэффициентами типа Complex. ^
Обратите внимание на успешное соединение четырех пользовательских m
для формирования нового типа данных. в
6.4.3. Поля данных
Чтобы определить кватернион однозначно, мы должны задать коэффициент
при четырех компонентах. Это в точности содержимое полей данных класг
Quaternion. Поля г, i, j, k представляют коэффициенты при 1,7, J,/^ c
ответственно.
6.4.4. Конструкторы
Создать Quaternion число просто. Достаточно пользователю обеспечить эти
четыре коэффициента, и Quaternion число создано. Пользователь может
вообще не заботиться о значениях коэффициентов: посредством вызова
заданного по умолчанию конструктора коэффициенты инициализируются нулем.
Ниже — некоторые примеры того, как экземпляры Quaternion чисел могут быть
созданы:
// Объявить и int-типа и инициализировать его значением О
Quaternion<int> u;
// Объявить v double типа и инициализировать его к 1+2I-3J+4K
Quaternion<double> v(l,2,-3,4);
// Объявить w и инициализировать его к 1/2 - 2/3 I + 3/4 J - 4/5 К
Quaternion<Rational<int> > w(Rational<int>(l,2),Rational<int>(-2,3),
Rational<int>C,4),Rational<int>(-4,5));
Конструктор копирования и деструктор тривиальны.
6.4.5. Операторы
Предположим, что q и р —два произвольных кватерниона,
ч + р, q-p, q*p, q/p
перегружаются как сложение, вычитание, умножение, деление q и р
соответственно; принимаем во внимание, что — q есть q со знаком минус. Математик'
ские детали были описаны в разд. 2.7.
6.4.6. Другие методы
В этом разделе мы хотели бы продемонстрировать использование метод0
conjugateQ, inverseO, magnitude() и оператора нормализации (~):
6.4. Класс кватернионов
213
squater.срр
elude <i°s'tream-h>
*^rlude "Quatern.h"
void main О
Quaternion<double> QlC,4,5,6),
Q2 = Ql.conjugateQ ,
Q3 = Ql.inverse();
double Mag = Ql.magnitude(), Magz = (~Q1).magnitude();
cout « "Ql = " « Ql « endl;
cout << "Q2 = сопряженное к Ql = " « Q2 << endl;
cout << "Q3 = инверсия Ql = " << Q3 « endl;
cout « "Mag = модуль Ql = " « Mag << endl;
cout << "Magz = модуль нормализованного Ql = " << Magz « endl;
cout << endl;
cout « "Ql * Q2 = " « Ql * Q2 « endl;
cout « "Q2 * Ql = " « Q2 * Ql « endl;
cout « "Mag~2 = квадрат модуля = " << Mag * Mag << endl;
cout << endl;
cout « "Ql * Q3 = " << Ql * Q3 « endl;
cout << "Q3 * Ql = " << Q3 * Ql « endl;
}
Результаты
Ql » C,4,5,6)
Q2 = сопряженное к Ql = C,-4,-5,-6)
Q3 = инверсия Ql = @.0348837,-0.0465116,-0.0581395,-0.0697674)
"ag = модуль Ql = 9.27362
Hagz = модуль нормализованного Ql = 1
41 * Q2 = (86,0,0,0)
02 * Ql = (86,0,0,0)
*в 2 = квадрат модуля = 86
Ql
Q3 = A,0,0,0)
Q3 * Ql = (i,o,o,o)
**•'• Потоки
Ф
кЦии потокового ввода и вывода в классе прямые. Функция ввода просто
<№т четыре коэффициента каждого элемента ввода. В функции вывода ква-
°H<7 = ai *l + ai*I + aj*J + a,K*K форматируется и экспортируется
>ar,aj,aK)
214
Глава 6. Классы компьютерной гшгебры
6.5. Класс производных
6.5.1. Введение
До сих пор мы имели дело с числовыми типами; здесь мы собираемся в
совершенно другую абстракцию. Класс Derive обеспечит нас операторо 6ctl1
торый применяется к числовому типу и результат операции — снова числ К°"
тип. Класс Derive характеризуется следующим: Boii
• Тип данных результата — шаблон Т, определяемый пользователем.
• Конструкция выражений проста: используются арифметические операт
типа-(унарный), +,-,*,/. ь'
• Метод set() используется, чтобы определить точку (число), где бер^,
производная.
• Значение производной может быть получено, используя метод df ().
• Поддерживается оператор вывода производной.
6.5.2. Поля данных
Имеются только два поля данных в классе: одно является переменной ц, в
которой хранится значение точки, где производная вычисляется; другое —
переменная du хранит значение производной в и.
6.5.3. Конструкторы
В классе имеются следующие конструкторы:
• Заданный по умолчанию конструктор объявляет независимую Derive
переменную. Зависимая переменная объявляется с использованием оператора
присваивания или конструктора копирования.
• Derive (const T пшп) объявляет постоянное число num.
• Закрытый конструктор Derive (const T, const T) используется, чтобы
определить значения точки и производной в ней.
• Конструктор копирования и оператор присваивания в классе триви;шьны.
и осуществляют не более чем поэлементное копирование.
• Деструктор тривиален.
Например, объявление у = 2х + 1 требует следующих инструкций:
Derive<int> x; // Это объявление независимой переменной
Derive<int> у = 2*х+1; // ... и зависимой переменной у.
6.5.4. Операторы
Арифметические операторы
-(унарный), +, -, *, /
перегружены, чтобы осуществлять операции, удовлетворяющие условиям W
вил дифференцирования, как описано в разд. 2.9.
6.5. Класс производных
215
-лощие методы доступны в классе:
. „ set (const T) оперирует с независимой переменной. Она использует-
' чтобы специфицировать точку и, в которой вычисляется производная /.
Мртод df (const Derive &х) возвращает значение производной в х.
* Потоковый оператор вывода << выдает значение и.
отрим фуНКцию f(x) = 2хг+Ьх + \. Предположим, что мы хотим оценить
чение df(x = 2)/dx. Выполним следующую программу:
// sderivel.cxx
♦include <iostream.h>
♦include "Derive.h"
void main()
{
Derive<int> x;
x.setB);
Derive<int> у = 2*x*x*x + 5*x + 1;
cout << "Производная у в х = " << x << " равна "
« df(y)« endl;
}
Результат
Производная у в х = 2 равна 29
Рассмотрим другую функцию д(х) — х2 + 3/х. Предположим, что мы хотим
оценить значение dg(x ~ 37/29)/dx, тогда выполним программу
// sderive2.cxx
•include <iostream.h>
•include "Derive.h"
«include "Rational.h"
void main()
{
Derive<Rational<int> > x;
x•set(Rational<int>C7,29));
Derive<Rational<int> > cC);
Derive<Rational<int> > у = x*x + c/x;
cout « » Производная у в х = " << x « " есть "
у « df(y) « endl;
216
Глава 6. Классы компьютерной алгебры
Результат
Производная у в х = 37/29 есть 28139/39701
Обратите внимание, что после объявления независимой переменной
всегда устанавливаем значение, где производная будет браться. Это важ ^
должно быть сделано до объявления зависимой переменной у. Иначе ° *
быть получены непредсказуемые результаты. ^
6.5.6. Возможные уточнения
Большой недостаток этого класса — негибкость в определении функции / ц
пример, чтобы определить выражение у = хь + 2х3 - 3, требуется длинна
инструкция подобно у = х*х*х*х*х + 2*х*х*х - 3. Что делать, если фуНк
ция содержит члены, подобные а:100?
Другие недостатки вытекают из факта, что производная функция /' Не.
известна. Может быть вычислено только значение f'{a). Это вносит сильные
ограничения на полезность и применимость класса.
Эти недостатки могут быть преодолены путем создания более сложного
класса, который описывается в следующей главе — символьного класса. Класс
решает все проблемы, упомянутые выше, к тому же он имеет намного больше
функциональных возможностей.
6.6. Класс векторов
6.6.1. Введение
Вектор — общая математическая структура в линейной алгебре и векторном
анализе (см. разд. 2.6). Эту структуру можно было бы создать, используя
массивы. Однако массивы в С и C++ имеют некоторые недостатки. Они
эффективно обрабатываются лишь как указатели. Поэтому имеет смысл ввести класс
Vector как абстрактный тип данных. Контролируя выход за границы
диапазона и перегружая математические операции (например, сложение векторов), мы
построим исчерпывающий и безопасный в использовании класс Vector. С
другой стороны, он вполне может заменять массивы, поддерживаемые С и C++
как совокупность объектных данных.
Класс Vector —структура, которая обладает многими интересными
свойствами. Поведение его можно суммировать следующим образом:
• Класс Vector лучше реализовать как шаблонный класс, потому что он"
контейнерный класс, благодаря чему элементы экземпляров класса мог)
иметь любой тип.
• Строение класса Vector достаточно простое.
• Доступны арифметические операторы типа +, -, *, /с векторами и чис
ловыми константами.
• Операторы присваивания и присваивания с изменением =, +=, -=, *-> '
перегружены. Мы можем также копировать векторы, используя операТ°^
присваивания.
6.6. Класс векторов
217
оатор взятия нижнего индекса [] перегружен. Он полезен для доступа
компонентам вектора.
* иаторы равенства (==) и неравенства (!=) проверяют, совпадают ли
компоненты двух векторов.
гг тупны операторы скалярного произведения векторов и векторного
проведения.
Л нкция length О возвращает длину вектора, в то время как функция
size О приводит вектор к указанному размеру.
Класс Matrix объявлен как дружественный класс. Это указывает, что клас-
Matrix доступна закрытая область данных класса.
Поддерживаются операторы потокового ввода и вывода векторов.
Вспомогательный файл VecNorm.h содержит различные типы операторов
вычисления нормы: ||v||i, ||v||2, ||v||oo и функцию нормализации для
векторов.
в.б.2. Шаблоны
ЛГонтпеймермъш класс реализует некоторые структуры данных, которые
«содержав другие объекты. Примерами контейнеров служат массивы, списки,
наборы, векторы и т. д. Использование шаблонов особенно подходит для
контейнеров, так как логика управления контейнером в значительной степени
независима от его содержимого. В этом разделе мы увидим, как шаблоны могут
использоваться для создания одной из фундаментальных структур данных в
математике — класса Vector.
Контейнер, который мы реализуем, гомогенен, т. е. содержит объекты
только одного типа в противоположность контейнерам, которые содержат объекты
разных типов. Он также несет определенную семантическую нагрузку.
Поэтому предпочтительнее, чтобы он содержал непосредственно объекты, а не
ссылки на объекты.
6.6.3. Поля данных
Имеются только два поля данных в классе:
• Переменная size, хранящая длину вектора.
Неременная data —указатель на шаблонный тип Т, используемый для
хранения компонентов вектора. Память распределяется динамически в
соответствии с потребностями приложения. Для типов данных, которые требу-
10т большого объема памяти, желательно освобождать память, как только
°аа становится не нужна. Это невозможно при статическом распределе-
Ии памяти. Поэтому типы данных, базирующиеся на массивах (например,
Вектора и матрицы), должны использовать механизм динамического рас-
"Ределения памяти для хранения своих данных.
Jt^ метим, что не существует компонент вектора с индексом за пределами
q j Сов вектора; это означает, что индекс может изменяться лишь в пределах
йу ' ' • • > size-1. Чтобы создать векторы с индексами, начинающимися не с
> Мы можем ввести производный класс, который наследует все свойства и
218
Глава 6. Классы компьютерной алгебры
поведения класса Vector и имеет дополнительное поле данных, котогщ
зывает иную нижнюю границу индексов векторов. Поэтому лучше об-к ^
поля данных класса Vector как protected, нежели private. Это позй ^
потомкам класса обращаться к его полям данных. Реализацию такот , Jlst6;i'
векторов с границей оставляем в качестве упражнения для читателей. Л
6.6.4. Конструкторы
Всякий раз, когда объявляется массив из п векторов, транслятор автомат
ски вызывает заданный по умолчанию конструктор. Поэтому чтобы обеспо,, 6
корректное использование класса, нам нужно должным образом инициали
ровать элементы данных в заданном по умолчанию конструкторе. Это вкл
чает и назначение NULL в качестве значения поля data. Этот шаг критичен то
как мы можем столкнуться с некоторыми трудностями во время исполнения
Общая ошибка состоит в присвоении Vector-величины
неинициализированному вектору с помощью оператора присваивания =, который сначала очищает
старое значение (поля data) левой части оператора. Но нет никакого «старого»
значения: в data очищается некоторое случайное значение, что может
привести к разрушительным последствиям. Выходом является обнуление
переменной data, потому что удаление указателя NULL совершенно законно и не имеет
никакого побочного эффекта.
Фактически имеется два перегружаемых конструктора:
Vector(int n) Vector(int n,T value)
Первый конструктор выделяет п ячеек памяти для вектора. Второй помимо
этого инициализирует компоненты вектора значением value.
Конструктор копирования Vector(const Vectorft source) создает новый
вектор, идентичный исходному вектору source. Он вызывается автоматически
при необходимости создания временных копий, например при передаче
параметров функции и возвращаемых значений. Он может также использоваться
явно при конструировании вектора.
Ниже мы перечислим некоторые общие способы объявления вектора:
// объявление вектора из 10 чисел типа int
Vector <int> uA0);
// объявление вектора из 10 чисел, и инициализация их значением О
Vector <int> vA0,0);
// использование конструктора копирования
// для создания и дублирования вектора
Vector <int> w(v);
Всякий раз, когда локальный Vector-объект выходит из оборота, вступает
в игру деструктор. Он освобождает память, занятую массивом компонент08
вектора, возвращая ее в кучу. Если этого не делать, память будет утеряна'
так как она остается распределенной, но никакой указатель уже не указывав
на нее.
6.6. Класс векторов
219
Операторы
" яство операторов, применимых к векторам, реализовано в классе, а
/„«арный)+, (унарный)-, +, -, *, /,
^+=, -=, *=, /=, ==, ! = . I. У- П.
плозким, что u, v, w — векторы, и с — числовая константа, тогда имею-
#ре,Д оПераторы определяются следующим образом:
u-v, u*v, u/v складывает, вычитает, умножает или делит соответ-
' ствующие компоненты и и v.
+=V) u-=v, u*=v, u/=v складывает, вычитает, умножает или делит
соответствующие компоненты v и и с последующим присваиванием результата
компонентам и.
и+=с, и-=с, и*=с, и/=с складывает, вычитает, умножает или делит
каждую компоненту и со скаляром с с последующим присваиванием результата
вектору п.
• Оператор присваивания = должен быть перегружен в классе. Если опустить
определение оператора присваивания, транслятор C++ сам вставит его.
Однако нужно иметь в виду, что код, произведенный транслятором,
просто копирует байт за байтом компоненты данных. В случае динамического
распределения памяти оператор копирования «байт за байтом» копирует
только адрес указателя, а не содержимое памяти, что приводит к появлению
указателей, указывающих на одну и ту же ячейку памяти. Это требует
соответствующих средств управления. Поэтому надежнее иметь собственный
оператор присваивания. Те же самые аргументы приводят к необходимости
иметь конструктор копирования векторов.
В классе имеются две формы оператора присваивания:
— u=v делает копию из v в и.
— и=с присваивает константу с каждой компоненте вектора и.
Заметим, что оператор присваивания определен так, что он возвращает
ссылку на сам объект (присваивания), тем самым позволяя конструкции
типа u = v = w.
• u==v возвращает истину, если и и v содержат равные компоненты в том же
самом порядке, иначе возвращает ложь.
u.=v — лишь обратное утверждение для u==v.
Мы используем символ | как оператор скалярного произведения (также
используется название внутреннее произведение). Оно определяется как
"екторное произведение (используется также название прямое
произведете) в классе обозначается как '/,.
Оператор [] позволяет, написав u[i], обратиться к i-й компоненте вектора
ц- Он возвращает ссылку на i-ю компоненту (но не значение) вектора, для
того чтобы его можно было использовать в левой стороне оператора присва-
вания. В терминологии языка C++, это именующее выражение (lvalue).
•
220
Глава 6. Классы компьютерной алгебры
Ниже показаны некоторые примеры использования скалярного и вектп
го произведений векторов. Предположим, что А, В, С, D — четыре вектгт
«а.
R3, тогда " ~"та*;
А х {В х С) + В х [С х А) + С х (А х В) = О
(Л х В) х {С х £>) = В(А • С х £>) - Л(В • С х D)
= С(А BxD)~ D(A -BxC)
А- (В х С) = (Ах В) С.
Следующая программа демонстрирует, что эти тождества выполняются
некоторых случайно выбранных векторов: я
// vprod.cpp
#include <iostream.h>
#include "Vector.h"
void main()
{
Vector<double> AC), BC), CC), DC);
A[0] = 1.2; A[l] = 1.3; A[2] = 3.4
B[0] = 4.3; B[l] = 4.3; B[2] = 5.5
C[0] = 6.5; C[l] = 2.6; C[2] = 9.3
D[0] = 1.1; D[l] = 7.6; D[2] = 1.8
cout « А'/.(ВУ.С) + ВУ.(С'/.А) + С'/.(А'/.В) « endl;
с out « (A'/.B)'/,(C,/.D) « endl;
cout « B*(A|C'/.D)-A*(B|C,/.D) « endl;
cout « C*(A|B,/.D)-D*(A|B'/.C) « endl;
// приоритет у I меньше, чем у «
cout « (AIB'/.C) - (АУ.В1С) « endl;
}
Результат
[1.42109e-14]
[0]
[0]
[372.619]
[376.034]
[540.301]
[372.619]
[376.034]
[540.301]
6.6. Класс векторов
221
ГЧ72-6193
О
ненулевое значение 1.42109е-14 вызвано погрешностями округления. Та-
^пйпазом, чтобы получить правильный результат, а именно нулевой вектор,
**" пользуем тип данных Vector<Rational<int> >.
я 6.6. Методы и нормы
е арИфметических операторов, в классе Vector есть еще несколько
полезных функций и методов. Их определения и свойства перечислены ниже:
, функция length () возвращает размерность вектора.
• resize (int n) устанавливает размерность вектора равной п. Компоненты
при этом не изменяются, за исключением того, что, если новая
размерность меньше начальной, то лишние хвостовые компоненты удаляются, а
если больше, то добавляются новые хвостовые неинициализированные
компоненты.
• resize (int n, value T) действует как предыдущий метод, кроме случая,
когда новая размерность больше начальной: в этом случае дополнительные
компоненты инициализируются значением Т.
Во вспомогательном файле VecNorm.h мы реализуем три различные векторные
нормы и функцию нормализации:
• norml(x) определяется как ||x||i := |xi| + |х2| + • • ■ + \х„\.
• norm2(x) определяется как ||х||2 := \/xf + х\ + ... + х%, тип norm2() —
double.
• norml(x) определяется как ||х||оо := max{|xi|, |х2|,-• • , |а:„|}.
функция normalize(х) используется, чтобы нормализовать вектору..
Нормализованная форма вектора х определена как х/1 х I, где I x I есть 2-
чорма х.
тобы иметь лучшее представление об этих функциях, рассмотрим неко-
^Рые примеры.
11 vaorm.cxx
^clude <iostream.h>
Seclude "Vector.h"
delude "VecNorm.h"
222
Глава 6. Классы компьютерной алгебры
Vector<int> v;
v.resizeE,2);
cout << "Размерность вектора v равна " << v.lengthQ << endl■
cout << endl;
Vector<double> aD,-3.1), b;
b.resizeD);
b[0] = 2.3; b[l] = -3.6; b[2] = -1.2; b[3] = -5.5;
// Различные векторные нормы
cout « "normlQ of a = " « norml(a) « endl;
cout « "norm2() of a = " « norm2(a) « endl;
cout « "normlO of a = " « norml(a) « endl;
cout « endl;
cout « "normlO of b = " « norml(b) « endl;
cout « "norm2() uf b = " « norm2(b) « endl;
cout « "normlO of b = " « norml(b) « endl;
cout << endl;
// Норма norm2() нормализованных векторов а и b равна 1
cout << "norm2() нормализ. a = " << norm2(normalize(a)) << endl;
cout << "norm2() нормализ. b = " << norm2(normalize(b)) << endl;
}
Результат
Размерность вектора v равна 5
norml()
norm2()
normlO
norml()
norm2()
norml()
norm2()
norm2()
of
of
of
of
of
of
a
a
a
b
b
b
= 12
= 6.
= 3.
= 12
= 7.
= 5.
нормализ.
нормализ.
!.4
2
1
!.6
06682
5
a =
b =
;
1
1
6.6.7. Потоки
Перегруженный оператор потокового вывода « просто выдает все компоненты
вектора v и помещает их между парой квадратных скобок [г>о, v\, г>2, •.. ,v,,~u'
Оператор потокового ввода >> сначала читает и записывает в поле size
размерность вектора, затем считывает соответствующее количество компонент
вектора.
6.7. Класс матриц
223
j Класс матриц
Ifj. Введение
__ двумерные массивы с некотором числом строк и столбцов. Они —
Д/00Я* структуры в линейной алгебре. Строить матричный класс мы будем не
ва3,сЯ~\ом месте. Мы используем преимущества (возможность многократного
в» В^з0вания и расширяемость) объектно-ориентированного программирова-
"^«тобы формировать новый класс, основанный на классе Vector. Вектор —
вЯ^1яый случай матрицы с числом столбцов, равным одному. Таким образом
Ч8С*\Г|кем определить матрицу как вектор векторов.
template <class T> class Matrix
{
private;
// Поля данных
int rowNum, colNum;
Vector<T>* mat;
}
Мы объявили Matrix как шаблонный класс. Определение матрицы как
вектора векторов имеет то преимущество, что методы матричного класса могут
использовать многие из векторных операций, определенных для векторного
класса. Например, мы могли произвести векторное сложение в каждой
строке матрицы, чтобы получить результат матричного сложения. Это сокращает
объем повторного кода.
Чтобы эффективно использовать матричный класс, пользователи должны
ознакомиться с поведением и интерфейсами класса. Ниже мы суммируем их:
• Matrix реализован как шаблонный класс. Это указывает на то, что тип
элементов матриц может быть любым, включая встроенные типы и
определяемые пользователем типы.
• Имеются несколько простых способов создания матриц.
• Доступны арифметические операторы типа +, -, * с матрицами и
+ > -> *, / с числовыми константами.
Перегружены операторы присваивания = и присваивания с изменением
+=> -=, *= и /=.
Доступны векторные операторы.
°ддерживается кронекерово произведение двух матриц.
Ператор взятия нижнего индекса [] перегружен для обращения к вектору
ДР°ки матрицы, в то время как парный оператор взятия верхнего индекса
• п ПеРегРУжен Для обращения к вектору столбца матрицы.
пеРаторы равенства (==) и неравенства (! =) проверяют, совпадают ли
, р матрицы или нет.
% £еализованы транспозиция, вычисление следа и детерминанта матрицы.
^изовано обращение квадратной матрицы.
224
Глава 6. Классы компьютерной алгебры
• Метод resize() переопределяет числа строк и столбцов согласно
значениям, поступившим в параметрах функции и размещает в памяти ^
рицу. МЪ-
• Методы row() и cols() возвращают число строк или столбцов матг>
соответственно. ^Ч
• Поддерживаются потоковые операторы ввода (») и вывода («).
• Вспомогательный файл MatNorm.h содержит три различные матрич
нормы: |И||Ь \\A\U U\\h. Щ*
6.7.2. Поля данных
Поля данных rowNum и colNum определяют число строк и столбцов матрищ,
соответственно.
Vector<T>* mat хранит элементы матрицы. Она объявлена как вектор век
торов. Чтобы выделить память для m x n матрицы, мы должны сначала раз-
местить вектор из m указателей на вектора. Затем для каждого указателя
мы размещаем снова вектор размерности п. В итоге под матрицу отводится
тп х п ячеек памяти. После соответствующей инициализации матрица готова
к использованию.
6.7.3. Конструкторы
В классе имеется два способа создать матрицу. Единственный критерий для
существования матрицы — специфицирование числа строк и столбцов:
• Matrix() объявляет матрицу без спецификации размерностей. Такая
матрица не пригодна для использования. Чтобы активировать матриц}', мы
используем метод, называемый resize(), который переразмещает матриц)'
с числом строк и столбцов, задаваемых в параметрах функции.
• MatrixCint nr, int nc) объявляет nr*nc матрицу с
неинициализированными элементами.
• Matrix(int nr, int nc,T value) объявляет nr*nc матрицу с элементами,
инициализированными значением value.
• MatrixCconst Vector<T>& v) создает матрицу из вектора v.
Подразумевается, что результирующая матрица будет иметь только один столбец.
• Конструктор копирования дублирует матрицу. Он вызывается
автоматически транслятором, когда необходимо, и также явно может быть вызван
пользователями.
• Деструктор возвращает неиспользованную память назад в пул свободно"
памяти.
Ниже приведены некоторые примеры объявлений матрицы:
// Объявить 2x3 матрицу типа int
Matrix<int> mB,3)
// Объявить 3x4 матрицу и инициализировать ее элементы значением 5
Matrix<int> пC,4,5)
6.7. Класс матриц
225
/,
i =
«повать матрицу, используя конструктор копирования
// дублир , ч
/' Ci*<int> p(n);
„чпать матрицу q из вектора v
//ctC:Aouble>vC 0);
J;trix<double>q(v);
6.7.4. Операторы
еется много матричных операторов, реализованных в классе, а именно
ляарный)+, (унарный)-, +, -
*1-, -=. *=• /=> п, о, =
Некоторые из операторов перегружаемы с более чем одним определением!
Пользователям советуем перед использованием класса тщательно читать
документацию.
Ниже мы обсуждаем поведение и использование операторов. Предположим,
что А, В— матрицы, v— вектор, с — числовая константа.
• Операторы А+В, А-В и А*В складывают, вычитают и перемножают две
матрицы согласно их обычным определениям.
• Операторы А+с, А-с, А*с и А/с определены как A+cI, A-cI, A*cl и А/с1
соответственно, где I — единичная матрица.
• Операторы с+А, с-А, с*А и с/А имеют аналогичные определения, как выше.
• А=В делает в А дубликат из В, а А=с присваивает с всем элементам матрицы А.
• А+=В, А-=В и А*=В — лишь модификации оператора присваивания, которые
осуществляют две операции на одном операторе. Например, А+=В
эквивалентно выполнению А=А+В.
• А+=с, А-=с, А*=с и А/=с — только модифицированная форма оператора
присваивания. Например, А+=с эквивалентно выполнению А=А+с1.
• Функция vec(A) используется для создания вектора, который содержит
элементы матрицы А, один столбец за другим. Предположим
2 х a
0 3-3
А =
тогда
/ 2 \
vec(A) =
2
0
х
3
а
V -з/
Ронекерово произведение двух матрицы описано в разд. 2.12. Функция
°ь(А,В) используется для вычисления кронекеровапроизведения матриц
и 5. Заметим, что в общем случае А <8> В ф В ® А (если А ® В и В ® А
^Меют одинаковые размерности) и {А ® В) ® (С ® D) - (AC) ® (BD) (если
совместима с С я В совместима с D по размерностям).
'- 154S
226
Глава 6. Классы компьютерной алгебры
• Оператор нижнего индекса [] перегружен и означает обращение к вок
строки матрицы. Например, A[i] возвращает i-ю строку матрицы а °^
вектор. ^
• Оператор верхнего индекса () перегружен и означает обращение к век
столбца матрицы. Например, B(j) возвращает j-й столбец матрицы в ^
вектор. к
• Операторы равенства (==) и неравенства (!=) проверяют, совпадают
элементы двух матриц. и
Операторы == и ! = имеют более низкое старшинство, чем оператор поток
вого вывода «. Это означает, что требуются скобки при записи инструкци
подобных следующим: '
cout « (u != v) << endl;
cout « (и == v) « endl;
в противном случае транслятор будет жаловаться на них.
6.7.5. Методы и нормы
В класс включено много полезных методов. Их свойства описаны ниже:
• transpose () mxn матрицы А представляет собой nxm матрицу Ат, такую,
что элемент ij матрицы Ат является элементом ji матрицы А.
• trace () пхп матрицы — это сумма всех диагональных элементов A, tiA :-
an +a22 + ■•• + ann.
• determinant () — метод для вычисления детерминанта матрицы был описан
в разд. 2.13. Используемый алгоритм зависит от того, является ли матрица
символьной или числовой. Так как наша система, как предполагается,
работает с символьными выражениями, мы используем метод Леверье(Ьеуегпег)
для вычисления детерминанта.
• inverse () используется, чтобы получить обратную матрицу (если матрица
обратима). Алгоритмы, использованные для нахождения обратной
матрицы, различны для числовой и символьной матриц. Для числовой матрицы
мы можем использовать LU-разложение [40] и метод обратных
подстановок, а для символьной матрицы мы используем метод Леверье. Этот метод
может также использоваться и для числовой матрицы.
• resize () переопределяет число строк и столбцов согласно новым специф!1"
кациям,задаваемым параметрами функции,и размещает матрицу в памяти-
• rows() и cols() возвращают число строк и столбцов матрицы,
соответственно.
Во вспомогательном файле MatNorm. h мы реализовали три различные матри4"
ные нормы:
• norml (А) определена как максимальное значение сумм абсолютных величй
элементов по столбцам,
Pl|i:=1^„{|j|a<il}-
6.7. Класс матриц
227
т (д) определена как максимальное значение сумм абсолютных величин
' Цементов по строкам,
ЦЛЦоо :=тах < У]\а^\
1<г<п I '—•'
-- [j=l
ттпН(А) —норма Гильберта—Шмидта, определяемая как
п m
\\А\\Я := [tvA-A]^ = МЛ']1/2 = ££ |а0-р.
\ t=i j=i
Пример 1
В этом примере демонстрируется использование кронекерова произведения
матриц- Мы объявляем и определяем четыре матрицы и затем вычисляем
произведение Кронекера.
// kron.cxx
•include <iostream.h>
«include "Matrix.h"
void main()
{
Matrix<int> AB,3), ВC,2)
A[0][0] = 2; A[0][1] = -4;
A[1][0] = 4; A[l][l] = -1;
B[0][0] = 2; B[0][1] = -4;
B[1][0] = 2; B[l][l] = -3;
BM[0] = 3; B[2][l] = -1;
C[0][0] = 2;
C[l][0] = 1;
C[2][0] = -2;
, CC,l),
A[0][2]
A[13[2]
DB,2);
= -3;
= -2;
D[0][0] = 2; D[0][1] = 1;
°tl][0] = 3; DEI] [13 = -1;
Cout « kron(A,B) « endl;
Cout « kron(B,A) « endl;
} c°ut « kron(A,B)*kron(C,D) - kron(A*C,B*D) « endl;
228
Result
[4 -8 -8 16
[4 -6 -8 12
[6 -2 -12 4
[8 -16 -2 4
[8 -12 -2 3
[12 -4 -3 1
[4 -8 -6 -8
[8 -2 -4 -16
[4 -8 -6 -6
[8 -2 -4 -12
-6 12]
-6 9]
-9 3]
-4 8]
-4 6]
-6 2]
16 12]
4 8]
12 9]
3 6]
[12 -3 -6 -4 1 2]
[О О]
[О О]
[О О]
[О О]
[О О]
[О О]
Пример 2
R II'DM npilMI'l-iP МЫ Д<".!чЦГтрНрус.\!
tr (AB) =tr (BA) tr (AB) otr (A) tr (В)
//trace.ххх
#include <iostream.h>
#include "Matrix.h"
void main()
{
Matrix<double> AC,3),BC,3,1);
A[0] [0]=2; A[0] [1]=-1; A[0] [2]=1;
A[l] [0]=1; A[l] [l]=-2; A[l] [2]=-l;
A[2] [0]=3; A[2] [1]=2; A[2] [2]=2;
cout<<"A = \n"<<A<<endl;
cout<<"B = \n"<<B<<endl;
cout<<"tr(A) = "<<A.trace()<<endl;
cout<<"tr(B) = "<<B.trace()<<endl;
<j 7 К.'ькг лг,и;нш
cout << "tr(AB) = " << -cout<< (А*В) .trace 0 <<endl;
cout << "tr(BA) = " << cout<< (B*A).trace() <<endl;
cout << "tr(A)tr(B) = " << A.traceO * B.traceO << endl;
}
Result
A =
[2 -1 -1]
[1 -2 -1]
[3 2 2]
[-1 -1 -1]
[-1 -1 -1]
[-1 -1 -1]
tr(A) = 2
tr(B) = -3
tr(AB) = -7
tr(BA) = -7
tr(A)tr(B) = -6
lipliM.p '■',
I! iii.;! ii[iHMr'; • ::r. v-w 4h' i l iii[ .;.'.'M in iu .:n. ««шш1 mi'T'j.i.l d rr term in;
#include <iostream.h>
#include "Matrix.h"
void main()
{
Matrix<double> AB,2);
A[0] [0]=1; A[0] [1]=2;
A[l] [0]=3; A[l] [1]=4;
COUt<<A;
cout<<"Matrix determinant = "<<A.determinant()<<endl;
cout<<endl;
for(int i=3;i,5;i++)
{
A. resize(i,i,i) ;
cout<<A;
cout<<"Matrix determinant = "<<A.determinant()<<endl;
230
Глава 6. Классы компьютерной алгебры
cout << endl;
}
}
Результат
[1 2]
[3 4]
Детерминант матрицы = -2
[1 2 3]
[3 4 3]
[3 3 3]
Детерминант матрицы = -6
[12 3 4]
[3 4 3 4]
[3 3 3 4]
[4 4 4 4]
Детерминант матрицы = 8
6.8. Классы массивов
6.8.1. Введение
Массив — общая структура данных в программировании. Языки С и C++
снабжены встроенными структурами данных массива, которые имеют некоторые
недостатки. Например, не контролируется выход за границы массива для
предотвращения возможных ошибок во время выполнения. Поэтому полезно ввести
класс Array как абстрактный тип данных. Мы построим исчерпывающий класс
Array контейнерного типа с контролем границ, перегружая некоторые простые
математические операторы (например, добавление массива). С другой
стороны, он может заменить массив, поддерживаемый С и C++ как совокупность
объектов данных.
В этом разделе мы реализуем одно-, дву-, трех- и четырехмерные массивы.
Каждый п+ 1-мерный массив использует методы и операторы n-мерного. Пх
близкая зависимость видна очевидным образом в полях данных:
template <class T> class Arrayl template <class T> class Array2
{ {
private: private:
// Поля данных // Поля данных
int n_data; int rows, cols;
T *data; Arrayl<T> *data2D;
}; };
6.8. Классы массивов
231
. <class T> class АггауЗ template <class T> class Array4
{nrivate: private:
// Поля данных // Поля данных
• t rows, cols, lews; int rows, cols, lews, blks;
A ray2<T> *data3D; Array3<T> *data4D;
};
>;
Здесь мы сильно упростили конструкцию класса, а код сделали более
спективным и информативным. В очередной раз мы продемонстрирова-
могць объектно-ориентированного программирования в плане повторного
использования кода и расширяемости.
Прикладные программы для одно- и дву- мерных классов Array общие.
Причины расширения класса Array на три и четыре измерения состоят в том,
что некоторые прикладные программы для тензорных полей нуждаются в
won. Например, символы Кристоффеля требуют трехмерного класса Array,
а для тензора кривизны мы нуждаемся в четырехмерном классе Array.
В этом разделе описываются четыре класса Array (мы назвали их как
Arrayl, Array2, АггауЗ и Аггау4), которые предназначены для классов одно-,
дву-, трех- и четырехмерных массивов, соответственно. Хотя они
различаются в определениях и функциональных возможностях, они используют похожие
интерфейсы. Поэтому описанное ниже относится ко всем четырем классам,
кроме некоторых незначительных различий, которые будут отмечены по ходу
описания.
• Класс реализован как шаблонный класс, поскольку шаблоны работают
очень хорошо в контейнерных классах.
• Конструкция Array проста.
• Доступны простые арифметические операторы типа +, -, *.
• Перегружены операторы присваивания = и присваивания с изменением
+», -=, *=.
• Мы можем копировать один массив в другой, используя оператор
присваивания =.
• Оператор взятия нижнего индекса [] перегружен. Он обеспечивает доступ
к индивидуальному элементу и возвращает ссылку на этот элемент.
Операторы равенства (==) и неравенства (! = ) проверяют, содержат ли два
массива те же самые элементы и в том же самом порядке.
Для массива размерности N метод size (int index) возвращает число
элементов в index-м измерении массива, где index= 0,...,N — 1. Например,-
S1ze@) возвращает число элементов по нулевому измерению для одно-,
^У-, трех- и четырехмерных массивов.
esize() переопределяет размер массива к специфицируемому размеру.
Поддерживаются операции потокового ввода и вывода в классе Array.
232
Глава 6. Классы компьютерной алгебры
6.8.2. Поля данных
Имеются только два поля данных в каждом классе. Это — размерности масг
и указатель на элементы массива. а
• Класс одномерных массивов Arrayl содержит только целую перемени
n_data, которая определяет размер массива. Он также содержит указатр
на непрерывный массив Ts.
Память распределяется динамически так, чтобы удовлетворять потрек
ности прикладных программ. Для типов данных, которые требуют больщо.
го объема памяти, желательно освобождать память, как только она больш
не нужна. Это невозможно со статическим распределением памяти.
Поэтому типы данных на основе массива должны использовать механизм
динамического распределения памяти для хранения данных.
• Класс двумерных массивов Аггау2 требует двух целых чисел — rows и cols
для определения их размеров, а также указатель на Arrayl. Чтобы
разместить в памяти mxn массив, мы сначала размещаем массив из m указателей
на Arrayl, затем для каждого указателя размещаем массивы размера п.
• Классы трех- и четырехмерных массивов АггауЗ и Аггау4 требуют,
соответственно, три и четыре целые переменные, определяющие их размеры.
АггауЗ содержит указатель на массив класса Array2, a Array4,
соответственно — на массив класса АггауЗ. Эти указатели используются для
распределения памяти, требуемой для хранения элементов массивов.
Процедуры для распределения памяти для этих указателей следуют той же самой
логике, как для двумерных массивов, описанной выше.
6.8.3. Конструкторы
Детализация для конструктора, заданного по умолчанию, важна, особенно
когда поля данных класса включают некоторые указатели или ссылки. Важно
инициализировать элементы данных в классе, включая пустые указатели. Это
предотвращает появление некоторых проблем на более поздних этапах
разработки. Каждый класс имеет два разных перегруженных конструктора:
Arrayl(int) Arrayl(int,T value)
Array2(int,int) Array2(int,int,T value)
АггауЗ(int,int,int) Array3(int,int,int,T value)
Array4(int,int,int,int) Array4(int,int,int,int,T value)
Первый набор конструкторов выделяет соответствующий объем памяти Д™
массивов класса, в то время как второй, кроме того, инициализирует все a.'ie"
менты значением value. Конструктор копии и деструктор должны быть дол*"
ным образом реализованы для класса, имеющего указатели в полях данных
Ниже мы даем несколько примеров использования конструкторов:
// Объявить и, как одномерный массив размера 20,
// элементы неинициализированы.
Arrayl<int> uB0);
6.8. Классы массивов
233
пбъявить v, как двумерный массив размера 2x3,
пементы инициализированы значением нуль.
//Ду2< double> vB,3,0.0);
Пбъявить w, как трехмерный массив типа double,
/ с размером 2x3x2, все его элементы
// иняЦИаЛИЗИРованы значением 2.0
дггауЗ< double> wB,3,2,2.0);
// Объявить х, как четырехмерный массив размера 1x2x3x4,
// все элементы в массиве неинициализированы.
Array4< double> x(l,2, 3,4);
// Конструктор копирования вызывается для дублирования массива.
АггауЗ у(w);
6.8.4. Операторы
Некоторые общие операторы перегружены в классе, а именно (унарные) +
и -, +, -, *, =. +=, -=, *=. ==, ! = , [], «, и ». Предположим, что
u, v, и—массивы, с — константа, тогда операции определены следующим
образом:
• +и не оказывает никакого эффекта на и, в то время как -и меняет знак
каждого элемента и.
• u+v, u-v складывает или вычитает соответствующие элементы и и v.
• u*c умножает каждый элемент и на скаляр с.
• u+=v, u-=v прибавляет или вычитает соответствующие элементы v и и и
Записывает результат в и.
• и*=с умножает на скаляр с каждый элемент и и записывает результат в и.
• u=v делает копию v и записывает ее в и. Заметим, что оператор
присваивания возвращает ссылку на сам объект присваивания (т.е. на и). Это
означает, что разрешается многократное копирование подобно u = v = w.
и=с записывает постоянную с во все элементы и.
u==v возвращает истину, если и и v идентичны, иначе возвращает ложь.
u.=v — всего лить обратное утверждение к u==v.
Ц оператор перегружен для возможности доступа к индивидуальным
элементам. Конструкция u[i] возвращает ссылку на i-й элемент массива. Так
^к возвращается ссылка, то конструкция может использоваться с обеих
ТоРон выражения присваивания.
Ператор потокового вывода « просто выводит все элементы и заключает
х в квадратные скобки.
234
Глава 6. Классы компьютерной алгебры
Например, массив и класса Arrayl форматируется как
[ Щ Щ ... Un_data-1 ],
а массив v класса Аггау2 форматируется как
[ г»ю vn
1*0 cols
Vl cols
^rows 0 ^rows 1
^rows cols J
Вывод для АггауЗ менее очевиден. Массив можно рассматривать ка
куб элементов массива. Результат вывода отформатирован таким образом
что каждый уровень куба выводится как массив класса Аггау2 и отделяется
пустой строкой. Образец АггауЗ иB,3,2) будет напечатан как
[ wooo wooi ]
[ Wow Won ]
i мет w°21 i
WlOO
who
W120
Wioi
Will
W121
Идея вывода массива АггауЗ расширена на Аггау4. Четырехмерный массив
можно визуализировать как совокупность трехмерных блоков АггауЗ.
Поэтому при печати массива Аггау4 мы печатаем блоки АггауЗ, следующие
один за другим, пока все четвертое измерение массива не будет исчерпано.
Как пример, мы печатаем массив Array4 xB,2,2,3)
[ Z0O00 г0001 ^0002 ]
[ZooiO Z0011 Z0012 1
[ ^0100 Zoioi ^0102 ]
[ ^oiio zom zom ]
[ ^1000 2Г1001 Z1002 ]
[zioio zioii ж1012 ]
[гцоо znoi a;U02 ]
[ ZlUO Inn ЖЦ12 ]
Каждый уровень в АггауЗ отделен пустой строкой в то время, как кажды11
блок в Аггау4 отделен двумя пустыми строками.
• Оператор потокового ввода > > — только обращение оператора потокового
вывода. Он читает размерности каждого измерения массивов, затем с-411'
тывает индивидуальные элементы данных, пока все элементы не будут nDt"
дены.
6.8. Классы массивов
235
,a4 Методы
доступные для каждого массива класса, включают:
«ize(int = 0) const; Возвращает число элементов в каждом измере-
* * и для каждого класса. Например, size@) возвращает размерность для
д гау1, или количество строк для Аггау2, АггауЗ и Array4; sizeB) воз-
оащает количество уровней для АггауЗ и Аггау4, а для Array 1 и Аггау2
не определен.
Заметим, что задание параметра по умолчанию используется для функ-
ггии- это указывает, что, если параметр опущен, то возвращается размер
нулевого измерения для каждого массива.
void resize(int) ; Это —важная функция для массивов класса.
Следующая инструкция
Array1 <int> u;
объявляет переменную и, но память под нее не выделяется. Такая
переменная не пригодна для использования. После выполнения инструкции
u.resize A00);
транслятор размещает 100 элементов массива и в памяти. После этого
переменная к использованию готова. Если мы хотим заполнить массив
некоторыми начальными значениями при размещении в памяти, применяется
другая форма функции:
void resize(int n, value T);
В дополнение к распределению памяти все элементы инициализируются
значением value. Для Аггау2, АггауЗ и Аггау4 метод работает аналогично,
за исключением того, что resize () требует два, три или четыре параметра
для задания размеров по соответствующим измерениям массивов.
Функция resize() фактически может использоваться для изменения
размерностей массивов в процессе использования. Увеличение размерности
массива привело бы к переносу старого содержимого памяти, копируемого
в новый массив с увеличенным размером, неопределенного или
инициализированного, в зависимости от того, какая форма resizeQ используется.
Когда resize () сокращает массив, только соответствующая часть старой
памяти будет скопирована в новый массив, и дополнительные элементы
будут «усечены».
Следующая программа демонстрирует использование функции resize О :
'/ aresize.cxx
#include <iostream.h>
♦include "Array.h"
v°id mainО
ArrayKdouble> M;
236
Глава 6. Классы компьютерной алгебры
M.resizeC);
М[0] = 5.0; М[1] = 8.0; М[2] = 4.0;
cout « "М = \п" « М « endl; cout « endl;
Array2<double> A;
A.resizeB,3);
A[0][0] = 1.0; A[0][1] = 3.0; A[0] [2] = 2.0;
A[1][0] = 4.0; A[l][l] = 5.0; A[l][2] = 6.0;
A.resizeC,4,2);
cout « "A = \n" << A « endl;
A.resizeB,2);
cout « "A = \n" << A « endl;
A.resizeD,4,9);
cout « "A = \n" « A « endl;
}
Результат
M =
[5 8 4]
A =
[13 2 2]
[4 5 6 2]
[2 2 2 2]
A =
[1 3]
[4 5]
A =
[13 9 9]
[4 5 9 9]
[9 9 9 9]
[9 9 9 9]
6.9. Класс строк
В традиционном С строка представлена как указатель символьного типа. Это
означает, что строку можно рассматривать как массив символов. В этом прсД"
ставлении конец строки отмечается символом '\0'. Это соглашение имеет <>'
щественный недостаток: символ '\0' занимает один байт памяти и явлжК'Я
частью строки. Например, строка "abc" требует 4 байта памяти. Несоотм'1"
ствие между длиной строки и размером занимаемой строкой памяти вызыи;1'
ет замешательство многих пользователей и является источником ошибок д-'>>'
них. Поэтому, чтобы избежать возможных ошибок и повысить эффективнее'11'
6.9. Класс строк
237
вых операций, лучше всего реализовать класс строк как абстрактный
г,?Р° ^,ных. Класс строк является стандартным в C++.
0.1. Введение
пение класса суммарно описывается следующим образом:
Конструирование строк просто.
•Экземпляры класса String дублируются оператором присваивания.
Оператор взятия индекса [] применительно к строкам обеспечивает доступ
к отдельным символам строки.
Оператор сложения + означает для строк их конкатенацию.
Доступны операторы сравнения для строк: >, >=, <, <=, ==, !=.
При необходимости строки могут быть преобразованы к const char*.
reverse О изменяет порядок символов строки на обратный.
• length () возвращает длину строки.
• Поддерживаются операторы ввода и вывода строк.
6.9.2. Поля данных
В классе имеются два поля данных:
• datalength хранит максимум возможной длины строки.
• data содержит указатель на объект типа char. Когда строка размещается в
некоторой области памяти, оно будет использоваться для хранения
символов строки.
6.9.3. Конструкторы
Строки создаются и инициализируются следующим образом:
• String x; String y=0; String z="";
х, у и z суть пустые строки. Заметим, что 0 и "" могут использоваться как
NULL-строка.
• String x('A'), у('#'); устанавливает х и у равными А и #,
соответственно.
• String x("abc"); String y="abc"; устанавливает х и у равными строке
"abc".
• String x(int n); размещает в памяти NULL-строку х длиной п байт.
• String u(x); String v=x; вызывается конструктор копирования, чтобы
Дублировать х в и и v.
Деструктор используется для освобождения неиспользованной памяти
обратно в кучу.
•"•4. Операторы
ь
Класс были включены некоторые общие операторы, а именно
"• G, +, ==, ! = , >, >=, <, <=, », «.
238
Глава 6. Классы компьютерной алгебры
Их свойства описываются следующим образом:
• Оператор присваивания = делает дубликат строки.
• Оператор взятия индекса [] обеспечивает доступ к отдельным симв
строки. Лам
• Оператор конкатенации + соединяет две строки в одну.
• Шесть операторов сравнения ==, ! =, >, >=, <, <= сравнивают упопя
ченные лексикографически строки, используя функцию strcmpQ .
• Операторы потокового ввода и вывода дают способ импорта и экспог>
экземпляров строк.
• Оператор преобразования типов автоматически конвертирует строку
встроенный тип const char*, когда это необходимо.
6.9.5. Методы
Имеется лишь два метода, реализованных в классе— length() и reverse() .-
• length() возвращает длину строки.
• reverse () изменяет порядок следования символов строки на обратный и
возвращает обращенную строку в качестве результата.
Описание большинства операций и методов очевидно и может быть
легко понято. Оператор преобразования к встроенному типу const char* играет
важную роль в классе. Он позволяет использовать с классом все необходимые
функции из библиотеки <string.h>. В силу этого мы избегаем дублирования
кода, хотя некоторые из функций могли бы быть заново реализованы в лучшей
форме. Мы демонстрируем использование класса в следующих примерах:
// sstring.cxx
#include <iostream.h>
#include "MString.h"
void main()
■С
String SI("Children");
cout « " Строка SI есть " « SI « endl;
cout << " Отражение строки SI есть " « SI.reverse() << endl;
cout « " Длина строки SI есть " « SI.length() « endl;
cout << endl;
String S2 = "Mother";
String S3(S2);
S3[0] = 'F';
S3[l] = 'a';
cout « " Строка S2 есть " « S2 « endl;
cout « " Строка S3 есть " « S3 « endl;
6.9. Класс строк
239
coUt « endl;
// Конкатенация строк
oUt « S2 + " and " + S3 « endl;
cout « S3 + " alid " + S2 <<: endl;
c0ut « endl;
// Сравнение строк
cout « (S2 == S3) « endl;
cout « (S2 != S3) « endl;
cout « (S2 < S3) « endl;
cout « (S2 > S3) « endl;
>
результат
Строка SI есть Children
Отражение строки SI есть nerdlihC
за строки SI есть 8
Строка S2 есть Mother
Строка S3 есть Father
Mother and Father
Father and Mother
6.9.6. Оператор преобразования типа
•В классе имеется один оператор преобразования типа, а именно
преобразование к встроенному типу string языка С (char*). Этот оператор производит
преобразование автоматически, когда это необходимо. Он может также быть
вызван явно при использовании понятия каста (см. разд. 5.5). Это
преобразование становится полезным при использовании библиотечных подпрограмм из
string.h>, таких, как strlenQ, strcpyO, strtokQ, и т.д.
"•9.7. Возможные усовершенствования
чевидно, что класс может быть улучшен и расширен. Например, можно
дожить Функции, работающие с подстроками и функции модификации строк.
аслуживают реализации также поиск вхождения и сравнение строк. Напри-
Р> вырезка подстроки, которая обеспечивает возможность обратиться к ча-
ти строки, может быть перегруженным оператором круглых скобок О. Опера-
240
Глава 6. Классы компьютерной алгебры
тор имел бы два параметра, где первый определял бы левый индекс подстп,
а второй — правый индекс. Следующий пример иллюстрирует это возмо;
расширение: с
String s("Бриллиантовое кольцо");
cout « s@,8) « endl; // вывод => Бриллиант
cout « sA5,20) << endl; // вывод => кольцо
Реализацию этого расширения оставляем читателям в качестве упражнения
6.10. Битовые наборы
Битовый набор — просто вектор, компоненты которого имеют в качестве
значений 0 или 1. Так как биты — основные величины, хранимые в компьютерах
мы в принципе можем реализовать битовые наборы на основе любого типа
данных. Здесь мы избрали для хранения битов тип unsigned char. Так как
unsigned char состоит из 8 битов, мы можем хранить в каждом символе 8
битовых значений. Другими словами, чтобы реализовать битовые наборы, мы
используем переменные из Vector (unsigned char) для хранения битовых ие-
личин.
биты 0-7 биты 8-15 биты 16-23 биты 24-31
00110101
11101011
10101100
01001011
Vector 0 Vector 1 Vector 2 Vector 3
Пользуясь структурой данных типа Vector во внутреннем представлении,
мы избегаем необходимости перестройки существующей структуры. Также мы
избавляемся от проблем, связанных с динамическим распределением памяти
и удалением. Следовательно, программисты могут сосредоточиться только на
деталях, связанных с новой структурой, и возможность внесения новых ошибок
сильно уменьшается.
6.10.1. Введение
Для того чтобы битовые наборы были полезны, они должны быть удобны в
использовании и должны обеспечивать доступ к отдельным битам. Ниже
суммируется содержание абстракции класса BitVector:
• экземпляры BitVector легко конструируются указанием требуемого
размера набора.
• sizeO возвращает размер битового набора.
• set(), clearQ, testO и flip() производят соответствующие операш111
над отдельным битом, который задан в параметрах.
• Поддерживается потоковый оператор вывода.
6.10. Битовые наборы
241
о Поля данных
б'* гт
дерЖит два поля данных —bsize и data. Поле bsize хранит размер
Кла набора, тогда как data есть вектор типа unsigned char, использу-
бй10 хранения значений битов. Здесь мы можем не задумываться о ди-
е**ь1 ском распре1 делении памяти и удалении, так как об этих операциях мы
BaNi котились в классе Vector, который был ранее сконструирован и тщательно
лротестирован.
6.10.3. Конструкторы
VI еется два возможных способа создания битовых наборов:
BitVector (unsigned int num) определяет размер num битового набора и
размещает необходимое количество векторов, чтобы разместить все биты.
unsigned char содержит 8 битов, поэтому это число есть наименьшее, чье
произведение с 8 больше или равно num. Этот конструктор инициализирует
все биты набора пулевым значением.
• BitVector (unsigned int num, unsigned int value) делает то же самое,
что и предыдущий конструктор, кроме установки начальных значений
битового набора в соответствии с битами параметра value.
• Конструктор копирования и деструктор просто вызывают код,
обеспечиваемый классом Vector.
6.10.4. Методы
Предположим, что v, и —наборы из BitVector размера N и М положительных
целых чисел, тогда
• v.size() возвращает размер битового набора, установленный
пользователем.
• v.reset(M) переопределяет размер BitVector к М и инициализирует все
биты значением 0.
• v.reset(M, flag) делает то же самое, кроме инициализации: биты
приобретают значение параметра flag @/1).
• v-set(i) устанавливает значение i-ro бита.
• v.clear (i) очищает i-й бит.
v-test(i) проверяет, установлено ли значение i-ro бита или очищено.
v-flip(i) зеркально отражает значение i-ro бита @ -> 1 и 1 -> 0).
v-unionSet(u) формирует объединение наборов v и и.
v-intersectSet (и) формирует пересечение наборов v и и.
v-differenceSet(u) формирует разность наборов v и и.
v- subset (и) проверяет,является ли и подмножеством v.
v~=u проверяет, совпадают ли v и и.
°ut << у помещает v в выходной поток.
v таких операциях, как v.subset(u), v.unionSet(u), v.intersectSet(u),
Йа *erenceSet (и) и v==u, размеры битовых наборов u, v должны быть оди-
bI- Ниже мы демонстрируем использование этих методов:
242
Глава 6. Классы компьютерной алгебры
/I sbitvec.cxx
#include <iostream.h>
#include "Bitvec.h"
void main()
{
// Установка битового набора размера=30;
// все биты устанавливаются в 1
BitVector BC0,l);
// Дублирование битового набора А
BitVector A(B) ;
// Битовые операции
A.clearE); A.clearB0); A.clearB9);
B.clearE); B.clear(lO); B.clearA2); B.clearB9);
B.flip(O); B.flip(l);
cout << "Размер битового набора А есть " << A.size() << endl;
cout << endl;
cout « " A = " « A « endl;
cout « " В = " « В « endl;
cout « " " « endl;
// Операции над наборами битов
cout << " объединение А и В = " << A.unionSet(B) << endl;
cout << " пересечение А и В = " << А.intersectSet(В) << endl;
cout << " разность А и В = " << A.differenceSet(В) << endl;
cout << endl;
cout << "Является А подмножеством В ? " « A.subset(В) << endl;
cout « " Совпадают А и В ?" « (A==B) « endl;
>
Результат
Размер битового набора А есть 30
А = 111110111111111111110111111110
В = 001110111101011111111111111110
объединение А и В = 111110111111111111111111111110
пересечение А и В = 001110111101011111110111111110
разность А и В = 110000000010100000000000000000
Является А подмножеством В ? 0
Совпадают А и В ? 0
6.11. Класс связанных списков
243
00
Закрытые методы
яВе вспомогательные функции, реализованные без доступа к ним
ЙМе10Т оттРй —bvteNumberO и mask О. Они объявлены как закрытые ме-
ffOpt*'
Number(i) возвращает номер байта, содержащего i-й бит набора. Он
• 8У ^тся целочисленным делением индекса бита i на 8, потому что каж-
П°!!йбайт состоит из 8 битов. Например, пусть i = 19. Его соответствующий
'^ячный код— 00010011. Деление на 8 эквивалентно смещению двоичного
Я8 аНаправо на 3 разряда, получаем 00000010, что соответствует 2. Таким
, азом 19-й бит расположен во 2-м байте.
sk(i) возвращает маскированное значение байта, в котором располага-
* ртся i-й бит. Напомним, что значение битов в байтах набора есть ' 1' для
/•тановленного бита и ' 0' — в противном случае. Маскированное значение
байта вычисляется по следующему алгоритму: берутся три младших
бита выбранного байта; оценивается их значение v, и сдвигается влево
число 1 на и разрядов. Например, пусть выбранный байт имеет содержимое
19 = 00010011. Три младших бита —это 011 = 3. Маскированное значение
получается сдвигом числа 1 на 3 разряда влево, образуя значение 00001000.
6.11. Класс связанных списков
6.11.1. Введение
Предположим, что мы имеем совокупность элементов, число элементов
которой не известно заранее или изменяется в широком диапазоне. В таком случае
естественно хранить данные в связанном списке. Главное здесь то, что
связанный список содержит все его элементы в цепочке. Каждый компонент цепочки
хранит только одно значение и указатель. Указатель или указывает на
следующий элемент списка, или указывает на NULL в случае последнего элемента в
цепочке.
Голова
данные
данные
данные
данные
р«с е.
1- Схематическая диаграмма связанного списка.
Возмо:
^^ жные операции на связанном списке абстрактно выглядят следую-
"^ образом;
asertion (вставка): добавление нового элемента в голову связанного
Списка.
(удаление): удаление элемента из головы связанного списка.
• IV CSSS (Д0СТУП): запрос значения первого элемента списка.
Plication (дублирование): дублирование списка, когда требуется.
244
Глава 6. Классы компьютерной алгебры
• Size (размер): проверка, не пуст ли связанный список.
• Inclusion (включение): проверка, имеется ли конкретный элемент i,
занном списке. м-
Может показаться, что операции, доступные в структуре данных связ-i
го списка, ограничены. Операции типа вставки или перемещения элемец °"
произвольное место списка отсутствуют. Позже это ограничение будет преп В
лено путем введения механизма итерации.
Структура данных связанного списка состоит из двух больших классов-
• Класс List. Этот класс содержит открытый интерфейс структуры даннк
связанного списка. Интерфейс включает конструктор и все операции, пеп
численные выше.
• Класс Link. Операции на значениях списка сформированы на верху это.
го класса. Он содержит области данных, а также и связь со следующИм
звеном.
6.11.2. Класс List
В этом классе объявлена структура данных связанного списка. Все
интерфейсы и операции определены в классе. Однако класс содержит только один
указатель на первое звено (голову списка). Значения данных списка хранятся в
классе Link. Класс реализован в виде шаблона, чтобы связанный список мог
содержать элементы различных типов данных, таких, как символы, строки или
других абстрактных типов данных.
Поля данных:
Класс List содержит только один указатель, называемый head (голова). Он
указывает на первое звено элементов экземпляров класса List.
Конструктор:
• Заданный по умолчанию конструктор создает пустой список, где указатель
head указывает на NULL.
• Конструктор копирования дублирует список, когда это необходимо.
• Деструктор удаляет данные всех элементов списка. Он вызывает метод
deleteAllNodes().
Методы:
Методы, перечисленные здесь, обеспечивают необходимый интерфейс классе
• f irst_Node() возвращает первый элемент связанного списка.
• dublicateO создает дубликат связанного списка.
• is_Empty() проверяет, не пуст ли связанный список.
• add_data(T value) добавляет value элемента данных в начало списка.
• deleteAHNodesO освобождает область памяти, занятую списком. СлеД0'
вательно, после этой операции связанный список перестает существовать
6.11. Класс связанных списков
245
lude(T value) проверяет, содержит ли список элемент данных
va First О удаляет первый элемент данных из связанного списка.
* ^ атор присваивания (=) позволяет присвоить связанный список друго-
* му списку.
друзья ^асса:
Listlterator объявлен как друг этого класса. Listlterator предлагает
„А тй способ обращаться и манипулировать данными элементов связанного
Ут!ска. Все это мы обсудим в разд. 6.11.4.
6.11.3. Класс Link
Tf ласе Link — вспомогательный класс. Он используется только, чтобы
облегчить операции класса List. Его существование не известно пользователям
структуры связанного списка. Поэтому большинство функций в классе,
включая конструктор, объявлено как private.
Поля данных:
Имеются два компонента данных в классе — data и next. Переменная data
хранит непосредственно данные элементов списка, тогда как переменная next
содержит указатель на следующий элемент списка.
Конструктор:
Конструктор Link (const T value, Link <T>* next) использует два
параметра. Первый задает значение элемента данных, а второй есть указатель на
следующий элемент.
Конструктор копирования Link(const Link<T>&) копирует поля данных
как обычно.
MeTi
оды:
• Link<T>* insert(const T val);
то — единственный метод в классе типа public. Он используется для
ставки элемента данных после текущего элемента списка.
' Link<T>* duplicate()const;
н создает дубликат всей цепочки элементов.
^ь* класса:
На закрытая (private) область данных класса.
Я,Ост\Г КлассЬ1 List и Listlterator объявлены как друзья класса Link, им
246
Глава 6. Классы компьютерной Алгебры
6.11.4. Итератор списка
Этот класс обеспечивает простой способ обращения и манипулирования
ком. Он включает выполнение цикла по элементам списка для удалени ~
извольных элементов списка и добавления элементов в произвольном ^
внутри списка. Сте
В основном идея создания итератора возникает из желания иметь
обработки каждого значения в структуре данных по очереди. Обычнг. ^
процесс выполняется с использованием цикла. Однако трудность возник
когда мы хотим иметь цикл, обеспечивающий пользователей легким достиг '
к индивидуальным значениям, в то же время скрывая внутренние детали п
лизации структур данных.
С другой стороны, создавая итератор, который отделен от фактическ й
структуры данных, мы имеем несколько преимуществ:
• Функционирование итератора непосредственно зависит от структуры
данных; отделение их вписывается в идеологию объектно-ориентированного
программирования.
• Такая реализация позволяет иметь различные стили работы итератора на
структуре. Например, при обходе бинарного дерева мы можем иметь
итераторы с прямым обходом узлов, с обратным и с обходом, вообще не
связанным с указателями элементов этой структуры.
• Наконец, такая реализация позволяет нескольким итераторам работать
одновременно на одной и той же структуре данных.
Класс Listlterator—открытый потомок абстрактного базового класса
Iterator. В базовом абстрактном классе описывается интерфейс
абстрактного итератора. Однако реализация его операций перенесена в классы-потомки
этого базового класса.
#ifndef MITERATOR.H
#define MITERATOR.H
// Iterator class
template <class T> class Iterator
{
public:
virtual int initQ =0; // Инициализация
virtual int operator !() =0; // Проверка существования
// текущего элемента
virtual T operator ()() = 0; // Возвращаем текущий
// элемент
virtual int operator ++() = 0; // Инкрементный оператор
virtual void operator = (const T) = 0: // Оператор присваивания
>;
#endif
6.11. Класс связанных списков 247
даяние
данные
>ЦС
о 2. Принципиальная схема итератора списка.
В общем случае, итератор имеет следующие пять операторов:
Initialization: оператор инициализирует итератор, устанавливая его в
начале структуры данных.
Termination: оператор проверяет, достиг ли итератор конца
обрабатываемой итератором структуры данных.
• Current Value: оператор возвращает текущее значение элемента списка,
на который указывает итератор.
• Increment: оператор переводит указатель итератора на следующий элемент
структуры данных.
. Assignment: оператор назначает новое значение текущему элементу
структуры данных.
Далее мы опишем проект класса List Iterator.
Поля данных:
Класс имеет три поля данных:
• Link<T>* current содержит указатель, который указывает на «текущий»
элемент связанного списка.
• Link<T>* previous содержит указатель, который указывает на
«предыдущий» элемент, т. е. элемент который расположен непосредственно перед
«текущим» элементом связанного списка.
• Link<T>& list содержит ссылку на связанный список. Ссылка
используется вместо указателя, потому что связанный список, обрабатываемый
итератором, будучи однажды назначенным, не должен быть изменен. Ссылка
обеспечивает такое ограничение. Кроме того, ссылка не может быть
назначена значению NULL.
деструкторы:
Нструктор LiatIterator(List<T>&) в качестве аргумента берет ссылку на
данный список. Он устанавливает соединение между итератором и
связанном списком.
Конструктор копирования Listlterator (const ListIterator<T>&) созда-
ДРугой итератор, который обращается к тому же самому списку. Итератор
^г применяться только к одному списку, но список может иметь больше
ш°го итератора.
248
Глава 6. Классы компьютерной алгебры
Методы:
a int init() устанавливает итератор на первый элемент в списке.
• Т operator () () возвращает значение текущего элемента.
• int operator ! () проверяет, достиг ли итератор конца списка или з
ния NULL. Нам«-
• int operator ++() переводит текущий указатель на следующий элсмр
• void operator = (const T value) присваивает текущему элементу чи '
ние value. ^
• delete_Current() удаляет элемент из списка.
• add_Bef ore (const T value) добавляет новый элемент к списку перед
кущим элементом.
• add_After(value T const) добавляет новый элемент к списку после тек
щего элемента.
Далее мы рассмотрим некоторые примеры из класса связанных списков
Первый пример демонстрирует основные операторы, имеющиеся в классе List
Не совсем просто напечатать содержимое связанного списка или обратиться
к произвольному элементу в списке. Однако задача становится относительно
простой с использованием итератора списка. Это применение
демонстрируется во втором примере, который создает связанный список с упорядочиванием
элементов по возрастанию.
Пример 1
В этом примере мы демонстрируем некоторые базовые операторы связанного
списка на списке чисел.
// numl.cxx
#include <iostream.h>
#include "MList.h"
void main()
{
int i;
int N=10;
List<int> A;
cout « "Связанный список А пуст ? " « A.is_Empty() « endl;
cout << endl;
for (i=0; i<N; i++)
{
A.add(i);
cout << "Первый элемент списка А есть "
« A.first_Node() « endl;
>
6.11. Класс связанных списков
c0ut « endl;
<int> B(A); // конструктор копирования
do
rout << "Первый элемент списка В есть "
« B.first_Node() « endl;
B.delete_First();
} while (!B.is_Empty());
cout « endl;
// проверка на вхождение в список
int р = 3, q = 15;
cout « р << " входит в связанный список А ? "
« A.is_Include(p) « endl;
cout « q << " входит в связанный список В ? "
« B.is_Include(q) « endl;
cout « endl;
A.deleteAllNodesQ ;
B.deleteAllNodesO ;
cout « "Связанный список А пуст ? " « A.is_Empty() « endl;
cout « "Связанный список В пуст ? " « B.is_Empty() « endl;
}
Результат
Связанный список А пуст ? 1
Первый элемент списка А есть О
Первый элемент списка А есть 1
Первый элемент списка А есть 2
Первый
Первый
Первый
Первый
Первый
ПеРвый
Первый
Первый
элемент списка А есть 3
элемент списка А есть 4
элемент списка А есть 5
элемент списка А есть 6
элемент списка А есть 7
Элемент списка А есть 8
элемент списка А есть 9
элемент списка В есть 9
Первый
Пес » Элемент списка В есть 8
ц6 ;J элемент списка В есть 7
РВый
элемент списка В есть 6
250
Глава 6. Классы компьютерной алгебры
7ервый элемент списка В есть 5
7ервый элемент списка В есть 4
7ервый элемент списка В есть 3
1ервый элемент списка В есть 2
1ервый элемент списка В есть 1
1ервый элемент списка В есть 0
3 входит в связанный список А ? 1
15 входит в связанный список В ? 0
Связанный список А пуст ? 1
Связанный список В пуст ? 1
Пример 2
В этом примере мы используем итератор списка, чтобы создать сортируемый
список. С использованием итератора списка обращение и управление списком
становится простой задачей.
// пшп2.схх
ttinclu.de <iostream.h>
^include "MList.h"
template <class T>
iroid Insert (List<T> &LL, T value)
(
ListIterator<T> LI(LL);
LI.initQ;
while (!LI && LI() < value) ++LI;
LI.add_Before(value);
roid mainO
{
List<int> A;
ListIterator<int> P(A);
Insert(A.lO);
Insert(A,1);
Insert(A,-15);
Insert(A,7);
Insert(A,12);
Insert(A,5);
Insert(A,-7);
Insert(A.O);
Insert(A,15);
6.12. Класс многочленов
251
« "Связанный список А есть" << endl;
cout
(Р initO; !P; ++P) cout « P() « " ";
for >-r •
}
**!!^няый список А есть
*Г-70 1 5 7 10 12 15
g j2. Класс многочленов
6.12.1- Введение
Поведение класса определено следующим образом:
, Это шаблонный класс, для которого тип данных коэффициентов
многочленов должен быть определен пользователем.
• Создание нового экземпляра из класса Polynomial просто.
• Доступны арифметические операторы типа +, -, *, /.
• Доступны присвоение и формы присвоения с изменением =, +=, -=, *=,
/=, •/.=■
• Доступны операторы сравнения == и !=.
• Многочлены можно возводить в целочисленную степень оператором ".
• Многочлен может использоваться как функция и может быть вычислен для
заданного значения аргумента многочлена.
• Возможны символьное дифференцирование и интегрирование многочленов.
• Обеспечивается класс одночленов для символьного представления
многочленов и представления полиномиальной переменной.
• Класс одночленов также поддерживает операторы, определенные в классе
Polynomial, чтобы обеспечить полное символьное представление
многочленов.
• Класс одночленов обеспечивает превращение коэффициента степени при
переменной многочлена и суммы одночленов в многочлен.
• Доступны операторы вывода многочленов.
Класс одночленов назван классом Polyterm. Так как многочлены суть сум-
полиномиальных термов, то для реализации многочленов имеет смысл ис-
ПйПьЗовать связанные списки.
класса
'•12.2. Шаблон
у
***<£ Polynomial — шаблонный класс, поэтому пользователь может выбирать
Данных коэффициентов многочленов исходя из своих потребностей. На-
•ФИмер
Poly]
nomial<Rational<int> >
tin использоваться для создания многочленов с рациональными коэффи-
^тами, а
252
Глава 6. Классы компьютерной алгебры
PolynomiaKPolynomial<double> >
может использоваться для создания многочленов от двух переменных с
фициентами типа double. Таким образом, шаблон класса позволяет ру., ^'
вать многочлены с произвольным числом переменных. 3°-
6.12.3. Поля данных
Переменная многочлена требуется, чтобы эффективно использовать класс п
ременная реализована как простой одночлен с необходимыми функциона
ными возможностями, чтобы создавать многочлены. В классе используют
связанные списки одночленов для формирования многочленов.
6.12.4. Конструкторы
Имеются три способа создания многочлена:
1. Определяя символьную строку из переменной многочлена (с единственной
переменной).
2. Определяя моном, который используется как символ переменной
многочлена. Это — наиболее простой способ создать многочлен, так как многочлен
может быть сформирован из специфицированного одночлена.
3. Создавая константу-многочлен из целого числа.
Имеется также конструктор копирования. Класс одночленов Polyterm
также является шаблонным классом и использует те же самые шаблонные
параметры, что и класс Polynomial, для которого он будет использоваться. В
классе мы располагаем большим количеством функциональных возможностей;
наиболее важными функциями класса Polyterm являются конструктор
Polyterm<T>(char *name)
(который создает одночлен с символьной строкой name) и математические
операторы. Вот некоторые примеры:
// Объявление многочлена pi с целыми коэффициентами и
// символьной переменной "х"
Polynomial<int> pl("x");
// Объявление многочлена р2 с целыми коэффициентами и
// символьной переменной "х"
Polyterm<int> x("x");
Polynomial<int> p2(x);
// Объявление одночлена х как полиномиальной переменной
// Объявление многочлена q с коэффициентами двойной точности и
// символьной переменной "х" и инициализация его выражением х+2х+1
Polyterm<double> x("x");
Polynomial<double> q=(x)+2.0*х+1;
6.12. Класс многочленов
253
пбъявление одночлена у как полиномиальной переменной
пбъявление многочлена г с полиномиальными коэффициентами
/' сИмвольной переменной "у" и инициализация его
II выражением q(x)*(y~2+y);
lvterm<Polynomial<double> > у("у");
?0 omial<Polynomial<double> > r=q*((y~2)+y);
6.12.5. Операторы
Я классе Polynomial замещаются следующие операторы:
-(унарный), +, -, *, /, У., ", =, +=, -=, *=, /=, '/.= , ==, ! = , ().
При использовании оператора " следует помнить, что его старшинство ниже,
чем у +, ~ и *. Большинство операторов имеет их обычный смысл, кроме ",
который возводит многочлен в целую положительную степень и (), который
используется для вычисления значения многочлена в точке. Большинство
операторов может использоваться с классом одночленов Polyterm. Сложение и
вычитание определены в Polyterm для того, чтобы создавать многочлены.
6.12.6. Операторы преобразования типов
Константа-многочлен может быть создана из целого числа с помощью
конструктора Polynomial(int).
6.12.7. Методы
Класс Polynomial имеет метод tidy (void). Его единственная задача —
удаление из многочлена членов с нулевыми коэффициентами. Это экономит ресурсы
памяти и делает многочлены более легкими для чтения.
6-12.8. Другие функции
Шаблонная функция _poly_power(T,int) используется для возведения
образца, Т в степень положительного целого числа. Функция Diff (Polynomial)
дифференцирует многочлен по ее переменной. Функция Int(Polynomial) берет
неопределенный интеграл от многочлена по ее переменной. Постоянная инте-
Рирования предполагается равной нулю.
,А*.9. Потоки
°ковый оператор вывода « сначала проверяет связанный список для мно-
лена на пустоту и, если это так, выводит нуль. Иначе оператор выводит
^Щлй одночлен из связанного списка, используя перегруженный оператор
jj 3 '>0lytermI который выводит коэффициент, переменную и экспоненту од-
I
254
Глава 6. Классы компьютерной алгебры
6.12.10. Пример
В качестве примера мы создаем многочлены с одной и несколькими пеп
ными и каждый из них дифференцируем, интегрируем и возводим в ква ^
// pexample.cpp
#include <iostream.h>
#include "poly.h"
#include "rational.h"
int main(void)
{
Polyterm<double> x("x");
Polyterm<Polynomial<double> > y("y"); // одночлен с несколькими
// переменными
Polynomial<double> p=(x)+2.0*(х~2)+7.0;
cout << "р(х)=" << р << endl;
cout « "Diff(p)=" « Diff(p) « endl;
cont « "Int(p)=" « Int(p) « endl;
cout « "p(x)=" « (p) « endl « endl;
// многочлен с несколькими переменными
// дифференцируем и интегрируем по у
Polynomial<Polynomial<double> > q=p+D.0*p)*(y):
cout « "q(y)=" « q << endl;
cout « "Ditf(q)=" « Diff(q) « endl;
cout « "Int(q)=" « Int(q) « endl;
cout « "q(y)~2=" « (q) « endl;
return 0;
>
Результаты
p(x)=x+B)x+G)
Diff(p)=C)x+D)x
Int(p)=@.25)x-4+@.666667)x+G)x
p(x)=x-6+D)x+D)x-4+A4)x+B8)x-2+D9)
q(y)=(D)x-3+(8)x-2+B8))y-2+(x~3+B)x-2+G))
Diff(q)=((8)x-3+A6)x+E6))y
Int(q)=(A.33333)x-3+B.66667)x-2+(9.33333))у-3+(х+B)х+G))У
q(y)~2=(A6)x-6+F4)x-5+F4)x-4+B24)x-3+D48)x-2+G84))y-4
+(D)x+A6)x-5+A6)x-4+E6)x-3+A12)x-2+A96))y-2
+(x-6+D)x~5+D)x~4+A4)x~3+B8)x~2+D9))
6.13. Класс множеств
255
<а Класс множеств
,-1 Введение
п„„р класса определено следующим образом:
ц0веДени
шаблонный класс, для которого тип данных элементов конечного
' ножества должен быть определен пользователем.
Гоздание нового экземпляра множества просто.
Оператор присваивания = перегружен.
Доступен оператор сравнения ==.
Операторы + и * перегружены для объединения и пересечения конечных
множеств.
Поддерживаются операции вывода множеств.
6-13.2. Шаблон класса
Класс Set реализован как шаблонный класс для того, чтобы пользователь мог
выбирать тип данных для коэффициентов полиномов и для вычислений.
Например, Set<char> может использоваться для создания множества с
рациональными коэффициентами в качестве элементов, a Set<string> —для
создания конечного множества со строками в качестве элементов.
6.13.3. Поля данных
Поля данных содержат элементы связанного списка, которым порожден класс
Set.
6.13.4. Конструкторы
Конструктор Set (const T) используется для создания списка элементов
множества. Реализованы конструктор копирования и деструктор.
6.13.5. Операторы
в классе Set замену
"toeer более высокое старшинство чем +.
"•13.6. Методы
Кл*сс Set имеет
Нтов в конечном множестве.
6-X3.7. Пото*
потокового вывода << перегружен и сначала проверяет связанный
классе Set замещены следующие операторы: +, *, =, ==. Заметим, что *
у-
^с Set имеет метод cardinality(), который определяет количество эле
)КИ
°ператор
С1,исок
он
конечного множества на пустоту и если это так, выводит «О». Иначе
т°бражает конечное множество.
256
Глава 6. Классы компьютерной алгебры
6.13.8. Пример
Следующий пример показывает простое использование класс;
// setapp.cpp
#include <iostream>
#include <string>
«include "set.h"
using namespace std;
void main(void)
{
Set<char> cl, c2, c3;
Set<char> setA('X');
Set<char> setB('Y');
Set<char> setC('Z');
Set<char> setD('Y');
cout << satA « "\n";
cl = setA + setB;
cont « "cl = " « cl « endl;
c2 = setA + setB + setC + setD;
cout « "c2 = " « c2 << endl;
c3 = cl * c2;
cout « "c3 = " << c3 << endl;
int size * c2.cardinality();
cout < "size of c2 = " « size « endl;
Set<string> si, s2;
Set<string> setSl("Willi");
Set<string> setS2("hans");
SI = setSl + setS2;
cout << "al << " « al « endl;
s2 = setSl * setS2;
cout « "s2 = " « s2 « endl;
>
Результаты
(X)
cl = (X,Y)
c2 = (X,Y,Z)
c3 = (X,Y)
6.14. Заклjo чение
257
of c2 = 3
siZ! (bans,villi)
1 - 0
Si
хека стандартных шаблонов, описанная в гл. 5, также включает класс
Set-
б j4. Заключение
пй главе представлена совокупность полезных классов, реализованных в
, для каждого класса были подробно описаны определения, поля данных,
(деструкторы, операторы и методы и т. д.
Среди классов, введенных в этой главе:
( Классы Verylong, Rational, Complex и Quaternion формируют основные
абстрактные типы данных.
• Классы Vector, Matrix, Array, Derive, и Polynomial представляют собой
верхний уровень математических структур.
• Последние (но не по значению) классы String, BitVector, List и Set — это
полезные структуры данных, которые найдут применение в последующих
главах.
Приложения, использующие эти классы, будут описаны в гл. 8.
Фактически имеется большое количество других классов, которые можно
было бы добавить к классам, описанным выше. Например, класс
многочленов со многими переменными. Другие примеры — классы алгебр Ли, конечных
групп и полей Галуа и т. д.
В гл. 9 мы введем полиморфный связанный список, который способен к
сохранению различных типов данных (базовых типов и абстрактных) в едином
связанном списке. Этот специальный связанный список формирует основную
структуру данных для языка программирования Лисп.
Глава 7
Символьный класс
Системы компьютерной алгебры, выполняющие символьную обработку, д0Кй
зали свою полезность во многих отношениях и стали необходимыми инстпу
ментами в научно-исследовательских вычислениях. Однако большинство до
ступных программных систем — независимые системы, и передача
математических выражений из них в иные среды программирования, такие, как С,
довольно утомительна, отнимает время и является дополнительным источником
ошибок. Следовательно, полезно использовать язык высокого уровня, который
предоставляет все необходимые инструментальные средства для комфортного
выполнения подобных задач. Этим и займемся в данной главе.
В следующих нескольких разделах мы создадим, последовательно
используя объектно-ориентированную технику, систему компьютерной алгебры
SymbolicC++. Эта система может использоваться во многих областях научных
исследований. Мы опишем структуры, функции и особенности реализации
системы. Для демонстрации использования функций мы также включим
множество примеров.
Система компьютерной алгебры строится на концепции классов в объектно-
ориентированном программировании. Следовательно, она обладает всеми
преимуществами и гибкостью объектно-ориентированного программирования.
Более важные плюсы объектно-ориентированного программирования -
модульность, возможность повторного использования кода и расширяемость. Это
особенно важно для систем компьютерной алгебры, поскольку для математики
характерно построение изучаемых объектов по уровням. Например,
комплексные числа строятся из вещественных чисел, рациональные числа создаются из
целых и т. д. С этой точки зрения математическая иерархия адекватна
философии объектно-ориентированного программирования. По-видимому, это прям
указывает на то, что объектно-ориентированное программирование являете
естественным выбором для построения системы компьютерной алгебры. С др>
гой стороны, возможность повторного использования и расширяемость игра'0
решающую роль для хорошего кодирования, что обеспечивает большую ж"8^
честь системы.
Программы из этой главы можно найти в MSymbol.h. Этот заголовочнь
файл использует Mall.h и MList.h. Файл Mall.h содержит классы оснен'111''
структур данных, такие, как Term, Magnitude, Function и т. п. MList .h. - ll"
занный список структур данных, которые были описаны в гл. 6. Связанный и1
сок используется для хранения подчиненного списка символьных перемен1"'1
1. Объектно-ориентированное проектирование 259
Объектно-ориентированное проектирование
7.1*
чала определим, что представляет собой выражение. Имеется много
Д"* определения выражения. Здесь мы рассматриваем его как рекурсивную
йУ* у данных, которые определяются в терминах самой себя. Считая, что
&W ажении можно использовать только знаки +, -, *, /и круглые скобки,
ВВ можно определять по следующим правилам:
выражение ->• терм [+ терм] [- терм]
терм —» множитель [* множитель] [/ множитель]
множитель —» переменная, число или (выражение).
й квадратных скобках помещен необязательный элемент, а —► заменяет собой
яово «это». Эти правила обычно называются порождающими правилами вы-
паясения. Базируясь на этих правилах, мы сможем создать классы, которые,
тесно взаимодействуя друг с другом, позволят представить математические
выражения в простой и ясной форме.
7.1.1. Дерево выражения
В обсуждаемой здесь символьной системе выражение организовано в виде
дерева. Каждый узел в дереве может представлять переменную, число, функцию
или корень поддерева. Поддерево может представлять терм или выражение.
Есть два типа узла—Sum и Product. Узел Sum используется для
представления переменной, числа, функции или корня дерева/поддерева. Узел Product
используется для представления терма, который образован произведением или
частным двух элементов. Это представление соответствует порождающим
правилам, упомянутым выше.
Sum
Symbol
Product
Uoa класса Sum и Product являются потомками класса Symbol. Они, следо-
Льно, наследуют свойства родительского класса, который описывает сим-
ьные величины. Тем не менее все алгебраические вычисления производятся
°Ьъектах из Sum и Product, которые взаимосвязаны между собой.
ласе Sum является более важным классом, так как он определяет символь-
переменную, а также число как таковое. Объекты из Sum складываются
Ремножаются между собой в процессе формирования выражения, кото-
0п Также представлено в класс Sum. Все операторы, функции и интерфейсы
. Деляются в этом же классе. В классе Sum также определены специальные
•ош ^*и' подобные sin(x) и cos(x), которые будут описаны подробнее в следу-
^Их Разделах.
»•
260
Глава 7. Символьный класс
Составной объект
Объект а типа " Sum" Объект b типа " Sum" Объект с типа " Sum"
Элементарные корневые объекты
Рис. 7.1. Схематическая диаграмма выражения у = (а+Ь)*(а+с).
С другой стороны, класс Product описывает термы, которые
перемножаются или делятся. Это — вспомогательный класс, и его существование не известно
пользователям.
Как пример, рассмотрим выражение
у = (а+Ь)*(а+с).
Объект у имеет структуру, показанную на рис. 7.1. Обратите внимание, чт0
символьные переменные а,Ь и с представлены узлами Sum. Они являются
листьями (концевыми вершинами) дерева выражения. Выражение а+b составлено
из других узлов Sum, которые указывают на переменные а и Ь. Выражение а+с
создано аналогично. Узел Product используется, чтобы перемножить (а+Ь) "
(а+с), на него в свою очередь указывает другой узел Sum, чтобы сформировать
дерево как выражение. Узел у типа Sum указывает на конечное выражение-
7.1.2. Полиморфизм дерева выражения
Вспомним, что узел Sum может содержать также переменную, число или спец
альную функцию. Это достигается использованием наследования и полиМ
физма объектно-ориентированной технологии.
Наследование обладает способностью создавать новые типы, импортер•
или многократно используя описания существующих типов. ПолиморФ1
7.1. Объектно-ориентированное проектирование
261
Sum
Magnitude
Variable
Terms
Number
exp()
Function
-7V
sin()
cos()
Ряс 7-2- Схематичная диаграмма узла Sum, который содержит указатель Terms и
gro потомков.
вместе с динамическим связыванием позволяет функциям, доступным для
базового типа, работать с производными типами. Такая функция может иметь
другие реализации, которые вызываются динамически при определении
производных типов во время исполнения.
В нашем случае узел Sum содержит указатель на класс, называемый Terms.
У класса Terms есть два потомка — классы Magnitude и Function. В свою
очередь, Magnitude имеет двух потомков — классы Variable и Number, a Function
имеет множество классов-потомков. Каждый потомок класса Function
соответствует специальной функции. При необходимости могут быть добавлены
другие функции (см. рис. 7.2). Используя виртуальные функции, свойства и
поведение классов-потомков можно определить во время исполнения.
Класс Terms является абстрактным базовым классом, как и его потомки,
классы Magnitude и Function:
template <class T>
class Terms
public:
virtual "Terms();
virtual char typeQ const = 0;
virtual String varNameQ const = 0;
у virtual void oprint(ostreamfc) const = 0;
И1 <Class T>
{ ass Magnitude : public Terms<T>
Рчъцс:
virtUai
virt
Magnitude();
u*l T val() const
}. lrtUal void set(const T)
0;
0;
262
Глава 7. Символьный класс
template <class T>
class Function : public Terms<T>
i
public:
virtual "Function();
virtual double f(const T&) const = 0;
};
Методы в классах Terms, Magnitude и Function— чистые виртуальные tf»
ции. Любой класс, который объявляет или наследует чистые виртуальн
функции, является абстрактным базовым классом. Попытка создать объе
абстрактного базового класса вызовет ошибку во время трансляции. Слевдн
тельно, все чистые виртуальные функции должны быть замещены в объявляр
мом классе-потомке. Абстрактный базовый класс используется для объявлени
интерфейса без описания всего множества реализаций для этого интерфейса
Этот интерфейс определяет абстрактные операции, которые поддерживаются
всеми объектами класса; он же распространяется и на классы-потомки в целях
обеспечения реализации этих абстрактных операций.
Интерфейсы, объявленные классом Terms, включают методы:
в type() возвращает информацию о типе класса, независимо от того, будут
ли это классы Variable, Number или классы Function разных типов.
• varNameQ возвращает имя переменной из Variable или имя функции.
Объекты из Number не имеют имени переменной.
• oprint(ostream& os) отправляет имя Variable, числовое значение Number
или имя функции в выходной поток os.
В классе Magnitude объявлены следующие интерфейсы:
в val() возвращает числовое значение Variable или Number.
• set (const T num) присваивает числовое значение num значению Variable
или Number.
Класс Function имеет только один дополнительный интерфейс f (const Tic a),
который возвращает численное значение функции, вычисленной в а.
Рассмотрим теперь классы-потомки каждый в отдельности:
1. Класс Variable: как подсказывает имя, он содержит информацию о сим
вольной переменной и соответствующее числовое значение. В классе ес
два поля данных name и value. Переменная name хранит символьное f'_
Variable, a value хранит соответствующее числовое значение переменно
Конструктор читает в переменной имя и значение. Методы замещаю? '
ответствующие методы базового класса:
в type () возвращает символ ' V, указывающий, что объект принадле*
классу Variable,
в VarNameQ возвращает name, символьное имя объекта.
• oprint() отправляет name в выходной поток.
7.1. Объектно-ориентированное проектирование
263
valO возвращает value, числовое значение объекта.
set (const T num) назначает числовую константу num в качестве
значения поля field.
Класс Number определяет шаблон для всех доступных встроенных или
пользовательских типов данных. Например,
Number<double>
объявляет определяемый пользователем тип double, который обладает
всеми свойствами встроенного типа double. Он также обладает
некоторыми дополнительными свойствами, которые полезны для нашей символьной
системы и будут обсуждены ниже.
Класс содержит только одно поле данных
Т data .
Оно хранит значение типа Т. Его числовое значение доступно конструктору
через его аргумент. Методы замещают соответствующие методы базового
класса:
• type () возвращает символ 'N', указывающий, что объект принадлежит
классу Number.
в varName () возвращает NULL-строку потому, что у объекта нет
определения в классе с однозначной интерпретацией.
• oprintQ отправляет data в выходной поток.
• val() возвращает data, числовое значение объекта класса.
• set (const T num) назначает числовую константу num полю data.
Дополнительно к методам интерфейса абстрактного базового класса
класс имеет два дополнительных метода:
• Оператор присваивания = обеспечивает нас корректным способом
присвоения значений экземплярам Number.
• Оператор преобразования к типу Т — важная функция. Это — шлюз ко
всем свойствам, которыми обладают данные типа Т. Он конвертирует
экземпляры Number в соответствующий тип Т, когда это необходимо, тем
самым позволяя использовать все функции, работающие с аргументами
типа Т, с аргументами из класса Number.
"■ Классы-потомки класса Function описывают специальные функции
ехр(х), sin(x), cos(x), sinh(x), cosh(x), sqrt(x)
■Х-Отя многие функции могут быть включены в этот список, все они должны
обладать некоторыми общими свойствами. Каждый класс содержит только
°ДНо свойство Fname, которое используется для загрузки имени функции.
"Ни также замещают все интерфейсы, объявленные в их предках:
• type() возвращает символ 'е', чтобы указать, что объект есть
экспоненциальная функция ехр(). Для индикации других функций
возвращаются другие символы. Отметим, что в соответствии с
соглашениями, принятыми в системе, возвращаемые символы являются символами
Нижнего регистра.
е varName () возвращает поле Fname, содержащее имя функции.
264
Глава 7. Символьный класс
• oprintO отправляет Fname в выходной поток.
• f (const T& х) возвращает числовую величину функции. Отметим
тип возвращаемого значения функции есть double, так как мы цСги г°
зуем встроенные функции из библиотеки <math.h>. " ь~
7.2. Поля данных и типы класса Symbol
Напомним, что Sum является открытым классом-потомком класса Syn^
Далее, Symbol является также абстрактным базовым классом. Он содер>Ки
много чистых виртуальных функций, которые могут быть замещены в е
классах-потомках. Ниже мы опишем, как взаимодействуют между собой клас
сы Symbol, Sum и Product в дереве выражения при формировании символьной
системы.
Класс Symbol имеет четыре поля данных:
• branches хранит количество ветвей, исходящих из этого узла.
• Symbol<T> **ер — указатель на указатель на SymboKT>. Эта переменная
после соответствующего распределения памяти, будет содержать массив
указателей на Symbol<T>. Размер массива равен branches, указатели же
показывают на непосредственных потомков этого узла.
• Т *fac_exp — указатель на Т. После размещения в памяти массива
указателей размера branches он хранит множитель для узла Sum или показателя
степени для узла Product.
• int *next_var— указатель на int. Он указывает на массив размера
branches. Массив содержит только 0 или 1, и используется для
указания наличия потомков узла с типом 'V. Эта информация необходима для
деструктора класса.
Существует шесть разных типов узлов в дереве выражения. Пять из них
представлены узлом Sum и один — узлом Product. При обращении к функции type()
возвращаются различные символы для индикации типа узла дерева:
• 'S' —корень дерева выражения или поддерева. Например, а+b или а+Ъ*с.
Он не имеет имени переменной.
е 'V' —узел Variable, например а,Ь. Обычно это лист дерева выражения.
Каждый узел Variable имеет имя, связанное с ним.
• 'N' —узел Number, например 2.0, 4/5. Это —также лист дерева
выражения.
• 'В' — «связанный» узел означающий, что это Variable, но он не является
листом дерева выражения. Например, для выражения у=а+Ь, у —
«связанный» узел.
е > р > — узел Product, который используется для представления произведет^
двух термов или выражений, например а*Ь. Он всегда следует за узлом Sum-
• Последний тип Sum соответствует специальным функциям; при этом разны>'
возвращаемым символам соответствуют разные функции. Например. Дл>1
узла функции ехр(х) возвращается 'е', для sin(x) возвращается 'т.' и ДлЯ
cos(x) возвращается 'с'. Возвращаемые символы для этих специа-'п>11Ь1Х
функций всегда являются символами нижнего регистра.
7.2. Поля данных и типы класса Symbol
265
Ряс. 7.3. Схематичная диаграмма выражения у = а*Ь + 1 + sin(x).
Рассмотрим выражение
У = а*Ь + 1 + sin(x)
и соответствующее дерево выражения на рис. 7.3. Заметим, что в этом
выражении использованы все шесть типов узлов: у— 'В'-тип; а, Ь, х— 'V'-тип;
>1'—'N'-тип, 'sin' является 'Function'-типом, а 'Р' и 'S'-типы
использованы в качестве промежуточных соединительных узлов для различных целей.
Другой пример дан на рис. 7.4. Выражение имеет вид
z = 3*а~2*Ь - 5 + 6*cos(x+c)
Отметим, что выражения, содержащие лишь +, -, *, /и круглые скобки,
можно однозначно представить деревом выражения. Маленькие
прямоугольники при узлах 'S' и 'Р' на диаграмме — это поля fac_exp, которые
представляют собой множители (при узлах-потомках узла типа 'S') или показатели
степени (при узлах-потомках узла типа 'Р') соответственно. Узел '1'
является специальным узлом, который мы обсудим позже. Выражение, которое
следует за специальной функцией, является аргументом функции.
z ► 'S'
Ж
«pi
► 'S'
С- 1 Л. Схематичная диаграмма z = 3*а*Ь - 5 + 6*cos(x+c).
266
Глава 7. Символьный класс
7.3. Конструкторы
У нас есть базовый класс Symbol и два его потомка—классы Sum и Prod
Для символьной системы необходимо, чтобы они работали в тесном взаимо -
ствии. Так как логика программы может требовать перехода из одного клас
другой в соответствии с природой структур данных, мы описываем здесь к &
структоры для всех трех классов вместе раньше, чем опишем все интерфе,ч
каждого класса один за другим. Мы начнем с описания роли конструктоп
базового класса. Основное назначение конструкторов заключается в инициал.,
зации полей данных в классе вместе с размещением в памяти соответствующи
указателей. Этот шаг важен для обеспечения корректного функционировани
классов.
• SymboK): Это — конструктор по умолчанию для класса Symbol.
Поскольку Symbol — абстрактный базовый класс, то не предполагается обращения
к этому конструктору для создания некоего экземпляра из этого класса в
любое время. Мы включили сообщение об ошибке в тело функции.
Компилятор, как правило, должен обнаруживать эту ошибку.
• Symbol(int n): Этот конструктор создает узел с п ветвями. Он выделяет
место в памяти для полей ер, fac_exp, next_var и инициализирует их.
• Symbol(int n,T *v,int *w) выполняет функцию, аналогичную
предшествующему конструктору, за исключением того, что он инициализирует
f ас_ехр значением v и next_var значением w.
• Symbol (int n,Symbol<T> **s,T *v,int *w) создает символьный узел и
инициализирует поля ер значением s, fac_exp значением v и next_var
значением w.
• Деструктор просто возвращает неиспользованную память обратно в пул
памяти.
Все конструкторы в классе объявлены как protected. Они видны и
доступны только внутренним функциям и классам-потомкам, но не пользователям
или функциям за пределами класса.
Рассмотрим конструкторы класса Sum. Данный класс имеет поле данных
Terms<T>*data и является открытым потомком класса Symbol. Конструктор
Sum всегда сначала вызывает конструктор класса Symbol, затем
инициализирует поле data и производит некоторые другие операции:
• Конструктор по умолчанию объявляет узел «нуль». Узлы «нуль» и
«один» —два специальных узла в классах. Они имеют следующую струк"
туру.
'S'
1
'0'
'S' ► '1»
7.3. Конструкторы
267
Число п. отличное от «один» или «нуль», представляется простым
укаем значения п в поле f ас_ехр узла Sum в качестве множителя, как по-
казано на следующей схеме:
(с
о
П
'V
Sum (const Т п) создает числовой узел с числовым значением, равным п,
как показано выше.
Sum(const T n,char) создает лист (концевую вершину) типа 'N' с
числовым значением, равным п. Это закрытый конструктор, который
используется внутри класса.
» Sum (char ftype, char) создает узел типа 'Function', соответствующий
функции, заданной символом ftype в вызывающем операторе. Например,
если ftype = 'e', то создается узел функции ехр(). Это также закрытый
конструктор.
• Sum (int n.int) создает узел Sum, который имеет п ветвей.
• Sum (String name,int) используется, чтобы объявить символьную
переменную name.
• Sum (int i,Symbol<T> **newep,T *new_fe,int *new_nx,String run)
создает узел Sum со всеми полями данных и структурами базового класса,
проинициаличированными соответствующими значениями аргументов.
• Конструктор копирования дублирует узел Sum и дерево выражения,
«растущего» из этого узла.
• Деструктор освобождает память, которая больше не нужна, обратно в пул
свободной памяти.
Конструкторы класса Product заметно проще, поскольку Product имеет
юлько одно поле данных Comm, которое является индикатором перестановоч-
шсти оператора умножения *.
• Product (int n) создает узел Product с п ветвями.
• Product (int i, Symbol<T> **newep, T *new_fe, int *new_nx) создает
узел Product со структурой базового класса, проинициализированного в
соответствии со спецификациями аргументов.
• Конструктор копирования и деструктор для Product тривиальны.
• Поле данных Comm объявлено как static. Следовательно, оно относится ко
всем экземплярам класса и записано в фиксированном месте. Поскольку
статическое свойство не зависит от любого конкретного экземпляра, поле
Доступно в форме Product<T>: : Comm. По умолчанию мы предполагаем
нашу систему компьютерной алгебры коммутативной, и это инициализируется
следующим утверждением:
Template <class T> int Product<T>::Comm = 1;
гДе 1 означает коммутативность системы и 0 означает обратное.
268
Глава 7. Символьный класс
7.4. Операторы
В этом разделе мы опишем арифметические и математические оператор
ступные в системе: ' До.
(унарный)+, (унарный)-, +, -, * /, =, +=
power(), df(), Int(), expandO, coeff()
типа
или
=, *=, /=, ==, !=
Пусть х, у — символьные переменные типа Sum и с — числовая константа
Т, тогда операторы совершают следующее:
• х+у, х+с, с+х — складываются символьные переменные, выражения
числовые константы.
• х-у, х-с, с-х — вычитаются символьные переменные, выражения или чи
ловые константы.
• х*у, х*с, с*х —умножаются символьные переменные, выражения или
числовые константы.
• х/у, х/с, с/х —делятся символьные переменные, выражения или
числовые константы.
• х=у присваивает символьную переменную/выражение у переменной х.
• х=с присваивает числовую константу с переменной х.
• +=, -=, *= и/=— лишь модифицированные формы оператора
присваивания.
• ==, ! = — проверка на равенство двух выражений. Например, х+у==у+х
возвращает true (истина), а х!=х возвращает false (ложь).
Результат в действительности зависит также от коммутативности
оператора *. Например, a*b==b*a возвратит true, если система коммутативна,
в противном случае возвращается false.
• power (const Sum<T> fts.int n) возводит выражение s в степень п.
Например, y=power(x,5) имеет структуру, показанную ниже.
В особом случае нулевой степени (например, y=power(x,0)) функция
просто возвращает постоянную величину ' 1'.
У ► 'S' ► 'Р' ► х
15
• power(Т х,Т п) вычисляет х", используя алгоритм, рассмотренный в
разд. 6.1. Отметим, что функция работает только для целочисленных
степеней. Термы типа хх не могут быть выражены с использованием функШ111
power (). Альтернативный способ выразить такой терм —это использовать
экспоненциальную функцию:
хх = ехр(х *ln(z)).
• df (const Sum<T>& y, const Sum<T>& x) вычисляет частную производим0 У
по х. Вычисление df (у,х) производится следующим образом:
7.4. Операторы
269
Положим, у выражается термами от х, тогда каждый терм у, содер-
жащий переменную х, дифференцируется согласно стандартным
правилам.
Если z — другая переменная, то ее производная по х есть нуль, если z не
объявлена ранее как зависимая от х; в таком случае производная есть
df(z,x),
Рассмотрим многочлен Р степени п,
Р{х) = anxn + an-iX*1'1 +■■■ + akxk + ■■■ + a2x2 + arx + a0.
Так как производная одночлена akхк есть ка,кХк~1, мы получаем, используя
diff О в функции df ():
^L = nanxn-1 +{n- l)an_ia:n_2 + • ■ • + ka + tar*-1 + • ■ • + 2a2x + сц.
dx
По ходу дела мы использовали различные правила дифференцирования.
функция Sum<T>: : diff () манипулирует выражениями в соответствии
с правилами, перечисленными ниже:
dx , dc-
1. — = 1, — = О, где с —числовая константа.
dx dx
( ({У
"V I —=—, если у зависит от х,
2- У~ = \ Aх
dx I ,,
(^ 0 в противном случае.
3. Оператор дифференцирования — линейный оператор, т.е.
d . ч1 du(x) dv(x)
— [а * и(х) + b* v(x)\ = a * — ho* ,
dx dx dx
где a, b — числовые константы, х — независимая переменная и и(х),
v(x) — функции х.
4. Цепное-правило:
d а ( \\ df du
dxJy K " du dx
где / зависит от и, которое зависит от х.
5. Интегрирование является обращением дифференцирования
/
d
—y(x)dx = у(х).
~~ В Product<T>: :diff () реализуется правило дифференцирования
произведения
d dui du2
— (ui(x) * u2{x)) - — * u2 + щ * ——
и его следствие — правило дифференцирования для одночленов
— (а * (и(х))п) =п*а* и"-1 * ( —
dx \dx
где а — константа.
270
Глава 7. Символьный класс
При дифференцировании нужно знать о наличии зависимости ц,р,
ду различными переменными. Такая зависимость объявляется функц
dependO. Например, y.depend(x); y.depend(z); объявляют, что v ч-
сит от х и z. После того как такие объявления сделаны, в операторе df (у
должны производиться вычисления, заложенные в нем (и зависящие от'
да зависимости у от х); при отсутствии этих объявлении в качестве резу
тата выдается 0.
Если по какой-либо причине зависимость необходимо удалить, м0)ь.
но воспользоваться функцией nodepend(). Например, в нашем слуЧа
y.nodepend(x) отменяет зависимость у от х (хотя при этом у остается за
висимым от z).
Иногда нам нужна информация о наличии зависимости от различныу
переменных. Это можно проверить, используя isdependQ. Например, ддя
вышеуказанных зависимостей y.isdepend(z) возвращает true, тогда как
y.isdepend(x) возвращает false.
Вся информация о зависимостях между переменными хранится с
использованием связанного списка MList.h. Отметим, что выбрана
структура связанного списка, поскольку разные символьные переменные имеют
различное количество свободных переменных. И поэтому в такой ситуации
трудно заранее зарезервировать необходимый объем памяти.
df (const Sum<T>& у, const Sum<T>& x.int n) вычисляет п-ю
производную у по х.
Int (const Sum<T> &y, const Sum<T> &x) вычисляет простые интегралы от
у по переменной х, пренебрегая константами интегрирования.
Рассмотрим многочлен Р степени п
Р(х) = anxn + an-ixn~l -I + akxk + ■ ■ ■ + аъх2 + ахх + а0.
Так как интеграл от одночлена a,kxk есть a,kXk+1 /(к + 1), мы получаем,
используя integrate() в функции Int():
/
'тг + 1 п к + 1 3 2
В процессе вычислений мы использовали различные правила
интегрирования.
— Функция Sum<T>: : integrate() манипулирует выражениями в
соответствии со следующим списком правил:
1. Для числовой константы с и независимой переменной х
/cdx = с* х, I xdx = —.
J 2
2.
J l У*х
если у зависит от х ,
в противном случае.
7.4. Операторы
271
3- Оператор интегрирования — линейный оператор, т.е.
[а, * b(x) + b* v(x)}dx — а * / u{x)dx + b* I v(x)dx,
где a. b числовые константы, х — независимая переменная и и(х),
v{x) функции х.
4. Интегрирование является обратной операцией по отношению к
дифференцированию:
/
—y(x)dx = у(х).
Функция Product<T>: :integrate() реализует правила
интегрирования для одночленов
!ТТ*П+1 пф-\,
1п(ж) п = —1,
где а - константа.
/ a*xndx = < ,n+r
При интегрировании иногда нужно знать о наличии зависимости
между различными неременными. Такая зависимость объявляется функцией
depend(). Например, y.depend(x); объявляет, что у зависит от х. После
того как такое объявление сделано, Int(y,x) должен произвести
вычисления, предусмотренные в нем, тогда как до объявления он выдал бы в
качестве результата у*х. На обсуждаемые здесь вопросы распространяется
и описание df().
• Int(const Sum<T> &y, const Sum<T> &x, int n) вычисляет п-й интеграл
у по х.
• expand() разворачивает произведение выражений, что делает возможным
возведение выражений в степень п. Он реализуется композицией двух
важных функций -dxpandQ и mxpandQ. dxpandQ разворачивает
выражения, используя дистрибутивный закон, тогда как mxpandQ использует
формулу бинома Ньютона, чтобы выполнить раскрытие бинома
(о + Ь)" =" С'оо" +п Cian~lb +n С2ап-2Ь2 + •••+" Cn-iabn~l +" СпЪп.
Для раскрытия многочленного выражения, когда выражение является
суммой более чем двух членов, мы расширим формулу бинома, группируя
члены в два множества членов. Однако нам нужно решить, как их разделить
на части « и Ь. Есть два очевидных пути: разделить выражение пополам,
Чтобы а и b были равных размеров, или же отделять один терм за один
Прием. Можно показать, что в большинстве случаев последний метод
более эффективен. Другими словами, выражение ti + t2 + £3 + ■ ■ ■ + tk будет
Рассмотрено как сумма а + Ь, где а = t\ и b = £2 + £3 + • ■ • + tk, т. е.
(*i + h + £3 + ■ • ■ + tk)n = [t1(t2+t3 + --- + tk)}n
И применено разложение бинома.
272
Глава 7. Символьный класс
coeff () возвращает символьные или числовые коэффициенты выраз,к >
Он перегружается в трех различных формах: l,S|.
— Sum<T> coeff(const Sum<T>& s) const;
возвращает коэффициент при терме s в выражении.
— Т coeff(int) const;
возвращается постоянный терм выражения.
— Sum<T> coeff(const Sum<T>& s, int n) const;
возвращает коэффициент при терме sn в выражении.
put (const Sum<T>& si, const Sum<T>& s2) заменяет все вхождения т
ма si в выражении на другой терм s2. Функция возвращает ненулевое
лое, если замена прошла успешно, в противном случае возвращается hvj-
Функция сначала проверяет каждый терм выражения. Если встречает
терм si, он заменяется на s2. Это полезная функция, которая помогае
упрощать выражения. Например, такое выражение из смеси sin2 x и cos2?
У
10 * sin2 х + 5 * cos2 x
можно упростить до
5 * sin2 х + 5,
заменяя все вхождения терма cos2 х на 1 — sin x и объединяя подобные
члены.
Пример 1
В этом примере рассматривается функция power ().
// expand.cxx
#include <iostream.h>
«include "MSymbol.h"
void main()
{
Sum<int> a("a",0), b("b",0), с("с",0), у, z;
у = powerG,3); cout « " у = " « у « endl;
у = power(a.O); cout « " у = " « у « endl;
у = power(a,3); cout « " у = " « у « endl;
cout << endl;
у = power(a+b-c,3); cout « " у = " « у « endl;
cout << endl;
у = (a+b)*(a-c); cout « " у = " « у « endl;
cout « endl;
У = a+b;
z = power(y,4); cout « " z = " « z « endl;
}
7.4. Операторы
273
_ а"О)+3*а"B)*Ь-3*а~B)*с+3*а*Ь~B)-6*a*b*c+3*a*c~B)+Ь~C)
У " _з*Ь~B)*с+3*Ь*с"B)-с~C)
_ a-B)-a*c+b*a-b*c
= a-D)+4*a"C)*b+6*a~B)*b"B)+4*a*b-C)+b"D)
Пример 2
R этом примере мы рассматриваем дифференцирование функции
у(х) = \- 2х3 - z
1 — х
по переменной х. Отметим, что
dx
поскольку система не предполагает никакой зависимости для любых двух
переменных, если это не определено явным образом. Во второй части этого примера
мы рассматриваем дифференцирование выражения
U(„) = I„3/5 _ 2 1/5 1
w 3 7 6
по переменной v. Отметим, что коэффициенты и степени переменной v —
рациональные числа.
// derivatv.cxx
«include <iostream.h>
«include "Rational.h"
«include "MSymbol.h"
void main()
int i;
Sum<int> x("x",0), z("z",0), у;
У = l/(l-x) + 2*x*x*x - z;
cout « »y = " « у « endl;
for (i=o; i<8; i++)
274
Глава 7. Символьный класс
у = df(у,х);
cout << "у = " « у « endl;
}
cout << endl;
Sum<Rational<int> > v("v",0), u;
u = Rational<int>(l,3) * power(v,Rational<int>C,5))
- Rational<int>B,7) * power(v,Rational<int>A,5))
+ Rational<int>(l,6);
cout « "u = " « u << endl;
for (i=0; i<8; i++)
i
u = df(u,v);
cout << "u = " « u « endl;
}
}
Результат
у = A-х)"(-l)+2*x*C)-z
у = (l-x)~(-2)+6*x~B)
у = 2*A-х)~(-3)+12*х
у = 6*A-х)"(-4)+12
у = 24*A-х)~(-5)
у = 120*A-хГ(-6)
у = 720*A-хГ(-7)
у = 5040*A-х)~(-8)
у = 40320*A-х)'(-9)
u = l/3*v~C/5)-2/7*v-(l/5)+l/6
u = l/5*v~(-2/5)-2/35*v~(-4/5)
u = -2/25*v-(-7/5)+8/175*v"(-9/5)
u = 14/125*v-(-12/5)-72/875*v~(-14/5)
u = -168/625*v~(-17/5)+144/625*v~(-19/5)
u = 2856/3125*v~(-22/5)-2736/3125*v~(-24/5)
u = -62832/15625*v~(-27/5)+65664/15625*v-(-29/5)
u = 1696464/78125*v'-(-32/5)-1904256/78125*v~(-34/5)
u = -54286848/390625*v~(-37/5)+64744704/390625*v~(-39/5)
Пример 3
В этом примере мы исследуем свойства оператора df (). По умолчанк
две переменные считаются независимыми друг от друга. Зависимое
создаваться функцией depend() и удаляться функцией nodependO.
7.4. Операторы
/f depend-cxx
.„elude <iostream.h>
J-nclude "MSymbol.h"
void main О
{ Sum<int> a("a",0), b("b",0), u(V,0), v("v",0),
x("x",0), z("z",0), y("y",0);
cout << "По умолчанию переменные предполагаются независимыми
« endl;
cout « "df(y,x) => " « df(y,x) « endl;
cout « "у зависит от х" « endl;
y. depend(x);
cout « "df(y,x) => " « df(y,x) « endl;
у = sin(x*x+5) + x; cout « "y = " « у « endl;
cout « "df(y,x) => " « df(y,x) « endl;
cout « endl;
cout << "u зависит от х" << endl;
u. depend(x);
cout << "df(cos(u),x) => " « df(cos(u),x) « endl;
cout << "u зависит от х, их зависит от v" << endl;
x.depend(v);
cout « "df(cos(u),v) => " « df(cos(u),v) « endl;
// пример
У = cos(x*x+5)-2*sin(a*z+b*x); cout « "y = " « у « endl;
cout « "df(y,v) => " « df(y,v) « endl;
cout « endl;
cout << "проверка зависимости" << endl;
cout « "y.isdepend(x) -> " « у.isdepend(x) « endl;
cout << "y.isdepend(v) -> " « у.isdepend(v) « endl;
cout « "y.isdepend(z) -> " « у.isdepend(z) « endl;
Cout « endl;
cout << "удаление зависимости" « endl;
x-nodepend(v);
c°ut « "df(y,v) => " « df(y,v) « endl;
c°ut « endl;
cout << "производная константы есть нуль" « endl;
276
Глава 7. Символьный класс
cout « "dfE,x) => " « dfE,x) « endl;
cout << endl;
// переопределение переменной у
y.clearO;
y.depend(x); y.depend(z);
cout « "df(y,x)+df(y,x) => " « df(y,x)+df(y,x) « endl;
cout « "df(y,x)*df(y,x) => " « df(y,x)*df(y,x) « endl;
cout << endl;
cout << "Другой пример," << endl;
x. clear (); y.clearO; u.clear();
y.depend(u); u.depend(v); v.depend(x);
у = df(u,x)*sin(x);
cout « "y = " « у « endl;
u = 2*v*x;
cout << "пусть u = " << u << " тогда," << endl;
cout « "y => " « у « endl;
cout « "df(y,x) => " « df(y,x) « endl;
}
Результаты
По умолчанию переменные предполагаются независимыми
df(y,x) => О
у зависит от х
df(y,x) => df(y,x)
у = sin(x~B)+5)+x
df(y,x) => 2*x*cos(x~B)+5)+l
u зависит от х
df(cos(u),x) => -df(u,x)*sin(u)
u зависит от x, их зависит от v
df(cos(u),v) => -df(u,v)*sin(u)
у = cos(x"B)+5)-2*sin(a*z+b*x)
df(y,v) => -2*x*df(x,v)*sin(x~B)+5)-2*b*df(x,v)*cos(a*z+b*x)
проверка зависимости
у.isdepend(x) -> 1
y.isdepend(v) -> 1
у.isdepend(z) -> 0
удаление зависимости
df(y,v) => О
7.4. Операторы
277
•эводная константы есть нуль
Кь х) => О
, x)+df(y,x) => 2*df(y,x)
J(y!x)*df(y,x) => df(y,x)-B)
другой пример,
s df(u,x)*sin(x)
LcTb u = 2*v*x тогда,
_> B*df(v,x)*x+2*v)*sin(x)
df(y>x) => B*df(df(v>x)>x)*x+4*df(v,x))*sin(x)
+ B*df(v,x)*x+2*v)*cos(x)
Последующие примеры 4, 5 и 6 демонстрируют разные способы
использования coeff О для получения коэффициентов выражений. Пример 7
иллюстрирует использование put () и использование тождества для упрощения.
Пример 4
// coeff1.схх
Sinclude <iostream.h>
#include "MSymbol.h"
void main()
{
Sum<int> a("a",0), b("b",0), c("c",0);
cout « B*a-3*b*a-2+c).coeff(a) « endl;
cout « B*a-3*b*a-2+c).coeff(b*a) « endl;
cout « B*a-3*b*a-2+c).coeff(b) « endl;
cout « endl;
cout « (b*b+c-3).coeff(a,0) « endl;
cout « (-b).coeff(a,0) « endl;
Результаты
2-3*b
~3*a
b~B)+c-3
-b
278
Глава 7. Символьный класс
Пример 5
// coeff2.cxx
#include <iostream.h>
«include "MSymbol.h"
void main()
{
Sum<int> a("a",0), b("b",0), c("c",0), d("d",0),
У, z;
у = -a*5*a*b*b*c + 4*a - 2*a*b*c*c + 6 - 2*a*b
+ 3*a*b*c - 8*c*c*b*a + 4*c*c*c*a*b - 3*b*c
<< у « endl; cout « endl;
cout « y.coeff(z) « endl;
cout << y.coeff(z) « endl;
cout « y.coeff(z) « endl;
cout << y.coeff(z) « endl;
cout « y.coeff(z) « endl;
cout << y.coeff(z) << endl;
cout << endl;
// найдем коэффициент при терме-константе
z = a; cout « y.coeff(z,0) « endl;
cout << y.coeff@) « endl;
}
Результаты
cout <<
z = a;
z = b;
z = c;
z = a*a;
z = a*b;
z = d;
"y = "
у = -5*a"B)*b"B)*c+4*a-10*a*b*c~B)
+6-2*a*b+3*a*b*c+4*c~C)*a*b-3*b*c
4-10*b*c"B)-2*b+3*b*c+4*c"C)*b
-10*a*c~B)-2*a+3*a*c+4*c"C)*a-3*c
-5*a~B)*b"B)+3*a*b-3*b
-5*b~B)*c
-10*c"B)-2+3*c+4*c-C)
0
6-3*b*c
6
7.4. Операторы
6
// coeff3.cxx
-include <iostream.h>
include "Rational.h"
include "MSymbol.h"
v0id main О
{
int i;
Sum< Rational<int> > x("x",0),w("w",0),p("p",0);
tf = Rational<int>@);
for (i=-5; i<=5; i++)
w += Rational<int>F+i,6-i)*power(p,5-i)*power(x,i);
cout « "w = " << w << endl; cout << endl;
cout « "И коэффициенты суть" « endl;
for (i=-5; i<=5; i++)
cout « w.coeff(x.i) << endl;
}
Результаты
w = l/ll*p~A0)*x~(-5)+l/5*p~(9)*x~(-4)+l/3*p~(8)*x-(-3)
+l/2*p~G)*x~(-2)+5/7*p~F) *x"(-1)
+P~E)+7/5*p~D)*x+2*p~C)*x~B)
+3*p-B)*x~C)+5*p*x~D)+ll*x~E)
И коэффициенты суть
1/11*р-(Ю)
1/5*р-(э)
i/Sp-CS)
l/2*p-G)
5/7*P~F)
P~E)
7/s*P~D)
2*P"C)
3*p-B)
5p
11
280
Глава 7. Символьный класс
Пример 7
// put.схх
#include <iostream.h>
«include "MSymbol.h"
void main()
{
Sum<int> a("a",0), b("b",0), c("c",0), w, у;
// Тест A)
у = (a+b)*(a+sin(power(cos(a),2)))*b;
cout « "y = " « у « endl;
y.put(b,c+c); cout « "y = " « у « endl;
у.put(cos(a)*cos(a),l-power(sin(a),2));
cout « "y = " << у « endl; cout << endl;
// Тест B)
у = sin(power(cos(a),2)) + b; cout « "y = " « у « endl;
у.put(cos(a)*cos(a),1-power(sin(a),2)) ;
cout « "y = " << у « endl; cout « endl;
// Тест C)
a.depend(c); b.depend(c);
у = 2*a*df(b*a*a,c) + a*b*c; cout << "y = " << у << endl;
y.put(a,cos(c)); cout « "y = " « у « endl;
w = df(b,c); y.put(w,exp(a)); cout « "y = " « у « endl;
}
Результаты
у = a~B)*b+a*sin(cos(a)"B))*b+b~B)*a+b~B)*sin(cos(a)~B))
у = 2*a~B)*c+2*a*sin(cos(a)~B))*c
+4*c~B)*a+4*c~B)*sin(cos(a)~B))
у = 2*a~B)*c+2*a*sin(l-sin(a)~B))*c
+4*c~B)*a+4*c~B)*sin(l-sin(a)~B))
у = sin(cos(a)~B))+b
у = sin(l-sin(a)"B))+b
у = 2*a~C)*df(b,c)+4*a~B)*b*df(a,c)+a*b*c
у = 2*cos(c)~C)*df(b,c)-4*cos(c)~B)*b*sin(c)+cos(c)*b*c
у = 2*cos(c)~C)*exp(a)-4*cos(c)~B)*b*sin(c)+cos(c)*b*c
7.4. Операторы
281
ЦрЯМеР 8
оМ примере мы интегрируем выражение
у(х) = A - а;J + cos(:r) + хех + с + z
переменной х. Мы объявляем z как зависимую от х. Так как система не
0 лполагает никакой зависимости любых двух переменных, мы можем ис-
пьзовать с как символьную константу. Мы также используем
/xe'=«e«-/e-dx.
// integ.cpp
«include <iostream.h>
«include "MSymbol.h"
«include "rational.h"
void main()
{
int i;
Sum<Rational<int> > x("x",0), c("c",0), z("z",0), у;
Sum<Rational<int> > one(Rational<int>(l));
z.depend(x);
y=(one-x)*(one-x)+cos(x)+x*exp(x)+c+z;
cout « "y = " « у << endl;
for(i=0; i<3; i++)
{
У = Int(y.x);
y-put(Int(x*exp(x),x), x*exp(x) - exp(x));
cout « »y = " << у << endl;
}
cout « endl;
}
Результаты
y = l~2*x+x~B)+cos(x)+x*exp(x)+c+z
y = x~x"B)+l/3*x"C)+sin(x)+x*exp(x)-exp(x)+c*x+Int(z,x)
y = l/2*x"B)-l/3*x-C)+l/12*x"D)-cos(x)+x*exp(x)-2*exp(x)
+l/2*c*x~B)+Int(Int(z)x),x)
У = l/6*x-C)-l/12*x"D)+l/60*x-E)-sin(x)+x*exp(x)-3*exp(x)
+l/6*c*x-C)+Int(Int(Int(z,x),x)>x)
282
Глава 7. Символьный класс
7.5. Функции
Мы описали пригодность для нашей символьной системы специальных гК
ций на примере синуса и косинуса. Все доступные операторы могут приме
ся с этими функциями, например оператор дифференцирования df (). Ть~
Функция записывается в дереве выражения как лист, т. е. у нее нет по
ков. Однако функция должна оперировать с аргументами. Нам нужно на-^
подходящее место для хранения аргумента. Поскольку аргумент может б "
затребован любой функцией, он также должен принадлежать дереву выпя Ь
ния. Мы решили сделать его, подобно дереву выражения, коренящимся
самой функции. Рассмотрим функцию sin(x+y) и структуру ее дерева
рис.
7.5.
На
-В
•и
Рис. 7.5. Схематичная диаграмма функции sin(х+у).
На диаграмме узел ' z' обозначает функцию sin() вместе с ее данными в
классе sin. Ее аргумент, указываемый указателем ер, является корнем дерева
выражения аргумента. В таком представлении мы успешно реализуем
функцию как часть дерева выражения. Ниже мы укажем функции, доступные в
символьной системе и операциях, связанных с ними. Пока в классе
реализовано только семь специальных функций:
ехрО , sinhQ, coshQ ,sin(), cos(), sqrtQ, ln()
Методы, которые связаны с ними, включают:
Правила дифференцирования:
d , . du . d . , du ,
— exp(u) = — ехр(ш, — sinhu = — coshu,
dx dx dx dx
d du d
— sin и = — cos u, — cos и
dx dx dx
d , du . .
— coshu = — sinhw,
dx dx
du d ,- du 1
-— sinu, — v« =
dx dx
d du\
dx~2^' dilnu= dxu'
• Значения в некоторых точках:
ехр(О) = 1, sinh(O) = 0, cosh(O) = 1, sin(O) = О,
cos@) = l, VT=l, V^^O, ln(l) = 0.
7.6. Упрощение выражений
283
с Упрощение выражении
lenue играет существенную роль в символьной системе. Оно переводит
^ жение в эквивалентную, но более простую форму. Ниже приведены неко-
&® примеры упрощения алгебраических выражений:
^2+5 "> 7
3#х-2*х -> х
и+0 "> u
sinB*x-x-x) -> 0
сймвол -> означает «упрощает до» или «превращает в». Рассмотрим другое
выражение,
8*а*а + Ь*с* (а + b - а - 2*Ь + Ь) + 2 - power(a,2)*3 + cos(x - х)
Здесь имеется явная усложненность. Применив алгебраические упрощения,
можно получить более «чистый» и «пригодный» результат; например,
упрощенная форма вышеприведенного выражения есть
5*а~2 + 3
Упрощение выражения базируется на существующих правилах упрощения
для каждого типа математического выражения. Например, упрощение
вышеприведенного выражения включает следующие алгебраические манипуляции:
8*а*а + Ь*с*(а + b - а - 2*Ь + Ь) + 2 - power(a,2)*3 + cos(x - х)
-> 8*а~2 + Ь*с*0 + 2 - 3*а~2 + cos(O)
-> 5*а~2 + 0+2+1
-> 5*а~2 + 3
Фактически, не существует конечного набора правил для упрощения всех
типов алгебраических выражений. Это означает, что трудно описать алгоритм,
который может полностью упростить произвольное алгебраическое
выражение. Тем не менее на практике могут быть достигнуты очень хорошие
результаты с подходящим конечным набором правил упрощения.
'•6.1. Канонические формы
Математические выражения могут существовать в различных эквивалентных
символьных представлениях. Каноническая форма есть определенный
«стандарт» представления для таких выражений. Например, многочлены обычно
представляются серией членов с убывающими степенями, каждому члену
предшествует его числовой коэффициент. Ниже показано преобразование, которое
°°тветствует соглашению «стандарта»:
-5*х3+а;6*8 + 4-2*а;2-^8*а;6-5*а;3-2*а;2+4.
Реобразование в каноническую форму может увеличить или уменьшить
0>кность выражения или сводится лишь к перегруппировке термов. Как пра-
Ло, такое преобразование приводит к улучшению удобочитаемости. Тем не
Чее наиболее важным применением является определение «эквивалентно-
и* двух различных символьных представлений. Следовательно, нам нужны
284
Глава 7. Символьный класс
другое. Получить исчерпывающий список корректных правил, однако, цA
трудно.
в
Когда каноническая форма выражения тщательно определена, преобп
некоторые правила эквивалентных преобразований из одного представ ц<ч.
I/
вание в каноническую форму двух эквивалентных выражений приведет кГ °
тичным, или почти идентичным представлениям. Это существенно уменьп •
сложность при сравнении выражений
В нашей символьной системе математическое выражение представляет
древовидной структурой. Процесс упрощения состоит из двух этапов: nepgi .•
есть преобразование в каноническую форму, а другой состоит в приведении
более простой форме, базирующейся на некоторых известных правилах. Н-
пример, а*(-1) может быть переписано как -а. Символьное упрощение проиг
ходит следующим образом:
[Шаг
[Шаг
[Шаг
[Шаг
1]
2]
3]
4]
->
->
->
->
а*(-1)
~(а*A>)
-(а*1)
-(а)
-а
Процесс упрощения включает три шага канонических преобразований
(шаги 1, 2, 4) и один шаг, основанный на правилах упрощения (шаг 3) (см.
рис. 7.6).
'S' —► *Р
[Шаг 1]
'S'
til
—► 'р>
[Шаг 2]
'S'
ы1
.р.
а
[Шаг 3]
'S'
—. 'Р>
0
[Шаг 4]
'S*
til
а
Рис. 7.6. Схематичная диаграмма процесса упрощения для а*(-1).
7.6. Упрощение выражений
285
отот приме]) иллюстрирует, что каноническая форма является способом
«имения множества допустимых форм выражения. Следовательно, прави-
ЦЙ.
упр1
огцения нужны только для ограничения класса выражении.
я л % Правила упрощения и методы
м разделе мы опишем более детально правила упрощения и преобразо-
ие в канонические формы. Пусть е,el,е2,еЗ,е4— произвольные алгебраи-
«сие выражения, а с, cl,c2 обозначают положительные числовые констан-
Некоторые примеры правил упрощения для каждого оператора помещены
на P«c- 77'
Операции
с\ + сг —> с
е+0 -> е
С1 * С2 -> С
0*е->0
1 * е —► е
0е -»0
1е -»1
е°^1
Функции
Sum::shrink_4()
Sum::shrinkJ5()
Sum::shrink_l()
Product: :shrink_3 ()
Product: :shrink.l ()
Product: :shrink_3()
Product: :shrink J()
Product::gather()
Рис. 7.7. Правила упрощения.
Оператор вычитания (-) имеет свойства, аналогичные оператору
сложения (+), а оператор деления (/) имеет свойства, аналогичные оператору
умножения (*). Правила преобразования в канонические формы даются на рис. 7.8.
Операции
(ci * ei) * (сг * ег) -> с * (ei * ег), где с = ci * с2
((ei + е2) + е3) + е4 -V е\ + ег + е3 + е4
((ei * ег) * ез) * е4 —> ei * ег * ез * е4
ei + (е2) -» ei + e2
Функции
Sum::shrinkJ()
Sum::shrink_2()
Product: :shrink_2()
Sum::shrmk-5()
Ри,
c- 7.8. Канонические правила преобразования.
Наконец, отдельные члены группируются, суммируются или умножаются,
^и они принадлежат одному и тому же классу. Например,
2*а + 3*а -> 5*а
e*p(z) + exp(z) -> 2*exp(z)
Р*(-2) * р~E) -> р'(З)
c°s(x) * cos(x) -> (cos(x))
итоге получаем упрощенное выражение. Может вызвать удивление огра-
Ченность нашего набора правил упрощения. Разве нельзя упростить выра-
Ние к самой простой возможной форме? Ответ на этот вопрос не очевиден.
286
Глава 7. Символьный класс
Тем не менее преобразование к канонической форме играет важную
процессе упрощения, поскольку оно уменьшает количество основных ^Ь й
упрощения и сложность процесса упрощения. вИд
В нашей канонической форме для выражений произведения с чисю
операндами только первый операнд может быть числовым. Это влечет пп- "^
замены л°
(е * с) -> (с* е),
которое является применением закона коммутативности умножения. Слелп
тельно, имеет место следующая эквивалентность, и она не показана на рис 7 ?
е * 1 <—> 1 * е
е * 0 <—> 0 * е,
е * с <—У с * е.
Аналогично, коммутативность сложения влечет правило замены:
(е + с) -> (с + е).
Это устраняет многие необязательные правила сложения. Другим важным
правилом преобразования к канонической форме является правило переноса
умножения за скобки. Например,
(ci * ei) * (с2 * е2) <—> (ci * ei) * (с2 * ei),
(ci * ei)/(c2 * e2) <—► (ci/c2) * (ei/e2).
Эти правила непосредственно не вызывают упрощения, но преобразуют
выражения в канонические формы, которые в дальнейшем могут быть упрощены.
7.7. Коммутативность
Итак, мы предполагаем справедливость коммутативности умножения
символов. Тем не менее в некоторых приложениях это не так. Для примера в общем
случае
А*В ф В* А,
если Л, В — матрицы. Это заставляет нас подумать о потребности в
некоммутативном операторе. В действительности имеется большая ветвь
математики (называемая «алгебры Ли»), в которой рассматриваются некоммутативные
операторы. Следовательно, мы должны уметь строить в нашей символьной
системе некоммутативные операторы.
В нашей системе компьютерной алгебры коммутативность предполагается
по умолчанию. Для того чтобы определить некоммутативную алгебру, мы
используем функцию Commutative(T,int t). Первый аргумент определяет тип
данных основной области, с которыми мы имеем дело. Это —тип, опредс'1еН'
ный в процессе объявления переменных. Второй аргумент t определяет выбор
коммутативности. Если t = 0, некоммутативность имеет место. В противное
случае, имеет место коммутативность.
7.7. Коммутативность
рассмотрим следующую программу:
,/ commute. cxx
■ elude <iostream.h>
J-nclude "MSymbol.h"
v0id main О
{ Sum<int> a("a",0), b("b",0), у;
cout << "коммутативная алгебра" << endl;
cout « "==================" « endl;
// Система коммутативна по умолчанию
У = a*b*a; cout << " у = " << у « endl;
у = a*b-b*a; cout << " у = " << у « endl;
у = power(a-b,2); cout « " у = " « у « endl;
cout << endl;
cout << "некоммутативная алгебра" << endl;
cout << "====================" << endl;
Commutative(int(),0);
у = a*b*a; cout << " у = " << у « endl;
у = a*b-b*a; cout << " у = " << у << endl;
У = power(a-b,2) ; cout « " у = " << у « endl;
cout << endl;
cout << "коммутативная алгебра" « endl;
cout << "==================" << endl;
Commutative(int(),1);
cout << " у = " << у « endl;
}
Результат
КО]
,МмУтативная алгебра
У = а~B)*Ь
У - 0
У а a~B)-2*a*b+b~B)
288
Глава 7. Символьный класс
некоммутативная алгебра
у = а*Ъ*а
у = a*b-b*a
у = а~B)-а*Ь-Ь*а+Ъ~B)
коммутативная алгебра
у = а~B)-2*а*Ъ+Ъ~B)
В приведенном примере обращаем внимание на то, как можно ввей
функцию в любое время в пределах программы для определения или из\о
нения свойства коммутативности. Последние два отображенных утвержден ^
показывают, что при переключении режима с некоммутативного на коммута
тивный выражение автоматически перевычисляется. Это правило не
применимо при обратном переключении режима.
7.8. Символьный и числовой интерфейсы
Когда мы оперируем с символьным алгебраическим выражением, нам также
бывает нужно его числовое значение. Наша система справляется с этой задачей
следующим образом:
1. Вначале получаются алгебраические значения всех переменных и
операторов, входящих в состав выражения, путем подстановки значений
аргументов, которые последний раз им назначались (если значение никогда не
назначалось, в качестве такового берется сама переменная).
2. Вычисляется выражение при помощи стандартных алгебраических правил.
3. В состав класса включены три функции для определения числовых
значений и вычисления алгебраических значений выражений. Пусть х, у, z —
символьные переменные, у присвоено алгебраическое выражение со
свободной переменной х, a z присвоено значение числовой постоянной. Тогда
• x.set( const T num) присваивает переменной х числовое значение
постоянной num.
• у.value() возвращает вычисленное алгебраическое значение
выражения у.
• z.nvalue() возвращает числовое значение переменной z.
Отметим, что присвоение числовой константы переменной с помощью ме'
тода x.set(iram) отличается от использования оператора присваивания x.-wi&-
Метод x.setO просто создает дубликат числовой переменной х. Эта величина
может быть изменена, и при этом символьные свойства переменной х остаются
неизменными. После же выполнения оператора присваивания х=пшп
переменная х становится числом num. Переменная х теперь семантически эквивалентН'
num, т.е. х теряет свои символьные свойства (и приобретает символьные сво»'
ства num) в выражении, и они не могут быть восстановлены. Эти различи*1
демонстрируются в следующей программе:
7.8. Символьный и числовой интерфейсы
I setvalue.cxx
lude <iostream.h>
'delude "MSymbol.h"
void main О
{Sum<double> x("x",0), y("y",0), z("z",0);
double cl = 0.5, c2 = 1.2;
= x*x + z/2.0;
cout « "У = " « У « endl;
cout << "Величина у есть " << y.valueO << endl;
cout « endl;
x.set(cl); z.set(c2) ;
cout « "Положим x = " « cl << ", z = " « c2 << endl;
cout << "Величина у = " « у « " есть " « y.valueO «
cout « endl;
cout « "Заменим х на " << cl << " и z на " << c2
« ", тогда" << endl;
x = cl;
z = c2;
cout << "y = " << у « endl; cout << endl;
x. clear () ; z. clear 0;
У = x*x*z + 0.7*z - x*z;
cout « "y = " << у << endl;
X.set(cl);
cout « "Положим x = " « cl << endl;
cout << "Величина у есть " « y.valueO « endl;
cout « endl;
z-set(c2);
cout « "Положим z = " « c2 « endl;
cout << "Величина у есть " « y.valueO « endl;
^еэУльтат
Ja x"B)+0.5*z
елачина у есть x"B)+0.5*z
290
Глава 7. Символьный класс
Положим х = 0.5, и z = 1.2
Величина у = x~B)+0.5*z is 0.85
Заменим х на 0.5 и z на 1.2, тогда
у = 0.85
у = x~B)*z+0.7*z-x*z
Положим х = 0.5
Величина у есть 0.45*z
Положим z = 1.2
Величина у есть 0.54
7.9. Заключение
В этой главе введена новая система компьютерной алгебры, которая строится
с использованием объектно-ориентированного программирования. Мы
отметили параллелизм иерархии математических структур и философии объектно-
ориентированного программирования, что служит мотивацией использования
языка C++ для этой разработки. В этой главе мы описали следующее:
• Объектно-ориентированная разработка: мы объяснили, как
математические выражения могут быть определены с помощью правил построения и
как это связано с нашей символьной системой.
• Поля данных и типы символов: мы очертили поля данных, описанных в
классах, и основные типы символов, используемых в системе.
• Операторы: были описаны назначение и функциональные свойства
арифметических операторов и функций. Дано описание дифференцирования и
интегрирования.
• Функции: были описаны специальные математические функции, такие, как
синус, косинус и др.
• Упрощение: это важная часть системы компьютерной алгебры, посредством
которой сложные выражения приводятся к более простым формам.
• Коммутативность: мы описали, как коммутативность играет важную
роль в системе.
• Символьный и числовой интерфейс: система компьютерной алгебры не п<ь
на без интерфейса между символьным и числовым аспектами. Здесь оъь
описаны правила и методы, которые обеспечивают такой интерфейс.
7 К
В следующей главе мы применим классы, реализованные в гл. 6 и ''
приложениям в различных областях, таких, как, например, теория чисел, #•
тическая динамика, сферические гармоники, кривизна и др.
Глава 8
приложения
функциональное назначение системы компьютерной алгебры лучше всего
иллюстрируется приложениями. В этой главе мы представим приложения,
связанные с классами, введенными в гл. 6 и 7. Представленные здесь задачи
имеют отношение к теории чисел, нелинейной динамике, специальным функциям
в математике и физике и др. В целом приложения распределены по классам.
В каждом из них сначала описывается математический контекст, потом
следует предлагаемое решение задачи и затем приводится фрагмент программы,
которая решает задачу в символьном виде. В этой главе представлено двадцать
восемь приложений:
A
B
C
D;
E:
F
G:
(в:
о
(ю
(и
A2
A3
A4
A5
Aб:
A7
A8;
A9;
B0:
B1
B2:
B3
B4:
Простые числа
Большие простые числа
Обратное отображение и счетное множество
Геделева нумерация
Логистическое отображение
Теорема о сжимающем отображении
Блуждающие решения
Системы итерационных функций
Логистическое отображение и показатель Ляпунова
Множество Мандельброта
Многочлены
Вычисление кумулянтов
Внешнее произведение
Первые интегралы
Сферические гармоники
Небесная механика
Разложение в ряд Тейлора дифференциальных уравнений
Коммутатор двух векторных полей
Производная Ли и свертка векторного поля
Норма Гильберта—Шмидта
Свободная пара и гамильтонова система
Аппроксимация Паде
Псевдосферические поверхности и солитонные уравнения
Метод Пикара
292
Глава 8. Приложения
B5) Техника рядов Ли
B6) Спектр малых групп спинов
B7) Системы и нелинейные отображения с хаотичным поведением
B8) Численно-символьное приложение
8.1. Класс Bit Vector
8.1.1. Простые числа
В этом разделе мы применим класс BitVector к теории чисел. Простое ч
ло — это положительное целое р > 1, такое, что никакое другое целое, кромр
и р, не делит р. Последовательность простых чисел — возможно наиболее зна
менитая последовательность в теории чисел. Мы сгенерируем ее, использу
«решето Эратосфена», описанное в разд. 2.2.
Чтобы реализовать алгоритм решета, необходимо выбрать способ записи и
вычеркивания числа в последовательности. Мы могли бы ввести массив
флагов, соответствующих каждому числу в последовательности. Массив
инициализируется установкой всех флагов значением true. Вычеркивание из
последовательности эквивалентно установке соответствующего флага значением false.
Когда мы достигаем простого р2 > N, все простые, меньшие N, будут
обнаружены, при этом элементы массива с флагами, равными true, будут
соответствовать всем простым числам < N.
Обычно массив флагов представлен битовым полем. Это просто вектор из
величин 0/1. Поле реализуется в виде вектора unsigned char. Поскольку
каждый символ состоит из 8 битов, он может хранить 8 флагов. Такая структура
данных достаточно эффективна с точки зрения затрат памяти для хранения
данных.
Реально нам нужно загружать флаги только для нечетных чисел, так как
мы знаем, что все четные числа, кроме 2, не просты. Таким образом, нам
требуется 1000000/(8 х 2) = 62500 байт, чтобы записать таблицу простых чисел
меньше миллиона. Затраты памяти в этом алгоритме есть величина порядка
O(N).
Ниже мы приводим класс Prime, который генерирует последовательность
простых чисел. Отметим, что он включает заголовочный файл "Bitvec.h".
// Prime.h
#ifndef PRIME_H
#define PRIME_H
#include <assert.h>
#include <iostream.h>
#include "Bitvec.h"
class Prime
{
private:
8.1. Класс BitVector
,1 Поля данных
'sued int max_num, max_index, index, p, q, current
tVector bvec;
p n конструкторы
prime() >
prime(unsigned int num);
void reset (unsigned int);
int step О;
void runО;
int is_prime(unsigned int) const;
unsigned int current_prime() const;
unsigned int operator () (unsigned int) const;
};
Prime::Prime()
: max_num@), шах.index@), index(O),
p@), q@) , current(O), bvecQ {}
Prime::Prime(unsigned int num)
: max_num(num), max_index((num+l)/2 - 1), index(O),
pC), qC), currentB), bvec(max_index, OxFF) О
void Prime::reset(unsigned int num)
{
assert(num > 1);
max_num = num;
max_index = (num+l)/2 - 1;
index =0; p = 3; q=3; current = 2;
bvec.reset(max_index, OxFF);
"»t Prime: :step()
lf (index < max_index)
while (!bvec.test(index))
{
++index;
P += 2;
if (q < max_index) q += p+p-2;
if (index > max_index) return 0;
}
Current = p;
294
Глава 8. Приложения
if (q < max_index)
i
II удаляем все нечетные кратные р, начиная с р~2
// к = индекс р~2
unsigned int k = q;
while (k < max_index) { bvec.clear(k); k += p; }
++index;
p += 2;
q += p+p - 2;
return 1;
}
// если p~2 > n, то bvec[] содержит все простые <= n
else // идем к следующему нечетному числу
{
р += 2;
++index;
return 2;
>
>
else return 0;
>
void Prime::run()
{ while (stepО == 1) ; }
int Prime::is_prime(unsigned int num) const
{
if (!(num7.2)) return 0;
if (bvec.test((num-3)/2)) return 1;
return 0;
}
unsigned int Prime::current_prime() const
{ return current; }
unsigned int Prime::operator () (unsigned int idx) const
{
if (idx == 0) return 2;
for (unsigned int i=0; idx && i<max_index; i++)
if (bvec.test(i)) —idx;
return i+i + 1;
}
#endif
8.1. Класс BitVector
295
оуктор Prime (insigned int N) определяет верхний предел iV последо-
^°fiC ьности простых чисел {pt}, где 1 < pi < N, обеспечивает соответствую-
в^ ^ит0вый вектор и включает все его биты.
"il яожим, что р является элементом класса Prime; тогда имеющиеся шесть
лов работают следующим образом:
О отыскивает следующее простое число, вычеркивая все кратные
текшего простого числа, начиная с 3.
ци() повторно выполняет метод step О до тех пор, пока все простые, мень-
' шив N, не будут найдены,
reset (int M) переопределяет верхний предел последовательности простых
чисел.
is prime (int пшп) проверяет, является ли num простым числом. Он
работает в предположении, что соответствующая битовая таблица уже создана.
, current _prime () возвращает текущее простое число, найденное
итерациями метода step().
» Оператор взятия индекса p(int M) возвращает М-е простое число
последовательности.
Посмотрим теперь на простую программу, которая вычисляет общее
количество простых чисел до 100,1000, 10000,100000,1000000 и отображает первые
20 простых чисел. Она также демонстрирует возможность доступа к г-му
простому числу.
// sprime.cxx
(include <iostream.h>
(include "Prime.h"
void main()
{
unsigned int i, j, maxO = 1000000, count = 0;
Prime p(max0); // задаем верхний предел последовательности
P-run(); // генерируем последовательность простых чисел
// для всех нечетных чисел > 3
// проверяем, просты ли они
f°r (i=3, j=100; i<=max0; i+=2)
if (p.is_prime(i)) count++;
// количество простых меньших 100, 1000, ..., 1000000
if (i == j-1)
{
j *= 10;
296
Глава 8. Приложения
cout « " Имеется " « count + 1
<< " простых чисел до " << i+1 << endl;
}
}
cout « endl;
// печать первых 20 простых чисел
for (i=0; i<20; i++) cout « p(i) « " ";
cout << endl; cout « endl;
// случайная выборка нескольких простых чисел
cout << 00-е простое число есть " << р(99) << endl;
cout << 00-е простое число есть " « рA99) << endl;
cout « 000-е простое число есть " « рB999) « endl;
cout « 0000-е простое число есть " « р(9999) « endl;
cout « 2500-е простое число есть " « рA2499) « endl;
}
Результат
Имеется 25 простых чисел до 100
Имеется 168 простых чисел до 1000
Имеется 1229 простых чисел до 10000
Имеется 9592 простых чисел до 100000
Имеется 78498 простых чисел до 1000000
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71
100-е простое число есть 541
200-е простое число есть 1223
3000-е простое число есть 27449
10000-е простое число есть 104729
12500-е простое число есть 134053
8.2. Класс Very long
8.2.1. Большие простые числа
В этом разделе мы используем класс Verylong, чтобы проверить, является л"
большое положительное целое число простым числом. Простое число — это
положительное целое р > 1, такое, что никакое другое целое не делит р, кроме
1 и р.
Функция is_prime(unsigned int num), которую мы описали в разд. 8ЛЛ-
может использоваться для проверки простоты числа num. Она работает пр"
условии, что соответствующая битовая таблица уже создана. Для большие
чисел (больше 232) встроенный тип unsigned int слишком тесен. Для преод""
8.2. Класс Very long
297
этого ограничения могут быть использованы числа из Verylong. Однако
Ле1,и аЯ проблема этого алгоритма —огромные затраты памяти для битовой
°cli° тм Для больших чисел, например 20-значных, это становится совершен-
10 нереальным.
лЛы опишем другой алгоритм проверки на простоту положительного целого
со скромными запросами к памяти. Это достаточно наглядный алгоритм
ЧЙ рпки на простоту. Для данного положительного целого N мы делим N
п{) „едовательно на р = 2,3,5,... до наименьшего р, для которого N modp = 0.
с пи мы дойдем до такого р, то N — не простое число и имеет множитель р.
и же на каком-то шаге мы обнаружим, что N modp ф 0 и [iV/p] < p, мы
заключаем, что iV - простое число.
Рассмотрим это рассуждение подробнее.
Данное положительное целое N мы делим на последовательность «пробных
делителей»
2 = г/о < d\ < d2 < d% < ■ ■ ■ < dn,
которая включает все простые числа < y/N (последовательность может также
включать некоторые составные числа, если это удобно). Последний делитель в
последовательности d, — минимальное число > vN. Если на каком-либо шаге
jVmoddi = 0 для 0 < i < п, мы заключаем, что N составное. В противном
случае оно — простое число.
Последовательность пробных делителей do,d\,d2,.. .dn работает
наилучшим образом, когда она содержит только простые числа. Иногда удобно
включить также некоторые составные числа, поскольку такую последовательность
обычно легче сгенерировать. Одна хорошая последовательность может быть
сгенерирована с помощью следующего рекуррентного соотношения:
Do = 2, d\ = 3, d2 = 5,
dk = dk-\ +2 для нечетных к > 2,
dk = (Д._1 +4 для четных к > 2.
Вот несколько первых пробных делителей, сгенерированных этими
соотношениями:
2 3 5 7 11 13 17 19 23 25 29 31 35 37 41 43 ....
■^та последовательность не содержит кратных 2 или 3 и первые 9 чисел —
все простые. Тем не менее она также включает некоторые составные числа,
например 25, 35, 49 и др.
//
isprime.cpp
#include <iostream.h>
include "Verylong.h"
temPlate <class T> int is_prime(T p)
T jB), zero(O), one(l), twoB), fourD);
298
Глава 8. Приложения
Т limit = T(sqrt(p)) + one;
if (j < limit && p%j == zero) return 0;
if (j < limit && p'/.j == zero) return 0;
j += tvo;
while (j < limit)
{
if (p'/,j == zero) return 0;
j += two;
if (p'/,j == zero) return 0;
j += four;
}
return 1;
}
void main()
{
Verylong x;
cout << "Введите положительное целое число: ";
cin >> х;
if (is_prime(x)) cout « " Число " << x « " просто " « endl;
else cout << " Число " « x << " составное " << endl;
>
Результат
Введите положительное целое число:20
Число 20 составное
Введите положительное целое число: 624682384467
Число 624682384467 составное
Введите положительное целое число: 48957934859
Число 48957934859 простое
Хотя это не лучший способ проверки на простоту, он иллюстрирует, как
класс Verylong мог бы использоваться в теории чисел. Иногда хорошо иметь
таблицу простых чисел (см. разд. 8.1.1) как часть программы. Например, если
таблица содержит все 78498 простых чисел до миллиона, мы могли бы
проверить простоту чисел N < 1012. Такую таблицу можно легко создать с помощы"
класса Prime, который мы обсудили в разд. 8.1.1.
Пока не известны эффективные методы проверки на простоту большие
чисел. Тем не менее известны алгоритмы [27], которые выполняют проверку
за приемлемое время.
8.2. Класс Very long
299
. ереСным расширением была бы программа поиска всех пар простых
близнецов, например 21, 23. Вот простенький вопрос: почему нет других
чйСе & триплетов (т.е. 3-х подряд идущих простых чисел), кроме 2, 3, 5?
П^°гТясла 2* - 1 {к € N) называются числами Мерсенна. Для к = 2, 3, 4, 7, 13,
31 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941,
0л ч' мы получаем простые числа. Большие простые числа также играют
чую роль в шифровании данных и сетевой безопасности.
В Простое число 2к — 1 (к = 11213) можно найти, используя следующую не-
ьщую программу. Это —большое число, состоящее из 2816 цифр.
// Mersenne.cxx
#include <iostream.h>
#include "Verylong.h"
int main0
{
Verylong p,
one("l"),
two("),
mersenne(1213");
p = pov(two.mersenne) - one;
cout << p << endl;
return 0;
8.2.2. Обратное отображение и счетное множество
Множество N х N счетно, потому что оно равномощно множеству
натуральных чисел N. Другими словами, существует взаимно-однозначное соответствие
между N и N х N.
Напишем элементы N х N в виде массива, как показано ниже:
Л)' /jfo) D,3)
E,2) E,3)
ХМ) A,5)
.B^B,5)
C,4)
D,4)
E,4)
C,5)
D,5)
E,5)
300
Глава 8. Приложения
На схеме стрелками показан способ упорядочивания элементов N
линейную последовательность, а именно б
A,1), B,1), A,2), C,1), B,2), A,3), D,1), C,2),...
Таким образом, существует взаимно-однозначное соответствие между \т
N х N. Если (т, п) € N х N, тогда имеем и
f(m,n) = -{т + п- 1){т + п-2) +п. ^
Соотношение (8.1) может быть получено следующим образом. Пара (m,n)
ходится в (т + п — 1)-й диагональной полосе в вышеуказанной таблице и о
есть n-я пара, отсчитывая от левого края полосы. Первая полоса содержи
только одну пару. Во второй имеется две пары и т.д. Таким образом, в на
шей счетной процедуре (т,п) располагается в позиции, задаваемой
выражением
m+n—1
п+ У^к.
к=\
Отсюда сразу следует (8.1). Решим теперь обратную задачу, т.е. зная номер
пары f(m,n), найдем ее компоненты тип.
Для того чтобы найти обратное отображение, мы сначала должны
определить, на какой диагональной полосе находится элемент с номером /(m,n).
Отметим, что m = 1 соответствует последнему элементу на диагональной
полосе. Пусть к = т + п — 1, таким образом, для m = 1, к = 1 + п — 1 = п.
Согласно (8.1),
т,-, \ !, w 1 ч п2 +п к2 +к
Последний элемент к-Pi диагонали имеет номер
f(m,n) = 2 ,
тогда как последний элемент (к — 1)-й диагонали имеет номер
. (к - IJ + (к - 1) к2-к
/(m,n) = _ = __
Таким образом, если f(m,n) лежит на к-й диагонали, то
к2-к „ , к2+к
Пусть х € R такое, что
<f(m,n)<!L±!L. (8-2)
(S.3)
х2 + х
= f(m,n),
тогда
•? г, -1 + л/1 + 8/(m,n)
аг2 + аг - 2/(т, п) = 0 и а; = v J K ' ;,
8.2. Класс Verylong
301
очевидно, выбираем положительный корень. Используя (8.2) и (8.3),
гДе Мучаем к — 1 < х < к. Таким образом, к — наименьшее положительное
&** большее или равное х. После того как мы нашли к, величины тип
деЛ 'быть легко вычислены из (8.1):
п = f[m,n) , т = к — п+1.
пеализовали обратное преобразование, использующее вышеописанный ал-
итМ- Программа использует класс Verylong, чтобы оперировать с очень
инными целыми числами. Функция sqrt(), использованная здесь, реализо-
«а в классе Verylong. Он вычисляет квадратный корень числа из Verylong.
К то бы интересно расширить нашу конструкцию до взаимно-однозначного
соответствия между N и N х N х N.
// inverse.схх
#include <iostream.h>
«include "Verylong.h"
void InverseMap(Verylong f, Verylong & m, Verylong & n)
{
Verylong zero@), one(l), twoB), eight(8), k;
k = (sqrt(one+eight*f)-one)/two + one;
n = f - k*(k-one)/two;
if (n==zero) {—k; n += k;}
m = k-n+one;
}
void main 0
{
Verylong one(l), twoB);
Verylong f, m, n, result;
cout << "Введите целое число : ";
cin » f;
InverseMap(f,m,n);
cout << " Соответствующее m есть " << m << endl;
cout << " и n есть " << n « endl;
}
Результат
еДите целое число: 123456789012345
е m есть 8553526
и п есть 7159959
с°ответствующее m есть 8553526
302
Глава 8. Приложения
8.2.3. Гёделева нумерация
Мы можем работать с алфавитом, который содержит только единственцу,
Ну
слова), —это |, ||, ||| и т.д. Эти слова тривиальным способом могут идоп-п к
"ФН-
кву, например |. Слова, созданные из этого алфавита (за исключением nVf. ^
слова), —это |, ||, ||| и т.д. Эти слова тривиальным способом могут идо
цироваться с натуральными числами 0,1,2,.... Такая предельная стаи
ОГ0
л иЧ>тц-
зация «материала» может быть использована в некоторых рассужденияу
другой стороны, часто бывает удобно избавиться от излишнего разнообра,
алфавита, состоящего из нескольких элементов.
Использование алфавита, состоящего из одного элемента, не влечет за с
бой какого-либо существенного ограничения. Мы можем связать слова W
алфавите из N элементов с натуральными числами G(W) таким образом, что
каждое натуральное число будет связано, самое большее, с одним словом. Дна
логичные аргументы относятся к словам в алфавите, состоящем из одною
элемента. Такое представление G называется гёделевой нумерацией [14] (также
используется термин арифметизация), a G(W) — гёделев номер слова 1Г. (вя.
занный с представлением G. Ниже перечислены условия арифметизации W:
1. Если Wt ф W2, то G(Wi) ф G{W2).
2. Существует алгоритм, который для любого данного слова W вычисляет
соответствующее натуральное число G(W) за конечное число inaroEs.
3. Для любого натурального числа п за конечное число шагов можно
определить, является ли п гёделевым номером какого-либо слова W в
алфавите А.
4. Существует алгоритм, который по гёделеву номеру слова W над А строит
это слово W (которое определено однозначно согласно п. 1) за конечное
число шагов.
Вот пример гёделевой нумерации. Рассмотрим алфавит с буквами а,Ь,с.
Слова строятся конкатенацией любого конечного набора этих букв, т. е.
размещением этих букв одной за одной в строку. Например, abcbba — слово. Мы
можем перенумеровать слова следующим образом:
Данному слову х\Х2 ■■-хп, где каждое х^ — а, Ь или с, мы присваиваем в
качестве номера число
2do*3dl *---*p„n,
где pi есть г-е простое число (и 2 есть 0-е простое число) и
1, если xi есть а,
di :— { 2, если Xi есть Ь,
3, если Xi есть с.
Пустому слову в качестве номера присваивается число 0.
Например, слово acbcc имеет номер 21*33*52*73 = 463050, и abc имеет ноМ'11
21 * З2 * 53 = 2250. Число 7350 представляет aabb, так как 7350 = 21 * З1 * 52 * '"■
Чтобы показать, что эта нумерация удовлетворяет описанным выше к]"
териям, мы используем основную теорему арифметики:
8.2. Класс Verylong
303
, цатуралъное число > 2 можно представить как произведение про-
ел и Э1П0 произведение, с точностью до порядка сомножителей,
^r *l1iCb j
f^cmeenno.
можем перенумеровать любые типы объектов, а не только слова из
о целом критерии для полезной нумерации таковы:
какие два объекта не имеют одинаковый номер.
тт я любого объекта мы можем «эффективно» найти его номер.
Г1ля любого натурального числа мы можем «эффективно» определить,
"*' лЯется ли оно номером какого-либо объекта и, если это так, найти этот
объект.
Яиже мы помещаем класс Goedel, описанный выше. Он использует Prime. h,
реализованный в разд. 8.1.1:
// Goedel.h
#ifndef G0EDEL_H
#define G0EDEL_H
#include <assert.h>
♦include <iostream.h>
«include "Prime.h"
«include "MString.h"
«include "Verylong.h"
class Goedel
{
private:
// Поля данных
Verylong nvalue;
String wvalue;
int is_G;
public:
// Конструкторы
Goedel();
Goedel(String);
Goedel(Verylong);
// Методы
Verylong number() const;
String word О const;
int is_goedel() const;
v°id rename(String);
void resize(Verylong);
Goedel::Goedel()
: nvalue(Verylong(")), wvalue(String("")), is_G(l) {}
304
Глава 8. Приложения
Goedel::Goedel(String s)
: nvalue(Verylong(")), wvalue(s), is_G(l)
{
static Prime pA00);
Verylong temp, prim, zero("), one("l");
p.run();
for (int i=0; i<s.length(); i++)
{
if (s[i]>='a' && s[i]<='c>)
{
temp = one;
prim = Verylong(p(i));
for (char j='a'; j<=s[i]; j++) temp *= prim;
nvalue *= temp;
}
else
{
nvalue = zero;
break;
}
}
}
Goedel::Goedel(Verylong num) : nvalue(num), wvalue(String('"
{
static Prime pA00);
static Verylong zero("), one("l");
Verylong prim;
int i=0, factor;
p.run();
while (num>one)
{
factor = 0; prim
while (num '/, prim
switch (factor)
{
case 1: wvalue
case 2: wvalue
case 3: wvalue
default : is_G
}
++i;
}
is_G = 1;
}
• Verylong(p(i));
== zero) { num /= prim; ++factor; }
wvalue + String("a"); break;
wvalue + String("b"); break;
wvalue + StringC'c"); break;
0; return;
8.2. Класс Very long
i^n<? Goedel: :number() const { return nvalue;
Very10 &
• „a- Goedel: : word О const
StnnS
■■( (is_G) return wvalue;
return String("");
}
. t Goedel::is_goedel() const { return is_G; }
void Goedel::rename(String s)
static Prime pA00);
static Verylong zero("), one("l");
Verylong temp, prim;
p.runO ;
nvalue = one;
wvalue = s;
is_G = 1;
for (int i=0; i<s.length(); i++)
{
if (s[i]>='a> && s[i]<='c')
{
temp = one;
prim = Verylong(p(i));
for (char j='a'; j<=s[i]; j++) temp *= prim
nvalue *= temp;
}
else
{
nvalue = zero;
break;
}
}
}
v°id Goedel::resize(Verylong num)
static Verylong zero("), one("l");
assert(num >= zero);
if (num == one) { is_G = 0; return; }
static Prime pA00);
int i=0, factor;
Verylong prim;
306
Глава 8. Приложения
р.run() ;
nvalue = num; wvalue = StringC"');
while (num>one)
{
factor = 0; prim = Verylong(p(i));
while (num '/. prim == zero) { num /= prim; ++factor; }
switch (factor)
{
case 1: wvalue = wvalue + String("a"); break;
case 2: wvalue = wvalue + String("b"); break;
case 3: wvalue = wvalue + StringC'c"); break;
default : is_G = 0; return;
}
++i;
}
is_G = 1;
}
#endif
Далее рассмотрим простую программу, которая вычисляет соотв
щие гёделевы номера слов acbc и aabb. Она также перечисляет все i
номера, меньшие 1500:
// sgoedell.схх
#include <iostream.h>
#include "Goedel.h"
void mainQ
{
Goedel g(String("acbc"));
cout << "Для слова " « g.wordQ;
cout << " соответствующий гёделев номер есть "
« g.number() « endl;
cout << endl;
g.rename("aabb");
cout << " Для слова " << g.wordQ;
cout << " соответствующий гёделев номер есть "
<< g.number() << endl;
cout << endl;
g.rename("abcbc");
8.2. Класс Verylong
t << " Для слова " << g. word О;
t << " соответствующий гёделев номер есть "
<< g.number() << endl;
c0ut « endl;
Goedel h;
// Список всех гёделевым номеров, меньших 1500
for (int i=0; K1500; i++)
h.resize(i);
if (h.is_goedel())
cout << i << " => " << " гёделев номер для "
<< h.word()« endl;
}
}
Результат
Для слова acbc соответствующий гёделев номер есть 463050
Для слова aabb соответствующий гёделев номер есть 7350
Для слова abcbc соответствующий гёделев номер есть 14674275
0 => гёделев номер для
2 => гёделев номер для а
4 => гёделев номер для b
6 => гёделев номер для аа
8 => гёделев номер для с
12 => гёделев номер для Ьа
18 => гёделев номер для ab
^4 => гёделев номер для са
оО => гёделев номер для ааа
оо => гёделев номер для bb
5L => гёделев номер для ас
=> гёделев номер для baa
=> гёделев номер для cb
=> гёделев номер для aba
=> гёделев номер для be
=> гёделев номер для саа
=> гёделев номер для aab
и => гёделев номер для bba
=> гёделев номер для аааа
5 => гёделев номер для ее
=> гёделев номер для аса
308
Глава 8. Приложения
300 => гёделев номер для bab ^
360 => гёделев номер для cba
420 => гёделев номер для Ьааа
450 => гёделев номер для abb
540 => гёделев номер для Ьса
600 => гёделев номер для cab
630 => гёделев номер для abaa
750 => гёделев номер для аас
840 => гёделев номер для сааа
900 => гёделев номер для ЪЬЪ
1050 => гёделев номер для aaba
1080 => гёделев номер для сса
1260 => гёделев номер для bbaa
1350 => гёделев номер для acb
1470 => гёделев номер для aaab
Теперь рассмотрим другой пример. Он вычисляет гёделев номер для строк-
а, аа, ааа, аааа, .... Отметим, что некоторые гёделевы номера превышают
границу для встроенного типа unsigned long.
// sgoedel2.cxx
#include <iostream.h>
#include "MString.h"
«include "Goedel.h"
void mainQ
{
String s;
Goedel g;
for (int i=l; i<=20; i++)
{
s = s + String('a');
g.rename(s);
cout << i << " " << g.word() << " => " « g.number() « endl;
}
}
Результат
1 a => 2
2 aa => 6
3 aaa => 30
4 aaaa => 210
5 aaaaa => 2310
6 aaaaaa => 30030
7 aaaaaaa => 510510
8.3. Классы Verylong и Rational
309
=> 9699690
£ааааааа => 223092870^
аааааааааа '
3 аааааааа
3 аа!Пяааааа => 6469693230
10 ааааааааааа => 200560490130
И
> 7420738134810
аааааааааааа => 304250263527210
*2 аааааааааааа => I
__^=яяаааааа =>
Х\ аааааааааааааа => 13082761331670030
В ааааааааааааааа => 614889782588491410
1 аааааааааааааааа => 32589158477190044730
1 ааааааааааааааааа => 1922760350154212639070
- аааааааааааааааааа => 117288381359406970983270
19 ааааааааааааааааааа => 7858321551080267055879090
20 аааааааааааааааааааа => 557940830126698960967415390
8.3. Классы Verylong и Rational
8.3.1. Логистическое отображение
Логистическое отображение f : [0,1] —> [0,1] задается формулой
/(х) = 4хA-х).
Оно может быть также записано в виде конечно-разностного уравнения
xt+i=4xt(l-xt), t = 0,1,2,..., для xte[0,l],
где xt € [0,1] для всех t £ N U {0}. Пусть задано начальное значение хо = 1/3.
Тогда
8 32 6272 7250432
Х\ = -, Х2 = —, Х3 = -Т7Т7, Х\ =
9 81 6561' 43046721' '
Это —так называемая хаотичная орбита [51]. Точное решение логистического
отображения задается формулой
xt = - — - cosB4 arccos(l — 2х0)).
о зависимости от типа хо мы можем получить приближенную или точную
орбиту отображения. В случае приближенной орбиты мы используем для хо
встроенный тип double, тогда как для точной орбиты логистического
отображения мы используем класс Rational, который построили в гл. 6.
Мы продемонстрируем особенности класса Rational, а заодно и его связи
с классом Verylong. Мы сравним Rational<long> и Rational<Verylong> на
Итерациях отображения.
Из полученных результатов мы видим, что длины числителя и зна-
е1*ателя увеличиваются так быстро, что Rational<long> мог отображать
fj 3Удьтат только до четвертой итерации. Отсюда видно, что тип данных
ational<Verylong> очень полезен для подобных вычислений.
Очень часто по большим числителю и знаменателю дроби трудно судить о
Величине; в этом смысле представление чисел с плавающей запятой пред-
Почтительнее. Оператор преобразования к типу double в классе обеспечивает
кУю возможность.
310
Глава 8. Приложения
// logistic.cxx
#include "Rational.h"
#include "Verylong.h"
void mainQ
{
Rational<int> aD,l), b(l,l), x0(l,3);//начальная величина х0=1/^
int i;
cout << "x[0] = " << xO << " или " « double(xO) << endl;
for(i=l; i<=4; i++) // нельзя использовать величину >4
{ // из-за диапазона типа данных int
xO = а*хО*(Ь - хО);
cout << "х[" << i << "]=" << хО << " или " « double(xO) << endl'
}
cout << endl;
RationaKVerylong> cl("l","l"), c2(","l"),
y0("l","); // начальная величина у0 = 1/3
cout « "y[0] = " « yO « " или " << double(yO) << endl;
for (i=l; i<=6; i++)
{
yO = c2*y0*(cl - yO);
cout << "y[" << i << "]=" << yO « " или " « double(yO) « endl;
}
}
Результат
x[0
x[l
x[2
x[3
x[4:
y[o
yd
У [2
УСЗ
У [4
У [5
У [6
= 1/3 или 0.333333
= 8/9 или 0.888889
= 32/81 или 0.395062
= 6272/6561 или 0.955952
= 7250432/43046721 или 0.168432
= 1/3 или 0.333333
= 8/9 или 0.888889
= 32/81 или 0.395062
= 6272/6561 или 0.955952
= 7250432/43046721 или 0.168432
= 1038154236987392/1853020188851841 или 0.56025
= 3383826162019367796397224108032
/3433683820292512484657849089281 или 0.98548
8.3. Классы Very long и Rational
311
Теорема о сжимающем отображении
g.3-2- x r
с —замкнутое множество в полном метрическом пространстве X. Сжи-
ПУсТЬ отображением называется отображение / : S -t S, такое, что
d(f(x),f(y)) < kd(x,y),0 <к<1, где d— расстояние в X.
м говорят также так: «/ — липшицево порядка к < 1».
Трог>ема о сжимающем отображении. Сжимающее отображение / имеет
одну неподвижную точку, т. е. существует одна и только одна точка х*,
такая, что
x*=f(x*).
r доказательстве используется последовательность итераций. Пусть х* € 5.
Тогда
/(*„) е 5,..., /(">Ы = /(/(n-x)(a:o)) € S
и
d(/n4*o),/(n-1)(io))<M/(n_1)(*o),/(n-2)(a:o))<-"<A(n-1)d(/(a:o),a:o).
Так как к < 1, последовательность /("'(яо) является последовательностью
Коши и поэтому, когда п стремится к бесконечности, сходится к пределу
z*€S:
**= lim /(n)(z0) = lim f(f{n~1\x0)) = f(xt).
П—УОО П—ЮО
Единственность а;* вытекает из определяющего свойства сжимающих
отображений. Допустим, что есть другая точка у*, такая, что у* = /(у*), тогда
d(/(t/'),/0O) = <%*,*•)■
С другой стороны, d(f(y*),f(x*)) < kd(y*,x*), где А; < 1. Следовательно,
В следующей программе мы рассматриваем отображение / : @,1] —> @,1]
5
f(x) = -хA-х).
оидно, что отображение / имеет устойчивую неподвижную точку х* — 3/5.
Мы применяем теорему о сжимающем отображении с начальным значением
*о = 9/10. Краткое описание программы дано ниже:
Конструктор программы имеет два аргумента. Первый аргумент является
именем функции, реализующей отображение, тогда как второй аргумент
является начальным значением.
Оператор инкремента (++) применяет отображение к текущей точке, чтобы
получить следующую точку итерации.
Функция Is_FP() проверяет, сходится ли последовательность к
неподвижной точке, рассматривая относительную разность между предыдущей и
текущей точками. Если относительная разность меньше Ю-5, мы считаем,
что неподвижная точка получена.
312
Глава 8. Приложения
Из полученных результатов видно, что численная величина / сходится
и это согласуется с нашим обсуждением. Тем не менее числители и знамен >
рациональных величин (точные величины) на итерациях растут очень бь ^^
1стРо.
// contract.схх
#include <iostream.h>
#include "Rational.h"
#include "Verylong.h"
class Map
{
private:
RationaKVerylong> (*function)(Rational<Verylong>);
Rational<Verylong> value; // текущее значение точки на итерации
public:
Map(Rational<Verylong>(*f)(Rational<Verylong>),Rational<Verylong>) •
void operator ++ О; // следующая итерация
void Is_FP() ; // найдена неподвижная точка?
};
Map::Map(Rational<Verylong> (*f)(RationaKVerylong>),
Rational<Verylong> xO) : function(f), value(xO) {}
void Map::operator ++ ()
{ value = (*function)(value); }
// найдена неподвижная точка?
void Map::Is_FP()
{
Rational<Verylong> temp, // точка с предыдущего шага
dist; // относительная разность между
// предыдущим и текущим шагами
do
{
temp = value;
++(*this);
dist = abs((value-temp)/temp); // относительная разность
cout « "Точка:" « value « " или " « double(value) « endl;
cout << endl;
} while (double(dist) > le-5);
}
// f(x) = 5/2 x(l-x)
Rational<Verylong> mapping(Rational<Verylong> x)
{
return (Rational<Verylong>(",")*x*(Rational<Verylong>(")-x)) i
}
8.3. Классы Verylong и Rational
313
,oid main°
* начальная точка = 9/10
' M(mapping, RationaKVerylong>("9",0"));
Map гтП-
^зультат
Точка : 9/40 или 0.225
Точка: 279/640 или 0.435938
Точка: 100719/163840 или 0.61474
Точка: 6357483999/10737418240 или 0.592087
Точка: 27845361853829709759/46116860184273879040 или 0.6038
Точка: 508776482622863300319753571750093713279/
850705917302346158658436518579420528640 или 0.598064
Точка: 173965655081451382405789767664737533188675751088414
948631221274520980106878719/
2894802230932904885589274625217197696331749616641014100
98643960019782824099840 или 0.600959
8.3.3. Блуждающие решения
Рассмотрим обыкновенное дифференциальное уравнение
du (л \
с начальным условием и@) — щ > 0. Граничные точки задаются
соотношениями
и* =0, и* = 1.
Граничная точка и* = 1 асимптотически устойчива. Точное решение диффе-
Ренциального уравнения есть
u(t) = - ■ для 0 < г*о < 1.
1 - г*0 + ще1
Точное решение, начинающееся из начального значения ио = 99/100 = 0.99,
м°нотонно возрастает и сходится к 1 при t —> со. Для t = In 99 « 4.5951 мы
ВЬ1числяем
u{t) = 0.9999.
314
Глава 8. Приложения
Итак, u(t) уже достаточно близка к асимптотически устойчивой граничной точ
кеи* = 1. Для того чтобы интегрировать это уравнение конечно-разностнк
методом, мы используем схему центральных разностей:
«П+1 - Un-1 ,-. ч 1 О Q
— =un(l-un) п = 1,2,3,... (щ
с начальными условиями щ = х0, щ = x0+hxo(l~x0). Это конечно-разностнор
уравнение второго порядка может быть переписано как система двух разност
ных уравнений первого порядка:
un+i = vn + 2hun(l - ип) для п = 1,2,3,...
с Ui = и0 = х0 и щ = xq + hxQ(l — хо). Мы итерируем систему нелинейных
дифференциальных уравнений, используя классы Rational и Verylong с
начальным значением х0 =■ 99/100 и шагом сетки по времени h = 0.1. Эта система
не сходится в граничной точке и* — 1, а демонстрирует колебательное
поведение. Такой феномен имеет название блуждающее решение (также используется
название ложное решение).
II ghost.срр
#include <fstream.h>
#include <math.h>
#include "Rational.h"
#include "Verylong.h"
const Rational<Verylong> h("l",0"); // жаг сетки по времени
const Rational<Verylong> x0("99",00"); // начальное значение
void main()
{
Rational<Verylong> u, v, ul, twoh, t,
zero("), one("l"), two("), fifty(0");
ofstream soutCghosts.dat"); // содержит рациональные величины
// начальные значения
u = xO; v = u + h*u*(one-u);
t = zero; twoh = two*h;
while (t <= fifty)
{
ul = u; u = v;
v = ul + twoh*u*(one-u);
t += h;
sout « t « " " << v « endl;
}
8.3. Классы Very long и Rational
315
Для 0 < t < 1.4 численное решение дает хорошую аппроксимацию
точного решения. Решение г*о монотонно увеличивается, достигая значения 1. После
+ = 1.5 численное решение более не монотонно. При t = 3.0 величина г*о впервые
становится больше чем 1, и решение в силу этого начинает колебаться.
Амплитуда колебаний становится все большей и большей. Она растет геометрически,
и скорость прироста такова, что амплитуда умножается наей 2.71828 за
каждую единицу приращения t. Когда t — 10, колебание теряет свою симметрию
относительно и* = 1. Повторение таких циклов выглядит почти
периодичным. Блуждающие решения появляются даже при совсем малых h. Одна из
причин этого феномена в том, что схема центральных разностей является
разностной схемой второго порядка и что нестабильность наступает при и* — 1
и и* — 0. Глобальное поведение вычисленного решения очень чувствительно к
начальным условиям и шагу сетки по времени. Чтобы показать, что поведение
(8.4) не обусловлено ограниченной точностью разрядной сетки компьютера, мы
повторно решаем уравнения, используя точную арифметику (ghost.схх) и,
чтобы избежать ошибок округления, применяем классы Rational и Verylong.
Из полученного результата мы видим, что колебательное поведение имеет
место даже для точных арифметик. Оно является унаследованной особенностью
конечно-разностного уравнения.
8.3.4. Системы итерационных функций
Система гиперболических итерационных функций [5] состоит из полного
метрического пространства (X, d) вместе с конечным набором сжимающих
отображений wn : X —> X с коэффициентами сжатия sn для п = 1, 2,... ,7V.
Обозначим систему итерационных функций как {X : wn,n = 1,2,... ,N} и
введем коэффициент сжатия
s = max{sn : п = 1,2,... , ЛГ}.
Пусть (X, d) — полное метрическое пространство и (И(Х), h(d)) обозначает
соответствующее пространство непустых компактных подмножеств с метрикой
Хаусдорфа h(d). Следующая теорема суммирует факты о системе
гиперболических итерационных функций:
Теорема. Пусть
{X:wn,n = l,2,...,N}
есть система гиперболических итерационных функций с коэффициентом
сжатия s, тогда преобразование W : 'Н(Х) —> Л(Х.), определенное как
N
W{B) := (J wn(B)
71=1
"ля всех В € 'Н(Х), есть сжимающее отображение полного метрического
пРостранства ('Н(Х), h(d)) с коэффициентом сжатия s, т. е.
h{W(B),W(C)) <s-h(B,C)
316
Глава 8. Приложения
для всех В, С € %(Х). Его единственная неподвижная точка, А е
удовлетворяет соотношению
N
А := W(A) = U «,„(Л)
*(Х),
и является пределом
А:= lim WM(B)
для любого В € И(Х).
Неподвижная точка A G 'Н(Х) называется аттрактором системы итет
ционных функций. За доказательством мы отсылаем к отличной книге
Барнсли [5].
Далее мы представляем интересный пример из фракталов — стандартное
множество Кантора.
Стандартное множество Кантора описывается следующей системой
итерационных функций:
1 12
{R:wi,w2}, где wx{x) := -х и w2(x) := -х + -.
Это —система итерационных функций с коэффициентом сжатия s = 1/3.
Положим
Во = [0,1] и Bn = W^n\B0).
Тогда В = Итп_юо Вп есть стандартное множество Кантора. Оно также
называется множеством Кантора средней трети или троичным множеством
Кантора. Множества Во, В\,В2, ■ ■ ■ задаются следующим образом:
Во = [0,1],
Bi =
в2 =
N1
<
и
и
'2 '
з'1.
2 1
9'2
J
и
2 7
3'9
и
§■'
Это означает, что мы удаляем открытую среднюю треть, т.е. интервал (j, f)
за первом шаге, и удаляем пару интервалов (i, |) и (|, §) на втором шаг*'
_27 S_
7 8
IS 20
25 26
J7 2l
'ис. 8.1. Построение множества Кантора.
8.3. Классы Very long и Rational
317
оЛжая таким образом удалять среднюю треть, мы приходим в пределе к
*еству Кантора при п —» оо (рис. 8.1).
Гтандартное множество Кантора имеет мощность с, совершенно, нигде не
„но и имеет нулевую меру Лебега. Каждое х £ В может быть записано в
г#Де
х = щЗ-1 +а23~2 +а33 3 + ... , где а^е{0,2}.
_ твехствующая функция Кантора называется лестницей дьявола.
3 следующей программе мы генерируемем множества Кантора Вп для п =
1 2 3,4,5, б с классами Verylong и Rational:
// Cantor.cxx
#include <iostream.h>
#include "Rational.h"
^include "Verylong.h"
#include "Vector.h"
const Rational<Verylong>a=Rational<Verylong>@); // нижняя граница
const RationaKVerylong>b=RationaKVerylong>(l); // верхняя граница
class Cantor
{
private:
Vector<Rational<Verylong> > CS;
int currentSize;
public:
Cant or (int) ;
Cantor(const Cantor &);
int step();
void run();
friend ostream & operator « (ostream &, const Cantor &);
Cantor::Cantor(int numStep)
: CS(powB,numStep+l)), currentSizeB)
■f CSCO] = a; CS[1] = b; }
Cantor::Cantor(const Cantor & s)
■' CS(s.CS), currentSize(s. currentSize) {}
int Cantor::step О
mt i, newSize;
static RationaKVerylong> threeC), ttB,3);
static int maxSize = CS.length();
318
Глава 8. Приложения
if(currentSize < maxSize)
{
for (i=0; KcurrentSize; i++) CS[i] /= three;
newSize = currentSize + currentSize;
for (i=currentSize; KnewSize; i++)
CS[i] = CS[i-currentSize] + tt;
currentSize = newSize;
return 1;
}
return 0;
}
void Cantor: :run() { while (stepO) ; }
ostream & operator << (ostream & s, const Cantor &
{
for (int i=0; i<c.currentSize; i+=2)
i
s « "[" « c.CS[i] « " ";
s « c.CS[i+l] « "] ";
}
return s;
}
void main()
{
const int N = 6;
Cantor C(N);
cout « С « endl;
for(int i=0; i<N; i++)
{
C.stepO ;
cout « С << endl;
}
}
Результат
[0 1]
[0 1/3] [2/3 1]
[0 1/9] [2/9 1/3] [2/3 7/9] [8/9 1]
8.4. Классы Verylong, Rational и Derive
319
о а Классы Verylong, Rational и Derive
- Логистическое отображение и показатель Ляпунова
nvi разделе мы вычисляем показатель Ляпунова логистического отобра-
' «йя [51], которое задается соотношениями
xt+i = 4it(l -xt),
t = 0,1,2,... и Хо € [0,1]. Вариационное уравнение логистического
отобрания (также называемое линеаризованным уравнением) определяется соот-
10шениями
df(xt)
где
Так как
из него следует, что
yt+i = da, yt,
f(x) =4i(l-a;).
да;
А(я0,2/о) = Jim ^Ь
Уот^О.
yt+i = D - 8zt)yt
с J/o 5^ 0. Показатель Ляпунова А определяется как
Уг_
Уо
Мы находим, что для почти всех начальных значений показатель Ляпунова
задается формулой
А = 1п2.
Мы итерируем логистическое отображение и вариационное уравнение,
чтобы найти показатель Ляпунова. Следующая программа использует классы
Verylong$, \verbRational— и Derive, чтобы аппроксимировать показатель
Ляпунова. Класс Derive используется также и для дифференцирования.
Таким образом, вариационное уравнение получается путем точного
дифференцирования.
'/ Ljapunov.cpp
♦include <iostream
♦include
♦include
♦include
♦include
<math.h>
"Verylong
"Rational
"Derive.h'
v°id main()
int N =
100;
h>
h"
h"
1
320
Глава 8. Приложения
double xl, x = 1.0/3.0;
double у = 1.0;
Derive<double> CIA.0); // константа 1.0
Derive<double> C4D.0); // константа 4.0
Derive<double> X;
cout « "i = 0 x = " « x « " " « "y = " « у « endl;
for (int i=l; i<=N; i++)
{
xl = x;
x = 4.0*xl*A.0 - xl);
X.set(xl);
Derive<double> Y = C4*X*(C1 - X);
у = df(Y)*y;
cout « "i = " << i << " "
« "x = " « x « " " « "y = " << у << endl;
}
double lam = log(fabs(y))/N;
cout << "Аппроксимирующая величина для лямбды = " << lam <<
cout << endl;
int M = 9;
RationaKVerylong> ul;
Rational<Verylong> u("l","), v("l");
RationaKVerylong> Kl("l");
RationaKVerylong> K2(");
Derive<Rational<Verylong> > D1(K1); // константа 1
Derive<Rational<Verylong> > D4(K2); // константа 4
Derive<Rational<Verylong> > U;
cout « "j = 0 u = " « u « " " « "v = " « v « endl;
for (int j=l; j<=M; j++)
{
ul = u;
u = K2*ul*(Kl - ul);
U.set(Rational<Verylong>(ul));
Derive<RationaKVerylong> > V = D4*U*(D1 - U) ;
v = df(V)*v;
cout << "j = " « j « " "
« "u = " « u « " " « "v = " << v « endl;
}
1 гиг. * log(fabs(double(v)))/M;
cout <-< "Аппроксимирующая величина для лямбды = " « lam «
8.5. Классы Verylong, Rational и Complex
321
ультат
*'s О
5 1
= 2
5 3
= 4
х
х
X
X
X
0.333333
0.888889
0.395062
0.955952
0.168432
х = 0.298676
х = 0.837875
х = 0.543363
х = 0.992479
х = 0.0298588
i-96
t. 97
i = 98
i=99
i = 100
дипроксимирующая величина для
У
У
У
У
У
У
У
У
У
У
= 0 и = 1/3
= 1 и = 8/9
=2 и= 32/81
= 3 и = 6272/6561
=4 и = 7250432/43046721 v = 182266112/14348907
= 1.33333
= -4.14815
= -3.4824
= 12.7024
= -7.69211е+28
= -1.23888е+29
= 3.3487е+29
= -1.16167е+29
= 4.57677е+29
лямбды = 0.68296
v = 1
v = 4/3
v = -112/27
v = -7616/2187
Аппроксимирующая величина для лямбды = 0.69225
8.5. Классы Verylong, Rational и Complex
8.5.1. Множество Мандельброта
Пусть С — комплексная плоскость, тогда множество Мандельброта М
определяется как
М := {с € С : с, с2 + с, (с2 + сJ + с, ... /> оо}.
Чтобы найти множество Мандельброта, мы изучим рекурсивное соотношение
zt+i=z?+c, « = 0,1,2...,
сначальным значением zq — 0. Так как z := x + iy и с := ci+ic-2 с x,y,c±,C2 € R,
"ы Можем переписать соотношение рекурсии как
xt+l = x2t -у\ + ci,
2/t+i = ZxtVt +C2,
Начальным значением (х0,у0) = @,0). Рассмотрим теперь некоторые точки
Комплексной плоскости С и определим, принадлежат ли они множеству
Мандельброта М:
lci,c2) = @,0) принадлежит М,
\ci,c2) — A/4,1/4) принадлежит М,
\с1)Сг) = A/2,0) не принадлежит М, поскольку
z0 = 0, z\ — -, z-2 -
17
Z3=16'
И _
324
Глава 8. Приложения
с Pq(x) = 1 и Pi(x) = х. Производящая функция для многочленов Лежа,,
задается соотношением " ^
1 сю
= У Pn{x)tn.
Vl - 2te + t2
В следующей программе мы используем рекуррентное соотношение, чтобы
нерировать несколько первых многочленов Лежандра е"
Ро(х) = 1, Pi{x) = x, P2(x) = ~(ix2-l),
Р3(х) = \{5х3 - Зх), Р4{х) = ^C5ж4 - ЗОаг + 3).
2 8
Мы также показываем справедливость дифференциального уравнения Лежан
дра для случая п = 4.
// Legendre.h
#include <iostream.h>
#include <assert.h>
#include "MSymbol.h"
#include "Rational.h"
class Legendre
{
private:
int maxTerm, currentStep;
Sum<Rational<int> > P, Q;
const Sum<Rational<int> > &x;
public:
Legendre(int, const Sum<Rational<int> > &) ;
int step();
void run();
void reset();
Sum<Rational<int> > current() const;
Sum<Rational<int> > operator () (int);
friend ostream & operator << (ostream &, const Legendre &);
>;
Legendre:-.Legendre(int n, const Sum<Rational<int> > & kernal)
: maxTerm(n), currentStep@), P(l), x(kernal) {>
int Legendre::step()
{
int prev = currentStep;
8.6. Символьный класс
5ujn<Rational<int> > R, one(l) ;
++currentStep;
if (currentStep==l) { Q = one; P = x; return 1; }
if (currentStep <= maxTerm)
{
r = Rational<int>(prev+currentStep,currentStep)*x*P
- Rational<int>(prev,currentStep)*Q;
Q = P; P = R;
return 1;
}
return 0;
}
void Legendre::run()
{ while (stepQ) ; }
void Legendre::reset()
{
currentStep = 0;
P = Rational<int>(l) ;
}
Sum<Rational<int> > Legendre::current() const
{ return P; }
Sum<Rational<int> > Legendre::operator () (int m)
{
assert(m <= maxTerm);
resetQ ;
for (int i=0; i<m; i++) stepO;
return P;
}
°stream & operator << (ostream & s, const Legendre & L)
* return s « L.P; }
//
Legendr
^nclude <iostream.h>
'include "MSymbol.h"
include "Rational.h"
include "Legendre.h"
326
Глава 8. Приложения
void mainO
{
int n=4;
Sum<Rational<int> > x("x",0), one(l);
Legendre P(n,x);
// Вычисляем несколько первых многочленов Лежандра
cout « "P@) = " « Р << endl;
for (int i=l; i<=n; i++)
{
P.step ();
cout « "P("« i « ") = " << P « endl;
}
cout << endl;
// Другой способ получения многочленов Лежандра
cout « "P(l) = " « P(l) « endl;
cout « "PB) = " « PB) « endl;
cout « "PC) = " « PC) « endl;
cout << "PD) = " << PD) << endl; cout « endl;
// Покажем, что для п=4,
// Дифференциальное уравнение Лежандра выполняется
Sum<Rational<int> > result;
result = df((one-x*x)*df(P.currentQ,x),x)
+ Sum<Rational<int> >(n*(n+l))*P.current();
cout << result << endl; // ==> 0
}
Ассоциированные функции Лежандра
Имея решение дифференциального уравнения Лежандра, мы теперь сможем
получить решение уравнения
Н2 9 го2 Л
^-х2^-2хтх+п^ + 1^т^)р^ = 0'
где т,п — константы и т не обязательно равно нулю. Решения этого уравнения
Рп (х) называются ассоциированными функциями Лежандра степени п (я =
0,1,2,...) и порядка \т\ < п. Они определяются соотношениями
rj\m\ ,\
/^1(*):=A-*2)Н/2__Рп(а;)] H=0,l,2,...,<n. (8j)
Отметим, что для т — 0 мы имеем Р^{х) = Рп{х), которые являются много'Ь'1'"
нами Лежандра. Функции Рп удовлетворяют рекуррентному соотношение'
Bn+ l)xPW(x) - (п- И + 1)Р%[(х) + (n + \rn\)Pl™[(x)
8.6. Сим1ш:1ьшлп класс 327
,1 <'0С'
угношошпо ортогональности
2;/ + 1 (т! — |т|)!
онеИ- можно покачать, что Р,," обрачуют полное множество г, гпльбер-
., пространстве £•_>(—1,1). Несколько первых ассоциированных (функции
Г/.М-Ц-.гI'-, ^'И=:И1-^I/2. /^(.г) = 3A -.#■-).
pi(x) = f](i- ■'-I/-(^'2 - 1). ^V) = 1-^A - -''2). Р;П = 15A - .г2):'/2.
о следующей программе мы пспо.чьчуем (8.о) н многочлены Лежапдра. роалп-
зованные в предшествующем рачделе для конструирования ассоциированных
многочленов Лежапдра.
// AssLegendre.h
linclude <iostream.h>
#include "MSymbol.h"
#include "Rational.h"
linclude "Legendre.h"
class AssLegendre
{
private:
Sum<Rational<int> > P;
const Sum<Rational<int> > &x;
public:
AssLegendre(const Sum<Rational<int> > &);
AssLegendre(int, int, const Sum<Rational<int> > &);
void redefine(int, int);
Sum<Rational<int> > current() const;
friend ostream & operator << (ostream &, const AssLegendre &);
AssLegendre::AssLegendre(const Sum<Rational<int> > & kernal)
: x(kernal) {}
AssLegendre::AssLegendre(int 1, int m, const Sum<Rational<int> >
& kernal) : x(kernal)
int i, absm = abs(m);
Rational<int> one(l);
Legendre L(l,x);
P = L(l);
for (i=0; Kabsm; i++) P = df(P,x);
P *= power(one-x*x,Rational<int>(absm,2));
328
Глава 8. Приложения
void AssLegendre:-.redefine(int 1, int m)
{
int i, absm = abs(m);
Rational<int> one(l);
Legendre L(l,x);
P = L(l);
for (i=0; i<absm; i++) P = df(P,x);
P *= power(one-x*x,Rational<int>(absm,2));
}
Sum<Rational<int> > AssLegendre::current() const { return P;
ostream & operator << (ostream & s, const AssLegendre к L)
{ return s << L.P; }
// AssLegendre.cxx
#include <iostream.h>
#include "MSymbol.h"
«include "Rational.h"
#include "AssLegendre.h"
void mainQ
{
int i, j, n=5;
Sum<Rational<int> > x("x",0) ,Y;
AssLegendre P(x);
for (i=0; i<n; i++)
{
for (j=0; j<=i; j++)
{
P.redefine(i,j);
Y = P.current();
cout << "P(" « i « "," « j << ") = " « Y « endl;
}
cout << endl;
}
}
Результат
P@,0) = 1
PA,0) = x
P(l,l) = (l-x~B))-(l/2)
PB,0) = 3/2*x"B)-l/2
PB,l) = 3*x*(l-x~B))~(l/2)
PB,2) = 3-3*x~B)
8.6. Символьный класс
329
0) = 5/2*х~C)-3/2*х
* 3 D = 15/2*х~B)*A-х~B))~A/2)-3/2*A-х~B)ГA/2)
?)'2) = 15*х-15*х~C)
Р я'з) = 15*A-х-B))~C/2)
(АО) = 35/8*х~D)-15/4*х~B)+3/8
D1) = 35/2*х-C)*A-х-B))-A/2)-15/2*х*A-х-B))-A/2)
Р^'2) - 60*х~B)-105/2*х~D)-15/2
J4'3) = 105*х*A-х-B))-C/2)
L4'>4) = 105-210*х~B)+105*х~D)
Многочлены Лагерра
Многочлены Лагерра могут быть определены по формуле Родриго:
Ln(x):=e'£;{xne-*), n = 0,l,...,
где Lo(x) = 1- Используя правило Лейбница, получаем
п '
п \ зГ
т=0
Многочлены Лагерра Ln — это решения линейного дифференциального
уравнения второго порядка
х—^+ 1-х —^+nLn = 0. (8.6)
ax2 dx
Они удовлетворяют рекуррентному соотношению
Ln+i{x) = Bn + 1 - x)Ln(x) - n2Ln_i(x),
где I/0(x) = 1 и Li(x) = 1 — x. Многочлены Лагерра также могут быть
определены своей производящей функцией
1 / -xt \ _ ^ Ln(x)tn
■ ехр
1 -1 \l-tj *~> п\
п—0
а -Е
Соотношение ортогональности для многочленов Лагерра имеет вид
/ е xLm{x)Ln{x)dx = (n!J<Jmn.
Кроме того, мы имеем
L
Ln(t)dt = Ln(x) - Ln+l{x)
jo n + l
следующей программе мы используем рекуррентное соотношение, чтобы
сгенерировать несколько первых многочленов Лагерра. Мы также демонстриру-
м> что (8.6) выполняется для п = 4. Несколько первых многочленов Лагерра
сУть
L0{x) = l, Li{x) = l-x, L2{x) = 2-4х + х2,
L:i(x) = 6 - 18х + 9х2 - х3, L4(x) = 24 - 96х + 72х2 - 16х3 + х4.
330
Глава 8. Приложения
II Laguerre.cxx
#include <iostream.h>
«include "MSymbol.h"
class Laguerre
{
private:
int maxTerm, currentStep;
Sum<int> P, Q;
const Sum<int> &x;
public:
LaguerreCint, const Sum<int> &) ;
int stepQ ;
void run();
Sum<int> current() const;
friend ostream & operator << (ostream &, const Laguerre
};
Laguerre::Laguerre(int n, const Sum<int> & kernal)
: maxTerm(n), currentStep@), P(l), x(kernal) {}
int Laguerre::step()
{
int prev = currentStep;
Sum<int> R;
++currentStep;
if (currentStep==l) { Q = 1; P = 1-х; return 1; }
if (currentStep <= maxTerm)
{
R = B*prev+l-x)*P-prev*prev*Q;
Q = P; P = R;
return 1;
}
return 0;
}
void Laguerre: :run() { while (stepQ) ; }
Sum<int> Laguerre::current() const { return P; }
ostream & operator << (ostream & s, const Laguerre & L)
{ return s << L.P; }
void main()
{
3.6. Символьный класс
331
^in^-xCx-.O);
Laguer
//
Вычислим несколько первых многочленов Лагерра
c0Ut << "L@) = " << L << endl;
for (int i=l; i<=n; i++)
{
L. step О;
cout << "L("<< i << ") = " << L << endl;
}
cout << endl;
// Покажем, что для п=4,
// дифференциальное уравнения Лагерра выполняется
Sum<int> result;
result = x*df(L.current(),x,2) + A-х)*df(L.current(),x)
+n*L.current();
cout << result << endl;
}
Результаты
Я„(.г):=(-1)"г'"—<J-
L@) = 1
L(l) = 1-х
LB) = 2-4*x+x~B)
LC) = 6-18*x+9*x-B)-x~C)
LD) = 24-96*x+72*x~B)-16*x-C)+x"D)
0
Многочлены Эрмита
Многочлены Эрмита определяются как
d.rn '
гДе п = 0. 1. 2 Можно доказать, что они удовлетворяют линейному
дифференциальному уравнению
^»_23;^i+2«H„=0. (8.7)
dx2 d.v
споль-зуя дифференциальное уравнение, мы можем доказать, что многочлены
РМита ортогональны па ( — ос, ос) с ьеоовон функцией е.~х". Мы имеем
/ с-'2Нп(х)Нт Hdx = y/J2"n\Slim,
де п., tj/, =г о. 1. 2,... и Snm есть дельта Кронсксра. Рекуррентное соотношение
я Многочленов Эрмита приобретает вид
Hn+l(j-) = -2j-Hn(:r)-2riHn-1{x),
332
Глава 8. Приложения
где Но(х) = 1 и i(x) = 2х. Многочлены Эрмита могут быть также опрел
производящей функцией:
p2tx-e _ v* Hn{x)tn
п=0
Кроме того, мы имеем
Л Нп(')Л-2(^ТТГ2(^й)-
В следующей программе мы используем рекуррентное соотношение, чтобы
нерировать первые несколько многочленов Эрмита, и показываем справе
вость (8.7) для п = 4:
Я0(х) = 1, Н1(х) = 2х, Н2(х) = 4х2-2, Н3{х) = 8х3 - 12х,
Я4(х) = 16х4 - 48х2 + 12, Я5(х) = 32х5 - 160х3 + 120х.
// Hermite.cxx
#include <iostream.h>
#include "MSymbol.h"
class Hermite
{
private:
int maxTerm, currentStep;
Sum<int> P, Q;
const Sum<int> &x;
public:
Hermite(int, const Sum<int> &);
int step() ;
void run();
Sum<int> current() const;
friend ostream & operator << (ostream &, const Hermite &);
};
Hermite::Hermite(int n, const Sum<int> & kernal)
: maxTerm(n), currentStep@), P(l), x(kernal) {}
int Hermite::step()
{
int prev = currentStep;
Sum<int> R;
++currentStep;
if(currentStep==l)
{
Q = 1;
8.6. Символьный класс
р = 2*х;
return 1;
}
■f(currentStep <= maxTerm)
R = 2*x*P-2*prev*Q;
Q = P; P = R:
return 1;
}
return 0;
}
void Hermite::run()
{ while (stepО) ; >
Sum<int> Hermite::current() const
{ return P; >
ostream & operator << (ostream & s, const Hermite & H
{ return s « H.P; }
void mainO
{
int n=4;
Sum<int> x("x",0);
Hermite H(n,x);
// Вычисляем несколько первых многочленов Эрмита
cout « "H@) = " « Н « endl;
for (int i=l; i<=n; i++)
{
H.stepO;
cout << "H("<< i << ") = " « H « endl;
}
cout « endl;
// Показываем, что для п=4
'/ Дифференциальное уравнение Эрмита вьшолняется.
Sum<int> result;
result = df(H.current(),x,2) - 2*x*df(H.current(),x
+ 2*n*H.current();
cout << result << endl;
334
Глава 8. Приложения
Результат
Н@) = 1
НA) = 2*х
НB) = 4*х"B)-2
НC) = 8*х~C)-12*х
НD) = 16*х~D)-48*х~B)+12
О
Многочлены Чебышева
Многочлены Чебышева определяются соотношением
Тп(х) := cos(narccosa:), п = 0,1,2,... .
Отметим, что Г_„(а;) = Тп(х) и из тригонометрических формул мы получаем
) = 2Tn(x)Tm(x).
Для m = 1 мы имеем рекуррентное соотношение
Тп+1(х) = 2хТп(х) - Тп^(х),
где То(х) — 1 и Т\{х) — х. Таким образом можно последовательно вычислить
все Тп(х). Пусть х := cos(#), тогда
у = Тп(х) = cos(n<9),
dy nsm(n6)
dx sin в
d2y _ -n2 cos(?i#) + n sin(n#) cot в
dx* ~ sin2 (9
n2y x dy
1 — x2 \ — x2 dx'
Видно, что многочлены Тп(х) удовлетворяют обыкновенному линейному
дифференциальному уравнению второго порядка
v ' dx1 dx
Первые несколько многочленов таковы:
Т0{х) = 1, Т1(х)=х, Т2(х) = 2х2 - 1,
Щх) = Ах3 - Зх, Т4(ж) = 8ж4 - 8х'2 + 1.
// Cbebysbev.схх
#include <iostream.h>
#include "MSymbol.h"
class Chebyshev
8.6. Символьный класс
rjvate:
•nt maxTerm, currentStep;
Sum<int> P, Q;
const Sum<int> &x;
„blic:
Chebyshev(int, const Sum<int> &) ;
int step О ;
void run О;
Sum<int> current () const;
friend ostream & operator << (ostream &, const Chebyshev
Thebyshev::Chebyshev(int n, const Sum<int> & kernal)
: maxTerm(n), currentStep(O), P(l), x(kernal) {}
int Chebyshev::step()
{
Sum<int> R;
++currentStep;
if (currentStep==l)
{
Q = 1;
P = x;
return 1;
}
if (currentStep <= maxTerm)
{
R = 2*x*P-Q;
Q = P; P = R;
return 1;
}
return 0:
}
v°id Chebyshev::run()
< While (stepO) ; }
Sum<int> Chebyshev::current() const
I return P; }
°stream k operator << (ostream & s, const Chebyshev & T)
{ return s « T.P; }
v°id main()
336
Глава 8. Приложения
int n=4;
Sum<int> x("x",0);
Chebyshev T(n,x);
// Вычисляем несколько первых многочленов Чебышева
cout « "T@) = " « Т « endl;
for (int i=l; i<=n; i++)
{
T.stepO;
cout « "T("« i « ") = " « T « endl;
}
cout << endl;
// Показываем, что для п=4
// дифференциальное уравнение Чебышева вьшолняется.
Sum<int> result;
result = (l-x*x)*df(T.currentO,x,2) - x*df(Т.current(),j
+ n*n*T.current();
cout << result << endl;
>
Результат
T@) = 1
T(l) = x
TB) = 2*x~B)-l
TC) = 4*x~C)-3*x
TD) = 8*x~D)-8*x~B)+l
0
8.6.2. Вычисление кумулянтов
Пусть x, an, bn ERii
exp
00 J. П
n!
in=l
= E
CIjiX
n=0
с ao = 1. Мы найдем соотношения между коэффициентами un и Ьп
экспоненты с левой стороны равенства задается формулой
1 f^bnxn\k 1 /^ Ьп,хпЛ /^ ЪПихпЛ
n! / ~~ к\ \^ пх\ )"\^ п
A,-! , ... , ... ,
CO CO
8.6. Символьный класс
337
.^доват'-льно.
еХР
2^ „! ~~ 1 + 2^ „! + 2! ^ ^ щ!
71=1 7li=l П'2 = 1
оо оо
+ ki^t^'"^, niln2\...nk\
= E
a„:r"
7Z
ТТойравнивая члены одинакового порядка по х, мы можем получить
соотношение между а„ ч ''и А-'1 я !>,('ех положительных целых п. Первые три
коэффициента при степенях ./; сп ь
х-1 : «I = 1>!, х2 : а2 = Ь2 + Ь2у, х3 : a3 = b3 + 362&i + b\.
Отсюда следует, что
6i = «1, 62 — а-г — а\, Ьз = a3 — 3a2ai + 2a\.
В программе мы воспроизводим этот процесс получения коэффициентов. Он
показывает, что система компьютерной алгебры в состоянии эффективно
получать коэффициенты.
// сшпи.срр
«include <iostream.h>
«include "MSymbol.h"
«include "Vector.h"
«include "Rational.h"
Sum<Rational<int> >
Taylor(Sum<Rational<int> > u, Sum<Rational<int> > & x, int n)
{
Rational<int> zero(O);
X.set(zero);
Sum<Rational<int> > series(u.value());
int j , f ac = 1 ;
for(j=l; j<=n; j++)
{
и = df(u,x); fac = fac * j;
series += Rational<int>(l,fac) * u.valueQ * power(x,i);
}
return series;
>
v°id main()
338
Глава 8. Приложения
int i, fac, n=5;
Vector<Sum<Rational<int> > > a(n), b(n);
Sum<Rational<int> > х("х",0), у, Р, Q,
aO("aO",0), al("al",0), a2("a2",0),
a3("a3",0) , a4("a4",0),
bO("bO",0), bl("bl",0), Ь2("Ь2",0),
ЬЗ("ЬЗ",0), Ь4("Ь4",0);
a[0] = aO; a[l] = al; a[2] = a2; a[3] = аЗ; а[4] = a4;
b[0] = bO; b[l] = bl; b [2] = b2; b[3] = b3; b[4] = b4;
fac = 1; P = a[0]; Q = zero;
for (i=l; i<n; i++)
{
fac *= i;
P += Rational<int>(l,fac) * a[i] * power(x,i);
Q += Rational<int>(l,fac) * b[i] * power(x,i);
>
cout << "P = " << P « endl;
cout « "Q = " << Q « endl; cout « endl;
у = Taylor(exp(Q),x,5);
cout << "Ряд Тейлора расширения exp(Q) = " << endl;
cout << у << endl;
cout << endl;
cout << "Коэффициент при х :"<< endl;
cout << "exp(Q) => " « y.coeff(x.l) << endl;
cout << " P => " << P.coeff(x,l) << endl;
cout << endl;
cout << " Коэффициент при х~2 :"<< endl;
cout « "exp(Q) => " << y.coeff(x,2) << endl;
cout « " P => " << P.coeff(x,2) << endl;
cout << endl;
cout << " Коэффициент при х~3 :"<< endl;
cout << "exp(Q) => " << y.coeff(x,3) << endl;
cout << " P => " << P.coeff(x,3) << endl;
cout << endl;
cout << " Коэффициент при x~4 :"<< endl;
cout << "exp(Q) => " << y.coeff(x,4) << endl;
cout << " P => " « P.coeff(x,4) << endl;
>
Результат
P = a0+al*x+l/2*a2*x~B)+l/6*a3*x~C)+l/24*a4*x~D)
Q = Ь1*х+1/2*Ь2*х-B)+1/6*ЬЗ*х~C)+1/24*Ь4*х~D)
8.6. Символьный класс
339
°яд
Тейлора расширения exp(Q) =
^1*х+1/2*Ъ1~B)*х-B)+1/2*Ъ2*х-B)+1/6*Ъ1-C)*х~C)
w2*bl*b2*x-C)+l/6*b3*x"C)+l/24*bl-D)*x-D)
/4*bl~B)*Ь2*х"D)+l/6*bl*b3*x"D)+l/8*b2"B)*х~D)
l/24*b4*x-D)+l/120*bl-E)*x-E)+l/12*bl-C)*b2*x-E)
1/l2*bl~B)*b3*x-E)+l/8*bl*b2-B)*x-E)
1/24*bl*b4*x~E)+l/12*b2*b3*x~E)
оэффициент при х :
xp(Q) => bl
р => al
оэффициент при х :
acp(Q) => 1/2*Ь1"B) + 1/2*Ь2
р => 1/2*а2
[оэффициент при х~3 :
ixp(Q) => 1/6*ЬГC) + 1/2*Ы*Ь2+1/6*ЬЗ
Р => 1/6*аЗ
[оэффициент при х :
sxp(q) => 1/24*Ы-D) + 1/4*Ь1~B)*Ь2+1/6*Ь1*ЬЗ+1/8*Ь2"B)+1/24*Ь4
Р => 1/24*а4
1.6.3. Внешнее произведение
з этом разделе мы дадим реализацию внешнего произведения (используется
акже название «произведение Грассмана»), описанного в разд. 2.13. В про-
рамме внешнее произведение обозначается значком *. Мы определяем
детерминант 4x4 матрицы:
А :=
( 1
О
1
V0
вычисляя внешнее произведение
2\
3
О
о
1
/2\
1
О
V3/
/5\
2
1
/2\
3
О
V7/
"Нешнее произведение дает 24ei Л в2 Л е3 Л в4, где ej, ег, ез, е± — стандартный
ззис в R1. Таким образом, мы находим, что det A = 24.
340
Глава 8. Приложения
I/ Grass.cpp
#include <iostream.h>
#include "Vector.h"
«include "Matrix.h"
«include "MSymbol.h"
void mainO
{
int i,j,n=4;
Sum<int> e0("e0",0), el("el",0), e2("e2",0), e3("e3",0
у,result;
Matrix<Sum<int> > A(n,n);
Vector<Sum<int> > e(n);
Commutative(int(),0);
A[0][0] = 1; A[0][1] = 2; A[0][2] = 5; A[0] [3] = 2;
A[1][0] = 0; A[l][l] = 1; A[l][2] = 2; A[l][3] = 3;
A[2][0] = 1; A[2][l] = 0; A[2] [2] = 1; A[2] [3] = 0;
A[3][0] = 0; A[3][l] = 3; A[3] [2] = 0; A[3] [3] = 7;
e[0] = eO; e[l] = el; e[2] = e2; e[3] = e3;
result = 1;
for (i=0; i<n; i++) result *= (A[i]|e);
cout << result << endl; cout << endl;
// для всех i>j, заменим e[i]*e[j] на -e[j]*e[i]
int flag;
do
{
flag=0;
for (i=0; i<n; i++)
for (j=0; j<i; j++)
flag += result.put(e[i]*e[j], -e[j]*e[i]);
} while(flag);
// положим e[i]*e[i] равным 0
for (i=0; i<n; i++) result.put(e[i]*e[i],0);
for (i=0; i<n; i++) result.put(e[i]*e [i]*e[i],0);
cout << "результат = " << result << endl;
8.7. Класс Symbolic и символьное дифференцирование 341
***"' l3)teo*el+7*eO*el*eO*e3+3*eO*el*e2*el+7*eO*el*e2*e3
3*е 0#e2*e0*el+14*e0*e2*e0*e3+6*e0*e2~B)*el+14*e0*e2~B)*e3
+б*е0#еЗ*е0*е1+21*е0*еЗ*е0*еЗ+9*е0*еЗ*е2*е1+21*е0*еЗ*е2*еЗ
+9*ei-B)*e0*el+14*el"B)*e0*e3+6*el"B)*e2*el+14*el-B)*e2*e3
^*el*e2*e0*el+28*el*e2*e0*e3+12*el*e2~^ *el+28*el*e2"B)*еЗ
+\,^1*еЗ*е0*е1+42*е1*еЗ*е0*еЗ+18*е1*еЗ*е2*е1М2*е1*еЗ*е2*еЗ
+18*®
I5*e2*el*e0*el+35*e2*el*e0*e3+15*e2*el*e2*el+35*e2*el*e2*e3
+ 0Фе2"B)*е0*е1+70*е2"B)*е0*еЗ+30*е2"C)*е1+70*е2~C)*еЗ
>45*е2*еЗ*еО*е1+105*е2*еЗ*еО*еЗ+45*е2*еЗ*е2*е1+105*е2*еЗ*е2*еЗ
6#e3#el*e0*el+14*e3*el*e0*e3+6*e3*el*e2*el+14*e3*el*e2*e3
+12*еЗ*е2*е0*е1+28*еЗ*е2*е0*еЗ+12*еЗ*е2~B)*е1+28*еЗ*е2~B)*еЗ
+18*еЗ"B)*e0*el+42*e3~B)*еО*еЗ+18*еЗ~B)*e2*el+42*e3~B)*е2*еЗ
результат
24*еО*е1*е2*еЗ
8.7. Класс Symbolic и символьное дифференцирование
8.7.1. Первые интегралы
Рассмотрим автономную систему обыкновенных дифференциальных
уравнений первого порядка
где u = (ui,u-z,... ,un) и Vj — гладкие функции переменных ui,u-2,... ,un.
Гладкая функция /(и) называется первым интегралом системы
дифференциальных уравнений,если
d Т, , .. \г-> dl duj ^ dl , .
Гладкая функция I(u(t),t) называется явно зависимым от времени первым
инПегралом системы дифференциальных уравнений, если
j=i j
РИмер 1. Рассмотрим следующую систему дифференциальных уравнений:
^ьг показываек
и
Ляются первыми интегралами системы.
diii
~~d7 :
го /ь
= и\
- и2и3,
h и h
-ul
du2
~dt
, где
h(n)
= U1U3,
= u23 - u\,
duz
dt
h
= U\U2-
(u) = u\
342
Глава 8. Приложения
Пример 2. Рассмотрим модель Лоренца
du\ du-7 dii3
— = -aul +au2, — = -щщ +гщ - u2, —7-=щи2-Ьщ.
Для некоторых значений параметров бифуркации а, Ь и г система допур
явно зависимые от времени первые интегралы. Затем мы подставляем Вк °т
жение "
7(u(t),t) = («2 + «3)expBt)
в систему для нахождения условий на коэффициенты а, г, Ь, при kotod
I(u(t),t) является первым интегралом.
Пример 1
// firstl.cpp
#include <iostream.h>
#include "MSymbol.h"
#include "Vector.h"
void main()
{
int i, j, N=3;
Smn<int> ul("ul",0), u2("u2",0), u3("u3",0),
term, sum=0;
Vector<Smn<int> > u(N) , v(N), I(N);
u[0] = ul; u[l] = u2; u[2] = u3;
v[0] = u2*u3; v[l] = ul*u3; v[2] = ul*u2;
I[0] = ul*ul-u2*u2; I[l] = u3*u3-ul*ul; I[2] = u2*u2-u3*u3;
for (i=0; i<N; i++)
{
for (j=0; j<N; j++)
{
term = df (I[i] ,u[j]) * v[j];
sum += term;
cout « "Член номер " << j << " = " << term « endl;
>
cout « " Сумма = " « sum << endl;
cout << endl;
>
>
Результат
Член номер 1 = 2*ul*u2*u3
Член номер 2 = -2*u2*ul*u3
8.7. Класс Symbolic и символьное дифференцирова!
qjIeH номер 3 = О
ц2Гз°меР 1 = ~2*ul*u2*u3
Член номер 2 = 0
адея номер 3 = 2*u3*ul*u2
Сумма = °
Член номер 1 - 0
Чяен номер 2 = 2*u2*ul*u3
Член номер 3 = -2*u3*ul*u2
Сумма = 0
Пример 2
// first2.cpp
«include <iostream.h>
«include "MSymbol.h"
«include "Vector.h"
void mainO
{
int i;
Vector<Sum<int> > uC), vD);
Sum<int> term, sum, I, Rl, R2,
ul("ul",0), u2("u2",0), u3("u3",0), t("t",
s("s",0), b("b",0), r("r",0);
u[0] = ul; u[l] = u2; u[2] = u3;
// Модель Лоренца
v[0] = s*u2- s*ul;
v[l] = -u2 - ul*u3 + r*ul;
v[2] = ul*u2 - b*u3; v[3] = 1;
// Вставляемое выражение
I = (u2*u2 + u3*u3)*expB*t);
sum = 0;
for(i=0; i<3; i++) sum += v[i]*df(I,u[i]);
sum += v[3]*df(I,t);
cout « " сумма = " « sum « endl; cout « endl;
Rl = sum.coefi(u3,2);
Rl = Rl/(expB*t));
cout « "Rl = " « Rl « endl;
R2 = sum.coeff(ul,l); R2 = R2.coeff(u2,1);
R2 = R2/(expB*t));
cout « "R2 = " « R2 « endl;
344
Глава 8. Приложения
Результат
сумма = 2*r*ul*u2*expB*t)-2*b*u3"B)*expB*t)+2*u3~B)*expB*t~)
Rl = -2*b+2
R2 = 2*r
Из полученных результатов следует, что для равенства нулю Rl, R2 тп г
ется, чтобы b = 1 и г = 0. У~
8.7.2. Сферические гармоники
Сферические гармоники являются комплексными однозначными функциям
определенными на поверхности единичной сферы, т. е. функциями двух вещ
ственных аргументов 0<в<тг,0<ф< 2л\ Они определяются выражения^
¥ы(в,ф) :=(-!)'
{21 + 1){1-тп)\
P?n{cose)eim't', m>0,
4п{1 + m)\
где P™(w) —ассоциированные функции Лежандра, определяемые как
^(||,):=A-ш»)Н/а__АИ|
с I = 0,1, 2,... и тп = —I, —1 + 1,... ,1. Для отрицательных значений тп мы
имеем
Ът(в,ф) = (-1)т¥1%т{е,ф),
где звездочка (*) обозначает комплексное сопряжение. Сферические гармоники
удовлетворяют соотношениям ортонормированности
[у1:т.{о,ф)У1т(о,ф)м= [ йф( d0sin0y;m,@,0)y;m(M),
J Jo Jo
где мы обозначаем dfl = sin 9с19Aф и f П означает, что мы интегрируем по всему
пространству угловых переменных (в,ф), а именно
/г2тх />тг
П= йф / dB sin в.
Jo Jo
Сферические гармоники образуют базис в гильбертовом пространстве US2)-
где
S2 := {(xi,x2,x3) : x'f + xl+xl = l}.
Первые несколько сферических гармоник суть следующие:
Уоо(М) = 4=, Уи(в,ф) = -Wi-sin0e*,
V47T V 07Г
Y10{9, ф) = J~ cos в, Fj_! (fl, 0) = ,/A sin flfi"'*.
V 47Г V 07Г
Любая функция в гильбертовом пространстве L2(S2) может быть записана
виде линейной комбинации сферических гармоник.
8.7. Класс Symbolic и символьное дифференцирование
345
// spberic-cPP
4include <iostream.h>
!include "MSymbol.h"
include "Rational.h"
include "AssLegendre.h"
Sum<Rational<int> > PI("PI",0), I("I",0);
int factorial(int n)
{
int i, result=l;
for (i=2; i<=n; i++) result *= i;
return result;
}
Sum<Rational<int> > Y(int 1, int m, const Sum<Rational<int> > &phi,
const Sum<Rational<int> > &w)
{
Sum<Rational<int> > a, b, u("u",0), result;
int absm = abs(ra);
AssLegendre A(l,m,u);
a = A.current(); a.put(u,cos(w));
if (m>0 && ra7.2) a = -a;
b = sqrt(Rational<int>(B*1+1)*factorial(l-absm),
4*factorial(l+absm))/PI);
result = a*b*exp(I*Rational<int>(m)*phi);
return result;
}
void main()
{
int i, j, n=3;
Sum<Rational<int> > phiO'phi",0), w("w",0), result, one(l);
for (i=o; i<=n; i++)
{
for (j=-i; j<=i; j++)
{
result = Y(i,j,phi,w);
result.put(cos(w)*cos(w),one-sin(w)*sin(w));
cout « "Y(" « i « "," << j << ") = " « result « endl;
}
cout << endl;
}
346
Глава 8. Приложения
Результат
Y@,0) = sqrt(l/4*PI~(-l))
Y(l,-1) = sin(w)*sqrtC/8*PI~(-l))*exp(-I*phi)
YA,0) = cos(w)*sqrtC/4*PI~(-l))
Y(l,l) = -sin(w)*sqrtC/8*PI~(-l))*exp(I*phi)
YB,-2) = 3*sin(w)"B)*sqrtE/96*PI-(-l))*exp(-2*I*phi)
YB,-l) = 3*cos(w)*sin(w)*sqrtE/24*PI"(-l))*exp(-I*phi)
YB,0) = sqrtE/4*PI-(-l))-3/2*sin(w)-B)*sqrtE/4*PI"(-l))
YB,l) = -3*cos(w)*sin(w)*sqrtE/24*PI"(-l))*exp(I*phi)
YB,2) = 3*sin(w)"B)*sqrtE/96*PI"(-l))*expB*I*phi)
YC,-3) = 15*sin(w)-C)*sqrtG/2880*PI"(-l))*exp(-3*I*phi)
YC,-2) = 15*cos(w)*sin(w)~B)*sqrtG/480*PI~(-l))*exp(-2*I*phi)
YC,-l) = 6*sin(w)*sqrtG/48*PI"(-l))*exp(-I*phi)
-15/2*sin(w)-C)*sqrtG/48*PI~(-l))*exp(-I*phi)
YC,0) = cos(w)*sqrtG/4*PI~(-l))
-5/2*cos(w)*sin(w)~B)*sqrtG/4*PI~(-1))
YC,l) = -6*sin(w)*sqrtG/48*PI"(-l))*exp(I*phi)
+15/2*sin(w)"C)*sqrtG/48*PI"(-l))*exp(I*phi)
YC,2) = 15*cos(w)*sin(w)"B)*sqrtG/480*PI*(-l))*expB*I*phi)
YC,3) = -15*sin(w)-C)*sqrtG/2880*PI~(-l))*expC*I*phi)
8.7.3. Небесная механика
В небесной механике, фазовое пространство натянуто на n-ку динамических
переменных щ для г = 1,... , п. Уравнения движения небесной механики (т. с.
автономная система обыкновенных дифференциальных уравнений первого
порядка) могут быть построены следующим образом. Пусть Ik : Rn —> R- Д|Я
к — 1,... , п — 1 являются гладкими функциями, тогда
<1щ _ d(uj,h,... ,/n-i) /g v;)
dt 9(ubu2, ■ • ■ ,«»)
где d(ui, Ii,... , In-i)/d(ui, щ,... un) обозначает якобиан. Следовательно.
уравнения движения могут также быть записаны в виде (учитывая соглашение
о суммировании)
dui я г я г
-j7" — eijk...lOjll ■ ■ .C1J71-I)
где tijk...i —универсальный символ Леви—Чивитаи dj ~ d/duj. Доказате-П-'
ство того, что 1\,... , /„_! — первые интегралы системы (8.8), следующее. 1<1К
как (с учетом соглашения о суммировании)
dli dli duj
dt duj dt
К "i;irc Syiuliolic /i гпмич. ii.iihc л'к/х/х'роииириплшк'
3-47
^""Д.,,,,.. . ,л.м m, , ., = 0(/../1-^MZi=()-
— = (<V<b-'i...'»-.(u>'i)---(a'..--.J»-i) = •-),„
»„
.. якобиан имеет два рапных столбца, он вырожденный. Если первые
^ как . „ ..,
,ятегРа
Х;гК
упы являются многочленами, то динамическая система (8.8) алгебра-
,/шостью интегрируема. Для случая п — 3 мы получаем уравнения
„чески по.
движения
д dm _ 0LLdI1_dILdI1 <lw, _ Oh Oh Oh Oh
lit 0v-> Дни O112D112 (It Ou.'Oiii ди\0и^'
•la* ~ illh^k __ ^IiBIi
ill Dii-i дч-> дч-i Oti\
Как прнм(>Р- мы рассматриваем небесную механику следующих первых
интегралов:
h = + + "-j и /i = "i":t-
Программа нснольлует систему {8.8). чтобы построить 3 х 3 матрицы и
вычислить их детерминанты:
/ да, Oh Oh \
da,
7Г
(let
ди\ Oti\ On,
On, 0h_ 0h_
Oiii dn-2 O112
Oil, Oh Oh
V dil;\ 0ил Ои-Л '
для i, = J. 2. 3.
// nambu.cpp
#include <iostream.h>
«include "MSymbol.h"
«include "Vector.h"
«include "Matrix.h"
v°id nambu(Vector<Sum<double> > I, Vector<Sum<double> > u, int n)
iat i, J;
Matrix<Sum<double> > J(n,n);
for (i=o; i<n; i++)
for (j = l; j<n; j++) J[i][j] = df(I[j-l], u[i]);
for (i=0; i<n; i++)
{
for (j=0; j<n; j++) J[j][0] = df(u[i], u[j]);
cout << "du(" << i << ")/dt = " << J.determinant() << endl;
348
Глава 8. Приложения
void mainO
i
Sum<double> ul("ul",0), u2("u2",0), u3("u3",0);
Vector<Sum<double> > 1B), uC);
u[0] = ul; u[l] = u2; u[2] = u3;
I[0] = ul + u2 + u3; I[l] = ul * u2 * u3;
nambu(I,u,3);
>
Результат
du@)/dt = ul*u2-ul*u3
du(l)/dt = -ul*u2+u2*u3
duB)/dt = ul*u3-u2*u3
8.7.4. Разложение в ряд Тейлора дифференциальных уравнений
Рассмотрим обыкновенное дифференциальное уравнение первого порядка
— = f(x,u), u(x0) = u0,
где / — аналитическая функция переменных х и и. Разложение в ряд Тейлора
дает аппроксимацию решения в окрестности х = xq и решение
дифференциального уравнения задается выражением
u(x0 + h) = u(x0) + h(-) + { ) +....
Производные могут быть получены последующим дифференцированием
уравнений
d?u _ dj_ d£du_ dj_ dj_
dx2 дх ди dx дх ди '
dx3 дх2 дхди ди2 ди \ дх ди
Формулы для высших производных становятся очень усложненными. В
качестве примера рассмотрим дифференциальное уравнение Риккати
— =и2+х, и@) = 1.
dx
Это уравнение не имеет решения в элементарных функциях. Для его решен»
необходимо использовать функции Бесселя. Предположим, что решение мо*1
разложить в ряд Тейлора,
8.7. Класс Symbolic и символьное дифференцирование 349
({СЛим все последующие производные
d2u du dzu _ fdu\ d2u
dx2 dx ' dx3 \dxj dx2'
производные могут быть найдены с использованием компьютерной алгеб-
Затем выбираем начальное значение и получаем разложение в ряд Тейлора
яоть ДО порядка п. В примере 1 мы решаем дифференциальное уравнение
чяскати, используя ряды Тейлора вплоть до порядка п — 4 с начальным зна-
ряием м@) = 1- Решение имеет вид
, ^ 1 3 .> 4 з 17 4
и(х) = 1 + х + -х~ + -хЛ + —ж4 + ... .
Пример 1
// Taylorl.cpp
«include <iostream.h>
«include "MSymbol.h"
«include "Vector.h"
«include "Rational.h"
int factorial(int N)
int result=l;
for (int i=2; i<=N; i++) result *= i;
return result;
}
void main()
{
int i, j, n=4;
Rational<int> one(l);
Sum<Rational<int> > u("u",0), x("x",0), result;
Vector<Sum<Rational<int> > > uO(n), y(n);
U.depend(x);
U0[0] = u*u+x;
for(j=l; j<n; j++) U0[j] = di(uO[j-l],x);
cout « uO << endl;
// начальное условие u@)=l
X.set(O); u.set(l);
U0[0] = u0[0].value();
350
Глава 8. Приложения
у[0] = и;
for (i=l; i<n; i++) y[i] = df (у [i-1] ,x) ;
// подстановка начальных условий
for (i=l; i<n; i++)
{
for (j=i; j>0; j~)
u0[i] .put(y[j],uO[j-l]);
uO[i] .expandQ ;
}
cout « uO « endl;
u = one;
cout << uO << endl;
// разложение в ряд Тейлора
result = one;
for (i=0; i<n; i++)
result += Rational<int>(l,factorial(i+l))*uO[i]*power(x,i+l)
cout « "u(x) = " « result « endl;
}
Результат
[u"B)+x]
[2*u*df(u,x)+l]
[2*df(u,x)"B)+2*u*df(df(u,x),x)]
[6*df(u,x)*df(df(u,x),x)+2*u*df(df(df(u,x),x),x)]
[1]
[2*u+l]
[2+4*u~B)+2*u]
[16*u+6+8*u~C)+4*u"B)]
[1]
[3]
[8]
[34]
u(x) = l+x+3/2*x-B)+4/3*x-C)+17/l2*x"D)
В качестве другого примера рассмотрим нелинейную задачу Коши
du ., 9
— = х + хи — и , где и@) = 1.
8.7. Класс Symbolic и символьное дифференцирование 351
чНо просто, хотя и скучно, вычислить все производные:
^ d2u „ du du
—- = 2х + u + х- Zu — ,
dxz ax dx
d2u „ „du dru „ / du\ „ d2u
da:3 dx da'2 \dx/ dx2'
d4u d2u d3u dudru d3u
dx4 dx2 dx3 dx dx2 dx3'
g точке x = 0, u(z = 0) = 1 имеем
Окончательно зависимость начального значения u(x) вблизи х = 0 задается
рядом
, s л 3 2 4 з 43 4
и{х) = 1 - х + -хг - -х3 + —х4 + ... .
В следующем примере мы воспроизводим процедуру вычисления,
использующего нашу систему компьютерной алгебры.
Пример 2
// Тау1ог2.срр
«include <iostream.h>
«include "MSymbol.h"
«include "Vector.h"
«include "Rational.h"
int factorial(int N)
{
int result=l;
}
for (int i=2; i<=N; i++) result *= i;
return result;
v°id main()
int i, j, n=4;
Rational<int> zero@), one(l);
Sum<Rational<int> > x("x",0), u("u",0), result;
Vector<Sum<Rational<int> > > u0(n), w(n);
U.depend(x);
U0[0] = x*x + x*u - u*u;
352
Глава 8. Приложения
for (i=l; i<n; i++) uO[i] = df(uO[i-l],x);
cout << uO << endl;
// начальное условие u@)=l
x.set(O); u.set(l);
u0[0] = u0[0].value();
w[0] = u;
for (i=l; i<n; i++) w[i] = df(w[i-l],x);
// подстановка начального условия
for (i=l; i<n; i++)
{
for (j=i; j>0; j —) uO[i] .put (w[j] ,uO[j-l] ) ;
uO[i].expand();
}
cout « uO « endl;
x = zero; u = one;
cout << uO << endl;
// Разложение в ряд Тейлора
х.clear();
result = one;
for (i=0; i<n; i++)
result += Rational<int>(l,factorial(i+l))*uO[i]*power(x,i+
cout « "u = " « result « endl;
>
Результат
[x"B)+x*u-u"B)]
[2*x+u+x*df(u,x)-2*u*df(u,x)]
[2+2*df(u,x)+x*df(df(u,x),x)-2*df(u,x)~B)-2*u*df(df(u,x),x)]
[3*df(df(u,x),x)+x*df(df(df(u,x),x),x)-6*df(u,x)*df(df(u,x),x)
-2*u*df(df(df(u,x),x),x)]
[-1]
[x+3*u]
[x"B)+x*u-2-6*u'B) ]
[7*x+31*u+x~C)-x~B)*u-8*x*u~B)+12*u~C)]
[-1]
[3]
[-8]
[43]
u = l-x+3/2*x~B)-4/3*x-C)+43/24*x-D)
8.7. Класс Symbolic и символьное дифференцирование 353
е Коммутатор двух векторных полей
" tdhm автономную систему обыкновенных дифференциальных уравне-
Flfпервого порядка
^ dx
.г . g_n _> R и j = 1,2,... , п. Мы считаем, что функции V} аналитические.
Г^ лАеренциальным уравнением мы можем ассоциировать векторное поле
Цусгъ W - другое векторное поле
Мы определяем коммутатор V и W следующим образом:
[vvn^yyfv^-w^-L
ккл *** jdXj)dXk-
Находим, что коммутатор обладает следующими свойствами:
• \V,W] = -[W,V]
• \V,U + W] = [V,U] + [V,W]
• Тождество Якоби [[V,W],U] + [[U,V],W] + [[W,U],V] = О,
где [/ — аналитическое векторное поле в R". Аналитические векторные поля
образуют алгебру Ли.
В следующей программе мы рассматриваем три векторных поля V,W,U и
показываем, что они удовлетворяют трем свойствам, указанным выше.
// coma. срр
•include <iostream.h>
•include "MSymb'ol.h"
•include "Vector.h"
J0nst int n = 3;
Vector<Sum<int> > x(n);
ctor<Sum<int>> commutator(Vector<Sum<int>> V,Vector<Sum<int>> W)
int -j k.
ector<Sum<int» U(V.length() ,0);
r (k=0; k<n; k++)
f°t (j=0; j<n; j++)
12-.548
354
Глава 8. Приложения
U[k] += V[j]*df (W[k],x[j]) - W[j]*df(V[k],x[j]); ^
return U;
}
void main()
{
Vector<Sum<int> > V(n), W(n), U(n), Y(n);
Sum<int> xl("xl",0), x2("x2",0), x3("x3",0);
x[0] = xl; x[l] = x2; x[2] = x3;
V[0] = xl*xl; V[l] = x2*x3; V[2] = x3*x3;
W[0] = x3; W[l] = xl-x2; W[2] = x2*x3;
U[0] = x2*xl; U[l] = x2; U[2] = xl-x3;
// [V,W] = -[W,V]
Y = commutator(V,W) + commutator(W,V);
cout << Y << endl; // выводится @,0,0)
// [V.U+W] = [V,U] + [V,W]
Y = commutator(V,U+W) - (commutator(V,U)+commutator(V,W));
cout << Y << endl; // выводится @,0,0)
// тождество Якоби [[V,W],U] + [[U,V],W] + [[W,U],V] = 0
Y = commutator(commutator(V,W),U)
+ commutator(commutator(U,V),W)
+ comnmtator(commutator(W,U),V) ;
cout << Y << endl; // выводится @,0,0)
}
8.7.6. Производная Ли и векторное поле Киплинга
Пусть М есть риманово многообразие с метрическим тензорным полем
4 4
9 = 53 5Z 9jk (x)dxj <g> dxk
j=i Jt=i
и V есть гладкое векторное поле, определенное в М. Векторное поле V
называется векторным полем Киллинга, если
Lvg = 0,
где Lyg обозначает производную Ли от g по направлению V. Производная •
линейна, т. е.
LV(T + S) = LVT + LVS,
где Т и S суть (г, s) тензорные поля. Кроме того, производная Ли подчиняет
правилу произведения
LV(T ® 5) = (LVS) ®T + S® (LVT),
8.7. Класс Symbolic и символьное дифференцирование 355
гДе
С есть (г, л) тензорное поле и Т есть (р, q) тензорное поле. Наконец, мы
ц»»(
[ееМ
в локальных координатах
dVi
Lvdxj = d{V\dxj) = dVj = 2J -к^-dxk,
Jfc=l
dxk
V задается соотношением
Окончательно мы получаем
В программе мы рассматриваем метрическое тензорное поле Гёделя
g = dx\ с*3 (iii - - expB2ri)da;2 ® с?ж2 + <£с3 ® <&з ~ <^е4 ® dx\
— exp(xi)da;2 ® di4 ~ exp(xx)dx4 ® ^2-
Метрическое тензорное поле может быть также записано в матричной форме:
/ 1 0 0 0 \
n _ ovn^T, wo n _ ovr^T-. ^
(<Ы =
О -expBa:i)/2 0 -exp(i1)
1 О
О -1
2ехр -жх)-—
02Г2 02Г4
О О
У 0 -exp(xi)
Мы показываем, что векторное поле
*ть поле Киллинга тензорного поля д.
II kill.cpp
•include <iostream.h>
•include "Vector.h"
•include "Matrix.h"
include "Rational.h"
delude "MSymbol.h"
J°id main()
lnt j, k, 1, N=4;
Rational<int> zero(O), one(l), halfA,2), twoB);
Matrix<Sum<Rational<int> > > g(N,N), Lg(N,N,zero);
Vector<Sum<Rational<int> > > V(N), x(N);
SUOi<Rational<int> > xlC'xl",0),x2("x2",0),x3("x3",0),x4("x4",0);
356
Глава 8. Приложения
х[0] = xl; х[1] = х2; х[2] = хЗ; х[3] = х4;
// метрика Гёделя
g[0][0] = one; g[0] [1] = zero; g[0] [2] = zero; g[0] [3] = ZerQ
g[l][0] = zero; g[l] [1] = -half *exp(two*xl) ; °'
g[l][2] = zero; g[l] [3] = -exp(xl);
g[2][0] = zero; g[2] [1] = zero; g[2] [2] = one; g[2][3] = Zer0-
g[3][0] = zero; g[3] [1] = -exp(xl); g[3] [2]=zero; g[3] [3]=-01lg.
// векторное поле Киллинга для метрики Гёделя
V[0] = х2; V[l] = exp(-two*xl) - half*x2*x2;
V[2] = zero; V[3] = -two*exp(-xl);
// производная Ли
for (j=0; j<N; j++)
for (k=0; k<N; k++)
for A=0; 1<N; 1++)
Lg[j][k] += V[l]*df(g[j][k],x[l]) + g[l][k]*df(V[l],x[j])
+ g[j][l]*df(V[l],x[k]);
cout << " метрика Гёделя, g\n" « g << endl;
cout « " векторное поле Киллинга для метрики Гёделя, V\n"
<< V << endl; cout << endl;
cout « " производная Ли g по напрявлению V, Lg\n"
« Lg << endl;
}
Результат
метрика Гёделя, g
[10 0 0]
[0 -l/2*expB*xl) 0 -exp(xl)]
[0 0 10]
[0 -exp(xl) 0 -1]
векторное поле Киллинга для метрики Гёделя,
[х2]
[exp(-2*xl)-l/2*x2-B)]
[0]
[-2*ехр(-х1>]
производная Ли g по напрявлению V, Lg
[0 0 0 0]
[0 0 0 0]
[0 0 0 0]
[0 0 0 0]
8.8. Класс Matrix
357
g Класс Matrix
! Норма Гильберта—Шмидта
ля В — Две произвольные матрицы пхп над R. Определим
1ЙЛ1Ь (A,B):=ti(ABT),
„т обозначает транспозицию В и tr() обозначает след матрицы. Мы видим,
ЧТО
(А,В)>0,
(А,В) = (В,А),
(сА,В) = с(А,В),
(А1+А2,В) = (А1,В) + (А2,В),
яес £ R» Таким образом, (А, В) определяет скалярное произведение для пхп
матриц над R- Скалярное произведение порождает норму, которая задается
формулой
\\А\\ ~ у/{£А) =
\
4=1 j = l
Норма называется нормой Гильберта—Шмидта. Эти результаты могут быть
распространены на случай бесконечного измерения при условии наложения
ограничения
оо оо
ЕЕМ2<°°-
Если бесконечномерная матрица А удовлетворяет этому условию, то мы
называем А оператором Гильберта—Шмидта.
В следующей программе мы рассматриваем символьную 2x2 матрицу
У о а
Иорма реализуется в классе Matrix, который описан в гл. 6.
'' Hilbert.срр
include <iostream.h>
#include "Matrix.h"
•include "MatNorm.h"
•include "MSymbol.h"
J°id main()
iat n = 2;
snm<double> a("a",0), b("b",0), yl;
Matrix<Sum<double> > A(n,n);
358
Глава 8. Приложения
А[0][0] = а; А[0][1] = Ъ;
А[1][0] = Ъ; А[1][1] = а;
cout « п << "х" << п « " матрица A is \n" << А << endl;
yl = normH(A);
cout << "Норма Гильберта-Шмидта матрицы А есть" << yl << еас>,
cout << endl;
а = 2.0; b = 3.0;
cout << " Положим а = " << a « " и b = " << b << endl;
cout << " Норма Гильберта-Шмидта матрицы А есть "
<< yl << " или " << у Lvalue () « endl;
}
Результат
2x2 матрица А есть
[а Ъ]
[Ъ а]
Норма Гильберта-Шмидта матрицы А есть sqrtB*a~B)+2*b~B))
Положим а = 2 и b = 3
Норма Гильберта-Шмидта матрицы А есть sqrtB6) или 5.09902
8.8.2. Пара Лакса и гамильтонова система
Рассмотрим функцию Гамильтона (решетка Тода)
Н{р, q) = - (Pi + р\ + pi) + exp(qt - q2) + exp(92 - Яг) + exp(q3 - ft).
где р, q — векторы импульса и координат, соответственно. Уравнения
движения Гамильтона суть следующие:
dt dqj' dt dpj'
Вводя величины
aJ := 2 eXp ( 2 ^ ~ Qj+14 ' bj := 2Pj'
и зацикливая индексы (т.е. #4 = qx), мы находим, что уравнения дви^е
Гамильтона принимают форму (с аз = 0)
da,j dbi 2 db2 2 2ч dbs 0 2 (8.9)
— = Mb,-bJ+i), ^ = ~2au _ = 2(а1-а2), -^ = 2а2,
8.8. Класс Matrix
359
0
dl
0
-ai
0
a2
0
-a2
0
1 2. Теперь введем матрицы
/iii ai о \
I := ai b2 a2 , -A :=
\ 0 a2 b3 /
. енные значения матрицы L являются константами движения, следо-
пьно и коэффициенты характеристического многочлена также являются
Ва тантами движения. Уравнения движения (8.9) могут быть переписаны в
Представлении Лакса:
^ = [A,L](t). (8.10)
Можно показать, что первые интегралы системы имеют вид
tr(Ln), гдеп= 1,2,... .
В следующей программе мы демонстрируем, что уравнения (8.9) и (8.10)
эквивалентны. Во второй части программы мы показываем, что
tr(L) = h + b2 + b3 и tr(Ln) = Ь\ + Ъ\ + Ь\ + 2а\ + 2а\
— первые интегралы. Определитель матрицы L,
det(L) = bib2b3 -Ьуа\ - Ьъа\
также является первым интегралом системы. Может ли det(L) выражаться в
виде tr(Ln) для некоторого п > 0? Заметим, что det(Z-) — произведение
собственных значений для L, а tr(L) — сумма собственных значений для L.
II Lax.срр
«include <iostream.h>
«include "MSymbol.h"
«include "Matrix.h"
«include "Rational.h"
void main()
{
Matrix<Sum<Rational<int> > > LC,3), AC,3), LtC,3); //Lt==dL/dt
sum<Rational<int> > al("al",0), a2("a2",0),
bl("bl",0), Ь2("Ь2",0), ЬЗ("ЬЗ",0),
alt, a2t, bit, b2t, b3t;
Rational<int> zero@);
L[°] CO] = bl; L[0][1] = al; L [0] [2] = zero;
j-^][0] = al; L[l][l] = b2; L[l][2] = a2;
^2][0] = Zero; L[2][l] = a2; L[2][2] = b3;
*t0][0] = Zero; A[0][1] = -al; A[0][2] = zero;
J[l][0] = al; A[l][l] = zero; A[l][2] = -a2;
At2][Q] = zero; A[2][l] = a2; A[2] [2] = zero;
360
Глава 8. Приложения
Lt = A*L - L*A; cout « "Lt =\n" « Lt « endl;
bit = Lt[0][0]; b2t = Lt[l][l]; b3t = Lt [2] [2] ;
alt = Lt [0] [1] ; a2t = Lt [1] [2] ;
cout « "bit = " « bit « ", b2t = " « b2t « ", b3t = "
<< b3t << endl;
cout « "alt = " « alt « ", a2t = " « a2t « endl;
cout << endl;
// Покажем, что I[0],I[1],I[2] являются первыми интегралами
int i, n=3;
Sum<Rational<int> > result;
Vector<Sum<Rational<int> > > I(n);
I[0] = L.trace(); cout « "I[0] = " « I[0] « endl;
I[l] = (L*L).trace(); cout « "I[l] = " « I[l] << endl;
I[2] = L.determinant(); cout « "I[2] = " « I[2] « endl;
cout << endl;
for (i=0; i<n; i++)
•C
result = blt*df(I[i],bl) + b2t*df(I[i],b2) + b3t*df(I[i],b3)
+ alt*df(I[i],al) + a2t*df(I[i],a2);
cout « "результат" << i+1 << " = " << result << endl;
>
}
Результат
Lt =
[-2*al~B) -al*b2+bl*al 0]
[al*bl-b2*al 2*al~B)-2*a2~B) -а2*ЬЗ+Ь2*а2]
[0 а2*Ь2-ЬЗ*а2 2*а2~B)]
bit = -2*al"B), b2t = 2*al~B)-2*a2~B), b3t = 2*a2~B)
alt = -al*b2+bl*al, a2t = -а2*ЬЗ+Ь2*а2
I[0] = bl+b2+b3
I[l] = bl~B)+2*al-B)+b2-B)+2*a2~B)+b3~B)
I[2] = bl*b2*b3-bl*a2-B)-al~B)*b3
результат1 = 0
результат2 = 0
результатЗ = 0
В этой программе Lt соответствует dL/dt, a bit, b2t, b3t, alt, а2*
ветствуют dbl/dt, db2/dt, dbd/dt, dal/dt, da2/dt.
8.8. Класс Matrix
361
R ^еденные результаты показывают, что (8.9) эквивалентно (8.10) при
«ении dbl/dt с @,0)-м, db2/dt с A,1)-м, и т.д. элементами матрицы L. Да-
сРа е3уЛьтат1 = 0, результат2 = 0, результатЗ = 0 указывают на то, что
Яее' j[i] и 1[2] являются первыми интегралами системы.
« 3. Аппроксимация Паде
а степенной ряд функции расходится, функция имеет особенности в
нерпой области. Аппроксимация Паде — это полиномиальная дробь, которая
рЖИт ту же информацию, что и степенной ряд на интервале, часто с
информацией о существовании особенностей. В [N, М] аппроксимации Паде чи-
яитель имеет степень М и знаменатель — степень N. Коэффициенты
определяются приравниванием членов с равными степенями х в следующем
уравнении:
f(x)Q(x) - Р{х) = AxM+N+l + BxM+N+2 + ... с Q@) = 1,
где P{x)/Q(x) есть [N,M] аппроксимация Паде для f(x).
Предположим, что решение дифференциального уравнения можно
выразить отрезком ряда Тейлора до k-го порядка:
f(x) — ао + ахх + а2х2 + ... + а^хк.
Тогда [N, М] аппроксимация Паде Pff (x), явно выраженная через коэффици-
[N,M](x):=
det
ам-
/V+l O-M-N+2
о-м а-м+i
Lj=/V 0-j-NXJ Lj=N Clj-N+i Xl
det
ам-N+i um-n+2 ■ ■ ■
o-M ам+i
XN I№-[
O-M+l
ам+N
■s-^M j
■ ■ ■ Zjj=0 aJX
a-M+i
ам+N
1
" + M + 1 = к. Отметим, что aj = 0 при j < 0, и суммы, у которых началь-
9,51 граница суммирования больше конечной границы суммирования, должны
Рйравниваться нулю. Часто Р^{х) сходится к истинному решению диффе-
*^нЦиального уравнения при N, М —> оо даже при наличии расходимости ряда
Тейл,
И;
аде.-
ора решения. Обычно мы рассматриваем сходимость последовательности
{P0J(x),P1J+l(x):
р/+а(*),...}
лЬко при М = N + J, a J считается постоянной при N —> со. Специальная
^ЗДовательность с J = 0 называется диагональной последовательностью.
362
Глава 8. Приложения
В следующей программе мы вычисляем аппроксимацию Паде [1 ]1 г0
[3,3] для функции ' L
2].
f{x) = sin(z) = Y^
fc=0
Bfc+l)!
x - -x° +
6
Дополнительно аппроксимация Паде [2,2] может быть вычислена как еле
щая: ДУ10~
[2,2] =
с ао = 0, ах = 1, а2 = 0, аз = —1/6 и а4 = 0. Мы определяем, что
[2>2] = ~~.
det
ai
аг
аох2
аг
а3
аз а4
аож 4- агх2 olq-\-aix + а,2Х2
det
ах а2 а3
аг аз а4
X2 X 1
1 +
// Pade.ерр
#include <iostream.h>
#include "MSymbol.h"
«include "Rational.h"
#include "Vector.h"
«include "Matrix.h"
Sum<Rational<int> >
Taylor(Sum<Rational<int> > u, Sum<Rational<int> > &x, int n)
•C
x.set(Rational<int>@));
Sum<Rational<int> > series(u.value());
int j, fac = 1;
for (j=l; j<=n; j++)
•c
u = df(u,x); fac = fac * j;
series += Rational<int>(u.nvalue(),fac) * power(x.j);
}
return series;
}
Sum<Rational<int> >
Pade(const Sum<Rational<int> >&f,Sum<Rational<int>> fex.int N,int
{
int i, j, k, N1 = N+l, Ml = M+l, n = M + N1;
M)
8.9. Классы Array и Symbolic
363
oational<int> zero(O);
gtun<Rational<int> > y, z;
i/ector<Sum<Rational<int> > > a(n) .
Matrix<Sum<Rational<int> > > P(N1,N1), Q(N1,N1);
y = Taylor(f,x,n);
for(i=°; i<n; i++) ati] = y-coeff(x,i);
for(i=0; i<N; i++)
for(j=0; j<Nl; j++)
{
к = M-N+i+j+1;
if (k >= 0) P[i][j] = Q[i][j] = a[k];
else P[i][j] = Q[i][j] = zero;
}
for (i=0; i<Nl; i++)
{
for (j=N-i; j<Ml; j++)
{
к = j-N+i;
if (k >= 0) P[N][i] += a[k]*power(x,j);
}
Q[N][i] = power(x,N-i);
}
у = P.determinant();
z = Q.determinant();
return y/z;
}
void main()
{
Sum<Rational<int> > x("x",0), f, g;
£ = sin(x);
cout « Pade(f,x,l,l) « endl; // => x
cout « Pade(f,x,2,2) « endl; // => -l/6*x*(-l/6-l/36*x~2)*(-1)
cout « Pade(f,x,3,3) « endl;
// => (-7/2160*x+1589885/3306816*x)*(-7/2160-7/43200*x-2)"(-l)
•9. Классы Array и Symbolic
•!• Псевдосферические поверхности и уравнения солитонов
ь
Разделе 3.2.2 мы описали, как находить уравнение синуса Гордона для метри-
ского тензорного поля. Была дана реализация с использованием REDUCE.
4есь мы дадим реализацию, использующую SymbolicC++. В нашем примере
"Ъявляется как переменная, зависящая от xl и х2. Оператор df О обозна-
364
Глава 8. Приложения
чает дифференцирование. Так как члены типа cos2 (х) и sin2 (x) появляю
результате наших вычислений, мы используем тождество ч в
cos2 (х) + sin2(x) = 1
для упрощения выражения. Это делается с использованием функции putn
MSymbol.h. йз
// tensor.cpp
#include <iostream.h>
#include "Matrix.h"
#include "Array.h"
#include "MSymbol.h"
void main0
{
int a,b,m,n,c,K=2;
Matrix<Sum<double> > g(K,K),
gl(K,K); // обратная матрица к g
Arrayl<Sum<double> > x(K);
Array2<Sum<double> > Ricci(K,K), Riccil(K,K);
Array3<Sum<double> > gamma(K,K,K);
Array4<Sum<double> > R(K,K,K,K);
Sum<double> u("u",0), sum, RR,
xl("xl",0), x2("x2",0);
g[0][0] = 1; g[0][l] = cos(u);
g[l][0] = cos(u); g[l][l] = 1;
x[0] = xl; x[l] = x2;
gl = g.inverse();
u.depend(xl); u.depend(x2);
for (a=0; a<K; a++)
for (m=0; m<K; m++)
for (n=0; n<K; n++)
{
sum = 0;
for (b=0; b<K; b++)
sum += gl[a][b]*(df(g[b][m],x[n]) + df (g[b] [n] ,x[m])
- df(g[m][n],x[b]));
gamma[a][m][n] = 0.5*sum;
cout « "gammaC << a << "," « m « "," << n « ") = "
8.9. Классы Array и Symbolic
Cout
<< gamma[a][m][n] << endl;
}
<< endl;
for(a=0; a<K; a++)
for(m=0; m<K; m++)
for(n=0; n<K; n++)
for(b=0; b<K; b++)
{
R[a] [m] [n] [b] = df (gamma[a] [m] [b] ,x[n])
-df (gamma [a] [m] [n] ,x[b]) ;
for(c=0; c<K; С++)
{
R[a] [m] [n] [b] += gamma [a] [c] [n]*gamma[c] [m] [b]
- gamma [a] [c] [b] *gamma[c] [m] [n] ;
}
R[a] [m][n][b].put(cos(u)*cos(u), l-sin(u)*sin(u));
}
for(m=0; m<K; m++)
for(n=0; n<K; n++)
{
Ricci [m] [n] =0;
for(b=0; b<K; b++) Ricci[m][n] += R[b][m][b][n];
cout << "Ricci(" « m << "," << n << ") = "
« Ricci [m][n] « endl;
}
cout « endl;
for(m=0; m<K; m++)
for(n=0; n<K; n++)
{
RiccilEm][n] = 0;
for(b=0; b<K; b++) RiccilEm][n] += gl[m][b]*Ricci[n][b] ;
}
RR = 0;
for(b=0; b<K; b++) RR += Riccil[b][b];
RR.put(cos(u)*cos(u), l-sin(u)*sin(u));
RR.expand();
RR.put(df(df(u,x2),xl),df(df(u,xl),x2));
cout « "R = " « RR « endl;
366
Глава 8. Приложения
Результат
gamma@,0,0) = -cos(u)*(-l+cos(u)~B))~(-l)*df(u,xO)*sin(u)
gamma@,0,l) = 0
gamma@,1,0) = 0
gamma@,l,l) = (-l+cos(u)"B))~(-l)*df(u,xl)*sin(u)
gamma(l,0,0) = (-l+cos(u)~B))~(-l)*df(u,x0)*sin(u)
gammaA,0,1) = 0
gamma(l,l,0) = 0
gamma(l,l,l) = -cos(u)*(-l+cos(u)~B))~(-l)*df(u,xl)*sin(u)
Ricci@,0) = -sin(u)~(-l)*df(df(u,x0),xl)
Ricci@,l) = -cos(u)*sin(u)~(-l)*df(df(u,x0),xl)
Ricci(l.O) = -cos(u)*sin(u)~(-l)*df(df(u,xl),x0)
Ricci(l,l) = -sin(u)~(-l)*df(df(u,xl),x0)
R = -2*sin(u)"(-l)*df(df(u,xl),x2)
8.10. Классы Polynomial и Symbolic
8.10.1. Метод Пикара
Мы рассмотрим метод Пикара аппроксимации решения дифференциального
уравнения
с начальным условием у(хо) = уо, где / — аналитическая функция х и у.
Интегрируя обе стороны уравнения, имеем:
у(х) =у0+ f(s,y(s))ds.
Jxq
Теперь, начиная с у$, можно итеративно использовать эту формулу для
достижения точного решения, если процесс сходится. Следующая аппроксимация
задается выражением
Уп+\{х) =Уо+ f(s,yn(s))ds.
Jхп
'ХО
В примере аппроксимируются решения линейного дифференциального ураБ~
нения
dy
-у- =х + у
ах
и нелинейного дифференциального уравнения
dV , 2
Т=х+у,
ах
используя пять и четыре шага метода Пикара. Начальные условия имеют вид-
у(х = 0) = 1. Мы также даем значение у(х = 2) для этих аппроксимаций- "
8.10. Классы Polynomial и Symbolic
367
е в этом классе. Во второй программе picardl.cpp мы используем инте-
'""оование в классе Symbol.
ой программе picard.cpp мы используем класс Polinomial и интегриро-
:рйрс
ц picard.cpp
#include "poly.h"
«include "rational.h"
iJlt main (void)
polyterm<Rational<Verylong> > x("x");
p0lynomial<Rational<Verylong> > pic(x);
RationaKVerylong> zeroC'O") ,one("l") ,two(") ;
int i;
cout « endl « " пять аппроксимаций для х+у : " « endl;
pic = one;
cout << pic << endl;
for(i=l;i<=5;i++)
{
//итегрируем и вычисляем в границах между х и zero
pic = one+Int(x+pic)-(Int(x+pic))(zero);
cout << pic << endl;
}
cout << "Аппроксимация в точке х=2 дает " << pic(two) << endl;
cout « endl << " пять аппроксимаций для х+у :" « endl;
pic = one;
cout « pic << endl;
for(i=l;i<=4;i++)
{
// итегрируем и вычисляем в границах между х и zero
pic = one+Int(x+(pic~2))-(Int(x+(pic-2)))(zero);
cout « pic « endl;
cout << » Аппроксимация в точке х=2 дает " << pic(two) << endl;
return 0;
}
__езУльтаты
ь аппроксимаций для х+у :
^2Ь-2+х+A)
и/6)х-3+х~2+х+A)
368
Глава 8. Приложения
A/24)х+A/3)х+х+х+A)
A/120)х~5+A/12)х-4+A/3)х~3+х-2+х+A)
A/720)х+A/60)х+A/12)х~4+A/3)х~3+х-2+х+A)
Аппроксимация в точке х=2 дает 523/45
пять аппроксимаций для х+у~2 :
A)
A/2)х+х+A)
A/20)х~5+A/4)х~4+B/3)х~3+C/2)х~2+х+A)
A/4400)х-11+A/400)х~10+C1/2160)х~9+B9/480)х~8+B33/1260)х-7
+A3/30)х-6+D9/60)х-5+A3/12)х-4+D/3)х-3+C/2)х-2+х+A)
A/445280000)х3+A/19360000)х~22+F07/997920000)х1
+(943/190080000)х0+B65897/8532216000)х9
+E69963/3592512000)х8+E506583/8143027200)х7
+A963837/798336000)х~16+A350761/174636000)х~15
+B967707/139708800)x-14+A90159/3706560)x~13
+A096099/9979200)х2+A16621/554400)х'-11
+B195/6048)х-10+F479/11340)х"9+A657/2016)х"8
+B71/252)х+A3/10)х-6+A7/12)х~5+A7/12)х
+D/3)х~3+C/2)х+х+A)
Аппроксимация в точке х=2 дает 295683385548145447/8696438938687Е
// spicard.cpp
#include "MSymbol.h"
#include "rational.h"
int main(void)
■C
Sum<double> x("x",0);
Sum<double> pic;
Sum<double> zero(O.O),oneA.0),twoB.0);
int i;
cout«endl<<" пять аппроксимаций для x+y :"<<endl;
x.set(O.O) ;
pic = one;
cout << pic << endl;
for(i=l;i<=5;i++)
■C
// итегрируем и вычисляем в границах между х и zero
pic = one+Int(x+pic,x)-Int(x+pic,x).valueQ;
cout << pic << endl;
}
8.10. Классы Polynomial и Symbolic
369
x.SetB.0);
t « " Аппроксимация в точке х=2 дает " << pic.value() << endl;
ut « endl << " пять аппроксимаций для х+у~2:" << endl;
1°set@.0);
pic = one;
cout « Pic « endl;
for(i=i;i<=4;i++)
{
// итегрируем и вычисляем в границах между х и zero
pic = one+Int(x+pic*pic,x)-Int(x+pic*pic,x).valueQ;
cout « pic << endl;
}
x.setB.0);
cout << " Аппроксимация в точке х=2 дает " << pic.value() << endl;
return 0;
}
Результаты
пять аппроксимаций для х+у:
1
1+0.5*х"B)+х
1+х"B)+х+0.166667*х~C)
1+х~B)+х+0.333333*х~C)+0.0416667*х"D)
1+х"B)+х+0.333333*х~C)+0.0833333*х"D)+0.00833333*х~E)
1+х"B)+х+0.333333*х~C)+0.0833333*х"D)+0.0166667*х~E)
+0.00138889*х~F)
Аппроксимация в точке х=2 дает 11.6222
пять аппроксимаций для х+у:
1
1+0.5*х~B)+х
1+1•5*х~B)+х+0.666667*х~C)+0.05*х~E)+0.25*х~D)
1+1.5*х~B)+х+1.33333*х~C)+1.08333*х-D)+0.433333*х~F)
+0.816667*х"E)+0.0604167*х"(8)+0.184921*х-G)+0.0143519*х~(9)
+0.000227273*х"A1)+0.0025*х-A0)
1+1•5*х~B)+х+1.33333*х~C)+1.41667*х~D)+1.41667*х~(Б)
+1•0754*х"G)+1. 3*х~F)+0.57134*х~(9)+0.821925*х~(8)
+0.36293*х~A0)+0.109838*х~A2)+0.210355*х~A1)+0.0212421*х-A4)
+0-0513034*х"A3)+0.00773472*х~AБ)+0.00245991*х-A6)
+0-000158653*х-A8)+0.000676233*х~A7)+4.96107е-006*х~B0)
+3Л1639е-005*х"A9)+6.08265е-007*х"B1)+2.24578е-009*х-B3)
+Б-1б529е-008*х~B2)
Ппроксимация в точке х=2 дает 3400.05
370
Глава 8. Приложения
8.11. Техника рядов Ли
Рассмотрим автономную систему обыкновенных дифференциальных
уравнений
dx
— = f(x), x(t = 0)=xo,
где х = (xi,... ,хп)т и хо—начальное значение в t = 0. Пусть /_,• (j =
1,2,... ,п) — аналитические функции, определенные в R". Рассмотрим
аналитическое векторное поле
3 = 1 3
Тогда решение задачи Коши при малых t может быть записано в виде
Xj(t) = exp(*F)a;j|x=x(o),
где j — 1,2,... , п. Продолжение экспоненциальных функций имеет
результатом
t2
Xj{t) = Xj@) + *К(а:л-)|х=х(о) + 2-V'(V(ii))|x=x(o) +■■■ ■
Этот метод называется техника рядов Ли. Рассмотрим модель Лоренца,
которая задается уравнениями
dx\ . dx2 dx3 ,
—г=а(х2-х1), —- = -xix3 +rxi -x2, -j- = xxx2 -0x3,
at at at
где <jj, b и )— положительные константы. Векторное поле задается
выражением
V = о(х2 ~xi)-* h (-a;ix3 +rxx -22H V(xxx2 -Ьх3)-—.
Отсюда решение системы есть
/ t2
х(*) = etvx2
etvx,
tvr
etvx3
к=х@)
ari@) + *K(a:i)|x=x(o) + yK(K(a:1))|x=x(o)+.
i2
12@) + <КЫ1х=х(о) + 2-^(К(аг2))|х=х(о) +
i2
^ x3@) + *К(агз)|х=х(о) + -^V(V(x3))\x=Ao) +
где
F(a;i) =c(a;2 -хг),
V(X2) = -X2 - ХгХг + TX\ ,
F(a;3) = xxx2 - ba;3
F(V(a;i)) = -cr2{x2 - x\) + <r(-x2 - x\X3 + rx\),
V{V(x2)) = a(x2 - xx)(-x3 +r) +x2 + xxx3 - rxx - Xi(xxx2 - bx3)
V(V(x3)) — ax2(x2 - xi) + xi(—x2 - xix3 +rxi) - Ъ(хгх2 - Ьх3).
8.11. Техника рядов Ли
371
Модель Лоренца обладает многими интересными свойствами. Дивергенция
равна —(G + 6+1). Следовательно, каждый малый элемент объема стремится
к нулю при t —t со, в случае независимости Хх,х2,хз- Однако это не
означает что каждый элемент объема сжимается в точку. Для некоторых значений
параметра (например a = 10, 6 = 8/3 и г = 28) мы находим, что близкие
траектории расходятся экспоненциально. Система демонстрирует хаотическое
поведение.
В следующей программе мы применяем технику рядов Ли в модели
Лоренца. Здесь мы расширили экспоненциальную функцию вплоть до второго
порядка. Во второй части программы мы повторяем решение рядов Ли с a — 10,
Ъ = 8/3 и г = 28.
// Lie.cpp
#include <iostream.h>
#include "Vector.h"
#include "MSymbol.h"
const int N=3;
Vector<Sum<double> > x(N), xt(N);
// Векторное поле V
template <class T> T V(const T & ss)
{
T sum(O);
for (int i=0; i<N; i++) sum += xt[i]*df(ss,x[i]);
return sum;
}
void main()
{
int i,j;
Sum<double> xl("xl",0), x2("x2",0), x3("x3",0), t("t",0),
s("s",0), b("b",0), r("r",0),
pl("pl",0), P2("p2",0), p3("P3",0);
Vector<Sum<double> > xs(N), xg(N);
double half =0.5;
x[0] = xl; x[l] = x2; x[2] = x3;
xg[0] = pl; xg[l] = P2; xg[2] = p3;
// модель Лоренца
xt[0] = s*(x2-xl);
xt[l] = -x2 - xl*x3 + r*xl;
372
Глава 8. Приложения
xt[2] = xl*x2 - Ь*хЗ;
// расширение рядов Тейлора до порядка 2
for (i=0; i<N; i++)
xs[i] = x[i] + t*V(x[i]) + half*t*t*V(V(x[i]));
cout << "xs =\n" << xs << endl;
// эволюция близкого решения
t.set(O.Ol); r.setB8.0); s.set(lO.O); b.set(8.0/3.0);
xg[0].set@.8); xg[l].set@.8); xg[2].set@.8);
for (j=0; j<100; j++)
{
for (i=0; i<N; i++) x[i].set(xg[i].nvalueQ);
for (i=0; i<N; i++)
{
xg[i].set(xs[i].nvalue());
cout « "x[" « i « "] = " « xg[i].value() « endl;
}
cout << endl;
}
}
Результат
xs =
[xl+t*s*(x2-xl)+0.5*t"B)*(-s~B)*(x2-xl)+(-x2-xl*x3+r*xl)*s) x2
+t*(-x2-xl*x3+r*xl)
+0.5*t"B)*(s*(x2-xl)*(-x3+r)+x2+xl*x3-r*xl-(xl*x2-b*x3)*xl) x3
+t*(xl*x2-b*x3)
+0.5*t"B)*(s*(x2-xl)*x2+(-x2-xl*x3+r*xl)*xl-(xl*x2-b*x3)*b)]
x[0] = 0.81048
x[l] = 1.00861
x[2] = 0.786104
x[0] = 0.839826
x[l] = 1.22078
x[2] = 0.774439
x[0] = 0.886839
x[l] = 1.44137
x[2] = 0.76532
8.12. Спектры малых спиновых групп
373
[0] = 0.950876
*п] = 1.67493
*t2] = 0.759203
аД2- Спектры малых спиновых групп
рассмотрим спиновый оператор Гамильтона
где а, Ь -
- вещественные константы и
<7j(l) = CTj ® J
ffiB) = Jig) a-!
G1 C) = J ® J $
где o« являются матрицами Паули:
О 1 \ _ / 0 -i
1 о J' ff2:- { i о
Ol
cr-3
0
-1
Здесь мы подразумеваем цикличность индекса, т. е.
<г3D)=<гзA).
Матричное представление первого члена правой стороны есть диагональная
матрица
з
Е
«гзШозС? + 1) = diagCa,
-а, За).
Второй член приводит к недиагональным элементам. Симметрическая матрица
размера 8x8 для Н приобретает вид
/ За
Ъ
Ъ
0
Ъ
0
0
V о
b
—а
0
Ъ
0
Ъ
0
0
b
0
—а
6
0
0
Ъ
0
0
&
b
—а
0
0
0
Ь
Ъ
0
0
0
—а
&
6
0
0
b
0
0
&
—а
0
Ъ
0
0
&
0
ъ
0
—а
&
0 \
0
0
ъ
0
ъ
ъ
За /
следующей программе мы вычисляем спиновый оператор Гамильтона сим-
°льно. Мы символьно вычисляем след и детерминант матрицы. Затем мы
*Чаем подстановку
1 , 1
a — х, о = -
2' 3
матрИце и детерминанте, чтобы получить матрицу с числовыми величинами.
374
Глава 8. Приложения
// spinS3.срр
#include <iostream.h>
#include <fstream.h>
«include "MSymbol.h"
#include "Matrix.h"
«include "Rational.h"
of stream foutCspinS3.dat");
Matrix<Sum<Rational<int> > > sigma(char coord, int inde
i
int i;
Matrix<Sum<Rational<int> > > 1B,2);
I.identity();
Matrix<Sum<Rational<int> > > result, sB,2);
Sum<Rational<int> > zero(O), one(l);
if (coord == 'x')
i
s[0][0] = zero; s[0][l] = one;
s[l][0] = one; s[l][l] = zero;
>
else // 'z'
{
s[0][0] = one; s[0][l] = zero;
s[l][0] = zero; s[l][l] = -one;
}
if (index == 0)
{
result = s;
for(i=l; i<N; i++) result = kron(result,I);
}
else
{
result = I;
for(i=l; i<index; i++) result = kron(result,1);
result = kron(result,s);
for(i=index+l; i<N; i++) result = kron(result,I);
}
return result;
template <class T>
Matrix<T> H(int N, Та, Т b)
8.12. Спектры малых спиновых групп
{ jL> size=powB,N) ;
iP rix<T> result, part(size,size,T@)), *sigmaX, *sigmaZ;
^LaX = new Matrix<T>[N];
maZ = new Matrix<T>[N];
(i=0; i<N; i++) sigmaX[i] = sigmaCx',i,N) ;
f°r(i=0; i<N; i++) sigmaZ[i] = sigma('z',i,N);
r(i=l; i<=N; i++) part += sigmaZ[i-l]*sigmaZ[i'/,N] ;
result = a * part;
part.filKT(O));
for (i=0; i<N; i++) part += sigmaXCi];
result += b * part;
delete [] sigmaX; delete [] sigmaZ;
return result;
}
void main О
{
int N=3, size = powB,N);
Rational<int> сA,2), d(l,3);
Sum<Rational<int> > a("a",0), b("b",0), p("p",0), det;
Matrix<Sum<Rational<int> > > result, HI, I(size,size);
I.identityO ;
result = H(N,a,b);
fout « result << endl;
fout « "след = " << result.trace() << endl;
det = result.determinant();
fout « "детерминант = " << det << endl;
fout « endl;
'/ назначение числовых значений
a = с; b = d;
fout « " Положим = " « a « " и b = " « d « endl;
fout « endl;
out << "Матрица становится:" << endl;
fout « result « endl;
lout << " детерминант = " « det << endl;
376
Глава 8. Приложения
Результат
[3*а b b 0 b 0 0 0]
[Ь -а 0 b О b О О]
[Ь 0 -a b О О b 0]
[О b b -а О О О b]
[b О О О -a b b 0]
[О b 0 0 b -a 0 b]
[О О Ъ О Ъ 0 -а Ъ]
[О 0 0 b 0 b b 3*a]
след = О
детерминант = 9*а~(8)-36*а~F)*Ь~B)+54*а~D)*Ъ~D)-36*а~B)*Ь-(б)
+9*Ь~(8)
Положим а = 1/2 и b = 1/3
Матрица становится:
[3/2 1/3 1/3 0 1/3 0 0 0]
[1/3 -1/2 0 1/3 0 1/3 О О]
[1/3 0 -1/2 1/3 О О 1/3 0]
[О 1/3 1/3 -1/2 0 0 0 1/3]
[1/3 0 0 0 -1/2 1/3 1/3 0]
[О 1/3 0 0 1/3 -1/2 0 1/3]
[О 0 1/3 0 1/3 0 -1/2 1/3]
[0 0 0 1/3 0 1/3 1/3 3/2]
детерминант = 625/186624
8.13. Нелинейные отображения и хаотичное поведение
Мы рассматриваем отображение
xt+i = r{3yt + l)xt(l - xt),
Vt+i = rDxt + l)j/t(l - yt),
которое демонстрирует переход Рюэля—Такенса—Ньюхауза (Ruelle—Takens-
Newhouse) к хаосу. Параметром бифуркации является г. Мы вычисляем
вариационное уравнение символьно
ut+i - r{3yt + 1)A - 2xt)ut + 3rxt(l - xt)vt,
vt+1 - 3ryt(l - yt)ut + rCxt + 1)A - 2yt)vt
и затем повторяем эти четыре уравнения, используя тип данных double. На
больший показатель Ляпунова вычислен приближенно для г = 1.0834
Aw -ln(|uT| + M),
8.13. Нелинейные отображения и хаотичное поведение
377
Т велико. Неподвижные точки отображения даются как решения уравне-
фу* + 1КA - а:*) = х*, r(Zy* + l)y*(l - у*) = у*.
^ь1 находим
xl = ^-(-V4r2-3r + r), 2/r = ^(-V/4r2-3r + r),
3r Sr
6r or
x\ = 0, y\ = 0,
r - 1
«5 = 0, 2/5 = ■
r
Неподвижные точки {х*,у*) и (а:^, J/|) существуют только при г > 3/4. В
следующей программе мы символьно вычисляем вариационные уравнения
отображения, описанного выше. Затем мы вычисляем показатель Ляпунова с
г — 1.0834 и начальными условиями
£о = 0.3, 2/о = 0.4, и0 = 0.5, v0 = 0.6.
Из полученного результата мы видим, что показатель Ляпунова отображения
равен приближенно 0.22.
// var.cpp
#include <iostream.h>
#include <math.h>
«include "MSymbol.h"
template <class T>
{
return r*C*y+l)*x*(l-x);
template <class T>
{
return r*C*x+l)*y*(l-y);
v°id main О
int T, N = 1000;
double x2, y2, u2, v2;
sum<double> x("x",0), xl("xl",0), y("y",0), yl("yl",0), r("r",0),
u("u",0), ul("ul",0), v("v",0), vl("vl",0);
378
Глава 8. Приложения
xl = f(х,у,г);
yl = g(x,y,r);
cout « "xl = " « xl « endl;
cout « "yl = " << yl << endl;
// вариационные уравнения
ul = df(xl,x)*u + df(xl,y)*v;
vl = df(yl,y)*v + df(yl,x)*u;
cout « "ul = " « ul « endl;
cout << "vl = " « vl « endl;
cout << endl;
// Вычисление показателя Ляпунова
// итерированием четырех уравнений.
// Начальные значения
х.set@.3); у.set@.4); г.setA.0834);
u.set@.5); v.set@.6);
for (T=l; T<N; T++)
{
x2 = xl.nvalueO ;
y2 = yl.nvalueO ;
u2 = ul.nvalueO ;
v2 = vl.nvalueQ ;
x.set(x2); y.set(y2); u.set(u2); v.set(v2);
cout << " Показателя Ляпунова для Т = " << T << " is "
« log(fabs(u.nvalue())+fabs(v.nvalue()))/T « endl;
}
Результат
xl = 3*r*y*x-3*r*y*x~B)+r*x-r*x~B)
yl = 3*r*x*y-3*r*x*y~B)+r*y-r*y~B)
ul = 3*r*y*u-6*r*y*x*u+r*u~2*r*x*u+3*r*x*v-3*r*x~B)*v
vl = 3*r*x*v-6*r*x*y*v+r*v-2*r*y*v+3*r*y*u-3*r*y~B)*u
Показатель Ляпунова для Т = 1 есть 0.420853
Показатель Ляпунова для Т = 2 есть 0.113849
Показатель Ляпунова для Т = 3 есть -0.2017
Показатель Ляпунова для Т = 4 есть -0.0178022
Показатель Ляпунова для Т = 5 есть 0.1242
Показатель Ляпунова для Т = 6 есть 0.224871
8.14. Численно-символьное приложение.
379
атель Ляпунова для Т = 994 есть 0.216136
По!са3атеЛь Ляпунова для Т = 995 есть 0.216531
Ио!Са3аТель Ляпунова для Т = 996 есть 0.215979
""^затель Ляпунова для Т = 997 есть 0.215501
"^азатель Ляпунова для Т = 998 есть 0.215698
^азатель Ляпунова для Т = 999 есть 0.215851
о 14. Численно-символьное приложение
рассмотрим уравнение
/(*) = 0,
гяе мы предполагаем, что функция / — по крайней мере дважды
дифференцируема. Пусть / обозначает некоторый интервал, содержащий корень /. Метод
приближения к корню / может быть описан следующим образом: берем
касательную к кривой у = f(x) в текущей точке (xn,f(xn)), соответствующей
текущему приближению хп корня. Пересечение ее с осью х дает следующую
оценку Xn+i корня. Наклон кривой у = f(x) в точке (xn,f(xn)) есть f'(xn).
Касательная в этой точке имеет вид у = f'(xn) *x + b. Так как она проходит через
точку (xn,f(xn)), то мы видим, что Ь = f{xn) - х„ * f'{xn). Следовательно,
уравнение касательной прямой есть
У = f'{xn) *х + f(xn) -xn* f'(xn).
Чтобы определить, где эта прямая пересекает ось х, мы приравниваем у = 0.
Беря точку пересечения в качестве следующей оценки корня, мы имеем 0 =
}'{хп) * xn+1 + f(xn) - xn * f'(xn). Из этого следует, что
_ f(xn)
Г(Хп)
о этом состоит метод Ньютона—Рафсона [15], который имеет следующую
форму:
следующая оценка = текущая оценка + член коррекции.
Член коррекции есть ~f{xn)/f'(xn) и он должен быть небольшим, когда хп
близко к корню, если необходимо обеспечить сходимость. Это зависит от
поведения f'(x) вблизи корня и, в частности, мы столкнемся с трудностями, ког-
«а Г(х) и f(x) имеют близкие корни. Метод Ньютона^Рафсона имеет вид
n+1 = 9{хп) с g(х) := х — f(x)/f'(x). Можно оценить порядок сходимости ме-
ода. Дифференцируя это уравнение, получим g'(x) = (f (x)f"(х))/((f (х)J).
^■"я сходимости мы потребуем, чтобы
/(*)/"(*)
< 1
я всех х в некотором интервале /, содержащем корень. Так как f(a) = 0,
^Неуказанное условие выполняется в корне х = а при условии, что f'{a) ф 0.
гДа, при условии, что д(х) непрерывно, вблизи корня должен существовать
380
Глава 8. Приложения
интервал I, в котором это условие выполняется. Иногда можно встрехи
трудностью, когда интервал мал, поскольку начальное приближение л °Я с
браться в пределах этого интервала. Это обычно бывает, когда f(x) и л^110
имеют близкие корни, так как член коррекции обратно пропорционален ^
чине Г{х). Вел«~
В следующем примере мы используем описанный выше метод, чтобы н
корень функции Xl1
f(x) = 4x — cos (я).
В программе мы сравниваем встроенный тип данных double и символьн
итерацию с численной подстановкой на каждом шаге. Последовательность г
дится к величине х = 0.2426746806.
// Newton.cpp
#include <iostream.h>
#include <iomanip.h> // для setprecisionQ
«include "MSymbol.h"
template <class T> T f(T x) // f(x)
{
return 4*x-cos(x);
}
void main()
{
int i, N=7;
double у, uO = -1.0;
Sum<double> x("x",0), ff, ffl, ff2, C;
ff = f(x);
ill = df(ff,x);
ff2 = df(ffl,x);
// Устанавливаем точность в 10 десятичных знаков
cout << setprecision(lO);
cout « "f(x) = " « ff « endl;
cout « "f (x) = " « ffl « endl;
cout « "f"(x) = " « ff2 « endl;
cout << endl;
// ======= условия сходимости =======
С = ff*ff2/ffl;
x.set(uO);
cout « "C = " « С « endl;
8.15. Заключение
381
ut « "|C(x=uO)l = " « fabs(C.nvalueO) « endl;
lout « endl;
==== Символьные вычисления =========
//
x set(uO);
for (i=0; i<N; i++)
y = (x - ff/iil).nvalue();
x.set(y);
cout << "x = " << x.nvalueC) << endl;
У
У
результат
f(x) = 4*x-cos(x)
f'(x) = 4+sin(x)
f"(x) = cos(x)
С = 4*x*cos(x)*D+sin(x))~(-l)-cos(x)~B)*D+sin(x))"(-l)
|C(x=uO)I = 0.7766703403
x = 0.4374736734
x = 0.2466653145
x = 0.2426765006
x = 0.2426746806
x = 0.2426746806
x = 0.2426746806
x = 0.2426746806
8.15. Заключение
В этой главе мы представили многие полезные приложения из математики и
Физики, как, например, множество Мандельброта, первые интегралы, свертку
векторных полей, технику рядов Ли и т.д. Мы продемонстрировали
полезность классов, которые мы разработали в предшествующих главах. Тем не
менее читатели должны осознавать, что список представленных здесь приложе-
Ний далек от полноты. Многие возможные области исследований будут иметь
пРиложения с различными вариантами использования систем компьютерной
^гебры.
Глава 9
Лисп и
компьютерная алгебра
9.1. Введение
Английское слово Lisp—сокращение для List Processing (обработка списков-)
обозначает машинный язык, который используется во многих прикладных
программах искусственного интеллекта. Одно из его главных свойств
состоит в том, что он позволяет легко манипулировать списками. Лисп был
разработан в 1950-х годах Джоном Маккарти [32] и является одним из
наиболее часто используемых языков для создания систем компьютерной алгебры.
Reduce, Macsyma, Derive и Axiom базируются на Лиспе. Основные типы
данных в Лиспе — атомы и точечные пары. Списки строятся из точечных пар.
Большинство функций в Лиспе оперируют со списками. Кроме того, в Лиспе
также доступны и арифметические операции.
В этой главе мы показываем, как система компьютерной алгебры может
быть построена с использованием Лиспа. Она будет включать в себя такие
возможности обработки многочленов, как упрощение и дифференцирование.
В использовании функций мы будем полагаться строго на рекурсию. Имеются
различные диалекты Лиспа. В этой главе мы используем переносимый
стандартный Лисп (Portable Standard Lisp). Перечисленные здесь программы
также выполняются в Коммон Лиспе (Common Lisp) с некоторыми
небольшими изменениями. В переносимом стандартном Лиспе функция указывается
ключевым словом (de ), тогда как в Коммон Лиспе функция указывается
ключевым словом (def un ). Комментарий в переносимом стандартном
Лиспе записывается в виде
У, Эта строка - комментарий в переносимом стандартном Лиспе
тогда как строка комментария в Коммон Лиспе отмечается точкой с
запятой (;):
; Это строка - комментарий в Коммон Лиспе
Функция (mapcan ) также реализована по-разному в Коммон Лиспе и в
переносимом стандартном Лиспе.
Лисп описан в ряде превосходных учебников [2], [60]. В некоторых из них
также обсуждается выполнение символьных манипуляций [8], [20], [38], [58]-
9.2. Вазовые функции Лиспа
383
о Базовые функции Лиспа
иЯ в Лиспе называются S-выражениями (сокращение от символьного
Зйа жеНИя). 5-выражение есть либо атом, который записан как символ, на-
nptftfeP
а
яблоко
part 2
ибо точечная пара, записанная в виде
(в_1 • в_2)
гпе s_l и s-2 обозначают произвольные S-выражения. Вот некоторые примеры
точечных пар
(а . Ь)
(а . (Ы . Ъ2))
((и . v) . (х . (у . z)))
Важное подмножество 5-выражений — это списки, которые строятся по
следующим правилам:
• Атом есть список, если он — атом nil.
• Точечная пара (s_l . s_2) есть список, если s_2 есть список.
Атом nil рассматривается как пустой список. Следовательно, список в
Лиспе—это последовательность из ^-выражений. Следующие 5-выражения:
nil
(apple .
(а . (b
((apple
nil)
. (с .
. a2)
nil)))
. nil)
являются списками. Списки более удобно выражать в виде списка:
(s_l s_2 ... s_n)
Для п > 0. Это выражение является сокращением для
Cs_l . (s_2 . ( ... (s_n . nil) ... )))
Например, (a b с) есть сокращение для (а . (Ь . (с . nil))). Список (а)
есть сокращение для (а . nil), а список ( ) (так называемый пустой список)
есть сокращение для nil.
Отметим разницу между точечной парой (а . Ь) и двухкомпонентным
сПиском (а Ъ), который может быть записан как (а . (b . nil)).
Очевидна и (а) не эквиваленты, так как последний есть сокращение для (а . nil).
^-выражения являются значениями, управляемыми программами Лиспа.
ЧЦнако программы в Лиспе также являются 5-выражениями. Например, ли-
ТеР&лы в Лиспе записываются в форме
(quote S),
384
Глава 9. Лисп и компьютерная алгебра
где S —некоторое 5-выражение. Вместо (quote S) можно также написат
Синтаксически это всего лишь двухкомпонентный список. Первый комп ^
атом quote, и второй 5-выражение S. Семантически его значение (незав
01Ч
от любой формы) есть 5-выражение S. Например, значение м°
(quote apple)
есть атом apple, а значением
(quote (a . Ъ))
является точечная пара (а . Ъ). В Лиспе неупомянутый атом, используешь -
в выражении, является идентификатором, кроме случаев, когда он являете
атомами nil или t, которые всегда обозначают сами себя.
В Лиспе изначально предусмотрено пять базовых операций для построения
отбора и проверки значений 5-выражений
cons car cdr atom null
Функция cons конструирует точечную пару из своих двух параметров.
Например, значением
(cons (quote a) (quote b))
является (а . Ъ). Конечно, фактические параметры при вызове не должны
быть литералами. Например, значением
(cons (quote a) (cons (quote b) nil))
также является (а . b). Функции car и cdr в качестве аргумента требуют
точечную пару (s_l . s_2) и возвращают компоненты s_l и s_2,
соответственно. Например, значениями
(car (quote (а . Ь))) и (cdr (quote (a . b)))
являются а и Ъ, соответственно. Далее мы рассмотрим результаты применения
функций cons, car и cdr к спискам аргументов. Значение
(cons (quote a) (quote (b с d)))
является списком (abed). Результатами применения саг и cdr к списку
аргументов являются первый элемент списка и остальная часть списка,
соответственно. Например, значения
(car (quote (a b с d))) и (cdr (quote (a b с d)))
есть а и (b с d), соответственно. Заметим также, что значения
(cons (quote a) nil), (car (quote (a))), (cdr (quote (a)))
есть (а), а, и nil, соответственно. Оставшиеся две базовые функции в Лисп6
являются предикатами, результатами которых являются атомы —nil (преД'
ставляющий ложь) и t (представляющий истину). Значение предиката
(atom E)
9.2. Вазовые функции Лиспа
385
если 5-выражение Е является атомом, и nil, если это —точечная пара.
е°Т й'кат null имеет один параметр. Он возвращает t, если его аргумент пуст,
ое возвращает nil. Например
(null nil)
паВДает значение t. Функция greaterp имеет один или несколько числовых
?° vueHTOB и возвращает t, если они упорядочены по убыванию. В противном
а" ае возвращается nil. Например,
(greaterp 9 4) '/, возвращаемое значение: t
Л „кция zerop берет числовой аргумент и возвращает t, если его значение
ть нуль- В противном случае возвращается nil. Например,
(zerop 1) '/• возвращаемое значение: nil
Арифметическая функция plus складывает свои аргументы. Например,
(plus 2 4 -7) °/. возвращаемое значение: -1
Арифметическая функция times перемножает свои аргументы. Например,
(times 4 5 6 7) '/, возвращаемое значение: 840
функция list имеет один или несколько аргументов. Она размещает все
аргументы в список. Эта функция может также быть вызвана без параметров, в
этом случае она возвращает nil.
Функция cond используется для условной обработки. Она имеет
несколько аргументов или ни одного, называемых случаями (case). Каждый случай
является списком, первый элемент которого является критерием, а остальные
элементы — выполняемые действия при выполнении критерия. Случаи в cond
рассматриваются по одному, от первого к последнему. Когда один из
критериев возвращает ненулевое значение, последующие случаи не рассматриваются.
Действия, содержащиеся в этом случае (где сработал критерий), выполняются,
и возвращается результат. Если ни один из критериев не возвращает ненулевое
значение, то cond возвращает nil. Например, функция length_list,
определенная как
(de length_list (L)
(cond
((null L) 0)
(t (plus 1 (length_list (cdr L))))))
°Жет быть применена следующим образом:
(setq L 'A2345 хх)) '/, возвращает значение: A2345 хх)
(length_list L) '/, возвращает значение: 6
УНкция mapcan имеет два аргумента: функцию и список. Она применяет
пункцию ко всем элементам списка. Функция, используемая здесь в качестве
•^УМента, должна возвращать результатом список, а результаты применения
^Ункции после разрушения структуры соединяются в единый список. Напри-
Р> в Коммон Лиспе мы имеем
386
Глава 9. Лисп и компьютерная алгебра.
(mapcan 'cdr' ((b с) (d e))) '/. возвращает значение: (Ь с е)
В переносимом стандартном Лиспе это выражается как
(mapcan '((b с) (d e)) 'cdr '/, возвращает значение: (Ь с е)
Многие функции в Лиспе могут быть определены в терминах других фуцк
Например, сааг может быть определена в терминах саг. Естественно m ^'
сить, имеется ли наименьший набор примитивов, достаточный для реализа!
языка? Фактически такого «лучшего» минимального набора примитивов И
имеется; всё зависит от реализации. Один из возможных наборов примитив 6
мог бы включать саг, cdr и cons для манипуляции S-выражениями, qUot
atom, read, print, eq для равенства, cond для условных выражений, setq дл '
назначения и def un для определений.
9.3. Примеры символьных вычислений
9.3.1. Многочлены
В этом разделе мы показываем, как складываются и умножаются многочлены
[12] вида
п
р(х) = XX'XJ-
3=0
Сначала мы должны дать представление для многочлена р, используя
список точечных пар. Первый элемент точечной пары — экспонента, а второй
элемент—коэффициент. Таким образом, саг точечной пары — экспонента, и cdr
точечной пары — коэффициент, например,
(саг 'C . 5)) '/. возвращает значение: 3
(cdr 'C . 5)) У. возвращает значение: 5
Рассмотрите, например, многочлен
р(х) = Зх2 + 7ж + 1.
Представление в виде списка точечных пар имеет вид
(B . 3) A . 7) @ . 1))
Рассмотрим другой пример: многочлен Зх2 + 2 имеет представление
(B . 3) @ . 2))
Используя это представление, многочлен 0 может быть представлен как m
или (@ . 0)), тогда как многочлен 1 представлен в виде (@ . 1))-
Ниже мы даем реализацию для сложения и умножения двух многочлен
[12]. Здесь используется рекурсия (т. е. функция вызывает саму себя)- ^г
многочлен Р имеет тв членов, и многочлен Q имеет п членов, то время вычи
ления (которое является числом операций Лиспа) для сложения ограни4
величиной 0(тп + п), а для умножения — величиной 0(m2n). Грубо говор '
для выполнения умножения мы должны рассортировать члены произведе
9.3. Примеры символьных вычислений
387
чТобы они располагались в убывающем порядке, и, используя функцию
1*"' еЯИя, заменить все члены с одинаковыми экспонентами на один в соответ-
С° ю1Цем месте. Конечно, использование лучших методов сортировки (типа
пой сортировки) дает более эффективный алгоритм умножения с оцен-
№ /n/rnn]ogGn)). Но в большинстве систем используется алгоритм, подобный
^санной процедуре.
У. Ро1У
,sl
(de add (P Q)
(cond
(null P) Q)
((null Q) P)
((greaterp (caar P) (caar Q)) (cons (car P) (add (cdr P) Q)))
((greaterp (caar Q) (caar P)) (cons (car Q) (add P (cdr Q))))
((zerop (plus (cdar P) (cdar Q))) (add (cdr P) (cdr Q)))
(t (cons (cons (caar P) (plus (cdar P) (cdar Q)))
(add (cdr P) (cdr Q))))))
(de multiply (P Q)
(cond
((or (null P) (null Q)) nil)
(t (cons(cons(plus(caar P) (caar Q)) (times (cdar P) (cdar Q)))
(add (multiply (list (car P)) (cdr Q))
(multiply (cdr P) Q))))))
7. Применение функций
% сложения и умножения
(add '(B . 3) A . 7) @ . 1)) '(B . 4) @ . 2)))
(add '(@ . 0)) '(C . 4) A . 2) @ . 7)))
(multiply '(C . 3) @ . 2)) '(B . 4) A . 1)))
(multiply '(C . 3) @ . 2)) '(@ . 0)))
° первом примере мы складываем два многочлена
(Зх2 + 7х + 1) + Dа:2 + 2) = 7а:2 + 7х + 3.
0 втором примере мы складываем два многочлена
0 + Dх3 + 2х + 7) = 4х3 + 2х + 7.
ь
тРетьем примере мы перемножаем два многочлена
(За:2 + 2) * Dа:2 + а:) = 12а:5 + Зх4 + 8х2 + 2х.
етвертом примере мы перемножаем два многочлена
(Зх2 + 2) * 0 = 0.
вьшоде последнего примера имеем (C . 0) @ . 0)), что является мно-
Леном 0 * х3 + 0 * хх 0. Он упрощается до 0.
13*
388
Глава 9. Лисп и компьютерная алгебра
9.3.2. Упрощения
Здесь мы показываем, как упрощение может быть осуществлено в Лиспр
предполагаем, что математическое выражение дается в префиксной нот
Это означает, что в выражении операторы располагаются перед их опр '
дами. Ран~
В simpl.sl мы реализуем правила
+ X
- 0
ехр(О)
log(l)
ain(O)
cos(O)
arcsin(O)
arctan(O)
sinh(O)
cosh(O)
-> x
-> 0
-> 1
-> 0
-> 0
-> 1
-> 0
-> 0
-> 0
-> 1
В основном упрощение производится путем сравнения элементов выражения с
элементами нашего списка. Если имеется соответствие, то заменяем выражение
упрощенной формой. Отметим, что
(simp-unary '(plus (plus x)))
(simp-unary '(minus (plus x)))
(simp-unary '(plus (exp 0)))
(simp-unary '(exp (sin 0)))
(simp-unary '(log (exp (sin 0))))
не упрощены полностью. Это указывает на то, что необходима рекурсия
упрощения для применения на различных уровнях. Кроме того, мы видим, что
(simp-unary '(plus x у))
дает неправильный ответ, и
(simp-unary 'x)
(simp-unary '(x))
выдадут сообщения об ошибках! Это побуждает нас уделить дополнительное
внимание числу и типам параметров для функции simp-unary. В следуют»*
попытке (simp2.sl) мы пробуем преодолеть эти проблемы.
7, simpl.sl
(de simp-unary (f)
(prog (op opd)
(setq op (car f))
(setq opd (cadr f))
(return (cond
9.3. Примеры символьных вычислений
389
((eq op 'plus)
((and (eq op
((and (eq op
((and (eq op
((and (eq op
((and (eq op
((and (eq op
((and (eq op
((and (eq op
((and (eq op
'minus)
'exp)
'log)
'sin)
'cos)
'arcsin)
'arctan)
'sinh)
'cosh)
(zerop opd))
(zerop opd))
(опер opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
opd)
0)
1)
0)
0)
1)
0)
0)
0)
1)
У. + x
•/. - о
7. exp@)
7. log(l)
7. sin@)
У. cos@)
'/. arcsin@)
'/. arctan(O)
7, sinh@)
7. cosh@)
=> X
=> 0
=> 1
=> 0
=> 0
=> 1
=> 0
=> 0
=> 0
=> 1
(t (list op opd)))))
'/, приложения упрощений
(simp-unary
(simp-unary
(simp-unary
(plus x))
(exp 0))
'(log D)
(simp-unary '(cosh 0))
(simp-unary '(exp 1))
(simp-unary ' (minus x))
(simp-unary '(minus 0))
7. => x
7. => 1
7. => 0
'/, => 1
7. => (exp 1)
7. => (minus x)
7. => 0
(simp-unary '(plus (plus x))) 7. => (plus x) < не упрощается!
(simp-unary '(minus (plus x))O. => (minus(plus x))< не упрощается!
(simp-unary '(plus (exp 0))) 7. => (exp 0) < не упрощается!
(simp-unary '(exp (sin 0))) 7. => (exp (sin 0)) < не упрощается!
(simp-unary '(log (exp (sin 0))))
7. => (log (exp (sin 0))))< не упрощается!
(simp-unary 'x)
(sim;
■p-unary ' (x) )
7, Сделана попытка применить car к ' x' ,
7. который не есть пара < ОШИБКА !
7. Сделана попытка применить саг к 'nil'
У. которое не есть пара < ОШИБКА!
(simp-unary ' (plus х у)) У. => х
<— НЕВЕРНО!
° программе simp2.sl мы успешно преодолеваем все проблемы,
существуете в предыдущей версии. В этой версии упрощение может применяться к
атомам, для которых возвращается первоначальное выражение;
спискам только с одним элементом, для которых мы применяем функцию
simp с тем же аргументом;
390
Глава 9. Лисп и компьютерная алгебра
• спискам с двумя элементами, для которых мы применяем фуйк
simpc-unary с первым аргументом, являющимся оператором, и дпу *°
являющимся операндом; ^>
• спискам с больше чем двумя элементами, для которых сообщение печ
ется cannot_simplif у, потому что такие выражения могут быть упрот *
только бинарным оператором. bI
Мы сформировали еще один уровень над функцией simp-unary. Нов
функция simp играет важную роль в обработке величин, поставляемых по
зователями. Она проверяет как типы, так и количество параметров. Действи
ее различно для каждого отдельного случая.
В функции simp-unary большинство инструкций те же самые, как и в
предыдущей программе simpl.sl. Мы изменили только одну инструкцию (строка
4) в этой функции, чтобы сделать ее рекурсивной:
(setq opd (simp (cadr f)))
Эта инструкция применяет упрощение ко второму аргументу функции, тем
самым позволяя применять упрощение к операндам различного уровня.
Заметим, что инструкция
(simp '(minus (minus x)))
не упрощена, потому что программа не учитывает взаимодействие между
различными уровнями выражения, т. е. операторы minus не взаимодействуют и
взаимно не уничтожают друг друга. Кроме того, мы видим, что
инструкция
(simp '(plus x у))
выдает сообщение cannot_simplify. Это связано с тем, что мы пока не
реализовали бинарных операторов. В следующей программе simp3. si мы
рассматриваем упрощение бинарных операторов, таких, как plus, minus, times,
quotent и power.
'I, simp2.sl
(de simp (f)
(cond
((atom f) f) '/, f является атомом
((null (cdr f)) (simp (car f))) '/, i имеет только один элемент
((null (cddr f)) (simp-unary f)) "/, f имеет два элемента
(t (quote cannot_simplify)) )) '/. i имеет более двух элементов
(de simp-unary (f)
(prog (op opd) У, локальные переменные: op, °P
(setq op (car f))
(setq opd (simp (cadr f)))
(return (cond
9.3. Примеры символьных вычислений
391
((eq op 'plus)
((and (eq op 'minus)
((and (eq op 'exp)
((and (eq op 'log)
((and (eq op 'sin)
((and (eq op 'cos)
((and (eq op 'arcsin)
((and (eq op 'arctan)
((and (eq op 'sinh)
((and (eq op 'cosh)
(zerop opd))
(zerop opd))
(опер opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
opd)
0)
1)
0)
0)
1)
0)
0)
0)
1)
7. + x
7. - 0
7. exp@)
7. log(l)
7. sin@)
'/. cos@)
'/, arcsin(O)
7. arctan@)
У. sinh@)
7. cosh@)
= > X
=> 0
=> 1
=> 0
=> 0
=> 1
=> 0
=> 0
=> 0
=> 1
(t (list op opd)))))
)
'/, Приложения упрощений
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
x)
(x))
(plus x))
(plus (plus x)))
(plus (minus x)))
(plus (minus 0)))
(minus (plus x)))
(minus (sin 0)))
(plus (exp 0)))
(minus (exp 0)))
(plus (log 1)))
(plus (cosh 0)))
(exp (sin 0)))
(log (exp (sin 0))))
(minus (minus x)))
(plus x y))
7.
7.
У.
I
7.
7.
7.
7.
7.
7.
7.
7.
7.
7.
7.
7.
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
X
X
X
X
(minus
0
(minus
0
1
(minus
0
1
1
0
(minus
cannot.
x)
x)
1)
(minus x))
.simplify
о программе simp3.sl на основе одноместных операторов упрощения мы
Реализуем следующие упрощающие бинарные операторы:
plus minus times quotient power
Функции simp мы изменяем инструкцию в строке 6
(t (quote cannot_simplify))
На
(t (simp-binary f))
^ инструкция просто означает, что если аргумент содержит больше двух
еМентов, то применяется бинарный оператор упрощения.
392
Глава 9. Лисп и компьютерная алгебра
В новой функции simp-binary мы рассматриваем следующие упроще
• операция х + у:
если х = 0, возвращается у,
если у = 0, возвращается х,
иЧ;
• операция х — у:
• операция х * у:
операция х/у:
если х = О,
если у = 0, возвращается О,
иначе возвращается -у,
если у = 0, возвращается х,
если х = у, возвращается О,
если х = 0 или у = 0, возвращается О,
если х = 1, возвращается у,
если у = 1, возвращается х,
если х = 0, возвращается О,
если у = 0, возвращается infinity,
если у = 1, возвращается х,
• операция х'
если х
если у
если у
0, возвращается О,
О или х = 1, возвращается 1,
1, возвращается х.
7, simp3.sl
(de simp (f)
(cond
((atom f) f) '/. f является атомом
((null (cdr f)) (simp (car f))) '/. f имеет только один элемент
((null (cddr f)) (simp-unary f)) У, f имеет два элемента
(t (simp-binary f)) )) "/. f имеет более двух элементов
(de simp-unary (f)
(prog (op opd)
(setq op (car f))
(setq opd (simp (cadr f)))
(return (cond
((eq op 'plus)
((and (eq op 'minus)
((and (eq op 'exp)
((and (eq op 'log)
У, локальные переменные: op,
opd
(zerop opd))
(zerop opd))
(опер opd))
opd)
0)
1)
0)
7. + x
У. - 0
7. exp@)
'/. log(l)
=> x
=> о
=> 1
=> о
9.3. Примеры символьных вычислений
393
((and (eq op 'sin)
((and (eq op 'cos)
((and (eq op 'arcsin)
((and (eq op 'arctan )
((and (eq op 'sinh)
((and (eq op 'cosh)
(t (list op opd)))))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
(zerop opd))
0)
1)
0)
0)
0)
1)
'/. sin@)
'/. cos@)
"/. arcsin(O)
'/, arctan(O)
"/. sinh@)
'/. cosh@)
=>
=>
=>
=>
=>
=>
0
1
0
0
0
1
)
(de simp-binary (f)
(prog (op opdl opd2)
(setq op (car f))
(setq opdl (simp (cadr f)))
(setq opd2 (simp (caddr f)))
(return (cond
((and (eq op 'plus)
(cond ((zerop opdl) opd2)
((zerop opd2) opdl))))
"/, упрощаем первый операнд
У, упрощаем второй операнд
У, операция : х + у
У, если х=0, возвращаем у
У, если у=0, возвращаем х
((and (eq op 'minus) У, операция: х - у
(cond ((zerop opdl) У, если х=0,
(cond ((zerop opd2) 0) '/, если у=0, возвращаем 0
(t (list 'minus opd2)))) У, иначе возвращаем -у
((zerop opd2) opdl) '/, если у=0, возвращаем х
((eq opdl opd2) 0)))) У, если х=у, возвращаем 0
((and (eq op 'times) У, операция: х * у
(cond ((or (zerop opdl) (zerop opd2)) 0) У, если х=0 или у=0,
У, возвращаем 0
((опер opdl) opd2) У, если х=1, возвращаем у
((опер opd2) opdl)))) У, если у=1, возвращаем х
((and (eq op 'quotient) '/, операция: х / у
(cond ((zerop opdl) 0) У. если х=0, возвращаем 0
((zerop opd2) 'infinity) У. если у=0, возвращаем infinity
((опер opd2) opdl)))) У, если у=1, возвращаем х
((and (eq op 'power) У, операция: х~у
(cond ((zerop opdl) 0) У, если х=0, возвращаем 0
((or (zerop opd2) (опер opdl))l)"/,ecnH y=0 или х=1, возвращаем 1
((опер opd2) opdl)))) '/, если у=1, возвращаем х
(t (list op opdl opd2)))))
394
Глава 9. Лисп и компьютерная алгебра.
У, Приложения упрощений
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(simp
(plus (plus x)))
(plus (minus x)))
(minus (minus x)))
(plus (sin 0)))
(plus (exp 0)))
(plus (log 1)))
(minus (cosh 0)))
(minus (sin 0)))
(minus (arctan 0)))
(plus x 0))
(plus Ox))
(minus x 0))
(times x 0))
(quotient x 0))
(quotient Ox))
(minus (quotient x 0)))
(power x 0))
(power Ox))
(plus x y))
(plus (times x 0) (times x 1)))
(minus (plus 0 x) (times 1 x)))
У. => x
'/, => (minus x)
'/. => (minus (minus x))< не
упР°ЧбЙ01
7. => о
У. => l
У. => о
У, => (минус 1)
У. => 0
У. => 0
У. => х
У. => х
У. => х
'/. => 0
У, => бесконечность
У. => 0
У, => (минус бесконечность)
У. => 1
У. => 0
У, => (plus х у)
У. => х
У. => 0
(simp '(minus (plus х у) (minus yx)))
У, => (minus (plus х у) (minus ух)) < не упрощено !
9.3.3. Дифференцирование
В этом разделе мы рассматриваем дифференцирование алгебраических
(полиномиальных) выражений. Реализованы следующие правила:
dc
— = 0, с—постоянная
dx
dx
dx
lew*»-£+i
|(/W.9W) = /(x),| + f,s(x).
В программе differ. si дифференцируется выражение ex, a v обозначает Де"
ременную. Выражение дается в префиксной нотации. В префиксной нотаДий
9.3. Примеры символьных вычислений
395
rrrriD ставится перед его операндами. Например, математическое выра-
o0ePaTUF
C + х) * (я - а)
ефиксной нотации с использованием Лиспа выглядит так:
(times (plus 3 х) (minus x a))
телом программа действует, проверяя операторы и находя соответствие меж-
нймй. Когда соответствие найдено, к выражению применяется соответству-
ее правило дифференцирования. Иногда после дифференцирования
выражение становится более сложным. Возникает необходимость упрощения вы-
ажения. Поэтому когда возникает такой случай, мы используем программу
упроЩения simp3. si, которую мы рассмотрели в конце предыдущего раздела.
Выполнение функции diff, вместе с некоторыми приложениями
следующее:
I differ.si
(de diff (ex v) 7. d(ex)/dv
(cond
((atom ex)
(cond ((eq ex v) 1) (t 0))) '/. d(v)/dv = 1, d(constant)/dv = 0
((eq (car ex) 'plus) '/. d(a+b)/dv = da/dv + db/dv
(list 'plus (diff (cadr ex) v) (diff (caddr ex) v)))
((eq (car ex) 'times) '/, d(a*b)/dv = da/dv * b + a * db/dv
(list 'plus
(list 'times (diff (cadr ex) v) (caddr ex))
(list 'times (cadr ex) (diff (caddr ex) v))))
((eq (car ex) 'minus) '/, d(a-b)/dv = da/dv - db/dv
(list 'minus (diff (cadr ex) v) (diff (caddr ex) v))))
)
'• Приложения
(diff >x >X) •/, => i
(diff >2 'x) '/. => 0
^iff 2 'x) '/. => 0
(diff >x >u) у, => о
(diff '(plus x x) 'x) У. => (plus 1 1)
'diff ' (times x x) 'x) '/, => (plus (times 1 x) (times x 1)
(simp (diff '(times x x) 'x)) '/. => (plus x x)
396
Глава 9. Лисп и компьютерная алгебра.
(difi '(times (plus 3 х) (minus a x)) 'x)
7. => (plus (times (plus 0 1) (minus ax))
'/, (times (plus 3 x) (minus 0 1)))
(simp (diff '(times (plus 3 x) (minus a x)) 'x))
'/• => (plus (minus a x) (times (plus 3 x) (minus 1)))
(diff '(times (times x x) x) 'x)
'/. => (plus (times (plus (times 1 x) (times 1 x)) x)
"/, (times (times x x) 1))
(simp (diff '(times (times x x) x) 'x))
*/. => (plus (times (plus x x) x) (times x x))
9.4. Система Лисп, основанная на C++
В этом разделе мы показываем, как Лисп может быть реализован, используя
объектно-ориентированное программирование на C++. В программе мы
реализуем следующие функции:
car cdr atom cons cond append
Базисными структурами данных системы Лисп являются атом и точечная
пара. Списки формируются из точечных пар. Список может состоять из
основных типов данных (например, int, double) или абстрактных типов данных
(например, String, Verylong, Matrix).
В основном программа состоит из трех классов, связанных друг с другом,
как показано на рис. 9.1.
Element
Pair
Type
(точечная пара) (атом)
Рис. 9.1. Принципиальная схема зависимости наследования.
Иерархия наследования абстрактного класса Element состоит из двух про-
изводных классов: Pair и Туре. Класс Pair соответствует точечным параМ
Лиспа, а класс Туре соответствует атомам в Лиспе.
Класс Element, который является абстрактным классом, определяет
только интерфейс, но не выполнение методов, доступных в классах-потомках. Ус
функции в классе объявлены как virtual. Это позволяет классам-потомк3*1
9.4. Система Лисп, основанная на C++
397
ршать определения функций. Имеются четыре метода, объявленных
virtual Element* clone() const = 0;
оН объявляет функцию, которая дублирует объект из Element.
virtual void print(ostreamu) const = 0;
он объявляет функцию, которая отображает содержимое объекта из
Element.
virtual int atот() const = 0;
он объявляет функцию, которая проверяет, является ли объект из Element
атомом.
, friend ostreamu operator < < (ostreamu,const Element *);
он объявляет функцию потокового вывода.
Структуру и функциональные возможности класса Pair можно сформули-
эвать следующим образом:
• Поля данных: класс содержит только два поля данных _саг и _cdr. Они
представляют собой первый и второй элементы точечной пары
соответственно.
• Конструкторы и деструктор:
— Pair (): заданный по умолчанию конструктор назначает NULL полям
_саг и _cdr.
— Pair (const Element *ee): он назначает ее полю _саг и NULL полю
_cdr.
— Pair (const Element *el, const Element *e2): он назначает el и е2
первому и второму элементам точечной пары, соответственно.
— В классе также перезагружены конструктор копирования и
деструктор.
• Методы:
— Element* car О const;
возвращает первый элемент точечной пары.
— Element* cdr() const;
возвращает второй элемент точечной пары.
— void car(const Element *ee);
устанавливает первый элемент точечной пары к значению ее.
— void cdr(const Element *ee);
устанавливает второй элемент точечной пары к значению ее.
— Виртуальные функции
Element* cloneQ const;
void print(ostreamu) const;
int atom() const;
а оператор потокового вывода
ostreamu operator << (ostreamu,const Pair*);
перегружены с новыми определениями для класса.
398
Глава 9. Лисп и компьютерная алгебра
Класс Туре реализован как шаблонный класс. Его структура и фун*
нальные возможности могут быть сформулированы следующим образом- °~
• Поле данных: единственный компонент данных в классе есть thinE
представляет численное значение образца класса Туре. *°
• Конструкторы:
— Туре(): заданный по умолчанию конструктор обнуляет значение п
thing. П0Ля
— Type (T vv): он устанавливает w в качестве значения поля thing
— Конструктор копирования перезагружен.
• Методы:
— const T& value() const;
возвращает значение поля thing.
— Виртуальные функции
Element* clone() const;
void print(ostream&) const;
int atomO const;
и оператор выходного потока
ostreamu operator < < (ostreamu,const Type < T> *);
перегружены с новыми определениями для класса.
Класс был произведен из класса Element, чтобы обеспечить наличие
атомов nil и t. Атомы используются как функциональные параметры при
помощи оператора адресации (&), например cons(&t, unil); отметим, что
только функция atom() объявлена как метод класса. Остальная часть
функций, которые мы намереваемся реализовать, объявлена как глобальные
функции:
• Element* car(const Element *);
Она применяется к точечной паре и возвращает первый элемент пары.
Сообщение об ошибке говорит, является ли аргумент атомом.
• Element* cdr(const Element *);
Она применяется к точечной паре и возвращает второй элемент пары. Если
параметр есть атом, то выдается сообщение об ошибке.
• Pair* cons(const Element *pl, const Element *p2);
Она формирует точечную пару с pi и р2 в качестве первого и второго
элементов пары, соответственно.
• Pair* append(const Element *pl, const Element *p2);
Она заменяет последний элемент точечной пары pi на р2. Отметим, чТ°
р2 может быть атомом или точечной парой. Сообщение об ошибке говори !
является ли pi атомом.
• Element* null(Element *e);
Возвращает &t, если е указывает на атом nil, и возвращает unil в про?и
ном случае.
• Int is_lisp_list (Element *1);
Определяет, указывает ли 1 на подходящий список Лиспа.
9.4. Система Лисп, основанная на C++
399
Tnt is_nonempty_lisp_list (Element *1);
Определяет, указывает ли 1 на подходящий непустой список Лиспа.
Element *cond (Element *c);
Осуществляет условную инструкцию Лиспа cond; с должен указывать на
непустой список списков, где каждый список представляет условие для
исполнения; cond возвращает &nil, если ни одно условие не выполняется.
Ниже мы приводим программу, в которой реализована наша конструкция,
о листинг добавлена прикладная программа lisprun.cpp, в которой использу-
тсЯ функции из новых классов. Отметим, что точечные пары могут
содержать элементы различных типов, таких, как Verylong и double.
// Lisp.h
#ifndef __LISP_H
#define __LISP_H
#include <iostream.h>
// Абстрактный базовый класс
class Element
{
public:
virtual Element * clone() const = 0;
virtual void print(ostream &) const = 0;
virtual int atomО const = 0;
friend ostream & operator << (ostream &, const Element *);
};
// уникальный класс для атомов nil и t
class nil_and_t_lisp_class : public Element
private:
int is_t;
public:
nil_and_t_lisp_class(int i=0) : is_t(i) {}
void print (ostream us) const {s«((is_t)?"t" : "nil") ;}
Element *clone(void) const {return (Element*)this;}
int atom(void) const {return 1;}
} niKO.td);
class Pair : public Element // Точечная пара
Private:
Element *_car; // Первый элемент точечной пары
Element *_cdr; // Второй элемент точечной пары
400
Глава 9. Лисп и компьютерная алгебра.
public:
// Конструкторы
PairO;
Pair(const Element *);
Pair(const Element *, const Element *);
Pair(const Pair &);
"Pair();
Pair & operator = (const Pair &);
// Методы
Element * car() const;
Element * cdr() const;
void car(const Element *);
void cdr(const Element *);
Element * clone() const;
void print(ostream &) const;
int atomQ const;
// функция содружества
friend ostream & operator << (ostream &, const Pair *);
};
template <class T> class Type : public Element // Атом
{
private:
T thing;
public:
// Конструкторы
Type();
Type(T);
Type(const Type<T> &);
// Методы
const T & value() const;
Type<T> & operator = (const Type<T> &);
Element * clone() const;
void print(ostream &) const;
int atomQ const;
// Функция содружества
friend ostream & operator << (ostream k, const Type<T> *)
};
9.4. Система Лисп, основанная на C++
j-добальные функции
' .j. * cons(const Element *, const Element *) ;
ment * append(const Element * .const Element *);
\e®ent * car(const Element *);
lament * cdr(const Element *);
w реализация
// Класс Pair
iair::Pair() : _car(&nil), _cdr(&nil) {}
pajr::Pair(const Element *e) : _car(e->clone()), _cdr(&nil) О
pair::Pair(const Element *el, const Element *e2) :
_car(el->clone()), _cdr(e2->clone()) {}
Pair::Pair(const Pair up)
{
_car = tail;
_cdr = &nil;
♦this = p;
}
Pair::~Pair()
{
//нельзя удалять системные константы
if((_car != &nil)&&(_car != &t))
delete _car; _car = unil;
if((_cdr != &nil)&&(_cdr != &t))
delete _cdr; _cdr = unil;
}
Pairfe Pair::operator = (const Pair up)
if(this != up)
i
if((_car != &nil)&&(_car != &t))
delete _car;
if((_car != &nil)&&(_car != &t))
delete _cdr;
_car = (p.car())->clone();
_cdr = (p.cdr())->clone();
}
return *this;
402
Глава 9. Лисп и компьютерная алгебра.
Element * Pair::car() const
{ return _car; >
Element * Pair::cdr() const
{ return _cdr; }
void Pair::car(const Element *e)
{
if(e != unil) _car = e->clone();
}
void Pair::cdr(const Element *e)
{
if(e != unil) _cdr = e->clone();
}
Element * Pair::clone() const
{
Pair *p = new Pair(*this);
return p;
>
void Pair::print(ostream &os) const
{
os « "(" « _car « " . " « _cdr « ")";
}
int Pair::atom() const
{ return 0; }
ostream & operator << (ostream uos, const Pair *p)
{
if(p != NULL)
{
p->print(os);
return os;
}
os « "nil";
return os;
}
ostreamfe operator << (ostream uos, const Element *e)
{
if (e != NULL)
{
e->print(os);
9.4. Система Лисп, основанная на C++
403
return os;
J„«-nil»;
return os;
>
plate <class T> Type<T>::Type() : thing(T@)) {}
late <class T> Type<T>::Type(T t) : thing(t) {}
late <class T> Type<T>::Type(const Type<T> &t)
{ thing = t.value(); }
template <class T>
Tvpe<T> & Type<T>::operator = (const Type<T> &t)
{
if (this != &t) thing = t.valueQ;
return *this;
}
template <class T> const T & Type<T>::value() const
{ return thing; }
template <class T> int Type<T>::atom() const
{ return 1; }
template <class T> Element * Type<T>::clone () const
{
Type<T> *t = new Type<T>(*this);
return t;
}
template <class T> void Type<T>::print(ostream &os) const
{ °s « thing; }
template <class T>
°stream& operator << (ostreamfe os, const Type<T> *t)
t~>print(os);
return os;
lr * cons(const Element *el, const Element *e2)
Pair *p = new Pair;
!>->car(el);
404
Глава 9. Лисп и компьютерная алгебра
p->cdr(e2);
return p;
}
Element* append(const Element *el, const Element *e2)
{
if(! el->atom())
{
Pair *p = new Pair(*(Pair *)el);
Pair *e = p;
while(e->cdr() != fenil) e = (Pair *)e->cdr();
e->cdr(e2);
return p;
}
сегг<<"\пПервый появившийся аргумент должен быть списком"<
return &nil;
}
Element * car(const Element *e)
{
if(! e->atom()) return ((Pair*)e)->car();
cerr << "\ncar: нельзя применять car к атому!" << endl;
return &nil;
}
Element * cdr(const Element *e)
{
if(! e->atom()) return ((Pair*)e)->cdr();
cerr << "\ncdr: нельзя применять cdr к атому!" << endl;
return &nil;
}
Element *null(Element *e)
{
if(e==&nil) return &t;
return &nil;
}
int is_lisp_list(Element *1)
{
if(l==&nil) return 1;
if(l->atom()) return 0;
return is_lisp_list(((Pair*)l)->cdr());
}
int is_nonempty_lisp_list(Element *1)
9.4. Система Лисп, основанная на C++
405
{ /1_>atomO) return 0;
ifC r« is lisp_list(((Pair*)l)->cdr());
retUJr
>
nt *cond(Element *e)
^•f(is,nonempty_lisp_list(e))
Element *firstcase=((Pair*)e)->car();
f(is_nonempty_lisp_list(firstcase))
Element *condition=((Pair*)firstcase)->car();
if(condition->atom())
{
if(condit i on==&ni1)
return cond(((Pair*)e)->cdr());
else if(condition==&t)
return ((Pair*)firstcase)->cdr();
else cerr<<"cond ожидает, что первый элемент - nil или t."«endl;
}
else cerr«"cond ожидает, что первый элемент - nil или t."«endl;
}
else cerr«"cond ожидает списка для каждого случая, "«endl;
}
else cerr<<"cond ожидает список для выполнения."«endl;
return &nil;
}
#endif
Следующая программа использует только что описанные классы из Lisp.h.
'' lisprun.cpp
•include <iostream.h>
"include "Lisp.h"
"include "Verylong.h"
typedef Type<int> l_int;
^Pedef Type<char> l.char;
ypedef Type<double> l_double;
ypedef Type<Verylong> l_verylong;
Aement *plus(Element *e)
** (i
is_nonempty_lisp_list(e))
406
Глава 9. Лисп и компьютерная алгебра
{
if (is_nonempty_lisp_list(cdr(e)))
{
// выберем int для вычислений
Element *paraml,*param2;
paraml=car(e); param2=car(cdr(e));
if(param2!=&nil)
return
l_int(((l_int*)paraml)->value() + ((l_int*)param2)->value()). clone(\
}
cerr<<"Plus берет список двух целых чисел как аргументы"<<endl-
return ftnil;
}
Element* length_list(Element *e)
{
l_int zero@),one(l);
// прекращение C++ рекурсии
if(e==&nil) return cons(&zero,&nil);
return cond(cons(cons(null(e),cons(&zero,&nil)), // ((null e) 0)
cons(cons(&t,cons(plus(
cons(&one,length_list(cdr(e)))),&nil)),&nil))) ;
}
void mainQ
{
// Определим два атома
l_int a(l); // 1
l_char b('a'); // a
cout << "Примеры для функции cons:" << endl;
Pair *A = cons(&a,&nil); // в Лиспе: ( 1 )
Pair *B = cons(&b,&nil); // в Лиспе: ( а )
Pair *C = cons(&a,&b); // в Лиспе: (cons '1 'a)
cout « "(cons '1 'a) => " « С « endl;
С = cons(&a,B); // в Лиспе: (cons '1 '(a))
cout « "(cons '1 '(a)) => " « С « endl;
С = cons(A,&b); // в Лиспе: (cons 'A) 'a)
cout « "(cons 'A) 'a) => " « С « endl;
9.4. Система Лисп, основанная на C++
407
,cons(A,B); // в Лиспе: (cons 'A) '(а))
С % « "(cons 'A) '(а)) => " « С « endl;
c0ut « endl;
t « "Примеры для функции append:" « endl;
ц (setq Dl 'A23))
pair *D1 =
ns(new l_int(l),cons(new l_intB),cons(new l_intC),&nil))) ;
// (setq D2 '(a b c))
Pair *D2 = cons(new l_char('a'),cons(new l_char('b'),
cons (new l_char('c'),&nil)));
pair *D = (Pair*)append(Dl,D2);
cout « "(append 'A2 3) '(a b c)) => " « D « endl;
cout « endl;
cout « "Примеры для функций car и cdr:" << endl;
// setq E '(A 2 3) (a b c))
Pair *E = cons(Dl,cons(D2,&nil));
cout « "(car '(A 2 3) (a b c))) => " « car(E « endl;
cout « "(car (car '(A 2 3) (a b c))))=> " « car(car(E)) « endl;
cout « "(cdr '(A 2 3) (a b c))) => " « cdr(E) « endl;
cout « "(car (cdr '(A 2 3) (a b c))))=> " « car(cdr(E)) « endl;
cout « "(cdr (cdr '(A 2 3) (a b c))))=> " « cdr(cdr(E)) « endl;
cout « "(car 'a) => " « car(&a) « endl;
cout « endl;
// Абстрактный тип данных
cout << "Приложение с абстрактным типом данных: Verylong" « endl;
l-verylong v(23456789012");
i-double rC.14159);
Pair *Very = cons(&v, cons(&r,&nil));
cout « "(cons 'v '(r)) => " « Very « endl;
cout « endl << "Приложение с функцией cond:" << endl;
//((nil r) (t v))
cout « "(cond (nil 3.14159) (t 123456789012)) => "
« cond(cons(cons(&nil,cons(&r,&nil)),
cons(cons(&t,cons(&v,&nil)),&nil)))
« endl;
408
Глава 9. Лисп и компьютерная алгебра
II ((t r) (nil v))
cout « "(cond (t 3.14159) (nil 123456789012)) => "
<< cond(cons(cons(&t,cons(&r,tail)),
cons(cons(tail,cons(&v,tail)),&nil)))
« endl;
Pair *test=cons(&a,cons(&b,cons(&v,cons(&r,tail))));
cout « endl « "(a b v r) => " « test « endl;
cout « "(length_list (a b v r))=>"« length list(test) « endl'
}
Результат
=> A .
=> 1
=> ((a
=> (a .
B .
. (b
(b .
C .
. (c
(c .
nil)))
. nil)))
nil)))
Примеры для функции cons:
(cons '1 'a) => A . a)
(cons '1 '(a)) => A . (a . nil))
(cons 'A) 'a) => (A . nil) . a)
(cons 'A) '(a)) => (A . nil) . (a . nil))
Примеры для функции append:
(append 'A 2 3)'(a b c)) => A . B . C . (a . (b . (c . nil))))))
Примеры для функций саг и cdr:
(car '(A 2 3) (a b с)))
(car (car '(A 2 3) (a b c))))
(cdr '(A 2 3) (a b c))) => ((a . (b . (c . nil))) . nil)
(car (cdr '(A 2 3) (a b c))))
(cdr (cdr '(A 2 3) (a b c)))) => nil
car: нельзя применять car к атому!
(car 'a) => nil
Приложение с абстрактным типом данных: Verylong
(cons 'v '(r)) => A23456789012 . C.14159 . nil))
Приложение с функцией cond:
(cond (nil 3.14159) (t 123456789012)) => A23456789012 . nil)
(cond (t 3.14159) (nil 123456789012)) => C.14159 . nil)
(a b v r) => A . (a . A23456789012 . C.14159 . nil))))
(length.list (a b v r)) => D . nil)
r^a 10
Дцстинги программ
Глава содержит листинги заголовочных файлов классов, представленных в
лавах 6 и 7. Для каждого класса кратко описываются открытые методы и
затем дается полный листинг.
10.1. Класс Verylong
Открытый интерфейс класса Verylong:
• Verylong(const char* = NULL): конструктор.
• Verylong(int): конструктор.
• abs(const Verylongfe): функция абсолютной величины.
• sqrt (const Verylongfe): функция целочисленного квадратного корня.
• pow(const Verylongfe, const Verylongfe): функция возведения в целую
степень.
• div(const Verylongfe, const Verylongfe): функция деления с двойной
точностью.
• арифметические операторы:++, —, -(унарный),+, -, *, /, '/,, =, +=,
-=, *=, /=, */.=
• операторы сравнения: ==, ! = , <, <=, >, >=
• операторы преобразования типа: operator int О , operator double ()
• потоковые операторы: », «
цДля детального описания структуры класса и каждого из методов,
пожалуйста, обращайтесь к разд. 6.1.
Ч Verylong.h
*ifndef VERYLONG.H
fftlefine VERYLONG_H
#lnclude <assert.h>
#lllclUde <iostream.h>
*nclu<ie <iomanip.h>
nclude <string.h>
#Jnclude <stdlib.h>
nclude <ctype.h>
410
Глава 10. Листинги программ
#include <limits.h>
#include <math.h>
class Verylong
<
private:
// Поля данных
char *vlstr;
int vlen;
int vlsign;
// Строка, записанная в обратном порядке.
// Длина строки Verylong
// Знак числа Verylong: +=>0; -=>1
// Обращение строки s
// Закрытые методы
char *strrev(char *s) const;
Verylong multdigit(int) const;
Verylong multlO(int) const;
// Данные класса
static const Verylong zero;
static const Verylong one;
static const Verylong two;
public:
// Конструкторы и деструктор
Verylong(const char* = NULL);
Verylong(int);
Verylong(const Verylong &);
"VerylongO;
// Операторы преобразования
operator int () const;
operator double () const;
operator char * () const;
// Арифметические операторы и операторы сравнения
const Verylongfe operator=(const Verylongfe); // оператор
// присваивания
Verylong operator - () const;
Verylong operator ++ ();
Verylong operator ++ (int);
Verylong operator — ();
Verylong operator — (int);
// оператор смены знака
// оператор инкремента префиксный^
// оператор инкремента постфиксный
// оператор декремента префиксный^
// оператор декремента постфиксный
Verylong operator +=
Verylong operator -=
Verylong operator *=
Verylong operator /=
Verylong operator '/.=
(const Verylong &)
(const Verylong &)
(const Verylong &)
(const Verylong &)
(const Verylong &)
10.1. Класс Verylong
411
friend
friend
friend
friend
friend
friend
friend
friend
friend
friend
friend
Verylong operator + (const Verylong &,
Verylong operator - (const Verylong &,
Verylong operator * (const Verylong &,
Verylong operator / (const Verylong &,
const Verylong &)
const Verylong &)
const Verylong &)
const Verylong &)
Verylong operator '/, (const Verylong &, const Verylong &)
int operator
int operator
int operator
int operator
int operator
int operator
<
<=
>
>=
(const Verylong
(const Verylong
(const Verylong
(const Verylong
(const Verylong
(const Verylong
const Verylong &)
const Verylong &)
const Verylong &)
const Verylong &)
const Verylong &)
const Verylong &)
// ДРУгие Функции
friend Verylong abs(const Verylong &);
friend Verylong sqrt(const Verylong &) ;
friend Verylong pow(const Verylong &, const Verylong &);
friend double div(const Verylong &, const Verylong &) ;
// функции потокового ввода/вывода
friend ostream & operator << (ostream
friend istream & operator >> (istream
};
const Verylong &);
Verylong &);
// Данные класса
const Verylong Verylong::zero = VerylongC'O")
const Verylong Verylong::one = VerylongC'l")
const Verylong Verylong::two = Verylong(")
// Конструкторы, деструкторы и операторы преобразования,
"erylong::Verylong(const char *value)
if (value)
{
vlsign = (*value == '-') ? 1:0; // проверка на знак минус
// если первый символ есть
// знак препинания.
if (ispunct(*value))
{
vlen = strlen(value)-l;
vlstr = new char[vlen + 1]; assert(vlstr != NULL);
strcpy(vlstr, value+1);
}
else
{
vlen = strlen(value);
vlstr = new char[vlen + 1]; assert(vlstr != NULL);
412
Глава 10. Листинги программ
strcpy(vlstr, value);
}
strrev(vlstr);
}
else // инициализируем нулем
{
vlstr = new char[2]; assert(vlstr != NULL);
♦vlstr = '0'; *(vlstr+l) = '\0';
vlen = 1; vlsign = 0;
}
Verylong::Verylong(int n)
■c
int i;
if (n<0) {vlsign = 1; n = (-n); } // проверка знака и смена его
else vlsign =0; // для отрицательного числа
if (n)
{
i = (int)logl0(n)+2; // проверка длины числа
vlstr = new char[i]; assert(vlstr != NULL);
vlen = i-1;
i = 0;
while (n >= 1) // выделение цифр числа по одной и запись
{ // их во внутреннее представление числа
vlstr [i] = n'/.Ю + '0';
n /= 10;
i++;
}
vlstr[i] = '\0';
}
else // иначе число есть нуль
{
vlstr = new char[2]; assert(vlstr != NULL);
*vlstr = '0'; *(vlstr+l) = '\0';
vlen = 1;
}
}
Verylong: .-Verylong(const Verylongfe x) :vlen(x.vlen) ,vlsign(x.vlsign)
{
vlstr = new char[x.vlen + 1]; assert(vlstr != NULL);
strcpy(vlstr, x.vlstr);
}
10.1. Класс Verylong
413
long::~Verylong() { delete [] vlstr; }
•iong: : operator int () const
i static Verylong maxO(INTJMX) ;
static Verylong minO(INT_MIN+l);
int number, factor=l;
jf(*this > maxO)
{
cerr << "Ошибка: преобразование Verylong->integer невозможно"
<< endl;
return INT.MAX;
}
else if (*this < minO)
{
cerr << "Ошибка: преобразование Verylong->integer невозможно"
« endl;
return INT_MIN;
}
number = vlstr[0]-'0';
for (int j=l; j<vlen; j++)
{
factor *= 10;
number += (vlstr[j]-'0') * factor;
}
if (vlsign) return -number;
return number;
}
Verylong::operator double() const
double sum, factor =1.0;
sum = double(vlstr [0]-'0');
for (int i=i; Kvlen; i++)
{
factor *= 10.0;
sum += double(vlstr[i]-'0') * factor;
*f (vlsign) return -sum;
return sum;
rylong::operator char * () const
char *temp = new char[vlen + 1]; assert(temp != NULL);
414
Глава 10. Листинги программ
char *s;
if (vlen > 0)
{
strcpy(temp, vlstr);
if (vlsign)
{
s = new char[vlen + 2];
strcpy(s,"-");
}
else
{
s = new char[vlen + 1];
strcpy(s,"");
}
strcat(s,strrev(temp));
}
else
{
s = new char[2];
strcpy(s,");
}
delete [] temp;
return s;
}
// Различные методы
const Verylong & Verylong::operator = (const Verylong &rhs)
{
if (this == &rhs) return *this;
delete [] vlstr;
vlstr = new char [rhs.vlen + 1]; assert(vlstr != NULL);
strcpy(vlstr, rhs.vlstr);
vlen = rhs.vlen;
vlsign = rhs.vlsign;
return *this;
}
// унарный оператор смены знака
Verylong Verylong::operator -() const
{
Verylong temp(*this);
if(temp != zero) temp.vlsign = !vlsign;
return temp;
10.1. Класс Verylong
ремента префиксный
!0пе?^1* long:: operator ++ О
•г'; ... - *this + one;
r-П *tbl
инкремента постфиксный
oflepaTtferyl°ng::°Perator ++ (int)
;егУ
viong
VerylonS result(*this)
,tbxs = *th;s + one:
return result;
, оператор декремента префиксный
vylong Verylong: operator - ()
'; return *this = *this - one; }
/оператор декремента постфиксный
Verylong Verylong: :operator — (int)
Verylong result(*this) ;
♦this = *this - one;
return result;
■erylong Verylong: : operator += (const Verylongfe v)
■ return *this = *this + v; }
'«ylong Verylong: .-operator -= (const Verylongfe v)
" return *this = *this - v; }
•; rg°ng Verylong: :operator *= (const Verylongfe v)
Urn *this = *this * v; }
' retUr У 0nS:operator /= (const Verylongfe v)
П *this = *this / v; }
'^Wg Ver
• retUrn yiong: : operator */.= (const Verylongfe v)
this = *this •/. v; }
Ус/^и^аые л
.• ^ong дРУжественные операторы и функции.
ator + (const Verylong &u, const Verylong &v)
lf (U'vbign
v.vlsign)
416
Глава 10. Листинги программ
{
if (u.vlsign == 0) { Verylong tl = u-abs(v); return tl-\
else { Verylong t2 = v-abs(u); return t2-V
}
int j,dl, d2, digitsum, carry = 0,
maxlen = (u.vlen > v.vlen) ? u.vlen:v.vlen;
char *temp = new char[maxlen+2]; assert(temp != NULL);
for (j=0; j<maxlen; j++) // сложение начинается слева
{ // потому что строка перевернуТа
dl = (j > u.vlen-1) ? 0 : u.vlstr[j]-'0»; // получили цифру
d2 = (j > v.vlen-1) ? 0 : v.vlstr[j]-'0'; // получили цифру
digitsum = dl + d2 + carry; // сложили цифры
if (digitsum >= 10) // если есть перенос,
{ // уменьшаем сумму на 10
digitsum -= 10;
carry = 1;
}
else // установим carry в 1
carry =0; //в противном случае carry есть О
temp[j] = digitsum + '0'; // вставка символа в строку
}
if (carry) // если carry закончилось,
temp[j++] = '1'; // последняя цифра есть 1
if (u.vlsign) temp[j++] = '-';
temp[j] = '\0'; // заканчиваем строку
u.strrev(temp);
Verylong result(temp);
delete [] temp;
return result;
Verylong operator - (const Verylong &u, const Verylong &v)
{
if (u.vlsign ~ v.vlsign)
{
if (u.vlsign == 0) { Verylong tl = u+abs(v); return tl;}
else { Verylong t2 = -(v+abs(u)); return t2»J
}
int maxlen = (u.vlen>v.vlen) ? u.vlen:v.vlen,
10.1. Класс Verylong
417
d, dl, d2, i, negative, borrow=0;
r #temp = new char [max.len+1] ; assert (temp != NULL);
Verylong w,y;
•f (u.vlsign == 0) // оба u,v положительны
if (u<v) { w=v; y=u; negative=l;}
else "C w=u; y=v; negative=0;}
nse // оба u,v отрицательны
if (u<v) { w=u; y=v; negative=l;}
else i w=v; y=u; negative=0;}
for (i=0; Kmaxlen; i++)
{
dl = (i>w.vlen-l) ? 0:w.vlstr[i]-'0';
d2 = (i>y.vlen-l) ? 0:y.vlstr[i]-'0';
d = dl - d2 - borrow;
if(d < 0) // если d меньше О
{ // 10 прибавляется к d и
d += 10; //1 занимается из следующей цифры
borrow = 1;
}
else
borrow =0; // иначе нет необходимости в занимании
temp[i] = d + '0' ;
}
while (i-1 > 0 && temp[i-l] == '0') // если результат нулевой
—i;
if (negative) temp[i++] = '-';
temp[i] = >\0';
u-strrev(temp);
Verylong result(temp);
delete [] temp;
return result;
ylong operator * (const Verylong feu, const Verylong &v)
Verylong pprod("l"), tempsum(");
f°r (int j=0; j<v.vlen; j++)
int digit = v.vlstr[j] - '0'; // выделяем цифру
418
Глава 10. Листинги программ
pprod = u.multdigit(digit); // умножение на цифру
pprod = pprod.multlO(j); // "добавляем" соответст
// нули сзади
tempsum = tempsum + pprod; // результат прибавляется
// к tempsum
>
tempsum.vlsign = u.vlsign~v.vlsign; // определяем знак
return tempsum;
ВУ*>%6
>
// Это алгоритм длинного деления
Verylong operator / (const Verylong &u, const Verylong &v)
{
Verylong w,y,b,c,d,quotient=Verylong::zero;
int len = u.vlen - v.vlen;
if(v == Verylong::zero)
{
cerr << "Ошибка : деление на ноль" << endl;
return Verylong::zero;
>
w=abs(u); y=abs(v);
if (w<y) return Verylong::zero;
char *temp = new char[w.vlen+1]; assert(temp != NULL);
strcpy(temp, w.vlstr + len);
b.strrev(temp); // b есть заготовка для strrevO
с = Verylong(temp);
delete [] temp;
for (int i=0; i<=len; i++)
{
quotient = quotient.mult10A);
b = d = Verylong::zero; // инициализация b и d значением О
while (b < c)
{ b = b + у; d=d+ Verylong::one;}
if (c < b) // если b>c, то значит
{ //мы добавили счетчику слишком много
b = b - у;
d = d - Verylong::one;
}
quotient = quotient + d; // прибавляем к частному
if (i < len)
{
с = (c-b).multlO(l);
10.1. Класс Verylong
419
с = с + Verylong(w.vlstr[len-i-1]-'0');
}
uotient.vlsign = u.vlsigiTv.vlsign;
Return quotient;
}
vlong operator '/„ (const Verylong &u, const Verylong &v)
return (u " v*(u/v)) ;
■nt operator == (const Verylong &u, const Verylong &v)
return (u.vlsign==v.vlsign && !strcmp(u.vlstr,v.vlstr));
int operator != (const Verylong &u, const Verylong &v)
return !(u==v) ;
int operator < (const Verylong &u, const Verylong &v)
if (u.vlsign < v.vlsign) return 0;
else if (u.vlsign > v.vlsign) return 1;
if (u.vlen < v.vlen) return (l~u.vlsign); // XOR (")
else if (u.vlen > v.vlen) return @"u.vlsign); // для определения
// знака
int temp;
char *templ = new char[u.vlen+lj, *temp2 = new char[v.vlen+1];
assert(tempi != NULL);
assert(temp2 != NULL);
strcPy(tempi, u.vlstr);
strcpy(temp2, v.vlstr);
u-strrev(templ);
■strrev(temp2); // u - вспомогательная переменная
emP = strcmp(tempi, temp2);
delete [] tempi; delete [] temp2;
lf (temp < 0)
return (l"u.vlsign); // используем XQR(~) для определения знака
4*
420
Глава 10. Листинги программ
else if (temp > 0)
return (O'u.vlsign);
else
return 0; // если u==v, возвращаем О
}
int operator <= (const Verylong &u, const Verylong &v)
{
return (u<v I I u==v);
}
int operator > (const Verylong &u, const Verylong &v)
{
return (!(u<v) && u!=v);
}
int operator >= (const Verylong &u, const Verylong &v)
{
return (u>v I I u==v);
}
// Вычисление абсолютной величины числа
Verylong abs(const Verylong &v)
{
Verylong u(v);
if (u.vlsign) u.vlsign = 0;
return u;
}
// Вычисление целочисленного квадратного корня,
// основанное на формуле (а+Ь)~2 = а~2 + 2ab + Ь~2
Verylong sqrt(const Verylong &v)
{
// если v отрицательно, сообщается об ошибке
if (v.vlsign) {cerr << "NaN" « endl; return Verylong::z
int j, k = v.vlen+1, num = k » 1;
Verylong y, z, sum, tempsum, digitsum;
char *temp = new char[num + 1]; assert(temp != NULL);
char *w = new char[k]; assert(w != NULL);
strcpy(w, v.vlstr);
k = v.vlen-1;
j = 1;
10.1. Класс Verylong
11 разбиваем число на группы по 2 цифры
.f (v.vlen % 2) digitsum = Verylong(w[к—] - '0');
if <
eise
{
digitsum = Verylong((w[k] - '0')*10 + w[k-l] - '0');
к -= 2;
}
// находим первую цифру целочисленного квадратного корня
сщп = z = Verylong(int(sqrt(double(digitsum))));
// запоминаем частичный результат
tempEO] = int(z) + '0';
digitsum = digitsum - z*z;
for(; j<num; j++)
{
// берем следующую цифру числа
digitsum = digitsum.mult10A) + Verylong(w[k—] - '0');
у = z + z; // 2*a
z = digitsum/y;
tempsum = digitsum.multlO(l) + Verylong(w[k] - '0');
digitsum = -y*z.multl0(l) + tempsum - z*z;
// уменьшаем z на 1 и перевычисляем digitsum
// когда она переоценена.
while (digitsum < Verylong::zero)
i
~z;
digitsum = -y*z.mult10A) + tempsum - z*z;
>
—k;
temp[j] = int(z) + '0';
z = sum = sum.mult10A) + z;
}
temp[num] = '\0' ;
Verylong result(temp);
delete [] temp; delete [] w;
return result;
r в°зведение числа в степень degree
erylong pow(const Verylong &X, const Verylong fedegree)
422
Глава 10. Листинги программ
Verylong N(degree), Y("l"), x(X);
if(N == Verylong::zero) return Verylong::one;
if(N < Verylong::zero) return Verylong::zero;
while(l)
{
if (N'/.Verylong: :two != Verylong: :zero)
{
Y = Y * x;
N = N / Verylong::two;
if (N == Verylong::zero) return Y;
}
else N = N / Verylong::two;
x = x * x;
>
}
// функция деления с двойной точностью
double div(const Verylong &u, const Verylong &v)
■C
double qq = 0.0, qqscale = 1.0;
Verylong w,y,b,c;
int d, count,
decno =16; // количество значащих цифр
if (v == Verylong::zero)
{
cerr « "ОШИБКА : Деление на нуль" « endl;
return 0.0;
>
if (u == Verylong::zero) return 0.0;
w=abs(u); y=abs(v);
while (w<y) { w = w.multlO(l); qqscale *= 0.1; >
int len = w.vlen - y.vlen;
char *temp = new char[w.vlen+1]; assert(temp != NULL);
strcpy(temp,w.vlstr+len);
w.strrev(temp); // w - вспомогательная перемен
с = Verylong(temp);
delete [] temp;
for (int i=0; i<=len; i++)
{
qq *= 10.0;
10.1. Класс Verylong
423
■к = Verylong::zero; d = 0; // инициализируем b и d значением О
while (b < c)
{ b +=■ y; d += 1;}
if (c < b) // если b>c, то значит
r j, -= у; d -= 1;} // мы добавили слишком много
qq += double(d); // добавим к частному
с = (с-Ъ).multlO(l); // частичный остаток * 10
it (i < leii) //и прибавим к следующей цифре
с += VerylongCw.vlstr[len~i-l]-'0') ;
}
qq
*= qqscale; count = 0;
while (c != Verylong;:zero && count < decno)
{
qqscale *- 0.1;
b = Verylong::zero; d = 0; // инициализация b и d значением 0
while (b < c)
{ b += у: d += 1;}
if (c<b) // если b>c, это значит
{ b -=- у; d -= 1;} //мы добавили слишком много
qq += double(d)*qqscale;
с = (c-b).multlO(l);
count++;
}
1;f (u.vlsign'v.vlsign) qq *= (-1.0); // проверка знака
return qq;
o.
ream & operator << (ostream &s, const Verylong &v)
cbar *temp -~- new char[v.vlen + 1] ; assert (temp != NULL)
if (v.vlen > 0)
{
strcpy(temp, v.vlstr);
if (v.vlsign) s << "-";
424
Глава 10. Листинги программ
s << v.strrev(temp);
}
else s << ";
delete [] temp;
return s;
}
istream & operator >> (istream &s, Verylong &v)
{
char temp[100000];
s >> temp;
delete [] v.vlstr;
v.vlen = strlen(temp);
v.strrev(temp);
v.vlstr = new char[v.vlen+l] ; assert(v.vlstr != NULL);
strcpy(v.vlstr, temp);
return s;
}
// закрытые методы: strrevQ, multdigitQ, multlOO.
//
// переворачиваем строку s в соответствии с измененным оригинало
char * Verylong::strrev(char *s) const
{
int len = strlen(s), lenl = len - 1, index,
limit = len >> 1;
char t;
for (int i=0; i<limit; i++)
{
index = lenl-i;
t = s[index];
s [index] = s [i] ;
s[i] = t;
}
return s;
}
// умножаем это Verylong число на num
Verylong Verylong::multdigit(int num) const
{
int j, carry = 0;
if (num)
{
char *temp = new char[vlen + 2]; assert(temp != NULL);
10.1. Класс Verylong
425
for (j = 0; j<vlen; j++)
{
int dl = vlstr[j] - '0',
if (digitprod >= 10)
{
carry = digitprod/10;
digitprod -= carry*10;
}
else carry = 0;
temp[j] = digitprod + '0'
}
if (carry)
temp[j++] = carry + '0';
temp[j] = '\0';
strrev(temp);
Verylong result(temp);
delete [] temp;
return result;
}
else return zero;
digitprod = dl*num + carry;
// если имеется новая цифра,
// carry - старшая цифра
// результат - младшая цифра
// иначе carry равна 0
// вставим символ в строку
// if carry закончилась,
// это - последняя цифра
// закрываем строку
}
// умножаем это Verylong число на 10*пшп
Verylong Verylong::multl0(int num) const
{
if (*this != zero)
{
int j , dd = vlen + num, bb = vlen - 1;
char *temp = new char [dd + 1]; assert(temp != NULL);
for (j=0; j<vlen; j++) temp[j] = vlstr[bb-j];
for (j=vlen; j<dd; j++) temp[j] = 'О';
temp[dd] = '\0';
Verylong result(temp);
delete [] temp;
return result;
}
else return zero;
#endii
426
Глава 10. Листинги программ
10.2. Класс Rational
Открытый интерфейс класса Rational:
• Rationale): конструктор по умолчанию.
• Rational(T): конструктор целых чисел.
• Rational(Т,Т): конструктор рациональных чисел.
• num(): числитель рационального числа.
• den (): знаменатель рационального числа.
• frac(): дробная часть рационального числа.
• normalize(): нормализация рационального числа.
• арифметические операторы:-(унарный),+, -, *, /,
• операторы сравнения: ==, ! = , <, <=, >, >=
• операторы преобразования типа: operator double()
• потоковые операторы: >>, <<
Для детального описания структуры класса и каждс
луйста, обращайтесь к разд. 6.2.
// Rational.h
// Класс рациональных чисел
#ifndef RATI0NAL_H
#define RATI0NAL_H
#include <iostream.h>
#include <stdlib.h>
#include <ctype.h>
template <class T> class Rational
{
private:
// Поля данных : числитель и знаменатель
Т p,q;
// внутренний метод
Т gcd(T,T);
public:
// конструкторы и деструктор
Rational();
Rational(T);
RationaKT, T) ;
Rational(const Rational<T> &);
"RationalO ;
// оператор преобразования
operator double () const;
// методы
T num() const; // числитель г
10.2. Класс Rational
427
1 denО const; // знаменатель г
Rational<T> fracQ const; // дробная часть г
oid normalize О; // нормализация рационального числа
// арифметические операторы и операторы сравнения
const Rational<T> к operator = (const Rational<T> &);
Rational<T> operator - () const;
const Rational<T> & operator += (const Rational<T> &)
const Rational<T> & operator -= (const Rational<T> &)
const Rational<T> & operator *= (const Rational<T> &)
const Rational<T> & operator /= (const Rational<T> &)
friend Rational<T> operator + (const Rational<T>&,
const Rational<T>&);
friend Rational<T> operator - (const Rational<T>&,
const Rational<T>&);
friend Rational<T> operator * (const Rational<T>&,
const Rational<T>&);
friend Rational<T> operator / (const Rational<T>&,
const Rational<T>&);
};
friend int
friend int
friend int
friend int
friend int
friend int
operator ==
operator !=
operator >
operator <
operator >=
operator <=
(const RationaKT>&,
(const Rational<T>&,
(const Rational<T>&,
(const Rational<T>&,
(const RationaKT>&,
(const RationaKT>&,
const Rational<T>&)
const Rational<T>&)
const Rational<T>&)
const Rational<T>&)
const Rational<T>&)
const Rational<T>&)
// функции потокового ввода/вывода
friend ostream & operator << (ostream &,
friend istream & operator » (istream &,
const Rational<T>&);
RationaKT>&) ;
'/ конструкторы и деструктор и оператор преобразования.
template <class T> RationaKT>: :Rational () : р(Т@)), q(T(l)) {}
template <class T> RationaKT>: :Rational (T N) : p(N), q(T(l)) {}
template <class T> RationaKT>: :Rational (T N, T D) : p(N), q(D)
static T zero@);
if (D==zero)
{
cerr << "Нулевой знаменатель рационального числа " << endl;
return;
}
if (q < zero) {p = -p; q = -q;}
428
Глава 10. Листинги программ
normalize();
}
template <class T> Rational<T>::Rational(const Rational<T> &r)
: p(r.p), q(r.q) {}
template <class T> Rational<T>::"Rational() {}
template <class T> Rational<T>::operator doubleO const
{ return double(p)/double(q); }
#include "Verylong.h"
Rational<Verylong>::operator double() const { return div(p,q); у
II методы
template <class T> T Rational<T>::num() const { return p; }
template <class T> T Rational<T>::den() const { return q; }
template <class T> Rational<T> Rational<T>::frac() const
{
static Rational<T> zero@), one(l);
Rational<T> temp(*this);
if (temp < zero)
{
while (temp < zero) temp = temp + one;
return temp - one;
}
else
{
while (zero < temp) temp = temp - one;
return temp + one;
>
}
template <class T> void Rational<T>: -.normalize()
■C
static T zero@); static T one(l);
T t;
if (p < zero) t = -p;
else t = p;
t = gcd(t, q);
if (t > one) { p /= t; q /= t;}
}
// различные операторы
template <class T>
10.2. Класс Rational
429
Rational<T> & Rational<T>::operator = (const Rational<T> &r)
c"°S r p; q = r.q; return *this; }
{? =
■, te <class T> Rational<T> Rational<T>:: operator - () const
Return RationaKT>(-p,q); }
plate <class T>
* + Rational<T> & Rational<T>::operator += (const Rational<T> &r)
; return *this = *this + r; }
templar <class T>
nSt Rational<T> & Rational<T>: .-operator -= (const Rational<T> &r)
/ j-eturn *this = *this - r; >
template <class T>
const Rational<T> & Rational<T>::operator *= (const Rational<T> &r)
{ return *this = *this * r; }
template <class T>
const Rational<T> & Rational<T>::operator /= (const Rational<T> &r)
{ return *this = *this / r; }
// различные дружественные операторы и функции.
template <class T>
Rational<T> operator + (const Rational<T> &rl,const Rational<T> &r2)
{return Rational<T> (rl.p * r2.q + r2.p * rl.q, rl.q * r2.q); }
template <class T>
Rational<T> operator - (const Rational<T> &rl,const Rational<T> &r2)
{ return Rational<T> (rl.p * r2.q - r2.p * rl.q, rl.q * r2.q); }
template <class T>
RationaKT> operator * (const RationaKT> &rl, const RationaKT> &r2)
{ return RationaKT> (rl.p * r2.p, rl.q * r2.q); }
template <class T>
RationaKT> operator / (const RationaKT> &rl,const RationaKT> &r2)
{ return RationaKT> (rl.p * r2.q, rl.q * r2.p); }
Opiate <class T>
nt operator == (const RationaKT> &rl, const RationaKT> &r2)
return (rl.p * r2.q) == (r2.p * rl.q); }
temPlate <class T>
nt operator != (const RationaKT> &rl, const Rational<T> &r2)
return !(rl == r2); }
430
Глава 10. Листинги программ
template <class T>
int operator > (const Rational<T> &rl, const Ratio
{ nal<;T> ^
static T zero@);
return (rl.p*r2.q - r2.p*rl.q > zero);
}
template <class T>
int operator < (const RationaKT> &rl, const Rationale
static T zero@);
return (rl.p*r2.q - r2.p*rl.q < zero);
}
template <class T>
int operator >= (const Rational<T> &rl, const Rational<T> &r?i
{ return (rl>r2) I I (rl==r2); }
template <class T>
int operator <= (const Rational<T> &rl, const Rational<T> &r2)
{ return (rl<r2) I I (rl==r2); }
template <class T>
ostream & operator << (ostream &s, const Rational<T> &r)
{
static T one(l);
if (r.q == one) return s << r.p;
return s << r.p << "/" << r.q;
>
template <class T>
istream & operator >> (istream &s, Rational<T> &r)
{
char c;
T n, d(l);
s.clearО; // установка статуса потока
s >> n; // читаем числитель е
if (! s.goodQ) return s; // если не получено целое, воз
с = s.peek(); // получить следующий символ
if (с == '/')
{
с = s.getO; // очистка '/'
s >> d; // читаем знаменатель
if (! s.goodQ)
{
s.clear(s.rdstateO I ios::badbit);
10.3. Класс Complex
431
return s;
>
> Batio^<T>(n'd)
retum s;
^«й метод: gcd()
// заКР <class T> T RationaKT>::gcd (Та, lb)
tefflPlate <C
i tic т zero@);
l£le (b > zer0)
{ т ш = a 7. b; a = b; b = m;
}
return a;
}
// Эта функция является глобальной,
// она должна быть в глобальном заголовочном файле.
template <class T> T abs(const Т &х)
{
if (x>T@)) return x;
return -х;
}
tendif
Ю.З. Класс Complex
Открытый интерфейс класса Complex:
Complex О: конструктор по умолчанию.
°mplex (T): конструктор вещественного числа.
mPlex(T,T): конструктор комплексного числа,
real р /\
г art (): вещественная часть комплексного числа.
g art(): мнимая часть комплексного числа.
8 1 ude(): модуль комплексного числа.
ent(): угловой аргумент комплексного числа.
°nJUffa1-Qn
Пе Б еи: сопряженное комплексное число.
ар , ^' минус сопряженное комплексное число.
Фметические операторы:-(унарный),+, -, *, /, =, +=, -=, *=, /=
поток'°Равнения:==' ! = ' <' <=' >' >=
к°вые операторы: », «
ClW:.лее Детального описания структуры класса и его методов, пожалуй-
РаЧайТеСь к р^. 6.3.
432
Глава 10. Листинги программ
// MComplex.h
// Класс комплексных чисел
#ifndef C0MPLEX_H
#define C0MPLEX_H
#include <iostream.h>
#include <math.h>
template <class T>
class Complex
{
private:
// поля данных : вещественная часть и мнимая часть
Т real;
Т imag;
public:
// конструкторы и деструктор
Complex();
Complex(T);
Complex(Т, Т);
Complex(const Complex<T> &);
"Complex();
// методы
Т realPartO const;
Т imagPartQ const;
double magnitudeQ const;
double argument() const;
Complex<T> conjugateO;
Complex<T> negateQ;
// арифметические операторы и операторы сравнения
const Complex<T> & operator = (const Complex<T>&);
Complex<T> operator - () const;
Complex<T> operator += (const Complex<T>&);
Complex<T> operator -= (const Complex<T>&);
Complex<T> operator *= (const Complex<T>&);
Complex<T> operator /= (const Complex<T>&);
friend Complex<T> operator + (const Complex<T>&,
const Complex<T>&);
friend Complex<T> operator - (const Complex<T>&,
const Complex<T>&);
friend Complex<T> operator * (const Complex<T>&,
const Complex<T>&);
10.3. Класс Complex
433
friend Complex<T> operator / (const Complex<T>&,
const Complex<T>&);
friend int operator < (const Complex<T>&, const Complex<T>&)
friend int operator <=(const Complex<T>&, const Complex<T>&)
friend int operator > (const Complex<T>&, const Complex<T>&)
friend int operator >=(const Complex<T>&, const Complex<T>&)
friend int operator ==(const Complex<T>&, const Complex<T>&)
friend int operator !=(const Complex<T>&, const Complex<T>&)
// функции потокового ввода/вывода
friend istream & operator >> (istreamfe, Complex<T>&);
friend ostream & operator << (ostreamfe, const Complex<T>&);
};
// конструкторы, деструктор и конструктор копирования
template <class T> Complex<T>::Complex() : real(T@)), imag(T@)) {}
template <class T> Complex<T>::Complex(T r) : real(r), imag(T@)) {}
template <class T> Complex<T>: .-Complex (T r,T i) : real (r) , imag(i) {}
template <class T> Complex<T>::Complex(const Complex<T> &c)
: real(c.real), imag(c.imag) {}
template <class T> Complex<T>::"Complex() {}
// методы
template <class T> T Complex<T>::realPart() const { return real; }
template <class T> T Complex<T>::imagPartO const { return imag; }
template <class T> double Complex<T>::magnitude() const
i return sqrt(double(real*real+imag*imag)); }
Opiate <class T> double Complex<T>:-.argument () const
{ return atan2(double(imag),double(real)); }
Opiate <class T> Complex<T> Complex<T>::conjugate()
return Complex<T>(real, imag = -imag); }
emPlate <class T> Complex<T> Complex<T>::negate()
return Complex<T>(real = -real, imag); }
, Рифметические операторы и операторы сравнения
^Plate <class T>
st Complex<T> & Complex<T>::operator = (const Complex<T> &c)
434
Глава 10. Листинги программ
{
real = с.real; imag = c.imag;
return *this;
}
template <class T> Complex<T> Complex<T>::operator - () const
{ return Complex<T>(-real, -imag); }
template <class T>
Complex<T> Complex<T>: -.operator += (const Complex<T> &c)
{ return *this = *this + c; }
template <class T>
Complex<T> Complex<T>::operator -= (const Complex<T> &c)
{ return *this = *this - c; }
template <class T>
Complex<T> Complex<T>::operator *= (const Complex<T> &c)
{ return *this = *this * c; }
template <class T>
Complex<T> Complex<T>::operator /= (const Complex<T> &c)
{ return *this = *this / c; }
template <class T>
Complex<T> operator + (const Complex<T> feci, const Complex<T> &c2)
{ return Complex<T>(cl.real+c2.real, cl.imag+c2.imag); }
template <class T>
Complex<T> operator - (const Complex<T> feci, const Complex<T> &c2)
{ return Complex<T>(cl.real-c2.real, cl.imag-c2.imag); }
template <class T>
Complex<T> operator * (const Complex<T> feci, const Complex<T> &c2)
{
return Complex<T>(cl.real*c2.real - cl.imag*c2.imag,
cl.real*c2.imag + cl.imag*c2.real);
}
template <class T>
Complex<T> operator / (const Complex<T> feci, const Complex<T> fee2-1
{
T modulus = c2.real * c2.real + c2.imag * c2.imag;
static T zero@);
if (modulus == zero)
{
10.3. Класс Complex
435
rr << "Ошибка : нулевой делитель" << endl;
■return Complex<T>(zero,zero) ;
* rn Complex<T>((cl.imag * c2.imag + с1.real * c2.real)/modulus,
re (cl.imag * c2.real - с1.real * c2.imag)/modulus);
.empire <class T>
operator < (const Complex<T> feci, const Complex<T> &c2)
1 eturn (cl.real*cl.real+cl.imag*cl.imag <
c2.real*c2.real+c2.imag*c2.imag); }
template <class T>
• ut operator <= (const Complex<T> feci, const Complex<T> &c2)
{return (cl<c2) II (cl==c2); }
template <class T>
int operator > (const Complex<T> feci, const Complex<T> &c2)
{return (cl.real*cl.real+cl.imag*cl.imag >
c2.real*c2.real+c2.imag*c2.imag); }
template < с lass T>
int operator >= (const Complex<T> feci, const Complex<T> &c2)
{ return (cl>c2) I I (cl==c2); }
template <class T>
int operator == (const Complex<T> feci, const Complex<T> &c2)
{return (cl.real==c2.real) && (cl.imag==c2.imag); }
template <class T>
int operator != (const Complex<T> feci, const Complex<T> &c2)
{ return !(cl==c2); }
'/ потоковый ввод/вывод
template <class T> istream & operator » (istream &s, Complex<T> &z)
static T zero@);
// удаляем лидирующие пробелы
// переход к следующему символу
// очистка '('
// чтение вещественной части
// удаляем хвостовые пробелы
Tr,i;
char с;
s » ws
с * s.p
if (c==
{
с
s
s
>
>eek() ;
= »(»)
= s.getQ ;
» r;
>> ws;
436
Глава 10. Листинги программ
с = s.peek(); // переход к следующему символу
if (с == ',')
{
с = s.getO ;
s >> i; // чтение мнимой части
s >> ws;
с = s.peek();
}
else i = zero; // мнимая часть отсутствует
if (с != ')') s.clear(s.rdstate() | ios::badbit);
}
else // это - действительное число
{
s >> г; // чтение вещественной части
i = zero; // инициализация вещественной част]
}
z = Complex<T>(r,i);
return s;
template <class T>
ostream & operator << (ostream &s, const Complex<T> &c)
{
static T zero@);
if (c.imag==zero) return s << c.real;
if (c.real==zero) return s << c.imag << "i";
return s « "(" « c.real « "," « c.imag « "i)";
}
#endif
10.4. Класс Quaternion
Открытый интерфейс класса Quaternion:
• Quaternion (): конструктор по умолчанию.
• Quaternion (T,T,T,T): конструктор.
• sqr (): квадрат кватерниона.
• conjugate(): сопряженное к кватерниону.
• inverse(): инверсия кватерниона.
• magnitude(): модуль кватерниона.
• арифметические операторы:-(унарный),+, -, *, /, ~
• потоковые операторы: >>, <<
Для более детального описания структуры класса и его методов, по
ста, обращайтесь к разд. 6.4.
10.4. Класс Quaternion
def QUATERNI0N_H
*ifJine QUATERNION.H
-,U(ie <iostream.h>
*in^ude <math.h> // для sqrtQ
late <class T> class Quaternion
{
private.
// поля данных
T r, i, j, к;
public:
// конструкторы
Quaternion();
Quaternion(T, T, Т, Т);
Quaternion(const Quaternion<T> &);
"Quaternion();
// операторы
const Quaternion<T> & operator = (const Quaternion<T> &);
Quaternion<T> operator + (const Quaternion<T> &);
Quaternion<T> operator - (const Quaternion<T> &);
Quaternion<T> operator - 0 const;
Quaternion<T> operator * (const Quaternion<T> &);
Quaternion<T> operator * (T);
Quaternion<T> operator / (const Quaternion<T> &);
Quaternion<T> operator ~ () const;
// методы
Quaternion<T> sqr();
Quaternion<T> conjugate() const;
Quaternion<T> inverse() const;
double magnitude() const;
// дружественные функции
friend Quaternion<T> operator * (T, Quaternion<T> &);
//' потоковый ввод/вывод
friend ostream & operator << (ostreamfe, const Quaternion<T>
i friend istream & operator >> (istreamfe, Quaternion<T>&);
opiate <class T> Quaternion<T>::Quaternion()
: r(T@)), i(T@)), j(T@)), k(T@)) О
438
Глава 10. Листинги программ
template <class T> Quaternion<T>::Quaternion(T rl, T il, T ji T
: r(rl), i(il), j(jl), k(kl) О ' kD
template<class T>Quaternion<T>::Quaternion(const Quaternion<T>&
: r(arg.r), i(arg-i), j(arg.j), k(arg.k) -Q ar§)
template <class T> Quaternion<T>::"QuaternionО {}
template <class T> const Quaternion<T>&
Quaternion<T>::operator = (const Quaternion<T>& rvalue)
{
r = rvalue.r; i = rvalue.i; j = rvalue.j; к = rvalue.к;
return *this;
}
template <class T>
Quaternion<T> Quaternion<T>::operator + (const Quaternion<T>& arg)
{ return Quaternion<T>(r+arg.r, i+arg.i, j+arg.j, k+arg.k); }
template <class T>
Quaternion<T> Quaternion<T>::operator - (const Quaternion<T>& arg)
{ return Quaternion<T>(r-arg.r, i-arg.i, j-arg.j, k-arg.k); }
template <class T>
Quaternion<T> Quaternion<T>::operator - () const
{ return Quaternion<T>(-r,-i,-j,-k); }
template <class T>
Quaternion<T> Quaternion<T>::operator * (const Quaternion<T>& arg)
{
return Quaternion<T>(r*arg.r - i*arg.i - j*arg.j - k*arg.k,
r*arg.i + i*arg.r + j*arg.k - k*arg.j,
r*arg.j + j*arg.r + k*arg.i - i*arg.k,
r*arg.k + k*arg.r + i*arg.j - j*arg.i);
}
template <class T>
Quaternion<T> Quaternion<T>::operator * (T arg)
{ return Quaternion<T>(r*arg, i*arg, j*arg, k*arg); }
template <class T>
Quaternion<T> Quaternion<T>::operator / (const Quaternion<T>& ^Q'
{ return *this * arg.inverse О; }
// нормализация кватерниона
template <class T>
Quaternion<T> Quaternion<T>::operator ~ () const
10.4. Класс Quaternion
43
nuaternion<T> result;
double length = magnitudeQ;
result.r = r/length; result.i = i/length;
result.j = j/length; result.к = k/length;
return result;
}
mplate <class T> Quaternion<T> Quaternion<T>::sqrO
Quaternion<T> result;
X temp;
T two = TB);
temp = two*r;
result.r = r*r - i*i - j*j - k*k;
result.i = temp*i;
result.j = temp*j;
result.к = temp*k;
return result;
}
template <class T> Quaternion<T> Quaternion<T>::conjugate() const
{return Quaternion<T>(r,-i,-j,-k); }
template <class T> Quaternion<T> Quaternion<T>::inverse() const
{
Quaternion<T> templ(conjugate());
T temp2 = r*r + i*i + j*j + k*k;
return Quaternion<T>(tempi.r/temp2,tempi.i/temp2,
tempi.j/temp2,tempi.k/temp2);
template <class T> double Quaternion<T>:magnitude() const
{ return sqrt(r*r + i*i + j*j + k*k) ; }
template <class T> Quaternion<T> operator *
(T factor, Quaternion<T>& arg)
{ return arg * factor; }
template <class T>
tream & operator « (ostreamfe s, const Quaternion<T>& arg)
s « "(" << arg.r « "," « arg.i « ","
<< arg.j << "," « arg.к « ")";
у return s;
440
Глава 10. Листинги программ
template <class T>
istreamfe operator >> (istreamfe s, Quaternion<T>& arg)
{
s >> arg.r >> arg.i >> arg.j » arg.k;
return s;
}
#endif
10.5. Класс Derive
Открытый интерфейс класса Derive:
• Derive (): конструктор по умолчанию.
• Derive (const T): конструктор.
• set (const T): задание точки, где берется производная.
• exp(const Derive<T>&): функция экспоненты.
• cos (const Derive<T>&): функция косинуса.
• sin(const Derive<T>&): функция синуса.
• df (const Derive<T>&): точная производная выражения.
• арифметические операторы: -(унарный), +, -, *, /, +=, -
• потоковый оператор: <<
Для более детального описания структуры класса и его мете
ста, обращайтесь к разд. 6.5.
// Derive.h
#ifndef DERIVE.H
#define DERIVE_H
#include <iostream.h>
#include <math.h>
template <class T> class Derive
{
private:
// поля данных
T u, du;
// закрытый конструктор
Derive(const T, const T);
public:
// конструкторы
Derive();
Derive(const T);
Derive(const Derive<T> &);
// методы
void set(const T);
10.5. Класс Derive
441
// арифметические операторы
rive<T> operator - О const;
rive<T> operator += (const Derive<T>&);
rive<T> operator -= (const Derive<T>&);
jve<T> operator *= (const Derive<T>&);
nerive<T> operator /= (const Derive<T>&);
friend Derive<T> operator + (const Derive<T>&, const Derive<T>&)
friend Derive<T> operator - (const Derive<T>&, const Derive<T>&)
friend Derive<T> operator * (const Derive<T>&, const Derive<T>&)
friend Derive<T> operator / (const Derive<T>&, const Derive<T>&)
friend Derive<T> exp(const Derive<T>&);
friend Derive<T> sin(const Derive<T>&);
friend Derive<T> cos(const Derive<T>&);
friend T df(const Derive<T>&);
friend ostreamfe operator << (ostreamfe, const Derive<T>&);
};
template <class T> Derive<T>: :Derive() : u(T@)), du(T(D) {}
template <class T> Derive<T>::Derive(const T v) : u(v), du(T@)) {}
template <class T>
Derive<T>::Derive(const T v, const T dv) : u(v), du(dv) {}
template <class T>
Derive<T>::Derive(const Derive<T> &r) : u(r.u), du(r.du) {}
template <class T> void Derive<T>::set(const T v) { u = v; }
template <class T> Derive<T> Derive<T>::operator - () const
{ return Derive<T>(-u,-du); }
template <class T>Derive<T>Derive<T>::operator+=(const Derive<T>& r)
г return *this = *this + r; }
emplate <class T>Derive<T>Derive<T>::operator-=(const Derive<T>& r)
* return *this = *this - r; }
emplate <class T>Derive<T>Derive<T>::operator*=(const Derive<T>& r)
return *this = *this * r; }
Plate <class T>Derive<T>Derive<T>::operator/=(const Derive<T>& r)
return *this = *this / r; }
temPl^te <class T>
442
Глава 10. Листинги программ
Derive<T> operator + (const Derive<T>& x, const Derive<T>& y)
{ return Derive<T>(x.u + y.u, x.du + y.du); }
template <class T>
Derive<T> operator - (const Derive<T>& x, const Derive<T>& y)
{ return Derive<T>(x.u - y.u, x.du - y.du); }
template <class T>
Derive<T> operator * (const Derive<T>& x, const Derive<T>& y)
{ return Derive<T>(x.u*y.u, y.u*x.du + x.u*y.du); }
template <class T>
Derive<T> operator / (const Derive<T>& x, const Derive<T>& y)
{ return Derive<T>(x.u/y.u, (y.u*x.du - x.u*y.du)/(y.u*y.u)); }
template <class T>
Derive<T> exp(const Derive<T>& x)
{ return Derive<T>(exp(x.u), x.du*exp(x.u)); }
template <class T>
Derive<T> sin(const Derive<T>& x)
{ return Derive<T>(sin(x.u), x.du*cos(x.u)); }
template <class T>
Derive<T> cos(const Derive<T>& x)
{ return Derive<T>(cos(x.u), -x.du*sin(x.u)); }
template <class T> T df(const Derive<T>& x)
{ return x.du; }
template <class T>
ostreamfe operator << (ostreamfe s, const Derive<T>& r)
■{ return s << r.u; }
#endif
10.6. Класс Vector
Открытый интерфейс класса Vector:
• Vector (): конструктор по умолчанию.
• Vector(int): конструктор.
• Vector(int,T): конструктор.
• length(): размерность вектора.
• resize (int n): приведение вектора к размерности п.
• resize(int n,T v): приведение вектора к размерности п и запол!
оставшихся элементов вектора значением v.
10.6. Класс Vector
443
at С int n): установка вектора к размерности п.
' 4et(int n>T v); установка вектора к размерности п и инициализация
' ментов вектора значением v.
Э ифметические операторы: + (унарный), -(унарный), +, -, *, /, +=, -=, *=,
0перат°Ры сРавнения: ==> ! =
' оператор взятия индекса: []
' внутреннее произведение: I
, прямое произведение: '/.
t дотоковые операторы: », «
Вспомогательные функции из VecNorm.h:
* norml(const Vectorfe): 1-норма вектора.
* norm2(const Vectorfe): 2-норма вектора.
* norml (const Vectorfe): бесконечная норма вектора.
* normalize (const Vectorfe): нормализация вектора.
Для более детального описания структуры класса и его методов,
пожалуйста, обращайтесь к разд. 6.6.
// Vector.h
#ifndef MVECT0R_H
#define MVECT0R_H
#include <iostream.h>
#include <string.h>
♦include <math.h>
♦include <assert.h>
// опережающее объявление
template <class T> class Matrix;
'/ определение класса Vector
template <class T> class Vector
Private:
II поля данных
int size;
T *data;
public:
// конструкторы
VectorQ ;
Vector(int);
Vector(int, T);
Vector(const Vector<T>&);
"VectorQ;
444
Глава 10. Листинги программ
II методы
Т & operator [] (int) const;
int lengthQ const;
void resize(int);
void resize(int, T);
void reset(int);
void reset(int, T);
// арифметические операторы
const Vector<T>& operator = (const Vector<T>&):
const Vector<T>& operator = (T) ;
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
Vector<T>
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
operator
+ (
- (
+=
-=
*=
/=
+
-
*
/
+=
-=
*=
/=
+
-
*
/
) const;
) const;
(const
(const
(const
(const
(const
(const
(const
(const
(T)
(T)
(T)
(T)
Vector<T>&)
Vector<T>&)
Vector<T>&)
Vector<T>&)
Vector<T>&)
Vector<T>&)
Vector<T>&)
Vector<T>&)
(T) const;
(T) const;
(T) const;
(T)
const;
const
const
const
const
T operator I (const Vector<T>&); // внутреннее/скалярное
// произведение
Vector<T> operator '/, (const Vector<T>&) ; // прямое произведение
>;
// функции потокового ввода/вывода
friend class Matrix<T>;
friend ostreamfe operator << (ostreamfe, const Vector<T>&);
friend istreamfe operator » (istreamfe, Vector<T>&);
// реализация класса Vector
template <class T> Vector<T>::Vector() : size@), data(NULL) О
template <class T> Vector<T>::Vector(int n) : size(n),data(new TLnJ
{ assert(data != NULL); }
10.6. Класс Vector
te <class T> Vector<T>::Vector(int n, T value)
1 size(n), data(new T[n])
i aSSert(data != NULL);
^ r (int i=0; i<n; i++) data[i] = value;
}
late <class T> Vector<T>::Vector(const Vector<T> &v)
tS . gize(v.size) , data(new T[v.size])
{ assert(data != NULL);
for (int i=0> i<v-size; i++) data[i] = v.data[i];
}
template <class T> Vector<T>::"Vector() { delete [] data; }
template <class T> T & Vector<T>::operator[] (int i) const
{
assert(i >= 0 && i < size);
return data[i];
}
template <class T> int Vector<T>::lengthO const
{ return size; }
template <class T> void Vector<T>::resize(int length)
{
int i;
T zero@);
T *newData = new T[length]; assert(newData != NULL);
if (length <= size)
for (i=0; Klength; i++) newData[i] = data[i] ;
else
{
for (i=0; Ksize; i++) newData[i] = data[i] ;
for (i=size; i<length; i++) newData[i] = zero;
delete [] data;
size = length;
data = newData;
Opiate <class T> void Vector<T>::resize(int length, T value)
int i;
T *newData = new T [length]; assert(newData != NULL);
446
Глава 10. Листинги программ
if (length <= size)
for (i=0; Klength; i++) newDataEi] = data[i] ;
else
{
for (i=0; i<size; i++) newDataCi] = data[i];
for (i=size; Klength; i++) newDataCi] = value;
>
delete [] data;
size = length;
data = newData;
template <class T> void Vector<T>::reset(int length)
{
T zero@);
delete [] data;
data = new T[length]; assert(data != NULL);
size = length;
for (int i=0; i<size; i++) data[i] = zero;
}
template <class T> void Vector<T>::reset(int length, T value)
-C
delete [] data;
data = new T[length]; assert(data != NULL);
size = length;
for (int i=0; i<size; i++) data[i] = value;
}
template <class T>
const Vector<T> & Vector<T>::operator = (const Vector<T> &v)
{
if (this == &v) return *this;
if (size != v.size)
{
delete [] data;
data = new T[v.size]; assert(data != NULL);
size = v.size;
>
for (int i=0; Kv.size; i++) data[i] = v.data[i];
return *this;
}
template <class T> const Vector<T>& Vector<T>::operator = (T va
{
for (int i=0; i<size; i++) data[i] = value;
10.6. Класс Vector
1-е
turn
*this;
} te <class T> Vector<T> Vector<T>::operator + () const
Return *this; }
te <class T> Vector<T> Vector<T>::operator - () const
^In *this * T(-l); }
return
plate <class T>
* tor<T> Vector<T>: .-operator += (const Vector<T>& v)
assert(size==v.size);
for (int i=0; Ksize; i++) data[i] += v.data[i] ;
return *this;
}
template <class T>
Vector<T> Vector<T>::operator -= (const Vector<T>& v)
{
assert(size==v.size);
for (int i=0; Ksize; i++) data[i] -= v.data[i] ;
return *this;
}
template <class T>
Vector<T> Vector<T>::operator *= (const Vector<T>& v)
{
assert(size==v.size) ;
for (int i=0; i<size; i++) data[i] *= v.data[i];
return tthis;
}
Opiate <class T>
Vector<T> Vector<T>::operator /= (const Vector<T>& v)
assert(size==v.size);
for (int i=0; Ksize; i++) data[i] /= v.data[i];
, return *this;
>^te <ciass T>
«61 ■
c °r<T> Vector<T>: .-operator + (const Vector<T>& v) const
6ctor<T> result(*this);
} return result += v;
448
Глава 10. Листинги программ
template <class T>
Vector<T> Vector<T>::operator - (const Vector<T>& v) const
{
Vector<T> result(*this);
return result -= v;
>
template <class T>
Vector<T> Vector<T>::operator * (const Vector<T>& v) const
-C
Vector<T> result(*this);
return result *= v;
>
template <class T>
Vector<T> Vector<T>::operator / (const Vector<T>& v) const
{
Vector<T> result(*this);
return result /= v;
>
template <class T> Vector<T> Vector<T>::operator += (T c)
i
for (int i=0; Ksize; i++) data[i] += c;
return *this;
}
template <class T> Vector<T> Vector<T>::operator -= (T c)
{
for (int i=0; Ksize; i++) data[i] -= c;
return *this;
>
template <class T> Vector<T> Vector<T>::operator *= (T c)
{
for (int i=0; Ksize; i++) data[i] *= c;
return *this;
}
template <class T> Vector<T> Vector<T>::operator /= (T c)
{
for (int i=0; Ksize; i++) data[i] /= c;
return *this;
}
template <class T> Vector<T> Vector<T>::operator + (T c) cons
10.6. Класс Vector 44!
1 tfector<T> result(*this);
Bturn
result += c;
ret
У
te <class T> Vector<T> Vector<T>::operator - (T c) const
{ Vector<T> result(*this);
j-eturn result -= c;
}
late <class T> Vector<T> Vector<T>::operator * (T c) const
{
Vector<T> result(*this);
return result *= c;
}
template <class T> Vector<T> Vector<T>::operator / (T c) const
{
Vector<T> result(*this);
return result /= c;
}
template <class T> Vector<T> operator + (T c, const Vector<T>& v)
{ return v+c; }
template <class T> Vector<T> operator - (T c, const Vector<T>& v)
{ return -v+c; }
template <class T> Vector<T> operator * (T c, const Vector<T>& v)
■f return v*c; }
template <class T> Vector<T> operator / (T c, const Vector<T>& v)
Vector<T> result(v.lengthO);
f°r (int i=0; Kresult.length0; i++) result[i] = c/v[i];
return result;
//
°калярное произведение
{
assert(size == v.size);
T result@);
0r (int i=0; Ksize; i++) result = result + data[i]*v.data[i];
result;
,6BlPlate <class T> T Vector<T>: : operator I (const Vector<T>& v)
450
Глава 10. Листинги программ
// векторное произведение
template <class T>
Vector<T> Vector<T>: : operator */. (const Vector<T>& v)
{
assert(size == 3 && v.size == 3);
Vector<T> resultC);
result.data[0] = data[l] * v.data[2] - v.data[l] * data [2] •
result.data[l] = v.data[0] * data[2] - data[0] * v.data[2]'
result.data[2] = data[0] * v.data[l] - v.data[0] * data[l]'
return result;
>
// равенство
template <class T>
int operator == (const Vector<T>& u, const Vector<T>& v)
-C
if (u.length.0 != v.lengthO) return 0;
for (int i=0; i<u. lengthO ; i++)
if (u[i] != v[i]) return 0;
return 1;
}
// неравенство
template <class T>
int operator != (const Vector<T>& u, const Vector<T>& v)
{ return !(u==v); }
template <class T>
ostream & operator << (ostream& s,const Vector<T>& v)
-C
int lastnum = v.lengthO;
for (int i=0; Klastnum; i++) s << "[" « v[i] « "]" « endl;
return s;
>
template <class T> istream & operator » (istreamft s, Vector<T>& v-
{
int i, num.;
s.clear(); // установка статуса потока в "готов
s >> num.; // читаем размерность вектора
if(!s.good()) return s; // если не получаем целого числа, уходят»
v.resize(num); // переустанавливаем размерность вектор
for (i=0; i<num; i++)
{
s » v[i]; // считываем компоненты вектора
if (! s. good О)
10.6. Класс Vector
я. clear (s.rdstateQ I ios::badbit):
>
s.cl
return s;
,rn s>
}
#endi*
,/ vecNorm-h
I/ векторные нормы
ifndef MVECNORM.H
define MVECN0RM_H
include <iostream.h>
tinclude <math.h>
template <class T> T norml(const Vector<T>& v)
{
T result@);
for (int i=0; i<v.length(); i++)
result = result + abs(v[i]);
return result;
}
double norml(const Vector<double>& v)
{
double result@);
f°r (int i=0; i<v.length(); i++)
result = result + fabs(v[i]);
return result;
etlPlate <class T> double norm2(const Vector<T>«
T result@) ;
°r (int i=0; i<v.length.О ; i++)
result = result + v[i]*v[i];
} return sqrt(double(result));
452
Глава 10. Листинги программ
template <class T> T normi(const Vector<T>& v)
-С
T maxltem(abs(v[0])) , temp;
for (int i=l; i<v.length(); i++)
{
temp = abs(v[i]);
if (temp > maxltem) maxltem = temp;
}
return maxltem;
double normi(const Vector<double>& v)
{
double maxltem(fabs(v[0])), temp;
for (int i=l; Kv.lengthO; i++)
{
temp = fabs(v[i]);
if (temp > maxltem) maxltem = temp;
}
return maxltem;
template <class T> Vector<T> normalize(const Vect
{
Vector<T> result(v.length());
double length = norm2(v);
for (int i=0; Kv.lengthO; i++)
result [i] = v[i]/length;
return result;
}
#endif
10.7. Класс Matrix
Открытый интерфейс класса Matrix:
• Matrix(): конструктор по умолчанию.
• Matrix(int, int): конструктор.
• Matrix(int, int T): конструктор.
• Matrix(const Vector<T>&): конструктор.
• identityO: создать единичную матрицу.
• transpose(): транспонирование матрицы.
10.7. Класс Matrix
453
rse(): обращение матрицы.
' 1 ,0П' вычисление следа матрицы.
' *0rminant(): вычисление детерминанта матрицы.
= fV количество строк матрицы.
i = (")' количество столбцов матрицы.
ire(int, int): приведение матрицы к новым размерностям.
res1*1 _.
^ze(int, mt, Т): приведение матрицы к новым размерностям и ини-
' яалйзация оставшихся элементов матрицы.
c(const Matrix<T>&): оператор векторизации.
. 0n(const Matrix<T>&, const Matrix<T>&): произведение Кронекера.
fill(T v): заполнение матрицы значением v.
, арифметические операторы: +(унарный), -(унарный), +, -, *, /, +=,
-=, *=, /=
• оператор взятия вектор-строки: ()
• оператор взятия вектор-столбца: []
» потоковые операторы: >>, «
Вспомогательные функции из Mat Norm, h:
• norml (const Matrix&): первая норма матрицы.
• norml (const Matrix&): бесконечная норма матрицы.
• normH(const Matrix&): норма Гильберта—Шмидта матрицы.
Для более детального описания структуры класса и его методов,
пожалуйста, обращайтесь к разд. 6.7.
// Matrix.h
"ifndef MATRIX_H
"define MATRIX.H
"include <iostream.h>
"include <math.h>
"include <assert.h>
"include "Vector.h"
определение класса Matrix
*6II*Plate <class T> class Matrix
Protected:
// поля данных
int rowNum, colNum;
Vector<T> *mat;
PUblic:
// конструкторы
MatrixO;
Matrix(int, int);
Matrix(int, int, T);
454
Глава 10. Листинги программ
Matrix(const Vector<T>&) ;
Matrix(const Matrix<T>&);
"Matrix();
// методы
Vector<T>& operator [] (int) const;
Vector<T> operator () (int) const;
Matrix<T> identityO;
Matrix<T> transpose() const;
Matrix<T> inverse() const;
T traceQ const;
T determinant() const;
int rows() const;
int cols() const;
void resize(int, int);
void resize(int, int, T);
void fill(T);
// арифметические операторы
const Matrix<T>& operator = (const Matrix<T>&);
const Matrix<T>& operator = (T);
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Vector<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
Matrix<T>
operator + (
operator - (
operator +=
operator -=
operator *=
operator +
operator -
operator *
operator *
operator +=
operator -=
operator *=
operator /=
operator +
operator -
operator *
operator /
const;
const;
const Matrix<T>&);
const Matrix<T>&);
const Matrix<T>&);
const Matrix<T>&) const;
const Matrix<T>&) const;
const Matrix<T>&) const;
const Vector<T>&) const;
T):
T):
T):
T)i
T) const;
T) const;
T) const;
T) const;
friend Vector<T> vec(const Matrix<T>&);
friend Matrix<T> kron(const Matrix<T>&, const Matrix<T>&) '•
friend ostream к operator << (ostreamft, const Matrix<T>&)>
10.7. Класс Matrix
friend istream & operator >> (istream &, Matrix<T>&);
)'> ялизация класса Matrix
// ?е te <class T> Matrix<T>::Matrix()
te rowNum(O), colNum(O), mat(NULL) {}
1ate <class T> Matrix<T>::Matrix(int r, int c)
telBP rowNum(r) , colNum(c), mat(new Vector<T>[r])
( aSsert(mat != NULL);
{or (int i=0; i<r; i++) mat[i].resize(c);
}
mplate <class T> Matrix<T>::Matrix(int r, int с, Т value)
. rowNum(r), colNum(c), mat(new Vector<T>[r])
assert(mat != NULL);
for (int i=0; i<r; i++) mat[i].resize(c,value);
}
template <class T> Matrix<T>::Matrix(const Vector<T>& v)
: rowNum(v.length()), colNum(l), mat(new Vector<T>[rowNum])
{
assert(mat != NULL);
for (int i=0; KrowNum; i++) mat[i] .resized ,v[i] ) ;
}
template <class T> Matrix<T>::Matrix(const Matrix<T>& m)
: rowNum(m.rowNum), colNum(m.colNum),
mat(new Vector<T>[m.rowNum] )
assert(mat != NULL);
for (int i=0; Km.rowNum; i++) mat[i] = m.mat[i];
^emPlate <class T> Matrix<T>::"Matrix()
} delete [] mat;
mPlate <class T> Vector<T> & Matrix<T>::operator []
/ (int index) const
Assert(index>=0 && index<rowNum);
у teturn mat [index] ;
456
Глава 10. Листинги программ
template <class T> Vector<T> Matrix<T>::operator ()
(int index) const
{
assert(index>=0 && index<colNum);
Vector<T> result(rowNum);
for (int i=0; i<rowNum; i++) result[i] = mat[i][index];
return result;
template <class T> Matrix<T> Matrix<T>::identity()
{
for (int i=0; KrowNum; i++)
for (int j=0; j<colNum; j++)
if (i==j) mat[i][j] = T(l);
else mat[i][j] = T@);
return *this;
}
template <class T> Matrix<T> Matrix<T>::transpose() const
{
Matrix<T> result(colNum,rowNum);
for (int i=0; KrowNum; i++)
for (int j=0; j<colNum; j++)
result [j] [i] =mat[i][j];
return result;
}
// символьное обращение методом Леверье
template <class T> Matrix<T> Matrix<T>: .-inverse() const
{
assert(rowNum == colNum) ;
Matrix<T> B(*this), D, KrowNum, colNum);
T cO(B.trace()), cl;
int i;
I.identity();
for (i=2; KrowNum; i++)
{
В = *this * (B-cO*I);
cO = B.trace()/T(i);
}
D = *this * (B-cO*I);
cl = D.trace()/T(i);
return (B-cO*I)/cl;
10.7. Класс Matrix
<class T> T Matrix<T>::trace() const
{ sert(rowNum == colNum);
Result @);
r (int i=0; i<ro¥Num; i++) result += mat[i][i];
return result;
'' "- -- <class T> T Matrix<T>::determinant() const
}
символьный детерминант
templar
assert(rowNum==colNum);
Matrix<T> B(*this), KrowNum, colNum, T@));
T c(B.traceO) ;
int i;
for (i=0; KrowNum; i++) I[i][i] = T(l);
// заметим, что детерминант типа int получается нулевым
// из-за деления на T(i)
for (i=2; i<=rowNum; i++)
{
В = *this * (B-c*I) ;
с = B.trace()/T(i);
}
if (rowNum°/,2) return c;
return -c;
}
template <class T> int Matrix<T>::rows() const
{
return rowNum;
}
template <class T> int Matrix<T>::cols() const
return colNum;
}
Opiate <class T> void Matrix<T>::resize(int r, int c)
int i;
Vector<T> *newMat = new Vector<T>[r]; assert(newMat != N
if (r<=rowNum)
for (i=0; i<r; i++)
{
(mat+i) -> resize(c);
458
Глава 10. Листинги программ
newMat[i] = mat[i];
>
}
else
{
for (i=0; i<rowNum; i++)
{
(mat+i) -> resize(c);
newMat[i] = mat[i];
}
for (i=rowNum; i<r; i++) newMat[i].resize(c);
}
delete [] mat;
rowNum = r; colNum = c;
mat = newMat;
template <class T> void Matrix<T>::resize(int r, int с, Т value)
{
int i;
Vector<T> *newMat = new Vector<T>[r]; assert(newMat != NULL);
if (r<=rowNum)
{
for (i=0; i<r; i++)
{
(mat+i) -> resize(c,value);
newMat[i] = mat[i];
}
}
else
{
for (i=0; i<rowNum; i++)
{
(mat+i) -> resize(c,value);
newMat[i] = mat[i];
}
for (i=rowNum; i<r; i++) newMat[i].resize(c,value);
}
delete [] mat;
rowNum = r; colNum = c;
mat = newMat;
template <class T> void Matrix<T>::fill(T value)
{
for (int i=0; KrowNum; i++)
10.7. Класс Matrix
}
for (int j=0; j<colNum; j++)
mat[i] [j] = value;
late <class T>
* +■ Matrix<T>& Matrix<T>::operator = (const Matrix<T>& m)
cons* ua
-f (this == &m) return *this;
delete [] mat;
rowNum = m.rowNum; colNum = m.colNum;
mat = new Vector<T>[m.rowNum]; assert(mat != NULL);
for (int i=°; Km.rowNum; i++) mat[i] = m.mat[i];
return *this;
}
template <class T>
const Matrix<T>& Matrix<T>::operator = (T value)
{
for (int i=0; i<rowNum; i++) mat[i] = value;
return *this;
}
template <class T> Matrix<T> Matrix<T>::operator + () const
{
return *this;
}
template <class T> Matrix<T> Matrix<T>::operator - () const
{
return *this * T(-l);
}
template <class T>
"atrix<T> Matrix<T>::operator += (const Matrix<T>& m)
return *this = *this + m;
J^Plate <class T>
trix<T> Matrix<T>::operator -= (const Matrix<T>& m)
}
return *this = *this - m;
M^Plate <class T>
lX<T> Matrix<T>::operator *= (const Matrix<T>& m)
460 Глава 10. Листинги программ
■С
return *this = *this * m;
}
template <class T>
Matrix<T> Matrix<T>::operator +
{
assert(rowNum == m.rowNum &&
Matrix<T> result(*this);
for (int i=0; KrowNum; i++)
return, result;
}
template <class T>
Matrix<T> Matrix<T>::operator -
{
assert(rowNum == m.rowNum &&
Matrix<T> result(*this);
for (int i=0; KrowNum; i++)
return result;
}
template <class T>
Matrix<T> Matrix<T>::operator * (const Matrix<T>& m) const
{
assert(colNum == m.rowNum);
Matrix<T> result(rowNum, m.colNum, T@));
for (int i=0; KrowNum; i++)
for (int j=0; j<m.colNum; j++)
for (int k=0; k<colNum; k++)
result [i] [j] += mat[i][k] * m[k] [j] ;
return result;
}
template <class T>
Vector<T> Matrix<T>::operator * (const Vector<T>& v) const
{
assert (colNum == v.lengthO);
Vector<T> result(rowNum);
// используется внутреннее произведение I
for (int i=0; KrowNum; i++) result [i] = (mat[i] I v) ;
return result;
}
template <class T> Matrix<T> Matrix<T>::operator += (T c)
(const Matrix<T>& m) const
colNum == m.colNum);
result[i] += m[i];
(const Matrix<T>& m) const
colNum == m.colNum);
result [i] -= m[i];
10.7. Класс Matrix
{ ert(rowNum == colNum);
aS (int i=0; KrowNum; i++) mat[i][i] += c;
return *this;
}
я <-« <class T> Matrix<T> Matrix<T>:: operator -= (T c)
temP1^6
* ssert(rowNum == colNum);
for (int i=0; i<rowNum; i++) mat[i][i] -= c;
return *this;
}
mnlate <class T> Matrix<T> Matrix<T>::operator *= (T c)
for (int i=0; KrowNum; i++) mat[i] *= c;
return *this;
}
template <class T> Matrix<T> Matrix<T>::operator /= (T c)
{
for (int i=0; i<rowNum; i++) mat[i] /= c;
return *this;
}
template <class T>
Matrix<T> Matrix<T>: : operator + (T value) const
{
assert(rowNum == colNum);
Matrix<T> result(*this);
return result += value;
template <class T>
^atrix<T> Matrix<T>:: operator - (T value) const
assert(rowNum == colNum);
Matrix<T> result(*this);
return result -= value;
J^Plate <class T>
rix<T> Matrix<T>::operator * (T value) const
Matrix<T> result(*this);
\ re'tiirn result *= value;
462
Глава 10. Листинги программ
template <class T>
Matrix<T> Matrix<T>::operator / (T value) const
{
Matrix<T> result(*this);
return result /= value;
}
template <class T>
Matrix<T> operator + (T value, const Matrix<T>& m)
{
return m + value;
}
template <class T>
Matrix<T> operator - (T value, const Matrix<T>& m)
{
return -m + value;
}
template <class T>
Matrix<T> operator * (T value, const Matrix<T>& m)
{
return m * value;
}
template <class T>
Matrix<T> operator / (T value, const Matrix<T>& m)
{
Matrix<T> result(m.rows(),m.cols());
for (int i=0; i<result.rows(); i++) result[i] = value/m[i];
return result;
}
// оператор векторизации
template <class T> Vector<T> vec(const Matrix<T>& m)
{
int i=0, j, k, size = m.rowNum * m.colNum;
Vector<T> result(size);
for (j=0; j<m.colNum; j++)
for (k=0; k<m.rowNum; k++) result[i++] = m.mat[k][j];
return result;
}
// произведение Кронекера
template <class T>
Matrix<T> kron(const Matrix<T>& s, const Matrix<T>& m)
10.7. Класс Matrix
463
^ nt sizel = s.rowNum * m.rowNum,
size2 = s.colNum * m.colNum,
i, j. k, p;
ijatrix<T> result (sizel, size2) ;
for (i=0; Ks.rowNum; i++)
for (j=0; j<s.colNum; j++)
for (k=0; k<m.rowNum; k++)
for (p=0; p<m.colNum; p++)
result[k + i*m.rowNum][p + j*m.colNum]
= s.mat[i][j] * m.mat[k][p];
return result;
}
template <class T>
int operator == (const Matrix<T>& ml, const Matrix<T>& m2)
{
if (ml.rows() != m2.rows()) return 0;
for (int i=0; i<ml.rows(); i++)
if (ml[i] != m2[i]) return 0;
return 1;
}
template <class T>
int operator != (const Matrix<T>& ml, const Matrix<T>& m2)
{
return !(ml==m2);
}
template <class T> ostreamft operator « (ostreamfe s,
const Matrix<T>& m)
int t = m.cols()-l;
for (int i=0; Km.rowsO; i++)
{
S « "[" ;
for (int j=0; j<t; j++) s « m[i][j] « " ";
s « m[i] [t] « "]" « endl;
}
return s;
mPlate <class T> istreamft operator » (istreamft s, Matrix<T>& m)
lnt i, j, numl, num2;
464
Глава 10. Листинги программ
s.clear(); // установка статуса потока в ",,
s >> numl; // чтение числа строк °й"
if (! s.goodO) return s; // если получили не целое, уходр^
s >> num2; // чтение числа столбцов
if (! s.goodO) return s; // если получили не целое, уходим
m.resize(numl,num2); // приводим размерности в соотве
for (i=0; Knuml; i++) ClBl
for (j=0; j<num2; j++)
{
s » m[i] [j];
if (! s.goodO)
{
s . clear (s .rdstateO I ios: :badbit) ;
return s;
}
>
return s;
>
#endif
// MatNorm.h
// матричные нормы
#ifndef MATNORM.H
#define MATNORM.H
#include <iostream.h>
#include <math..h>
«include "Vector.h"
«include "VecNorm.h"
template <class T> T norml(const Matrix<T>& m)
{
T maxItem(O), temp;
int i,j ;
for (i=0; i<m.rows(); i++) maxltem += m[i][0];
for (i=l; i<m.cols(); i++)
{
temp = T@);
for (j=0; j<m.rows(); j++)
temp += abs(m[j][i]);
if (temp > maxltem) maxltem = temp;
>
10.8. Класс Array
465
if (norml(m[i]) > maxltem) maxltem = norml(m[i]);
eturn maxltem;
}
*a <class T> T norml(const Matrix<T> &m)
I maXitem(norml(m[0]));
(int i=l; i<m.rows(); i++)
Lf (norml(m[
return maxltem;
}
note <class T> T normH(const Matrix<T> &m)
teBPxa
return sqrt((m*(m.transpose())).trace());
}
(fendif
10.8. Класс Array
Открытый интерфейс класса Array 1:
• Array К int = 0): конструктор.
• Array1(int, T) : конструктор.
• resize (int): изменение размера одномерного массива.
• resize (int, T v): изменение размера одномерного массива и
инициализация оставшихся элементов значением v.
• size (int = 0): размер массива.
• арифметические операторы: +, -, *, +=, -=, *=
• оператор взятия индекса: []
• потоковый оператор: <<
Открытый интерфейс класса Аггау2:
• Array2(int = 0, int = 0): конструктор.
•Array2(int, int, T): конструктор.
• resize(int, int): изменение размеров двумерного массива.
resize (int, int, T v): изменение размеров двумерного массива и иници-
элизация оставшихся элементов значением v.
size(int = 0): размеры массива.
аРифметические операторы: +, -, *, +=, -=, *=
оператор взятия индекса: []
п°токовый оператор: <<
кРЬ1тый интерфейс класса АггауЗ:
Array3(int = 0, int = 0, int = 0): конструктор.
rray3(int, int, int, T): конструктор.
esize(int, int, int): изменение размеров трехмерного массива.
466
Глава 10. Листинги программ
• resize (int, int, int, T v): изменение размеров трехмерного ма
инициализация оставшихся элементов значением v. Ctlfia ц
• size(int = 0): размеры массива.
• арифметические операторы: +, -, *, +=, -=, *=
• оператор взятия индекса: []
• потоковый оператор: <<
Открытый интерфейс класса Аггау4:
• Array4(int = 0, int = 0, int = 0, int = 0): конструктор.
• Array4(int, int, int, int, T): конструктор.
• resize (int, int, int): изменение размеров четырехмерного массива
• resize(int, int, int, int, T v): изменение размеров четырехмеп»
массива и инициализация оставшихся элементов значением v. °
• size(int = 0): размеры массива.
• арифметические операторы: +, -, *, +=, -=, *=
• оператор взятия индекса: []
• потоковый оператор: <<
Для более детального описания структуры класса и его методов, пожалуйста
обращайтесь к разд. 6.8.
// Array.h
#ifndef ARRAY_H
«define ARRAY_H
«include <iostream.h>
«include <assert.h>
template <class T>
class Arrayl
{
private:
// поля данных
int n_data;
T *data;
public:
// конструкторы
Arrayl(int = 0);
Arrayl(int, T);
Arrayl(const Arrayl<T>&);
"Arrayl();
// методы
void resize(int);
void resize(int, T) ;
10.8. Класс Array
1 & operator [] (int) const;
int size(int = 0) const;
// арифметические операторы
const Arrayl<T>& operator = (const Arrayl<T>&);
const Array1<T>& operator = (T);
ArrayKT> operator *= (T) ;
Array 1<T> operator += (const ArrayKT>&)
Arrayl<T> operator -= (const Arrayl<T>&)
Arrayl<T> operator + (const Arrayl<T>&)
Arrayl<T> operator - (const Arrayl<T>&)
// операторы потокового ввода/вывода
friend ostream & operator << (ostreamu, const Array1<Т>Я
г. // конец объявления класса Array1
// конструкторы, деструкторы и конструктор копирования
template <class T>
Arrayl<T>::Arrayl(int n) : n_data(n), data(new T[n])
{ assert(data != NULL); >
template <class T>
Arrayl<T>::Arrayl(int n, T num) : n_data(n), data(new T[n])
{
assert(data != NULL);
for (int i=0; i<n_data; i++) data[i] = num;
}
template <class T> Array1<T>::Array1(const Array1<T>& v)
: n_data(v.n_data), data(new T[v.n_data])
{
assert(data != NULL);
for (int i=0; i<v.n_data; i++) data[i] = v.data[i];
template <class T> Array 1<T>: : "ArrayK)
delete [] data;
'' методы
efflplate <class T> void Arrayl<T>::resize(int n)
lilt i;
* *newData = new T[n]; assert(newData != NULL);
468
Глава 10. Листинги программ
if (n <= n_data)
for (i=0; i<n; i++) newData[i] = data[i];
else
for (i=0; i<n_data; i++) newData[i] = data[i];
delete [] data;
n_data = n;
data = newData;
>
template <class T> void Arrayl<T>::resize(int n, T value)
{
int i;
T *newData = new T[n]; assert(newData != NULL);
if (n <= n_data)
for (i=0; i<n; i++) newData[i] = data[i];
else
{
for (i=0; i<n_data; i++) newData[i] = data[i];
for (i=n_data; i<n; i++) newData[i] = value;
>
delete [] data;
n_data = n;
data = newData;
>
// различные закрытые операторы
template <class T> T& Arrayl<T>::operator [] (int i) const
{
assert(i >= 0 && i < n_data);
return data[i];
>
template <class T> int Arrayl<T>::size(int index) const
{
assert(index == 0);
return n_data;
>
template <class T>
const Array 1<T>& Arrayl<T>: .-operator = (const Array 1<T>& v)
{
if (this == &v) return *this;
if (n_data != v.n_data)
{
delete [] data;
n_data = v.n_data;
10.8. Класс Array
data = new T[v.n_data]; assert(data != NULL);
Cint i=0; i<v.n_data; i++) data[i] = v.data[i];
return *tnis;
}
,„te <class T> const Array1<T>& Array1<T>::operator = (T пшп)
^ ioI (int i=0; i<n_data; i++) data[i] = пшп;
return *this;
}
mplate <class T> Arrayl<T> Array1<T>::operator *= (T пшп)
for (int i=0; i<n_data; i++) data[i] *= пшп;
return *this;
}
template <class T>
Array 1<T> Array1<T>::operator += (const Arrayl<T>& v)
{ return *this = *this + v; }
template <class T>
Arrayl<T> Array1<T>::operator -= (const Arrayl<T>& v)
{ return *this = *this - v; }
template <class T>
Arrayl<T> Array1<T>::operator + (const Array1<T>& v)
{
assert(n_data==v.n_data) ;
ArrayKT> temp(n_data) ;
for (int i=0; i<n_data; i++) temp[i] = data[i] + v.data[i];
return temp;
Opiate <class T>
Ггау1<т> ArrayKT>: : operator - (const ArrayKT>& v)
assert(n_data==v.n_data);
Arrayl<T> temp(n.data);
for (int i=0; i<n_data; i++) temp[i] = data[i] - v.data[i];
у return temp;
дружественные функции
^JPlate <class T>
teata& operator << (ostreamft s,const Arrayl<T>& v)
470
Глава 10. Листинги программ
•С
int n_data = v.n_data-l;
s « "С";
for (int i=0; i<n_data; i++) s « v.data[i] « " ";
s « v.data[n_data] « "]";
return s;
}
template <class T>
int operator == (const Arrayl<T>& vl, const Arrayl<T>& v2)
{
if (vl.sizeQ != v2.size()) return 0;
for (int i=0; i<vl.size(); i++)
{
if (vl[i] != v2[i]) return 0;
}
return 1;
}
template <class T>
int operator != (const ArrayKT>& vl, const Arrayl<T>& v2)
{ return !(vl==v2); >
template <class T> class Array2
{
private:
// поля данных
int rows, cols;
ArrayKT> *data2D;
public:
// конструкторы
Array2(int = 0, int = 0);
Array2(int, int, T);
Array2(const Array2<T>&);
~Array2();
// методы
void resize(int, int);
void resize(int, int, T);
Arrayl<T>& operator [] (int) const;
int size(int = 0) const;
// арифметические операторы
const Array2<T>& operator = (const Array2<T>&);
const Array2<T>& operator = (T);
Array2<T> operator *= (T);
10.8. Класс Array
471
Л
Array2<T> operator += (const Array2<T>&)
Array2<T> operator -= (const Array2<T>&)
Array2<T> operator + (const Array2<T>&)
Array2<T> operator - (const Array2<T>&)
// функции потокового ввода/вывода
friend ostream & operator « (ostreamfe, const Array2<T>&);
// конец объявления класса Array2
// конструктор, деструктор и конструктор копирования
mplate <class T> Array2<T>::Array2(int r, int с)
: rows(г), cols(c), data2D(new Arrayl<T>[r])
assert(data2D != NULL);
for (int i = 0; i<r; ++i) (data2D+i) -> resize(c);
}
template <class T> Array2<T>: : Array2(int r, int с, Т num)
: rows(r), cols(c), data2D(new Array1<T>[r])
{
assert(data2D != NULL);
for (int i = 0; i<r; ++i) (data2D+i) -> resize(c,num);
}
template <class T> Array2<T>::Array2(const Array2<T>& m)
: rows(m.rows), cols(m.cols), data2D(new Array1<T>[m.rows])
{
assert(data2D != NULL);
for (int i=0; Km.rows; ++i) data2D[i] = m.data2D[i];
}
template <class T> Array2<T>::~Array2() { delete [] data2D; >
'/ методы
tempiate <class T> void Array2<T>::resize(int r, int c)
int i;
ArrayKT> *newArray = new Arrayl<T>[r]; assert(newArray != NULL);
if (r <= rows)
{
for (i=0; i<r; i++)
{
(data2D+i) -> resize(c);
newArray[i] = data2D[i];
>
472
Глава 10. Листинги программ
else
{
for (i=0; i<rows; i++)
{
(data2D+i) -> resize(c);
newArray[i] = data2D[i];
>
for (i=rows; i<r; i++) newArray[i].resize(c);
>
delete [] data2D;
rows = r; cols = c;
data2D = newArray;
template <class T> void Array2<T>::resize(int r, int с, Т value)
{
int i;
Array1<T> *newArray = new Array1<T> [r]; assert(newArray != NULL)'
if (r<=rows)
{
for (i=0; i<r; i++)
{
(data2D+i) -> resize(c,value);
newArray[i] = data2D[i];
>
>
else
{
for (i=0; Krows; i++)
{
(data2D+i) -> resize(c.value);
newArray[i] = data2D[i] ;
}
for (i=rows; i<r; i++) newArray[i].resize(c,value);
>
delete [] data2D;
rows = r; cols = c;
data2D = newArray;
// различные закрытые операторы
template <class T> Array1<T>& Array2<T>::operator [] (int i) const
{
assert(i >= 0 && i < rows);
return data2D[i];
>
10.8. Класс Array
4
te <class T> int Array2<T>::size(int index) const
teBPla
( sert(index==0 II index==l);
af (index) return cols;
return rows;
}
template
<class T>
1- Array2<T>& Array2<T>::operator = (const Array2<T>& m)
cons1'
if (this == &m) return *this;
delete [] data2D;
cols = m.cols; rows = m.rows;
data2D = new Array1<T>[m.rows]; assert(data2D != NULL);
for (int i=0; Km.rows; i++) data2D[i] = m.data2D[i];
return *this;
}
template <class T> const Array2<T>& Array2<T>::operator = (T num)
{
for (int i=0; Krows; i++) data2D[i] = num;
return *this;
}
template <class T> Array2<T> Array2<T>::operator *= (T num)
{
for (int i=0; Krows; i++) data2D[i] *= num;
return *this;
}
template <class T>
Array2<T> Array2<T>::operator += (const Array2<T>& m)
■f return *this = *this + m; >
template <class T>
*rray2<T> Array2<T>::operator -= (const Array2<T>& m)
* return *this = *this - m; >
^ttplate <class T>
Ггау2<т> Array2<T>::operator + (const Array2<T>& m)
assert((m.cols == cols) && (m.rows == rows));
Array2<T> temp(rows,cols);
f°r (int i=0; Krows; i++) temp[i] = data2D[i] + m.data2D[i] ;
у teturn temp;
474
Глава 10. Листинги программ
template <class T>
Array2<T> Array2<T>::operator - (const Array2<T>& m)
i
assert((m.cols == cols) kk (m.rows == rows));
Array2<T> temp(rows,cols);
for (int i=0; i<rows; i++) temp[i] = data2D[i] - m.data2D[i].
return temp;
}
template <class T>
ostream к operator << (ostreamfe s,const Array2<T>& m)
i
for (int i=0; Km.rows; i++) s << m.data2D[i] << endl;
return s;
}
template <class T>
int operator == (const Array2<T>& ml, const Array2<T>& m2)
{
if (ml.sizeO != m2.size()) return 0;
for (int i=0; Kml.size(); i++)
{
if (ml[i] != m2[i]) return 0;
}
return 1;
}
template <class T>
int operator != (const Array2<T>& ml, const Array2<T>& m2)
-( return ! (ml==m2) ; }
template <class T> class АггауЗ
{
private:
// поля данных
int rows, cols, levs;
Array2<T> *data3D;
public:
// конструкторы
АггауЗ(int = 0, int = 0, int = 0);
Array3(int, int, int, T);
Array3(const Array3<T> &);
"АггауЗО;
// методы
void resize(int, int, int);
10.8. Класс Array
4
};
void resize(int, int, int, T);
Array2<T> к operator [] (int) const;
int size(int = 0) const;
// арифметические операторы
const Array3<T>& operator = (const Array3<T>&):
const Array3<T>& operator = (T);
Array3<T> operator *= (T);
Array3<T> operator += (const Array3<T>&)
Array3<T> operator -= (const Array3<T>&):
Array3<T> operator + (const Array3<T>&)
Array3<T> operator - (const Array3<T>&)
// функции потокового вводы/вывода
friend ostream & operator << (ostreamfe, const Array3<T>&);
// конец описания класса АггауЗ
// конструктор, деструктор и конструктор копирования,
template <class T> Array3<T>::АггауЗ(int r, int с, int v)
: rows(r), cols(c), levs(v), data3D(new Array2<T>[r])
{
assert(data3D != NULL);
for (int i=0; i<r; ++i) (data3D+i) -> resize(c.v);
}
template <class T> Array3<T>::АггауЗ(int r, int c, int v, T num)
: rows(r), cols(c), levs(v), data3D(new Array2<T>[r])
{
assert(data3D != NULL);
int i;
for (i=0; i<r; ++i) (data3D+i) -> resize(c,v.num);
teaplate <class T> Array3<T>::АггауЗ(const Array3<T>& m)
: rows(m.rows), cols(m.cols), levs(m.levs),
data3D(new Array2<T>[m.rows])
assert(data3D != NULL);
f°r (int i=0; i<m.rows; i++) data3D[i] = m.data3D[i];
}
ещР1^е <class T> Array3<T>: : "АггауЗ() { delete [] data3D; >
^ Plate <class T> void Array3<T>::resize(int r, int c, int v)
476
Глава 10. Листинги программ
int i;
Array2<T> *newArray = new Array2<T>[r]; assert(newArray ■-
if (r <= rows) '~ NULL).
{
for (i=0; i<r; i++)
{
(data3D+i) -> resize(c,v);
newArray[i] = data3D[i];
}
}
else
{
for (i=0; i<rows; i++)
{
(data3D+i) -> resize(c,v);
newArray[i] = data3D[i];
}
for (i=rows; i<r; i++) newArray[i].resize(c,v);
}
delete [] data3D;
rows = r; cols = c; levs = v;
data3D = newArray;
template <class T> void Array3<T>::resize(int r,int c.int v,T value)
{
int i;
Array2<T> *newArray = new Array2<T>[r]; assert(newArray != NULL);
if (r <= rows)
{
for (i=0; i<r; i++)
{
(data3D+i) -> resize(c,v,value);
newArray[i] = data3D[i];
}
}
else
{
for (i=0; i<rows; i++)
{
(data3D+i) -> resize(c,v,value);
newArray[i] = data3D [i];
}
for (i=rows; i<r; i++) newArray[i].resize(c,v,value);
}
delete [] data3D;
10.8. Класс Array
477
roWS = r; cols = с; levs = v;
data3D = newArray;
}
различные внутренние операторы
late <class T> Array2<T> & Array3<T>::operator [] (int i) const
tempJ-a
assert(i >= 0 && i < rows);
return data3D [i] ;
}
plate <class T> int Array3<T>::size(int index) const
assert(index>=0 && index<3);
switch(index)
{
case 0: return rows;
case 1: return cols;
default: return levs;
}
}
template <class T>
const Array3<T>& Array3<T>::operator = (const Array3<T>& m)
{
if (this == &m) return *this;
delete [] data3D;
cols = m.cols; rows = m.rows; levs = m.levs;
data3D = new Array2<T>[m.rows]; assert(data3D != NULL);
for (int i=0; Km.rows; i++) data3D[i] = m.data3D[i];
return *this;
}
template <class T> const Array3<T>& Array3<T>::operator = (T num)
}
for (int i=0; i<rows; i++) data3D[i] = num;
return *this;
template <class T> Array3<T> Array3<T>::operator *= (T num)
for (int i=0; i<rows; i++) data3D[i] *= num;
return *this;
Opiate <class T>
478
Глава 10. Листинги программ
Array3<T> Array3<T>::operator += (const Array3<T>& m)
-( return *this = *this + m; }
template <class T>
Array3<T> Array3<T>::operator -= (const Array3<T>& m)
{ return *this = *this - m; }
template <class T>
Array3<T> Array3<T>::operator + (const Array3<T>& m)
{
assert((m.cols == cols) && (m.rows == rows) && (m.levs == levs'n
Array3<T> temp(rows,cols,levs); '
for (int i=0; i<rows; i++) temp[i] = data3D[i] + m.data3D[i].
return temp;
}
template <class T>
Array3<T> Array3<T>:'.operator - (const Array3<T>& m)
{
assert((m.cols == cols) Ш (m.rows == rows) && (m.levs == levs))-
Array3<T> temp(rows,cols,levs);
for (int i=0; i<rows; i++) temp[i] = data3D[i] - m.data3D[i];
return temp;
}
template <class T>
ostreamfe operator << (ostreamfe s, const Array3<T>& m)
{
for (int i=0; i<m.rows; i++) s << m.data3D[i] « endl;
return s;
}
template <class T>
int operator == (const Array3<T>& ml, const Array3<T>& m2)
{
if (ml.sizeO != m2.size()) return 0;
for (int i=0; i<ml.size(); i++)
{ if (ml[i] != m2[i]) return 0; }
return 1;
}
template <class T>
int operator != (const Array3<T>& ml, const Array3<T>& m2)
{ return !(ml==m2); }
template <class T> class Array4
10.8. Класс Array
479
{ orivate:
Y /I поля данных
int rows, cols, levs, blks;
Array3<T> *data4D;
public:
// конструкторы
Array4(int = 0, int = 0, int = 0, int = 0);
Array4(int, int, int, int, T);
Array4(const Array4<T>&);
~Array4();
// методы
void resize(int, int, int, int);
void resize(int, int, int, int, T);
Array3<T>& operator [] (int) const;
int size(int = 0) const;
// арифметические операторы
const Array4<T>& operator = (const Array4<T>&);
const Array4<T>& operator = (T);
Array4<T> operator *= (T);
Array4<T> operator += (const Array4<T>&)
Array4<T> operator -= (const Array4<T>&)
Array4<T> operator + (const Array4<T>&)
Array4<T> operator - (const Array4<T>&)
// функции потокового ввода/вывода
friend ostream & operator << (ostreamfe, const Array4<T>&);
>'> // конец объявления класса Array4
'/ конструктор, деструктор и конструктор копирования
template <class T> Array4<T>::Array4(int r, int с, int v, int b)
: rows(r), cols(c), levs(v), blks(b), data4D(new Array3<T>[r])
}
assart(data4D != NULL);
f°r (int i=0; i<r; ++i) (data4D+i) -> resize(c,v,b);
eaPlate <class T> Array4<T>::Array4(int r.int c.int v.int b,T num)
: rows(r), cols(c), levs(v), blks(b), data4D(new Array3<T>[r])
assert(data4D != NULL);
i f°r (int i=0; i<r; ++i) (data4D+i) -> resize(c,v,b,num);
480
Глава 10. Листинги программ
template <class T> Array4<T>::Array4(const Array4<T>& m)
: rows(m.rows), cols(т.cols), levs(m.levs), blks(m.blks)
data4D(new Array3<T>[m.rows])
{
assert(data4D != NULL);
for (int i=0; i<m.rows; i++) data4D[i] = m.data4D[i];
}
template <class T> Array4<T>::~Array4()
{
delete [] data4D;
}
// методы
template <class T> void Array4<T>::resize(int r,int c,int v.int b)
{
int i;
Array3<T> *newArray = new Array3<T>[r]; assert(newArray != NULL)'
if (r <= rows)
{
for (i=0; i<r; i++)
{
(data4D+i) -> resize(c,v,b);
newArray[i] = data4D[i];
}
}
else
{
for (i=0; i<rows; i++)
{
(data3D+i) -> resize(c,v,b);
newArray [i] = data4D[i];
}
for (i=rows; i<r; i++) newArray[i].resize(c,v,b);
}
delete [] data4D;
rows = r; cols = c; levs = v; blks = b;
data4D = newArray;
}
template <class T>
void Array4<T>::resize(int r, int c, int v, int b, T value)
{
int i;
Array3<T> *newArray = new Array3<T>[r]; assert(newArray '.= NULL/
if (r <= rows)
10.8. Класс Array
481
{ for (i=0; i<r; i++)
{
(data4D+i) -> resize(c,v,b,value);
newArrayCi] = data4D[i];
>
}
else
for (i=0; i<rows; i++)
{
(data4D+i) -> resize(c,v,b,value);
newArray[i] = data4D[i];
}
for (i=rows; i<r; i++) newArray[i].resize(c,v,b,value);
}
delete [] data4D;
rows = r; cols = c; levs = v; blks = b;
data4D = newArray;
}
// различные внутренние операторы
template <class T> Array3<T> & Array4<T>::operator [] (int i) const
{
assert(i >= 0 && i < rows);
return data4D[i] ;
}
template <class T> int Array4<T>::size(int index) const
{
assert(index>=0 && index<4);
switch(index)
{
}
}
case 0:
case 1:
case 2:
default:
return
return
return
return
rows;
cols;
levs;
blks;
JeaPlate <class T>
°ust Array4<T>& Array4<T>::operator = (const Array4<T>& m)
lf (this == &m) return *this;
int i;
delete [] data4D;
482
Глава 10. Листинги программ
cols = m.cols; rows = m.rows; levs = m.levs; blks = m.blks.
data4D = new Array3<T>[m.rows]; assert(data4D != NULL);
for (i=0; Km.rows; i++) data4D[i] = m.data4D[i];
return *this;
}
template <class T> const Array4<T>& Array4<T>::operator = G n .
for (i=0; i<m.rows; i++) data4D[i] = num;
return *this;
}
template <class T> Array4<T> Array4<T>::operator *= (T num)
{
for (int i=0; Krows; i++) data4D[i] *= num;
return *this;
}
template <class T>
Array4<T> Array4<T>::operator += (const Array4<T>& m)
{ return *this = *this + m; }
template <class T>
Array4<T> Array4<T>::operator -= (const Array4<T>& m)
{ return *this = *this - m; }
template <class T>
Array4<T> Array4<T>::operator + (const Array4<T>& m)
{
assert((m.cols == cols) && (m.rows == rows) kk
(m.levs == levs) kk (m.blks == blks));
Array4<T> temp(rows,cols,levs,blks);
for (int i=0; i<rows; i++) temp[i] = data4D[i] + m.data4D[i];
return temp;
}
template <class T>
Array4<T> Array4<T>::operator - (const Array4<T>& m)
{
assert((m.cols == cols) && (m.rows == rows) kk
(m.levs == levs) kk (m.blks == blks));
Array4<T> temp(rows,cols,levs,blks);
for (int i=0; Krows; i++) temp[i] = data4D[i] - m.data4D[i]J
return temp;
}
10.9. Класс String
483
late <class T>
t^ ^ operator << (ostreamfe s, const Array4<T>& m)
l f r (int i=0; Km.rows; i++) s << m.data4D[i] « endl;
return s;
}
fe0plate <class T>
operator == (const Array4<T>& ml, const Array4<T>& m2)
if (ml.size() != m2.size()) return 0;
for (int i=0; i<ml.size(); i++)
{ if (ml[i] != m2[i]) return 0; }
return 1;
}
template <class T>
int operator != (const Array4<T>& ml, const Array4<T>& m2)
{ return ! (ml==m2) ; >
#endif
10.9. Класс String
Открытый интерфейс класса String:
• String(): конструктор по умолчанию.
• String(char): конструктор.
• String(int): конструктор.
• String(const char*): конструктор.
• String (const Stringfe): конструктор копирования.
• reverse(): обращение строки.
• length(): длина строки .
• арифметический оператор: =
• оператор взятия индекса: []
• оператор конкатенации: +
• оператор преобразования типа: operator char* О
• операторы сравнения: ==, ! = , <, <=, >, >=
п°токовые операторы: », <<
|~ГЯ более детального описания структуры класса и его методов, пожалуйста,
ьРащайтесь к разд. 6.9.
" MStr
ing.h
^Udef MSTRING.H
efiHe MSTRING.H
484
Глава 10. Листинги программ
#include <iostream.h>
#include <assert.h>
#include <string.h>
class String
■C
private:
// поля данных
int datalength;
char *data;
public:
// конструкторы и деструктор
String();
String(char);
String(int);
String(const char *);
String(const Stringft);
"String();
// оператор присваивания
const Stringft operator = (const Stringft);
// методы и оператор индекса
String reverse() const;
int length() const;
charft operator [](int) const;
// оператор преобразования
operator const char *() const;
// конкатенация
friend String operator + (const Stringft, const Stringft);
// дружественные операторы
friend ostreamft operator « (ostreamft, const Stringft);
friend istreamft operator » (istreamft, Stringft);
};
// операторы отношения
friend int operator <
friend int operator <=
friend int operator !=
friend int operator ==
friend int operator >=
friend int operator >
(const Stringft,
(const Stringft,
(const Stringft,
(const Stringft,
(const Stringft,
(const Stringft,
const Stringft)
const Stringft)
const Stringft)
const Stringft)
const Stringft)
const Stringft)
10.9. Класс String
еалязация класса
// РоЯструктор и деструкторы
ll K o---String() : datalength(l) , data(new char[l])
{ aSsert(data != NULL);
dataCO] = '\0';
>
iug::String(char c) : datalengthB), data(new char[2])
{ assert(data != NULL);
data[0] = c;
data CI] = '\0';
}
String::String(int size)
{
assert(size >= 0);
datalength = size + 1;
data = new char[datalength];
assert(data != NULL);
data[0] = '\0'; // строка определяется как строка NULL
}
String::String(const char *s)
: datalength(strlen(s) + 1), data(new char[datalength])
{
assert(data != NULL);
strcpy(data.s);
String::String(const String 4s)
: datalength(strlen(s.data) + 1), data(new char[datalength])
}
assert(data != NULL);
strcpy(data,s.data);
Jtriug::~String()
1 delete [] data; >
оператор присваивания
ast Stringfe String::operator = (const Stringft s)
486
Глава 10- Листинги программ
i
if (&s != this)
{
delete [] data;
datalength = strlen(s.data) + 1;
data = new char[datalength];
assert(data != NULL);
strcpy(data,s.data);
>
return *this;
}
// методы и оператор взятия индекса
String String::reverse() const
{
char t;
int i, idx, len = length();
String temp(*this);
for (i=0; i<len/2; i++)
{
idx = len-l-i;
t = temp[idx];
temp[idx] = temp[i];
temp[i] = t;
>
return temp;
>
int String::length() const { return strlen(data); }
charft String:-.operator [](int index) const
i return data[index]; >
// оператор преобразования
String::operator const char *() const
{. return data; У
// дружественные операторы
ostreamft operator « (ostreamfe out, const Stringft s)
i
out << s.data;
return out;
>
10.10. Класс BitVec
gjnfc operator >> (istream* in, String* str)
i cbar temp[1000];
if (in » temp)
str = temp;
else
str = "";
return in;
H оператор конкатенации
ring operator + (const String* si, const String* s2)
String S(sl.length() + s2.1ength());
strcpy(S.data, si.data);
strcat(S.data, s2.data);
return S;
}
// операторы отношения
int operator < (const String* left, const String* right)
{return strcmp(left.data,right.data) < 0; >
int operator <= (const String* left, const String* right)
{return strcmp(left.data,right.data) <= 0; >
int operator == (const String* left, const String* right)
{return strcmp(left.data,right.data) == 0; >
int operator != (const String* left, const String* right)
{ return strcmpdeft. data,right, data) != 0; >
int operator > (const String* left, const String* right)
{ return strcmpdeft.data,right.data) > 0; >
int operator >= (const String* left, const String* right)
{ retui
#endif
{ return strcmpdeft.data,right.data) >= 0; >
10-10. Класс BitVec
врытый интерфейс класса BitVec:
BitVec(): конструктор по умолчанию.
BitVec(unsigned int): конструктор.
BitVec(unsigned int, unsigned int): конструктор.
BitVec (const BitVec*): конструктор копирования.
size(): размер битового вектора.
488
Глава 10. Листинги программ
• reset (unsigned int): переопределение размера битового вектора
• reset (unsigned int, unsigned int): переопределение размера би
вектора и инициализация битовых полей. ' °в°го
• set (unsigned int): включение отдельного бита.
• clear (unsigned int): выключение отдельного бита.
• test (unsigned int): проверка отдельного бита на включение.
• flip (unsigned int): зеркальное отражение отдельного бита.
• unionSet(unsigned int): объединение двух битовых векторов.
• intersectSet (unsigned int, unsigned int): пересечение двух бито
векторов.
• differenceSet (unsigned int, unsigned int): разность двух битовых в
торов.
• subset (unsigned int, unsigned int): проверка на вхождение битовог
вектора в состав другого.
• оператор равенства: ==
• потоковый оператор: «
Для более детального описания структуры класса и его методов,
пожалуйста, обращайтесь к разд. 6.10.
// Bitvec.h
#ifndef BITVEC.H
#define BITVEC.H
#include <iostream.h>
#include <assert.h>
#include "Vector.h"
// вектор двоичных величин @ или 1)
class BitVector
{
private:
unsigned int bsize;
Vectorcunsigned char> data;
// функции определения позиции
unsigned int byteNumber(unsigned int) const;
unsigned int mask(unsigned int) const;
public:
// конструкторы
BitVector();
BitVector(unsigned int);
BitVector(unsigned int, unsigned int);
BitVector(const BitVectorft);
10.10. Класс BitVec
489
signed int size() const; // размер битового вектора
0id reset(unsigned int); // переопределение размера
0id reset(unsigned int, unsigned int);
// переопределение размера битового вектора
// и инициализация битовых полей
// битовые операции
void set(unsigned int);
void clear(unsigned int);
jnt test(unsigned int) const;
void flip(unsigned int);
// операции над наборами
BitVector unionSet(const BitVectorft);
BitVector intersectSet(const BitVectorft);
BitVector differenceSet(const BitVectorft);
int operator == (const BitVectorft);
int subset(const BitVectorft);
// друзья
friend ostreamft operator « (ostreamft, const BitVectorft);
};
BitVector:-.BitVector() : bsize(O) , data() {>
BitVector::BitVector(unsigned int num)
: bsize(num), data((num+7)/8, 0) {>
BitVector::BitVector(unsigned int num,unsigned int value):bsize(num)
{
if (value) data.reset((num+7)/8, OxFF);
else data.reset((num+7)/8);
}
BitVector::BitVector(const BitVectorfc b) :
bsize(b.bsize) , data(b.data) {}
'/ возвращается размер битового вектора
^signed int BitVector::size() const { return bsize; }
Переопределяется размер битового вектора
°id BitVector::reset(unsigned int num)
"■ bsi2e = num; data, reset ((num+7)/8, 0); >
Переопределяется размер битового вектора
и инициализация оставшихся битов значениями @ или 1)
lcl BitVector::reset(unsigned int num, unsigned int value)
490
Глава 10. Листинги программ
i
bsize = num;
if (value) data.reset((num+7)/8, OxFF);
else data.reset((num+7)/8, 0);
>
// установка определенного бита в векторе
void BitVector::set(unsigned int index)
{ data[byteNumber(index)] I= mask(index); >
// очистка определенного бита в векторе
void BitVector::clear(unsigned int index)
{ data[byteNumber(index)] &= " mask(index); >
// проверка определенного бита в векторе
int BitVector::test(unsigned int index) const
{ return (data[byteNumber(index)] & mask(index)) != 0; }
// зеркальное отражение определенного бита в векторе
void BitVector::flip(unsigned int index)
{ data[byteNumber(index)] "= mask(index); }
// возвращает номер байта, содержащего указанный номер бита
// где номер байта есть номер бита, делённый на 8
unsigned int BitVector::byteNumber(unsigned int index) const
{ return index » 3; >
// изготавливает маску для указанного индекса
unsigned int BitVector::mask(unsigned int index) const
i
II вычисление сдвигом всей величины проверкой
// младших 3 битов индекса
const int shiftAmount = index & 07;
// строим маску сдвигом величины '1'
// влево на заданную величину
return 1 « shiftAmount;
}
// формирование объединения
BitVector BitVector::unionSet(const BitVectorft b)
{
assert(bsize == b.bsize);
BitVector result(bsize);
int i, total = (bsize+7)/8;
for (i=0; i<total; i++)
10.10. Класс BitVec
491
result.data[i] = data[i] I b.data[i];
return result;
/ формирование пересечения
■1- Vector BitVector: : intersectSet (const BitVectorft b)
assert(bsize == b.bsize);
BitVector result(bsize);
int i. total = (bsize+7)/8;
for (i=0; i<total; i++)
result.data[i] = data[i] & b.data[i];
return result;
}
// формирование разности
BitVector BitVector::differenceSet(const BitVectorft b)
{
assert(bsize == b.bsize);
BitVector result(bsize);
int i, total = (bsize+7)/8;
for (i=0; i<total; i++)
result.data[i] = data[i] & (~b.data[i]);
return result;
}
// проверка на совпадение двух наборов
int BitVector::operator == (const BitVector &b)
{
assert(bsize == b.bsize);
// проверка на совпадение во всех позициях
for (int i=0; i<bsize; i++)
if (data[i] != b.data[i])
return 0;
return 1;
'J возвращает истину, если набор входит в аргумент
int BitVector::subset(const BitVectorft b)
assert(bsize == b.bsize);
// проверка по каждой позиции аргумента, совпадает ли
// он с соответствующей позицией вектора
for (int i=0; i<bsize; i++)
if (b.data[i] != (data[i] & b.data[i])) return 0;
. return 1;
492
Глава 10. Листинги прогр&мм
II вывод битового вектора в поток ^
ostreamfe operator << (ostreamft s, const BitVectorft b)
{
for (int i=0; Kb.bsize; i++) s << b.test(i);
return s;
>
#endif
10.11. Класс MList
Открытый интерфейс класса List:
• List (): конструктор по умолчанию.
• List(const List<T>&): конструктор копирования.
• add (T): добавление элемента в начало списка.
• duplicate(): создание дубликата списка.
• f irst_Node(): первый элемент списка.
• is_Empty(): проверка списка на пустоту.
• deleteAHNodes(): очистка списка.
• is_Include(T v): проверка на наличие v в списке.
• delete_First(): удаление первого элемента списка.
Открытый интерфейс класса List Iterator:
• ListIterator(List<T>&): конструктор.
• init (): инициализация итератора.
• delete_Current(): удаление элемента, отмеченного итератором.
• add_Bef ore (const T v): добавление элемента v до элемента, отмеченного
итератором.
• add_After(const T v): добавление элемента v после элемента, отмеченного
итератором.
• оператор взятия текущего значения: ()
• оператор признака конца списка: !
• оператор инкремента: ++
• оператор присваивания: =
Для более детального описания структуры класса и его методов, пожалуйста,
обращайтесь к разд. 6.11.
// MList.h
#ifndef MITERATOR.H
#define MITERATOR.H
template <class T> class Iterator
{
public:
virtual int init() =0; // инициализация
virtual int operator !() = 0; // проверка наличия текущего элем<з
10.11. Класс MList
493
tual T operator 0 0 = 0; // возвращает текущий элемент
Vl. tual int operator ++() = 0; // оператор инкремента
ffl tual void operator = (const T) = 0;// оператор присваивания
#ifndef MLIST_H
Jdefine MLIST_H
#include
<assert.h>
// опережающее объявление
teBplate <class T> class Link;
ten)plate <class T> class Listlterator;
template <class T> class List
{
protected:
// поля данных
Link<T> *head; // голова указывает на первый элемент
friend class ListIterator<T>;
public:
// конструкторы
List();
List(const List<T>&);
virtual "List();
// методы
void add(const T);
List<T> * duplicate() const;
T first_Node() const;
int is_Empty() const;
виртуальные функции
virtual void deleteAHNodes() ;
virtual int is_Include(T) const;
virtual void delete.First();
virtual const List<T>& operator = (const List<T>&);
template <class T> class Link
Private:
'/ конструкторы
Liak(const T, Link<T> *);
494
Глава 10. Листинги программ
Link(const Link<T>&);
Link<T> * duplicateO const;
// поля данных
T data;
Link<T> *next;
friend class List<T>;
friend class ListIterator<T>;
public:
Link<T> * insert(const T); // вставка нового элемента ]
// текущей величины
};
template <class T> class Listlterator : public Iterator<T>
■C
protected:
// поля данных
Link<T> *current;
Link<T> *previous;
List<T>& list;
public:
// конструкторы
ListIterator(List<T>&);
Listlterator(const ListIterator<T>&);
// протокол итератора
virtual int init();
virtual T operator 0 0;
virtual int operator !();
virtual int operator ++();
virtual void operator = (const T);
// новые функции, определенные в итераторе
void delete_Current О;
void add_Before(const T);
void add_After(const T);
};
// реализация класса List
template <class T> List<T>::List() : head(NULL) {}
// очистка всех элементов списка
template <class T> List<T>::"List()
-С deleteAHNodesO; }
10.11. Класс MList
495
бавление новой величины в начало связанного списка
// Kate <class T> void List<T>::add(const T val)
^ head = new Link<T>(val, head);
ass
У
ert(head != NULL);
всех элементов из списка
<class T> void List<T>: :deleteAHNodes()
// удаление
te*plate <C
Link<T> *nxt;
nxt = NULL;
// удаление элемента, указываемого р
for (Link<T> *p = head; p; p = nxt)
{
nxt = p->next;
p->next = NULL;
delete p;
}
head = NULL;
}
// дублирование связанного списка
template <class T> List<T> * List<T>::duplicate() const
{
List<T> *newlist = new List<T>; assert(newlist != NULL);
// копируем список
if (head) newlist->head = head->duplicate();
return newlist;
}
'/ Конструктор копирования
template <class T> List<T>::List(const List<T>& 1st)
Дублирование элементов списка
if (lst.is_Empty()) head = NULL;
else
{
Link<T> *firstLink = 1st.head;
head = firstLink->duplicate();
}
opiate < с lass T>
°bst List<T> & List<T>::operator = (const List<T>& 1st)
496
Глава 10. Листинги программ
if (this != &lst)
i
I/ дублирование элементов списка 1st
if (Ist.is.EmptyO) head = NULL;
else
■c
Link<T> *firstLink = 1st.head;
head = firstLink->duplicate();
}
}
return *this;
}
// возвращаем первый элемент списка
template <class T> T List<T>::first_Node() const
■C
assert(head != NULL); return head->data;
}
// проверяем, имеется ли v в списке
template <class T> int List<T>::is_Include(T v) const
{
for (Link<T> *p = head; p; p = p->next)
if (v == p->data) return 1;
return 0;
// проверка списка на пустоту
template <class T> int List<T>::is_Empty() const
{ return head == NULL; }
// удаление первого элемента из списка
template <class T> void List<T>::delete_First()
{
assert(head != NULL);
Link<T> *p = head;
head = p->next;
delete p;
}
// реализация класса Link
// вставка нового элемента позади текущего
template <class T> Link<T> * Link<T>: .-insert(const T va
■C
next = new Link<T>(val, next);
10.11. Класс MList
sSert(next != NULL);
return next;
)
■здание и инициализация нового связанного поля
(I Clate <class T> Link<T>::Link(const T val, Link<T> *nxt)
teD1P data(val), next(nxt) {}
// конструктор копирования
plate <class T> Link<T>::Link(const Link<T> &lst)
■ data(lst.data), nextAst.next) {}
// дублирование связи
opiate <class T> Link<T> * Link<T>::duplicate() const
Link<T> *newlink;
// если имеется следующее поле, копируем хвост списка
if (next != NULL)
newlink = new Link<T>(data, next->duplicate());
else
newlink = new Link<T>(data, NULL);
// проверка, что все полностью размещено
assert(newlink != NULL);
return newlink;
}
// реализация класса Listlterator
// создание и инициализация нового списка
template <class T> ListIterator<T>::Listlterator(List<T>& aLis
: list(aList)
{ init(); }
конструктор копирования
Opiate <class T>
istIterator<T>::Listlterator(const ListIterator<T>& x)
: list(x.list) { initQ; }
Установка итератора на первый элемент списка
Opiate <class T> int ListIterator<T>::init()
Previous = NULL; current = list.head;
у return current != NULL;
498
Глава 10. Листинги программ
// возвращаем величину текущего элемента
template <class T> T ListIterator<T>::operator () ()
-С
assert(current != NULL);
return current->data;
}
// проверка закрытия итератора
template <class T> int ListIterator<T>::operator !()
{
// если текущая связь указывает на удаленное значение,
// переводим её на следующую позицию
if (current == NULL)
if (previous != NULL) current = previous->next;
// проверка на корректность
return current != NULL;
}
// оператор инкремента, который переводит указатель
//на следующий элемент
template <class T> int ListIterator<T>::operator ++()
{
// если текущая связь удалена
if (current == NULL)
{
if (previous == NULL) current = list.head;
else current = previous->next;
}
else // продвижение указателя
{
previous = current;
current = current->next;
}
// возвращаем истину, если текущий элемент корректен
return current != NULL;
}
// оператор присваивания : модифицируем значение
// текущего элемента
template <class T> void ListIterator<T>::operator = (const
-С
assert(current != NULL);
current->data = val;
}
10.12. Класс Polynomial
499
^аляем текущий элемент из списка
Tate <class T> void ListIterator<T>::delete_Current()
tei»F
assert(current != NULL);
as
// удаляем первый элемент
if (previous == NULL) list.head = current->next;
else previous->next = current->next;
// удаляем текущий элемент и установка указателя в нуль
delete current;
current = NULL;
}
// добавляем новый элемент в список перед текущим значением
template <class T> void ListIterator<T>::add_Before(const T val)
{
if (previous) // находимся не в начале
previous = previous->insert(val);
else // начало списка
{
list.add(val);
previous = list.head;
current = previous->next;
}
}
// добавляем новый элемент в список после текущего значения
template <class T> void ListIterator<T>::add_After(const T val)
{
if (current != NULL) // находимся не в начале
current->insert(val);
else if (previous != NULL) // конец списка
current = previous->insert(val);
else // начало списка
list.add(val);
#ebdif
10.12. Класс Polynomial
тКрытый интерфейс класса Polyterm:
Polyterm(char*): конструктор, дающий имя переменной (по
умолчанию "х").
P°lyterm(const Polyterm<T>&): конструктор копирования.
* polyterm<T>& operator+=(Polyterm<T>)
500 Глава 10. Листинги программ
Polyterm<T>& operator-=(Polyterm<T>)
Polyterm<T>& operator*=(Polyterm<T>)
Polyterm<T>& operator*=(T)
Polyterm<T>& operator/=(Polyterm<T>)
Polyterm<T>& operator/=(T)
Polyterm<T> operator/(Polyterm<T>): деление с игнорированием
ков. Тат-
Polyterm<T> operator/(T)
int operator> (Polyterm<T>): сравнение для упорядочивания многочл
int operator==(Polyterm<T>) 6lia-
int operator!=(Polyterm<T>)
Polyterm<T>& operator=(Polyterm<T>)
Polyterm<T>& operator=(T)
int equal(Polyterm<T>): проверка, представлена ли в Polyterm та же са
мая переменная.
Polyterm<T> operator*(Polyterm<T>)
Polyterm<T> operator" (unsigned int): установка показателя терма.
Polyterm<T> variable(): возвращаем простой Polyterm для переменной с
коэффициентом и показателем 1.
Т value(T t): вычисляем значение терма для t.
"PolytermO: деструктор.
Дружественные функции класса Polyterm:
Polyterm<T> operator*( T,Polyterm<T>)
Polyterm<T> operator*( T,Polyterm<T>,T)
Polyterm<T> operator-( T,Polyterm<T>)
Polyterm<T> operator+( T,Polyterm<T>)
Polyterm<T> Diff (Polyterm<T>): дифференцирование терма.
Polyterm<T> Int (Polyterm<T>): интегрирование терма с точностью до
константы интегрирования,
ostreamu operator«(ostream&, Polyterm<T>)
Открытый интерфейс класса Polynomial:
Polynomial (Polyterm<T> x): конструктор многочлена от переменной х.
Polynomial (const Polyterm<T>&): конструктор, дающий имя переменной.
Polynomial(Polynomial<T>): конструктор копирования.
Polynomial (int): конструктор многочлена-константы.
Polynomial<T>& operator=(Polynomial<T>)
Polynomial<T>& operator+=(Polyterm<T>)
Polynomial<T>& operator-=(Polyterm<T>)
PolynomiaKT> operator+ (Polyterm<T>)
Polynomial<T> operator-(Polyterm<T>)
PolynomiaKT> operator+=(T)
Polynomial<T> operator-=(T)
Polynomial<T> operator+(Polynomial<T>)
Polynomial<T> operator-(Polynomial<T>)
10.12. Класс Polynomial
501
„olynomial<T>& operator+=(Polynomial<T>)
" p0iy»omial<T>& operator-=(Polynomial<T>)
' polynomial<T>& operator=(Polyterm<T>)
' polyn°mial<T:> °perator+(T)
' p0lynomiaKT> operator-(T)
p0lynomial<T>& operator=(T)
p0Xynomial<T>& operator*=(T)
p0lynomial<T>& operator*=(Polyterm<T>)
p0lynomial<T>& operator*=(Polynomial<T>)
, p0lynomial<T> operator*(T)
« p0lynomial<T> operator*(Polyterm<T>)
, polynomial<T> operator*(Polynomial<T>)
, polynomial<T> operator"(unsigned int)
• p0lynomial<T>& operator/=(T)
, polynomial<T>& operator/=(Polyterm<T>): деление с игнорированием
остатка.
• polynomial<T>& operator/=(Polynomial<T>): деление с игнорированием
остатка.
• Polynomial<T> operator/(T)
• Polynomial<T> operator/(Polyterm<T>): деление с игнорированием
остатка.
• Polynomial<T> operator/(Polynomial<T>): деление с игнорированием
остатка.
• Polynomial<T>& operator'/.= (T)
• Polynomial<T>& operator'/,= (Polyterm<T>): остаток после деления.
• Polynomial<T>& operator7,= (Polynomial<T>): остаток после деления.
• Polynomial<T> operator'/.(T)
• Polynomial<T> operator'/. (Polyterm<T>): остаток после деления.
• Polynomial<T> operator'/,(Polynomial<T>): остаток после деления.
• int operator==(Polynomial<T>)
• int operator==(Polyterm<T>)
• int operator==(T)
• int operator
• int operator
• int operator
=(Polynomial<T>)
=(Polyterm<T>)
! = (T)
* T valued t): вычисление значения многочлена в точке t.
* Т operatorO (T t): вычисление значения многочлена в точке t.
* "Polynomial(): деструктор.
Мужественные функции класса Polynomial:
polynomial<T> operator-(Polynomial<T>)
polynomial<T> operator+(Polynomial<T>)
polynomiaKT> operator+(Polyterm<T>,Polynomial<T>)
polynomial<T> operator-(Polyterm<T>,PolynomiaKT>)
PolynomiaKT> Diff (PolynomiaKT>): дифференцирование многочлена.
502
Глава 10. Листинги программ
Polynomial<T> operator- (Polyterm<T> ,T): разность константы и полите
• Polynomial<T> Int (Polynomial<T>): интегрирование многочлена с
стью до константы.
• Polynomial<T> operator+(T,Polynomial<T>)
• Polynomial<T> operator-(T,Polynomial<T>)
• Polynomial<T> operator+(Polyterm<T>,Polyterm<T>): сумма полито
многочлена. а«
• Polynomial<T> operator-(Polyterm<T>,Polyterm<T>): разность tm„
ма и многочлена. р~
• PolynomiaKT> operator+(T,Polyterm<T>)
• PolynomiaKT> operator-(T,Polyterm<T>)
• Polynomial<T> operator+(Polyterm<T>,T): сумма константы и политеп
как многочлен.
•
ма как многочлен.
• Polynomial<T> operator*(T,Polynomial<T>)
• Polynomial<T> operator*(Polyterm<T>,Polynomial<T>)
• int operator==(Polyterm<T>,Polynomial<T>)
• int operator==(T,Polynomial<T>)
• int operator !=(Polyterm<T>,Polynomial<T>)
• int operator !=(T,Polynomial<T>)
• ostreamu operator<<(ostream&,Polynomial<T>fc)
Для более детального описания структуры класса и его методов, пожалуйста,
обращайтесь к разд. 6.12.
// poly.h
#ifndef .POLYNOMIAL
#define .POLYNOMIAL
#include <string.h>
#include <iostream.h>
// предположение : T имеет подобранный тип
// и может вести себя как числовой тип
template <class P>
Р poly power(P х,unsigned int у)
■С
Р resultA);
for(int i=l;i<=y;i++) result*=x;
return result;
}
// класс Polyterm для мономов
template <class T> class Polyterm
10.12. Класс Polynomial
0ut>lic:
p0lyterm(char *sym="x");
p0lyterm(const Polyterm<T> &);
p0lyterm<T>& operator+=(const Polyterm<T>&);
polyterm<T>& operator-=(const Polyterm<T>&);
polyterm<T>& operator*=(const Polyterm<T>&);
polyterm<T>& operator*=(T);
polyterm<T>& operator/=(const Polyterm<T>&);
polyterm<T>& operator/=(T);
polyterm<T> operator/(const Polyterm<T>&) const;
polyterm<T> operator/(T) const;
int operator>(const Polyterm<T>&) const;
int operator==(const Polyterm<T>&) const;
int operator!=(const Polyterm<T>&) const;
Polyterm<T>& operator=(const Polyterm<T>&);
Polyterm<T>& operator=(T);
int equal(const Polyterm<T>&) const;
Polyterm<T> operator*(const Polyterm<T>&) const;
friend Polyterm<T> operator*(T,const Polyterm<T>&);
friend Polyterm<T> operator*(const Polyterm<T>&,T);
Polyterm<T> operator"(unsigned int) const;
friend Polyterm<T> operator-(const Polyterm<T>&);
friend Polyterm<T> operator+(const Polyterm<T>&);
Polyterm<T> variable() const;
friend Polyterm<T> Diff(Polyterm<T>);
friend Polyterm<T> Int(Polyterm<T>);
T value(T);
friend ostreamfe operator<<(ostreamfe,const Polyterm<T>&
"PolytermO ;
protected:
Polyterm(T coeff,char *sym,unsigned int exp);
char *symbol;
T coefficient;
unsigned int exponent;
teaplate <class T>
P°lyterm<T>::Polyterm(char *sym) : coefficient(T(l))
eXponent=l;
symbol=new char[strlen(sym)+l];
strcpy(symbol,sym);
ea>Plate <class T>
504
Глава 10. Листинги программ
Polyterm<T>::Polyterm(const Polyterm<T> &p)
: coefficient(p.coefficient)
{
symbol=new char[strlen(p.symbol)+1];
strcpy(symbol,p.symbol);
exponent=p.exponent;
}
template <class T>
Polyterm<T>& Polyterm<T>::operator+=(const Polyterm<T> &p)
{
if((p.exponent==exponent)&&
(Istrcmp(symbol,p.symbol)||(exponent==0)))
coefficient+=p.coefficient;
return *this;
}
template <class T>
Polyterm<T>& Polyterm<T>::operator-=(const Polyterm<T> &p)
{
if((p.exponent==exponent)&&
(Istrcmp(symbol,p.symbol)I I(exponent==0)))
coefficient-=p.coefficient;
return *this;
}
template <class T>
Polyterm<T>& Polyterm<T>::operator*=(const Polyterm<T> &p)
•C
char *sym;
if(!strcmp(symbol,p.symbol)I I(exponent==0)|I(p.exponent==0))
•C
if(exponent!=01 Ip.exponent==0) sym=symbol;
else sym=p.symbol;
return *this=Polyterm<T>(coefficient*p.coefficient,
sym,exponent+p.exponent);
}
return *this;
}
template <class T>
Polyterm<T>& Polyterm<T>::operator*=(T c)
{
coefficient*=c;
return *this;
}
10.12. Класс Polynomial
late <class T>
^ 0rm<T>& Polyterm<T>::operator/=(const Polyterm<T> &x)
{ ,,- coefficient==T@))
return *this;
efficient/=x.coefficient;
■ f (exponent>=x.exponent) exponent-=x.exponent;
glse coefficient=T@);
return *this;
}
template <class T> Polyterm<T>& Polyterm<T>::operator/=(T c)
{
if(c==T@))
{
*this=T@);
return *this;
}
coefficient/=c;
return *this;
}
template <class T>
Polyterm<T> Polyterm<T>::operator/(const Polyterm<T> &x) const
{
polyterm<T> xl(*this);
xl/=x;
return xl;
}
template <class T> Polyterm<T> Polyterm<T>::operator/(T c) const
p°lyterm<T> xl(*this);
xl/=c.
retUrn xl;
}
tei4>late <class T>
at Polyterm<T>::operator>(const Polyterm<T> &p) const
eturn (exponent>p.exponent);
506
Глава 10. Листинги программ
template <class T>
int Polyterm<T>::operator==(const Polyterm<T> &p) const
{
if((coefficient==T@))&&(coefficient==p.coefficient)) return
return ((exponent==p.exponent)&&
(!strcmp(symbol,p.symbol)||(exponent==0))&&
(coefficient==p.coefficient));
}
template <class T>
int Polyterm<T>: .-operator! = (const Polyterm<T> &p) const
{
return !(*this==p);
}
template <class T>
Polyterm<T> &Polyterm<T>::operator=(const Polyterm<T> &p)
{
delete[] symbol;
coefficient=p.coefficient;
symbol=new char[strlen(p.symbol)+1];
strcpy(symbol,p.symbol);
exponent=p.exponent;
return *this;
}
template <class T> Polyterm<T> &Polyterm<T>::operator=(T c)
•C
exponent=0;
coefficient=c;
return *this;
}
template <class T>
int Polyterm<T>::equal(const Polyterm<T> &p) const
{
return ((Istrcmp(symbol,p.symbol))||
(exponent==0)I I(p.exponent==0));
}
template <class T>
Polyterm<T> Polyterm<T>::operator*(const Polyterm<T> &p) const
{
if(strcmp(symbol,p.symbol)&&(exponent!=0)&&(p.exponent!=0))
return Polyterm<T>(T@).symbol,0);
10.12. Класс Polynomial
rn polyterm<T>(coefficient*p.coefficient.symbol,
f exponent+p.exponent);
}
rOate <class T> Polyterm<T> operator*(T c,const Polyterm<T> &p
turn Polyterm<T>(c*p.coefficient,p.symbol,p.exponent);
}
template <class T>
polyterm<T> operator*(const Polyterm<T> &p,T c)
return Polyterm<T>(c*p.coefficient,p.symbol,p.exponent);
}
template <class T>
Polyterm<T> Polyterm<T>::operator"(unsigned int y) const
{
return Polyterm<T>(coefficient,symbol,y);
}
template <class T> Polyterm<T> operator-(const Polyterm<T> &x)
{
return (Polyterm<T>(T@)*x)-=x);
}
template <class T> Polyterm<T> operator+(const Polyterm<T> &x)
{ return x; }
template <class T> Polyterm<T> Polyterm<T>::variable() const
{ return Polyterm<T>(T(l),this->symbol,1); }
template <class T> Polyterm<T> Diff(Polyterm<T> p)
P-coefficient*=T(p.exponent);
P-exponent—;
Return p:
template <class T> Polyterm<T> Int(Polyterm<T> p)
P-coefficient/=T(p.exponent+l);
P•exponent++;
return p;
508
Глава 10. Листинги программ
template <class T> T Polyterm<T>: : valued x)
{
return coefficient* poly power(x,exponent);
}
template <class T>
ostream &operator«(ostream &o,const Polyterm<T> &p)
•C
if(((p.coefficient!=T(l))&&(p.coefficient!=T(-l)))I|
(p.exponent==0))
o«"("«P- coef f icient«") " ;
if(p.exponent) o<<p.symbol;
if (p.exponent>l) o« "~"<<p. exponent;
return o;
}
template <class T> Polyterm<T>::"PolytermQ
{ deleted symbol; }
template <class T>
Polyterm<T>::Polyterm(T coeff,char *sym,unsigned int exp)
: coefficient(coeff)
{
exponent=exp;
symbol=new char[strlen(sym)+l];
strcpy(symbol,sym);
}
//структура вспомогательного списка многочлена
template <class T> struct polyListltem
{
Polyterm<T> *item;
polyListItem<T> *next;
polyListItem<T> ^previous;
};
// класс Polynomial
template <class T>
class Polynomial
{
public:
Polynomial(Polyterm<T> x) : variable(x.variable()), head@) O;
Polynomial(char *sym): variable(Polyterm<T>(sym)), head@) 0>
Polynomial(const Polynomial<T>&);
Polynomial(int);
Polynomial<T>& operator=(const Polynomial<T>&);
10.12. Класс Polynomial
friend Polynomial<T> operator+(const Polynomial<T>&);
friend Polynomial<T> operator-(const Polynomial<T>&);
p0lynomial<T>& operator+=(const Polyterm<T>&);
p0lynomial<T>& operator-=(const Polyterm<T>&);
p0lynomial<T> operator+(const Polyterm<T>&) const;
p0lynomial<T> operator-(const Polyterm<T>&) const;
p0lynomial<T> operator+=(T);
p0lynomial<T> operator-=(T);
friend Polynomial<T> operator+(const Polyterm<T>&,
const Polynomial<T>&);
friend Polynomial<T> operator-(const Polyterm<T>&,
const Polynomial<T>&);
Polynomial<T> operator+(const Polynomial<T>&) const;
polynomial<T> operator-(const Polynomial<T>&) const;
Polynomial<T>& operator+=(const Polynomial<T>&);
Polynomial<T>& operator-=(const Polynomial<T>&);
Polynomial<T>& operator=(const Polyterm<T>&);
friend Polynomial<T> Diff(Polynomial<T>);
friend Polynomial<T> Int(Polynomial<T>);
Polynomial<T> operator+(T) const;
Polynomial<T> operator-(T) const;
friend Polynomial<T> operator+(T,const Polynomial<T>&);
friend Polynomial<T> operator-(T,const Polynomial<T>&);
PolynomiaKT>& operator=(T) ;
friend Polynomial<T> operator+(const Polyterm<T>&,
const Polyterm<T>&);
friend Polynomial<T> operator-(const Polyterm<T>&,
const Polyterm<T>&);
friend Polynomial<T> operator+(T,const Polyterm<T>&)
friend Polynomial<T> operator-(T,const Polyterm<T>&);
friend Polynomial<T> operator+(const Polyterm<T>&,T);
friend Polynomial<T> operator-(const Polyterm<T>&,T);
Polynomial<T>& operator*=(T);
Polynomial<T>& operator*=(const Polyterm<T>&);
Polynomial<T>& operator*=(const Polynomial<T>&);
Polynomial<T> operator*(T) const;
Polynomial<T> operator*(const Polyterm<T>&) const;
Polynomial<T> operator*(const Polynomial<T>&) const;
Polynomial<T> operator"(unsigned int);
friend Polynomial<T> operator*(T,const Polynomial<T>&);
friend Polynomial<T> operator*(const Polyterm<T>&,
const Polynomial<T>&);
polynomial<T>& operator/=(T);
polynomial<T>& operator/=(const Polyterm<T>&);
polynomial<T>& operator/=(const Polynomial<T>&);
polynomial<T> operator/(T) const;
510
Глава 10. Листинги программ
Polynomial<T> operator/(const Polyterm<T>&) const;
Polynomial<T> operator/(const Polynomial<T>&) const;
PolynomiaKT>& operator'/,= (T);
Polynomial<T>& operator'/.= (const Polyterm<T>&);
Polynomial<T>& operator'^(const Polynomial<T>&);
Polynomial<T> operator'/,(T) const;
Polynomial<T> operator'/,(const Polyterm<T>&) const;
Polynomial<T> operator'/,(const Polynomial<T>&) const;
int operator==(T) const;
int operator==(const Polyterm<T>&) const;
int operator==(const Polynomial<T>&) const;
friend int operator==(T,const Polynomial<T>&);
friend int operator==(const Polyterm<T>&,const Polynomial<T>&) •
int operator!=(T) const;
int operator!=(const Polyterm<T>&) const;
int operator!=(const Polynomial<T>&) const;
friend int operator!=(T,const Polynomial<T>&);
friend int operator! = (const Polyterm<T>&,const Polynomial<T>&);
T value(T);
T operator О(Т);
friend ostream &operator<<(ostream&,const Polynomial<T> &) ;
"Polynomial();
protected:
void tidy(void);
Polyterm<T> variable;
polyListItem<T> *head;
};
template <class T> Polynomial<T>::Polynomial(const Polynomial<T> &p)
{
head=NULL;
polyListItem<T> *templ=p.head,*temp2;
variable=p.variable;
if(tempi)
•C
head=new polyListItem<T>;
head->item=new Polyterm<T>(*(templ->item));
head->previous=NULL;
head->next=NULL;
templ=templ->next;
}
temp2=head;
while(tempi)
{
temp2->next=new polyListItem<T>;
(temp2->next)->previous=temp2;
10.12. Класс Polynomial
511
teffiP2=temp2_>next;
mp2->item=new Polyterm<T>(*(templ->item));
lImp2->next=NULL;
templ=templ->next;
}
}
nlate <class T> Polynomial<T>::Polynomial(int c)
(if(c!=0)
variable=Polyterm<T>("x");
head=new polyListItem<T>;
head->item=new Polyterm<T>(T(c)*variable"'0);
head->previous=NULL;
head->next=NULL;
}
else
{
variable=Polyterm<T>("x");
bead=NULL;
}
}
template <class T>
Polynomial<T>& Polynomial<T>::operator=(const Polynomial<T> &p)
{
if(head==p.head) return *this;
// уничтожение многочлена
polyListItem<T> *temp=head;
variable=p.variable;
"bile (temp)
{
head=temp->next;
delete temp->item;
delete temp;
temp=head;
создание нового многочлена
p°lyListItem<T> *templ=p.head,*temp2;
Variable=p.variable;
if (tempi)
head=new polyListItem<T>;
head->item=new Polyterm<T>(*(templ->item));
512
Глава 10. Листинги программ
head->previous=NULL;
head->next=NULL;
templ=templ->next;
}
temp2=head;
while(tempi)
{
temp2->next=new polyListItem<T>;
(temp2->next)->previous=temp2;
temp2=temp2->next;
temp2->item=new Polyterm<T>(*(templ->item));
temp2->next=NULL;
templ=templ->next;
}
return *this;
}
template <class T> Polynomial<T> operator+(const Polynomial<T> &p)
{ return p; }
template <class T> Polynomial<T> operator-(const Polynomial<T> &p)
{
return Polynomial<T>(p.variable)-p;
}
template <class T>
Polynomial<T>& Polynomial<T>::operator+=(const Polyterm<T> &x)
{
polyListItem<T> *temp=head,*temp2;
if(!variable.equal(x)) return *this;
if(head==NULL)
•C
head=new polyListItem<T>;
head->item=new Polyterm<T>(x);
head->previous=NULL;
head->next=NULL;
}
else
{
while((temp->next!=NULL)&&(*(temp->item)>x))
{
temp=temp->next;
}
if((*(temp->item)+=x)==(*(temp->item))&&(x>*(temp->item)))
•C
10.12. K/mcc Polynomial
513
temp2=new polyListItem<T>;
temp2->next=temp;
temp2->previous=temp->previous;
if(temp->previous==NULL) head=temp2;
else (temp->previotis)->next=temp2;
temp->previous=temp2;
temp2->item=new Polyterm<T>(x);
eXSe if(*(temp->item)>x)
{
temp2=new polyListItem<T>;
temp->next=temp2;
temp2->next=NULL;
temp2->previous=temp;
temp2->item=new Polyterm<T>(x);
}
}
tidyO;
return *this;
}
template <class T>
Polynomial<T>& Polynomial<T>::operator-=(const Polyterm<T> &x)
{
*this+=-x;
return *this;
}
template <class T> Polynomial<T>
p°lynomial<T>::operator+(const Polyterm<T> &x) const
if (head==NULL) return (PolynomiaKT>(x)+=x) ;
return (Polynomial<T>(*this)+=x);
^plate <class T> PolynomiaKT>
°lynomial<T>::operator-(const Polyterm<T> &x) const
lf(aead==NULL) return (PolynomiaKT>(x)-=x) ;
return (PolynomiaKT>(*this)-=x);
, "'Plate <class T> PolynomiaKT> Polynomial<T>: roper ator+=(T x)
0lyterm<T> y;
514
Глава 10. Листинги программ
*this+=y;
return *this;
>
template <class T> Polynomial<T> Polynomial<T>::operator-=
{
Polyterm<T> y;
У=х;
*this-=y;
return *this;
}
template <class T> Polynomial<T>
operator+(const Polyterm<T> &x, const Polynomial<T> &p)
{ return p+x; }
template <class T> Polynomial<T>
operator-(const Polyterm<T> &x, const Polynomial<T> &p)
{ return x+(-p); }
template <class T> Polynomial<T>
Polynomial<T>::operator+(const Polynomial<T> &p) const
{
if(head==NULL) return p;
PolynomiaKT> pl(*this);
polyListItem<T> *temp=p.head;
while(temp)
{
pl+=*(temp->item);
temp=temp->next;
}
return pi;
}
template <class T> Polynomial<T>
Polynomial<T>::operator-(const Polynomial<T> &p) const
{
if(head==NULL) return T(-l)*p;
PolynomiaKT> pl(*this);
polyListItem<T> *temp=p.head;
while(temp)
{
pl-=*(temp->item);
temp=temp->next;
}
return pi;
}
10.12. Класс Polynomial
515
. te <c!ass T> Polynomial<T>&
^ oinial<T>::operator+=(const Polynomial<T> &p)
Ро1У^иГП (*this=*this+p) ; }
late <class T> Polynomial<T>&
omial<T>::operator-=(const Polynomial<T> &p)
Return (*this=*this-p); }
late <class T> PolynomiaKT>&
pol
omial<T>::operator=(const Polyterm<T> &x)
return *this=Polynomial<T>(variable)+x; }
template <class T>
polynomiaKT> Dif f (PolynomiaKT> p)
Polynomia.KT> pl(p);
polyListItem<T> *temp=pl.head;
while (temp)
{
*(temp->item)=Diff(*(temp->item));
temp=temp->next;
}
pl.tidyQ;
return pi;
}
template <class T> PolynomiaKT> Int(Polynomial<T> p)
{
PolynomiaKT> pl(p) ;
P°lyListItem<T> *temp=pl.head;
while (temp)
{
*(temp->item)=Int(*(temp->item));
temp=temp->next;
}
Pl.tidyQ;
^etUrn pi ;
SmPlate <class T> Polynomial<T> Polynomial<T>::operator+(T c) const
return (*this)+(c*(variable)); }
- *-late <class T> Polynomial<T> Polynomial<T>: .-operator-(T c) const
teturn (*this)~(c*(variable)); }
S>late <cl3ss T>
516
Глава 10. Листинги программ
Polynomial<T> operator+(T с,const Polynomial<T> &p)
{ return p+(c*(p.variable~0)); }
template <class T>
Polynomial<T> operator-(T c,const Polynomial<T> &p)
{ return (c*(p.variable~0))-p; }
template <class T> Polynomial<T>& Polynomial<T>::operator=
{ return *this=Polynomial<T>(variable).operator+(c); }
template <class T> Polynomial<T>
operator+(const Polyterm<T> &xl, const Polyterm<T> &x2)
{
Polynomial<T> pi(xl.variable());
return pl+xl+x2;
}
template <class T> Polynomial<T>
operator-(const Polyterm<T> &xl, const Polyterm<T> &x2)
{
PolynomiaKT> pi(xl.variable());
return pl+xl-x2;
>
template <class T> Polynomial<T>
operator+(T xl,const Polyterm<T> &x2)
{
Polynomial<T> pi(x2.variable());
return pl+xl+x2;
}
template <class T> Polynomial<T>
operator-(T xl,const Polyterm<T> &x2)
{
PolynomiaKT> pi(x2. variable()) ;
return pl+xl-x2;
}
template <class T> Polynomial<T>
operator+(const Polyterm<T> &xl,T x2)
{
Polynomial<T> pi(xl.variable());
return (pl.operator+(xl)).operator+(x2);
}
template <class T> Polynomial<T>
10.12. Класс Polynomial
,-rator- (const Polyterm<T> &xl,T x2)
of"
[ lynomiaKT> pi (xl. variable () ) ;
turn (pl.operator+(xl)).operator-(x2) ;
}
mplate <class T> Polynomial<T>&
polynomial<T>::operator*=(T c)
p0lyterm<T> x;
return *this*=x;
}
template <class T> Polynomial<T>&
polynomial<T>::operator*=(const Polyterm<T> &x)
{
polyListItem<T> *temp=head;
while(temp)
{
* (temp->item)*=x;
temp=temp->next;
}
tidy() ;
return *this;
}
template <class T> Polynomial<T>&
Polynomial<T>::operator*=(const Polynomial<T> &p)
Polynomial<T> pi(this->variable),p2(this->variable);
P°lyListItem<T> *temp=p.head;
while(temp)
{
Pl=*this;
pl*=*(temp->item);
P2+=pl;
tetivp=temp->next;
*this=p2;
teturn *this;
emPlate <class T> Polynomial<T>
°lynomial<T>::operator*(T c) const
518
Глава 10. Листинги программ
PolynomiaKT> pl(*this);
pl*=c;
return pi;
}
template <class T> Polynomial<T>
Polynomial<T>::operator*(const Polyterm<T> &x) const
{
PolynomiaKT> pl(*this);
pl*=x;
return pi;
}
template <class T> Polynomial<T>
Polynomial<T>::operator*(const Polynomial<T> &p) const
{
PolynomiaKT> pl(*this);
pl*=p;
return pi;
>
template <class T> Polynomial<T>
Polynomial<T>::operator"(unsigned int n)
{ return poly power(*this,n); }
template <class T> Polynomial<T>
operator*(T c,const Polynomial<T> &p)
{ return p*c; }
template <class T> Polynomial<T>
operator*(const Polyterm<T> &x, const Polynomial<T> &p)
{ return p*x; }
template <class T> Polynomial<T>& Polynomial<T>::operator/
{
polyListItem<T> *temp=head;
while(temp)
{
*(temp->item)/=c;
temp=temp->next;
}
tidyO;
return *this;
}
template <class T> Polynomial<T>&
10.12. Класс Polynomial
omlal<T>::operator/=(const Polyterm<T> &x)
ро1Ув'"
lvListItem<T> *temp=head;
„bxie(temp)
{
*(temp_>item)/=x;
temp=temp->next;
tidy О;
return *this;
}
template <class T> Polynomial<T>&
p0lynomial<T>::operator/=(const Polynomial<T> &p)
{
polyListItem<T> *templ=head,*temp2=p.head;
polynomial<T> result(this->variable);
Poiyterm<T> temp(this->variable),zero;
zero=T@);
if (temp2==NULL) return *this;
while((temp!=zero)&&(tempi!=NULL))
{
temp=(*(templ->item))/(*(temp2->item));
*this-=temp*p;
result+=temp;
templ=head:
}
*this=result;
return *this;
}
template <class T> PolynomiaKT>
p°lynomiaKT>: : operator/(T c) const
p°lynomiaKT> pl(*this);
Pl/=c;
Return pi;
^ei4>late <class T> Polynomial<T>
о1Упот1аКТ>: :operator/(const Polyterm<T> &x) cons
°lynomia.KT> pl(*this);
Pl/=x;
feturn pl>
520
Глава 10. Листинги программ
template <class T> Polynomial<T>
Polynomial<T>::operator/(const Polynomial<T> &p) const
{
PolynomiaKT> pl(*this);
pl/=p;
return pi;
}
template <class T> Polynomial<T>& Polynomial<T>: :operator°/,= (T
{ return *this-=c*(*this/c); }
template <class T> Polynomial<T>&
Polynomial<T>: : operator'/,= (const Polyterm<T> &x)
{ return *this-=x*(*this/x); }
template <class T> PolynomiaKT>&
Polynomial<T>: :operator'/,= (const Polynomial<T> &p)
{ return *this-=p*(*this/p); }
template <class T> Polynomial<T>
Polynomial<T>: : operator'/, (T c) const
{ return *this-c*(*this/c); }
template <class T> Polynomial<T>
Polynomial<T> : : operator'/(const Polyterm<T> &x) const
{ return *this-x*(*this/x); }
template <class T> Polynomial<T>
Polynomial<T>: : operator'/,(const Polynomial<T> &p) const
{ return *this-p*(*this/p); }
template <class T> int Polynomial<T>::operator==(T c) const
{
Polyterm<T> x;
x=c;
return *this==x;
}
template <class T>
int Polynomial<T>::operator==(const Polyterm<T> &x) const
{
Polynomial<T> p(x);
p=x;
return *this==p;
}
10.12. Класс Polynomial
521
,late <class T>
te p0lynomial<T>::operator==(const Polynomial<T> &p) const
ipt turn ((*this-p).head==NULL); }
late <class T> int operator==(T c,const Polynomial<T> &p)
{ return p—c, 1
opiate <class T>
t operator==(const Polyterm<T> &x,const Polynomial<T> &p)
{ return p==x; >
mplate <class T> int Polynomial<T>:-.operator ! = (T c) const
{ return !(*this==c); }
template <class T>
int Polynomial<T>::operator!=(const Polyterm<T> &x) const
{ return !(*this==x); }
template <class T>
int Polynomial<T>::operator!=(const Polynomial<T> &p) const
{ return !(*this==p); }
template <class T> int operator!=(T c,const Polynomial<T> &p)
{ return p!=c; }
template <class T>
int operator!=(const Polyterm<T> &x,const Polynomial<T> &p)
{ return p!=x; }
template <class T> T Polynomial<T>: :valued c)
{
polyListItem<T> *temp=head;
T sum=T@);
while(temp)
{
sum+=(temp->item)->value(c);
temp=temp->next;
return sum;
}
tetnplate <class T> T PolynomiaKT> :: operator () (T c)
t return value(c); }
te»plate <class T>
°stream &operator<<(ostream &o,const Polynomial<T> &p)
522
Глава 10. Листинги программ
■С
polyListItem<T> *temp=p.head;
Т zero(O);
if(p.head==0)
{
o<<zero;
return o;
}
while(temp)
{
o<<*((temp)->item);
temp=((temp)->next);
if (temp) o<<"+";
}
return о;
>
template <class T> Polynomial<T>::"Polynomial()
{
polyListItem<T> *temp=head;
while (temp)
{
head=temp->next;
delete temp->item;
delete temp;
temp=head;
}
}
template <class T> void Polynomial<T>-. : tidy (void)
{
polyListItem<T> *temp,*temp2;
while ((head!=NULL)&&((T@)*(*(head->item)))==(*(he
{
temp=head;
head=head->next;
if (head) head->previous=NULL;
delete temp->item;
delete temp;
}
temp=head;
while (temp)
{
temp2=temp->next;
if(((T@)*(*(temp->item)))==(*(temp->item))))
{
10.13. Класс Set
523
if(temp->previous) (temp->previous)->next=temp->next;
jj(temp->next) (temp->next)->previous=temp->previous;
delete temp->item;
delete temp;
temp=temp2;
}
#endii
10.13. Класс Set
Открытый интерфейс класса Set:
• Set О: конструктор по умолчанию.
• Set (const T): конструктор для простого элемента.
• "Set(): деструктор.
• Set (const Set<T>&): конструктор копирования.
• Set<T>& operator = (const Set<T>&): оператор присваивания.
• int cardinality() const: количество элементов множества.
Дружественные функции класса Set.
• friend int operator == (Set<T>&, Set<T>&): оператор равенства.
• friend int operator + (Set<T>&,Set<T>&): объединение двух множеств.
• friend int operator * (Set<T>&,Set<T>&): пересечение двух множеств.
• ostreamfe operator << (ostream&,Set<T>&):оператор вывода в поток.
Для более детального описания структуры класса и его методов, пожалуйста,
обращайтесь к разд. 6.13.
// set.h
#ifndef _SET
#define _SET
^include <iostream>
#include <stdlib.h> // для exit(O)
Using namespace std;
// объявление класса Set
tempiate <class T> class Set
{
Public:
Set(); // конструктор по умолчанию
Set(const T); // конструктор простого элемента
~Set(); // деструктор
524
Глава 10. Листинги программ
Set(const Set <Т>&); // конструктор копировав-
Set<T>& operator = (const Set<T>&); // оператор присваивани Я
int cardinality() const; // число элемент
friend Set<T> operator + (Set<T>&,Set<T>&); // объединение
friend Set<T> operator * (Set<T>&,Set<T>&); // пересечение
friend int operator == (Set<T>&,Set<T>&);
friend ostreamfe operator << (ostream&,Set<T>&);
struct Box { // элемент связанного списка
T value; // значение отдельного элемента множества
Box* next; // указатель на следующий элемент
};
struct Set_DESC { // дескриптор
Box* root; // корень списка значений
Box* last; // последний элемент
int no_of„elements; // число элементов в списке
int references; // ссылка на этот элемент
};
private:
void first_element() // повторная установка первого элемента
void next_element(); // повторный переход к следующему
Т current_element() const;// повторный возврат текущего элемента
int is_at_end() const; // повторно идем в конец
void add_at_end(const T); // добавление к списку с конца
void releaseQ; // очистка памяти
void fail (const charD) const; // отказ от множества
Set_DESC* the_desc; // описание множества
Box* tne_current; // указатель на текущий элемент
>;
// определение класса Set
template <class T> Set<T>::Set()
{
the_desc = new Set_DESC[l];
if(the.desc == (Set.DESO) NULL)
failC'He могу открыть дескриптор множества");
the_desc -> no_of„elements = 0;
the_desc -> references = 1;
the.desc -> root = (Box*) NULL;
the_desc -> last = (Box*) NULL;
10.13. Класс Set
„late <class T> Set<T>::Set(const T item)
* tbe_desc = new Set_DESC[l];
if(tbe_desc == (Set_DESC*) NULL)
failC'He могу открыть дескриптор множества");
tbe_desc -> no_of_elements = 1;
tbe_desc -> references = 1;
tbe_desc -> root = new Box[l];
if(tbe_desc -> root == (Box*) NULL)
failC'He могу открыть дескриптор множества");
tbe_desc -> last = the_desc -> root;
tbe_desc -> root->value = item;
the_desc -> root->next = (Box*) NULL;
}
template <class T> void Set<T>::release()
{
if(—(the_desc->references)==0)
{
Box* p_active = the_desc->root;
while(p_active != (Box*) NULL)
{
Box* p_remove = p_active;
p_active = p_active->next;
delete [] p_remove;
}
delete [] the_desc;
}
}
template <class T> Set<T>::"Set()
{ release () ; }
template <class T> void Set<T>::fail(const char mes[]) const
cout << "\n" << "Ошибка класса : Set" << "\n";
cout << mes << "\n";
exit(-l) ;
Opiate <class T> Set<T>;: Set (const Set <T>& copy)
copy.the_desc -> references++; // скопированный счетчик
// ссылки элемента
the_desc = copy.the_desc; // копируем указатель на desc
526
Глава 10. Листинги программ
the_current = сору.the_desc -> root; // текущий элемент
}
template <class T> int Set<T>::cardinality() const
{ return the_desc -> no_of„elements; }
template <class T> void Set<T>::first_element()
{ the_current = the_desc -> root; }
template <class T> void Set<T>::next_element()
{
if(the_current != (Box*) NULL)
the_current = the_current->next;
template <class T> T Set<T>::current_element(void) const
{
if(the_current == (Box*) NULL)
{
fail("Выбранный элемент находится вне границ");
}
return the_current -> value;
template <class T> int Set<T>::is_at_end() const
{ return the_current == (Box*) NULL; }
template <class T> void Set<T>::add_at_end( const T item )
{
Box* p_add = new Box[l];
if(p_add == (Box*) NULL)
fail("He могу открыть новый элемент");
p_add -> value = item; p_add->next = (Box*) NULL;
if(the_desc->root == (Box*) NULL)
{
the_desc->root = p_add;
the_desc->last = p_add;
} else {
the_desc -> last -> next = p_add;
the_desc -> last = p_add;
}
the_desc -> no_of_elements++;
template <class T>
Set<T>& Set<T>::operator = (const Set<T>& si)
10.13. Класс Set
r^.the_desc->references++;// скопированный счетчик ссылки эле:
release(); // память под обновляемый элемент
tbe_desc = sl.the_desc;
■return *this;
}
mplate <class T> Set<T> operator + (Set<T>& sl,Set<T>& s2)
{
Set<T> result;
sl.first„element(); s2.first_element();
while(!si.is_at_end() II !s2.is_at_end())
{
if(sl.is_at_end())
{
result.add_at_end(s2.current„element());
s2.next_element();
} else
if(s2.is_at_end())
{
result.add_at_end(sl.current„element());
si.next_element();
} else
if(si.current_element() < s2.current_element())
{
result.add_at_end(sl.current„element());
s1.next„element();
} else
if(sl.current_element() > s2.current_element())
{
result.add_at_end(s2.current_element());
s2.next„element();
} else
if(si.current_element() == s2.current_element())
{
result.add_at_end(sl.current_element());
si,next_element(); s2.next_element();
}
}
return result;
}
template <class T> Set<T> operator * (Set<T>& sl,Set<T>& s2)
int flag = 0;
Set<T> intersection;
528
Глава 10. Листинги программ
si.first„element(); s2.first„element();
while(!si.is_at_end())
{
while((!s2.is_at_end()) && (!flag))
{
if(si.current„element() == s2.current_element())
{
intersection.add_at_end(si.current„element());
flag = 1;
}
s2.next_element();
}
flag = 0;
si.next_element();
}
return intersection;
template <class T> int operator == (Set<T>& sl,Set<T>& s2)
{
si.first„element(); s2.first„element();
if(si.cardinality() != s2.cardinality())
return 0;
if(si.cardinality() == 0) return 1;
while(!si.is_at_end())
{
if(si. current_element() != s2.current„element())
return 0;
si.next_element(); s2.next_element();
}
return 1;
}
template <class T> ostreamfe operator << ( ostreamfe s,Set<T>&
{
int i;
sl.first_element(); s << "(";
for(i=0; i < sl.cardinality(); i++ )
{
s << sl.current„element();
sl.next_element();
if(i != sl.cardinality()-l) s << ",";
}
s « ")";
return s;
}
#endif
10.14. Класс Symbolic
529
|4. Класс Symbolic
\0
оловочный файл Mall.h включает классы Terms, Magnitude, Variable,
her, Function, Fsqrt, Fdf, Fint, Fexp, Fsinh, Fcosh, Fsin, Fcos, Fin.
оловочный Msymbol.h файл включает классы Symbol, Sum, Product.
рЫТый интерфейс класса Variable:
Variable(): конструктор по умолчанию.
, Variable(String): конструктор.
, Variable(String,T): конструктор.
type О: возвращает символ ' V для указания типа объекта.
• varNameO : возвращает имя переменной.
ф oprint (ostreamfe): отправляет Variable в выходной поток.
( val(): численное значение Variable.
0 set (const T): установка численного значения Variable.
Открытый интерфейс класса Number:
• Number О: конструктор по умолчанию.
• Number(T): конструктор.
• Оператор преобразования типа: operator T()
• Оператор присваивания: =
• type(): возвращает символ 'N' для указания типа объекта.
• varNameO : возвращает строку NULL.
• oprint (ostreamfe): отправляет Number в выходной поток.
• val(): численное значение Number.
• set (const T): установка численного значения Number.
Открытый интерфейс классов Fsqrt, Fdf, Fint, Fexp, Fsinh, Fcosh, Fsin,
Fcos, Fin
• varNameO : возвращает имя функции FName.
• oprint (): отправляет имя функции в выходной поток.
• f (const Т&): численное значение функции.
• type(): возвращает букву, указывающую на вид функции.
Открытый интерфейс класса Sum:
Sum(): конструктор по умолчанию.
Sum(const T): конструктор для численных значений.
Sum(String, int): конструктор для символьных переменных.
clear (): удаление значения и зависимости переменной.
expand(): дистрибутивный закон и раскрытие биномов и мультиномов.
set (const T): назначение численного значения переменной.
put (const Sum<T>&, const Sum<T>&): замена выражения на другое.
lvalue(): численное значение выражения.
value(): символьное значение выражения.
coeff (int): коэффициент при постоянном члене.
coeff (const Sum<T>&): коэффициент при переменном члене.
coeff (const Sum<T>&,int n): коэффициент при члене со степенью п.
530
Глава 10. Листинги программ
• depend(const Sum<T>&): объявление зависимости переменной.
• nodepend(const Sum<T>&): объявление независимости переменной
• isdepend(const Sum<T>&): проверка зависимости переменной.
• i s .Number (): проверка, является ли переменная числовым значением
• df (const Sum<T>&, const Sum<T>&)'. первая производная выражения
• df (const Sum<T>&,const Sum<T>&,int n): n-я производная выражени
• Int(const Sum<T>&,const Sum<T>&): интеграл от выражения.
• Int (const Sum<T>&,const Sum<T>&,int n): n-й интеграл от выражени
• power (const Sum<T>&,T): функция возведения в целую степень.
• exp(const Sum<T>&): экспоненциальная функция.
• cosh(const Sum<T>&): функция гиперболического косинуса.
• sinh(const Sum<T>&): функция гиперболического синуса.
• cos (const Sum<T>&): функция косинуса.
• sin(const Sum<T>&): функция синуса.
• ln(const Sum<T>&): функция логарифма.
• sqrt (const Sum<T>&): функция квадратного корня.
• Арифметические операторы: +(унарный), -(унарный), +, -, *, /, = +=
• Операторы сравнения: ==, !=
• Потоковые операторы: >>, <<
Для детального описания структуры класса и каждого из методов,
пожалуйста, обращайтесь к гл. 7.
// Mall.h
#ifndef ALL_H
#define ALL_H
#include <iostream.h>
#include <math.h>
#include "MString.h"
template <class T> class Terms
public:
virtual "Terms();
virtual char type() const = 0;
virtual String varNameQ const = 0;
virtual void oprint(ostream &) const = 0;
};
template <class T> class Magnitude : public Terms<T>
{
public:
10.14. Класс Symbolic
virtual "Magnitude();
virtual T val() const = 0;
virtual void set(const T) = 0;
};
mplate <class T> class Variable : public Magnitude<T>
{
private:
String name;
T value;
public:
Variable();
Variable(String);
Variable(String, T) ;
Variable(const Variable<T>&);
virtual ~Variable();
virtual char typeQ const;
virtual String varName() const;
virtual void oprint(ostream &) const;
virtual T valO const;
virtual void set(const T);
};
template <class T> class Number : public Magnitude<T>
{
private:
T data;
public:
// конструктор
Number();
Number(T);
Number(const Number<T>&);
virtual "NumberO;
// оператор преобразования
operator T () const;
// методы
Number<T> & operator = (const Number<T>&);
virtual char type() const;
virtual String varName() const;
virtual void oprint(ostreamfe) const;
virtual T val() const;
virtual void set(const T);
532
Глава 10. Листинги программ
template <class T> class Function : public Terms<T>
{
public:
virtual "FunctionO ;
virtual double f(const T &) const = 0;
};
template <class T> class Fsqrt : public Function<T>
{
private:
static const char * const Fname;
public:
Fsqrt();
virtual "Fsqrt();
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char typeQ const;
virtual double f(const T &) const;
};
template <class T> class Fdf : public Function<T>
{
private:
static const char * const Fname;
public:
FdfQ;
virtual "Fdf();
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char typeQ const;
virtual double f(const T &) const;
>;
template <class T> class FInt : public Function<T>
{
private:
static const char * const Fname;
public:
FInt О ;
virtual "FInt();
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char type О const;
virtual double f(const T &) const;
>;
10.14. Класс Symbolic
late <class T> class Fexp : public Function<T>
{
orivate:
" static const char * const Fname;
public
Fexp О ;
virtual "FexpО;
virtual String varNameQ const;
virtual void oprint(ostream &) const;
virtual char type() const;
virtual double f(const T &) const;
};
template <class T> class Fsinh : public Function<T>
{
private:
static const char * const Fname;
public:
FsinhO ;
virtual ~Fsinh();
virtual String varNameQ const;
virtual void oprint(ostream &) const;
virtual char type О const;
virtual double f(const T &) const;
};
template <class T> class Fcosh : public Function<T>
{
private:
static const char * const Fname;
Public:
FcoshQ ;
virtual ~Fcosh();
virtual String varNameQ const;
virtual void oprint(ostream &) const;
virtual char typeQ const;
virtual double f(const T &) const;
teaplate <class T> class Fsin : public Function<T>
^ivate:
static const char * const Fname;
FsinQ ;
virtual "FsinQ ;
534
Глава 10. Листинги программ
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char typeO const;
virtual double f(const T &) const;
>;
template <class T> class Fcos : public Function<T>
{
private:
static const char * const Fname;
public:
FcosQ ;
virtual ~Fcos();
virtual String varNameO const;
virtual void oprint(ostream ft) const;
virtual char type О const;
virtual double f(const T &) const;
>;
template <class T> class Fin : public Function<T>
i
private:
static const char * const Fname;
public:
FlnQ;
virtual ~Fln() ;
virtual String varNameO const;
virtual void oprint(ostream &) const;
virtual char type О const;
virtual double f(const T &) const;
>;
// класс Terms
template <class T> Terms<T>::"Terms() {>
// класс Magnitude
template <class T> Magnitude<T>::"Magnitude() O
// класс Variable
template <class T> Variable<T>::Variable() : name(""),value(T@))
template <class T> Variable<T>::Variable(String nm)
: name(nm), value(T@)) О
template <class T> Variable<T>::Variable(String nm, T v)
: name(nm), value(v) O
template <class T> Variable<T>::Variable(const Variable<T>& v)
: name(v.name), value(v.value) О
10.14. Класс Symbolic
535
mplate <class T> Variable<T>::"Variable() О
mplate <class T> void Variable<T>::set(const T num) {value = num;}
mplate <class T> char Variable<T>: :type() const { return 'V; }
mplate <class T> String Variable<T>::varNameO const{return name;}
template <class T> void Variable<T>::oprint(ostreamfe os) const
{ os « name;}
template <class T> T Variable<T>::val() const { return value; }
// класс Number
template <class T> Number<T>:
template <class T> Number<T>:
template <class T> Number<T>:
: data(num.data) {}
template <class T> Number<T>::"Number() {}
NumberQ : data(T@)) О
Number(T num) : data(num) {}
Number(const Number<T>& num)
template <class T> Number<T> & Number<T>::operator =
(const Number<T>& num)
{
if (this != &num) data = num.data;
return *this;
}
template <class T> Number<T>::operator T () const { return data; }
template <class T> char Number<T>::type() const { return 'N'; }
template <class T> String Number<T>::varNameO const { return ""; }
template <class T> void Number<T>::oprint(ostreamfe os) const
{ os << data; }
template <class T> T Number<T>::val() const { return data; }
template <class T> void Number<T>::set(const T num){ data = num; }
// класс Function
template <class T> Function<T>::"FunctionO {}
// класс Fsqrt
template <class T> const char * const Fsqrt<T>::Fname = "sqrt";
template <class T> Fsqrt<T>::Fsqrt() {}
template <class T> Fsqrt<T>::"Fsqrt() {}
template <class T> char Fsqrt<T>::type() const { return 'q'; }
template <class T> String Fsqrt<T>::varNameO const { return Fname;}
template <class T> void Fsqrt<T>::oprint(ostreamfe os) const
{ os << Fname; }
template <class T> double Fsqrt<T>::f(const T& x) const
{ return sqrt(double(x)); }
11 класс Fdf
template <class T> const char * const Fdf<T>::Fname = "df";
536
Глава 10. Листинги программ
template <class T> Fdf<T>::Fdf() О
template <class T> Fdf<T>::"Fdf() О
template <class T> char Fdf<T>
template <class T> String Fdf<T>
template <class T> void Fdf<T>
{ os << Fname; }
template <class T> double Fdf<T>::f(const T&) const
{ return 0.0; >
:type() const { return 'd'; \
:varName() const { return Fname- \
:oprint(ostream& os) const
// класс
template
template
template
template
template
template
FInt
<class T> const char * const FInt<T>::Fname = "Inf
<class T> FInt<T>::FInt() {}
<class T> FInt<T>::~FInt() {}
:type() const { return 'i>; }
:varName() const { return Fname; }
:oprint(ostreamfe os) const
<class T> char FInt<T>
<class T> String FInt<T>
<class T> void FInt<T>
{ os << Fname; У
template <class T> double FInt<T>::f( const T&) const
{ return 0.0; }
// класс
template
template
template
template
template
template
Fexp
<class T> const char * const Fexp<T>::Fname
<class T> Fexp<T>::Fexp() {>
<class T> Fexp<T>::"Fexp() О
"exp"
:type() const { return 'e'; }
:varName() const { return Fname; }
:oprint(ostreamfc os) const
<class T> char Fexp<T>:
<class T> String Fexp<T>:
<class T> void Fexp<T>:
{ os << Fname; }
template <class T> double Fexp<T>::f(const T& x) const
{ return exp(double(x)); }
// класс
template
template
template
template
template
template
Fsinh
<class T> const char * const Fsinh<T>::Fname
<class T> Fsinh<T>::Fsinh() О
<class T> Fsinh<T>::"Fsinh() {}
"sinh"
<class T> char Fsinh<T>
<class T> String Fsinh<T>
<class T> void Fsinh<T>
{ os << Fname; }
template <class T> double Fsinh<T>::f(const T& x) const
{ return sinh(double(x)); >
:type() const { return 'У> У
:varName() const { return Fname;}
:oprint(ostreamfe os) const
// класс Fcosh
template <class T> const char * const Fcosh<T>::Fname = "cosh";
template <class T> Fcosh<T>::Fcosh() {}
template <class T> Fcosh<T>::"FcoshO {>
10.14. Класс Symbolic
537
einplate <class T> char Fcosh<T>: : typeQ const { return 'w'; }
emplate <class T> String Fcosh<T>::varNameO const { return Fname;}
mplate <class T> void Fcosh<T>::oprint(ostreamfe os) const
{ os << Fname; У
mplate <class T> double Fcosh<T>::f(const T& x) const
{ return cosh(double(x)); }
// класс Fsin
template <class T> const char * const Fsin<T>::Fname = "sin";
template <class T> Fsin<T>::Fsin() О
template <class T> Fsin<T>::~FsinO О
template <class T> char Fsin<T>::typeO const { return 'z'; }
template <class T> String Fsin<T>::varNameO const { return Fname; У
template <class T> void Fsin<T>::oprint(ostreamfe os) const
{ os << Fname; }
template <class T> double Fsin<T>::f(const T& x) const
{ return sin(double(x)); }
// класс Fcos
template <class T> const char * const Fcos<T>::Fname = "cos";
template <class T> Fcos<T>::Fcos() О
template <class T> Fcos<T>::~Fcos() О
template <class T> char Fcos<T>::type() const { return 'c'; >
template <class T> String Fcos<T>::varNameO const { return Fname; }
template <class T> void Fcos<T>::oprint(ostreamfc os) const
{ os << Fname; У
template <class T> double Fcos<T>::f(const T& x) const
{ return cos(double(x)); }
// класс Fin
template <class T> const char * const Fln<T>::Fname = "In";
template <class T> Fln<T>::Fln() О
template <class T> Fln<T>::"FlnO {}
typeO const { return '1'; }
varNameO const { return Fname; }
oprint(ostreamfe os) const
template <class T> char Fln<T>
template <class T> String Fln<T>
template <class T> void Fln<T>
{ os << Fname; }
template <class T> double Fln<T>::f(const T& x) const
{ return log(double(x)); }
#endif
// MSymbol.h
#ifndef SYMBOLIC.H
#define SYMBOLIC.H
538
Глава 10. Листинги программ
#include <assert.h>
#include <iostream.h>
#include <stdlib.h>
#include <ctype.h>
«include "MString.h"
#include "Mall.h"
#include "MList.h"
typedef Number<int> Integer;
typedef Number<double> Double;
// опережающее объявление
template <class T> class Sum;
template <class T> class Product;
// объявление и определение класса Symbol
template <class T>
class Symbol
i
protected:
// конструктор
Symbol();
Symbol(int);
Symbol(int, T *, int *);
Symbol(int, Symbol<T> **, T *, int *);
public:
// поля данных
int branches;
Symbol<T> **ep;
T *fac_exp;
int *next_var;
// виртуальный деструктор
virtual "Symbol();
// невиртуальные методы
void remove(int); // удаление лишних узлов
void removeBQ; // удаление переменных с type()=='B'
int simpFuncs(Symbol<T>**,char);// упрощение элементарных функции
Symbol<T>*zeroO const; // специальная функция для константы 0
Symbol<T>*one() const; // специальная функция для константы 1
void Shrink(); // упрощение выражения
// чисто виртуальные внутренние функции
virtual char type() const = 0;
virtual Sum<T> val() const = 0;
virtual T nval() const = 0;
10.14. Класс Symbolic
539
virtual int replacel(Symbol<T> *, Symbol<T> *) = 0;
virtual int equal_index(Symbol<T>*) = 0;
virtual int is_equal(Symbol<T> *) const = 0;
virtual int gather() = 0;
virtual void copy(Symbol<T> **) const = 0;
„irtual void oprint(ostream &) const = 0;
virtual void deleteOneNodeO =0; // удаляем только этот узел
virtual void deleteAllNodesO = 0; // удаляем все узлы,
// исходящие из этого узла
virtual int mxpand(Symbol<T> **) = 0;
virtual int dxpand(Symbol<T> **) = 0; // дистрибутивный закон
virtual void dif f (SymboKT> **, SymboKT> *) const = 0;
virtual void integrate(Symbol<T> **, Symbol<T> *) const = 0;
virtual int funcSimpCint &, int &) = 0; // упрощение функций
virtual int shrink_l() =0; // удаление из произведения
virtual int shrink_2() =0; // удаление слишком многих сумм
// и произведений
// удаление нулевых произведений и слагаемых
virtual int shrink_3(Symbol<T> **) = 0;
virtual int shrink_4() =0; // суммы из сумм
virtual int shrink_5() = 0; // удаление сумм из одного слагаемого
};
// объявление класса Product
template <class T> class Product : public Symbol<T>
{
private:
// поля данных
static int Comm;
// закрытый конструктор
Product(int, Symbol<T> **, T *, int *);
// закрытые виртуальные функции
virtual char type() const;
virtual Sum<T> val() const;
virtual T nvalQ const;
virtual int replacel(Symbol<T> *, Symbol<T> *);
virtual int equal_index(Symbol<T>*);
virtual int is_equal(Symbol<T> *) const;
virtual int gatherQ;
virtual void copy(Symbol<T> **) const;
virtual void oprint(ostream &) const;
virtual void deleteOneNodeO;
virtual void deleteAllNodesO;
540
Глава 10. Листинги программ
virtual int mxpand(Symbol<T> **);
virtual int dxpand(Symbol<T> **) ;
virtual void difi(Symbol<T> **, Symbol<T> *) const;
virtual void integrate(Symbol<T> **, Symbol<T> *) c<
virtual int funcSimp(int &, int &) ;
virtual int shrink_l();
virtual int shrink_2();
virtual int shrink_3(Symbol<T> **) ;
virtual int shrink_4();
virtual int shrink_5();
public:
// конструкторы
Product(int);
"Product();
friend void Commutative(T, int);
};
// объявление класса Sum
template <class T> class Sum : public Symbol<T>
i
private:
// закрытый конструктор
Sum(const T, char);
Sum(char, char);
Sum(int, Symbol<T> **, T *, int *, String);
// закрытые функции
void diffRules(Symbol<T> **, char) const;
void IntRules(Symbol<T> **, char) const;
// закрытые виртуальные функции
virtual char typeO const;
virtual Sum<T> val() const;
virtual T nval() const;
virtual int replacel(Symbol<T> *, Symbol<T> *);
virtual int equal_index(Symbol<T>*);
virtual int is_equal(Symbol<T> *) const;
virtual int gather();
virtual void copy(Symbol<T> **) const ;
virtual void oprint(ostream &) const;
virtual void deleteOneNodeO ;
virtual void deleteAHNodes () ;
virtual int mxpand(Symbol<T> **);
10.14. Класс Symbolic
541
virtual int dxpand(Symbol<T> **);
virtual void dif f (SymboKT> **, SymboKT> *) const;
virtual void integrate(Symbol<T> **, Symbol<T> *) const;
virtual int funcSimp(int &, int &) ;
virtual int shrink_l();
virtual int shrink_2();
virtual int shrink_3(Symbol<T> **) ;
virtual int shrink_4();
virtual int shrink_5();
// поля данных
int is_Bound, is_Set;
Terms<T> *data;
List<Sum<T>*> deplist;
public:
// конструкторы
SumO ;
Sum(const T);
Sum(int, int);
Sum(String, int);
Sum( const Sum<T>&);
~Sum();
// закрытые функции, доступные только Sum<T>
void clearQ; // обновление использованной переменной
void expand(); // раскрытие скобок выражения
void set(const T); // присвоение значения переменной
int put(const Sum<T>&, const Sum<T &);
// присвоение выражения другому выражению
Т nvalueQ const;// возвращает численное значение переменной
Sum<T> value() const; // возвращает значение выражения
Т coeff(int) const; // возвращает коэффициент
// члена-константы
Sum<T> coeff(const Sum<T>&) const; // возвращает коэффициент
// при члене
Sum<T> coeff(const Sum<T>&,int) const; // возвращает коэффициент
// при члене со степенью п.
void depend(const Sum<T>&); // объявл. зависимости перем.
void nodepend(const Sum<T>&); // объявл. независимости перем.
int isdepend(const Sum<T>&) const; // проверка зависимости перем.
int is_Number() const; // проверка, переменная - чисто число?
// арифметические операторы
Sum<T>& operator = (const Sum<T>&);
542
Глава 10. Листинги программ
Sum<T> operator +
Sum<T> operator -
Sum<T> operator +=
Sum<T> operator -=
Sum<T> operator *=
Sum<T> operator /=
() const;
() const;
(const Sum<T>&)
(const Sum<T>&)
(const Sum<T>&)
(const Sum<T>&)
friend Sum<T> operator + (const Sum<T>&, const Sum<T>&)
friend Sum<T> operator - (const Sum<T>&, const Sum<T>&)
friend Sum<T> operator * (const Sum<T>&, const Sum<T>&)
friend Sum<T> operator / (const Sum<T>&, const Sum<T>&)
friend int operator ==
friend int operator !:
(const Sum<T>&, const Sum<T>&);
(const Sum<T>&, const Sum<T>&);
friend ostream & operator << (ostreamfe, const Sum<T>&);
friend istream & operator >> (istreamfe, Sum<T>&);
friend
friend
friend
friend
friend
friend
friend
friend
friend
friend
friend
friend
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
Sum<T>
exp(const Sum<T>&);
sinh(const Sum<T>&);
cosh(const Sum<T>&);
sin(const Sum<T>&);
cos(const Sum<T>&);
ln(const Sum<T>&);
sqrt(const Sum<T>&);
power(const Sum<T>&, T);
df(const Sum<T>&, const Sum<T>&);
df(const Sum<T>&, const Sum<T>&, int);
Int(const Sum<T>&, const Sum<T>&);
Int(const Sum<T>&, const Sum<T>&, int);
>;
friend Symbol<T> * SymboKT>: :zero() const;
friend Symbol<T> * SymboKT>: :one() const;
friend int Product<T>::mxpand(Symbol<T> **) ;
friend void Product<T>::integrate(Symbol<T>**,Symbol<T>*) с
// определение для класса Symbol
template <class T> SymboKT> :: Symbol ()
: branches(O), ep(NULL), fac_exp(NULL), next_var(NULL)
{ cerr « "Symbol: вызван конструктор по унолчанию" << endl;}
template <class T> Symbol<T>::Symbol(int num)
: branches(num), ep(NULL), fac_exp(NULL), next_var(NULL)
{
// если num нулевое, ничего не делаем
10.14. Класс Symbolic
543
(num)
ер = new Symbol<T>*[num];
fac_exp = new T[num];
next_var = new int[num];
assert(ep != NULL);
assert(fac_exp != NULL);
assert(next_var != NULL);
// инициализируем ер[] в NULL, fac_exp[] в 1 и next_var[] в О
for (int i=0; i<num; i++)
i
ep[i] = NULL;
fac_exp[i] = T(l);
next_var[i] = 0;
}
}
template <class T> Symbol<T>::Symbol(int num,T *new_fe,int *new_nx)
: branches(num), ep(NULL), fac_exp(NULL), next_var(NULL)
{
// если num нулевое, ничего не делаем
if (num)
i
ep = new Symbol<T>*[num];
fac_exp = new T[num];
next_var = new int[num];
// инициализируем ер[] в NULL
assert(ep != NULL);
assert(fac_exp != NULL);
assert(next_var != NULL);
fac_exp[]
// и next_var[] в соответствующие значения
for (int i=0; i<num; i++)
{
ep[i] = NULL;
fac_exp[i] = new_fe[i];
next_var[i] = new_nx[i];
}
template <class T>
Symbol<T>::Symbol(int num,Symbol<T> **newep,T *new_fe,int *new_nx)
: branches(num), ep(NULL), fac_exp(NULL), next_var(NULL)
{
// если num нулевое, ничего не делаем
if (num)
{
ep = new Symbol<T>*[num]; assert(ep != NULL);
fac_exp = new T[num]; assert(fac_exp != NULL);
next_var = new int[num]; assert(next_var != NULL);
544
Глава 10. Листинги программ
II инициализируем ер[] и fac_exp[] в соответствующие зна.ч
for (int i=0; i<num; i++) efiItl1
{
ep[i] = newepEi];
fac_exp[i] = new_fe[i];
next_var[i] = new_nx [i];
}
}
}
// деструктор
template <class T> SymboKT>: : "Symbol () {}
// удаляем связь и используемую память
// Замечание: число ветвей уменьшается на 1,
// индекс есть позиция удаляемого узла
template <class T> void Symbol<T>::remove(int index)
{
int k, m;
SymboKT> **ept;
T *fac_expt;
int *next_vart;
// удаление узла приводит к уменьшению числа ветвей на 1
branches—;
ept = new Symbol<T>*[branches] ; assert(ept != NULL);
fac_expt = new T[branches]; assert(fac_expt != NULL);
next_vart = new int[branches]; assert(next_vart != NULL);
// копируем узел, который не может быть удален
for (k=0; k<index; k++)
{
ept [k] = ep [k] ;
fac_expt[k] = fac_exp[k];
next_vart[k] = next_var[k];
}
for (k=index, m=index+l; k<branches; k++, m++)
{
ept [k] = ep [m] ;
fac_expt[k] = fac_exp[m];
next_vart[k] = next_var[m];
}
// удаляем старое месторасположение
ep [index] ->deleteAHNodes () ;
10.14. Класс Symbolic
545
delete [] ер; delete [] fac_exp; delete [] next_var;
// назначаем новое месторасположение
ер = ept;
fac_exp = fac_expt;
next_var = next_vart;
}
I/ удаляем переменные с type()==;B' (определенные переменные)
mplate <class T> void Symbol<T>::removeB()
{
int i;
for (i=0; i<branches; i++)
{
if (ep[i]->type() == »B')
{
ep[i]->ep[0]~>copy(ep+i) ;
if (ep[i]->type() == 'V') next_var[i] = 1;
}
ep[i]->removeB();
if (!ep[i]->branches)
{
if ((ep[i]->type() == 'd')I I(ep[i]->type() == 'i'))
// это - оператор дифференцирования или интегрирования
{
if (ep[i]->ep[0]->type() == 'В')
ep[i]->ep[0]->ep[0]->copy(ep[i]->ep); // удалить узел типа 'В'
if (ep[i]->ep[l]->type() == 'В')
ер[i]->ep[l]->ep[0]->copy(ep[i]->ep+l);//удалить узел типа 'В'
if (ep[i]->ep[0]->type() == 'V') ep[i]->next_var[0] = 1;
if (ep[i]->ep[l]->type() == 'V') ep[i]->next_var[1] = 1;
if(ep[i]->type() == 'd')
ep [i] ->ep [0] ->dif f (ep+i, ep [i] ->ep [1] ) ;
else // type == 'i'
ep[i]->ep[0]->integrate(ep+i,ep[i]->ep[1] );
}
else if (islower(ep[i]->type())) // это - функция
{
if (ep[i]->ep[0]->type() == 'B')
ep[i]->ep[0]->ep[0]->copy(ep[i]->ep);// удалить узел типа 'В'
if (ep[i]->ep[0]->type() == 'V') ep[i]->next_var[0] = 1;
ep[i]->ep[0]->Shrink();
546
Глава 10. Листинги программ
ep[i]->simpFuncs(ep+i, ep[i]->typeO) ;
}
}
}
}
// упрощение элементарных функций
template <class T> int Symbol<T>::simpFuncs(Symbol<T> **s, char T\
switch (T)
{
case 'e':
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s) = one(); return 1; }
break;
case 'j':
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s) = zeroO; return 1; }
break;
case 'w' :
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s) = one(); return 1; }
break;
case 'z':
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s) = zeroO; return 1; }
break;
case ' с' :
if (ep[0]->branches==l && ep[0]->ep[0]==zero())
{ (*s) = one(); return 1; }
break;
case '1':
if (ep[0]~>branches==l && ep[0]->ep[0]==one())
{ (*s) = zeroO; return 1; }
break;
case 'q' :
if (ep[0]->branches==l)
{
if (ep[0]->ep[0]==zero()) { (*s) = zero(); return 1; J
else if(ep[0]->ep[0]==one() && ep[0]->fac_exp[0] == T(D)
{ (*s) = one(); return 1; }
}
break;
default: ;
}
return 0;
}
10.14. Класс Symbolic
547
1я<-е <class T> Symbol<T> * Symbol<T>::zero() const
I tatic SymboKT> *addr = new Sum<T>(T@) , charQ) ;
Jssert(addr != NULL);
return addr;
}
-plate <class T> Symbol<T> * Symbol<T>::one() const
static SymboKT> *addr = new Sum<T>(T(l) ,char()) ;
assert(addr != NULL);
return addr;
}
// определение класса Sum
// открытие уэла Sum, который не содержит данных
template <class T> Sum<T>::Sum()
: SymboKT>@), is_Bound@), is_Set@), data(new Variable<T>)
{ assert(data != NULL); }
// открытие узла Sum, который имеет п ветвей
template <class T> Sum<T>::Sum(int n, inT)
: SymboKT>(n), is_Bound@), is_Set@), data(new Variable<T>)
{ assert(data != NULL); }
// открытие узла Number со значением num
template <class T> Sum<T>::Sum(const T num)
: SymboKT>(l),is_Bound@),is_Set@),data(new Variable<T>("",num))
{
assert(data != NULL);
if (num == T@)) ep[0] = zero();
else
{
ep[0] = one();
fac_exp[0] = num;
}
next_var[0] = 1;
tejnplate <class T> Sum<T>: : Sum (const T num, char)
: SymboKT>@), is_Bound@), is_Set@), data(new Number<T>(num))
* assert(data != NULL); }
template <class T> Sum<T>::Sum(char ftype, char)
: SymboKT>B), is_Bound@), is_Set@)
IS*
548
Глава 10. Листинги программ
switch (ftype)
{
case
case
case
case
case
case
case
case
case
'd'
'i'
'e>
'У
'W'
>z>
'c'
'1'
'q*
default
data
data
data
data
data
data
data
data
data
>
=
=
=
=
=
=
=
=
=
new
new
new
new
new
new
new
new
new
Fdf<T>;
FInt<T>;
Fexp<T>;
Fsinh<T>;
Fcosh<T>;
Fsin<T>;
Fcos<T>;
Fln<T>;
Fsqrt<T>;
break;
break;
break;
break;
break;
break;
break;
break;
break;
}
assert(data != NULL);
branches =0; // это указывает, что это - концевая вершина
}
template <class T>
Sum<T>::Sum(int i,Symbol<T> **newep,T *new_ef,int *new_nx,String nm'
: Symbol<T>(i, newep, new_ef, new_nx), is_Bound@), is_Set@),
data(new Variable<T>(nm))
{ assert(data != NULL); }
template <class T> Sum<T>::Sum(String new_name, inT)|
: SymboKT>@), is_Bound@), is_Set@),
data(new Variable<T>(new_name))
{ assert(data != NULL); }
template <class T> Sum<T>::Sum(const Sum<T> &s)
: Symbol<T>(l), is_Bound@), is_Set@), data(new Variable<T>)
{
assert(data != NULL);
if (s.typeO == 'B') s.ep[0]->copy(ep);
else s.copy(ep);
if (s.type() == 'V') next_var[0] = 1;
template <class T> Sum<T>::~Sum()
{
if ((branches)
{
if (islower(typeO)) ep[0]->deleteAHNodes() ;
deleteOneNodeQ ;
}
else deleteAHNodesO ;
deplist.deleteAllNodes() ;
10.14. Класс Symbolic
plate <class T> Sum<T> & Sum<T>::operator = (const Sum<T> &s
If (this == &s) return *this;
((Sum<T>&)s).Shrink();
// удаляем старое значение *this, если оно было назначено.
// removeBO важна, потому что если она не включена, то в
// у = а+b; а = х; у = с;
// перед у = с будет освобождена вся старая память у,
// которая включает память а; тем самым будет потеряно х:
// У -> ""->а->х
// ">Ь
if (is_Bound) { removeBO; ep[0]->deleteAHNodes(); }
delete [] ер; delete [] fac_exp; delete [] next_var;
branches = 1;
ep = new Symbol<T>*[l]; assert(ep != NULL);
fac_exp = new T[l]; assert(fac_exp != NULL);
next_var = new int[l]; assert(next_var != NULL);
is_Bound = 1; is_Set = 0;
next_var[0] = 0;
if (s.typeO == 'B') s.ep[0]->copy(ep);
else if (s.branches) s.copy(ep);
else
{
ep[0] = new Sum<T>(l,0); assert(ep[0] != NULL);
s.copy(ep[0]->ep);
ep[0]->next_var[0] = l;
}
fac_exp[0] = TA);
expandО;
return *this;
}
'/ возвращает тип узла указьтаемого указателем
template <class T> char Sum<T>::type 0 const
if (branches)
{
if (is_Bound) return 'B';
return 'S';
}
return data->type();
550
Глава 10. Листинги программ
II печать численного значения выражения
template <class T> Sum<T> Sum<T>::val() const
{
int i;
Sum<T> s(T@));
// печать значения, если это - концевой узел
if (lbrancb.es)
{
if (islower(typeQ)) // это - функция
{
if (((Sum<T>*)ep[0])->is_Number())
return Sum<T>
(((Function<T>*)data)->f(((Sum<T>*)ep[0])->nvalue()));
else
{
Sum<T> r(type(),char()), p;
p = ep[0]->val();
p.copy(r.ep);
if (r.ep[0]->type() == 'V') r.next_var[0] = 1;
return r;
}
}
else
{
if (is_Set) return Sum<T>(((Magnitude<T>*)data)->val()) ;
else return *this;
}
}
else
for (i=0; Kbranches; i++)
s += Sum<T>(fac_exp[i]) * ep[i]->val();
return s;
}
// замена выражения 1 на выражение 2
template <class T> int Sum<T>::replacel(Symbol<T> *sl,Symbol<T> *s2)
{
int i, index, flag=0;
for (i=0; Kbranches; i++) flag += ep[i]->replacel(sl,s2);
if (islower(typeQ))
flag +=((Sum<T>*)ep [0])->put(*((Sum<T>*)si),*((Sum<T>*)s2));
if (sl->branches >= 1)
10.14. Класс Symbolic
551
{
index = equal_index(sl) ;
if (index >= 0)
{
s2->copy(ep+index);
fac_exp[index] = T(l);
flag++;
}
}
else
{
for (i=0; Kbranch.es; i++)
if (ep[i]->is_equal(sl))
{ s2->copy(ep+i); flag++; }
>
return flag;
}
// возвращаем индекс равного компонента
// возвращаем (-1), если такого компонента не найдено
template <class T> int Sum<T>::equal_index(Symbol<T> *)
{ return (-1); }
// проверяем, равно ли 2-е выражение
template <class T> int Sum<T>::is_equal(Symbol<T> *s) const
{
int i,k;
int *ip;
// если s - узел product, возвращаем О
if (s->type() == 'P') возвращаем О;
// если s эквивалентно this, возвращаем 1
if (s == (Symbol<T>*)this) возвращаем 1;
// если оба - концевые листы, a s не равно this,
// то проверяем функцию
if (!s->branches && !branches)
{
if (islower(typeO) && s->type() == typeQ)
{
if ((typeQ == >d>)ll(typeO == 'i'))
return (ep[0]->is_equal(s->ep[0]) && ep[l]->is_equal(s->ep[l]));
return ep[0]->is_equal(s->ep[0]);
}
else return 0;
}
552
Глава 10. Листинги программ
II если ветви не совпадают, возвращаем 0
if (branches != s->branches) return 0;
ip = new int[branches]; assert(ip != NULL);
for (i=0; Kbranches; i++) ip[i] = 0;
// проверяем, все ли ветви совпадают
for (i=0; i<branches; i++)
-С
for (k=0; k<branches; k++)
{
if (ep[i]~>is_equal(s->ep[k]) &&
fac_exp[i] == s~>fac_exp[k] && ip[k] == 0)
{ ip[k] = 1; break; }
}
// если нет парного члена, возвращаем 0
if (к == branches) return 0;
}
delete [] ip;
return 1;
}
// собираем члены, которые имеют одинаковые переменные
template <class T> int Sum<T>::gather()
{
int i, j, flag=0;
for (i=0; i<branches; i++) flag += ep[i]->gather();
// сравнение разных поддеревьев: если равны, собираем их
for (i=0; i<branches; i++)
for (j=i+l; j<branches; j++)
{
if (ep[i]->is_equal(ep[j]))
{
flag++;
fac_exp[i] += fac_exp[j]; // сумма множителей
// если fac_exp[] == 0 после сложения
if (fac_exp[i] == T@))
if (branches > 2)
{ remove(j); remove(i); j=i;}
else // branches == 2-> удаляем этот член
{ //и создаем узел zeroQ
remove(j); j=i;
10.14. Класс Symbolic
55;
ер[0] = zero () ;
fac_exp[0] = TA);
}
else { remove(j); j=i; }
}
}
return flag;
}
// копируем поддерево отсюда в sp
template <class T> void Sum<T>::copy(Symbol<T> **sp) const
{
if (branches)
{
int i;
*sp = new Sum<T>(branches,0); assert(*sp != NULL);
((Sum<T>*)(*sp))->is_Bound = is_Bound;
((Sum<T>*)(*sp))->is_Set = is_Set;
for (i=0; Kbranches; i++) (*sp)->fac_exp[i] = fac_exp[i];
for (i=0; Kbranches; i++) (*sp)->next_var[i] = next_var[i];
for (i=0; Kbranches; i++) ep[i]->copy((*sp)->ep+i);
}
else
{
if (islower(data->type()))
{
*sp = new Sum<T>(data->type(),char()); assert(*sp != NULL);
ep[0]->copy((*sp)->ep);
if ((data->type() == 'd')I I(data->type() == 'i'))
ep[l]->copy((*sp)->ep+l);
}
else *sp = (Symbol<T> *)this;
}
}
// Печать алгебраического выражения
template <class T> void Sum<T>::oprint(ostream &os) const
{
// печать содержимого, если зто - концевой лист
if ((branches)
{
data->oprint(os);
if (islower(data->type()))
{
if ((data->type() == 'd')I I(data->type() == 'i'))
554
Глава 10. Листинги программ
■С
os « "(";
if (ep[0]->branches==l && ер[0]->f ас_ехр[0] == T(m
ер[0]->ер[0]->oprint(os) ;
else ep[0]->oprint(os);
os « ",";
ep[l]->oprint(os); os « ")";
}
else ep[0]->oprint(os);
}
}
else
{
os « "(";
// не печатаем + т.к. это - первый член
if (fac_exp[0] == T(-1)) { os « "-"; ep[0]->oprint(os) ; }
else if (fac_exp[0] !== TA))
{
os << fac_exp[0];
if (ep[0] != oneO) { os « "*"; ep[0]->oprint(os) ; }
}
else ep[0]->oprint(os);
// печатаем остальные члены
for (int i=l; i<branches; i++)
{
if (fac_exp[i] > T@))
{
os « "+";
if (fac_exp[i] ==TA)) ep[i]->oprint(os);
else
{
os << fac_exp[i];
if (ep[i] != oneO) { os « "*"; ep[i] ->oprint (os); >
}
}
else // fac_exp[i] < T@)
{
if (fac_exp[i] == T(-l)) { os « "-"; ep[i]->oprint(os); >
else
{
os << fac_exp[i];
if (ep[i] != oneO) { os « "*"; ep[i] ->oprint(os); }
}
}
}
10.14. Класс Symbolic
os « ")";
}
}
// удаляем только этот узел
template <class T> void Sum<T>::deleteOneNode()
{
delete data;
delete [] ep; delete [] fac_exp; delete [] next_var;
}
// удаляем все узлы, исходящие иэ него
template <class T> void Sum<T>: :deleteAHNodes()
{
if (branches)
i
for (int i=0; i<branches; i++)
if (!next_var[i]) ep[i]->deleteAHNodes() ;
deleteOneNode();
}
else if (islower(type()))
{
ep [0] ->deleteAHNodes () ;
deleteOneNodeQ ;
}
}
// раскрытие биномов и мультиномов
template <class T> int Sum<T>::mxpand(Symbol<T> **)
{
int i, flag = 0;
for (i=0; i<branches; i++) flag += ep[i]->mxpand(ep+i);
return flag;
// дистрибутивный закон
template <class T> int Sum<T>::dxpand(Symbol<T> **)
<
int i, flag = 0;
for (i=0; Kbranches; i++) flag += ep[i]->dxpand(ep+i);
return flag;
}
дифференцирование
template <class T> void Sum<T>::diff(Symbol<T>**s,Symbol<T>*t
556
Глава 10. Листинги программ
int i;
if (branches)
{
*s = new Sum<T>(branches,0); assert(*s != NULL);
// копируем все коэффициенты
for (i=0; i<branches; i++) (*s)->fac_exp[i] = fac_exp[i]■
// дифференцируем все члены
for (i=0; Kbranch.es; i++)
{
ep[i]->diff((*s)->ep+i, t);
if ((*s)->ep[i]->type() == 'V II (*s)->ep[i]->type() == >N')
(*s)->next_var[i] = 1;
else (*s)->next_var[i] = 0;
}
}
else // type()=='V или 'N' или Functions,dx/dx=l,dy/dx=0,dC/dx=O
{
if (type() == 'd') // это - оператор дифференцирования
{
// (d/dx) df(y,x) = df(df(y,x),x)
*s = new Sum<T>('d',char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
}
else if (type() == 'i')
{
if(ep[l] == t) //d/dx Int(y(x),x) = y(x);
{
ep[0]->copy(s);
}
else if (isdepend(*((Sum<T>*)t)))
{
*s = new Sum<T>('d',char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
}
else *s = zero();
}
else if (islower(typeQ)) // это - функции
{
*s = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>B); assert((*s)->ep[0] != NULL);
10.14. Класс Symbolic 557
ep[0]->diff((*s)->ep[0]->ep,t);
diffRules((*s)->ep[0]->ep+l, type());
}
else if (type() == 'N') *s = zero();
else // type 0 == 'V
{
if ((Symbol<T>*)this == t) *s = one();
else if (isdepend(*((Sum<T>*)t)))
{
*s = new Sum<T>('d',char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
}
else *s = zero();
}
// интегрирование
template <class T> void Sum<T>::integrate(Symbol<T> **s,
Symbol<T> *t) const
{
int i;
// делаем выражение наиболее простым
// для интегрирования произведения
((Sum<T>*)this)->expand();
if (branches)
{
*s = new Sum<T>(branches,0); assert(*s != NULL);
// копируем все коэффициенты
for (i=0; i<branches; i++) (*s)->fac_exp[i] = fac_exp[i];
// интегрируем все члены
for (i=0; Kbranches; i++)
{
ep[i]->integrate((*s)->ep+i, t);
if ((*s)->ep[i]->type() == 'V II (*s)->ep[i]->type() == 'N')
(*s)->next_var[i] = 1;
else (*s)->next_var[i] = 0;
}
}
else // type() == 'V или 'N' или функции
Глава 10. Листинги программ
{
if (type() == 'i') // это - оператор интегрирования
{
// Int(Int(y,x),x)
*s = new Sum<T>('i',char()); assert(*s != NULL);
copy((*s)->ep) ;
t->copy((*s)->ep+l);
}
else if (typeQ == >d>) // это - оператор дифференцирования
{
// Int (df(y,x),x) = у
if (ep[l] == t)
{
*s=new Sum<T>(l,0);
ep[0]->copy((*s)->ep);
}
// Int (y(x),x)
else if(isdepend(*((Sum<T>*)t)))
{
*s = new Sum<T>('i',char()); assert(*s != NULL);
copy(Os)->ep) ;
t->copy((*s)->ep+l);
}
else // Int(y,x)=yx
{
*s = new Sum<T>(l,0); assert(Os) != NULL);
(*s)->ep[0] = new Product<T>B); assert((*s) != NULL);
copy((*s)->ep[0]->ep);
t->copy((*s)->ep[0]->ep+l);
}
}
else if (islower(typeQ)) // это - функции
{
SymboKT> *p=ep[0] ;
while((p->branches==l)&&(p->type()!='V'))
p=p->ep[0];
if (p == t)
IntRules(s, typeO);
else if (isdepend(*((Sum<T>*)t)))
{
*s = new Sum<T>('i',char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
}
else // Int(y,x)=yx
{
10.14. Класс Sym bolic
559
*s = new Sum<T>(l,0); assert(Os) != NULL);
Os)->ep[0] = new Product<T>B); assert((*s) != NULL);
copy((*s)->ep[0]->ep);
t->copy((*s)->ep[0]->ep+l);
}
}
else if (type() == 'N')
{
// int@,x)=0
if(((Number<T>*)data)->val()==T@)) *s = zero();
else // int(C,x)=Cx
{
*s = new Sum<T>(l,0); assert((*s) != NULL);
t->copy((*s)->ep);
}
}
else // typeQ == 'V
{
if ((SymboKT>*)this == t) // Int(x,x)=(x~2)/2
{
*s = new Sum<T>(l,0); assert ((*s) != NULL);
(*s)->ep[0]=new Product<T>(l); assert ((*s)->ep[0] !=NULL);
(*s)->fac_exp[0] = T(l)/TB);
(*s)->ep[0]->fac_exp[0] = TB);
t->copy((*s)->ep[0]->ep);
}
else if (isdepend(*((Sum<T>*)t)))
{
*s = new Sum<T>('i',char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
}
else // Int(y,x)=yx, у - переменная, отличная от х
{
*s = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>B); assert((*s)->ep[0] != NULL);
copy((*s)->ep[0]->ep);
t->copy((*s)->ep[0]->ep+l);
}
}
}
}
// упрощение специальных функций
template <class T> int Sum<T>::funcSimp(int&, int&)
i return 0; }
560
Глава 10. Листинги программ
// максимальное упрощение алгебраического выражения
template <class T> void Symbol<T>::Shrink()
{
int i, flag;
if (type() == '
else
{
removeBQ ;
do
{
do
{
flag = 0;
for (i=0;
for (i=0;
for (i=0;
for (i=0;
for (i=0;
B') ep[0]->
i<branches
i<branches
i<branches
i<branches
i<branches
i++) flag += shrink_2();
i++) flag += shrink_5();
i++) flag += shrink_3(ep+i);
i++) flag += shrink_l();
i++) flag += shrink_4();
} while(flag); // повторный цикл, если есть что упрощать
} while(gather()); // повторный цикл, если есть комбинация члено:
}
// возвращаем коэффициент при s
template <class T> Sum<T> Sum<T>::coeff(const Sum<T> &s) const
{
int i, j, k, m, found;
Symbol<T> *ptr, *sptr, *mptr;
Sum<T> result(T@));
// перед выделением коэффициента сначала упрощаем выражение
((Sum<T>*)this)->Shrink();
((Sum<T>&)s) .ShrinkO;
// x.coeff(x) -> 1
if(!branches &&
(&s==this I I
(s.typeQ == ;B' && s.ep[0]->type() == 'S' &&
s.ep[0]->branches == 1 && s.ep[0]->ep[0] == (Symbol<T>*)this))
{ result = T(l); return result; >
// инициализация различных указателей: ptr, sptr, mptr
//в зависимости от разных ситуаций
if (typeQ == 'В')
{
ptr = ер[0];
10.14. Класс Symbolic 561
if (s.type О == 'В')
{
mptr = s.ер [0];
if (mptr->type() == 'S' && mptr->branches == 1
&& mptr->fac_exp[0] == T(l) && !mptr->ep[0]->branches)
mptr = mptr->ep[0];
if (mptr->branches == 1) sptr = s.ep[0]->ep[0] ;
}
else
{
mptr = (SymboKT>*) (&s) ;
if (mptr->type() == 'S' && mptr->branches == 1
&& mptr->fac_exp[0] == T(l) && !mptr->ep[0]->branches)
mptr = mptr->ep[0];
if (mptr->branches == 1) sptr = s.ep[0];
}
}
else
{
ptr = (SymboKT>*)this;
if (s.type() == 'B')
{
mptr = s.ep [0];
if (mptr->type() == 'S' && mptr->branches == 1
&& mptr->fac_exp[0] == T(l) && !mptr->ep[0]->branches)
mptr = mptr->ep[0];
if (mptr->branches == 1) sptr = s.ep[0]->ep[0];
}
else
{
mptr = (SymboKT>*) (&s);
if (mptr->type() == 'S' && mptr->branches == 1
&& mptr->fac_exp[0] == T(l) && !mptr->ep[0]->branches)
mptr = mptr->ep[0];
if (mptr->branches == 1) sptr = s.ep[0];
}
}
// если s - произведение двух и более переменных
if (mptr->branches)
-с
if (mptr->branches == 1) // избегая z = a+b
{ // предполагая p->branches==l
for (i=0; i<ptr->branches; i++)
{
if (ptr->ep[i]->type() == 'P')
Глава 10. Листинги программ
■С
// выделяем память для записи индексов парных членов
int *index = new int [sptr->branches] ; assert (index != NULL") ■
// проверяем, является ли член парным & s;
// если так, запишем индекс
for (m=0; m<sptr->branches; m++)
{
for (j=0; j<ptr->ep[i]->branches; j++)
{
if (ptr->ep[i]->ep[j]->is_equal(sptr->ep[m])
&& ptr->ep[i]->fac_exp[j] == sptr->fac_exp[m])
{ index[m] = j ; break; }
}
// член не парный (используем index[0]=-l для индексации)
if (j == ptr->ep[i]->branches) { index[0] = -1; break; }
}
// если требуемый член парный
if (index[0] >= 0)
{
// если все члены парные, тогда коэффициент
// должен быть числовым значением
if (ptr->ep[i]->branches == sptr->branches)
{ result += Sum<T>(ptr->fac_exp[i]); }
else // коэффициент должен быть символьной переменной
{
// выделяем память под коэффициенты
Sum<T> tempsum(l,0);
Product<T> *р;
р = new Product<T>(ptr->ep[i]->branches-sptr->branches);
tempsum.ep[0] = p;
// выделяем члены, которые не принадлежат списку
// сравнения. Эти члены суть коэффициенты
found = 0;
for (m=0, k=0; k<ptr->ep[i]->branches; k++)
{
for(j=0; j<sptr->branches; j++)
{
if (index[j] == k) { found = 1; break; }
}
if (found) { found = 0; continue; }
ptr->ep[i]->ep[k]->copy(tempsum.ep[0]->ep+m);
tempsum.ep[0]->fac_exp[m] = ptr->ep[i]->fac_exp[k];
10.14. Класс Symbolic
563
tempsum.ep[0]->next_var[m] = ptr->ep[i]->next_var[к];
m++;
}
// добавим этот член к собираемому коэффициенту
result += Sum<T>(ptr->fac_exp[i]) * tempsum;
}
}
delete [] index;
}
}
}
lse // если s типа 'V или 'N'
if (! ptr->branches && ptr->is_equal(mptr)) result += T(l);
for (i=0; i<ptr->branches; i++)
{
// если выражение состоит из одного члена
if (ptr->ep[i]->type() != 'Р')
{
if (ptr->ep[i]->is_equal(mptr)) result+=Sum<T>(ptr->fac_exp[i]);
}
else
{
for (j=0; j<ptr->ep[i]->branches; j++)
{
// проверяем, существует ли парный член
if (ptr->ep[i]->ep[j]->is_equal(mptr) &&
(ptr->ep[i]->fac_exp[j] == T(l)))
{
Sum<T> tempsum(l,0);
Product<T> *p = new Product<T>(ptr->ep[i]->branches-l);
tempsum.ep[0] = p;
for (k=0; k<j; k++)
{
ptr~>ep [i]->ep [k]->copy(tempsum.ep[0] ->ep+k) ;
tempsum.ep[0]->fac_exp[k] = ptr->ep[i]->fac_exp[k];
tempsum.ep[0]->next_var[k] = ptr->ep[i]->next_var[k];
}
for (m=j, k=j+l; k<ptr->ep[i]->branches; k++, m++)
{
ptr->ep[i]->ep[k]->copy(tempsum.ep[0]->ep+m);
tempsum.ep[0]->fac_exp[m] = ptr->ep[i]->fac_exp[k];
564
Глава 10. Листинги программ
tempsum.ep[0]->next_var[m] = ptr->ep[i]->next_var[M
>
result += Sum<T>(ptr->fac_exp[i]) * tempsum;
break;
>
>
>
>
>
return result;
// возвращаем коэффициент члена - константы
template <class T> T Sum<T>::coeff(int) const
i
int i;
T result;
((Sum<T>*)this)->ShrinkO;
if (type() == 'В')
i
for (i=0; i<ep[0]->branches; i++)
if (ep[0]->ep[i] == oneQ) return ep[0]->f ac_exp[i] ;
>
else
i
for (i=0; i <branch.es; i++)
if (ep[i] == one())
i
result = fac_exp[i];
return result;
>
>
return T@);
// возвращаем коэффициент при s"n
// заметим, что s должен иметь typeQ == 'V
template <class T> Sum<T> Sum<T>::coeff(const Sum<T> fes.int n) const
i
if (n == 1) return coeff(s);
else if (n) // если n != 0,1
i
Sum<T> newsA,0);
Product<T> *p = new Product<T>(l); assert(p != NULL);
news.ep[0] = p;
s.copy(news.ep [0]->ep);
10.14. Класс Symbolic
news.ep[0]->fac_exp[0] = Т(п);
if (s.type() == 'V') news.ep[0]->next_var[0] = 1;
return coeff(news);
}
else // n == 0
{
int i, j ;
Symbol<T> *ptr, *sptr;
Sum<T> *temp;
Sum<T> result(T@));
// инициализация различных указателей: ptr, sptr
if (type() == 'В')
{
ptr = ep[0] ;
if (s.typeQ == 'B') sptr = s.ep[0];
else sptr = (Symbol<T>*)(&s);
if (sptr->type() == 'S' kk sptr->branches == 1
kk sptr->fac_exp[0] == TA)) sptr = sptr->ep[0];
}
else
{
ptr = (SymboKT>*)this;
if (s.typeQ == 'B') sptr = s.ep[0];
else sptr = (Symbol<T>*)(&s);
if (sptr->type() == 'S' kk sptr->branches == 1
kk sptr->fac_exp[0] == TA)) sptr = sptr->ep[0];
if (!(ptr->branches II ptr->is_equal(sptr)))
{
temp = (Sum<T>*)ptr;
result += *temp;
for (i=0; i<ptr->branches; i++)
{
if (ptr->ep[i]->type() != 'P')
{
if (! ptr->ep[i]->is_equal(sptr))
{
temp = (Sum<T>*)(ptr->ep[i]);
result += Sum<T>(ptr->fac_exp[i]) * *temp;
>
566
Глава 10. Листинги программ
else
{
// проверяем, имеется ли s в выражении
for (j=0; j<ptr->ep[i]->branches; j++)
if (ptr->ep[i]->ep[j]->is_equal(sptr)) break;
// если это не так, тогда этот член есть часть коэффициент
if (j == ptr->ep[i]->branches)
{
Sum<T> tempsum(l,0);
ptr->ep[i]->copy(tempsum.ep);
result += Sum<T>(ptr->fac_exp[i]) * tempsum;
>
>
>
return result;
>
// объявление зависимости переменных
template <class T> void Sum<T>::depend(const Sum<T> &x)
{ if (!deplist.is_Include((Sum<T>*)&x)) deplist.add((Sum<T>*)&x); }
// объявление независимости переменных
template <class T> void Sum<T>::nodepend(const Sum<T> &x)
{
ListIterator<Sum<T>*> m((List<Sum<T>*>&)deplist);
for (m.initO; !m; ++m)
if (m() == &x)
{
m.delete_Current();
return;
>
>
// проверка скрытной зависимости переменных
template <class T>
int implicit_depend(Symbol<T> **s,Symbol<T> *x,int n)
{
int i;
for(i=0;i<n;i++)
{
if (s[i] == x) return 1;
>
for(i=0;i<n;i++)
{
// проверка скрытной зависимости переменных и сумм
10.14. Класс Symbolic
56
if Us[i]->typeO=='S') I |(s[i]->type()=='B') I I (s[i]->type()==>V ))
{
if (((Sum<T>*)s[i])->isdepend(*((Sum<T>*)x))) return 1;
}
else if (s[i]->type() != 'N')
{
// проверка явной или скрытной зависимости переменных
// аргументов функции
if(implicit_depend(s[i]->ep,x,s[i]->branches+(s[i]->branches==0))]
return 1;
>
>
return 0;
}
// проверка зависимости переменных
template <class T> int Sum<T>::isdepend(const Sum<T> &x) const
{
if (type() == 'N') return 0;
if (this == &x) return 1;
if (deplist.is_Include((Sum<T>*)&x)) return 1;
ListIterator<Sum<T>*> m((List<Sum<T>*>&)deplist);
for (m.initO; !m; ++m)
if (m()->isdepend(x)) return 1;
// проверка скрытной зависимости
// или скрытной зависимости аргументов функции
if ((branches)I I((branches==0)&&islower(type())))
return implicit_depend(ep,(Symbol<T>*)&x,branches+(branches==0))
return 0;
}
// проверка, является ли переменная числовым значением
template <class T> int Sum<T>::is_Number() const
{
Symbol<T> *ptr;
if (type() == 'В') ptr = ep[0];
else ptr = (Symbol<T>*)this;
if (ptr->branches == 1 kk ptr->ep[0]->type() == 'N') return 1;
return 0;
}
// экспоненциальная функция
template <class T> Sum<T> exp(const Sum<T> &s)
{
Sum<T> r('e',char());
568
Глава 10. Листинги программ
((Sum<T>&)s).Shrink();
if (s.typeQ == 'В') s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
{
r.ep[0] = new Sum<T>(l,0); assert(r.ep[0] != NULL);
r.ep[0]->ep[0] = (Symbol<T>*)&s;
r.ep[0]->next_var[0] = l;
>
return r;
// функция гиперболического синуса
template <class T> Sum<T> sinh(const Sum<T> &s)
{
Sum<T> r('j',char());
((Sum<T>&)s) .ShrinkO;
if (s.typeO == 'B') s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
{
r.ep[0] = new Sum<T>(l,0); assert(r.ep [0] != NULL);
r.ep[0]->ep[0] = (Symbol<T>*)&s;
r.ep[0]->next_var[0] = l;
>
return r;
>
// функция гиперболического косинуса
template <class T> Sum<T> coshCconst Sum<T> &s)
{
Sum<T> r('w',charO) ;
((Sum<T>&)s) .ShrinkO;
if (s.typeO == 'B') s.ep[0]->copy(r .ep) ;
else if (s.branches) s.copy(r.ep);
else
{
r.epCO] = new Sum<T>(l,0); assert(r.ep[0] != NULL);
r.ep[0]->ep[0] = (Symbol<T>*)&s;
r.ep[0]->next_var[0] = l;
>
return r;
// функция синуса
template <class T> Sum<T> sin(const Sum<T> &s)
10.14. Класс Symbolic
Sum<T> r('z' ,char()) ;
((Sum<T>&)s) .ShrinkO ;
if (s.type() == 'B') s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
{
r.ep[0] = new Sum<T>(l,0); assert(r.ep[0] != NULL);
r.ep[0]->ep[0] = (Symbol<T>*)&s;
r.ep[0]->next_var[0] = 1;
>
return r;
// функция косинуса
template <class T> Sum<T> cos(const Sum<T> &s)
{
Sum<T> r('c',char());
((Sum<T>&) s) . ShrinkO ;
if (s.type() == 'B') s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
{
r.ep[0] = new Sum<T>(l,0); assert(r.ep[0] != NULL);
r. ep [0]->ep [0] = (SymboKT>*)&s;
r.ep[0]->next_var[0] = l;
>
return r;
// функция логарифма
template <class T> Sum<T> ln(const Sum<T> &s)
{
Sum<T> r('l',char());
((Sum<T>&)s) .ShrinkO ;
if (s.typeO == 'B') s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
{
r.ep[0] = new Sum<T>(l,0); assert(r.ep[0] != NULL);
r.ep[0]->ep[0] = (Symbol<T>*)&s;
r.ep[0]->next_var[0] = l;
>
return r;
570
Глава 10. Листинги программ
// функция квадратного корня
template <class T> Sum<T> sqrt(const Sum<T> &s)
{
Sum<T> r('q',char());
((Sum<T>&)s).Shrink();
if (s.type() == 'B') s.ep[0]->copy(r.ep);
else if (s.branches) s.copy(r.ep);
else
{
r.ep[0] = new Sum<T>(l,0); assert(r.ep[0] != NULL);
r.ep[0]->ep[0] = (Symbol<T>*)&s;
r.ep[0]->next_var[0] = l;
>
return r;
>
// возведение суммы s в степень п
template <class T> Sum<T> power(const Sum<T> &s, T n)
{
static T zero@);
Sum<T> r(l,0);
if (n != zero)
{
Product<T> *p = new Product<T>A);
r.ep[0] = p;
// сначала упрощаем выражение перед возведением в ст<
((Sum<T>&)s).Shrink();
if (s.typeO == 'В') s.ep[0]->copy(r.ep[0]->ep);
else s.copy(r.ep[0]->ep);
if (s.typeO == 'V') r.ep[0]->next_var[0] = l;
r.ep[0]->fac_exp[0] = n;
>
else
{
r.ep[0] = s.one();
r.fac_exp[0] = TA);
>
return r;
>
// возводим T x в степень п
template <class T> T power(T x, T n)
10.14. Класс Symbolic
571
int N;
T Y(l);
if (n < T@))
{
x = T(l)/x;
N = int(-n);
>
else
N = int(n);
if (N==0) return T(l);
while(l)
{
if (№/„2)
{
У = Y * x;
N /= 2;
if (N==0) return Y;
>
else N /= 2;
x = x * x;
>
// частная производная s no t
template <class T> Sum<T> df(const Sum<T> &s, const Sum<T> &T) I
{
Sum<T> resultA,0);
SymboKT> *tt;
((Sum<T>&)s).Shrink();
if (t.typeQ == 'B') tt = t.ep[0];
else tt = (SymboKT>*)&t;
if (tt->type()=='S' kk tt->branches == 1 kk tt->fac_exp[0]==T(l))
tt = tt->ep[0];
s.diff(result.ep.tt);
result.Shrink();
return result;
}
// n-я частная производная s no t
template <class T> Sum<T> df(const Sum<T> &s,const Sum<T> fet.int n)
{
Sum<T> result(s);
for (int i=0; i<n; i++) result = df(result,t);
572
Глава 10. Листинги программ
return result;
>
// частный интеграл s no t
template <class Т> Sum<T> Int(const Sum<T> &s, const Sum<T> &T)|
{
Sum<T> resultA,0);
SymboKT> *tt;
((Sum<T>&)s) .ShrinkO ;
if (t.typeO == 'B') tt = t.ep[0];
else tt = (SymboKT>*)&t;
if (tt->type()=='S' kk tt->branches==l kk tt->fac_exp[0]==T(l))
tt = tt->ep[0];
s.integrate(result.ep,tt);
result.ShrinkO ;
return result;
// n-й частный интеграл s no t
template <class T> Sum<T> Int(const Sum<T> &s,const Sum<T> &t,int n)
{
Sum<T> result(s);
for (int i=0; i<n; i++)
result = Int(result,t);
return result;
>
// установка численного значения переменной
template <class T> void Sum<T>::set(const T num)
{
if (type() == 'B') { (*(Sum<T>*)ep[0]).set(num); return; >
if (typeQ == 'S' kk branches==l kk fac_exp[0]==T(l))
{ (*(Sum<T>*)ep[0]).set(num); return; }
is_Set = 1;
((Variable<T>*)data)->set(num);
>
// присвоение выражения другому
template <class T> int Sum<T>::put(const Sum<T> &sl,const Sum<T>&s2)
{
int i, flag=0;
SymboKT> *ptr;
if (sl.typeO == 'B') return put(*(Sum<T>*)(si.ep[0]),s2);
if (type() == 'В') ptr = ep[0];
else ptr = (SymboKT> *)this;
10.14. Класс Symbolic
573
((Sum<T>&)sl) .ShrinkO ;
if (sl.typeO == 'S' kk si.branches == 1)
{
for (i=0; i<ptr->branches; i++)
{
flag += ptr->ep[i]->replacel(sl.ep[0] ,(SyrnboKT>*) (&s2)) ;
if (ptr->ep[i]->is_equal(si.ep[0]))
{ s2.copy(ptr->ep+i); flag++; >
>
>
else // (si.branches == 0)
i
for (i=0; i<ptr->branches; i++)
{
flag += ptr->ep[i]->replacel((Symbol<T>*)(&sl) ,
(SymboKT>*)(&s2));
if (ptr->ep[i]->is_equal((Symbol<T>*)(&sl)))
{ s2.copy(ptr->ep+i); flag++; >
>
>
ShrinkO;
return flag;
>
// возвращаем численное значение переменной
template <class T> T Sum<T>::nvalue() const
{
return nvalQ ;
template <class T> T Sum<T>::nval() const
{
if (typeQ == 'B') return (*(Sum<T>*)ep[0]).nval();
if (typeQ == 'S' kk branches==l kk fac_exp[0]==T(l))
return (*(Sum<T>*)ep[0]).nval() ;
int i;
T s@);
if (!branches)
{
if (islower(type())) // это - функция
return ((Function<T>*)data)->f(((Sum<T>*)ep[0])->nval());
else
return ((Magnitude<T>*)data)->val();
>
else
574
Глава 10. Листинги программ
for (i=0; Kbranches; i++)
s += fac_exp[i] * ((Sum<T>*)ep[i])->nval();
return s;
}
// возвращаем значение выражения
template <class T> Sum<T> Sum<T>::value() const
■c
return val();
}
// изменение используемой переменной
template <class T> void Sum<T>::clear()
■c
if (ep) { removeBO; ep[0]->deleteAHNodes(); }
delete [] ep; delete [] fac_exp;
delete [] next_var; deplist .deleteAHNodesO ;
branches = 0;
ep = NULL; fac_exp = NULL; next_var = NULL;
is_Bound = 0; is_Set = 0;
}
// раскрытие скобок выражения
template <class T> void Sum<T>::expand()
■c
int i, flag;
Shrink();
do-t
do{
flag = 0;
for(i=0; i<branches; i++) flag += ep[i]->mxpand(ep+i);
if(!flag) for(i=0;i<branches;i++)flag += ep[i]->dxpand(ep^
Shrink();
} while (flag);
} while (gatherO);
Shrink();
}
// унарный плюс
template <class T> Sum<T> Sum<T>::operator + () const
{ return *this; }
// унарный минус
template <class T> Sum<T> Sum<T>::operator - () const
i return *this * T(-l); }
10.14. Класс Symbolic
с
template <class T> Sum<T> Sum<T>::operator += (const Sum<T> &s)
{ return *this = *this + s; }
template <class T> Sum<T> Sum<T>::operator -= (const Sum<T> &s)
{ return *this = *this - s; }
template <class T> Sum<T> Sum<T>::operator *= (const Sum<T> &s)
{ return *this = *this * s; }
template <class T> Sum<T> Sum<T>::operator /= (const Sum<T> &s)
{ return *this = *this / s; }
template <class T> Sum<T> operator + (const Sum<T> &sl,
const Sum<T> &s2)
{
Sum<T> rB,0);
if (sl.typeO == 'B') sl.ep[0]->copy(r.ep);
else sl.copy(r.ep);
if (s2.type() == 'B') s2.ep[0]->copy(r.ep+l);
else s2.copy(r.ep+l);
if (sl.typeO == 'V') r.next_var[0] = l;
if (s2.type() == 'V') r.next_var[l] = 1;
return r;
}
template <class T> Sum<T> operator - (const Sum<T> &sl,
const Sum<T> &s2)
{
Sum<T> rB,0);
if (sl.typeO == 'B') sl.ep[0]->copy(r.ep) ;
else si.copy(r.ep) ;
if (s2.type() == 'B') s2.ep[0]->copy(r.ep+l);
else s2.copy(r.ep+l);
if (sl.typeO == 'V') r.next_var[0] = l;
if (s2.type() == 'V') r.next_var[l] = 1;
r.fac_exp[l] = T(-l);
return r;
template <class T> Sum<T> operator * (const Sum<T> &sl,
const Sum<T> &s2)
■c
Sum<T> r(l,0);
Product<T> *p;
576
Глава 10. Листинги программ
р = new Product<Т>B); assert(p != NULL);
r.ep[0] = p;
if (sl.typeO == 'B') sl.ep[0]->copy(r.ep[0]->ep);
else sl.copy(r.ep[0]->ep);
if (s2.type() == 'B') s2.ep[0]->copy(r.ep[0]->ep+l);
else s2.copy(r.ep[0]->ep+l);
if (sl.typeO == 'V') r.ep[0]->next_var[0] = l;
if (s2.type() == 'V') r.ep[0]->next_var[l] = l;
return r;
template <class T> Sum<T> operator / (const Sum<T> &sl,
const Sum<T> &s2)
{
((Sum<T>fc)s2).ShrinkО;
if (s2.branches == 1 && s2.ep[0] == s2.zero())
■c
cerr << "Division by zero" << endl;
exit(l) ;
}
Sum<T> r(l,0);
Product<T> *p = new Product<T>B);
r.ep[0] = p;
if (sl.typeO == 'B') sl.ep[0]->copy(r.ep[0]->ep);
else sl.copy(r.ep[0]->ep);
if (s2.type() == 'B') s2.ep[0]->copy(r.ep[0]->ep+l);
else s2.copy(r.ep[0]->ep+l);
if (sl.typeO == 'V') r.ep[0]->next_var[0] = l;
if (s2.type() == 'V') r.ep[0]->next_var[l] = l;
r.ep[0]->fac_exp[l] = T(-1);
return r;
}
template <class T> int operator == (const Sum<T> &sl,
const Sum<T> &s2)
■C
((Sum<T>&)sl) .ShrinkO ;
((Sum<T>&)s2) .ShrinkO ;
return si.is_equal((Symbol<T>*)&s2) ;
}
10.14. Класс Symbolic
577
template<class T>int operator != (const Sum<T> fesl,const Sum<T> &s2)
{ return !(sl==s2); }
template<class T>ostream & operator << (ostream &os,const Sum<T> &s)
{
SymboKT> *ptr;
((Sum<T>&)s) .ShrinkO;
if (s.typeO == 'B') ptr = s.ep[0];
else ptr = (SymboKT>*)&s;
// печатаем содержимое, если это - концевой лист
if (!ptr->branches)
{
((Sum<T>*)ptr)->data->oprint(os);
if (islower(((Sum<T>*)ptr)->data->type()))
■С
if (ptr->ep[0]->branches) ptr->ep[0]->oprint(os);
else if ((((Sum<T>*)ptr)->data->typeO == 'd')ll
(((Sum<T>*)ptr)->data->type() == 'i'))
{
os << "("; ptr->ep[0]->oprint(os); os << ",";
ptr->ep[l]->oprint(os); os << ")";
}
else
-t os « "("; ptr->ep[0]->oprint(os); os « ")"; }
}
}
else
{
//не печатаем + , т.к. это - первый член
if(ptr->fac_exp[0]==T(-l)) {os << "-";ptr->ep[0]->oprint(os);}
else if (ptr->fac_exp[0] != T(l))
{
os << ptr->fac_exp[0];
if (ptr->ep[0] != ptr->one())
{ os << "*"; ptr->ep[0]->oprint(os); }
}
else ptr->ep[0]->oprint(os);
// печатаем остальные члены
for (int i=l; i<ptr->branches; i++)
{
if (ptr->fac_exp[i] > T@))
{
OS << "+";
if (ptr->fac_exp[i] == T(l)) ptr->ep[i]->oprint(os);
578
Глава 10. Листинги программ
else
{
os << ptr->fac_exp[i];
if (ptr->ep[i] != ptr->one())
■i os << "*"; ptr->ep[i]->oprint(os) ; }
}
}
else
■C
if (ptr->fac_exp[i] == T(-l))
{ os « "-"; ptr->ep[i]->oprint(os); }
else
{
os << ptr->fac_exp[i];
if (ptr->ep[i] != ptr->one())
{ os << "*"; ptr->ep[i]->oprint(os); }
}
}
}
}
return os;
}
template <class T> istream & operator » (istream &s, Sum<T> &)
i. return s; }
// выводим множитель во внешний уровень, возводим в соотв. степень
// для сохранения представления канонической формы
template <class T> int Sum<T>::shrink_l()
■С
int i, flag=0, k;
SymboKT> *ptr;
for (i=0; i<branches; i++)
■C
if (ep[i]->type() == '?')
{
if (ep[i]->branches == 1 && ep[i]->fac_exp[0] == T(D)
■C
ptr = ep[i]->ep[0];
next_var[i] = ep[i]->next_var[0];
ep[i]->deleteOneNode();
ep[i] = ptr;
flag++;
}
else
for (k=0; k < ep[i]->branches; k++)
10.14. Класс Symbolic
579
if (ep[i]->ep[k]->type() == 'S' &&
ep[i]->ep[k]->branches == 1 &&
ep[i]->ep[k]->fac_exp[0] != T(l))
{
flag++;
// выводим множитель во внешний уровень
f ac_exp [i] *=power (ер [i] ->ер [к] ->f ac_exp [0] ,
ер [i] ->f ас_ехр [к] ) ;
ep[i]->ep[k]->fac_exp[0] = T(l);
}
}
}
}
for(i=0; Kbranches; i++) flag += ep[i]->shrink_l();
return flag;
}
//удаляем дополнительные узлы Sum: нижний уровень последовательности
//уровней узла Sum перемещается на один уровень вверх
template <class T> int Sum<T>::shrink_2()
{
int i, k, j, n, flag=0;
SymboKT> **ept;
T *fac_expt;
int *next_vart;
for (i=0; i<branches; i++)
{
if (ep[i]->type() == 'S')
{
n = branches - 1 + ep[i]->branches;
ept = new Symbol<T>*[n]; assert(ept != NULL);
fac_expt = new T[n]; assert(fac_expt != NULL);
next_vart = new int[n]; assert(next_vart != NULL);
// копируем узлы, которые не затронуты
for (k=0; k<i; k++)
{
ept [k] = ep [k] ;
fac_expt[k] = fac_exp[k];
next_vart[k] = next_var[k];
}
// переносим узел Sum на один уровень вверх
for (j=i, k=0; k < ep[i]->branches; k++, j++)
580
Глава 10. Листинги программ
{
ept[j] = ep[i]->ep[k] ;
fac_expt[j] = ep[i]->fac_exp[k] * fac_exp[i];
next_vart[j] = ep[i]->next_var[k];
}
// копируем узлы, которые не затронуты
for (k=i+l; k<branches; k++, j++)
{
ept[j] = ep[k] ;
fac_expt[j] = fac_exp[k];
next_vart[j] = next_var[k];
}
branches = n;
ep[i]->deleteOneNode();
delete [] ep; delete [] fac_exp; delete [] next_vai
ep = ept;
fac_exp = fac_expt;
next_var = next_vart;
flag++;
}
}
for (i=0; Kbranches; i++) flag += ep[i]->shrink_2();
return flag;
}
// удаляем нулевые слагаемые
template <class T> int Sum<T>::shrink_3(Symbol<T> **)
{
int i, flag = 0;
// for-цикл выполняется в обратном порядке, потому что
// remove(j) уменьшает количество ветвей на 1
for (i=branches-l; i>=0; i—)
{
if (fac_exp[i] == T@))
{
ep [i] ->deleteAHNodes () ;
ep[i] = zero(); fac_exp[i] = T(l);
next_var[i] = 1; flag++;
}
if (ep[i] == zero())
{
if (branches > 1)
{ remove(i); flag++; }
10.14. Класс Symbolic
581
else if (branches == 1 && fac_exp[i] != T(D)
{ fac_exp[i] = T(l); flag++; }
}
}
for (i=0; i<branches; i++) flag += ep[i]->shrink_3(ep+i);
return flag;
}
// суммируем Numbers (тип='Ы') из Sum
template <class T> int Sum<T>::shrink_4()
{
int i, k, flag=0;
for (i=0; i<branches; i++) flag += ep[i]->shrink_4();
// находим первый узел типа 'N'
for (i=0; i<branches; i++)
if (ep[i]->type() == 'N') break;
// проверяем остальные узлы; если есть узлы типа 'N',
// добавляем к первому узлу
for (k=i+l; k<branches; k++)
if (ep[k]->type() == 'N')
{
flag++;
fac_exp[i] = fac_exp[i] * ((Number<T>*)
((Sum<T>*)ep[i])->data)->val() + fac_exp[k]*
((Number<T>*)((Sum<T>*)ep[k])->data)->val();
ep[i] = one();
remove(k); k—;
}
return flag;
}
template <class T> int Sum<T>::shrink_5()
{
int i, flag=0;
for (i=0; i<branches; i++) flag += ep[i]->shrink_5();
return flag;
}
// закрытые методы-функции
template <class T> void Sum<T>::diffRules(Symbol<T>**s, char t)const
{
switch (t)
{
582
Глава 10. Листинги программ
case 'e': // (d/dx)exp(u) = exp(u) (du/dx)
(*s) = new Sum<T>('e',char()); assert((*s) != NULL)•
ep[0]->copy((*s)->ep);
break;
case 'j': // (d/dx)sinh(u) = cosh(u) (du/dx)
(*s) = new Sum<T>('w',char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case 'w': // (d/dx)cosh(u) = sinh(u) (du/dx)
(*s) = new Sum<T>('j',char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case 'z': // (d/dx)sin(u) = cos(u) (du/dx)
(*s) = new Sum<T>('c',char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case 'c': // (d/dx)cos(u) = -sin(u) (du/dx)
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Sum<T>('z',char());
(*s)->fac_exp[0] = T(-1);
ep [0]->copy((*s)->ep[0]->ep);
break;
case '1': // (d/dx)ln(u) = l/(u) (du/dx)
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(l);
assert((*s)->ep[0] !=NULL);
(*s)->ep[0]->fac_exp[0] = T(-1);
ep[0]->copy((*s)->ep[0]->ep);
break;
case 'q': // (d/dx)sqrt(u) = l/B*sqrt(u)) (du/dx)
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(l);
assert( (*s)->ep[0] != NULL);
(*s)->ep[0]->ep[0] = new Sum<T>A,0);
assert( (*s)->ep[0]->ep[0] != NULL);
(*s)->ep[0]->ep[0]->ep[0] = new Sum<T>('q',char());
assert((*s)->ep[0]->ep[0]->ep[0] != NULL);
(*s)->ep[0]->fac_exp[0] = T(-1);
(*s)->ep[0]->ep[0]->fac_exp[0] = TB);
ep[0]->copy((*s)->ep[0]->ep[0]->ep[0]->ep);
break;
default : ;
}
}
template <class T> void Sum<T>::IntRules(Symbol<T> **s,char t) const
10.14. Класс Symbolic
583
{
switch (t)
{
case 'e': // Int(exp(u),y) = exp(u)
copy(s);
break;
case 'j': // Int(sinh(u),u) = cosh(u)
(*s) = new Sum<T>('w',char()); assert((*s) !=NULL);
ep[0]->copy((*s)->ep);
break;
case 'w': // Int(cosh(u),u = sinh(u)
(*s) = new Sum<T>('j',char()); assert((*s) !=NULL);
ep[0]->copy((*s)->ep);
break;
case 'z': // Int(sin(u),u) = -cos(u)
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Sum<T>('c»,char());
(*s)->fac_exp[0] = T(-1);
ep[0]->copy((*s)->ep[0]->ep);
break;
case 'c': // Int(cos(u),u) = sin(u)
(*s) = new Sum<T>('z',char()); assert((*s) != NULL);
ep[0]->copy((*s)->ep);
break;
case '1': // Int(ln(u),u) = u*ln(u)-u
(*s) = new Sum<T>B,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>B):
assert((*s)->ep[0] != NULL);
(*s)->ep[0]->ep[l] = new Sum<T>('l',char());
assert((*s)->ep[0] !=NULL);
(*s)->fac_exp[l] = T(-l);
ep[0]->copy((*s)->ep[0]->ep);
ep[0]->copy((*s)->ep[0]->ep[1]->ep);
ep[0]->copy((*s)->ep+l);
break;
case 'q>: // Int(sqrt(u),u) = B/3)*u*C/2) = B/3)*(sqrt(u))
(*s) = new Sum<T>(l,0); assert((*s) != NULL);
(*s)->ep[0] = new Product<T>(l);
assert((*s)->ep[0] !=NULL);
(*s)->fac_exp[0]=TB)/TC);
(*s)->ep[0]->fac_exp[0] = TC)/TB);
ep[0]->copy((*s)->ep[0]->ep);
break;
default : ;
}
584
Глава 10. Листинги программ
// определение класса Product
// оператор * по умолчанию коммутативен
template <class T> int Product<T>::Comm = 1;
template <class T> Product<T>: -.Product(int i) : Symbol<T>(i) {}
template <class T>
Product<T>: .-Product(int i,Symbol<T> **newep,T *new_fe,int *new_n
: Symbol<T>(i, newep, new_fe, new_nx) {}
template <class T> Product<T>::"Product()
{ deleteOneNode(); }
// возвращает тип Product
template <class T> char Product<T>::type() const
{ return 'P'; }
// печатаем численное значение выражения (возвращает тип Sum<T>)
template <class T> Sum<T> Product<T>::val() const
{
Sum<T> s(T(D);
for (int i=0; i<branches; i++)
s *= power(ep[i]->val(), fac_exp[i]);
return s;
// печатаем численное значение выражения (возвращает тип Т)
template <class T> T Product<T>::nval() const
■С
Т s(l);
for (int i=0; i<branches; i++)
s *= power(ep[i]->nval(), fac_exp[i]);
return s;
// заменяем выражение 1 на выражение 2
template<class T>int Product<T>::replacel(Symbol<T>*sl,Symbol<T>*s2)
{
int i, index, flag=0;
for (i=0; i<branches; i++) flag += ep[i]->replacel(sl,s2);
if (sl->branches >= 1)
{
index = equal_index(sl);
if (index >= 0)
{
s2->copy(ep+index);
fac_exp[index] = T(l);
10.14. Класс Symbolic
585
flag++;
}
}
else
{
for (i=0; i<branches; i++)
if (ep[i]->is_equal(sl))
{ s2->copy(ep+i); flag++; }
}
return flag;
}
// возвращает индекс равной компоненты
// возвращает (-1), если такого компонента не найдено
template <class T> int Product<T>::equal_index(Symbol<T> *s)
{
int i.j.k;
if (Comm) // коммутативная алгебра
{
if (branches >= s->branches)
■C
int *ip, // ip[] используется как индикатор пройденного узла
count=0; // число парных компонент
ip = new int[branches]; assert(ip != NULL);
for (i=0; i<branches; i++) ip[i] = 0;
for (j=0; j<branches; j++)
{
for (i=0; i<s->branches; i++)
if(s->ep[i]->is_equal(ep[j]) fcfc s->fac_exp[i]==fac_exp[j])
{ ip[count] = j; ++count; break; }
}
if(count == s->branches)
i
for(count—; count>0; count—) remove(ip[count]);
return ip[0];
}
}
return (-1);
}
else // некоммутативная алгебра
{
for (j=0; j<branches-s->branches+l; j++)
{
for (i=0, k=j; i<s->branches; i++, k++)
if(!(s->ep[i]->is_equal(ep[k]) fefe s->fac_exp[i]==fac_exp[k]))
586
Глава 10. Листинги программ
break;
if(i == s->branches)
{
for (i—, к—; i>0; i—, к—) remove(k);
return k;
}
>
return (-1);
}
}
// проверка двух выражений на равенство
template <class T> int Product<T>::is_equal(Symbol<T> *s) const
{
if (Comm) // для коммутативной алгебры
■С
int i, k;
int *ip; // ip[]используется как индикатор пройденного узла
// узел произведения должен быть равен произведению узлов
if (s->type() != 'P') return 0;
// если s эквивалентен ему, возвращаем 1
if (s == (Symbol<T>*)this) return 1;
// т.к. s не эквивалентен ему (кто-то из них есть
// концевой лист), возвращаем 0
if (!s->branches || Ibranches) return 0;
// если ветви не равны, возвращаем 0
if (branches != s->branches) return 0;
// ветви равны
ip = new int[branches]; assert(ip != NULL);
for (i=0; i<branches; i++) ip[i] = 0;
// проверяем, все ли поддеревья равны
for (i=0; i<branches; i++)
■С
for (k=0; k<branches; k++)
i
if (ep[i]->is_equal(s->ep[k]) fcfc
fac_exp[i] == s->fac_exp[k] fcfc ip[k] == 0)
{ ip[k] = 1; break; }
}
10.14. Класс Symbolic
587
// если нет подходящих членов, возвращаем 0
if (к == branches) return 0;
}
delete [] ip;
return 1;
}
else // для некоммутативной алгебры
{
int i;
// узел произведения должен быть равен произведению узлов
if (s->type() != 'P') return 0;
// если s эквивалентен ему, возвращаем 1
if (s == (SymboKT>*)this) return 1;
// т.к. s не эквивалентен ему (кто-то из них есть
// концевой лист), возвращаем 0
if (!s->branches II !branches) return 0;
// если ветви не равны, возвращаем 0
if (branches != s->branches) return 0;
// ветви равны
// проверяем, все ли поддеревья равны
for (i=0; i<branches; i++)
{
if(!(ep[i]->is_equal(s->ep[i]))I|fac_exp[i]!= s->fac_exp[i])
return 0;
}
return 1;
}
}
// Собираем члены, которые имеют ту же самую переменную
template <class T> int Product<T>::gather()
{
if (Comm) // для коммутативной алгебры
{
int i, j, flag;
flag = 0;
for(i=0; i<branches; i++) flag += ep[i]->gather();
// сравниваем различные поддеревья; если равны, собираем их
for(i=0; i<branches; i++)
for(j=i+l; j<branches; j++)
588
Глава 10. Листинги программ
{
if (ep[i]->is_equal(ep[j]))
{
flag++;
fac_exp[i] += fac_exp[j]; // суммируем множители
// если fac_exp[] == 0, потом прибавим
if (fac_exp[i] == Т@))
if (branches > 2)
{ remove(j); remove(i); j=i;}
else // branches==2,
// удаляем член и открываем узел опе()
{
remove(j); j=i;
ер[0] = one О ;
fac_exp[0] = TA);
}
else { remove(j); j=i; }
}
else if (!ep[i]->branches fefe !ep[j]->branches)
{
if(islower(ep[i]->type())fcfcep[i]->type()==ep[j]->type())
flag += funcSimp(i,j);
}
}
return flag;
}
else // для некоммутативной алгебры
{
int i, j, flag;
flag = 0;
for(i=0; i<branches; i++) flag += ep[i]->gather();
// сравниваем различные поддеревья; если равны, собираем их
for(i=0; i<branches; i++)
if ((j=i+l) < branches)
■C
if (ep[i]->is_equal(ep[j]))
■C
flag++;
fac_exp[i] += fac_exp[j]; // суммируем множители
// если fac_exp[] == , потом добавим
if (fac_exp[i] == T@))
if (branches > 2)
{ remove(j); remove(i); }
10.14. Класс Symbolic
589
else // branches == 2,
// удаляем член и открываем узел опе()
■С
remove(j);
ер[0] = one(); assert(ер[0] != NULL);
fac_exp[0] = TA);
>
else remove(j);
>
else if (!ep[i]->branches && !ep[j]->branches)
i
if(islower(ep[i]->type())fcfcep[i]->type()==ep[j]->type())
flag += funcSimp(i,j);
}
>
return flag;
}
}
// копируем поддерево отсюда в sp
template <class T> void Product<T>::copy(Symbol<T> **sp) const
{
int i;
*sp = new Product<T>(branches) ; assert(*sp != NULL);
for (i=0;i<branches;i++) (*sp)->fac_exp[i] = fac_exp[i];
for (i=0;i<branches;i++) (*sp)->next_var[i] = next_var[i];
for (i=0;i<branches;i++) ep[i]->copy((*sp)->ep+i);
}
// выводим алгебраическое выражения
template <class T> void Product<T>::oprint(ostream fcos) const
{
// не выводим * , т.к. зто - первый член
ер[0]->oprint(os);
if (fac_exp[0] != TA)) os « "-(" « fac_exp[0] « ")";
// выводим остальные члены
for(int i=l; i<branches; i++)
{
os « "*";
ep[i]->oprint(os);
if (fac_exp[i] != T(D) os « "-(" « fac_exp[i] « ")";
}
}
// удаляем только этот узел
template <class T> void Product<T>::deleteOneNode()
590
Глава 10. Листинги программ
{
delete [] ер; delete [] fac_exp; delete [] next_var;
}
// удаляем все узлы, исходящие из этого
template <class T> void Product<T>:: deleteAHNodes ()
{
if (branches)
{
for (int i=0; i<branches; i++)
if (!next_var[i]) ep[i]->deleteAHNodes() ;
deleteOneNode();
}
}
// раскрытие скобок в биноме/мультиноме
template <class T> int Product<T>::mxpand(Symbol<T> **f)
{
if (Comm) // для коммутативной алгебры
{
int i, flag=0, m;
(*f) = new Product<T>(branches, ep, fac_exp, next_var);
assert(*f != NULL);
for (i=0; i<branches; i++)
{
if (ep[i]->type() == 'S' fcfc ep[i]->branches > 1
fcfc fac_exp[i] > T(l)
fcfc double(fac_exp[i]) == int(double(fac_exp[i])))
i
flag++;
int k, n,
newbranches = int(fac_exp[i]) + 1,
lastindex = newbranches - 1,
bcenter = int(fac_exp[i]) » 1;
T *bcoeff = new T[newbranches-2];assert(bcoeff != NULL);
// вычисляем биномиальный коэффициент
bcoeff[0] = fac_exp[i];
for (k=(int)fac_exp[i]-l,n=2,m=l;m<bcenter;m++,k—,n++)
bcoeff[m] = bcoeff[m-l]*T(k)/T(n);
for (k=0, m=newbranches-3; m>=bcenter; m—, k++)
bcoeff[m] = bcoeff[k];
// открытие новых узлов для раскрытого выражения
(*f)->ep[i] = new Sum<T>(newbranches,0);
10.14. Класс Symbolic
591
assert((*f)->ep[i] !=NULL);
(*f)->fac_exp[i] = T(l);
Sum<T> *suml = new Sum<T>(l,0);
Sum<T> *sum2 = new Sum<T>(ep[i]->branches-l,0) ;
assert(suml != NULL); assert(sum2 != NULL);
// если раскрытое выражение содержит более двух членов,
// тогда группируем выражения в 1 член в порядке
// раскрытия бинома
if (ер [i]->branch.es > 2)
■С
suml->ep[0] = ep[i]->ep[0];
suml->fac_exp[0] = ep[i]->fac_exp[0];
for (m=l; m<ep[i]->branches; m++)
•c
k = m-1;
sum2->ep[k] = ep [i]->ep [m] ;
sum2->fac_exp[k] = ep[i]->fac_exp[m];
}
}
else // с обоих концов пусто
■С
suml->ep[0] = ep[i]->ep[0];
suml->fac_exp[0] = ep[i]->fac_exp[0];
sum2->ep[0] = ep[i]->ep[l] ;
sum2->fac_exp[0] = ep[i]->fac_exp[l];
}
// раскрытие бинома
// для нулевого члена
(*f)->ep[i]->fac_exp[0] = TA);
(*f)->ep[i]->ep[0] = new Product<T>(l);
assert((*f)->ep[i]->ep[0] != NULL);
suml->copy((*f)->ep[i]->ep[0]->ep);
(*f)->ep[i]->ep[0]->fac_exp[0] = fac_exp[i];
// для члена с последним индексом
(*f)->ep[i]->fac _ехр[lastindex] = T(l);
(*f)->ep[i]->ep[lastindex] = new Product<T>A);
assert((*f)->ep[i]->ep[lastindex] != NULL);
sum2->copy((*f)->ep[i]->ep[lastindex]->ep);
(*f)->ep[i]->ep[lastindex]->fac_exp[0] = fac_exp[i];
// все другие члены
592
Глава 10. Листинги программ
for (m=l; m<lastindex; m++)
{
// коэффициент произвольного члена
(*f)->ep[i]->fac_exp[m] = bcoeff[m-l];
(*f)->ep[i]->ep[m] = new Product<T>B);
assert((*f)->ep[i]->ep[m] != NULL);
suml->copy((*f)->ep[i]->ep[m]->ep) ;
sum2->copy((*f)->ep[i]->ep[m]->ep+l);
// показатель произвольного члена
(*f)->ep[i]->ep[m]->fac_exp[0] = fac_exp[i]-T(m);
(*f)->ep[i]->ep[m]->fac_exp[l] = T(m);
}
delete [] bcoeff;
suml->deleteOneNode();
sum2->delete0neNode() ;
ep[i]->deleteAllNodes() ;
for (m=0; m<newbranches; m++)
flag += (*f)->ep[i]->ep[m]->mxpand((*f)->ep[i]->ep+m);
}
}
return flag;
}
else // для некоммутативной алгебры
{
int i, flag=0, flagl;
(*f) = new Product<T>(branches, ep, fac_exp, next_var);
assert(*f != NULL);
for (i=0; i<branches; i++)
{
if (ep[i]->type() == 'S' fcfc ep[i]->branches > 1
fcfc fac_exp[i] > T(l)
kk double(fac_exp[i]) == int(double(fac_exp[i])))
■C
int m, newbranches = int(fac_exp[i]);
flag++;
(*f)->ep[i] = new Product<T>(newbranches);
assert((*f)->ep[i] !=NULL);
(*f)->fac_exp[i] = T(l);
for(m=0;m<newbranches;m++)ep[i]->copy((*f)->ep[i]->ep+m);
ep[i]->deleteAHNodes();
do{
10.14. Класс Symbolic
5£
flagl = 0;
flagl = (*f)->ep[i]->dxpand((*f)->ep+i);
} while (flagl);
}
}
return flag;
}
}
// дистрибутивный закон
template <class T> int Product<T>::dxpand(Symbol<T> **f)
{
int i, flag=0, k, m;
for (i=0; i<(*f)->branches; i++)
flag += (*f)->ep[i]->dxpand((*f)->ep+i) ;
// ищем индекс члена, к которому можно применить дистриб. закон
for (i=0; i<branches; i++)
if (ep[i]->type() == >S' fcfc ep[i]->branches > 1) break;
// проверяем для fac_exp[]==l или если мы используем раскрытие
// скобок бинома/мультинома
if (i < branches && fac_exp[i] == T(l))
{
flag++;
*f = new Sum<T>(ep[i]->branches,0); assert(*f != NULL);
for (k=0; k < ep[i]->branches; k++)
{
(*f)->ep[k] = new Product<T>(branches);
assert((*f)->ep[k] != NULL);
(*f)->fac_exp[k] = ep[i]->fac_exp[k];
for (m=0; m<i; m++)
{
ep[m] -> copy((*f)->ep[k]->ep+m);
(*f)->ep[k]->fac_exp[m] = fac_exp[m];
(*f)->ep[k]->next_var[m] = next_var[m];
}
// не назначаем fac_exp[], потому что инициализировано
//1 конструктором
// Он устанавливается в 1, из-за дистрибутивного закона
ep[i]->ep[k] -> copy((*f)->ep[k]->ep+i);
(*f)->ep[k]->next_var[i] = ep[i]->next_var[k];
for (m=i+l; m<branches; m++)
594
Глава 10. Листинги программ
■с
ep[m]->copy((*f)->ep[k]->ep+m);
(*f)->ep[k]->fac_exp[m] = fac_exp[m];
(*f)->ep[k]->next_var[m] = next_var[m];
>
>
deleteAHNodes() ;
>
return flag;
>
// дифференцирование
template <class T>
void Product<T>::diff(SymboKT> **s,Symbol<T> *t) const
i
int i, j, k, newbranches = branches + 1;
*s = new Sum<T>(branches,0); assert(*s != NULL);
// d/dx(al a2 a3 ... aN) = dal/dx * a2 a3...aN
+ al * da2/dx * a3...aN +
for (i=0; i<branches; i++)
i
if (fac_exp[i] == T(l))
(*s)->ep[i] = new Product<T>(branches);
assert((*s)->ep[i] !=NULL);
for (j=0; j<i; j++)
i
(*s)->ep[i]->fac_exp[j] = fac_exp[j];
(*s)->ep[i]->next_var[j] = next_var[j];
ep[j]->copy((*s)->ep[i]->ep+j);
>
(*s)->ep[i]->fac_exp[i] = fac_exp[i];
ep[i]->diff((*s)->ep[i]->ep+i,t);
if ((*s)->ep[i]->ep[i]->type() == 'V II
(*s)->ep[i]->ep[i]->type() == 'N')
(*s)->ep[i]->next_var[i] = 1;
else (*s)->ep[i]->next_var[i] = 0;
for (j=i+l; j<branches; j++)
(*s)->ep[i]->fac_exp[j] = fac_exp[j];
(*s)->ep[i]->next_var[j] = next_var[j];
10.14. Класс Symbolic 591
ep[j]->copy((*s)->ep[i]->ep+j);
>
}
else // fac_exp[i] != T(l)
i
(*s)->ep[i] = new Product<T>(newbranches) ;
assert((*s)->ep[i] !=NULL);
for (j=0; j<=i; j++)
i
(*s)->ep[i]->fac_exp[j] = fac_exp[j];
(*s)->ep[i]->next_var[j] = next_var[j];
ep[j]->copy((*s)->ep[i]->ep+j);
>
for (k=i+l, j=i+2; j<newbranches; j++, k++)
(*s)->ep[i]->fac_exp[j] = fac_exp[k];
(*s)->ep[i]->next_var[j] = next_var[k];
ep[k]->copy((*s)->ep[i]->ep+j);
>
// d/dx (u~n) = n u~(n-l) du/dx
(*s)->ep[i]->fac_exp[i] = fac_exp[i] - T(l);
(*s)->fac_exp[i] *= fac_exp[i];
ep[i]->diff((*s)->ep[i]->ep+i+l, t);
}
>
>
// интегрирование
template <class T> void Product<T>::integrate(Symbol<T> **s,
Symbol<T> *t) const
{
if(branches == 1)
<
if(ep[0] == t)
{
if(fac_exp[0] !=T(-D) // Int (х-п,х)=х"(п+1)/(п+1)
{
*s = new Sim<T>(l,0); assert(*s != NULL);
(*s)->ep[0] = new Product<T>A); assert((*s)->ep[0] != NULL);
t->copy((*s)->ep[0]->ep);
(*s)->fac_exp[0]=T(l)/(fac_exp[0]+T(l));
(*s)->ep[0]->fac_exp[0] = (fac_exp[0]+T(D);
(*s)->ep[0]->next_var[0]=0;
}
Та*
596
Глава 10. Листинги программ
else // Int(x~(-l),x)=ln(x);
i
*s = new Sum<T>('1',char О); assert(*s != NULL);
(*s)->ep[0] = new Sum<T>(l,0); assert((*s)->ep[0] != NULL)■
t->copy((*s)->ep[0]->ep);
>
>
else if (implicit_depend(ep,t.branches))
{
*s = new Sum<T>('i',char()); assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
>
else
{
*s = new Product<T>B) ; assert(*s != NULL);
copy((*s)->ep);
t->copy((*s)->ep+l);
>
>
else // ветвей>1
{
int i,j,const ant _f ound=-1;
//выбор константы в соответствии с интегрированием
if(Comm == 1)
for(i=0;(i<branches)&&(constant_found<0);i++)
{
if ((ep[i]->type()=='S')ll(ep[i]->type()=='B,)ll
(ep[i]->type()=='V'))
if( ! ((Smn<T>*)ep[i])->isdepend(*((Sum<T>*)t))) constant.found=i;
>
else
{
if (ep[i]->type() == 'N') constant_found=i;
else
if (!implicit_depend(ep[i]->ep,t,ep[i]->branches
+(ep[i]->branches==0)))
constant_found=i;
>
>
if (constant_found<0)
{
*s = new Sum<T>('i',char()); assert(*s != NULL);
10.14. Класс Symbolic
i
copy((*s)->ep);
t->copy((*s)->ep+l) ;
>
else
{
*s = new Product<T>(branches); assert(*s != NULL);
Product<T> *p=new Product<T>(branches-l); assertCp != NULL);
ep[constant.!ound]->copy((*s)->ep);
for(i=0,j=0;i<branches;i++)
if (i!=constant_found)
{
p->fac_exp[j]=fac_exp[i] ;
p->next_var[j]=next_var[i] ;
ep[i]->copy(p->ep+(j++));
>
p->integrate((*s)->ep+l,t);
delete p;
}
>
>
// упрощение специальных функций
template <class T> int Product<T>::funcSimp(int &i, int &j)
{
switch (ep[i]->type())
{
case 'e':
Symbol<T> *ptr;
ptr = ep[i]->ep[0];
ep[i]->ep[0] = new Sum<T>B,0); assert(ep[i] != NULL);
ep[i]->ep[0]->fac_exp[0] = fac_exp[i];
ep[i]->ep[0]->fac_exp[l] = fac_exp[j];
ep[i]->ep[0]->ep[0] = ptr;
ep[j]->ep[0]->copy(ep[i]->ep[0]->ep+l) ;
fac_exp[i] = fac_exp[j] = T(l);
ep[i]->ep[0]->Shrink();
remove(j); j=i;
return 1;
default: return 0;
>
}
// удаление их из произведений
template <class T> int Product<T>::shrink_l()
598
Глава 10. Листинги программ
int i, flag=0;
for (i=0; i<branches; i++)
i
if (ep[i] == oneO)
i
if (branches > 1)
i
remove(i); i—;
flag++;
>
// (branches==l), помещаем l~(n) = 1
else if (fac_exp[i] != T(D) fac_exp[i] = T(l);
>
>
for (i=0; i<branches; i++) flag += ep[i]->shrink_l();
return flag;
>
// удаляем лишние узлы Product: передвигаем с нижнего уровня
// последовательности уровней узлов Product на верхний уровень
template <class T> int Product<T>::shrink_2()
i
int i, j, k, n, flag=0;
SymboKT> **ept;
T *fac_expt;
int *next_vart;
for (i=0; i<branches; i++)
if (ep[i]->type() == >P')
i
n = branches - 1 + ep[i]->branches;
ept = new Symbol<T>*[n]; assert(ept != NULL);
fac_expt = new T[n]; assert(fac_expt != NULL);
next_vart = new int[n]; assert(next_vart != NULL);
// копируем узлы, которые не охвачены
for (k=0; k<i; k++)
ept [k] = ep [k] ;
fac_expt[k] = fac_exp[k];
next_vart[k] = next_var[k];
>
// переносим узел Product на следующий уровень вверх
for (j=i, k=0; k < ep[i]->branches; k++, j++)
10.14. Класс Symbolic
■с
ept[j] = ep[i]->ep[k] ;
fac_expt[j] = ep[i]->fac_exp[k] * fac_exp[i];
next_vart[j] = ep[i]->next_var[k];
>
// копируем узлы, которые не охвачены
for (k=i+l; k<branches; k++, j++)
i
ept[j] = ep[k] ;
fac_expt[j] = fac_exp[k];
next_vart[j] = next_var[k];
>
branches = n;
ep[i]->deleteOneNode();
delete [] ep; delete [] fac_exp; delete [] next_var;
ep = ept;
fac_exp = fac_expt;
next_var = next_vart;
flag++;
>
}
for (i=0; Kbranches; i++) flag += ep[i]->shrink_2();
return flag;
}
// удаляем нулевые произведения
template <class T> int Product<T>::shrink_3(Symbol<T> **f)
{
int i, flag=0;
for (i=0; i<branches; i++)
if (ep[i] == zero())
i
*f = zero();
deleteAllNodesQ ;
return 1;
>
for (i=0; i<branches; i++) flag += ep[i]->shrink_3(ep+i);
return flag;
}
// суммируем Number (xHn='N') из Sums
template <class T> int Product<T>::shrink_4()
{
int i, flag=0;
600
Глава 10. Листинги программ
for (i=0; Kbranches; i++) flag += ep[i]->shrink_4();
return flag;
>
// удаляем Sims только из одного слагаемого
template <class T> int Product<T>::shrink_5()
i
int i, flag=0;
SymboKT> *ptr;
for (i=0; Kbranches; i++)
if (ep[i]->type() == 'S' &&
ep[i]->branches == 1 &&
ep[i]->fac_exp[0] == T(l))
{
ptr = ep[i]->ep[0] ;
next_var[i] = ep[i]->next_var[0];
ep[i]->deleteOneNode();
ep[i] = ptr;
flag++;
>
for (i=0; Kbranches; i++) flag += ep[i]->shrink_5();
return flag;
>
// объявляем коммутативный/некоммутативный оператор *
template <class T> void Commutative(T, int flag)
i
if (flag) Product<T>::Comm = 1;
else Product<T>::Comm = 0;
>
#endif
Глава 11
Параллельная
виртуальная машина
и абстрактные типы данных
Параллельная виртуальная машина (Parallel Virtual Machine —PVM) [16]
является программной системой, которая позволяет использовать
гетерогенную сеть компьютеров под управлением Unix в качестве одного большого
компьютера с параллельной архитектурой. Таким способом можно решать
большие вычислительные задачи путем использования суммарной мощности
многих компьютеров. Приложения, написанные на языках Fortran 77, С или C++,
могут быть распараллелены на совокупность процессов, работающих на общей
распределенной памяти компьютеров и взаимодействующих между собой с
помощью передачи сообщений. Посылая и получая сообщения, составные части
приложения кооперируются для решения задачи в параллельном режиме.
Система PVM формируется из двух частей. Первая часть называется
демоном, который присутствует на всех компьютерах, образующих виртуальную
машину. Вторая часть системы является библиотекой программ интерфейса
PVM. Эта библиотека содержит программы языка С для обмена сообщениями,
порождения процессов, диспетчер задач и модификации виртуальной машины.
Все базовые типы данных в С и строки (массивы символов) могут быть
упакованы для пересылки и распакованы. Данные кодируются либо в двоичное
представление объектов данных (например, целые числа, числа с плавающей
запятой) типа XDR или во внутренний формат микропроцессора; PVM может
содержать данные в XDR формате или во внутреннем формате; XDR
означает external Data Representation (внешнее представление данных). Это —
стандартный формат кодирования данных в Интернете (по существу целых
и чисел с плавающей запятой в формате IEEE). PVM преобразует данные в
формат XDR для обеспечения связи между машинами с разными внутренними
форматами данных.
Типичные приложения Р VM — это ситуации, когда изучаемые задачи могут
быть распараллелены или может быть применен метод «разделяй и властвуй».
Например, рассмотрим логистическое отображение
xt+i=4xt(l-xt), f = 0,1,2,...,
с заданным xq (начальное значение). Отображение может быть разбито на два
отображения, задаваемых соотношениями
xt+2 = 16a;t(l - xt)(l - 4xt)(l - xt)),
xt+ъ = 16a;t+1(l - a;t+i)(l - 4a;t+i)(l - xt+l)).
602 Глава 11. Параллельная виртуальная машина и абстрактные типы
Два ряда независимы и могут быть вычислены раздельно. Запускаются л
дачи, чтобы вычислить оба ряда. После этого ряды объединяются Так *-■> За~
a i „ ' КАк оба
отображения идентичны (за исключением начальных значений в их индек \
могут быть порождены два идентичных подчиненных процесса. ''
Ниже мы показываем, как PVM может быть использована вместе
страктными типами данных (ADT). В следующей программе мы вычит
сумму двух рациональных чисел А и В, где
. 41152 123234 40600126678
^ =218107' В=333445 ИТ°ГДа Л + В=72726688бТ5-
Программа состоит из двух частей. Взаимодействие основной и подчиненной
программ таково:
• Основная программа определяет величины А и В, упаковывает числа и
затем посылает их в подчиненной программе по сети. Подчиненная
программа производит вычисление А + В и возвращает результат вычисления
в основную программу.
• Подчиненная программа, которая находится на удаленной машине,
сначала принимает пакет, посланный основной программой, распаковывает его
выполняет сложение и, наконец, возвращает результат вычисления обратно
в основную программу.
Отметим, что библиотечные программы PVM не поддерживают
упаковку абстрактных типов данных. Поэтому мы должны иметь наши собственные
функции упаковки и распаковки:
• pvm_pkvlong() упаковывает данные типа Very long.
• pvn_upkvlong () распаковывает данные типа Verylong.
• pvm_pkrat() упаковывает данные типа Rational.
• pvm_upkrat О распаковывает данные типа Rational.
// master.ее (Главная программа)
// в этом примере мы показываем, как отправить данные абстрактных
// типов. Отметим, что упаковываются и пересылаются только
// внутренние данные, необходимые для воспроизведения объекта
#include <iostream.h>
#include <pvm3.h>
#include "Verylong.h"
#include "Rational.h"
// Мы написали наши собственные версии функций упаковки PVM
// Упаковка и распаковка для Verylong
void pvm_pkvlong(const Verylong *1)
char *s = (char*)(*1); //преобразование типа: Verylong -> cn
int length = strlen(s);
pvm_pkint(&length,l,l);
pvm_pkstr(s);
>
Глава 11. Параллельная виртуальная машина и абстрактные типы данных
void pvm_upkvlong(Verylong *1)
{
int length;
pvm_upkint(&length,l,l) ;
char *s = new char[length+l];
pvm_upkstr(s);
Verylong v(s);
*1 = v;
>
// Упаковка и распаковка для Rational
void pvm_pkrat(const Rational<Verylong> *r)
{
Verylong v = r->num();
pvm_pkvlong(&v);
v = r->den();
pvm_pkvlong(&v);
>
void pvm_upkrat(Rational<Verylong> *r)
{
Verylong num, den;
pvm_upkvlong(&num);
pvm_upkvlong(&den);
Rational<Verylong> a(num,den);
*r = a;
>
void mainQ
{
II Включаем задачу в PVM запрашивая ID задачи
int tid = pvm_mytid();
// Открываем два рациональных числа:
RationaKVerylong> A(1152",18107"), В(23234",33445"
// Запускаем подчиненную задачу
int slaveid;
int error = pvm_spawn("slave", (char**)NULL,
PvmTaskDefault, "", 1, feslaveid);
if (error > 0) // если error < 0, подчиненная задача
// не может быть запущена
{
// Отправляем два рациональных числа
pvm_initsend(PvmDataDefault);
604 Глава 11. Параллельная виртуальная машина и абстрактные типы данных
pvm_pkrat(&A);
pvm_pkrat(&B);
pvm_send(slaveid, 1) ;
// Получаем ответ
pvm_recv(slaveid, 2);
Rational<Verylong> С;
pvm_upkrat(&C);
cout « А « " + " « В « " = " « С « endl;
}
// Корректно удаляем текущую задачу из PVM
pvm_exit();
}
// slave.ее (Подчиненная задача)
#include <iostream.h>
#include <pvm3.h>
#include "Verylong.h"
#include "Rational.h"
// Мы написали наши собственные версии функций упаковки PVM
// Упаковка и распаковка для Verylong
void pvm_pkvlong(const Verylong *1)
{
char *s = (char*)(*1);// преобразование типа: Verylong -> char*
int length = strlen(s);
pvm_pkint(ftlength,1,1);
pvm_pkstr(s);
}
void pvm_upkvlong(Verylong *1)
{
int length;
pvm_upkint(ftlength,1,1);
char *s = new char[length+1];
pvm_upkstr(s);
Verylong v(s);
*1 = v;
}
// Упаковка и распаковка для Rational
void pvm_pkrat(const Rational<Verylong> *r)
{
Verylong v = r->num();
Глава 11. Параллельная виртуальная машина и абстрактные типы данных 605
pvm_pkvlong(&v) ;
v = r->den();
pvm_pkvlong(&v);
}
void pvm_upkrat(Rational<Verylong> *r)
{
Verylong пшп, den;
pvm_upkvlong(&num);
pvm_upkvlong(&den);
Rational<Verylong> a(num,den);
*r = a;
}
void main()
{
// Запускаем задачу в PVM запрашивая ID родителя
int ptid = pvm_parent();
// Получаем два рациональных числа
Rational<Verylong> А, В;
pvm_recv(ptid,l);
pvm_upkrat(&A); pvm_upkrat(&B);
// Производим вычисления
Rational<Verylong> С = А + В;
// Отправляем ответ обратно
pvm_initsend(PvmDataDefault);
pvm_pkrat(&C);
pvm_send(ptid, 2);
// Задача успешно завершена
pvm_exit();
}
На выходе программы имеем
41152/218107 + 123234/333445 = 40600126678/72726688615.
Отметим, что в функции упаковки pvm_pkvlong() оператор
char *s = (char*)(*l);
преобразовывает число типа Verylong во встроенный тип данных char*. Этот
шаг необходим, поскольку PVM поддерживает только упаковку встроенных
типов данных. Оператор преобразования был реализован в классе Verylong
(см. гл. 6).
Глава. 12
Техника обработки ошибок
включение является ошибкой, которая происходит во время исполнения
Типичным примером является деление на нуль. Поэтому было бы хорошей
[деей разбить программу на отдельные подсистемы, которые или выполняют-
я успешно или терпят неудачу. В этом случае локальная проверка ошибок
. программе позволит обеспечить надежность всей системы. Следовательно
.есьма важно идентифицировать возможные источники ошибок. Они могут
1ыть вызваны самим программистом. Сюда же относится и обнаружение вну-
ренних логических ошибок, таких, как, например, ошибочные утверждения.
1ногда специальное внимание должно быть обращено на условия выполне-
[ия функции. Например, попытка получить седьмой символ трехеимвольной
троки должна вызывать ошибку. Однако некоторые возникающие проблемы
ie являются логическими ошибками, а являются отказами при попытке полу-
[ить некоторый ресурс во время исполнения. Это такие ошибки, как нехватка
[амяти, неудача записи из-за переполнения диска и т. п.
Устойчивая к ошибкам система обычно имеет иерархическую структуру,
а каждом уровне которой исправляется максимально возможное количество
шибок. Тем не менее некоторые ошибки не могут быть обработаны локально.
Таким образом, нам нужен глобальный механизм связи с ошибками, который
юг бы производить следующее:
• Выдавать состояние ошибки, т. е. возвращать разные значения функции
или метода класса для представления различных исходов выполнения
функции.
• Обрабатывать исключительные ситуации.
» языке С мы можем использовать функцию void assert (int test) для пр»
ерки условий и возможного аварийного завершения. Эта функция является
акросом, который распространяется на оператор if; если test получает зна-
ение нуль, то assert выдает сообщение в поток stderr и аварийно завершает
рограмму. Используя подсистему обработки исключительных ситуаций языка
'++, мы можем структурировать и контролировать способы обработки ошй-
эк времени исполнения. Обработка исключительной ситуации C++ строится
а трех ключевых словах: try, catch и throw.
12.2. Обработка исключительной ситуации
607
12.1. Состояние ошибки
Можно реализовать состояния ошибок в виде переменных класса.
Естественно, каждый объект имеет флаг ошибки (переменная статуса ошибки). Если
ошибка происходит, то переменная статуса ошибки устанавливается в значение,
указывающее тип ошибки. Например, библиотека <iostream.h> C++ имеет
целочисленную переменную статуса state. Разные биты этой переменной
указывают разные статусы ошибки. Статус ошибки может быть опрошен через
методы класса bad(), eof (), fail О. На их основе пользователь может
затем реализовать свой собственный механизм обработки ошибок.
12.2. Обработка исключительной ситуации
Более совершенный метод управления обнаружением и обработкой ошибок
состоит в обработке исключительной ситуации. Это — нелокальный механизм,
который обеспечивает средства передачи ошибок между классами/функциями
и программами, которые используют эти классы/функции. Он также
позволяет программистам писать свой код, не беспокоясь об ошибках при каждом
вызове функции.
В механизме обработки исключительной ситуации в C++ функция, которая
обнаруживает появление ошибки, порождает исключение, используя ключевое
слово throw. Функция-обработчик исключения затем перехватывает и
обрабатывает это исключение. Функция, которая порождает исключение, должна
находиться внутри try-блока. Обработчики, которые объявляются с
использованием ключевого слова catch, располагаются после try-блока.
В качестве примера рассмотрим следующий класс Rational вместе с
программой main():
// except.срр
#include <iostream.h>
«include "MString.h"
class Rational
-c
private:
long int num, den;
public:
Rational(const long int = 0, const long int = 1);
};
class Check_Error
{
private:
String reason;
public:
08
Глава 12. Техника обработки ошибок
Check_Error(const String &s) : reason(s) {}
String diagnosticO const { return reason; }
};
Rational::Rational(const long int N, const long int D)
: num(N), den(D)
{
if (D==0) throw Check_Error("Деление на нуль!");
}
int main()
{
try
{
long int a, b;
a = 5; b = 0;
Rational Rl(a,b);
}
catch(Check_Error diag)
{
cerr << "Внутренняя ошибка: " « diag.diagnosticO << endl;
return 1;
}
return 0;
}
езультат
нутренняя ошибка: Деление на нуль !
Конструктор класса Rational проверяет, является ли знаменатель числа
улем. Он создает исключительную ситуацию при появлении этой ошибки,
озданный функцией, инициирующей исключение, объект в этом случае со-
эржит диагностическое сообщение
Деление на нуль!
ласе Check_Error обрабатывает все исключения, которые могли произойти
пределах программы. Он содержит в области private (закрытые данные)
временную, которая хранит строку, содержащую причину исключения. Метод
LagnosticO печатает диагностическое сообщение об аварии. Он используется
функции catch программы.
Отметим, что в функции mainO вся программа располагается в try-блоке.
епосредственно после блока располагается функция catch, которая является
актическим обработчиком исключительной ситуации. Функция catch опре-
;ляет тип исключения, которое она обрабатывает.
Глава 13
Программа Gnuplot
и язык PostScript
Gnuplot — командно-управляемая интерактивная программа вычерчивания
функций. Она может использоваться для вычерчивания стандартных или
определяемых пользователем математических функций, заданных в двух- и
трехмерном пространстве. Исходное программное обеспечение было
разработано Томасом Вильямсом (Thomas Williams) и Колином Келли (Colin Kelley).
Программа доступна на многих платформах, таких, как Unix, Atari, VMS,
MS-DOS и OS/2. Она удовлетворяет многие потребности сегодняшних ученых
для графического представления данных.
В Gnuplot доступен широкий диапазон команд и функций, что позволяет
оперировать как с кривыми (двумерными), так и с поверхностями
(трехмерными). Поверхности могут вычерчиваться как сетка, расположенная в
трехмерном координатном пространстве, или как графики контуров в х — у плоскости.
Для двумерных графиков стиль графика включает линии, точки и полосы
ошибок и т. д. Графы могут помечаться произвольными метками и стрелками,
метками осей, названием, датой и временем, кодами.
Команды plot и splot — две первичные команды в Gnuplot. Команда plot
используется для вычерчивания двумерных функций и данных, тогда как
splot чертит трехмерные поверхности и данные. Например,
gnuplot> plot cos(x)
чертит простую кривую косинуса, и
gnuplot> splot х*х*у
чертит трехмерную поверхность. Чтобы начертить файл данных с именем
result.dat, мы набираем
gnuplot> plot 'result.txt'
Gnuplot имеет интерактивную помощь, которая может выводиться командой
help со следующим синтаксисом:
gnuplot> help < topic >
где <topic> ссылается на конкретную тему в контексте. По поводу других
команд gnuplot мы отсылаем к руководству пользователя [59].
PostScript был создан и развивается в Adobe Systems Incorporated.
В основном это язык описания страниц, который разработан специально для
610
Глава 13. Программа Gnuplot и язык PostScript
того, чтобы обеспечить аппаратно-независимое описание выводимого из к
пьютерной системы документа на принтер. Он является также языком п
граммирования общего назначении, который содержит большое разнообра
графических операций. Он поддерживает типы данных, такие, как вещее™ 6
ные числа, логический, массивы и строки. Кроме того, он содержит перем
ные и допускает построение более сложных процедур и функций, комбинип
различными операторами.
PostScript включен в программу интерпретатора, которая обычно 3anv
кается на независимом устройстве, например лазерном принтере. Программ
интерпретатора переводит операции PostScript и данные в коды устройства и
генерирует графику, рисуемую на листе. Одна из сильных сторон PostScript —
это то, что он не зависит от устройства вывода, будь то наборщик или
издательское устройство высокой плотности печати.
Файл PostScript должен начинаться с символа У,!. Это магическое число
является признаком PostScript файла. PostScript является постфиксным
языком, где операторы следуют за своими аргументам. Например, оператор
х у moveto
перемещает текущий указатель в координаты (х,у). В общих чертах синтаксис
данной команды таков:
argi arg2 ■ ■ ■ argnn operator
Следующая программа PostScript рисует треугольник и размещает текстовое
сообщение "SymbbolicC++" под углом 50° к горизонтали.
*/.! PostScript
70 140 moveto
300 620 lineto
630 140 lineto
closepath
stroke
/Palatino-Roman findfont
65 scalefont
setfont
140 148 movato
50 rotate
(SymbolicC++) show
showpage
7. "Магическое число" PostScipt'a
У, установка начальной точки
У. добавить отрезок
У, добавить отрезок
У, замкнуть фигуру
У, нарисовать траекторию
У, выбрать требуемый шрифт
У. установка размера шрифта
У. выбрать текущий шрифт
У, установить текущую точку
У, поворот системы координат
У, указать текст сообщения
У, вывести лист на экран
Другие команды языка PostScript можно найти в [21], [33]. Предположим
теперь, что мы хотим наглядно изобразить эволюцию во времени логистич
ского отображения
xt+i =4xt(l-xt), t = 0,1,2,... , ж0е[0,1]
с х0 = 1/3. Для начала мы сгенерируем набор данных (в файле timeev.da ji
используя следующую программу:
Глава 13. Программа Gnuplot и язык PostScript
611
// logis.cpp
#include <fstream.h> // для ofstream, close()
#include "Verylong.h"
«include "Rational.h"
void main()
{
ofstream dataCtimeev.dat");
RationaKVerylong> cl("l"), c2("),
x0("l","); // начальное значение хО = 1/3
data « 0 « " " « double(xO) « endl;
for (int i=l; i<=10; i++)
{
xO = c2*x0*(cl - xO);
data « i « " " « double(xO) « endl;
}
data.closeO ;
}
Эволюция во времени отображения timeev. dat теперь может быть изображена
с помощью Gnuplot. В среде Gnuplot мы набираем следующую команду:
gnuplot> plot [0:10] 'timeev.dat'
Эта команда вычерчивает первые одиннадцать точек эволюции во времени.
Мы можем создать постскриптовский файл, используя команды:
gnoplot> set term postscript default
gnuplot> set output "timeev.ps"
gnuplot> plot 'timeev.dat'
Постскриптовский файл timeev.ps можно послать на принтер, используя
команду:
copy timeev.ps lptl
в средах MS-DOS, OS/2, Windows NT и
lp timeev.ps
для систем Unix.
Список литературы
1. Ammeraal Leen, STL for C++ Programmers, John Wiley, Chichester A997).
2. Anderson J. R., Corbett A. T. and Reiser B. J., Essential LISP, Addison-Wesley A987'\
3. Aslaksen H., Multiple-valued Complex Functions and Computer Algebra SIGSAlM
Bull., 30, 12-20 A996). ' M
4. Ayres F., Modern Algebra, Schaum's Outline Series, McGraw-Hill, New York A965)
5. Barnsley M.F., Fractals Everywhere, 2nd ed., Academic Press Professional Boston
A993).
6. Berry J. Т., The Waite Group's C++ Programming, 2nd ed., SAMS, Carmel, Indiana
A992).
7. Birtwistle G.M., Dahl O.J., Myhrhaug B. and Nygaard K., SIMULA BEGIN, Stu-
dentlitteratur, Sweden, Auerbach, Philadelphia A973).
8. Brackx F., Computer Algebra with LISP and REDUCE: An Introduction to Computer-
aided Pure Mathematics, Kluwer Academic, Boston A991).
9. BuddT.A., Classic Data Structures in C++, Addison-Wesley A994).
10. Char B.W., First Leaves — A Tutorial Introduction to MAPLE V, Springer-Verlag,
New York A991).
11. Dautcourt G., Jann K. P., Riemer E. and Riemer M., Astronomische Nachrichten, 102,
1 A981).
12. [12] Davenport J. H., Siret Y. and Tournier E., Computer Algebra: Systems and
Algorithms for Algebraic Computation, 2 ed., Academic Press, London A993). [Имеется
перевод с 1-го издания: Давенпорт Дж., Сирэ И., Турнье Э. Компьютерная
алгебра. — М.: Мир, 1991.]
13. Ellis M.A. and Stroustrup В., The Annotated C++ Reference Manual, Addison-
Wesley. A990). [Имеется перевод: Эллис М. Строуструп Б. Справочное
руководство по языку программирования С+-1- с комментариями. — М.: Мир, 1992.]
14. Epstein R. L. and Carnielli W. A., Computability, Wadsworth & Brooks/Cole, Pacific
Grove, California A989).
15. Froberg C. E., Numerical Mathematics, Theory and Computer Applications, Benjamm-
Cummings, Menlo Park A985).
16. Geist A., Beguelin A., Dongarra J., Jiang Weicheng, Manchek R. and Sunderam V.,
PVM: Parallel Virtual Machine, A User's Guide and Tutorial for Networked Parallel
Computing, MIT Press, Cambridge, MA A994).
17. Gray P.D. and Mohamed R., Smalltalk-80: A Practical Introduction, Pitman, London
A990).
L8. Hearn A., REDUCE User's Manual, Version 3.5, RAND publication CP78 (Rev.
10/93).
L9. Hehl F. W., Winkelmann V. and Meyer H., REDUCE, 2 ed., Springer-Verlag A993).
!0. Hekmatpour S., An Introduction to LISP and Symbol Manipulation, Prentice Hall,
New York A988).
!1. Holzgang D. A., Understanding PostScript Programming, second edition, SYBEX Inc.,
Alameda A987).
!2. Howson A. G., A Handbook of Terms used in Algebra and Analysis, Cambridge
University Press, Cambridge A972).
!3. Jamsa K., Success with C++, Jamsa Press, Las Vegas A994). [Имеется русский
перевод: Джамса К. Учимся программировать на языке С+-К — М.: Мир, 1997, 19 '
2001.1
Список литературы
613
24. Jenks R. D. and Sutor R. S., Axiom: The Scientific Computation System, Springer-
Verlag, New York A992).
25. Kim W., Introduction to Object-Oriented Databases, MIT Press, Cambridge, MA
A990).
26. Knuth D.E., Fundamental Algorithms, vol. 1 of The Art of Computer Programming,
Addison-Wesley, Reading, MA A968). [Имеется перевод: Кнут Д., Искусство
программирования для ЭВМ: в 3 т.; т. 1: Основные алгоритмы. — М.: Мир, 1976.]
27. Knuth D.E., Seminumerical Algorithms, 2nd ed., vol. 2 of The Art of Computer
Programming, Addison-Wesley, Reading, MA A981). [Имеется перевод: см. [26]; т. 2:
Получисленные алгоритмы. — М.: Мир, 1977; М.: «Вильяме», 2000.]
28. Knuth D. Е., Sorting and Searching, vol. 3 of The Art of Computer Programming,
Addison-Wesley, Reading, MA A973). [Имеется перевод: см. [26]; т. 3: Сортировка
и поиск. —М.: Мир, 1978; М.: «Вильяме», 2000.]
29. Lafore R., Object-Oriented Programming in TURBO C++, Waite Group Press, Mill
Valley A991).
30. Lang S., Linear Algebra, Addison-Wesley, Reading, MA A968).
31. Lippman S.B., C++ Primer, 2nd ed., Addison-Wesley, Reading, MA A991).
32. McCarthy J. et al., LISP 1.5 Programmer's Manual, 2 ed., MIT Press A965).
33. McGilton H. and Campione M., PostScript by Example, Addison-Wesley A992).
34. MacCalhim M. A. H. and Wright F. J., Algebraic Computing with Reduce, Clarendon
Press, Oxford A991).
35. Mevel A. and Gueguen Т., Smalltalk-80, Macmillan Education, London A987).
36. Meyer В., Object-Oriented Software Construction, Prentice-Hall International A988).
37. Meyers S., Effective C++, Addison-Wesley, Reading, MA A992).
38. Norvig P., Paradigms of Artificial Intelligence Programming: Case Studies in Common
Lisp, Morgan Kaufman, San Mateo A991).
39. Oevel W., Postel F., Rtischer G., and Wehmeier S., Das MuPAD Tutorium, SciFace
Software, Paderborn A998).
40. Press W. H., Teukolsky S. A., Vetterling W. T. and Flannery B. P., Numerical Recipes
in C, The Art of Scientific Computing, Cambridge University Press A992).
41. Rayna G., REDUCE: Software for Algebraic Computation, Springer-Verlag, New York
A987).
42. Reiser M., The Oberon System: user guide and programmer's manual, Addison-Wesley
A991).
43. Reiser M. and Wirth N., Programming in Oberon, Addison-Wesley A992).
44. RischR. H., Transactions of the American Mathematical Society, 139, 167-189 A969).
45. Smith M. A., Object-Oriented Software in C++, Chapman and Hall, London A993).
46. Steeb W. H. and Euler N., Nonlinear Evolution Equations and Painleve Test, World
Scientific, Singapore A988).
47. Steeb W. H. and Lewien D., Algorithms and Computation with Reduce, Bl-Wissen-
schaftsverlag, Mannheim A992).
48. Steeb W. H., Lewien D. and Boine-Frankenhein O., Object-Oriented Programming in
Science with C++, Bl-Wissenschaftsverlag, Mannheim A993).
49. Steeb W.H., Quantum Mechanics using Computer Algebra, World Scientific A994).
50. Steeb W. H. and Euler N., Continuous Symmetries, Lie Algebras and Differential
Equations, World-Scientific, Singapore A996).
51. Steeb W. H., Problems and Solutions in Theoretical and Mathematical Physics, World
Scientific, Singapore A996).
52. Steeb W. H., The Nonlinear Workbook: Chaos, Fractals, Cellular Automata, Neural
Networks, Genetic Algorithms, Fussy Logic with C++, Java, SymbolicC++ and Reduce
Programs, World Scientific, Singapore A999).
53. Stephani H., General Relativity, Cambridge University Press, Cambridge A985).
4
Список литературы
Stoutemeyer D.R., Notices of the American Mathematical Society, Volume Яя
pp. 778-785 A991). й'
Stroustrup В., The C+ + Programming Language, 2nd ed., Addison-Wesley Readin
MA A991). ' g'
Tennent R. D., Principles of Programming Languages, Prentice-Hall, Englewood Cliff
A981).
Thomas P. and Weedon R., Object-Oriented Programming in Eiffel, Addison-Wesle
Wokingham A985). y'
Touretzky D. S., Common Lisp: A Gentle Introduction to Symbolic Computation Ben-
jamin-Cummings, Redwood City A990).
Williams Т., Kelley C. et al., Gnuplo: An Interactive Plotting Program
http://www.cs.dartmouth.edu/gnuplot-info.html.
Winston P.H., Lisp, Addison-Wesley A989).
Wirth N. and Gutknecht J., Project Oberon: The Design of an Operating System and
Compilers, Addison-Wesley A992).
Wolfram S., Mathematica: A System for Doing Mathematics by Computer, 2nd ed
Addison-Wesley A992).
Yan S.S., Collofello J.S. and MacGergor Т., Ripple Effect Analysis of Software
Maintenance, In Proc. COMPSAC'78, pp. 60-65 A978).
Предметный указатель
car
cdr
cond
Eiffel
Java
map
mapсan
null
private
protected
public
Smalltalk
this
384
384
386
107
89
179
386
385
127
127
127
110
123
1-спиновые матрицы (Spin-1 matrices) 71
S-выражение (S-expression) 383
Абсолютное значение (absolute value) 28
Абстрактный (abstract)
базовый класс (base class) 261
класс (class) 82
тип данных (data type) 77, 105
Агрегация (aggregation) 77
Алгебра ассоциативная (associative
algebra) 32, 51
кватернионов (quaternion) 37
Ли (Lie) 33, 52
линейная (linear) 32
Алгоритм деления (division algorithm) 21
Ригиа (Risch) 51
Антикоммутационное соотношение (an-
ticommutation relation) 144
Аппроксимация Паде (Pade approxi-
mant) 361
Аргумент (argument) 31
Арифметизация (arithmetization) 302
Atom (atom) 383
Атрибуты экземпляра по умолчанию
(default instance attributes)
78
Аттрактор (attractor) 316
Базис Гребнера (Grobner basis) 33, 69
Библиотека стандартных шаблонов
(Standard Template Library)
162
Бином Ньютона (binomial theorem) 271
Биномиальное разложение (binomial
expansion) 271
Битовый вектор (bit vector) 240
Блуждающее решение (ghost solution)
314
Вакуумное состояние (vacuum state) 144
Векторное подпространство (vector sub-
space) 32
произведение (vector product) 33, 219
внешнее (exterior) 55, 339
внутреннее (dot, inner) 219
Вещественная часть (real part) 30
Взаимная рекурсия (mutual recursion)
190
Взаимно простые числа (relatively
primes) 22
Выражение (expression) 259
Главный аргумент (principal argument)
31
идеал (ideal) 19
Глобальная перегрузка (global
overloading) 140
переменная (variables) 112
Гомоморфизм (homomorphism) 33
Грассманова алгебра (Grassmann
algebra) 56
Грассманово произведение (Grassmann
product) 55
Двойственное пространство (dual space)
36
Дедекиндово сечение (Dedekind cut) 27
Делитель нуля (zero divisor) 18, 20
общий (common) 20
наибольший общий (greatest common)
20
Деструктор (destructor) 130
Десятичное представление (decimal
representation) 26
Диагональная последовательность
(diagonal sequence) 361
Диаграмма Арганда (Argand diagram) 30
316
Предметный указатель
Дифференциал (differential) 46
Цомен вычисления (domain of
computation) 68
Цробь (fraction) 25
Закон трихотомии (trichotomy law) 26
Закрытый (private) 87
Знаменатель (denominator) 25
Идеал (ideal) 18, 69
идентификатор (identifier) 108
инверсия (inverse) 30
инкапсуляция (encapsulation) 77
инструкция (instruction) 108
интерфейс (interface) 262
иррациональные числа (irrational
numbers) 29
итератор (iterator) 246
итерация (iteration) 244
Каноническая форма (canonical form)
283
Каст (cast) 134
Категории (categories) 68
Кватернион (quaternion) 211
Класс (class) 78
атрибуты (attributes) 78
вспомогательный (facilitator) 245
вычетов (residue) 23
гомогенный (homogeneous) 217
методы (methods) 113
обертывающий (wrapper) 161
переменная (variable) 111
производный (derived) 118
сравнений (congruence) 23
шаблонный (template) 251
Сод ошибки (error status) 607
Сольцо (ring) 17
главных идеалов (principal ideal) 19
коммутативное (commutative) 17
с делением (division ring) 18
Соммутативная алгебра (commutative
algebra) 33
Соммутативный закон (law) 277
Соммутатор (commutator) 71
конструктор копирования (copy
constructor) 130, 131
контейнерный класс (container class) 148
контракт (contract) 75, 108
коэффициент (coefficient) 39
сжатия (contractivity factor) 315
кривизна скалярная (curvature scalar) 63
Кропекера дельта (Kronecker delta) 3fi
331
произведение (product) 54, 223
Куча (heap) 119
Лестница дьявола (devil's staircase) 317
Линейная независимость (linearly
independent) 33
общая линейная группа (general linear
group) 36
оператор (operator) 269
отображение (mapping) 33
преобразование (transformation) 33
пространство (space) 32
специальная линейная группа (special
linear group) 36
Лисп общий (Common Lisp) 382
переносимый стандартный (Portable
Standard) 382
Литерал знаковый (character) 113
символьный (symbol) 114
строковый (string) 114
числовой (numeric) 113
Ложное решение (spurious solution) 314
Массив литералов (array literals) 114
Математическое ожидание (expectation
value) 66
Матрица (matrix) 34
единичная (identity, unit) 36
невырожденная (non-singular) 36
необратимая (non-invertible) 36
нулевая (zero) 36
обратимая (singular) 36
обратная (inverse) 36
симметричная (symmetric) 36
спиновая Паули (Pauli spin) 38
тождественная (identity) 36
транспонированная (transpose) 36
Матричное представление (matrix
representation) 38
Метакласс (metaclass) 112
Метод Горовица (Horowitz method) 49
Леверьера (Leverrier's) 57
логарифмических слагаемых
(logarithmic part) 50
наивный (naive) 48
Ньютона—Рафсона (Newton—Raph-
son scheme) 379
Пикара (Picard's) 72, 366
Эрмита (Hermite's) 48
Предметный указатель
617
Метрика Хаусдорфа (Hausdorff metric)
315
Мнимая часть (imaginary part) 30
Многочлен (polynomial) 39
Лагерра (LaguerreJ 329
Лежандра (Legandre) 323, 326
нулевой (zero) 40
характеристический (characteristic)
18, 57
Чебышева (Chebyshev) 334
Эрмита (Hermite) 331
Множество Кантора (Cantor set) 316
Манделъброта (Mandelbrot set) 321
Модель Лоренца (Lorenz model) 340
Модуль (modulus) 21
Модульность разработки (modular
design) 115
Надкласс (superclass) 112
Наследование (inheritance) 80
выборочное (selective) 81
множественное (multiple) 80
повторное (repeated) 81
Небесная механика (nambu mechanics)
346
Неподвижная точка (fixed point) 311
Неравенство треугольника (triangle
inequality) 31
Норма Гильберта—Шмидта (Hilbert—
Schmidt norm) 227
Нормализация вектора (vector
normalization) 221
Нумерация Гёделя (Goedel numbering)
302
Оберон (Oberon) 115
Область целостности (integral domain)
18
Обобщение (generalization) 80
Обобщенная специализация (generalized-
specialization) 80
Обобщенный (generic) 108
параметр (parameter) 109
тип данных (data type) 108
Обработка исключительных ситуаций
(exception handling) 607
списков (list processing) 382
Обратимый элемент (invertible element)
18
Общие атрибуты экземпляра (shared
instance attributes) 78
переменные (pool variables) 112
Объект (object) 87
Объектно-ориентированное
проектирование (object-oriented design)
290
Объектно-ориентированный язык
(object-oriented language) 116
Оператор Бозе (Bose operator) 141
взятия по модулю (modulo operator)
198
Гильберта — Шмидта (Hilbert —
Schmidt) 357
преобразования типа (type conversion)
204
присваивания (assignment) 132
создания (creation) 141
уничтожения (annihilation) 141
Ферми (Fermi) 144
целого деления (division) 198
Определитель (determinant) 57
Основная теорема арифметики
(fundamental theorem of arithmetic)
302
Остаток (remainder) 21
Открытый (public) 87
Отображение липшицево (Lipschitzian
map) 311
логистическое (logistic) 309
Отправитель (sender) 79
Очередь (queue) 172
Параллельная виртуальная машина
(Parallel Virtual Machine) 601
Параметризованный тип (parametrized
type) 148
Первый интеграл (first integral) 341
Перегрузка методов класса (member
overloading) 140
функции (function overloading) 84
Передача сообщений (message passing)
79
Переменные экземпляра (instance
variables) 111
Подалгебра (sub-algebra) 33
Поддомен (subdomain) 68
Подкласс (subclass) 112
Подкольцо (subring) 17
Подполе (subfield) 18
Показатель Ляпунова (Ljapunov
exponent) 319
Поле (field) 18
векторное Киллинга (Killing vector)
354
i
Предметный указатель
летрического тензора (metric tensor)
62
летрическое тензорное поле Гёделя
(Goedel metric tensor) 355
упорядоченное (ordered) 28
лиморфизм (polymorphism) 83
лучатель (receiver) 79
лярные координаты (polar
coordinates) 31
следовательность Kouiu (Cauchy
sequence) 311
стфиксный язык (postfix language)
610
авило дифференцирования
(differentiation rule) 214
дифференцирования произведения
(product) 269
Лейбница (Leibniz) 323
Попиталя (L'Hospital's) 13
юрождающие правила (production
rules) 259
шожения (интегралов) (sum) 47
:уммирования (sum convention) 63
едставление Лакса (Lax
representation) 358
ефиксная нотация (prefix notation)
388
оизведение векторное (cross product)
33, 219
иакопеременное (alternating) 55
жалярное (scalar) 219
оизводная (derivative) 46
lu (Lie) 354
эизводящая функция (generating
function) 324
эстранство имен (namespaces) 164
тдопеременная (pseudo-variable) 114
)дельная компиляция (separate
compilation) 115
ложение в ряд Тейлора (Taylor series
expansion) 348
мерность (dimension) 34
мещение динамических переменных
(allocating dynamic variables)
122
именование (dereferencing) 121
г матрицы (rank of matrix) 13
ширенный тип (expanded type) 109
ширяемость разработки (extensible
design) 116
урсия (recursion) 183
Решетка Тода (Toda lattice) 358
Решето Эратпосфена (sieve of Eratos
thenes) 24
Ряд кумулянтов (cumulant expansion)
Самоименующий указатель
(self-referential pointer) 123
Сверхдлинные целые числа (very long
integers) 194
Свободная переменная (indeterminate"!
39 ;
Свойство архимедово (Archimedean
property) 30
плотности (density) 30
полноты (completeness) 30
Связанный список (linked list) 243
Связывание динамическое (dynamic
binding) 83
позднее (late) 83
статическое (static) 83
Сегмент данных (data segment) 119
Семантическая нагрузка (value
semantics) 217
Сжимающее отображение (contracting
mapping) 311
Символ Леей— Чивита (Levi—Cevita
symbol) 346
Символы Кристоффеля (Christoffel
symbols) 63
Символьный файл (symbol file) 115
Скаляр (scalar) 32
Скрытие данных (data hiding) 76
информации (information) 77
След (матрицы) (trace) 36
Случай (case) 385
Специализация (specialization) 80
Спецификатор доступа (access specifier)
127
Список (list) 383
Ссылка (reference) 125
Стек (stack) 170
Степень (degree) 39
Столбец (column) 34
Строка (row) 34
Сумма прямая (direct sum) 37
Сферические гармоники (spherical
harmonics) 344
Схема центральных разностей (central
difference scheme) 314
Счетное множество (denumerable set)
299
Предметный указатель
619
Тензор кривизны (curvature tensor) 231
римановой кривизны (Riemann
curvature) 63
Риччи (Ricci) 63
Тензорное произведение (tensor product)
53
Теорема о сжимающем отображении
(mapping theorem) 311
Техника рядов Ли (Lie series technique)
370
Тождество Якоби (Jacobi identity) 33
Точечная пара (dotted pair) 382
Точная верхняя грань (supremum) 29
нижняя грань (infimum) 29
Указатель (pointer) 119
повисший (dangling) 131
Упрощение (simplification) 282
Уравнение вариационное (variational
equation) 319
движения Гамильтона (Hamilton of
motion) 358
дифференциальное Лежандра (Legan-
dre differential) 323
Риккати (Riccati differential) 348
линеаризованное (linearized) 319
синуса Гордона (sine-Gordon) 62
характеристическое (characteristic) 66
Условная обработка (conditional
processing) 385
Утечка кучи (losing heap memory) 124
Формула Родриго (Rodrigue's formula)
323
Функция Бесселя (Bessel function) 348
виртуальная (virtual) 159, 261
Гамильтона (Hamilton) 358
гиперболические итерационные
функции (hyperbolic iterated
functions) 315
пробная (trial) 66
чистая виртуальная (pure virtual) 262
Хаотическая орбита (chaotic orbit) 309
Характеристика кольца (characteristic of
a ring) 18
Характеристический многочлен
(characteristic polynomial) 18, 57
Цепное правило (chain-rule) 269
Частное (quotient) 21
Черный ящик (black box) 87
Число вещественное (real number) 27
Геделя (Goedel) 302
простое (prime) 20
комплексно сопряженное (complex
conjugate) 30
Мерсенна (Mersenne) 299
рациональное (rational) 25
целое (integer) 19
Числитель (numerator) 25
Член, терм (term) 39
Шаблон (template) 148
Экземпляр (instance) 78
методы (methods) 113
переменные (variables) 111
Элемент длины (line element) 62
Ядро (kernel) 116
Якобиан (Jacobian) 344
2
Оглавление
10.2. Класс Rational 426
10.3. Класс Complex 431
10.4. Класс Quaternion 436
10.5. Класс Derive 440
10.6. Класс Vector 442
10.7. Класс Matrix 452
10.8. Класс Array 465
10.9. Класс String 483
10.10. Класс BitVec 487
10.11. Класс MList 492
10.12. Класс Polynomial 499
10.13. Класс Set 523
10.14. Класс Symbolic 529
ава 11. Параллельная виртуальная машина и абстрактные типы данных 601
ава 12. Техника обработки ошибок 606
12.1. Состояние ошибки 607
12.2. Обработка исключительной ситуации 607
ава 13. Программа Gnuplot и язык PostScript 609
исок литературы 612
юдметный указатель 615
Учебное издание
Киат Ши Тан, Вилли-Ханс Стиб, Йорик Харди
СИМВОЛЬНЫЙ C++:
Введение в компьютерную алгебру с использованием
объектно-ориентированного программирования
Зав. редакцией академик В. И. Арнольд
Зам. зав. редакцией А. С. Попов
Ведущий редактор Г. М. Кобельков
Художник Э. А. Смирнов
Художественный редактор Н. В. Зотова
Технический редактор О. Г. Лапко
Оригинал-макет подготовлен С. А. Янковой в пакете WF^K.2e
с использованием кириллических шрифтов семейства LH
Лицензия ЛР № 010174 от 20.05.97 г.
Подписано к печати 23.03.2001 г. Формат 70 х 100/16.
Бумага офсетная. Гарнитура Computer Modern. Печать офсетная.
Объем 19,50 бум. л. Усл.-печ. л. 50,70. Уч.-изд. л. 44,82.
Изд. № 1/9718. Тираж 5 000 экз. Заказ № 1548
Издательство «Мир»
Министерства РФ по делам печати,
телерадиовещания и средств массовых коммуникаций
107996, Москва, 1-й Рижский пер., 2.
Диапозитивы изготовлены в издательстве «Мир»
Отпечатано с готовых диапозитивов в ППП «Типография Наука»
121099, Москва, Шубинский пер., 6
Лучшие книги мира—
в издательстве «мир»
Готовится к печати:
ДеммельДж. ВЫЧИСЛИТЕЛЬНАЯ
ЛИНЕЙНАЯ АЛГЕБРА. Теория и приложения: Пер.
с англ. — 32 л., ил.
Книга известного американского математика-
вычислителя представляет собой учебник
повышенного уровня по вычислительным
методам линейной алгебры, выделяющийся среди
изданий этого типа рядом особенностей:
— знакомит с современными методами решения линейных
систем, задач наименьших квадратов, вычисления собственных
значений и сингулярных разложений;
— прививает читателям навыки эффективного решения
реальных задач путем выбора наилучших алгоритмов;
— содержит упражнения и задачи, облегчающие усвоение
материала;
— изложение сопровождается многочисленными ссылками на
Интернет-ресурсы по реализации конкретных алгоритмов;
— материал книги самодостаточен, от читателя требуется только
знакомство с основами линейной алгебры.
Основное содержание книги:
• Введение
• Решение линейных уравнений
• Линейные задачи наименьших квадратов
• Несимметричные задачи на собственные значения
• Симметричные задачи и сингулярное разложение
• Итерационные методы для линейных систем
• Итерационные методы для задач на собственные значения
Для студентов и аспирантов вузов и университетов, изучающих
вычислительную математику и ее приложения.
К. Ш. Тан, В.-Х. Стиб, Й. Харди
Символьный C++
введение
в компьютерную алгебру
с использованием
объектно-ориентированного
программирования